Demonstrate NoutOf policies may be set for SBE (#316)

Signed-off-by: Gaurav Giri <girigaurav@gmail.com>
This commit is contained in:
Gaurav Giri 2020-09-02 23:38:07 +05:30 committed by GitHub
parent 8df641f10d
commit f9679ed9c9
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
3 changed files with 100 additions and 39 deletions

View file

@ -4,6 +4,8 @@ Transactions that are submitted to Hyperledger Fabric networks need to be endors
Each chaincode that is deployed to a channel has an endorsement policy that governs the assets managed by the chaincode smart contracts. However, you can override the chaincode level endorsement policy to create an endorsement policy for a specific key, either on the public channel ledger or in a private collection. State-based endorsement policies, also known as key-level endorsement policies, allow channel members use different endorsement policies for assets that are managed by the same smart contract. For more information about endorsement policies and state-based endorsement, visit the [Endorsement Policies](https://hyperledger-fabric.readthedocs.io/en/master/endorsement-policies.html) topic in the Fabric documentation.
The implementation provided by State Based interface creates a policy which requires signatures from all the Org principals added, and hence is equivalent to an AND policy. For other advanced State Based policy implementations which are not supported by State Based interface directly like OR or NOutOf policies, please refer to method implementations setAssetStateBasedEndorsementWithNOutOfPolicy(), which can be used as an alternative for setAssetStateBasedEndorsement() inside asset-transfer-sbe smart contracts.
## About the Sample
The state-based endorsement (SBE) asset transfer sample demonstrates how to use key-level endorsement policies to ensure that an asset only needs to be endorsed by an asset owner. In the course of the tutorial, you will use the smart contract to complete the following transfer scenario:

View file

@ -15,51 +15,55 @@ import com.owlike.genson.annotation.JsonProperty;
public final class Asset {
@Property()
private final String id;
private final String ID;
@Property()
private int value;
private int Value;
@Property()
private String owner;
private String Owner;
@Property()
private String ownerOrg;
private String OwnerOrg;
public String getId() {
return id;
@JsonProperty("ID")
public String getID() {
return ID;
}
@JsonProperty("Value")
public int getValue() {
return value;
return Value;
}
public void setValue(final int Value) {
this.Value = Value;
}
@JsonProperty("Owner")
public String getOwner() {
return owner;
return Owner;
}
public void setOwner(final String Owner) {
this.Owner = Owner;
}
@JsonProperty("OwnerOrg")
public String getOwnerOrg() {
return ownerOrg;
return OwnerOrg;
}
public void setValue(final int value) {
this.value = value;
public void setOwnerOrg(final String OwnerOrg) {
this.OwnerOrg = OwnerOrg;
}
public void setOwner(final String owner) {
this.owner = owner;
}
public void setOwnerOrg(final String ownerOrg) {
this.ownerOrg = ownerOrg;
}
public Asset(@JsonProperty("id") final String id, @JsonProperty("value") final int value,
@JsonProperty("owner") final String owner, @JsonProperty("ownerOrg") final String ownerOrg) {
this.id = id;
this.value = value;
this.owner = owner;
this.ownerOrg = ownerOrg;
public Asset(@JsonProperty("ID") final String ID, @JsonProperty("Value") final int Value,
@JsonProperty("Owner") final String Owner, @JsonProperty("OwnerOrg") final String OwnerOrg) {
this.ID = ID;
this.Value = Value;
this.Owner = Owner;
this.OwnerOrg = OwnerOrg;
}
@Override
@ -73,7 +77,7 @@ public final class Asset {
Asset asset = (Asset) o;
return getValue() == asset.getValue()
&&
getId().equals(asset.getId())
getID().equals(asset.getID())
&&
getOwner().equals(asset.getOwner())
&&
@ -82,12 +86,12 @@ public final class Asset {
@Override
public int hashCode() {
return Objects.hash(getId(), getValue(), getOwner(), getOwnerOrg());
return Objects.hash(getID(), getValue(), getOwner(), getOwnerOrg());
}
@Override
public String toString() {
return "Asset{" + "id='" + id + '\'' + ", value=" + value + ", owner='"
+ owner + '\'' + ", ownerOrg='" + ownerOrg + '\'' + '}';
return "Asset{" + "ID='" + ID + '\'' + ", Value=" + Value + ", Owner='"
+ Owner + '\'' + ", OwnerOrg='" + OwnerOrg + '\'' + '}';
}
}

View file

@ -11,6 +11,8 @@ import org.hyperledger.fabric.contract.annotation.Default;
import org.hyperledger.fabric.contract.annotation.Info;
import org.hyperledger.fabric.contract.annotation.License;
import org.hyperledger.fabric.contract.annotation.Transaction;
import org.hyperledger.fabric.protos.common.MspPrincipal;
import org.hyperledger.fabric.protos.common.Policies;
import org.hyperledger.fabric.shim.ChaincodeException;
import org.hyperledger.fabric.shim.ChaincodeStub;
import org.hyperledger.fabric.shim.ext.sbe.StateBasedEndorsement;
@ -18,6 +20,11 @@ import org.hyperledger.fabric.shim.ext.sbe.impl.StateBasedEndorsementFactory;
import com.owlike.genson.Genson;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Comparator;
import java.util.List;
@Contract(
name = "sbe",
info = @Info(
@ -40,6 +47,7 @@ public final class AssetContract implements ContractInterface {
/**
* Creates a new asset.
* Sets the endorsement policy of the assetId Key, such that current owner Org Peer is required to endorse future updates.
* Optionally, set the endorsement policy of the assetId Key, such that any 1(N) out of the Org's specified can endorse future updates.
*
* @param ctx the transaction context
* @param assetId the id of the new asset
@ -62,8 +70,12 @@ public final class AssetContract implements ContractInterface {
String assetJSON = genson.serialize(asset);
stub.putStringState(assetId, assetJSON);
// Set the endorsement policy of the assetId Key, such that current owner Org Peer is required to endorse future updates
setAssetStateBasedEndorsement(ctx, assetId, new String[]{ownerOrg});
// Optionally, set the endorsement policy of the assetId Key, such that any 1(N) out of the Org's specified can endorse future updates
// setAssetStateBasedEndorsementWithNOutOfPolicy(ctx, assetId, 1, new String[]{"Org1MSP", "Org2MSP"});
return asset;
}
@ -75,7 +87,7 @@ public final class AssetContract implements ContractInterface {
* @return the asset found on the ledger if there was one
*/
@Transaction(intent = Transaction.TYPE.EVALUATE)
public Asset ReadAsset(final Context ctx, final String assetId) {
public String ReadAsset(final Context ctx, final String assetId) {
ChaincodeStub stub = ctx.getStub();
String assetJSON = stub.getStringState(assetId);
@ -85,8 +97,7 @@ public final class AssetContract implements ContractInterface {
throw new ChaincodeException(errorMessage, AssetTransferErrors.ASSET_NOT_FOUND.toString());
}
Asset asset = genson.deserialize(assetJSON, Asset.class);
return asset;
return assetJSON;
}
/**
@ -102,7 +113,8 @@ public final class AssetContract implements ContractInterface {
public Asset UpdateAsset(final Context ctx, final String assetId, final int newValue) {
ChaincodeStub stub = ctx.getStub();
Asset asset = ReadAsset(ctx, assetId);
String assetString = ReadAsset(ctx, assetId);
Asset asset = genson.deserialize(assetString, Asset.class);
asset.setValue(newValue);
String updatedAssetJSON = genson.serialize(asset);
stub.putStringState(assetId, updatedAssetJSON);
@ -145,7 +157,8 @@ public final class AssetContract implements ContractInterface {
public Asset TransferAsset(final Context ctx, final String assetId, final String newOwner, final String newOwnerOrg) {
ChaincodeStub stub = ctx.getStub();
Asset asset = ReadAsset(ctx, assetId);
String assetString = ReadAsset(ctx, assetId);
Asset asset = genson.deserialize(assetString, Asset.class);
asset.setOwner(newOwner);
asset.setOwnerOrg(newOwnerOrg);
String updatedAssetJSON = genson.serialize(asset);
@ -170,6 +183,16 @@ public final class AssetContract implements ContractInterface {
return (assetJSON != null && !assetJSON.isEmpty());
}
/**
* Retrieves the client's OrgId (MSPID)
*
* @param ctx the transaction context
* @return String value of the Org MSPID
*/
private static String getClientOrgId(final Context ctx) {
return ctx.getClientIdentity().getMSPID();
}
/**
* Sets an endorsement policy to the assetId Key.
* Enforces that the owner Org Peers must endorse future update transactions for the specified assetId Key.
@ -178,19 +201,51 @@ public final class AssetContract implements ContractInterface {
* @param assetId the id of the asset
* @param ownerOrgs the list of Owner Org MSPID's
*/
private void setAssetStateBasedEndorsement(final Context ctx, final String assetId, final String[] ownerOrgs) {
private static void setAssetStateBasedEndorsement(final Context ctx, final String assetId, final String[] ownerOrgs) {
StateBasedEndorsement stateBasedEndorsement = StateBasedEndorsementFactory.getInstance().newStateBasedEndorsement(null);
stateBasedEndorsement.addOrgs(StateBasedEndorsement.RoleType.RoleTypeMember, ownerOrgs);
ctx.getStub().setStateValidationParameter(assetId, stateBasedEndorsement.policy());
}
/**
* Retrieves the client's OrgId (MSPID)
* Sets an endorsement policy to the assetId Key.
* Enforces that any N out of the Org's Peers specified must endorse future update transactions for the specified assetId Key.
*
* @param ctx the transaction context
* @return String value of the Org MSPID
* @param assetId the id of the asset
* @param nOrgs the number of N Orgs to endorse out of the list of Orgs provided
* @param ownerOrgs the list of Owner Org MSPID's
*/
private String getClientOrgId(final Context ctx) {
return ctx.getClientIdentity().getMSPID();
private static void setAssetStateBasedEndorsementWithNOutOfPolicy(final Context ctx, final String assetId, final int nOrgs, final String[] ownerOrgs) {
ctx.getStub().setStateValidationParameter(assetId, policy(nOrgs, Arrays.asList(ownerOrgs)));
}
/**
* Create the policy such that it requires any N signature's from all of the principals provided
*
* @param nOrgs the number of N Org signature's to endorse out of the list of Orgs provided
* @param mspids the list of Owner Org MSPID's
*/
private static byte[] policy(final int nOrgs, final List<String> mspids) {
mspids.sort(Comparator.naturalOrder());
final List<MspPrincipal.MSPPrincipal> principals = new ArrayList<>();
final List<Policies.SignaturePolicy> signPolicy = new ArrayList<>();
for (int i = 0; i < mspids.size(); i++) {
final String mspid = mspids.get(i);
principals.add(MspPrincipal.MSPPrincipal.newBuilder().setPrincipalClassification(MspPrincipal.MSPPrincipal.Classification.ROLE)
.setPrincipal(MspPrincipal.MSPRole.newBuilder().setMspIdentifier(mspid).setRole(MspPrincipal.MSPRole.MSPRoleType.MEMBER).build().toByteString()).build());
signPolicy.add(signedBy(i));
}
// Create the policy such that it requires any N signature's from all of the principals provided
return Policies.SignaturePolicyEnvelope.newBuilder().setVersion(0).setRule(nOutOf(nOrgs, signPolicy))
.addAllIdentities(principals).build().toByteArray();
}
private static Policies.SignaturePolicy signedBy(final int index) {
return Policies.SignaturePolicy.newBuilder().setSignedBy(index).build();
}
private static Policies.SignaturePolicy nOutOf(final int n, final List<Policies.SignaturePolicy> policies) {
return Policies.SignaturePolicy.newBuilder().setNOutOf(Policies.SignaturePolicy.NOutOf.newBuilder().setN(n).addAllRules(policies).build()).build();
}
}