Skip to content
Discussions/App Development/How to fetch a contract from dumlhub Ledger via JavaForum ↗

How to fetch a contract from dumlhub Ledger via Java

App Development33 posts898 views4 likesLast activity Jun 2022
LI
liavOP
May 2022

Hi there,

Can you refer me to a documentation or example on how to fetch a certain contract from a Ledger (from damlHub) according to a predefined key (e.g: fetch a product contract by its productId key)

Thanks

CO
cocreature
May 2022

Daml Hub exposes the JSON API which provides endpoints for fetching a contract by contract id and by contract key.

LI
liav
May 2022

Thanks @cocreature.

The documentation lack to explain several things regarding the body:

  1. Is the templateId should be the full name? In the screenshot below its “Workflow.Workflow:Product”
  2. Is the key _1 is the name of the key (In the screenshot below it’s “productId”)
  3. Is the _2 is the value of the key? (In the screenshot below it “P1234-337”)
{
    "templateId": "Account:Account",
    "key": {
        "_1": "Alice",
        "_2": "abc123"
    }
}

image

postman smaple return 404 although the contract exists so i assume i mis understood the body:

{
    "templateId": "Workflow.Workflow:Product",
    "key": {
        "_1": "productId",
        "_2": "P1234-337"
    }
}

Response:

{
    "errors": [
        "HttpMethod(POST), uri: http://p4np49dc43hsdza3.daml.app/v1/stream/fetch%20"
    ],
    "status": 404
}
CO
cocreature
May 2022

templateId is the template id of the contract you’re trying to fetch. As anywhere else in the JSON API where a template id is specified you can either use

<package ID>:<module>:<entity>

or if it’s unambiguous

<module>:<entity>

The key is not separated into name and value, it is only the value. In the example in the docs, the key is a tuple (Party, Text). Daml tuples are translated to records with fields _1, …, _n (docs) so that’s why the JSON representation looks like this.

LI
liav
May 2022

Hi @cocreature ,

When you refer to the contract key, is it as below screenshot?

Can you see anything missing in the below request that prevents from 200OK to respond (response is the second screenshot)?

CO
cocreature
May 2022

It depends on how your defined your key. Can you share the key definition of Product?

LI
liav
May 2022

Are you referring to this?

CO
cocreature
May 2022

I was thinking of the Daml definition but this is sufficient. Your request looks correct but I believe you’re contacting the wrong endpoint: You’re querying the streaming endpoint with a POST request. Try /v1/fetch instead of /v1/stream/fetch. The docs seem off there, I’ll make sure that gets fixed.

LI
liav
May 2022

Still the same error.

Although i should have received 401 (instead of 404), maybe the Bearer token is wrong?
Should i place the token of the contract’s Party (e.g the Carrier in my example)

CO
cocreature
May 2022

You have a %20 so a space at the end of your url.

LI
liav
May 2022

Embarrassing…It works :smile:
Thanks @cocreature

LI
liav
May 2022

@cocreature Can you advise on the best way to deserialize the returned JSON into a contract?

My question is not technical since i’m aware of kibraries such as Gson or Jackson. My question is wether there is a recommended way to create an instance of a contract by its corresponding json (it’s a huge one by the way)

My end game is to submit and exercise a Command with this contract

CO
cocreature
May 2022

There is no recommended way to decode the JSON API responses in Java atm. You have to implement it yourself.

LI
liav
May 2022

I’ll ask it differently:

I want to exercise a multi party submission command on a certain contract which i fetched from a Ledger.

According to my knowledge, for a single party submission, i need to create a com.daml.ledger.javaapi.data.Command and use the Ledger client to “submitAndWait” it.

My question is: Given a contractId, what are the steps to perform such operation (submit multi party command)

Is there a constructor like CreateAndExerciseCommand(templateId, createArguments, choice, choiceArgument) that receives the contractId as an argument?

CO
cocreature
May 2022

