Open yurii-uhlanov-intellecteu opened 1 year ago
When writing and re-reading JSON in any platform, the before and after are not guaranteed to match exactly, per the JSON spec. This is true with most JSON parsers, with CouchDB by itself, and when using CouchDB with Fabric.
The behavior is documented here: https://hyperledger-fabric.readthedocs.io/en/latest/couchdb_as_state_database.html#reading-and-writing-json-data
The intended use of GetPrivateDataHash() is to verify that a passed pre-image's hash matches a previously stored pre-image's hash on an organization's peer that doesn't have the private data, not to check against the re-retrieved value on a peer that has the private data, this is described here: https://hyperledger-fabric.readthedocs.io/en/latest/private-data/private-data.html?highlight=getprivatedatahash#sharing-private-data
Therefore this is working as expected and as documented. If you don't think the documentation describes it well enough, this issue can be used to clarify the documentation.
Note that you can accomplish what you want by not saving JSON to CouchDB. Simple string key/value pairs will remain the same when saved and re-read (using either LevelDB or CouchDB).
Description
JSON input with trailing zero(s) is being added to a Private Data Collection (PDC):
Example:
JSON Input before hashing
{"assetId":"BUG","value":1.400}
JSON stored in PDC
{"assetId":"BUG","value":1.4}
Tested on Fabric 2.3.0 + CouchDB 3.1.1, Fabric 2.5.0 + CouchDB 3.2.2
Steps to reproduce
Prerequisites
To demonstrate the issue, Fabric samples were used.
fabric-samples/test-network
fabric-samples/asset-transfer-private-data/chaincode-java
was modifiedIn the chaincode project:
build.gradle
file:implementation group: 'com.owlike', name: 'genson', version: '1.6'
All existing DataTypes and Contracts were replaced with the following Contract file:
AssetContract.java
```java /* * SPDX-License-Identifier: Apache-2.0 */ package org.hyperledger.fabric.samples.privatedata; import com.owlike.genson.Genson; import org.hyperledger.fabric.contract.Context; import org.hyperledger.fabric.contract.ContractInterface; import org.hyperledger.fabric.contract.annotation.Contract; import org.hyperledger.fabric.contract.annotation.Default; import org.hyperledger.fabric.contract.annotation.Transaction; import org.hyperledger.fabric.contract.annotation.DataType; import org.hyperledger.fabric.contract.annotation.Property; import org.hyperledger.fabric.shim.ChaincodeException; import java.math.BigDecimal; import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; import java.util.Objects; @Contract(name = "AssetContract") @Default public final class AssetContract implements ContractInterface { private static final String ASSET_COLLECTION_NAME = "assetCollection"; private static final String ASSET_ID_BUG = "BUG"; private static final String ASSET_ID_OK = "OK"; private static final String DIGEST_ALGORITHM = "SHA-256"; private final Genson genson = new Genson(); @Transaction(name = "WriteBug", intent = Transaction.TYPE.SUBMIT) public void writeBug(final Context ctx) { Asset asset = new Asset(ASSET_ID_BUG, new BigDecimal("1.400")); String assetJson = genson.serialize(asset); System.out.printf("Put asset: %s, collection: %s, ID: %s\n", assetJson, ASSET_COLLECTION_NAME, ASSET_ID_BUG); ctx.getStub().putPrivateData(ASSET_COLLECTION_NAME, ASSET_ID_BUG, assetJson); } @Transaction(name = "ValidateBug", intent = Transaction.TYPE.EVALUATE) public void validateBug(final Context ctx) throws Exception { byte[] assetJson = ctx.getStub().getPrivateData(ASSET_COLLECTION_NAME, ASSET_ID_BUG); byte[] realHash = MessageDigest.getInstance(DIGEST_ALGORITHM).digest(assetJson); byte[] expectedHash = ctx.getStub().getPrivateDataHash(ASSET_COLLECTION_NAME, ASSET_ID_BUG); System.out.println("Real json: " + new String(assetJson)); System.out.println("Real hash: " + new String(realHash)); System.out.println("Expected hash: " + new String(expectedHash)); if (!Objects.deepEquals(realHash, expectedHash)) { throw new ChaincodeException("Ledger hash is not equal to hash of private data"); } } @Transaction(name = "WriteOk", intent = Transaction.TYPE.SUBMIT) public void writeOk(final Context ctx) { Asset asset = new Asset(ASSET_ID_OK, new BigDecimal("1.4")); String assetJson = genson.serialize(asset); System.out.printf("Put asset: %s, collection: %s, ID: %s\n", assetJson, ASSET_COLLECTION_NAME, ASSET_ID_OK); ctx.getStub().putPrivateData(ASSET_COLLECTION_NAME, ASSET_ID_OK, assetJson); } @Transaction(name = "ValidateOk", intent = Transaction.TYPE.EVALUATE) public void validateOk(final Context ctx) throws ChaincodeException, NoSuchAlgorithmException { byte[] assetJson = ctx.getStub().getPrivateData(ASSET_COLLECTION_NAME, ASSET_ID_OK); byte[] realHash = MessageDigest.getInstance(DIGEST_ALGORITHM).digest(assetJson); byte[] expectedHash = ctx.getStub().getPrivateDataHash(ASSET_COLLECTION_NAME, ASSET_ID_OK); System.out.println("Real json: " + new String(assetJson)); System.out.println("Real hash: " + new String(realHash)); System.out.println("Expected hash: " + new String(expectedHash)); if (!Objects.deepEquals(realHash, expectedHash)) { throw new ChaincodeException("Ledger hash is not equal to hash of private data"); } } } @DataType() final class Asset { @Property() private final String assetId; @Property() private final BigDecimal value; Asset(final String assetId, final BigDecimal value) { this.assetId = assetId; this.value = value; } public String getAssetId() { return assetId; } public BigDecimal getValue() { return value; } } ```To start the network and deploy the chaincode, execute:
To reproduce the bug, execute:
You should see this: