Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

May org.json.JSONObject used in org.hyperledger.fabric.contract.execution.JSONTransactionSerializer lead submit payloads not matching? #361

Open
8f235831 opened this issue Nov 13, 2024 · 3 comments
Labels

Comments

@8f235831
Copy link

When I call a submit method from application via gateway, I received an Exception below.

Caused by: org.hyperledger.fabric.client.EndorseException: io.grpc.StatusRuntimeException: ABORTED: failed to collect enough transaction endorsements, see attached details for more info
        at org.hyperledger.fabric.client.GatewayClient.endorse(GatewayClient.java:74)
        at org.hyperledger.fabric.client.ProposalImpl.endorse(ProposalImpl.java:76)
        at org.hyperledger.fabric.client.Proposal.endorse(Proposal.java:64)
        at pers.u8f23.fabric.app.api.AbstractAssetContractSubmit.createAsset(AbstractAssetContractSubmit.java:13)
        at pers.u8f23.fabric.app.Main.chaincodeOperations(Main.java:84)
        at pers.u8f23.fabric.app.Main.main(Main.java:65)
Caused by: io.grpc.StatusRuntimeException: ABORTED: failed to collect enough transaction endorsements, see attached details for more info
        at io.grpc.stub.ClientCalls.toStatusRuntimeException(ClientCalls.java:268)
        at io.grpc.stub.ClientCalls.getUnchecked(ClientCalls.java:249)
        at io.grpc.stub.ClientCalls.blockingUnaryCall(ClientCalls.java:167)
        at org.hyperledger.fabric.protos.gateway.GatewayGrpc$GatewayBlockingStub.endorse(GatewayGrpc.java:455)
        at org.hyperledger.fabric.client.GatewayClient.endorse(GatewayClient.java:72)
        ... 5 more

Detail message attached to this Exception is ProposalResponsePayloads do not match.

Returned type is a generic class. Both the generic type and the type argument are defined as type in fabric demo .

public interface AbstractAssetContractSubmit {
  Response<Asset> createAsset(Context context, String value);

  Response<Void> deleteAsset(Context context, String assetId);

  Response<Void> updateAsset(Context context, String assetId, String value);
}
@DataType
public final class Response<T> {
  @Property
  private final T body;

  @Property
  private final int code;

  @Property
  private final String msg;

  public Response(@JsonProperty("body") T body, @JsonProperty("code") int code,
      @JsonProperty("msg") String msg) {
    this.body = body;
    this.code = code;
    this.msg = msg;
  }

  public T getBody() {
    return this.body;
  }

  public int getCode() {
    return this.code;
  }

  public String getMsg() {
    return this.msg;
  }
}
@DataType
public final class Asset {
  @Property
  private final String id;

  @Property
  private final String creatorId;

  @Property
  private final String ownerId;

  @Property
  private final long createTime;

  @Property
  private final long lastTransferTime;

  @Property
  private final long lastUpdateTime;

  @Property
  private final String assetValue;

  public Asset(@JsonProperty("id") String id, @JsonProperty("creatorId") String creatorId,
      @JsonProperty("ownerId") String ownerId, @JsonProperty("createTime") long createTime,
      @JsonProperty("lastTransferTime") long lastTransferTime,
      @JsonProperty("lastUpdateTime") long lastUpdateTime,
      @JsonProperty("assetValue") String assetValue) {
    this.id = id;
    this.creatorId = creatorId;
    this.ownerId = ownerId;
    this.createTime = createTime;
    this.lastTransferTime = lastTransferTime;
    this.lastUpdateTime = lastUpdateTime;
    this.assetValue = assetValue;
  }

  public String getId() {
    return this.id;
  }

  public String getCreatorId() {
    return this.creatorId;
  }

  public String getOwnerId() {
    return this.ownerId;
  }

  public long getCreateTime() {
    return this.createTime;
  }

  public long getLastTransferTime() {
    return this.lastTransferTime;
  }

  public long getLastUpdateTime() {
    return this.lastUpdateTime;
  }

  public String getAssetValue() {
    return this.assetValue;
  }
}

Does this chaincode api support java generic type as method return type?
Anyone is welcome to answer this question.

@8f235831
Copy link
Author

I found below codes from line 72 of org.hyperledger.fabric.contract.execution.JSONTransactionSerializer .

// at this point we can assert that the value is
// representing a complex data type
// so we can get this from
// the type registry, and get the list of propertyNames
// it should have
final DataTypeDefinition dtd = this.typeRegistry.getDataType(ts);
final Set<String> keySet = dtd.getProperties().keySet();
final String[] propNames = keySet.toArray(new String[keySet.size()]);


// Note: whilst the current JSON library does pretty much
// everything is required, this part is hard.
// we want to create a JSON Object based on the value,
// with certain property names.


// Based on the constructors available we need to have a two
// step process, create a JSON Object, then create the object
// we really want based on the propNames
final JSONObject obj = new JSONObject(new JSONObject(value), propNames);
buffer = obj.toString().getBytes(UTF_8);

Those codes seem to be used to serializer complex objects returned by chaincode methods. The given value is processed by org.json.JSONObject, which using java.util.HashMap to obtain key-value pairs and is not sure to make pairs ordered by fixed sequence. I suspect this is a bug.

@8f235831 8f235831 changed the title Can methods in contract return generic types? May org.json.JSONObject used in org.hyperledger.fabric.contract.execution.JSONTransactionSerializer lead submit payloads not matching? Nov 14, 2024
@bestbeforetoday
Copy link
Member

What logs do you see from the Gateway peer? The logs there should give you a breakdown of what the mismatch was between responses.

@bestbeforetoday
Copy link
Member

I have had a quick look through the implementation and I suspect that you are correct about the built-in JSON serializer not ensuring consistent field ordering in JSON objects. Both the JSON org.json serializer and the fabric-chaincode-shim TypeSchema use unordered maps to store fields.

As a workaround, I would suggest using an alternative serialization solution, such as Jackson with its @JsonPropertyOrder annotation. You could either do the serialization of transaction function return values explicitly in your smart contract code, and declare the transaction function as returning a String, or provide your own serializer implementation that does the same.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

No branches or pull requests

2 participants