Exercise choice through Java bindings: INVALID_ARGUMENT: Missing field: value

Dear Experts,

I get a confusing runtime error trying to exercise a choice through java bindings. Any ideas where I might be going wrong?

STACK TRACE:

io.grpc.StatusRuntimeException: CANCELLED: Failed to read message.
at io.grpc.Status.asRuntimeException(Status.java:534)
at io.grpc.stub.ClientCalls$StreamObserverToCallListenerAdapter.onClose(ClientCalls.java:478)
at io.grpc.internal.ClientCallImpl.closeObserver(ClientCallImpl.java:553)
at io.grpc.internal.ClientCallImpl.access$300(ClientCallImpl.java:68)
at io.grpc.internal.ClientCallImpl$ClientStreamListenerImpl$1StreamClosed.runInternal(ClientCallImpl.java:739)
at io.grpc.internal.ClientCallImpl$ClientStreamListenerImpl$1StreamClosed.runInContext(ClientCallImpl.java:718)
at io.grpc.internal.ContextRunnable.run(ContextRunnable.java:37)
at io.grpc.internal.SerializingExecutor.run(SerializingExecutor.java:123)
at java.base/java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1130)
at java.base/java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:630)
at java.base/java.lang.Thread.run(Thread.java:831)
Caused by: io.grpc.StatusRuntimeException: INVALID_ARGUMENT: Missing field: value
at io.grpc.stub.ClientCalls.toStatusRuntimeException(ClientCalls.java:262)
at io.grpc.stub.ClientCalls.getUnchecked(ClientCalls.java:243)
at io.grpc.stub.ClientCalls.blockingUnaryCall(ClientCalls.java:156)
at com.daml.ledger.api.v1.CommandSubmissionServiceGrpc$CommandSubmissionServiceBlockingStub.submit(CommandSubmissionServiceGrpc.java:233)
at com.daml.TraderApp.MyTrader.processTransaction(MyTrader.java:241)
at com.daml.TraderApp.MyTrader.access$100(MyTrader.java:81)
at com.daml.TraderApp.MyTrader$1.lambda$onNext$0(MyTrader.java:197)
at java.base/java.util.ArrayList.forEach(ArrayList.java:1511)
at java.base/java.util.Collections$UnmodifiableCollection.forEach(Collections.java:1087)
at com.daml.TraderApp.MyTrader$1.onNext(MyTrader.java:197)
at com.daml.TraderApp.MyTrader$1.onNext(MyTrader.java:194)
at io.grpc.stub.ClientCalls$StreamObserverToCallListenerAdapter.onMessage(ClientCalls.java:465)
at io.grpc.internal.ClientCallImpl$ClientStreamListenerImpl$1MessagesAvailable.runInternal(ClientCallImpl.java:652)
at io.grpc.internal.ClientCallImpl$ClientStreamListenerImpl$1MessagesAvailable.runInContext(ClientCallImpl.java:637)

MY REQUEST OUTPUT AS TEXT

commands {
ledger_id: “35a409dc-c9f1-4404-8608-a283354da555”
workflow_id: “MarketData-TradeCommunity-1”
application_id: “TraderApp”
command_id: “50374723-6dc3-4844-ab92-af52d905bc89”
party: “TradeCommunity”
commands {
exercise {
template_id {
package_id: “be19f03385255b49b3e7b5573a111cc65d91b3a7456e2d1e7987de4b5ab5aaa6”
module_name: “MarketData”
entity_name: “MarketData”
}
contract_id: “008d4d00738332caa22d1f714465d4bf1cf44c29c2d6e9a8cdf542314c3d6bed45”
choice: “MakeTrade”
}
}
}

JAVA CODE

private void processTransaction(Transaction tx) {

    List<Command> commands = tx.getEventsList().stream()
            .filter(Event::hasCreated).map(Event::getCreated)
            .flatMap(e -> processEvent(tx.getWorkflowId(), e))
            .collect(Collectors.toList());

    if (!commands.isEmpty()) {

        SubmitRequest request = SubmitRequest.newBuilder()
                .setCommands(Commands.newBuilder()
                        .setCommandId(UUID.randomUUID().toString())
                        .setWorkflowId(tx.getWorkflowId())
                        .setLedgerId(ledgerId)
                        .setParty(party)
                        .setApplicationId("TraderApp")
                        .addAllCommands(commands)
                        .build())
                .build();
        submissionService.submit(request);

    }
}

private Stream processEvent(String workflowId, CreatedEvent event) {

    Identifier template = event.getTemplateId();

    boolean isMarketDataModule = template.getModuleName().equals("MarketData");

    if (!isMarketDataModule) return Stream.empty();

    String contractId = event.getContractId();

    String choice = "MakeTrade";

    // assemble the exercise command
   Command cmd = Command
            .newBuilder()
            .setExercise(ExerciseCommand
                    .newBuilder()
                    .setTemplateId(template)
                    .setContractId(contractId)
                    .setChoice(choice))
            .build();
    return Stream.of(cmd);
}

DAML TEMPLATES

module MarketData where

import Trade

type MarketDataCid = ContractId MarketData

template MarketData
with
buyer : Party
seller : Party
volume: Decimal
price: Decimal
timestamp: Datetime
observers : [Party]
where
signatory buyer
observer observers
nonconsuming choice MakeTrade : TradeCid
controller buyer
do
create Trade with
buyer = this.buyer
seller = this.seller
volume = this.volume
price = this.price
timestamp = this.timestamp
observers = this.observers

module Trade where

type TradeCid = ContractId Trade

template Trade
with
buyer : Party
seller : Party
volume: Decimal
price: Decimal
timestamp: Datetime
observers : [Party]
where
signatory buyer
observer observers

When your choice looks like it doesn’t take an argument, under the hood, an empty record is used:

The code in processEvent will need to change:

private Stream processEvent(String workflowId, CreatedEvent event) {
    Identifier template = event.getTemplateId();

    boolean isMarketDataModule = template.getModuleName().equals("MarketData");

    if (!isMarketDataModule) return Stream.empty();

    String contractId = event.getContractId();

    String choice = "MakeTrade";

    // assemble the exercise command
   Value arg = Value.newBuilder()
       .setRecord(Record.newBuilder())  // this should be a blank, empty record
       .build();
   Command cmd = Command
            .newBuilder()
            .setExercise(ExerciseCommand
                    .newBuilder()
                    .setTemplateId(template)
                    .setContractId(contractId)
                    .setChoiceArgument(choice)
                    .setArgument(arg)
            .build();
    return Stream.of(cmd);
}

I’m not in front of an IDE at the moment, so I might be missing a typo or an import or two, but hopefully this gives enough of an idea!

2 Likes