gohyperledger-fabrichyperledger-chaincodechaincode

json encode in golang fabric chaincode behavior


I've seen several articles with similar errors, but none seems to work for me. I've seen the marbles samples, as well as many others, and still can't pinpoint the error.

I'm in fabric 2.x. The chaincode below works just fine when saving data, but fails when reading with the following message:

ERROR] Error submitting transaction: No valid responses from any peers. Errors:
    peer=org1peer-api.127-0-0-1.nip.io:8080, status=500, message=Error handling success response. Value did not match schema:
1. return: Additional property field1 is not allowed
2. return: Additional property field2 is not allowed
3. return: field1,omitempty is required
4. return: field2,omitempty is required
type Asset struct {
    Field1 string `json:"field1,omitempty"`
    Field2 string `json:"field2,omitempty"`
}

func (c *AssetContract) CreateAsset(ctx contractapi.TransactionContextInterface, assetID string, values string) (bool, error) {
    
    // convert json input to byte array
    txData := []byte(values)

    // convert byte array to HlpAsset struct
    asset := new(asset)
    err = json.Unmarshal(txData, &asset)

    // convert struct back to bytes
    txBytes, err := json.Marshal(asset)

    return true, ctx.GetStub().PutState(assetID, txBytes)
}

func (c *AssetContract) ReadAsset(ctx contractapi.TransactionContextInterface, assetID string) (*Asset, error) {

    txBytes, _ := ctx.GetStub().GetState(assetID)

    // convert byte array to HlpAsset struct
    asset := new(Asset)
    err = json.Unmarshal(txBytes, &asset)

    return asset, nil
}

testing with the following input data:

assetID: "3",
values: "{\"field1\":\"123\",\"field2\":\"a05\"}"

In addition, I'm not exactly sure why I need to Unmarshal/Marshal. Couldn't I just convert the stringified JSON to byte and save that? I know that works, is it "only" for data validation purposes that this is required?

Anyway, thanks a bunch.


Solution

  • In omitempty fields, set also metadata:",optional" to pass validation. And you cannot set json:"omitempty" metadata:",optional" on all fields of your model. The default Fabric 2.X Go chaincode's transaction serializer does not like it for any reason. If you really need omitempty for those fields, you can add any other dumb field.

    You can add a dumb boolean...

    type Asset struct {
        Dumb bool `json:"dumb"`
        Field1 string `json:"field1,omitempty" metadata:",optional"`
        Field2 string `json:"field2,omitempty" metadata:",optional"`
    }
    

    ...or add the key/ID itself to the model...

    type Asset struct {
        ID string `json:"id"`
        Field1 string `json:"field1,omitempty" metadata:",optional"`
        Field2 string `json:"field2,omitempty" metadata:",optional"`
    }
    

    ...or a document type...

    type Asset struct {
        DocType string `json:"docType"`
        Field1 string `json:"field1,omitempty" metadata:",optional"`
        Field2 string `json:"field2,omitempty" metadata:",optional"`
    }
    

    As an alternative, you can try to override default ContractChaincode's TransactionSerializer (https://pkg.go.dev/github.com/hyperledger/fabric-contract-api-go/contractapi#ContractChaincode). I've done it when migrating a chaincode that already had its own input validation from 1.X to 2.X, to avoid metadata checks and to return seralization errors in my own format; and may work for your case.

    Or you can return a string instead of an *Asset from ReadAsset as a workaround to avoid the check that is causing your error, so that it is deserialized only in the client. In fact, I find not much coherent to receive an string in CreateAsset, but return an *Asset in ReadAsset. I would use the same format for both (*Asset, preferably, unless you are stuck with your issue).