Constructing a gRPC exercise command by Hand

Hi all,

I’m trying to construct an exercise command by hand and send it to the LedgerAPI using Postman.
Unfortunately, I’m getting an error, and I was hoping someone could point me to what’s going wrong.

The choice I want to exercise is:

choice CreateProposals : (ContractId TransactionManifest, [ContractId TransferProposal])
  with
    messageIdToLegs: [(Text, Leg)]
    assembler: Party

data Leg = Leg with
    legPayload: Text
    approversToSettlementSteps: [(Party, SettlementStep)]

data SettlementStep = SettlementStep with
    sender: Optional Text -- sender account iban
    receiver: Optional Text -- receiver account iban
    delivery: Instrument

data Instrument = Instrument with
    amount: Decimal
    label: Text

This is what I came up with:

{
    "commands": {
        "act_as": [
            "scheduler::12203a80aea09ceaca412f7ae3d5a7a0ebba76cf78b352a2b450d566c445cabb40f5"
        ],
        "application_id": "app",
        "command_id": "myId",
        "ledger_id": "sandbox",
        "party": "scheduler::12203a80aea09ceaca412f7ae3d5a7a0ebba76cf78b352a2b450d566c445cabb40f5",
        "submission_id": "subId",
        "workflow_id": "workflowId",
        "commands": [
            {
                "exercise": {
                    "choice": "CreateProposals",
                    "template_id": {
                        "package_id": "1ff2164777e6e7bc31a87b53c8301743a8594fd03c4c92abbbc7b57bb63c309b",
                        "entity_name": "InitiateTransfer",
                        "module_name": "Workflow.InitiateTransfer"
                    },
                    "choice_argument": {
                        "assembler": "assembler::12203a80aea09ceaca412f7ae3d5a7a0ebba76cf78b352a2b450d566c445cabb40f5",
                        "messageIdToLegs": {
                            "elements": [
                                "MessageId",
                                {
                                    "legPayload": "PAYLOAD",
                                    "approversToSettlementSteps": {
                                        "elements": [
                                            "bankA::12203a80aea09ceaca412f7ae3d5a7a0ebba76cf78b352a2b450d566c445cabb40f5",
                                            {
                                                "sender": "SenderIBAN",
                                                "receiver": "ReceiverIBAN",
                                                "delivery": {
                                                    "amount": "100.0",
                                                    "label": "USD"
                                                }
                                            }
                                        ]

                                    }
                                }
                            ]
                        }
                    },
                    "contract_id": "00daa8c866c648e13a28797b964f07208077bcff9e9af2099134700ce8f63bcd29ca01122027171d4055492024a620041a6a70f0b52bb97a1db5db191e6b193fecf4362845"
                }
            }
        ]
    }
}

The error message I received was

MISSING_FIELD(8,subId): The submitted command is missing a mandatory field: value

Unfortunately I don’t know where value is expected.

I constructed this message by using postman’s “generate example message” and looking at Ledger API Reference — Daml SDK 2.5.0 documentation

Thank you for your help.

Best,
darko

Also, is there a simple config setting for the sandbox / ledger API, such that it prints all commands it receives into the logs? Then I could simply grab it from there after executing the exercise through Navigator.

FYI… I found gRPC UI to be very helpful for assembling gRPC payloads.

The protobuf encoding is a lot more explicit than in the JSON API. You have to explicitly specify types everywhere. Here is an example of a record with two fields where the first is a single contract id and the second is a list of contract ids:

choice_argument {
  record {
    fields {
      label: "a"
      value {
        contract_id: "0081e0cdddb59f9d2c6e8bdd2eb896d792486e6e96c24fcdeac629d3b5d0ad4b77ca011220a280cc011b9eccf9153575c92710bb4e429af1f109d5e957f68d5ac0a598d096"
      }
    }
    fields {
      label: "b"
      value {
        list {
          elements {
            contract_id: "004327202b9208d246ce8c01f336eafafdfc73c5882f48c3dec148d6246dbb62daca0112205c559083e0a5eb7e5d8c2921cc7f9b8dca38b7a2d63ab40556d9222e4549c1fa"
          }
          elements {
            contract_id: "00f757f7309da7637fa27af2786f513ca80cd7085fb39a6fbdab7ef1f825b7f312ca01122022d1fefd3109b720c376af67b6897489387a5d0bce785fb3a797dc68ec1f28e9"
          }
          elements {
            contract_id: "00ecdb3f29c63443aecd79086064a459848ec3935f1a1260db9199b2b3d7dc950eca011220373d0ed93aea51ac457bf884e132ef759b55bf5586c37263a88c3ef991b10aa3"
          }
        }
      }
    }
  }
}

Should be relatively easy to see hopefully how that matches up with the corresponding protobuf in daml/value.proto at 8f5b25fc1f80dd7baee671ed981b3b076a9e573b · digital-asset/daml · GitHub

As for your question of seeing request payloads, Canton has options for detailed logs that will show you exactly that. That’s actually where I copied the above from.

Looking at the code (here and here) it looks like you might be missing something in a record or variant. It’s weird that this points to subId apparently though.

The commands are added to the logging context as soon as they arrive on the Ledger API server and will be printed alongside every log entry related to that command submission. I’m not sure whether the default logger config for the sandbox is configured to display the logging context though – if it’s not, you can touch up the Logback configuration file to make sure that the %marker (which carries the logging context) is printed alongside the rest. Here is the default Logback file for the HTTP JSON API Service for reference (which prints the context):

