Merge branch 'master' of github.com:girigaurav/fabric-samples into asset-transfer-sbe-java

This commit is contained in:
Gaurav Giri 2020-08-23 17:37:39 +05:30
commit 7b6b2aba91
31 changed files with 959 additions and 493 deletions

View file

@ -25,7 +25,7 @@ transfer an asset in a more realistic transfer scenario.
| **Smart Contract** | **Description** | **Tutorial** | **Smart contract languages** | **Application languages** |
| -----------|------------------------------|----------|---------|---------|
| [Basic](asset-transfer-basic) | The Basic sample smart contract that allows you to create and transfer an asset by putting data on the ledger and retrieving it. This sample is recommended for new Fabric users. | [Writing your first application](https://hyperledger-fabric.readthedocs.io/en/master/write_first_app.html) | Go, JavaScript, TypeScript, Java | JavaScript, Java |
| [Basic](asset-transfer-basic) | The Basic sample smart contract that allows you to create and transfer an asset by putting data on the ledger and retrieving it. This sample is recommended for new Fabric users. | [Writing your first application](https://hyperledger-fabric.readthedocs.io/en/master/write_first_app.html) | Go, JavaScript, TypeScript, Java | Go, JavaScript, Java |
| [Ledger queries](asset-transfer-ledger-queries) | The ledger queries sample demonstrates range queries and transaction updates using range queries (applicable for both LevelDB and CouchDB state databases), and how to deploy an index with your chaincode to support JSON queries (applicable for CouchDB state database only). | [Using CouchDB](https://hyperledger-fabric.readthedocs.io/en/master/couchdb_tutorial.html) | Go, JavaScript | Java |
| [Private data](asset-transfer-private-data) | This sample demonstrates the use of private data collections, how to manage private data collections with the chaincode lifecycle, and how the private data hash can be used to verify private data on the ledger. It also demonstrates how to control asset updates and transfers using client-based ownership and access control. | [Using Private Data](https://github.com/hyperledger/fabric-samples/tree/master/asset-transfer-private-data/chaincode-go) | Go | JavaScript |
| [State-Based Endorsement](asset-transfer-sbe) | This sample demonstrates how to override the chaincode-level endorsement policy to set endorsement policies at the key-level (data/asset level). | [Using State-based endorsement](https://github.com/hyperledger/fabric-samples/tree/master/asset-transfer-sbe) | TypeScript | **Coming soon** |

View file

@ -7,9 +7,9 @@ SPDX-License-Identifier: Apache-2.0
package main
import (
"errors"
"fmt"
"io/ioutil"
"log"
"os"
"path/filepath"
@ -18,20 +18,22 @@ import (
)
func main() {
fmt.Println("============ application-golang starts ============")
log.Println("============ application-golang starts ============")
err := os.Setenv("DISCOVERY_AS_LOCALHOST", "true")
if err != nil {
log.Fatalf("Error setting DISCOVERY_AS_LOCALHOST environemnt variable: %v", err)
}
os.Setenv("DISCOVERY_AS_LOCALHOST", "true")
wallet, err := gateway.NewFileSystemWallet("wallet")
if err != nil {
fmt.Printf("failed to create wallet: %v\n", err)
os.Exit(1)
log.Fatalf("Failed to create wallet: %v", err)
}
if !wallet.Exists("appUser") {
err = populateWallet(wallet)
if err != nil {
fmt.Printf("failed to populate wallet contents: %v\n", err)
os.Exit(1)
log.Fatalf("Failed to populate wallet contents: %v", err)
}
}
@ -50,57 +52,73 @@ func main() {
gateway.WithIdentity(wallet, "appUser"),
)
if err != nil {
fmt.Printf("failed to connect to gateway: %v\n", err)
os.Exit(1)
log.Fatalf("Failed to connect to gateway: %v", err)
}
defer gw.Close()
network, err := gw.GetNetwork("mychannel")
if err != nil {
fmt.Printf("failed to get network: %v\n", err)
os.Exit(1)
log.Fatalf("Failed to get network: %v", err)
}
contract := network.GetContract("basic")
result, err := contract.EvaluateTransaction("GetAllAssets")
result, err := contract.SubmitTransaction("InitLedger")
if err != nil {
fmt.Printf("failed to evaluate transaction: %v\n", err)
os.Exit(1)
log.Fatalf("failed to evaluate transaction: %v", err)
}
fmt.Println(string(result))
log.Println(string(result))
result, err = contract.SubmitTransaction("CreateAsset", "asset13", "yellow", "Tom", "5", "1300")
result, err = contract.EvaluateTransaction("GetAllAssets")
if err != nil {
fmt.Printf("failed to submit transaction: %v\n", err)
os.Exit(1)
log.Fatalf("Failed to evaluate transaction: %v", err)
}
fmt.Println(string(result))
log.Println(string(result))
result, err = contract.EvaluateTransaction("ReadAsset", "asset4")
result, err = contract.SubmitTransaction("CreateAsset", "asset13", "yellow", "5", "Tom", "1300")
if err != nil {
fmt.Printf("failed to evaluate transaction: %v\n", err)
os.Exit(1)
log.Fatalf("Failed to submit transaction: %v", err)
}
fmt.Println(string(result))
log.Println(string(result))
log.Println("--> Submit Transaction: CreateAsset, creates new asset with ID, color, owner, size, and appraisedValue arguments")
result, err = contract.SubmitTransaction("CreateAsset", "asset13", "yellow", "5", "Tom", "1300")
if err != nil {
log.Fatalf("Failed to evaluate transaction: %v", err)
}
log.Println(string(result))
log.Println("--> Evaluate Transaction: ReadAsset, function returns an asset with a given assetID")
result, err = contract.EvaluateTransaction("ReadAsset", "asset13")
if err != nil {
log.Fatalf("failed to evaluate transaction: %v\n", err)
}
log.Println(string(result))
log.Println("--> Evaluate Transaction: AssetExists, function returns 'true' if an asset with given assetID exist")
result, err = contract.EvaluateTransaction("AssetExists", "asset1")
if err != nil {
log.Fatalf("failed to evaluate transaction: %v\n", err)
}
log.Println(string(result))
log.Println("--> Submit Transaction: TransferAsset asset1, transfer to new owner of Tom")
_, err = contract.SubmitTransaction("TransferAsset", "asset1", "Tom")
if err != nil {
fmt.Printf("Failed to submit transaction: %v\n", err)
os.Exit(1)
log.Fatalf("Failed to submit transaction: %v", err)
}
log.Println("--> Evaluate Transaction: ReadAsset, function returns 'asset1' attributes")
result, err = contract.EvaluateTransaction("ReadAsset", "asset1")
if err != nil {
fmt.Printf("failed to evaluate transaction: %v\n", err)
os.Exit(1)
log.Fatalf("Failed to evaluate transaction: %v", err)
}
fmt.Println(string(result))
fmt.Println("============ application-golang ends ============")
log.Println(string(result))
log.Println("============ application-golang ends ============")
}
func populateWallet(wallet *gateway.Wallet) error {
fmt.Println("============ populate wallet starts ============")
log.Println("============ Populating wallet ============")
credPath := filepath.Join(
"..",
"..",
@ -127,7 +145,7 @@ func populateWallet(wallet *gateway.Wallet) error {
return err
}
if len(files) != 1 {
return errors.New("keystore folder should have contain one file")
return fmt.Errorf("keystore folder should have contain one file")
}
keyPath := filepath.Join(keyDir, files[0].Name())
key, err := ioutil.ReadFile(filepath.Clean(keyPath))
@ -137,10 +155,5 @@ func populateWallet(wallet *gateway.Wallet) error {
identity := gateway.NewX509Identity("Org1MSP", string(cert), string(key))
err = wallet.Put("appUser", identity)
if err != nil {
return err
}
fmt.Println("============ populate wallet ends ============")
return nil
return wallet.Put("appUser", identity)
}

View file

@ -4,8 +4,10 @@ set -euo pipefail
METADIR=$2
# check if the "type" field is set to "external"
# crude way without jq, fragile but good enough for this sample...
if [ "$(grep type "$METADIR/metadata.json" |cut -f4 -d\")" = "external" ]; then
# crude way without jq which is not in the default fabric peer image
TYPE=$(tr -d '\n' < "$METADIR/metadata.json" | awk -F':' '{ for (i = 1; i < NF; i++){ if ($i~/type/) { print $(i+1); break }}}'| cut -d\" -f2)
if [ "$TYPE" = "external" ]; then
exit 0
fi

View file

@ -5,7 +5,8 @@
module.exports = {
env: {
node: true,
mocha: true
mocha: true,
es6: true
},
parserOptions: {
ecmaVersion: 8,

View file

@ -5,6 +5,9 @@
# Coverage directory used by tools like istanbul
coverage
# Report cache used by istanbul
.nyc_output
# Dependency directories
node_modules/
jspm_packages/

View file

@ -31,7 +31,9 @@
"nyc": {
"exclude": [
"coverage/**",
"test/**"
"test/**",
"index.js",
".eslintrc.js"
],
"reporter": [
"text-summary",

View file

@ -0,0 +1,262 @@
'use strict';
const sinon = require('sinon');
const chai = require('chai');
const sinonChai = require('sinon-chai');
const expect = chai.expect;
const { Context } = require('fabric-contract-api');
const { ChaincodeStub } = require('fabric-shim');
const AssetTransfer = require('../lib/assetTransfer.js');
let assert = sinon.assert;
chai.use(sinonChai);
describe('Asset Transfer Basic Tests', () => {
let transactionContext, chaincodeStub, asset;
beforeEach(() => {
transactionContext = new Context();
chaincodeStub = sinon.createStubInstance(ChaincodeStub);
transactionContext.setChaincodeStub(chaincodeStub);
chaincodeStub.putState.callsFake((key, value) => {
if (!chaincodeStub.states) {
chaincodeStub.states = {};
}
chaincodeStub.states[key] = value;
});
chaincodeStub.getState.callsFake(async (key) => {
let ret;
if (chaincodeStub.states) {
ret = chaincodeStub.states[key];
}
return Promise.resolve(ret);
});
chaincodeStub.deleteState.callsFake(async (key) => {
if (chaincodeStub.states) {
delete chaincodeStub.states[key];
}
return Promise.resolve(key);
});
chaincodeStub.getStateByRange.callsFake(async () => {
function* internalGetStateByRange() {
if (chaincodeStub.states) {
// Shallow copy
const copied = Object.assign({}, chaincodeStub.states);
for (let key in copied) {
yield {value: copied[key]};
}
}
}
return Promise.resolve(internalGetStateByRange());
});
asset = {
ID: 'asset1',
Color: 'blue',
Size: 5,
Owner: 'Tomoko',
AppraisedValue: 300,
};
});
describe('Test InitLedger', () => {
it('should return error on InitLedger', async () => {
chaincodeStub.putState.rejects('failed inserting key');
let assetTransfer = new AssetTransfer();
try {
await assetTransfer.InitLedger(transactionContext);
assert.fail('InitLedger should have failed');
} catch (err) {
expect(err.name).to.equal('failed inserting key');
}
});
it('should return success on InitLedger', async () => {
let assetTransfer = new AssetTransfer();
await assetTransfer.InitLedger(transactionContext);
let ret = JSON.parse((await chaincodeStub.getState('asset1')).toString());
expect(ret).to.eql(Object.assign({docType: 'asset'}, asset));
});
});
describe('Test CreateAsset', () => {
it('should return error on CreateAsset', async () => {
chaincodeStub.putState.rejects('failed inserting key');
let assetTransfer = new AssetTransfer();
try {
await assetTransfer.CreateAsset(transactionContext, asset.ID, asset.Color, asset.Size, asset.Owner, asset.AppraisedValue);
assert.fail('CreateAsset should have failed');
} catch(err) {
expect(err.name).to.equal('failed inserting key');
}
});
it('should return success on CreateAsset', async () => {
let assetTransfer = new AssetTransfer();
await assetTransfer.CreateAsset(transactionContext, asset.ID, asset.Color, asset.Size, asset.Owner, asset.AppraisedValue);
let ret = JSON.parse((await chaincodeStub.getState(asset.ID)).toString());
expect(ret).to.eql(asset);
});
});
describe('Test ReadAsset', () => {
it('should return error on ReadAsset', async () => {
let assetTransfer = new AssetTransfer();
await assetTransfer.CreateAsset(transactionContext, asset.ID, asset.Color, asset.Size, asset.Owner, asset.AppraisedValue);
try {
await assetTransfer.ReadAsset(transactionContext, 'asset2');
assert.fail('ReadAsset should have failed');
} catch (err) {
expect(err.message).to.equal('The asset asset2 does not exist');
}
});
it('should return success on ReadAsset', async () => {
let assetTransfer = new AssetTransfer();
await assetTransfer.CreateAsset(transactionContext, asset.ID, asset.Color, asset.Size, asset.Owner, asset.AppraisedValue);
let ret = JSON.parse(await chaincodeStub.getState(asset.ID));
expect(ret).to.eql(asset);
});
});
describe('Test UpdateAsset', () => {
it('should return error on UpdateAsset', async () => {
let assetTransfer = new AssetTransfer();
await assetTransfer.CreateAsset(transactionContext, asset.ID, asset.Color, asset.Size, asset.Owner, asset.AppraisedValue);
try {
await assetTransfer.UpdateAsset(transactionContext, 'asset2', 'orange', 10, 'Me', 500);
assert.fail('UpdateAsset should have failed');
} catch (err) {
expect(err.message).to.equal('The asset asset2 does not exist');
}
});
it('should return success on UpdateAsset', async () => {
let assetTransfer = new AssetTransfer();
await assetTransfer.CreateAsset(transactionContext, asset.ID, asset.Color, asset.Size, asset.Owner, asset.AppraisedValue);
await assetTransfer.UpdateAsset(transactionContext, 'asset1', 'orange', 10, 'Me', 500);
let ret = JSON.parse(await chaincodeStub.getState(asset.ID));
let expected = {
ID: 'asset1',
Color: 'orange',
Size: 10,
Owner: 'Me',
AppraisedValue: 500
};
expect(ret).to.eql(expected);
});
});
describe('Test DeleteAsset', () => {
it('should return error on DeleteAsset', async () => {
let assetTransfer = new AssetTransfer();
await assetTransfer.CreateAsset(transactionContext, asset.ID, asset.Color, asset.Size, asset.Owner, asset.AppraisedValue);
try {
await assetTransfer.DeleteAsset(transactionContext, 'asset2');
assert.fail('DeleteAsset should have failed');
} catch (err) {
expect(err.message).to.equal('The asset asset2 does not exist');
}
});
it('should return success on DeleteAsset', async () => {
let assetTransfer = new AssetTransfer();
await assetTransfer.CreateAsset(transactionContext, asset.ID, asset.Color, asset.Size, asset.Owner, asset.AppraisedValue);
await assetTransfer.DeleteAsset(transactionContext, asset.ID);
let ret = await chaincodeStub.getState(asset.ID);
expect(ret).to.equal(undefined);
});
});
describe('Test TransferAsset', () => {
it('should return error on TransferAsset', async () => {
let assetTransfer = new AssetTransfer();
await assetTransfer.CreateAsset(transactionContext, asset.ID, asset.Color, asset.Size, asset.Owner, asset.AppraisedValue);
try {
await assetTransfer.TransferAsset(transactionContext, 'asset2', 'Me');
assert.fail('DeleteAsset should have failed');
} catch (err) {
expect(err.message).to.equal('The asset asset2 does not exist');
}
});
it('should return success on TransferAsset', async () => {
let assetTransfer = new AssetTransfer();
await assetTransfer.CreateAsset(transactionContext, asset.ID, asset.Color, asset.Size, asset.Owner, asset.AppraisedValue);
await assetTransfer.TransferAsset(transactionContext, asset.ID, 'Me');
let ret = JSON.parse((await chaincodeStub.getState(asset.ID)).toString());
expect(ret).to.eql(Object.assign({}, asset, {Owner: 'Me'}));
});
});
describe('Test GetAllAssets', () => {
it('should return success on GetAllAssets', async () => {
let assetTransfer = new AssetTransfer();
await assetTransfer.CreateAsset(transactionContext, 'asset1', 'blue', 5, 'Robert', 100);
await assetTransfer.CreateAsset(transactionContext, 'asset2', 'orange', 10, 'Paul', 200);
await assetTransfer.CreateAsset(transactionContext, 'asset3', 'red', 15, 'Troy', 300);
await assetTransfer.CreateAsset(transactionContext, 'asset4', 'pink', 20, 'Van', 400);
let ret = await assetTransfer.GetAllAssets(transactionContext);
ret = JSON.parse(ret);
expect(ret.length).to.equal(4);
let expected = [
{Record: {ID: 'asset1', Color: 'blue', Size: 5, Owner: 'Robert', AppraisedValue: 100}},
{Record: {ID: 'asset2', Color: 'orange', Size: 10, Owner: 'Paul', AppraisedValue: 200}},
{Record: {ID: 'asset3', Color: 'red', Size: 15, Owner: 'Troy', AppraisedValue: 300}},
{Record: {ID: 'asset4', Color: 'pink', Size: 20, Owner: 'Van', AppraisedValue: 400}}
];
expect(ret).to.eql(expected);
});
it('should return success on GetAllAssets for non JSON value', async () => {
let assetTransfer = new AssetTransfer();
chaincodeStub.putState.onFirstCall().callsFake((key, value) => {
if (!chaincodeStub.states) {
chaincodeStub.states = {};
}
chaincodeStub.states[key] = 'non-json-value';
});
await assetTransfer.CreateAsset(transactionContext, 'asset1', 'blue', 5, 'Robert', 100);
await assetTransfer.CreateAsset(transactionContext, 'asset2', 'orange', 10, 'Paul', 200);
await assetTransfer.CreateAsset(transactionContext, 'asset3', 'red', 15, 'Troy', 300);
await assetTransfer.CreateAsset(transactionContext, 'asset4', 'pink', 20, 'Van', 400);
let ret = await assetTransfer.GetAllAssets(transactionContext);
ret = JSON.parse(ret);
expect(ret.length).to.equal(4);
let expected = [
{Record: 'non-json-value'},
{Record: {ID: 'asset2', Color: 'orange', Size: 10, Owner: 'Paul', AppraisedValue: 200}},
{Record: {ID: 'asset3', Color: 'red', Size: 15, Owner: 'Troy', AppraisedValue: 300}},
{Record: {ID: 'asset4', Color: 'pink', Size: 20, Owner: 'Van', AppraisedValue: 400}}
];
expect(ret).to.eql(expected);
});
});
});

View file

@ -0,0 +1,5 @@
#
# SPDX-License-Identifier: Apache-2.0
#
coverage

View file

@ -0,0 +1,37 @@
/*
* SPDX-License-Identifier: Apache-2.0
*/
'use strict';
module.exports = {
env: {
node: true,
mocha: true
},
parserOptions: {
ecmaVersion: 8,
sourceType: 'script'
},
extends: 'eslint:recommended',
rules: {
indent: ['error', 'tab'],
'linebreak-style': ['error', 'unix'],
quotes: ['error', 'single'],
semi: ['error', 'always'],
'no-unused-vars': ['error', { args: 'none' }],
'no-console': 'off',
curly: 'error',
eqeqeq: 'error',
'no-throw-literal': 'error',
strict: 'error',
'no-var': 'error',
'dot-notation': 'error',
'no-trailing-spaces': 'error',
'no-use-before-define': 'error',
'no-useless-call': 'error',
'no-with': 'error',
'operator-linebreak': 'error',
yoda: 'error',
'quote-props': ['error', 'as-needed']
}
};

View file

@ -67,335 +67,336 @@
'use strict';
const { Contract } = require('fabric-contract-api');
const {Contract} = require('fabric-contract-api');
class Chaincode extends Contract{
class Chaincode extends Contract {
// CreateAsset - create a new asset, store into chaincode state
async CreateAsset(ctx, assetID, color, size, owner, appraisedValue) {
const exists = await this.AssetExists(ctx, assetID)
if (exists) {
throw new Error(`The asset ${assetID} already exists`)
}
// CreateAsset - create a new asset, store into chaincode state
async CreateAsset(ctx, assetID, color, size, owner, appraisedValue) {
const exists = await this.AssetExists(ctx, assetID);
if (exists) {
throw new Error(`The asset ${assetID} already exists`);
}
// ==== Create asset object and marshal to JSON ====
let asset = {};
asset.docType = 'asset';
asset.ID = assetID;
asset.color = color;
asset.size = size;
asset.owner = owner;
asset.appraisedValue = appraisedValue;
// ==== Create asset object and marshal to JSON ====
let asset = {
docType: 'asset',
assetID: assetID,
color: color,
size: size,
owner: owner,
appraisedValue: appraisedValue
};
// === Save asset to state ===
await ctx.stub.putState(assetID, Buffer.from(JSON.stringify(asset)));
let indexName = 'color~name'
let colorNameIndexKey = await ctx.stub.createCompositeKey(indexName, [asset.color, asset.ID]);
// Save index entry to state. Only the key name is needed, no need to store a duplicate copy of the marble.
// Note - passing a 'nil' value will effectively delete the key from state, therefore we pass null character as value
await ctx.stub.putState(colorNameIndexKey, Buffer.from('\u0000'));
}
// === Save asset to state ===
await ctx.stub.putState(assetID, Buffer.from(JSON.stringify(asset)));
let indexName = 'color~name';
let colorNameIndexKey = await ctx.stub.createCompositeKey(indexName, [asset.color, asset.assetID]);
// ReadAsset returns the asset stored in the world state with given id.
async ReadAsset(ctx, id) {
const assetJSON = await ctx.stub.getState(id); // get the asset from chaincode state
if (!assetJSON || assetJSON.length === 0) {
throw new Error(`Asset ${id} does not exist`);
}
// Save index entry to state. Only the key name is needed, no need to store a duplicate copy of the marble.
// Note - passing a 'nil' value will effectively delete the key from state, therefore we pass null character as value
await ctx.stub.putState(colorNameIndexKey, Buffer.from('\u0000'));
}
return assetJSON.toString();
}
// ReadAsset returns the asset stored in the world state with given id.
async ReadAsset(ctx, id) {
const assetJSON = await ctx.stub.getState(id); // get the asset from chaincode state
if (!assetJSON || assetJSON.length === 0) {
throw new Error(`Asset ${id} does not exist`);
}
// delete - remove a asset key/value pair from state
async DeleteAsset(ctx, id) {
if (!id) {
throw new Error('Asset name must not be empty');
}
return assetJSON.toString();
}
var exists = await this.AssetExists(ctx, id)
if (!exists) {
throw new Error(`Asset ${id} does not exist`)
}
// delete - remove a asset key/value pair from state
async DeleteAsset(ctx, id) {
if (!id) {
throw new Error('Asset name must not be empty');
}
// to maintain the color~name index, we need to read the asset first and get its color
let valAsbytes = await ctx.stub.getState(id); // get the asset from chaincode state
let jsonResp = {};
if (!valAsbytes) {
jsonResp.error = 'Asset does not exist: ' + name;
throw new Error(jsonResp);
}
let assetJSON = {};
try {
assetJSON = JSON.parse(valAsbytes.toString());
} catch (err) {
jsonResp = {};
jsonResp.error = 'Failed to decode JSON of: ' + id;
throw new Error(jsonResp);
}
await ctx.stub.deleteState(id); //remove the asset from chaincode state
let exists = await this.AssetExists(ctx, id);
if (!exists) {
throw new Error(`Asset ${id} does not exist`);
}
// delete the index
let indexName = 'color~name';
let colorNameIndexKey = ctx.stub.createCompositeKey(indexName, [assetJSON.color, assetJSON.ID]);
if (!colorNameIndexKey) {
throw new Error(' Failed to create the createCompositeKey');
}
// Delete index entry to state.
await ctx.stub.deleteState(colorNameIndexKey);
}
// to maintain the color~name index, we need to read the asset first and get its color
let valAsbytes = await ctx.stub.getState(id); // get the asset from chaincode state
let jsonResp = {};
if (!valAsbytes) {
jsonResp.error = `Asset does not exist: ${id}`;
throw new Error(jsonResp);
}
let assetJSON;
try {
assetJSON = JSON.parse(valAsbytes.toString());
} catch (err) {
jsonResp = {};
jsonResp.error = `Failed to decode JSON of: ${id}`;
throw new Error(jsonResp);
}
await ctx.stub.deleteState(id); //remove the asset from chaincode state
// TransferAsset transfers an asset by setting a new owner name on the asset
async TransferAsset(ctx, assetName, newOwner) {
// delete the index
let indexName = 'color~name';
let colorNameIndexKey = ctx.stub.createCompositeKey(indexName, [assetJSON.color, assetJSON.assetID]);
if (!colorNameIndexKey) {
throw new Error(' Failed to create the createCompositeKey');
}
// Delete index entry to state.
await ctx.stub.deleteState(colorNameIndexKey);
}
let assetAsBytes = await ctx.stub.getState(assetName);
if (!assetAsBytes || !assetAsBytes.toString()) {
throw new Error(`Asset ${assetName} does not exist`);
}
let assetToTransfer = {};
try {
assetToTransfer = JSON.parse(assetAsBytes.toString()); //unmarshal
} catch (err) {
let jsonResp = {};
jsonResp.error = 'Failed to decode JSON of: ' + assetName;
throw new Error(jsonResp);
}
assetToTransfer.owner = newOwner; //change the owner
// TransferAsset transfers a asset by setting a new owner name on the asset
async TransferAsset(ctx, assetName, newOwner) {
let assetJSONasBytes = Buffer.from(JSON.stringify(assetToTransfer));
await ctx.stub.putState(assetName, assetJSONasBytes); //rewrite the asset
}
let assetAsBytes = await ctx.stub.getState(assetName);
if (!assetAsBytes || !assetAsBytes.toString()) {
throw new Error(`Asset ${assetName} does not exist`);
}
let assetToTransfer = {};
try {
assetToTransfer = JSON.parse(assetAsBytes.toString()); //unmarshal
} catch (err) {
let jsonResp = {};
jsonResp.error = 'Failed to decode JSON of: ' + assetName;
throw new Error(jsonResp);
}
assetToTransfer.owner = newOwner; //change the owner
// GetAssetsByRange performs a range query based on the start and end keys provided.
// Read-only function results are not typically submitted to ordering. If the read-only
// results are submitted to ordering, or if the query is used in an update transaction
// and submitted to ordering, then the committing peers will re-execute to guarantee that
// result sets are stable between endorsement time and commit time. The transaction is
// invalidated by the committing peers if the result set has changed between endorsement
// time and commit time.
// Therefore, range queries are a safe option for performing update transactions based on query results.
async GetAssetsByRange(ctx, startKey, endKey) {
let assetJSONasBytes = Buffer.from(JSON.stringify(assetToTransfer));
await ctx.stub.putState(assetName, assetJSONasBytes); //rewrite the asset
}
let resultsIterator = await ctx.stub.getStateByRange(startKey, endKey);
let results = await this.GetAllResults(resultsIterator, false);
// GetAssetsByRange performs a range query based on the start and end keys provided.
// Read-only function results are not typically submitted to ordering. If the read-only
// results are submitted to ordering, or if the query is used in an update transaction
// and submitted to ordering, then the committing peers will re-execute to guarantee that
// result sets are stable between endorsement time and commit time. The transaction is
// invalidated by the committing peers if the result set has changed between endorsement
// time and commit time.
// Therefore, range queries are a safe option for performing update transactions based on query results.
async GetAssetsByRange(ctx, startKey, endKey) {
return JSON.stringify(results);
}
let resultsIterator = await ctx.stub.getStateByRange(startKey, endKey);
let results = await this.GetAllResults(resultsIterator, false);
// TransferAssetBasedOnColor will transfer assets of a given color to a certain new owner.
// Uses a GetStateByPartialCompositeKey (range query) against color~name 'index'.
// Committing peers will re-execute range queries to guarantee that result sets are stable
// between endorsement time and commit time. The transaction is invalidated by the
// committing peers if the result set has changed between endorsement time and commit time.
// Therefore, range queries are a safe option for performing update transactions based on query results.
// Example: GetStateByPartialCompositeKey/RangeQuery
async TransferAssetByColor(ctx, color, newOwner) {
// Query the color~name index by color
// This will execute a key range query on all keys starting with 'color'
let coloredAssetResultsIterator = await ctx.stub.getStateByPartialCompositeKey('color~name', [color]);
return JSON.stringify(results);
}
// Iterate through result set and for each asset found, transfer to newOwner
while (true) {
let responseRange = await coloredAssetResultsIterator.next();
if (!responseRange || !responseRange.value || !responseRange.value.key) {
return;
}
// TransferAssetBasedOnColor will transfer assets of a given color to a certain new owner.
// Uses a GetStateByPartialCompositeKey (range query) against color~name 'index'.
// Committing peers will re-execute range queries to guarantee that result sets are stable
// between endorsement time and commit time. The transaction is invalidated by the
// committing peers if the result set has changed between endorsement time and commit time.
// Therefore, range queries are a safe option for performing update transactions based on query results.
// Example: GetStateByPartialCompositeKey/RangeQuery
async TransferAssetByColor(ctx, color, newOwner) {
// Query the color~name index by color
// This will execute a key range query on all keys starting with 'color'
let coloredAssetResultsIterator = await ctx.stub.getStateByPartialCompositeKey('color~name', [color]);
let objectType;
let attributes;
({
objectType,
attributes
} = await ctx.stub.splitCompositeKey(responseRange.value.key));
// Iterate through result set and for each asset found, transfer to newOwner
let responseRange = await coloredAssetResultsIterator.next();
while (!responseRange.done) {
if (!responseRange || !responseRange.value || !responseRange.value.key) {
return;
}
console.log(objectType)
let returnedAssetName = attributes[1];
let objectType;
let attributes;
(
{objectType, attributes} = await ctx.stub.splitCompositeKey(responseRange.value.key)
);
// Now call the transfer function for the found asset.
// Re-use the same function that is used to transfer individual assets
await this.TransferAsset(ctx, returnedAssetName, newOwner);
}
}
console.log(objectType);
let returnedAssetName = attributes[1];
// QueryAssetsByOwner queries for assets based on a passed in owner.
// This is an example of a parameterized query where the query logic is baked into the chaincode,
// and accepting a single query parameter (owner).
// Only available on state databases that support rich query (e.g. CouchDB)
// Example: Parameterized rich query
async QueryAssetsByOwner(ctx, owner) {
let queryString = {};
queryString.selector = {};
queryString.selector.docType = 'asset';
queryString.selector.owner = owner;
let queryResults = await this.GetQueryResultForQueryString(ctx, JSON.stringify(queryString));
return queryResults; //shim.success(queryResults);
}
// Now call the transfer function for the found asset.
// Re-use the same function that is used to transfer individual assets
await this.TransferAsset(ctx, returnedAssetName, newOwner);
responseRange = await coloredAssetResultsIterator.next();
}
}
// Example: Ad hoc rich query
// QueryAssets uses a query string to perform a query for assets.
// Query string matching state database syntax is passed in and executed as is.
// Supports ad hoc queries that can be defined at runtime by the client.
// If this is not desired, follow the QueryAssetsForOwner example for parameterized queries.
// Only available on state databases that support rich query (e.g. CouchDB)
async QueryAssets(ctx, queryString) {
let queryResults = await this.GetQueryResultForQueryString(ctx, queryString);
return queryResults;
}
// QueryAssetsByOwner queries for assets based on a passed in owner.
// This is an example of a parameterized query where the query logic is baked into the chaincode,
// and accepting a single query parameter (owner).
// Only available on state databases that support rich query (e.g. CouchDB)
// Example: Parameterized rich query
async QueryAssetsByOwner(ctx, owner) {
let queryString = {};
queryString.selector = {};
queryString.selector.docType = 'asset';
queryString.selector.owner = owner;
return await this.GetQueryResultForQueryString(ctx, JSON.stringify(queryString)); //shim.success(queryResults);
}
// GetQueryResultForQueryString executes the passed in query string.
// Result set is built and returned as a byte array containing the JSON results.
async GetQueryResultForQueryString(ctx, queryString) {
// Example: Ad hoc rich query
// QueryAssets uses a query string to perform a query for assets.
// Query string matching state database syntax is passed in and executed as is.
// Supports ad hoc queries that can be defined at runtime by the client.
// If this is not desired, follow the QueryAssetsForOwner example for parameterized queries.
// Only available on state databases that support rich query (e.g. CouchDB)
async QueryAssets(ctx, queryString) {
return await this.GetQueryResultForQueryString(ctx, queryString);
}
let resultsIterator = await ctx.stub.getQueryResult(queryString);
let results = await this.GetAllResults(resultsIterator, false);
// GetQueryResultForQueryString executes the passed in query string.
// Result set is built and returned as a byte array containing the JSON results.
async GetQueryResultForQueryString(ctx, queryString) {
return JSON.stringify(results);
}
let resultsIterator = await ctx.stub.getQueryResult(queryString);
let results = await this.GetAllResults(resultsIterator, false);
// Example: Pagination with Range Query
// GetAssetsByRangeWithPagination performs a range query based on the start & end key,
// page size and a bookmark.
// The number of fetched records will be equal to or lesser than the page size.
// Paginated range queries are only valid for read only transactions.
async GetAssetsByRangeWithPagination(ctx, startKey, endKey, pageSize, bookmark) {
return JSON.stringify(results);
}
const { iterator, metadata } = await ctx.stub.getStateByRangeWithPagination(startKey, endKey, pageSize, bookmark);
const results = await this.GetAllResults(iterator, false);
// Example: Pagination with Range Query
// GetAssetsByRangeWithPagination performs a range query based on the start & end key,
// page size and a bookmark.
// The number of fetched records will be equal to or lesser than the page size.
// Paginated range queries are only valid for read only transactions.
async GetAssetsByRangeWithPagination(ctx, startKey, endKey, pageSize, bookmark) {
results.ResponseMetadata = {
RecordsCount: metadata.fetched_records_count,
Bookmark: metadata.bookmark,
};
return JSON.stringify(results);
}
const {iterator, metadata} = await ctx.stub.getStateByRangeWithPagination(startKey, endKey, pageSize, bookmark);
const results = await this.GetAllResults(iterator, false);
// Example: Pagination with Ad hoc Rich Query
// QueryAssetsWithPagination uses a query string, page size and a bookmark to perform a query
// for assets. Query string matching state database syntax is passed in and executed as is.
// The number of fetched records would be equal to or lesser than the specified page size.
// Supports ad hoc queries that can be defined at runtime by the client.
// If this is not desired, follow the QueryAssetsForOwner example for parameterized queries.
// Only available on state databases that support rich query (e.g. CouchDB)
// Paginated queries are only valid for read only transactions.
async QueryAssetsWithPagination(ctx, queryString, pageSize, bookmark) {
results.ResponseMetadata = {
RecordsCount: metadata.fetched_records_count,
Bookmark: metadata.bookmark,
};
return JSON.stringify(results);
}
const { iterator, metadata } = await ctx.stub.getQueryResultWithPagination(queryString, pageSize, bookmark);
const results = await this.GetAllResults(iterator, false);
// Example: Pagination with Ad hoc Rich Query
// QueryAssetsWithPagination uses a query string, page size and a bookmark to perform a query
// for assets. Query string matching state database syntax is passed in and executed as is.
// The number of fetched records would be equal to or lesser than the specified page size.
// Supports ad hoc queries that can be defined at runtime by the client.
// If this is not desired, follow the QueryAssetsForOwner example for parameterized queries.
// Only available on state databases that support rich query (e.g. CouchDB)
// Paginated queries are only valid for read only transactions.
async QueryAssetsWithPagination(ctx, queryString, pageSize, bookmark) {
results.ResponseMetadata = {
RecordsCount: metadata.fetched_records_count,
Bookmark: metadata.bookmark,
};
const {iterator, metadata} = await ctx.stub.getQueryResultWithPagination(queryString, pageSize, bookmark);
const results = await this.GetAllResults(iterator, false);
return JSON.stringify(results);
}
results.ResponseMetadata = {
RecordsCount: metadata.fetched_records_count,
Bookmark: metadata.bookmark,
};
// GetAssetHistory returns the chain of custody for an asset since issuance.
async GetAssetHistory(ctx, assetName) {
return JSON.stringify(results);
}
const resultsIterator = await ctx.stub.getHistoryForKey(assetName);
const results = await this.GetAllResults(resultsIterator, true);
// GetAssetHistory returns the chain of custody for an asset since issuance.
async GetAssetHistory(ctx, assetName) {
return JSON.stringify(results);
}
let resultsIterator = await ctx.stub.getHistoryForKey(assetName);
let results = await this.GetAllResults(resultsIterator, true);
// AssetExists returns true when asset with given ID exists in world state
async AssetExists(ctx, assetName) {
// ==== Check if asset already exists ====
const assetState = await ctx.stub.getState(assetName);
if ( !assetState || assetState.length === 0 ) {
return false;
}
return true
}
return JSON.stringify(results);
}
async GetAllResults(iterator, isHistory) {
const allResults = [];
let res = await iterator.next();
while (!res.done) {
const jsonRes = {};
// AssetExists returns true when asset with given ID exists in world state
async AssetExists(ctx, assetName) {
// ==== Check if asset already exists ====
let assetState = await ctx.stub.getState(assetName);
return assetState && assetState.length > 0;
}
if (isHistory) {
jsonRes.TxId = res.value.txId;
jsonRes.Timestamp = res.value.timestamp;
jsonRes.IsDelete = res.value.isDelete;
}
async GetAllResults(iterator, isHistory) {
let allResults = [];
let res = await iterator.next();
while (!res.done) {
if (res.value && res.value.value.toString()) {
let jsonRes = {};
console.log(res.value.value.toString('utf8'));
if (isHistory && isHistory === true) {
jsonRes.TxId = res.value.tx_id;
jsonRes.Timestamp = res.value.timestamp;
try {
jsonRes.Value = JSON.parse(res.value.value.toString('utf8'));
} catch (err) {
console.log(err);
jsonRes.Value = res.value.value.toString('utf8');
}
} else {
jsonRes.Key = res.value.key;
try {
jsonRes.Record = JSON.parse(res.value.value.toString('utf8'));
} catch (err) {
console.log(err);
jsonRes.Record = res.value.value.toString('utf8');
}
}
allResults.push(jsonRes);
}
res = await iterator.next();
}
iterator.close();
return allResults;
}
if (res.value.value.length > 0) {
jsonRes.Record = JSON.parse(res.value.value.toString('utf8'));
} else {
jsonRes.Record = {ID: res.value.key};
}
// InitLedger creates sample assets in the ledger
async InitLedger(ctx) {
const assets = [
{
assetID: 'asset1',
color: 'blue',
size: 5,
owner: 'Tom',
appraisedValue: 100
},
{
assetID: 'asset2',
color: 'red',
size: 5,
owner: 'Brad',
appraisedValue: 100
},
{
assetID: 'asset3',
color: 'green',
size: 10,
owner: 'Jin Soo',
appraisedValue: 200
},
{
assetID: 'asset4',
color: 'yellow',
size: 10,
owner: 'Max',
appraisedValue: 200
},
{
assetID: 'asset5',
color: 'black',
size: 15,
owner: 'Adriana',
appraisedValue: 250
},
{
assetID: 'asset6',
color: 'white',
size: 15,
owner: 'Michel',
appraisedValue: 250
},
];
jsonRes.Key = res.value.key;
console.log('Result: ' + JSON.stringify(jsonRes));
allResults.push(jsonRes);
res = await iterator.next();
}
await iterator.close();
return allResults;
}
// InitLedger creates sample assets in the ledger
async InitLedger(ctx) {
const assets = [
{
ID: 'asset1',
color: 'blue',
size: 5,
owner: 'Tom',
appraisedValue: 100
},
{
ID: 'asset2',
color: 'red',
size: 5,
owner: 'Brad',
appraisedValue: 100
},
{
ID: 'asset3',
color: 'green',
size: 10,
owner: 'Jin Soo',
appraisedValue: 200
},
{
ID: 'asset4',
color: 'yellow',
size: 10,
owner: 'Max',
appraisedValue: 200
},
{
ID: 'asset5',
color: 'black',
size: 15,
owner: 'Adriana',
appraisedValue: 250
},
{
ID: 'asset6',
color: 'white',
size: 15,
owner: 'Michel',
appraisedValue: 250
},
];
for (let i = 0; i < assets.length; i++) {
await this.CreateAsset(
ctx,
assets[i].ID,
assets[i].color,
assets[i].size,
assets[i].owner,
assets[i].appraisedValue
);
}
}
for (let asset in assets) {
await this.CreateAsset(
ctx,
asset.assetID,
asset.color,
asset.size,
asset.owner,
asset.appraisedValue
);
}
}
}
module.exports = Chaincode;

View file

@ -123,7 +123,7 @@ async function main() {
let asset2Data = { objectType: assetType, assetID: assetID2, color: 'blue', size: 35, appraisedValue: 727 };
console.log('\n**************** As Org1 Client ****************');
console.log('Adding Assets to work with: Submit Transaction: CreateAsset ' + assetID1);
console.log('Adding Assets to work with:\n--> Submit Transaction: CreateAsset ' + assetID1);
let statefulTxn = contractOrg1.createTransaction('CreateAsset');
//if you need to customize endorsement to specific set of Orgs, use setEndorsingOrganizations
//statefulTxn.setEndorsingOrganizations(mspOrg1);
@ -255,4 +255,4 @@ async function main() {
}
}
main();
main();

View file

@ -147,7 +147,6 @@ func (s *SmartContract) QueryAssetByOwner(ctx contractapi.TransactionContextInte
return queryResults, nil
}
// QueryAssets uses a query string to perform a query for assets.
// Query string matching state database syntax is passed in and executed as is.
// Supports ad hoc queries that can be defined at runtime by the client.

View file

@ -1,55 +1,135 @@
# Sample to demonstrate State-Based Endorsements (SBE)
# State-based endorsement asset transfer sample
## 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).
Transactions that are submitted to Hyperledger Fabric networks need to be endorsed by peers that are joined to a channel before the transaction can be added to the ledger. Fabric peers endorse transactions by executing a smart contract using the inputs of the transaction proposal. The peers then sign the input and output generated by the smart contract execution. The endorsement policy specifies the set of organizations whose peers need to endorse a transaction before it can be added to the ledger.
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.
## 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:
- Deploy the SBE smart contract to a channel that was created using the Fabric test network. The channel will have two members, Org1 and Org2, that will participate in the asset transfer. Each organization operates one peer that is joined to the channel.
- Create an asset using the chaincode endorsement policy. The chaincode level endorsement policy requires that a majority of organizations on the channel to endorse a transaction. As a result, the transaction that creates the asset needs to be endorsed by peers that belong to Org1 and Org2. When the asset is created, the smart contract creates a state-based endorsement policy that specifies that only the organization that owns that asset needs to endorse any asset updates. Because the asset is owned by Org1, any future updates to the asset need to be endorsed by the Org1 peer.
- Update the asset using the state-based endorsement policy.
- Transfer the asset to Org2. The transfer transaction will create a new state-based endorsement policy that reflects the new asset owner.
- Update the asset once more, this time with Org2 as the owner. Because the state-based endorsement policy has been updated, this transaction only needs to be endorsed by Org2.
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>;
## Deploy the smart contract
We are going to run the SBE smart contract using the Fabric test network. Open a command terminal and navigate to test network directory in your local clone of the `fabric-samples`. We will operate from the `test-network` directory for the remainder of the tutorial.
```
cd fabric-samples/test-network
```
## 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).
Run the following command to deploy the test network and create a channel named `mychannel`:
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.
```
./network.sh up createChannel
```
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.
You can use the test network script to deploy the smart contract to the channel that was just created. The script uses the Fabric chaincode lifecycle to deploy the smart contract to the channel. We will use the default chaincode level endorsement policy used by the Fabric chaincode lifecycle, which requires an endorsement from a majority of channel members. In our case, this will require that both Org1 and Org2 endorse a transaction (2 of 2). Deploy the smart contract to `mychannel` using the following command:
```
./network.sh deployCC -ccn sbe -ccl typescript
```
Hence transactions like UpdateAsset or TransferAsset will not succeed if endorsed by the non-owner Org Peer.
Set the following environment variables to interact with the network as a user from Org1:
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
```
export PATH=${PWD}/../bin:${PWD}:$PATH
export FABRIC_CFG_PATH=$PWD/../config/
export CORE_PEER_TLS_ENABLED=true
export CORE_PEER_LOCALMSPID="Org1MSP"
export CORE_PEER_MSPCONFIGPATH=${PWD}/organizations/peerOrganizations/org1.example.com/users/User1@org1.example.com/msp
export CORE_PEER_TLS_ROOTCERT_FILE=${PWD}/organizations/peerOrganizations/org1.example.com/peers/peer0.org1.example.com/tls/ca.crt
export CORE_PEER_ADDRESS=localhost:7051
```
## Run the transfer scenario
We can now invoke the SBE smart contract to create a new asset:
```
peer chaincode invoke -o localhost:7050 --waitForEvent --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 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
The create transaction needs to target both peers from Org1 and Org2 to meet the chaincode endorsement policy. The chaincode will read the MSP ID of the client user submitting the transaction and assign that organization as the asset owner. As a result, the asset will initially be owned by Org1.
You can query the asset using with the following command:
```
peer chaincode query -C mychannel -n 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)
```bash
The result is a new asset owned by Org1, identified using the Org1 MSP ID `Org1MSP`:
`{"ID":"asset1","Value":100,"Owner":"Org1User1","OwnerOrg":"Org1MSP"}`
In addition to creating the asset, the `CreateAsset` function also sets a state-based endorsement policy for the asset. Only a peer of the asset owner, can successfully endorse an asset update. To demonstrate the key-level endorsement policy, lets try to update the asset while targeting the Org2 peer:
```
peer chaincode invoke -o localhost:7050 --waitForEvent --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 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
The result is an endorsement policy failure:
```
Error: transaction invalidated with status (ENDORSEMENT_POLICY_FAILURE) - proposal response: <nil>
```
If we attempt to update the asset with an endorsement from the Org1 peer, the update succeeds:
```
peer chaincode invoke -o localhost:7050 --waitForEvent --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 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)
```bash
peer chaincode invoke -o localhost:7050 --waitForEvent --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 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"]}'
You can query the asset one more time to verify that the update was successful:
```
peer chaincode query -C mychannel -n sbe -c '{"Args":["ReadAsset","asset1"]}'
```
The asset value is now 200:
```
{"ID":"asset1","Value":200,"Owner":"Org1User1","OwnerOrg":"Org1MSP"}
```
Now that we have tested the asset key-level endorsement policy, we can transfer the asset to Org2. Run the following command to transfer the asset from Org1 to Org2. This time the Org2 MSP ID is provided as a transaction input. The `TransferAsset` function will update the endorsement policy to specify that only a peer of the new owner can update the asset. Note that this command targets the Org1 peer.
```
* 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 --waitForEvent --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 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)
```bash
We can query the asset to see that the owner has been updated from Org1 to Org2:
```
peer chaincode query -C mychannel -n sbe -c '{"Args":["ReadAsset","asset1"]}'
```
The owning organization is now Org2:
```
{"ID":"asset1","Value":200,"Owner":"Org2User1","OwnerOrg":"Org2MSP"}
```
Org2 now needs to endorse any asset updates. Run the following command to try to update the asset with an endorsement from the Org1 peer:
```
peer chaincode invoke -o localhost:7050 --waitForEvent --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 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
The response will be an endorsement policy failure:
```
Error: transaction invalidated with status (ENDORSEMENT_POLICY_FAILURE) - proposal response: <nil>
```
Now try to update the asset with an endorsement from the Org2 peer:
```
peer chaincode invoke -o localhost:7050 --waitForEvent --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 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"]}'
```
You can query the asset again to verify that the transaction update succeeded:
```
peer chaincode query -C mychannel -n sbe -c '{"Args":["ReadAsset","asset1"]}'
```
The asset value is now 300:
```
{"ID":"asset1","Value":300,"Owner":"Org2User1","OwnerOrg":"Org2MSP"}
```
Note that the transaction to update the asset was submitted by a user from Org1, even though the asset was owned by Org2. The transfer enabled by the SBE smart contract is a simple scenario meant only to demonstrate the use of state-based endorsement policies. The smart contract can use access control to specify that an asset can only be updated by its owner. Private data collections can also be used to ensure that transfers need to be endorsed by the owner and recipient of the transfer, instead of just the asset owner. For a more realistic example of an asset transfer scenario, see the [Secured asset transfer in Fabric](https://hyperledger-fabric.readthedocs.io/en/master/secured_asset_transfer/secured_private_asset_transfer_tutorial.html) tutorial.
## Clean up
When you are finished, you can bring down the test network. The command will remove all the nodes of the test network, and delete any ledger data that you created:
```
./network.sh down
```

View file

@ -6,10 +6,9 @@ import { Object, Property } from 'fabric-contract-api';
@Object()
export class Asset {
@Property()
public ID: string;
@Property()
public Value: number;
@ -18,5 +17,4 @@ export class Asset {
@Property()
public OwnerOrg: string;
}

View file

@ -17,7 +17,7 @@ export class AssetContract extends Contract {
if (exists) {
throw new Error(`The asset ${assetId} already exists`);
}
const ownerOrg = this.getClientOrgId(ctx);
const ownerOrg = AssetContract.getClientOrgId(ctx);
const asset = new Asset();
asset.ID = assetId;
asset.Value = value;
@ -27,7 +27,7 @@ export class AssetContract extends Contract {
// 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]);
await AssetContract.setAssetStateBasedEndorsement(ctx, asset.ID, [ownerOrg]);
}
// ReadAsset returns asset with given assetId
@ -78,7 +78,7 @@ export class AssetContract extends Contract {
// 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]);
await AssetContract.setAssetStateBasedEndorsement(ctx, asset.ID, [newOwnerOrg]);
}
// AssetExists returns true when asset with given ID exists
@ -89,14 +89,14 @@ export class AssetContract extends Contract {
// 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);
private static async setAssetStateBasedEndorsement(ctx: Context, assetId: string, ownerOrgs: string[]): Promise<void> {
const 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 {
private static getClientOrgId(ctx: Context): string {
return ctx.clientIdentity.getMSPID();
}
}

View file

@ -76,6 +76,14 @@ jobs:
vmImage: ubuntu-18.04
strategy:
matrix:
Basic-Application-Go:
DIRECTORY: asset-transfer-basic
LANGUAGE: go
TYPE: application
Basic-Application-Java:
DIRECTORY: asset-transfer-basic
LANGUAGE: java
TYPE: application
Basic-Application-Javascript:
DIRECTORY: asset-transfer-basic
LANGUAGE: javascript
@ -96,10 +104,26 @@ jobs:
DIRECTORY: asset-transfer-basic
LANGUAGE: typescript
TYPE: chaincode
Ledger-Application-Java:
DIRECTORY: asset-transfer-ledger-queries
LANGUAGE: java
TYPE: application
Ledger-Chaincode-Go:
DIRECTORY: asset-transfer-ledger-queries
LANGUAGE: go
TYPE: chaincode
TYPE: chaincode
Ledger-Chaincode-Javascript:
DIRECTORY: asset-transfer-ledger-queries
LANGUAGE: javascript
TYPE: chaincode
PrivateData-Application-Javascript:
DIRECTORY: asset-transfer-private-data
LANGUAGE: javascript
TYPE: application
PrivateData-Chaincode-Go:
DIRECTORY: asset-transfer-private-data
LANGUAGE: go
TYPE: chaincode
SBE-Chaincode-Typescript:
DIRECTORY: asset-transfer-sbe
LANGUAGE: typescript
@ -108,14 +132,6 @@ jobs:
DIRECTORY: asset-transfer-sbe
LANGUAGE: java
TYPE: chaincode
PrivateData-Chaincode-Go:
DIRECTORY: asset-transfer-private-data
LANGUAGE: go
TYPE: chaincode
PrivateData-Application-Javascript:
DIRECTORY: asset-transfer-private-data
LANGUAGE: javascript
TYPE: application
Secured-Chaincode-Go:
DIRECTORY: asset-transfer-secured-agreement
LANGUAGE: go
@ -166,6 +182,9 @@ jobs:
Ledger-Go:
CHAINCODE_NAME: ledger
CHAINCODE_LANGUAGE: go
Ledger-Javascript:
CHAINCODE_NAME: ledger
CHAINCODE_LANGUAGE: javascript
steps:
- template: templates/install-deps.yml
- script: ../ci/scripts/run-test-network-ledger.sh
@ -193,12 +212,12 @@ jobs:
vmImage: ubuntu-18.04
strategy:
matrix:
Basic-Java:
CHAINCODE_NAME: sbe
CHAINCODE_LANGUAGE: java
Basic-Typescript:
SBE-Typescript:
CHAINCODE_NAME: sbe
CHAINCODE_LANGUAGE: typescript
SBE-Java:
CHAINCODE_NAME: sbe
CHAINCODE_LANGUAGE: java
steps:
- template: templates/install-deps.yml
- script: ../ci/scripts/run-test-network-sbe.sh

View file

@ -11,20 +11,42 @@ function print() {
echo -e "${GREEN}${1}${NC}"
}
print "Creating network"
./network.sh up createChannel -ca -s couchdb -i "${FABRIC_VERSION}"
print "Deploying ${CHAINCODE_NAME} chaincode"
./network.sh deployCC -ccn "${CHAINCODE_NAME}" -ccv 1 -ccs 1 -ccl "${CHAINCODE_LANGUAGE}"
function createNetwork() {
print "Creating network"
./network.sh up createChannel -ca -s couchdb -i "${FABRIC_VERSION}"
print "Deploying ${CHAINCODE_NAME} chaincode"
./network.sh deployCC -ccn "${CHAINCODE_NAME}" -ccv 1 -ccs 1 -ccl "${CHAINCODE_LANGUAGE}"
}
function stopNetwork() {
print "Stopping network"
./network.sh down
}
# Run Go application
#createNetwork
#print "Initializing Go application"
#pushd ../asset-transfer-basic/application-go
#print "Executing AssetTransfer.go"
#go run .
#popd
#stopNetwork
# Run Java application
createNetwork
print "Initializing Java application"
pushd ../asset-transfer-basic/application-java
print "Executing Gradle Run"
gradle run
popd
stopNetwork
# Run Javascript application
createNetwork
print "Initializing Javascript application"
pushd ../asset-transfer-basic/application-javascript
npm install
print "Executing app.js"
node app.js
popd
print "Stopping network"
./network.sh down
rm -R ../asset-transfer-basic/application-javascript/wallet
stopNetwork

View file

@ -1,6 +1,7 @@
set -euo pipefail
FABRIC_VERSION=${FABRIC_VERSION:-2.2}
CHAINCODE_LANGUAGE=${CHAINCODE_LANGUAGE:-go}
CHAINCODE_NAME=${CHAINCODE_NAME:-ledger}
function print() {
@ -10,31 +11,33 @@ function print() {
echo -e "${GREEN}${1}${NC}"
}
print "Creating network"
./network.sh up createChannel -ca -s couchdb -i "${FABRIC_VERSION}"
function createNetwork() {
print "Creating network"
./network.sh up createChannel -ca -s couchdb -i "${FABRIC_VERSION}"
print "Deploying ${CHAINCODE_NAME} chaincode"
./network.sh deployCC -ccn "${CHAINCODE_NAME}" -ccv 1 -ccs 1 -ccl "${CHAINCODE_LANGUAGE}"
}
print "Deploying ${CHAINCODE_NAME} go chaincode"
./network.sh deployCC -ccn "${CHAINCODE_NAME}" -ccv 1.0 -ccs 1 -ccl go
function stopNetwork() {
print "Stopping network"
./network.sh down
}
# Run Javascript application against the go chaincode
# Run Java application
createNetwork
print "Initializing Java application"
pushd ../asset-transfer-ledger-queries/application-java
print "Executing Gradle Run"
gradle run
popd
stopNetwork
# Run Javascript application
createNetwork
print "Initializing Javascript application"
pushd ../asset-transfer-ledger-queries/application-javascript
npm install
print "Executing app.js"
node app.js
popd
print "Deploying ${CHAINCODE_NAME} javascript chaincode"
./network.sh deployCC -ccn "${CHAINCODE_NAME}" -ccv 2.0 -ccs 2 -ccl javascript
# Run Javascript application against the javascript chaincode
print "Initializing Javascript application"
pushd ../asset-transfer-ledger-queries/application-javascript
npm install
print "Executing app.js"
node app.js skipInit
popd
print "Stopping network"
./network.sh down
rm -R ../asset-transfer-ledger-queries/application-javascript/wallet
stopNetwork

View file

@ -11,19 +11,24 @@ function print() {
echo -e "${GREEN}${1}${NC}"
}
print "Creating network"
./network.sh up createChannel -ca -s couchdb -i "${FABRIC_VERSION}"
function createNetwork() {
print "Creating network"
./network.sh up createChannel -ca -s couchdb -i "${FABRIC_VERSION}"
print "Deploying ${CHAINCODE_NAME} chaincode"
./network.sh deployCC -ccn "${CHAINCODE_NAME}" -ccv 1 -ccs 1 -ccl "${CHAINCODE_LANGUAGE}" -ccep "OR('Org1MSP.peer','Org2MSP.peer')" -cccg ../asset-transfer-private-data/chaincode-go/collections_config.json
}
print "Deploying private-data ${CHAINCODE_NAME} chaincode"
./network.sh deployCC -ccn "${CHAINCODE_NAME}" -ccv 1 -ccs 1 -ccl "${CHAINCODE_LANGUAGE}" -ccep "OR('Org1MSP.peer','Org2MSP.peer')" -cccg ../asset-transfer-private-data/chaincode-go/collections_config.json
function stopNetwork() {
print "Stopping network"
./network.sh down
}
# Run Javascript application
createNetwork
print "Initializing Javascript application"
pushd ../asset-transfer-private-data/application-javascript
npm install
print "Executing app.js"
node app.js
popd
print "Stopping network"
./network.sh down
stopNetwork

View file

@ -11,11 +11,18 @@ function print() {
echo -e "${GREEN}${1}${NC}"
}
print "Creating network"
./network.sh up createChannel -ca -s couchdb -i "${FABRIC_VERSION}"
function createNetwork() {
print "Creating network"
./network.sh up createChannel -ca -s couchdb -i "${FABRIC_VERSION}"
print "Deploying ${CHAINCODE_NAME} chaincode"
./network.sh deployCC -ccn "${CHAINCODE_NAME}" -ccv 1 -ccs 1 -ccl "${CHAINCODE_LANGUAGE}"
}
print "Deploying ${CHAINCODE_NAME} chaincode"
./network.sh deployCC -ccn "${CHAINCODE_NAME}" -ccv 1 -ccs 1 -ccl "${CHAINCODE_LANGUAGE}"
function stopNetwork() {
print "Stopping network"
./network.sh down
}
print "Stopping network"
./network.sh down
# Run Javascript application
createNetwork
stopNetwork

View file

@ -11,11 +11,18 @@ function print() {
echo -e "${GREEN}${1}${NC}"
}
print "Creating network"
./network.sh up createChannel -ca -s couchdb -i "${FABRIC_VERSION}"
function createNetwork() {
print "Creating network"
./network.sh up createChannel -ca -s couchdb -i "${FABRIC_VERSION}"
print "Deploying ${CHAINCODE_NAME} chaincode"
./network.sh deployCC -ccn "${CHAINCODE_NAME}" -ccv 1 -ccs 1 -ccl "${CHAINCODE_LANGUAGE}"
}
print "Deploying ${CHAINCODE_NAME} chaincode"
./network.sh deployCC -ccn "${CHAINCODE_NAME}" -ccv 1 -ccs 1 -ccl "${CHAINCODE_LANGUAGE}"
function stopNetwork() {
print "Stopping network"
./network.sh down
}
print "Stopping network"
./network.sh down
# Run Javascript application
createNetwork
stopNetwork

View file

@ -43,7 +43,7 @@ checkCommitReadiness() {
set -x
peer lifecycle chaincode checkcommitreadiness --channelID mychannel --name bigdatacc --signature-policy "OR('Org1MSP.peer', 'Org2MSP.peer')" --version 0 --init-required --sequence 1 >&log.txt
res=$?
set +x
{ set +x; } 2>/dev/null
test $res -eq 0 || continue
let rc=0
for var in "$@"

View file

@ -17,7 +17,7 @@ checkCommitReadiness() {
set -x
peer lifecycle chaincode checkcommitreadiness -o irs-orderer:7050 --channelID irs --signature-policy "AND(OR('partya.peer','partyb.peer','partyc.peer'), 'auditor.peer')" --name irscc --version 1 --init-required --sequence 1 >&log.txt
res=$?
set +x
{ set +x; } 2>/dev/null
test $res -eq 0 || continue
let rc=0
for var in "$@"

View file

@ -73,7 +73,7 @@ function generateOrg3() {
set -x
cryptogen generate --config=org3-crypto.yaml --output="../organizations"
res=$?
set +x
{ set +x; } 2>/dev/null
if [ $res -ne 0 ]; then
echo "Failed to generate certificates..."
exit 1
@ -131,7 +131,7 @@ function generateOrg3Definition() {
set -x
configtxgen -printOrg Org3MSP > ../organizations/peerOrganizations/org3.example.com/org3.json
res=$?
set +x
{ set +x; } 2>/dev/null
if [ $res -ne 0 ]; then
echo "Failed to generate Org3 config material..."
exit 1

View file

@ -13,7 +13,7 @@ function createOrg3 {
set -x
fabric-ca-client enroll -u https://admin:adminpw@localhost:11054 --caname ca-org3 --tls.certfiles ${PWD}/fabric-ca/org3/tls-cert.pem
set +x
{ set +x; } 2>/dev/null
echo 'NodeOUs:
Enable: true
@ -35,21 +35,21 @@ function createOrg3 {
echo
set -x
fabric-ca-client register --caname ca-org3 --id.name peer0 --id.secret peer0pw --id.type peer --tls.certfiles ${PWD}/fabric-ca/org3/tls-cert.pem
set +x
{ set +x; } 2>/dev/null
echo
echo "Register user"
echo
set -x
fabric-ca-client register --caname ca-org3 --id.name user1 --id.secret user1pw --id.type client --tls.certfiles ${PWD}/fabric-ca/org3/tls-cert.pem
set +x
{ set +x; } 2>/dev/null
echo
echo "Register the org admin"
echo
set -x
fabric-ca-client register --caname ca-org3 --id.name org3admin --id.secret org3adminpw --id.type admin --tls.certfiles ${PWD}/fabric-ca/org3/tls-cert.pem
set +x
{ set +x; } 2>/dev/null
mkdir -p ../organizations/peerOrganizations/org3.example.com/peers
mkdir -p ../organizations/peerOrganizations/org3.example.com/peers/peer0.org3.example.com
@ -59,7 +59,7 @@ function createOrg3 {
echo
set -x
fabric-ca-client enroll -u https://peer0:peer0pw@localhost:11054 --caname ca-org3 -M ${PWD}/../organizations/peerOrganizations/org3.example.com/peers/peer0.org3.example.com/msp --csr.hosts peer0.org3.example.com --tls.certfiles ${PWD}/fabric-ca/org3/tls-cert.pem
set +x
{ set +x; } 2>/dev/null
cp ${PWD}/../organizations/peerOrganizations/org3.example.com/msp/config.yaml ${PWD}/../organizations/peerOrganizations/org3.example.com/peers/peer0.org3.example.com/msp/config.yaml
@ -68,7 +68,7 @@ function createOrg3 {
echo
set -x
fabric-ca-client enroll -u https://peer0:peer0pw@localhost:11054 --caname ca-org3 -M ${PWD}/../organizations/peerOrganizations/org3.example.com/peers/peer0.org3.example.com/tls --enrollment.profile tls --csr.hosts peer0.org3.example.com --csr.hosts localhost --tls.certfiles ${PWD}/fabric-ca/org3/tls-cert.pem
set +x
{ set +x; } 2>/dev/null
cp ${PWD}/../organizations/peerOrganizations/org3.example.com/peers/peer0.org3.example.com/tls/tlscacerts/* ${PWD}/../organizations/peerOrganizations/org3.example.com/peers/peer0.org3.example.com/tls/ca.crt
@ -92,7 +92,7 @@ function createOrg3 {
echo
set -x
fabric-ca-client enroll -u https://user1:user1pw@localhost:11054 --caname ca-org3 -M ${PWD}/../organizations/peerOrganizations/org3.example.com/users/User1@org3.example.com/msp --tls.certfiles ${PWD}/fabric-ca/org3/tls-cert.pem
set +x
{ set +x; } 2>/dev/null
cp ${PWD}/../organizations/peerOrganizations/org3.example.com/msp/config.yaml ${PWD}/../organizations/peerOrganizations/org3.example.com/users/User1@org3.example.com/msp/config.yaml
@ -103,7 +103,7 @@ function createOrg3 {
echo
set -x
fabric-ca-client enroll -u https://org3admin:org3adminpw@localhost:11054 --caname ca-org3 -M ${PWD}/../organizations/peerOrganizations/org3.example.com/users/Admin@org3.example.com/msp --tls.certfiles ${PWD}/fabric-ca/org3/tls-cert.pem
set +x
{ set +x; } 2>/dev/null
cp ${PWD}/../organizations/peerOrganizations/org3.example.com/msp/config.yaml ${PWD}/../organizations/peerOrganizations/org3.example.com/users/Admin@org3.example.com/msp/config.yaml

View file

@ -214,7 +214,7 @@ function createOrgs() {
set -x
cryptogen generate --config=./organizations/cryptogen/crypto-config-org1.yaml --output="organizations"
res=$?
set +x
{ set +x; } 2>/dev/null
if [ $res -ne 0 ]; then
echo $'\e[1;32m'"Failed to generate certificates..."$'\e[0m'
exit 1
@ -227,7 +227,7 @@ function createOrgs() {
set -x
cryptogen generate --config=./organizations/cryptogen/crypto-config-org2.yaml --output="organizations"
res=$?
set +x
{ set +x; } 2>/dev/null
if [ $res -ne 0 ]; then
echo $'\e[1;32m'"Failed to generate certificates..."$'\e[0m'
exit 1
@ -240,7 +240,7 @@ function createOrgs() {
set -x
cryptogen generate --config=./organizations/cryptogen/crypto-config-orderer.yaml --output="organizations"
res=$?
set +x
{ set +x; } 2>/dev/null
if [ $res -ne 0 ]; then
echo $'\e[1;32m'"Failed to generate certificates..."$'\e[0m'
exit 1
@ -329,7 +329,7 @@ function createConsortium() {
set -x
configtxgen -profile TwoOrgsOrdererGenesis -channelID system-channel -outputBlock ./system-genesis-block/genesis.block
res=$?
set +x
{ set +x; } 2>/dev/null
if [ $res -ne 0 ]; then
echo $'\e[1;32m'"Failed to generate orderer genesis block..."$'\e[0m'
exit 1

View file

@ -13,7 +13,7 @@ function createOrg1 {
set -x
fabric-ca-client enroll -u https://admin:adminpw@localhost:7054 --caname ca-org1 --tls.certfiles ${PWD}/organizations/fabric-ca/org1/tls-cert.pem
set +x
{ set +x; } 2>/dev/null
echo 'NodeOUs:
Enable: true
@ -35,21 +35,21 @@ function createOrg1 {
echo
set -x
fabric-ca-client register --caname ca-org1 --id.name peer0 --id.secret peer0pw --id.type peer --tls.certfiles ${PWD}/organizations/fabric-ca/org1/tls-cert.pem
set +x
{ set +x; } 2>/dev/null
echo
echo "Register user"
echo
set -x
fabric-ca-client register --caname ca-org1 --id.name user1 --id.secret user1pw --id.type client --tls.certfiles ${PWD}/organizations/fabric-ca/org1/tls-cert.pem
set +x
{ set +x; } 2>/dev/null
echo
echo "Register the org admin"
echo
set -x
fabric-ca-client register --caname ca-org1 --id.name org1admin --id.secret org1adminpw --id.type admin --tls.certfiles ${PWD}/organizations/fabric-ca/org1/tls-cert.pem
set +x
{ set +x; } 2>/dev/null
mkdir -p organizations/peerOrganizations/org1.example.com/peers
mkdir -p organizations/peerOrganizations/org1.example.com/peers/peer0.org1.example.com
@ -59,7 +59,7 @@ function createOrg1 {
echo
set -x
fabric-ca-client enroll -u https://peer0:peer0pw@localhost:7054 --caname ca-org1 -M ${PWD}/organizations/peerOrganizations/org1.example.com/peers/peer0.org1.example.com/msp --csr.hosts peer0.org1.example.com --tls.certfiles ${PWD}/organizations/fabric-ca/org1/tls-cert.pem
set +x
{ set +x; } 2>/dev/null
cp ${PWD}/organizations/peerOrganizations/org1.example.com/msp/config.yaml ${PWD}/organizations/peerOrganizations/org1.example.com/peers/peer0.org1.example.com/msp/config.yaml
@ -68,7 +68,7 @@ function createOrg1 {
echo
set -x
fabric-ca-client enroll -u https://peer0:peer0pw@localhost:7054 --caname ca-org1 -M ${PWD}/organizations/peerOrganizations/org1.example.com/peers/peer0.org1.example.com/tls --enrollment.profile tls --csr.hosts peer0.org1.example.com --csr.hosts localhost --tls.certfiles ${PWD}/organizations/fabric-ca/org1/tls-cert.pem
set +x
{ set +x; } 2>/dev/null
cp ${PWD}/organizations/peerOrganizations/org1.example.com/peers/peer0.org1.example.com/tls/tlscacerts/* ${PWD}/organizations/peerOrganizations/org1.example.com/peers/peer0.org1.example.com/tls/ca.crt
@ -92,7 +92,7 @@ function createOrg1 {
echo
set -x
fabric-ca-client enroll -u https://user1:user1pw@localhost:7054 --caname ca-org1 -M ${PWD}/organizations/peerOrganizations/org1.example.com/users/User1@org1.example.com/msp --tls.certfiles ${PWD}/organizations/fabric-ca/org1/tls-cert.pem
set +x
{ set +x; } 2>/dev/null
cp ${PWD}/organizations/peerOrganizations/org1.example.com/msp/config.yaml ${PWD}/organizations/peerOrganizations/org1.example.com/users/User1@org1.example.com/msp/config.yaml
@ -103,7 +103,7 @@ function createOrg1 {
echo
set -x
fabric-ca-client enroll -u https://org1admin:org1adminpw@localhost:7054 --caname ca-org1 -M ${PWD}/organizations/peerOrganizations/org1.example.com/users/Admin@org1.example.com/msp --tls.certfiles ${PWD}/organizations/fabric-ca/org1/tls-cert.pem
set +x
{ set +x; } 2>/dev/null
cp ${PWD}/organizations/peerOrganizations/org1.example.com/msp/config.yaml ${PWD}/organizations/peerOrganizations/org1.example.com/users/Admin@org1.example.com/msp/config.yaml
@ -123,7 +123,7 @@ function createOrg2 {
set -x
fabric-ca-client enroll -u https://admin:adminpw@localhost:8054 --caname ca-org2 --tls.certfiles ${PWD}/organizations/fabric-ca/org2/tls-cert.pem
set +x
{ set +x; } 2>/dev/null
echo 'NodeOUs:
Enable: true
@ -145,21 +145,21 @@ function createOrg2 {
echo
set -x
fabric-ca-client register --caname ca-org2 --id.name peer0 --id.secret peer0pw --id.type peer --tls.certfiles ${PWD}/organizations/fabric-ca/org2/tls-cert.pem
set +x
{ set +x; } 2>/dev/null
echo
echo "Register user"
echo
set -x
fabric-ca-client register --caname ca-org2 --id.name user1 --id.secret user1pw --id.type client --tls.certfiles ${PWD}/organizations/fabric-ca/org2/tls-cert.pem
set +x
{ set +x; } 2>/dev/null
echo
echo "Register the org admin"
echo
set -x
fabric-ca-client register --caname ca-org2 --id.name org2admin --id.secret org2adminpw --id.type admin --tls.certfiles ${PWD}/organizations/fabric-ca/org2/tls-cert.pem
set +x
{ set +x; } 2>/dev/null
mkdir -p organizations/peerOrganizations/org2.example.com/peers
mkdir -p organizations/peerOrganizations/org2.example.com/peers/peer0.org2.example.com
@ -169,7 +169,7 @@ function createOrg2 {
echo
set -x
fabric-ca-client enroll -u https://peer0:peer0pw@localhost:8054 --caname ca-org2 -M ${PWD}/organizations/peerOrganizations/org2.example.com/peers/peer0.org2.example.com/msp --csr.hosts peer0.org2.example.com --tls.certfiles ${PWD}/organizations/fabric-ca/org2/tls-cert.pem
set +x
{ set +x; } 2>/dev/null
cp ${PWD}/organizations/peerOrganizations/org2.example.com/msp/config.yaml ${PWD}/organizations/peerOrganizations/org2.example.com/peers/peer0.org2.example.com/msp/config.yaml
@ -178,7 +178,7 @@ function createOrg2 {
echo
set -x
fabric-ca-client enroll -u https://peer0:peer0pw@localhost:8054 --caname ca-org2 -M ${PWD}/organizations/peerOrganizations/org2.example.com/peers/peer0.org2.example.com/tls --enrollment.profile tls --csr.hosts peer0.org2.example.com --csr.hosts localhost --tls.certfiles ${PWD}/organizations/fabric-ca/org2/tls-cert.pem
set +x
{ set +x; } 2>/dev/null
cp ${PWD}/organizations/peerOrganizations/org2.example.com/peers/peer0.org2.example.com/tls/tlscacerts/* ${PWD}/organizations/peerOrganizations/org2.example.com/peers/peer0.org2.example.com/tls/ca.crt
@ -202,7 +202,7 @@ function createOrg2 {
echo
set -x
fabric-ca-client enroll -u https://user1:user1pw@localhost:8054 --caname ca-org2 -M ${PWD}/organizations/peerOrganizations/org2.example.com/users/User1@org2.example.com/msp --tls.certfiles ${PWD}/organizations/fabric-ca/org2/tls-cert.pem
set +x
{ set +x; } 2>/dev/null
cp ${PWD}/organizations/peerOrganizations/org2.example.com/msp/config.yaml ${PWD}/organizations/peerOrganizations/org2.example.com/users/User1@org2.example.com/msp/config.yaml
@ -213,7 +213,7 @@ function createOrg2 {
echo
set -x
fabric-ca-client enroll -u https://org2admin:org2adminpw@localhost:8054 --caname ca-org2 -M ${PWD}/organizations/peerOrganizations/org2.example.com/users/Admin@org2.example.com/msp --tls.certfiles ${PWD}/organizations/fabric-ca/org2/tls-cert.pem
set +x
{ set +x; } 2>/dev/null
cp ${PWD}/organizations/peerOrganizations/org2.example.com/msp/config.yaml ${PWD}/organizations/peerOrganizations/org2.example.com/users/Admin@org2.example.com/msp/config.yaml
@ -232,7 +232,7 @@ function createOrderer {
set -x
fabric-ca-client enroll -u https://admin:adminpw@localhost:9054 --caname ca-orderer --tls.certfiles ${PWD}/organizations/fabric-ca/ordererOrg/tls-cert.pem
set +x
{ set +x; } 2>/dev/null
echo 'NodeOUs:
Enable: true
@ -255,14 +255,14 @@ function createOrderer {
echo
set -x
fabric-ca-client register --caname ca-orderer --id.name orderer --id.secret ordererpw --id.type orderer --tls.certfiles ${PWD}/organizations/fabric-ca/ordererOrg/tls-cert.pem
set +x
{ set +x; } 2>/dev/null
echo
echo "Register the orderer admin"
echo
set -x
fabric-ca-client register --caname ca-orderer --id.name ordererAdmin --id.secret ordererAdminpw --id.type admin --tls.certfiles ${PWD}/organizations/fabric-ca/ordererOrg/tls-cert.pem
set +x
{ set +x; } 2>/dev/null
mkdir -p organizations/ordererOrganizations/example.com/orderers
mkdir -p organizations/ordererOrganizations/example.com/orderers/example.com
@ -274,7 +274,7 @@ function createOrderer {
echo
set -x
fabric-ca-client enroll -u https://orderer:ordererpw@localhost:9054 --caname ca-orderer -M ${PWD}/organizations/ordererOrganizations/example.com/orderers/orderer.example.com/msp --csr.hosts orderer.example.com --csr.hosts localhost --tls.certfiles ${PWD}/organizations/fabric-ca/ordererOrg/tls-cert.pem
set +x
{ set +x; } 2>/dev/null
cp ${PWD}/organizations/ordererOrganizations/example.com/msp/config.yaml ${PWD}/organizations/ordererOrganizations/example.com/orderers/orderer.example.com/msp/config.yaml
@ -283,7 +283,7 @@ function createOrderer {
echo
set -x
fabric-ca-client enroll -u https://orderer:ordererpw@localhost:9054 --caname ca-orderer -M ${PWD}/organizations/ordererOrganizations/example.com/orderers/orderer.example.com/tls --enrollment.profile tls --csr.hosts orderer.example.com --csr.hosts localhost --tls.certfiles ${PWD}/organizations/fabric-ca/ordererOrg/tls-cert.pem
set +x
{ set +x; } 2>/dev/null
cp ${PWD}/organizations/ordererOrganizations/example.com/orderers/orderer.example.com/tls/tlscacerts/* ${PWD}/organizations/ordererOrganizations/example.com/orderers/orderer.example.com/tls/ca.crt
cp ${PWD}/organizations/ordererOrganizations/example.com/orderers/orderer.example.com/tls/signcerts/* ${PWD}/organizations/ordererOrganizations/example.com/orderers/orderer.example.com/tls/server.crt
@ -303,7 +303,7 @@ function createOrderer {
echo
set -x
fabric-ca-client enroll -u https://ordererAdmin:ordererAdminpw@localhost:9054 --caname ca-orderer -M ${PWD}/organizations/ordererOrganizations/example.com/users/Admin@example.com/msp --tls.certfiles ${PWD}/organizations/fabric-ca/ordererOrg/tls-cert.pem
set +x
{ set +x; } 2>/dev/null
cp ${PWD}/organizations/ordererOrganizations/example.com/msp/config.yaml ${PWD}/organizations/ordererOrganizations/example.com/users/Admin@example.com/msp/config.yaml

View file

@ -22,7 +22,7 @@ createChannelTx() {
set -x
configtxgen -profile TwoOrgsChannel -outputCreateChannelTx ./channel-artifacts/${CHANNEL_NAME}.tx -channelID $CHANNEL_NAME
res=$?
set +x
{ set +x; } 2>/dev/null
if [ $res -ne 0 ]; then
echo "Failed to generate channel configuration transaction..."
exit 1
@ -39,7 +39,7 @@ createAncorPeerTx() {
set -x
configtxgen -profile TwoOrgsChannel -outputAnchorPeersUpdate ./channel-artifacts/${orgmsp}anchors.tx -channelID $CHANNEL_NAME -asOrg ${orgmsp}
res=$?
set +x
{ set +x; } 2>/dev/null
if [ $res -ne 0 ]; then
echo "Failed to generate anchor peer update transaction for ${orgmsp}..."
exit 1
@ -58,7 +58,7 @@ createChannel() {
set -x
peer channel create -o localhost:7050 -c $CHANNEL_NAME --ordererTLSHostnameOverride orderer.example.com -f ./channel-artifacts/${CHANNEL_NAME}.tx --outputBlock ./channel-artifacts/${CHANNEL_NAME}.block --tls --cafile $ORDERER_CA >&log.txt
res=$?
set +x
{ set +x; } 2>/dev/null
let rc=$res
COUNTER=$(expr $COUNTER + 1)
done
@ -81,7 +81,7 @@ joinChannel() {
set -x
peer channel join -b ./channel-artifacts/$CHANNEL_NAME.block >&log.txt
res=$?
set +x
{ set +x; } 2>/dev/null
let rc=$res
COUNTER=$(expr $COUNTER + 1)
done
@ -101,7 +101,7 @@ updateAnchorPeers() {
set -x
peer channel update -o localhost:7050 --ordererTLSHostnameOverride orderer.example.com -c $CHANNEL_NAME -f ./channel-artifacts/${CORE_PEER_LOCALMSPID}anchors.tx --tls --cafile $ORDERER_CA >&log.txt
res=$?
set +x
{ set +x; } 2>/dev/null
let rc=$res
COUNTER=$(expr $COUNTER + 1)
done

View file

@ -153,7 +153,7 @@ packageChaincode() {
set -x
peer lifecycle chaincode package ${CC_NAME}.tar.gz --path ${CC_SRC_PATH} --lang ${CC_RUNTIME_LANGUAGE} --label ${CC_NAME}_${CC_VERSION} >&log.txt
res=$?
set +x
{ set +x; } 2>/dev/null
cat log.txt
verifyResult $res "Chaincode packaging on peer0.org${ORG} has failed"
echo "===================== Chaincode is packaged on peer0.org${ORG} ===================== "
@ -167,7 +167,7 @@ installChaincode() {
set -x
peer lifecycle chaincode install ${CC_NAME}.tar.gz >&log.txt
res=$?
set +x
{ set +x; } 2>/dev/null
cat log.txt
verifyResult $res "Chaincode installation on peer0.org${ORG} has failed"
echo "===================== Chaincode is installed on peer0.org${ORG} ===================== "
@ -181,7 +181,7 @@ queryInstalled() {
set -x
peer lifecycle chaincode queryinstalled >&log.txt
res=$?
set +x
{ set +x; } 2>/dev/null
cat log.txt
PACKAGE_ID=$(sed -n "/${CC_NAME}_${CC_VERSION}/{s/^Package ID: //; s/, Label:.*$//; p;}" log.txt)
verifyResult $res "Query installed on peer0.org${ORG} has failed"
@ -196,7 +196,7 @@ approveForMyOrg() {
set -x
peer lifecycle chaincode approveformyorg -o localhost:7050 --ordererTLSHostnameOverride orderer.example.com --tls --cafile $ORDERER_CA --channelID $CHANNEL_NAME --name ${CC_NAME} --version ${CC_VERSION} --package-id ${PACKAGE_ID} --sequence ${CC_SEQUENCE} ${INIT_REQUIRED} ${CC_END_POLICY} ${CC_COLL_CONFIG} >&log.txt
res=$?
set +x
{ set +x; } 2>/dev/null
cat log.txt
verifyResult $res "Chaincode definition approved on peer0.org${ORG} on channel '$CHANNEL_NAME' failed"
echo "===================== Chaincode definition approved on peer0.org${ORG} on channel '$CHANNEL_NAME' ===================== "
@ -219,7 +219,7 @@ checkCommitReadiness() {
set -x
peer lifecycle chaincode checkcommitreadiness --channelID $CHANNEL_NAME --name ${CC_NAME} --version ${CC_VERSION} --sequence ${CC_SEQUENCE} ${INIT_REQUIRED} ${CC_END_POLICY} ${CC_COLL_CONFIG} --output json >&log.txt
res=$?
set +x
{ set +x; } 2>/dev/null
let rc=0
for var in "$@"; do
grep "$var" log.txt &>/dev/null || let rc=1
@ -249,7 +249,7 @@ commitChaincodeDefinition() {
set -x
peer lifecycle chaincode commit -o localhost:7050 --ordererTLSHostnameOverride orderer.example.com --tls --cafile $ORDERER_CA --channelID $CHANNEL_NAME --name ${CC_NAME} $PEER_CONN_PARMS --version ${CC_VERSION} --sequence ${CC_SEQUENCE} ${INIT_REQUIRED} ${CC_END_POLICY} ${CC_COLL_CONFIG} >&log.txt
res=$?
set +x
{ set +x; } 2>/dev/null
cat log.txt
verifyResult $res "Chaincode definition commit failed on peer0.org${ORG} on channel '$CHANNEL_NAME' failed"
echo "===================== Chaincode definition committed on channel '$CHANNEL_NAME' ===================== "
@ -272,7 +272,7 @@ queryCommitted() {
set -x
peer lifecycle chaincode querycommitted --channelID $CHANNEL_NAME --name ${CC_NAME} >&log.txt
res=$?
set +x
{ set +x; } 2>/dev/null
test $res -eq 0 && VALUE=$(cat log.txt | grep -o '^Version: '$CC_VERSION', Sequence: [0-9]*, Endorsement Plugin: escc, Validation Plugin: vscc')
test "$VALUE" = "$EXPECTED_RESULT" && let rc=0
COUNTER=$(expr $COUNTER + 1)
@ -303,7 +303,7 @@ chaincodeInvokeInit() {
echo invoke fcn call:${fcn_call}
peer chaincode invoke -o localhost:7050 --ordererTLSHostnameOverride orderer.example.com --tls --cafile $ORDERER_CA -C $CHANNEL_NAME -n ${CC_NAME} $PEER_CONN_PARMS --isInit -c ${fcn_call} >&log.txt
res=$?
set +x
{ set +x; } 2>/dev/null
cat log.txt
verifyResult $res "Invoke execution on $PEERS failed "
echo "===================== Invoke transaction successful on $PEERS on channel '$CHANNEL_NAME' ===================== "
@ -324,7 +324,7 @@ chaincodeQuery() {
set -x
peer chaincode query -C $CHANNEL_NAME -n ${CC_NAME} -c '{"Args":["queryAllCars"]}' >&log.txt
res=$?
set +x
{ set +x; } 2>/dev/null
let rc=$res
COUNTER=$(expr $COUNTER + 1)
done

View file

@ -40,12 +40,12 @@ fetchChannelConfig() {
echo "Fetching the most recent configuration block for the channel"
set -x
peer channel fetch config config_block.pb -o orderer.example.com:7050 --ordererTLSHostnameOverride orderer.example.com -c $CHANNEL --tls --cafile $ORDERER_CA
set +x
{ set +x; } 2>/dev/null
echo "Decoding config block to JSON and isolating config to ${OUTPUT}"
set -x
configtxlator proto_decode --input config_block.pb --type common.Block | jq .data.data[0].payload.data.config >"${OUTPUT}"
set +x
{ set +x; } 2>/dev/null
}
# createConfigUpdate <channel_id> <original_config.json> <modified_config.json> <output.pb>
@ -64,7 +64,7 @@ createConfigUpdate() {
configtxlator proto_decode --input config_update.pb --type common.ConfigUpdate >config_update.json
echo '{"payload":{"header":{"channel_header":{"channel_id":"'$CHANNEL'", "type":2}},"data":{"config_update":'$(cat config_update.json)'}}}' | jq . >config_update_in_envelope.json
configtxlator proto_encode --input config_update_in_envelope.json --type common.Envelope >"${OUTPUT}"
set +x
{ set +x; } 2>/dev/null
}
# signConfigtxAsPeerOrg <org> <configtx.pb>
@ -75,7 +75,7 @@ signConfigtxAsPeerOrg() {
setGlobals $PEERORG
set -x
peer channel signconfigtx -f "${TX}"
set +x
{ set +x; } 2>/dev/null
}
echo
@ -88,7 +88,7 @@ fetchChannelConfig 1 ${CHANNEL_NAME} config.json
# Modify the configuration to append the new org
set -x
jq -s '.[0] * {"channel_group":{"groups":{"Application":{"groups": {"Org3MSP":.[1]}}}}}' config.json ./organizations/peerOrganizations/org3.example.com/org3.json > modified_config.json
set +x
{ set +x; } 2>/dev/null
# Compute a config update, based on the differences between config.json and modified_config.json, write it as a transaction to org3_update_in_envelope.pb
createConfigUpdate ${CHANNEL_NAME} config.json modified_config.json org3_update_in_envelope.pb
@ -107,7 +107,7 @@ echo
setGlobals 2
set -x
peer channel update -f org3_update_in_envelope.pb -c ${CHANNEL_NAME} -o orderer.example.com:7050 --ordererTLSHostnameOverride orderer.example.com --tls --cafile ${ORDERER_CA}
set +x
{ set +x; } 2>/dev/null
echo
echo "========= Config transaction to add org3 to network submitted! =========== "

View file

@ -36,7 +36,7 @@ joinChannelWithRetry() {
set -x
peer channel join -b $CHANNEL_NAME.block >&log.txt
res=$?
set +x
{ set +x; } 2>/dev/null
cat log.txt
if [ $res -ne 0 -a $COUNTER -lt $MAX_RETRY ]; then
COUNTER=$(expr $COUNTER + 1)
@ -54,7 +54,7 @@ echo "Fetching channel config block from orderer..."
set -x
peer channel fetch 0 $CHANNEL_NAME.block -o orderer.example.com:7050 --ordererTLSHostnameOverride orderer.example.com -c $CHANNEL_NAME --tls --cafile $ORDERER_CA >&log.txt
res=$?
set +x
{ set +x; } 2>/dev/null
cat log.txt
verifyResult $res "Fetching config block from orderer has Failed"