From b933b35a95fc7df68a9ebd5687467a96f0ed868c Mon Sep 17 00:00:00 2001 From: fraVlaca Date: Mon, 6 Sep 2021 11:01:22 +0100 Subject: [PATCH] updated chaincodes for asset-trnsfer-basic in order to show good example on how achieving determinism in json Signed-off-by: fraVlaca --- .../chaincode-go/chaincode/smartcontract.go | 10 +++--- .../samples/assettransfer/AssetTransfer.java | 33 +++++++++++-------- .../assettransfer/AssetTransferTest.java | 15 +++++---- .../chaincode-javascript/lib/assetTransfer.js | 23 +++++++++---- .../chaincode-javascript/package.json | 4 ++- .../chaincode-typescript/package.json | 4 ++- .../chaincode-typescript/src/assetTransfer.ts | 21 ++++++++---- 7 files changed, 71 insertions(+), 39 deletions(-) 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..1a07921b 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,9 @@ package org.hyperledger.fabric.samples.assettransfer; import java.util.ArrayList; import java.util.List; +import com.fasterxml.jackson.core.JsonProcessingException; +import java.io.IOException; + import org.hyperledger.fabric.contract.Context; import org.hyperledger.fabric.contract.ContractInterface; import org.hyperledger.fabric.contract.annotation.Contact; @@ -51,7 +54,7 @@ public final class AssetTransfer implements ContractInterface { * @param ctx the transaction context */ @Transaction(intent = Transaction.TYPE.SUBMIT) - public void InitLedger(final Context ctx) { + public void InitLedger(final Context ctx) throws JsonProcessingException { ChaincodeStub stub = ctx.getStub(); CreateAsset(ctx, "asset1", "blue", 5, "Tomoko", 300); @@ -76,7 +79,7 @@ public final class AssetTransfer implements ContractInterface { */ @Transaction(intent = Transaction.TYPE.SUBMIT) public Asset CreateAsset(final Context ctx, final String assetID, final String color, final int size, - final String owner, final int appraisedValue) { + final String owner, final int appraisedValue) throws JsonProcessingException { ChaincodeStub stub = ctx.getStub(); if (AssetExists(ctx, assetID)) { @@ -86,8 +89,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; } @@ -100,7 +104,7 @@ public final class AssetTransfer implements ContractInterface { * @return the asset found on the ledger if there was one */ @Transaction(intent = Transaction.TYPE.EVALUATE) - public Asset ReadAsset(final Context ctx, final String assetID) { + public Asset ReadAsset(final Context ctx, final String assetID) throws JsonProcessingException, IOException { ChaincodeStub stub = ctx.getStub(); String assetJSON = stub.getStringState(assetID); @@ -127,7 +131,7 @@ public final class AssetTransfer implements ContractInterface { */ @Transaction(intent = Transaction.TYPE.SUBMIT) public Asset UpdateAsset(final Context ctx, final String assetID, final String color, final int size, - final String owner, final int appraisedValue) { + final String owner, final int appraisedValue) throws JsonProcessingException { ChaincodeStub stub = ctx.getStub(); if (!AssetExists(ctx, assetID)) { @@ -137,9 +141,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; } @@ -186,7 +190,7 @@ public final class AssetTransfer implements ContractInterface { * @return the updated asset */ @Transaction(intent = Transaction.TYPE.SUBMIT) - public Asset TransferAsset(final Context ctx, final String assetID, final String newOwner) { + public Asset TransferAsset(final Context ctx, final String assetID, final String newOwner) throws JsonProcessingException { ChaincodeStub stub = ctx.getStub(); String assetJSON = stub.getStringState(assetID); @@ -199,8 +203,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; } @@ -212,7 +217,7 @@ public final class AssetTransfer implements ContractInterface { * @return array of assets found on the ledger */ @Transaction(intent = Transaction.TYPE.EVALUATE) - public String GetAllAssets(final Context ctx) { + public String GetAllAssets(final Context ctx) throws JsonProcessingException, IOException { ChaincodeStub stub = ctx.getStub(); List queryResults = new ArrayList(); @@ -225,8 +230,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-java/src/test/java/org/hyperledger/fabric/samples/assettransfer/AssetTransferTest.java b/asset-transfer-basic/chaincode-java/src/test/java/org/hyperledger/fabric/samples/assettransfer/AssetTransferTest.java index cbbb172f..cd82ab1d 100644 --- a/asset-transfer-basic/chaincode-java/src/test/java/org/hyperledger/fabric/samples/assettransfer/AssetTransferTest.java +++ b/asset-transfer-basic/chaincode-java/src/test/java/org/hyperledger/fabric/samples/assettransfer/AssetTransferTest.java @@ -24,6 +24,9 @@ import org.junit.jupiter.api.Nested; import org.junit.jupiter.api.Test; import org.mockito.InOrder; +import com.fasterxml.jackson.core.JsonProcessingException; +import java.io.IOException; + public final class AssetTransferTest { private final class MockKeyValue implements KeyValue { @@ -109,7 +112,7 @@ public final class AssetTransferTest { class InvokeReadAssetTransaction { @Test - public void whenAssetExists() { + public void whenAssetExists() throws JsonProcessingException, IOException { AssetTransfer contract = new AssetTransfer(); Context ctx = mock(Context.class); ChaincodeStub stub = mock(ChaincodeStub.class); @@ -141,7 +144,7 @@ public final class AssetTransferTest { } @Test - void invokeInitLedgerTransaction() { + void invokeInitLedgerTransaction() throws JsonProcessingException { AssetTransfer contract = new AssetTransfer(); Context ctx = mock(Context.class); ChaincodeStub stub = mock(ChaincodeStub.class); @@ -180,7 +183,7 @@ public final class AssetTransferTest { } @Test - public void whenAssetDoesNotExist() { + public void whenAssetDoesNotExist() throws JsonProcessingException { AssetTransfer contract = new AssetTransfer(); Context ctx = mock(Context.class); ChaincodeStub stub = mock(ChaincodeStub.class); @@ -194,7 +197,7 @@ public final class AssetTransferTest { } @Test - void invokeGetAllAssetsTransaction() { + void invokeGetAllAssetsTransaction() throws JsonProcessingException, IOException { AssetTransfer contract = new AssetTransfer(); Context ctx = mock(Context.class); ChaincodeStub stub = mock(ChaincodeStub.class); @@ -216,7 +219,7 @@ public final class AssetTransferTest { class TransferAssetTransaction { @Test - public void whenAssetExists() { + public void whenAssetExists() throws JsonProcessingException { AssetTransfer contract = new AssetTransfer(); Context ctx = mock(Context.class); ChaincodeStub stub = mock(ChaincodeStub.class); @@ -251,7 +254,7 @@ public final class AssetTransferTest { class UpdateAssetTransaction { @Test - public void whenAssetExists() { + public void whenAssetExists() throws JsonProcessingException { AssetTransfer contract = new AssetTransfer(); Context ctx = mock(Context.class); ChaincodeStub stub = mock(ChaincodeStub.class); diff --git a/asset-transfer-basic/chaincode-javascript/lib/assetTransfer.js b/asset-transfer-basic/chaincode-javascript/lib/assetTransfer.js index a1a27aa4..fba9dc5b 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,9 +61,12 @@ 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)))); + } } // CreateAsset issues a new asset to the world state with given details. @@ -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..475e2b68 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() +const stringify = require('json-stringify-deterministic') +const sortKeysRecursive = require('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);