If you want to exercise a choice on a contract id use ExerciseCommand instead of CreateAndExerciseCommand. You don’t need to fetch the contract for that. The command is also independent of whether you do a multi-command submission or not.

For a multi-party submission you need to set actAs and readAs on the commands.

Note though that you also need a token for those parties and Daml Hub does not issue multi-party tokens at the moment so while you can use that locally you cannot make multi-party submissions against hub.

LI
liav
May 2022

@cocreature,

Is “Submit Multi” only supported in sandbox (via Java)?

I’m asking because i’m pretty sure it’s supported in DAML against damlhub…

CO
cocreature
May 2022

It’s supported against any ledger. The issue is not the lack of ledger support in Daml Hub but the fact that the Hub IAM does not issue you a token for multiple parties.

LI
liav
May 2022

I thought that “Submit Multi” for two parties receives as an input two separated tokens. According to you it requires a single token allocated for multiple parties and that’s not supported?

CO
cocreature
Jun 2022

Exactly.

LE
Leonid_Rozenberg
Jun 2022

@liav To correct @cocreature slightly, you have to ask hub to create a multiparty token for you, which you can do using the cli damlhub-cli - npm, please see

damlhub-cli ledger multipartyToken

With that token you can make submitMulti requests as those are natural requests via the LedgerAPI. The only wrinkle is that all of the parties that you aggregate into a single token must be controlled by the party that created the ledger; which I think is true in your case.

LI
liav
Jun 2022

Thanks @Leonid_Rozenberg

I indeed performed damlhub token eyJ0e…

And got this response but i wonder where the created token is saved so i can use it?

{
“message”: “Saved Daml Hub token with base domain: hub.daml.com”,
“success”: true
}

LE
Leonid_Rozenberg
Jun 2022

@liav Please read my response carefully.

damlhub token

is the not same as

damlhub ledger multipartyToken

The latter takes as argument the identity of parties that you want to add to a multiparty JWT. Whereas the former saves your account JWT locally (in the scratch folder) for reuse with other commands.

LI
liav
Jun 2022

Something like that? (sorry, i couldn’t find the manual nor the help entry on the client)

Command:
damlhub ledger multipartyToken p4np49dc43hsdza3 Carrier Owner

Output:

{
“accessToken”: “eyJ0eXAiOiJKV1QiLCJhbGciOiJSXXXXXXXXXXXXXXX1IiwiaHR0cHM6Ly9kYW1sLmNvbS9sZWRnZXItYXBpIjp7ImFjdEFzIjpbImxlZGdlci1wYXJ0eS04NTRlMwXXXXXXtuhafywLaaVPXmNEqlJS6z2sbSoetYz0KZ1X8G8rIVsP1M-zdw05CSxawMERXm6DJUm5W6raDnvSLUJ3kbh4A5IgJOs”
}

LE
Leonid_Rozenberg
Jun 2022

I don’t know if you copy and pasted correctly, sure looks like a lot of Xs :wink: , but yes that’s the right call.

LI
liav
Jun 2022

What am i missing?