<?xml version="1.0" encoding="UTF-8"?>
<configuration>
    <appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
        <if condition='isDefined("LOG_FORMAT_JSON")'>
            <then>
                <encoder class="net.logstash.logback.encoder.LogstashEncoder"/>
            </then>
            <else>
                <!-- encoders are assigned the type
                    ch.qos.logback.classic.encoder.PatternLayoutEncoder by default -->
                <encoder>
                    <pattern>%date{"dd-MM-yyyy HH:mm:ss.SSS", UTC} [%thread] %-5level %logger{36} - %msg%replace(, context: %marker){', context: $', ''} %n</pattern>
                </encoder>
            </else>
        </if>
    </appender>

    <logger name="io.netty" level="WARN" />
    <logger name="io.grpc.netty" level="WARN" />
    <logger name="ch.qos.logback" level="WARN" />

    <root level="INFO">
        <appender-ref ref="STDOUT" />
    </root>
</configuration>

If you don’t have Commands are also printed straight out to the output if you set the logging level to TRACE. That might be easier to set up but you are probably going to get a lot more logging output to search through (source).

Thanks a lot for everyone’s input. I’ve made some progress, but I’m again hitting the same error.

Can someone please verify if the encoding of the Optional Text type is correct? My understanding was, that it should look like this:

"optional": {
  "value": {
    "text": "mySomeValue"
  }
}

What wasn’t clear to me from looking at the protos was how I would encode a None.

That looks right. To encode None, simply omit the value field. All fields of message types are optional in protobuf.

"optional": {
}

Thank you!

Phew, that was a tough one.
If someone in the future is looking for a slightly bigger example, here is a successful encoding of the choice CreateProposals as defined as follows:

choice CreateProposals : (ContractId TransactionManifest, [ContractId TransferProposal])
  with
    messageIdToLegs: [(Text, Leg)]
    assembler: Party

data Leg = Leg with
    legPayload: Text
    approversToSettlementSteps: [(Party, SettlementStep)]

data SettlementStep = SettlementStep with
    sender: Optional Text -- sender account iban
    receiver: Optional Text -- receiver account iban
    delivery: Instrument

data Instrument = Instrument with
    amount: Decimal
    label: Text

The json encoded body of the gRPC message looks like this (example running on my sandbox with the partyIds allocated as part of a startup script).

{
  "commands": {
    "act_as": [
      "scheduler::1220682c7ac95e8397a6ef0461c25d7b3a6205c66a07331f451e5da7ffa88988cbbe"
    ],
    "application_id": "app",
    "command_id": "myCommandId",
    "ledger_id": "sandbox",
    "submission_id": "submissionId",
    "workflow_id": "workflowId",
    "commands": [
      {
        "exercise": {
          "choice": "CreateProposals",
          "template_id": {
            "package_id": "1ff2164777e6e7bc31a87b53c8301743a8594fd03c4c92abbbc7b57bb63c309b",
            "entity_name": "InitiateTransfer",
            "module_name": "Workflow.InitiateTransfer"
          },
          "contract_id": "00d2b098ecd5dd550d59422ab03cacd0d2e4a458fda7180ff33feed246ce494eadca011220b087ca031bda84190981872bca256949491bea2d8faaec288a2ae6fda4e96e34",
          "choice_argument": {
            "record": {
              "fields": [
                {
                  "label": "assembler",
                  "value": {
                    "party": "assembler::1220682c7ac95e8397a6ef0461c25d7b3a6205c66a07331f451e5da7ffa88988cbbe"
                  }
                },
                {
                  "label": "messageIdToLegs",
                  "value": {
                    "list": {
                      "elements": [
                        {
                          "record": {
                            "fields": [
                              {
                                "label": "_1",
                                "value": {
                                  "text": "myMessageIdToLegs"
                                }
                              },
                              {
                                "label": "_2",
                                "value": {
                                  "record": {
                                    "fields": [
                                      {
                                        "label": "legPayload",
                                        "value": {
                                          "text": "myLegPayload"
                                        }
                                      },
                                      {
                                        "label": "approversToSettlementSteps",
                                        "value": {
                                          "list": {
                                            "elements": [
                                              {
                                                "record": {
                                                  "fields": [
                                                    {
                                                      "label": "_1",
                                                      "value": {
                                                        "party": "bankB::1220682c7ac95e8397a6ef0461c25d7b3a6205c66a07331f451e5da7ffa88988cbbe"
                                                      }
                                                    },
                                                    {
                                                      "label": "_2",
                                                      "value": {
                                                        "record": {
                                                          "fields": [
                                                            {
                                                              "label": "sender",
                                                              "value": {
                                                                "optional": {
                                                                  "value": {
                                                                    "text": "mySenderAcc"
                                                                  }
                                                                }
                                                              }
                                                            },
                                                            {
                                                              "label": "receiver",
                                                              "value": {
                                                                "optional": {
                                                                  "value": {
                                                                    "text": "myReceiverAcc"
                                                                  }
                                                                }
                                                              }
                                                            },
                                                            {
                                                              "label": "delivery",
                                                              "value": {
                                                                "record": {
                                                                  "fields": [
                                                                    {
                                                                      "label": "amount",
                                                                      "value": {
                                                                        "numeric": "123.0"
                                                                      }
                                                                    },
                                                                    {
                                                                      "label": "label",
                                                                      "value": {
                                                                        "text": "myUSD"
                                                                      }
                                                                    }
                                                                  ]
                                                                }
                                                              }
                                                            }
                                                          ]
                                                        }
                                                      }
                                                    }
                                                  ]
                                                }
                                              }
                                            ]
                                          }
                                        }
                                      }
                                    ]
                                  }
                                }
                              }
                            ]
                          }
                        }
                      ]
                    }
                  }
                }
              ]
            }
          }
        }
      }
    ]
  }
}

And this is why we offer bindings with codegen. :wink:

3 Likes