diff --git a/asset-transfer-basic/chaincode-go/chaincode/smartcontract.go b/asset-transfer-basic/chaincode-go/chaincode/smartcontract.go index 71e8dd84..7373ad42 100644 --- a/asset-transfer-basic/chaincode-go/chaincode/smartcontract.go +++ b/asset-transfer-basic/chaincode-go/chaincode/smartcontract.go @@ -13,12 +13,14 @@ type SmartContract struct { } // Asset describes basic details of what makes up a simple asset +//Insert struct field in alphabetic order => to achieve determinism accross languages +// golang keeps the order when marshal to json but doesn't order automatically type Asset struct { + AppraisedValue int `json:"AppraisedValue"` + Color string `json:"Color"` ID string `json:"ID"` - Color string `json:"color"` - Size int `json:"size"` - Owner string `json:"owner"` - AppraisedValue int `json:"appraisedValue"` + Owner string `json:"Owner"` + Size int `json:"Size"` } // InitLedger adds a base set of assets to the ledger diff --git a/asset-transfer-basic/chaincode-java/src/main/java/org/hyperledger/fabric/samples/assettransfer/AssetTransfer.java b/asset-transfer-basic/chaincode-java/src/main/java/org/hyperledger/fabric/samples/assettransfer/AssetTransfer.java index 465d4694..fa7aba67 100644 --- a/asset-transfer-basic/chaincode-java/src/main/java/org/hyperledger/fabric/samples/assettransfer/AssetTransfer.java +++ b/asset-transfer-basic/chaincode-java/src/main/java/org/hyperledger/fabric/samples/assettransfer/AssetTransfer.java @@ -7,6 +7,7 @@ package org.hyperledger.fabric.samples.assettransfer; import java.util.ArrayList; import java.util.List; + import org.hyperledger.fabric.contract.Context; import org.hyperledger.fabric.contract.ContractInterface; import org.hyperledger.fabric.contract.annotation.Contact; @@ -86,8 +87,9 @@ public final class AssetTransfer implements ContractInterface { } Asset asset = new Asset(assetID, color, size, owner, appraisedValue); - String assetJSON = genson.serialize(asset); - stub.putStringState(assetID, assetJSON); + //Use Genson to convert the Asset into string, sort it alphabetically and serialize it into a json string + String sortedJson = genson.serialize(asset); + stub.putStringState(assetID, sortedJson); return asset; } @@ -137,9 +139,9 @@ public final class AssetTransfer implements ContractInterface { } Asset newAsset = new Asset(assetID, color, size, owner, appraisedValue); - String newAssetJSON = genson.serialize(newAsset); - stub.putStringState(assetID, newAssetJSON); - + //Use Genson to convert the Asset into string, sort it alphabetically and serialize it into a json string + String sortedJson = genson.serialize(newAsset); + stub.putStringState(assetID, sortedJson); return newAsset; } @@ -199,8 +201,9 @@ public final class AssetTransfer implements ContractInterface { Asset asset = genson.deserialize(assetJSON, Asset.class); Asset newAsset = new Asset(asset.getAssetID(), asset.getColor(), asset.getSize(), newOwner, asset.getAppraisedValue()); - String newAssetJSON = genson.serialize(newAsset); - stub.putStringState(assetID, newAssetJSON); + //Use a Genson to conver the Asset into string, sort it alphabetically and serialize it into a json string + String sortedJson = genson.serialize(newAsset); + stub.putStringState(assetID, sortedJson); return newAsset; } @@ -225,8 +228,8 @@ public final class AssetTransfer implements ContractInterface { for (KeyValue result: results) { Asset asset = genson.deserialize(result.getStringValue(), Asset.class); + System.out.println(asset); queryResults.add(asset); - System.out.println(asset.toString()); } final String response = genson.serialize(queryResults); diff --git a/asset-transfer-basic/chaincode-javascript/lib/assetTransfer.js b/asset-transfer-basic/chaincode-javascript/lib/assetTransfer.js index a1a27aa4..22d109c8 100644 --- a/asset-transfer-basic/chaincode-javascript/lib/assetTransfer.js +++ b/asset-transfer-basic/chaincode-javascript/lib/assetTransfer.js @@ -6,6 +6,9 @@ 'use strict'; +// Deterministic JSON.stringify() +const stringify = require('json-stringify-deterministic'); +const sortKeysRecursive = require('sort-keys-recursive'); const { Contract } = require('fabric-contract-api'); class AssetTransfer extends Contract { @@ -58,8 +61,11 @@ class AssetTransfer extends Contract { for (const asset of assets) { asset.docType = 'asset'; - await ctx.stub.putState(asset.ID, Buffer.from(JSON.stringify(asset))); - console.info(`Asset ${asset.ID} initialized`); + // example of how to write to world state deterministically + // use convetion of alphabetic order + // we insert data in alphabetic order using 'json-stringify-deterministic' and 'sort-keys-recursive' + // when retrieving data, in any lang, the order of data will be the same and consequently also the corresonding hash + await ctx.stub.putState(asset.ID, Buffer.from(stringify(sortKeysRecursive(asset)))); } } @@ -77,7 +83,8 @@ class AssetTransfer extends Contract { Owner: owner, AppraisedValue: appraisedValue, }; - await ctx.stub.putState(id, Buffer.from(JSON.stringify(asset))); + //we insert data in alphabetic order using 'json-stringify-deterministic' and 'sort-keys-recursive' + await ctx.stub.putState(id, Buffer.from(stringify(sortKeysRecursive(asset)))); return JSON.stringify(asset); } @@ -105,7 +112,8 @@ class AssetTransfer extends Contract { Owner: owner, AppraisedValue: appraisedValue, }; - return ctx.stub.putState(id, Buffer.from(JSON.stringify(updatedAsset))); + // we insert data in alphabetic order using 'json-stringify-deterministic' and 'sort-keys-recursive' + return ctx.stub.putState(id, Buffer.from(stringify(sortKeysRecursive(updatedAsset)))); } // DeleteAsset deletes an given asset from the world state. @@ -128,7 +136,8 @@ class AssetTransfer extends Contract { const assetString = await this.ReadAsset(ctx, id); const asset = JSON.parse(assetString); asset.Owner = newOwner; - return ctx.stub.putState(id, Buffer.from(JSON.stringify(asset))); + // we insert data in alphabetic order using 'json-stringify-deterministic' and 'sort-keys-recursive' + return ctx.stub.putState(id, Buffer.from(stringify(sortKeysRecursive(asset)))); } // GetAllAssets returns all assets found in the world state. @@ -146,7 +155,7 @@ class AssetTransfer extends Contract { console.log(err); record = strValue; } - allResults.push({ Key: result.value.key, Record: record }); + allResults.push(record); result = await iterator.next(); } return JSON.stringify(allResults); diff --git a/asset-transfer-basic/chaincode-javascript/package.json b/asset-transfer-basic/chaincode-javascript/package.json index 9cc22242..430f9f61 100644 --- a/asset-transfer-basic/chaincode-javascript/package.json +++ b/asset-transfer-basic/chaincode-javascript/package.json @@ -18,7 +18,9 @@ "license": "Apache-2.0", "dependencies": { "fabric-contract-api": "^2.0.0", - "fabric-shim": "^2.0.0" + "fabric-shim": "^2.0.0", + "json-stringify-deterministic": "^1.0.1", + "sort-keys-recursive": "^2.0.0" }, "devDependencies": { "chai": "^4.1.2", diff --git a/asset-transfer-basic/chaincode-typescript/package.json b/asset-transfer-basic/chaincode-typescript/package.json index 2b681fc1..befe4a5f 100644 --- a/asset-transfer-basic/chaincode-typescript/package.json +++ b/asset-transfer-basic/chaincode-typescript/package.json @@ -22,7 +22,9 @@ "license": "Apache-2.0", "dependencies": { "fabric-contract-api": "^2.0.0", - "fabric-shim": "^2.0.0" + "fabric-shim": "^2.0.0", + "json-stringify-deterministic":"^1.0.0", + "sort-keys-recursive":"^2.0.0" }, "devDependencies": { "@types/chai": "^4.1.7", diff --git a/asset-transfer-basic/chaincode-typescript/src/assetTransfer.ts b/asset-transfer-basic/chaincode-typescript/src/assetTransfer.ts index 55b19b77..db2f9a9e 100644 --- a/asset-transfer-basic/chaincode-typescript/src/assetTransfer.ts +++ b/asset-transfer-basic/chaincode-typescript/src/assetTransfer.ts @@ -1,7 +1,9 @@ /* * SPDX-License-Identifier: Apache-2.0 */ - +// Deterministic JSON.stringify() +import * as stringify from 'json-stringify-deterministic'; +import * as sortKeysRecursive from 'sort-keys-recursive'; import {Context, Contract, Info, Returns, Transaction} from 'fabric-contract-api'; import {Asset} from './asset'; @@ -57,7 +59,11 @@ export class AssetTransferContract extends Contract { for (const asset of assets) { asset.docType = 'asset'; - await ctx.stub.putState(asset.ID, Buffer.from(JSON.stringify(asset))); + // example of how to write to world state deterministically + // use convetion of alphabetic order + // we insert data in alphabetic order using 'json-stringify-deterministic' and 'sort-keys-recursive' + // when retrieving data, in any lang, the order of data will be the same and consequently also the corresonding hash + await ctx.stub.putState(asset.ID, Buffer.from(stringify(sortKeysRecursive(asset)))); console.info(`Asset ${asset.ID} initialized`); } } @@ -77,7 +83,8 @@ export class AssetTransferContract extends Contract { Owner: owner, AppraisedValue: appraisedValue, }; - await ctx.stub.putState(id, Buffer.from(JSON.stringify(asset))); + // we insert data in alphabetic order using 'json-stringify-deterministic' and 'sort-keys-recursive' + await ctx.stub.putState(id, Buffer.from(stringify(sortKeysRecursive(asset)))); } // ReadAsset returns the asset stored in the world state with given id. @@ -106,7 +113,8 @@ export class AssetTransferContract extends Contract { Owner: owner, AppraisedValue: appraisedValue, }; - return ctx.stub.putState(id, Buffer.from(JSON.stringify(updatedAsset))); + // we insert data in alphabetic order using 'json-stringify-deterministic' and 'sort-keys-recursive' + return ctx.stub.putState(id, Buffer.from(stringify(sortKeysRecursive(updatedAsset)))); } // DeleteAsset deletes an given asset from the world state. @@ -133,7 +141,8 @@ export class AssetTransferContract extends Contract { const assetString = await this.ReadAsset(ctx, id); const asset = JSON.parse(assetString); asset.Owner = newOwner; - await ctx.stub.putState(id, Buffer.from(JSON.stringify(asset))); + // we insert data in alphabetic order using 'json-stringify-deterministic' and 'sort-keys-recursive' + await ctx.stub.putState(id, Buffer.from(stringify(sortKeysRecursive(asset)))); } // GetAllAssets returns all assets found in the world state. @@ -153,7 +162,7 @@ export class AssetTransferContract extends Contract { console.log(err); record = strValue; } - allResults.push({Key: result.value.key, Record: record}); + allResults.push(record); result = await iterator.next(); } return JSON.stringify(allResults); diff --git a/test-network/README.md b/test-network/README.md index 418e467e..f861db94 100644 --- a/test-network/README.md +++ b/test-network/README.md @@ -3,3 +3,22 @@ You can use the `./network.sh` script to stand up a simple Fabric test network. The test network has two peer organizations with one peer each and a single node raft ordering service. You can also use the `./network.sh` script to create channels and deploy chaincode. For more information, see [Using the Fabric test network](https://hyperledger-fabric.readthedocs.io/en/latest/test_network.html). The test network is being introduced in Fabric v2.0 as the long term replacement for the `first-network` sample. Before you can deploy the test network, you need to follow the instructions to [Install the Samples, Binaries and Docker Images](https://hyperledger-fabric.readthedocs.io/en/latest/install.html) in the Hyperledger Fabric documentation. + +## Using the Peer commands +The `setOrgEnv.sh` script can be used to setup the environment variables for the ogrganziations, this will will help to be able to use the `peer` commands directly. + +First, ensure that the peer binaries are on your path, and the Fabric Config path is set Assuming that you're in the `test-network` directory. + +```bash + export PATH=$PATH:$(realpath ../bin) + export FABRIC_CFG_PATH=$(realpath ../config) +``` + +You can then set up the environment variables for each organization. The `./setOrgEnv.sh` command is designed to be run as follows. + +```bash +export $(./setOrgEnv.sh Org2 | xargs) +``` + +You will now be able to run the `peer` commands in the context of Org2. If a different command prompt you can run the same command with Org1 instead. +The `setOrgEnv` script outputs a series of `=` strings. These can then be fed into the export command for your current shell \ No newline at end of file diff --git a/test-network/docker/docker-compose-test-net.yaml b/test-network/docker/docker-compose-test-net.yaml index 86fc85bf..c070f82e 100644 --- a/test-network/docker/docker-compose-test-net.yaml +++ b/test-network/docker/docker-compose-test-net.yaml @@ -151,7 +151,6 @@ services: working_dir: /opt/gopath/src/github.com/hyperledger/fabric/peer command: /bin/bash volumes: - - /var/run/:/host/var/run/ - ../organizations:/opt/gopath/src/github.com/hyperledger/fabric/peer/organizations - ../scripts:/opt/gopath/src/github.com/hyperledger/fabric/peer/scripts/ depends_on: diff --git a/test-network/setOrgEnv.sh b/test-network/setOrgEnv.sh new file mode 100755 index 00000000..b66074fb --- /dev/null +++ b/test-network/setOrgEnv.sh @@ -0,0 +1,59 @@ +#!/bin/bash +# +# SPDX-License-Identifier: Apache-2.0 + + + + +# default to using Org1 +ORG=${1:-Org1} + +# Exit on first error, print all commands. +set -e +set -o pipefail + +# Where am I? +DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )/.." && pwd )" + +ORDERER_CA=${DIR}/test-network/organizations/ordererOrganizations/example.com/orderers/orderer.example.com/msp/tlscacerts/tlsca.example.com-cert.pem +PEER0_ORG1_CA=${DIR}/test-network/organizations/peerOrganizations/org1.example.com/peers/peer0.org1.example.com/tls/ca.crt +PEER0_ORG2_CA=${DIR}/test-network/organizations/peerOrganizations/org2.example.com/peers/peer0.org2.example.com/tls/ca.crt +PEER0_ORG3_CA=${DIR}/test-network/organizations/peerOrganizations/org3.example.com/peers/peer0.org3.example.com/tls/ca.crt + + +if [[ ${ORG,,} == "org1" || ${ORG,,} == "digibank" ]]; then + + CORE_PEER_LOCALMSPID=Org1MSP + CORE_PEER_MSPCONFIGPATH=${DIR}/test-network/organizations/peerOrganizations/org1.example.com/users/Admin@org1.example.com/msp + CORE_PEER_ADDRESS=localhost:7051 + CORE_PEER_TLS_ROOTCERT_FILE=${DIR}/test-network/organizations/peerOrganizations/org1.example.com/peers/peer0.org1.example.com/tls/ca.crt + +elif [[ ${ORG,,} == "org2" || ${ORG,,} == "magnetocorp" ]]; then + + CORE_PEER_LOCALMSPID=Org2MSP + CORE_PEER_MSPCONFIGPATH=${DIR}/test-network/organizations/peerOrganizations/org2.example.com/users/Admin@org2.example.com/msp + CORE_PEER_ADDRESS=localhost:9051 + CORE_PEER_TLS_ROOTCERT_FILE=${DIR}/test-network/organizations/peerOrganizations/org2.example.com/peers/peer0.org2.example.com/tls/ca.crt + +else + echo "Unknown \"$ORG\", please choose Org1/Digibank or Org2/Magnetocorp" + echo "For example to get the environment variables to set upa Org2 shell environment run: ./setOrgEnv.sh Org2" + echo + echo "This can be automated to set them as well with:" + echo + echo 'export $(./setOrgEnv.sh Org2 | xargs)' + exit 1 +fi + +# output the variables that need to be set +echo "CORE_PEER_TLS_ENABLED=true" +echo "ORDERER_CA=${ORDERER_CA}" +echo "PEER0_ORG1_CA=${PEER0_ORG1_CA}" +echo "PEER0_ORG2_CA=${PEER0_ORG2_CA}" +echo "PEER0_ORG3_CA=${PEER0_ORG3_CA}" + +echo "CORE_PEER_MSPCONFIGPATH=${CORE_PEER_MSPCONFIGPATH}" +echo "CORE_PEER_ADDRESS=${CORE_PEER_ADDRESS}" +echo "CORE_PEER_TLS_ROOTCERT_FILE=${CORE_PEER_TLS_ROOTCERT_FILE}" + +echo "CORE_PEER_LOCALMSPID=${CORE_PEER_LOCALMSPID}" \ No newline at end of file