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.
Unfortunately I don’t know where value is expected.
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.
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.
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. ![]()
Dear Future Self,
If you get this error message…
MISSING_FIELD(8,subId): The submitted command is missing a mandatory field: value
… for something like this…
"fields": [
{
"label": "operator",
"value:": {
"party": "operator::12200...1de9f"
}
},
…check that you’re not doing this…
"value:" vs. "value":
![]()