The command seemed to work (see earlier posts in this thread but now it fails with 401

Command:
damlhub ledger multipartyToken p4np49dc43hsdza3 Carrier Owner

Output:

2022-06-05 15:27:15,982 ERROR [tokens] Tried to compute a URL for .admin, .site; got back //login.hub.daml.com/auth/login which requires the same scope.
Unexpected error: Error: Received a 401 when making a site level API request.
    at TokenManager._this._circularTokenRequirement (/Users/le056g/.config/yarn/global/node_modules/damlhub-cli/lib/src/dabl/apiTokens.js:198:19)
    at TokenManager.refreshToken (/Users/le056g/.config/yarn/global/node_modules/damlhub-cli/lib/src/dabl/apiTokens.js:283:18)
    at TokenManager.<anonymous> (/Users/le056g/.config/yarn/global/node_modules/damlhub-cli/lib/src/dabl/apiTokens.js:237:51)
    at step (/Users/le056g/.config/yarn/global/node_modules/damlhub-cli/lib/src/dabl/apiTokens.js:67:23)
    at Object.next (/Users/le056g/.config/yarn/global/node_modules/damlhub-cli/lib/src/dabl/apiTokens.js:48:53)
    at /Users/le056g/.config/yarn/global/node_modules/damlhub-cli/lib/src/dabl/apiTokens.js:42:71
    at new Promise (<anonymous>)
    at __awaiter (/Users/le056g/.config/yarn/global/node_modules/damlhub-cli/lib/src/dabl/apiTokens.js:38:12)
    at TokenManager.getToken (/Users/le056g/.config/yarn/global/node_modules/damlhub-cli/lib/src/dabl/apiTokens.js:228:16)
    at TokenManager.<anonymous> (/Users/le056g/.config/yarn/global/node_modules/damlhub-cli/lib/src/dabl/apiTokens.js:221:51)

I assume i need a preliminary login step? I tried running this command just befor the multiParty one but it didn’t help (i used account token):

damlhub token eyJ0eXAiOiJKV1QiLCJh....
LE
Leonid_Rozenberg
Jun 2022

@liav Maybe your account token has expired?

  1. Remove the scratch/ folder where you’re running this command. This is where your account token is stored.
  2. Get the latest account token from when you login into DamlHub.
  3. Save it locally with damlhub token [paste-token-here]
  4. Try running damlhub ledger multipartyToken

(@Alex_Matson @dtanabe , I wish there was a better error message here)

LI
liav
Jun 2022

Thanks @Leonid_Rozenberg it worked as you suggested.

Can you please refer me to the online documentation of the JSON API/gRPC for ExerciseCommand with actAs and readAs additions (to support multiParty submission)

A_
a_putkov
Jun 2022

@liav
When you use HTTP JSON API, the actAs and readAs are taken from the token included in the HTTP request. For multiparty submission you simply need to include the token that contains all the actAs and readAs that you want to use.
For gRPC the act_as and read_as fields are included in the Commands message. You can find the definitions for the Commands message under Ledger API gRPC definitions on GitHub.

LI
liav
Jun 2022

Thanks @a_putkov ,

I indeed use the gRPC api rather than the JSON API. If you have a link to the documentation of the multi submit via JSON API i would check it also.

For now, with current gRPC implementation - i couldn’t understand your reply: The fact that actAs and readAs lists are part of the message i’m aware of (see code below) but i wonder if i need to populate them both with same parties

Code:

private void exerciseCommandOnProduct(String contractId, ProductFields productFields, String multiPartyToken) {
        List<String> actAs     = Arrays.asList(damlProperties.getOwnerParty(), "DemoParty");
        List<String> readAs    = Arrays.asList(damlProperties.getOwnerParty(), "DemoParty");

        ArrayList<DamlRecord.Field> fields = new ArrayList<DamlRecord.Field>();
        fields.add(new DamlRecord.Field(new Party(damlProperties.getOwnerParty())));
        fields.add(new DamlRecord.Field(new Party(productFields.getProductCode())));

        DamlRecord choiceArgument = new DamlRecord(fields);

        ExerciseCommand command = new ExerciseCommand(TEMPLATE_ID, contractId, "CustomerCreatesPolicyRequest", choiceArgument);
        List<Command> commands = Collections.singletonList(command);

        final Single<Empty> emptySingle = client.
                getCommandSubmissionClient().
                submit(WORKFLOW_ID, APPLICATION_ID, UUID.randomUUID().toString(), actAs, readAs, commands, multiPartyToken);

        emptySingle.blockingGet();
    }
A_
a_putkov
Jun 2022

@liav
My apologies, I thought you were asking how to specify actAs and readAs in gRPC message. Now I understand your question to be about the significance of act_as and read_as fields, right?

act_as must include all parties, whose authority is required to execute the transaction, else the transaction will fail according to Daml ledger authorization rules.

read_as allows you to provide additional parties that may be required for the visibility of contracts involved in the transaction. As stated in the comments for the read_as field in the Commands message spec, "A command can only use contracts that are visible to at least one of the parties in act_as or read_as".

So, if every contract involved in the transaction is visible to at least one party in the act_as list, the read_as list does not need to be populated. However, if at least one contract involved in the transaction is not visible to any party in the act_as list, the transaction will fail unless read_as list contains at least one party, for which that contract is visible. An example, where a contract involved in the transaction may not be visible to any party authorizing the transaction, is a fetch action used in a choice, where the fetched contract is not visible to any of the choice controllers.

LI
liav
Jun 2022

Thanks @a_putkov ,

I indeed encounter an issue on the exact topic you’re referring to: authentication.

My submitMulti Java implementation was as @Leonid_Rozenberg advised (see code below).

How can i gain access to the logs in order to understand the root cause of below authentication error thrown from the submit command?

The parties are: Owner2 Public

code:

multi party token is created via damlhub-cli:

damlhub ledger multipartyToken ry3wqxrcdqpkz7xz Owner2 Public

submitMulti:

private void exerciseCommandOnProduct(String contractId, ProductFields productFields, String multiPartyToken) {
        List<String> actAs   = Arrays.asList("Public", "Owner2");
        List<String> readAs = Arrays.asList("Public", "Owner2");

        ArrayList<DamlRecord.Field> fields = new ArrayList<DamlRecord.Field>();
        fields.add(new DamlRecord.Field(new Party("Owner2")));

        DamlRecord choiceArgument = new DamlRecord(fields);

        ExerciseCommand command = new ExerciseCommand(TEMPLATE_ID, contractId, "CustomerCreatesPolicyRequest", choiceArgument);
        List<Command> commands = Collections.singletonList(command);

        final Single<Empty> emptySingle = client.
                getCommandSubmissionClient().
                submit(WORKFLOW_ID, APPLICATION_ID, UUID.randomUUID().toString(), actAs, readAs, commands, multiPartyToken);

        emptySingle.blockingGet();
    }

Caused by: io.grpc.StatusRuntimeException: UNAUTHENTICATED: An error occurred. Please contact the operator and inquire about the request
at io.grpc.Status.asRuntimeException(Status.java:535) ~[grpc-api-1.44.0.jar:1.44.0]
at io.grpc.stub.ClientCalls$UnaryStreamToFuture.onClose(ClientCalls.java:534) ~[grpc-stub-1.44.0.jar:1.44.0]
at io.grpc.internal.ClientCallImpl.closeObserver(ClientCallImpl.java:562) ~[grpc-core-1.44.0.jar:1.44.0]
at io.grpc.internal.ClientCallImpl.access$300(ClientCallImpl.java:70) ~[grpc-core-1.44.0.jar:1.44.0]
at io.grpc.internal.ClientCallImpl$ClientStreamListenerImpl$1StreamClosed.runInternal(ClientCallImpl.java:743) ~[grpc-core-1.44.0.jar:1.44.0]
at io.grpc.internal.ClientCallImpl$ClientStreamListenerImpl$1StreamClosed.runInContext(ClientCallImpl.java:722) ~[grpc-core-1.44.0.jar:1.44.0]
at io.grpc.internal.ContextRunnable.run(ContextRunnable.java:37) ~[grpc-core-1.44.0.jar:1.44.0]
at io.grpc.internal.SerializingExecutor.run(SerializingExecutor.java:133) ~[grpc-core-1.44.0.jar:1.44.0]
at java.base/java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1128) ~[na:na]
at java.base/java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:628) ~[na:na]
at java.base/java.lang.Thread.run(Thread.java:834) ~[na:na]

A_
a_putkov
Jun 2022

@liav
As @dtanabe explained in this post on another thread you opened, if you’re en enterprise user, you need to open a support ticket with Digital Asset Support team and provide your ledger ID to have the logs pulled from Daml Hub.

LI
liav
Jun 2022

@Roy_Silon @cohen.avraham

← Back to Discussions