mirror of
https://github.com/hyperledger/fabric-samples.git
synced 2026-06-17 15:35:09 +00:00
fabric samples for state-based endorsement for chaincode typescript
Signed-off-by: Gaurav Giri <girigaurav@gmail.com>
This commit is contained in:
parent
55c0b47952
commit
6e965ec55f
8 changed files with 311 additions and 0 deletions
55
asset-transfer-sbe/README.md
Normal file
55
asset-transfer-sbe/README.md
Normal file
|
|
@ -0,0 +1,55 @@
|
||||||
|
# Sample to demonstrate State-Based Endorsements (SBE)
|
||||||
|
|
||||||
|
## Introduction to SBE
|
||||||
|
Fabric allows different ways to set endorsements for transactions on the network. The default method is specified in chaincode definition, which is agreed by channel members and committed to the channel. However, there are cases where it may be necessary for a particular Key (public channel state or private data collection state) to have a different endorsement policy. State-Based endorsement allows endorsement policies to be overridden for the specified Key's. State-Based Endorsements are also known as Key Level Endorsements. To learn more about endorsement policies and State-Based endorsements, visit the [Fabric Endorsement Policies documentation](https://hyperledger-fabric.readthedocs.io/en/master/endorsement-policies.html).
|
||||||
|
|
||||||
|
|
||||||
|
Fabric Shim Method's for State-Based endorsements (below references from fabric-shim, ChaincodeStub for Node):
|
||||||
|
- setStateValidationParameter(key: string, ep: Uint8Array): Promise<void>;
|
||||||
|
- getStateValidationParameter(key: string): Promise<Uint8Array>;
|
||||||
|
- setPrivateDataValidationParameter(collection: string, key: string, ep: Uint8Array): Promise<void>;
|
||||||
|
- getPrivateDataValidationParameter(collection: string, key: string): Promise<Uint8Array>;
|
||||||
|
|
||||||
|
|
||||||
|
## About the SBE Asset Transfer Sample
|
||||||
|
Using this sample we demonstrate State-Based Endorsements (SBE), and set a different endorsement policy for a specified Key, instead of the default chaincode policy. This sample is deployed and demonstrated on the fabric-samples, Test network (2 Org network, Org1 and Org2). The endorsement policy for chaincodes deployed in this network defaults to majority, that is both Org's need to endorse transactions on the network. To bootstrap the Test Network, and deploy the SBE Asset Transfer Sample chaincode, visit the [Test Network documentation](https://hyperledger-fabric.readthedocs.io/en/latest/test_network.html).
|
||||||
|
|
||||||
|
This sample demonstrates modifying the default chaincode policy (requiring endorsements from both Org Peers), and changing it to a less restrictive policy requiring endorsement only from a specific Org Peer for the specified Key. Endorsements can also be modified to be more restrictive for a particular Key in a similar way.
|
||||||
|
|
||||||
|
On Asset creation, State-Based endorsements for the Asset Key is set to either Org1 or Org2 Peer, depending on the client Org creating the Asset. Creation of Asset still needs the default chaincode endorsement policy, and hence will need to be endorsed by both Org's (Org1 & Org2). However, all future updates for the Asset Key will use the modified endorsements as per the State-Based endorsements set during Asset creation.
|
||||||
|
|
||||||
|
Hence transactions like UpdateAsset or TransferAsset will not succeed if endorsed by the non-owner Org Peer.
|
||||||
|
|
||||||
|
Sample invokations to demonstrate State-Based Endorsements (SBE) on test-network, using client identity of Org1
|
||||||
|
* Create Asset (requires Org1 & Org2 Peer to endorse as per the default chaincode endorsement policy, however future updates will need only Org1 Peer to endorse, as the State-Based endorsement policy is set for assetId Key during the createAsset transaction)
|
||||||
|
```bash
|
||||||
|
peer chaincode invoke -o localhost:7050 --ordererTLSHostnameOverride orderer.example.com --tls --cafile ${PWD}/organizations/ordererOrganizations/example.com/orderers/orderer.example.com/msp/tlscacerts/tlsca.example.com-cert.pem -C mychannel -n asset-transfer-sbe --peerAddresses localhost:7051 --tlsRootCertFiles ${PWD}/organizations/peerOrganizations/org1.example.com/peers/peer0.org1.example.com/tls/ca.crt --peerAddresses localhost:9051 --tlsRootCertFiles ${PWD}/organizations/peerOrganizations/org2.example.com/peers/peer0.org2.example.com/tls/ca.crt -c '{"function":"CreateAsset","Args":["asset1","100","Org1User1"]}'
|
||||||
|
```
|
||||||
|
* Read & Verify Asset (Use this to verify changes for UpdateAsset & TransferAsset)
|
||||||
|
```bash
|
||||||
|
peer chaincode query -C mychannel -n asset-transfer-sbe -c '{"Args":["ReadAsset","asset1"]}'
|
||||||
|
```
|
||||||
|
* Update Asset (requires Org1 Peer to endorse as per SBE specified for assetId Key, but since Org2 Peer is endorsing it will result in ENDORSEMENT_POLICY_FAILURE by the peer during endorsement validations for the transaction, check peer logs)
|
||||||
|
```bash
|
||||||
|
peer chaincode invoke -o localhost:7050 --ordererTLSHostnameOverride orderer.example.com --tls --cafile ${PWD}/organizations/ordererOrganizations/example.com/orderers/orderer.example.com/msp/tlscacerts/tlsca.example.com-cert.pem -C mychannel -n asset-transfer-sbe --peerAddresses localhost:9051 --tlsRootCertFiles ${PWD}/organizations/peerOrganizations/org2.example.com/peers/peer0.org2.example.com/tls/ca.crt -c '{"function":"UpdateAsset","Args":["asset1","200"]}'
|
||||||
|
```
|
||||||
|
* Update Asset succeeds (requires Org1 Peer to endorse as per SBE specified for assetId Key, and Org1 Peer is endorsing here)
|
||||||
|
```bash
|
||||||
|
peer chaincode invoke -o localhost:7050 --ordererTLSHostnameOverride orderer.example.com --tls --cafile ${PWD}/organizations/ordererOrganizations/example.com/orderers/orderer.example.com/msp/tlscacerts/tlsca.example.com-cert.pem -C mychannel -n asset-transfer-sbe --peerAddresses localhost:7051 --tlsRootCertFiles ${PWD}/organizations/peerOrganizations/org1.example.com/peers/peer0.org1.example.com/tls/ca.crt -c '{"function":"UpdateAsset","Args":["asset1","200"]}'
|
||||||
|
```
|
||||||
|
* Transfer Asset (requires Org1 Peer to endorse as per SBE specified for assetId Key, but since Org2 Peer is endorsing it will result in ENDORSEMENT_POLICY_FAILURE by the peer during endorsement validations for the transaction, check peer logs)
|
||||||
|
```bash
|
||||||
|
peer chaincode invoke -o localhost:7050 --ordererTLSHostnameOverride orderer.example.com --tls --cafile ${PWD}/organizations/ordererOrganizations/example.com/orderers/orderer.example.com/msp/tlscacerts/tlsca.example.com-cert.pem -C mychannel -n asset-transfer-sbe --peerAddresses localhost:9051 --tlsRootCertFiles ${PWD}/organizations/peerOrganizations/org2.example.com/peers/peer0.org2.example.com/tls/ca.crt -c '{"function":"TransferAsset","Args":["asset1","Org2User1","Org2MSP"]}'
|
||||||
|
```
|
||||||
|
* Transfer Asset succeeds (requires Org1 Peer to endorse as per SBE specified for assetId Key, and Org1 Peer is endorsing here)
|
||||||
|
```bash
|
||||||
|
peer chaincode invoke -o localhost:7050 --ordererTLSHostnameOverride orderer.example.com --tls --cafile ${PWD}/organizations/ordererOrganizations/example.com/orderers/orderer.example.com/msp/tlscacerts/tlsca.example.com-cert.pem -C mychannel -n asset-transfer-sbe --peerAddresses localhost:7051 --tlsRootCertFiles ${PWD}/organizations/peerOrganizations/org1.example.com/peers/peer0.org1.example.com/tls/ca.crt -c '{"function":"TransferAsset","Args":["asset1","Org2User1","Org2MSP"]}'
|
||||||
|
```
|
||||||
|
* Update Asset (now requires Org2 Peer to endorse as per SBE specified for assetId Key, but since Org1 Peer is endorsing it will result in ENDORSEMENT_POLICY_FAILURE by the peer during endorsement validations for the transaction, check peer logs)
|
||||||
|
```bash
|
||||||
|
peer chaincode invoke -o localhost:7050 --ordererTLSHostnameOverride orderer.example.com --tls --cafile ${PWD}/organizations/ordererOrganizations/example.com/orderers/orderer.example.com/msp/tlscacerts/tlsca.example.com-cert.pem -C mychannel -n asset-transfer-sbe --peerAddresses localhost:7051 --tlsRootCertFiles ${PWD}/organizations/peerOrganizations/org1.example.com/peers/peer0.org1.example.com/tls/ca.crt -c '{"function":"UpdateAsset","Args":["asset1","300"]}'
|
||||||
|
```
|
||||||
|
* Update Asset succeeds (requires Org2 Peer to endorse as per SBE specified for assetId Key, and Org2 Peer is endorsing here)
|
||||||
|
```bash
|
||||||
|
peer chaincode invoke -o localhost:7050 --ordererTLSHostnameOverride orderer.example.com --tls --cafile ${PWD}/organizations/ordererOrganizations/example.com/orderers/orderer.example.com/msp/tlscacerts/tlsca.example.com-cert.pem -C mychannel -n asset-transfer-sbe --peerAddresses localhost:9051 --tlsRootCertFiles ${PWD}/organizations/peerOrganizations/org2.example.com/peers/peer0.org2.example.com/tls/ca.crt -c '{"function":"UpdateAsset","Args":["asset1","300"]}'
|
||||||
|
```
|
||||||
20
asset-transfer-sbe/chaincode-typescript/.gitignore
vendored
Normal file
20
asset-transfer-sbe/chaincode-typescript/.gitignore
vendored
Normal file
|
|
@ -0,0 +1,20 @@
|
||||||
|
#
|
||||||
|
# SPDX-License-Identifier: Apache-2.0
|
||||||
|
#
|
||||||
|
|
||||||
|
# Coverage directory used by tools like istanbul
|
||||||
|
coverage
|
||||||
|
|
||||||
|
# Dependency directories
|
||||||
|
node_modules/
|
||||||
|
jspm_packages/
|
||||||
|
package-lock.json
|
||||||
|
|
||||||
|
# Compiled TypeScript files
|
||||||
|
dist
|
||||||
|
|
||||||
|
# Editor Config
|
||||||
|
.editorconfig
|
||||||
|
|
||||||
|
# npm ignore
|
||||||
|
.npmignore
|
||||||
65
asset-transfer-sbe/chaincode-typescript/package.json
Normal file
65
asset-transfer-sbe/chaincode-typescript/package.json
Normal file
|
|
@ -0,0 +1,65 @@
|
||||||
|
{
|
||||||
|
"name": "asset-transfer-sbe",
|
||||||
|
"version": "0.0.1",
|
||||||
|
"description": "Asset Transfer contract, using State Based Endorsement(SBE), implemented in TypeScript",
|
||||||
|
"main": "dist/index.js",
|
||||||
|
"typings": "dist/index.d.ts",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=8",
|
||||||
|
"npm": ">=5"
|
||||||
|
},
|
||||||
|
"scripts": {
|
||||||
|
"lint": "tslint -c tslint.json 'src/**/*.ts'",
|
||||||
|
"pretest": "npm run lint",
|
||||||
|
"test": "nyc mocha -r ts-node/register src/**/*.spec.ts",
|
||||||
|
"start": "fabric-chaincode-node start",
|
||||||
|
"build": "tsc",
|
||||||
|
"build:watch": "tsc -w",
|
||||||
|
"prepublishOnly": "npm run build"
|
||||||
|
},
|
||||||
|
"engineStrict": true,
|
||||||
|
"author": "Hyperledger",
|
||||||
|
"license": "Apache-2.0",
|
||||||
|
"dependencies": {
|
||||||
|
"fabric-contract-api": "^2.0.0",
|
||||||
|
"fabric-shim": "^2.0.0"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"@types/chai": "^4.2.11",
|
||||||
|
"@types/chai-as-promised": "^7.1.2",
|
||||||
|
"@types/mocha": "^7.0.2",
|
||||||
|
"@types/node": "^13.9.3",
|
||||||
|
"@types/sinon": "^7.5.2",
|
||||||
|
"@types/sinon-chai": "^3.2.3",
|
||||||
|
"chai": "^4.2.0",
|
||||||
|
"chai-as-promised": "^7.1.1",
|
||||||
|
"mocha": "^7.1.1",
|
||||||
|
"nyc": "^15.0.0",
|
||||||
|
"sinon": "^9.0.1",
|
||||||
|
"sinon-chai": "^3.5.0",
|
||||||
|
"ts-node": "^8.8.1",
|
||||||
|
"tslint": "^6.1.0",
|
||||||
|
"typescript": "^3.8.3",
|
||||||
|
"winston": "^3.2.1"
|
||||||
|
},
|
||||||
|
"nyc": {
|
||||||
|
"extension": [
|
||||||
|
".ts",
|
||||||
|
".tsx"
|
||||||
|
],
|
||||||
|
"exclude": [
|
||||||
|
"coverage/**",
|
||||||
|
"dist/**"
|
||||||
|
],
|
||||||
|
"reporter": [
|
||||||
|
"text-summary",
|
||||||
|
"html"
|
||||||
|
],
|
||||||
|
"all": true,
|
||||||
|
"check-coverage": true,
|
||||||
|
"statements": 100,
|
||||||
|
"branches": 100,
|
||||||
|
"functions": 100,
|
||||||
|
"lines": 100
|
||||||
|
}
|
||||||
|
}
|
||||||
22
asset-transfer-sbe/chaincode-typescript/src/asset.ts
Normal file
22
asset-transfer-sbe/chaincode-typescript/src/asset.ts
Normal file
|
|
@ -0,0 +1,22 @@
|
||||||
|
/*
|
||||||
|
* SPDX-License-Identifier: Apache-2.0
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { Object, Property } from 'fabric-contract-api';
|
||||||
|
|
||||||
|
@Object()
|
||||||
|
export class Asset {
|
||||||
|
|
||||||
|
@Property()
|
||||||
|
public ID: string;
|
||||||
|
|
||||||
|
@Property()
|
||||||
|
public Value: string;
|
||||||
|
|
||||||
|
@Property()
|
||||||
|
public Owner: string;
|
||||||
|
|
||||||
|
@Property()
|
||||||
|
public OwnerOrg: string;
|
||||||
|
|
||||||
|
}
|
||||||
102
asset-transfer-sbe/chaincode-typescript/src/assetContract.ts
Normal file
102
asset-transfer-sbe/chaincode-typescript/src/assetContract.ts
Normal file
|
|
@ -0,0 +1,102 @@
|
||||||
|
/*
|
||||||
|
* SPDX-License-Identifier: Apache-2.0
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { Context, Contract, Info, Transaction } from 'fabric-contract-api';
|
||||||
|
import { Asset } from './asset';
|
||||||
|
import { KeyEndorsementPolicy } from 'fabric-shim';
|
||||||
|
|
||||||
|
@Info({title: 'AssetContract', description: 'Asset Transfer Smart Contract, using State Based Endorsement(SBE), implemented in TypeScript' })
|
||||||
|
export class AssetContract extends Contract {
|
||||||
|
|
||||||
|
// CreateAsset creates a new asset
|
||||||
|
// CreateAsset sets the endorsement policy of the assetId Key, such that current owner Org Peer is required to endorse future updates
|
||||||
|
@Transaction()
|
||||||
|
public async CreateAsset(ctx: Context, assetId: string, value: string, owner: string): Promise<void> {
|
||||||
|
const exists = await this.AssetExists(ctx, assetId);
|
||||||
|
if (exists) {
|
||||||
|
throw new Error(`The asset ${assetId} already exists`);
|
||||||
|
}
|
||||||
|
const ownerOrg = this.getClientOrgId(ctx);
|
||||||
|
const asset = new Asset();
|
||||||
|
asset.ID = assetId;
|
||||||
|
asset.Value = value;
|
||||||
|
asset.Owner = owner;
|
||||||
|
asset.OwnerOrg = ownerOrg;
|
||||||
|
const buffer = Buffer.from(JSON.stringify(asset));
|
||||||
|
// Create the asset
|
||||||
|
await ctx.stub.putState(assetId, buffer);
|
||||||
|
// Set the endorsement policy of the assetId Key, such that current owner Org Peer is required to endorse future updates
|
||||||
|
await this.setAssetStateBasedEndorsement(ctx, asset.ID, [ownerOrg]);
|
||||||
|
}
|
||||||
|
|
||||||
|
// ReadAsset returns asset with given assetId
|
||||||
|
@Transaction(false)
|
||||||
|
public async ReadAsset(ctx: Context, assetId: string): Promise<string> {
|
||||||
|
const exists = await this.AssetExists(ctx, assetId);
|
||||||
|
if (!exists) {
|
||||||
|
throw new Error(`The asset ${assetId} does not exist`);
|
||||||
|
}
|
||||||
|
// Read the asset
|
||||||
|
const assetJSON = await ctx.stub.getState(assetId);
|
||||||
|
return assetJSON.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
// UpdateAsset updates an existing asset
|
||||||
|
// UpdateAsset needs an endorsement of current owner Org Peer
|
||||||
|
@Transaction()
|
||||||
|
public async UpdateAsset(ctx: Context, assetId: string, newValue: string): Promise<void> {
|
||||||
|
const assetString = await this.ReadAsset(ctx, assetId);
|
||||||
|
const asset = JSON.parse(assetString) as Asset;
|
||||||
|
asset.Value = newValue;
|
||||||
|
const buffer = Buffer.from(JSON.stringify(asset));
|
||||||
|
// Update the asset
|
||||||
|
await ctx.stub.putState(assetId, buffer);
|
||||||
|
}
|
||||||
|
|
||||||
|
// DeleteAsset deletes an given asset
|
||||||
|
// DeleteAsset needs an endorsement of current owner Org Peer
|
||||||
|
@Transaction()
|
||||||
|
public async DeleteAsset(ctx: Context, assetId: string): Promise<void> {
|
||||||
|
const exists = await this.AssetExists(ctx, assetId);
|
||||||
|
if (!exists) {
|
||||||
|
throw new Error(`The asset ${assetId} does not exist`);
|
||||||
|
}
|
||||||
|
// Delete the asset
|
||||||
|
await ctx.stub.deleteState(assetId);
|
||||||
|
}
|
||||||
|
|
||||||
|
// TransferAsset updates the Owner & OwnerOrg field of asset with given assetId, OwnerOrg must be a valid Org MSP Id
|
||||||
|
// TransferAsset needs an endorsement of current owner Org Peer
|
||||||
|
// TransferAsset re-sets the endorsement policy of the assetId Key, such that new owner Org Peer is required to endorse future updates
|
||||||
|
@Transaction()
|
||||||
|
public async TransferAsset(ctx: Context, assetId: string, newOwner: string, newOwnerOrg: string): Promise<void> {
|
||||||
|
const assetString = await this.ReadAsset(ctx, assetId);
|
||||||
|
const asset = JSON.parse(assetString) as Asset;
|
||||||
|
asset.Owner = newOwner;
|
||||||
|
asset.OwnerOrg = newOwnerOrg;
|
||||||
|
// Update the asset
|
||||||
|
await ctx.stub.putState(assetId, Buffer.from(JSON.stringify(asset)));
|
||||||
|
// Re-Set the endorsement policy of the assetId Key, such that a new owner Org Peer is required to endorse future updates
|
||||||
|
await this.setAssetStateBasedEndorsement(ctx, asset.ID, [newOwnerOrg]);
|
||||||
|
}
|
||||||
|
|
||||||
|
// AssetExists returns true when asset with given ID exists
|
||||||
|
public async AssetExists(ctx: Context, assetId: string): Promise<boolean> {
|
||||||
|
const buffer = await ctx.stub.getState(assetId);
|
||||||
|
return (!!buffer && buffer.length > 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
// setAssetStateBasedEndorsement sets an endorsement policy to the assetId Key
|
||||||
|
// setAssetStateBasedEndorsement enforces that the owner Org Peers must endorse future update transactions for the specified assetId Key
|
||||||
|
private async setAssetStateBasedEndorsement(ctx: Context, assetId: string, ownerOrgs: string[]): Promise<void> {
|
||||||
|
let ep = new KeyEndorsementPolicy();
|
||||||
|
ep.addOrgs("MEMBER", ...ownerOrgs);
|
||||||
|
await ctx.stub.setStateValidationParameter(assetId, ep.getPolicy());
|
||||||
|
}
|
||||||
|
|
||||||
|
// getClientOrgId gets the client's OrgId (MSPID)
|
||||||
|
private getClientOrgId(ctx: Context): string {
|
||||||
|
return ctx.clientIdentity.getMSPID();
|
||||||
|
}
|
||||||
|
}
|
||||||
8
asset-transfer-sbe/chaincode-typescript/src/index.ts
Normal file
8
asset-transfer-sbe/chaincode-typescript/src/index.ts
Normal file
|
|
@ -0,0 +1,8 @@
|
||||||
|
/*
|
||||||
|
* SPDX-License-Identifier: Apache-2.0
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { AssetContract } from './assetContract';
|
||||||
|
export { AssetContract } from './assetContract';
|
||||||
|
|
||||||
|
export const contracts: any[] = [ AssetContract ];
|
||||||
18
asset-transfer-sbe/chaincode-typescript/tsconfig.json
Normal file
18
asset-transfer-sbe/chaincode-typescript/tsconfig.json
Normal file
|
|
@ -0,0 +1,18 @@
|
||||||
|
{
|
||||||
|
"compilerOptions": {
|
||||||
|
"outDir": "dist",
|
||||||
|
"target": "es2017",
|
||||||
|
"moduleResolution": "node",
|
||||||
|
"module": "commonjs",
|
||||||
|
"declaration": true,
|
||||||
|
"experimentalDecorators": true,
|
||||||
|
"emitDecoratorMetadata": true,
|
||||||
|
"sourceMap": true
|
||||||
|
},
|
||||||
|
"include": [
|
||||||
|
"./src/**/*"
|
||||||
|
],
|
||||||
|
"exclude": [
|
||||||
|
"./src/**/*.spec.ts"
|
||||||
|
]
|
||||||
|
}
|
||||||
21
asset-transfer-sbe/chaincode-typescript/tslint.json
Normal file
21
asset-transfer-sbe/chaincode-typescript/tslint.json
Normal file
|
|
@ -0,0 +1,21 @@
|
||||||
|
{
|
||||||
|
"defaultSeverity": "error",
|
||||||
|
"extends": [
|
||||||
|
"tslint:recommended"
|
||||||
|
],
|
||||||
|
"jsRules": {},
|
||||||
|
"rules": {
|
||||||
|
"indent": [true, "spaces", 4],
|
||||||
|
"quotemark": [true, "single"],
|
||||||
|
"semicolon": [true, "always"],
|
||||||
|
"no-console": false,
|
||||||
|
"curly": true,
|
||||||
|
"triple-equals": true,
|
||||||
|
"no-string-throw": true,
|
||||||
|
"no-var-keyword": true,
|
||||||
|
"no-trailing-whitespace": true,
|
||||||
|
"object-literal-key-quotes": [true, "as-needed"],
|
||||||
|
"max-line-length": false
|
||||||
|
},
|
||||||
|
"rulesDirectory": []
|
||||||
|
}
|
||||||
Loading…
Reference in a new issue