firefly icon indicating copy to clipboard operation
firefly copied to clipboard

Arguments of type 'object' lead to internal server on invokes.

Open Tillaert opened this issue 2 years ago • 2 comments

Given a firefly schema

    {
      "name": "createVoting",
      "description": "Create a vote, using a ballot.",
      "params": [
        {
          "name": "ballot",
          "schema": {
            "type": "object",
            "properties": {
              "title": {
                "type": "string"
              },
              "id": {
                "type": "string"
              }
            } 
          }
        }
      ],
      "returns": [
        {
          "name": "",
          "schema": {
            "type": "string"
          }
        }
      ]
    },

When called with:

curl -X 'POST' \
  'http://127.0.0.1:5000/api/v1/namespaces/general/apis/chaincode/invoke/createVoting' \
  -H 'accept: application/json' \
  -H 'Request-Timeout: 2m0s' \
  -H 'Content-Type: application/json' \
  -d '{"input":{"title":"Food preferences - title","ballot":{"id":"00000000-00000000-00000000-00000000","title":"Food preferences."}},"options":{}}'

Firefly returns status code 400 Bad Request:

{
  "error": "FF10284: Error from fabconnect: failed to validate argument \"ballot\": - (root): Invalid type. Expected: object, given: string\n"
}

Firefly shows these log lines:

[2023-05-18T17:41:52.790Z]  INFO --> POST /api/v1/namespaces/general/apis/chaincode/invoke/createVoting httpreq=hD6T8jaI pid=1 req=ZO0HVzmC
[2023-05-18T17:41:52.790Z]  INFO <-- POST /api/v1/namespaces/general/apis/chaincode/invoke/createVoting [400] (0.01ms): invalid character ':' looking for beginning of object key string httpreq=hD6T8jaI pid=1 req=ZO0HVzmC
[2023-05-18T17:42:38.197Z]  INFO --> POST /api/v1/namespaces/general/apis/chaincode/invoke/createVoting httpreq=YY3hA6_v pid=1 req=whl_kIeZ
[2023-05-18T17:42:38.221Z]  INFO Emitted transaction_submitted event 147457e2-a1e3-4dfb-9c85-c32129e88a56 for general:4af22562-16b3-4ef1-aa89-58ee218bbcca (correlator=,topic=contract_invoke) dbtx=kqV8Ssxn httpreq=YY3hA6_v pid=1 req=whl_kIeZ
[2023-05-18T17:42:38.222Z]  INFO Executing blockchain_invoke operation 60b657d9-705d-4e23-800f-36fb4684106d via handler ContractManager httpreq=YY3hA6_v pid=1 req=whl_kIeZ
[2023-05-18T17:42:38.223Z] ERROR <== POST http://fabconnect-0.fabconnect.default:3000/transactions [400] (1.28ms) breq=tSKqVL6u pid=1 proto=fabric
[2023-05-18T17:42:38.242Z]  INFO Emitted blockchain_invoke_op_failed event c4937a8c-8a17-4ce7-b7ba-16847f8e1c6d for general:60b657d9-705d-4e23-800f-36fb4684106d (correlator=,topic=) dbtx=W1ImNQtu ns=general pid=1
[2023-05-18T17:42:38.242Z]  INFO <-- POST /api/v1/namespaces/general/apis/chaincode/invoke/createVoting [500] (44.83ms): FF10284: Error from fabconnect: failed to validate argument "ballot": - (root): Invalid type. Expected: object, given: string

Fabconnect shows these log lines:

[2023-05-18T17:42:38.223Z]  INFO --> POST /transactions
[2023-05-18T17:42:38.223Z] ERROR <-- POST /transactions [400]: 
failed to validate argument "ballot": - (root): Invalid type. Expected: object, given: string

It seems that firefly passes the nested object as a string, instead of a nested of an object.

Firefly Version: 1.2.0 Fabconnect v0.9.17

Tillaert avatar May 18 '23 17:05 Tillaert

I suspect that the input arguments are always serialized as a seprate string, instead of preserving the structure: https://github.com/hyperledger/firefly/blob/4df24f30508b046fe57def1d386d2fb73a5d1979/internal/blockchain/fabric/fabric.go#L820

Tillaert avatar May 18 '23 18:05 Tillaert

I think that json.Marshal is called twice. Once on the nested json-object and once on the body of the request by the rest client.

This is the equivalent of:

package main

import (
	"encoding/json"
	"fmt"
)

func main() {
	var inner = make(map[string]string)
	inner["inner"] = "data"
	var output, _ = json.Marshal(inner)
	var outer = make(map[string]string)
	outer["outer"] = string(output)
	var output2, _ = json.Marshal(outer)
	fmt.Println(string(output2))
}

Which results in:

{"outer":"{\"inner\":\"data\"}"}

While what should happen is:

package main

import (
	"encoding/json"
	"fmt"
)

func main() {
	var inner = make(map[string]string)
	inner["inner"] = "data"
	var outer = make(map[string]map[string]string)
	outer["outer"] = inner
	var output2, _ = json.Marshal(outer)
	fmt.Println(string(output2))
}

Which results in:

{"outer":{"inner":"data"}}

Because Resty is called using an unmarshalled object, it is marshalled again. I think this line is not required: https://github.com/hyperledger/firefly/blob/4df24f30508b046fe57def1d386d2fb73a5d1979/internal/blockchain/fabric/fabric.go#L712 And the comment is wrong.

Tillaert avatar May 18 '23 18:05 Tillaert