Which token should i pass?

Hi experts,

I want to call the getTransactions() API.

Which access token should i pass as the last argument? TPA? publicUser? userAdmin? owner?

Thanks

The transaction service and getTransactions in particular requires readAs claims for all subscribing parties. (note that actAs implies readAs).

So the answer really depends on the parties you are subscribing to. If you are looking for transactions visible to the public party use the one for the publicUser, if you are looking for transactions for another party use a token that has the claims for that party. It sounds like you are using Daml hub. In that case, I’d expect that in most cases you are subscribing to one of the parties listed under “User parties” rather than the default user admin & public parties so use the token for that.

Thanks. Can you refer me to a guide which explain how do i subscribe to a certain party in the Ledger?

The getTransactions method you found is exactly the right one. It accepts a TransactionFilter as an argument which controls both the parties as well as the templates you want to subscribe for. The quickstart-java example is probably a useful reference for how to set this up.

When i have this deployed dar on my Ledger, how do i fetch each of the below templates (ping and pong) from the module in Java?

I couldn’t find an example of how you retrieve via the client its contained DAR with its Module and nested templates… In the reference of the iou this is a constant value

module PingPong where

template Ping
with
sender: Party
receiver: Party
count: Int
where
signatory sender
observer receiver

controller receiver can
  RespondPong : ()
    do
      if count > 10 then return ()
      else do
        create Pong with sender = receiver; receiver = sender; count = count + 1
        return ()

template Pong
with
sender: Party
receiver: Party
count: Int
where
signatory sender
observer receiver

controller receiver can
  RespondPing : ()
    do
      if count > 10 then return ()
      else do
        create Ping with sender = receiver; receiver = sender; count = count + 1
        return ()

quickstart-java uses the Java codegen which generates a Java class corresponding to each Daml template which you can use here. That class gives you the Identifier directly that you need to use in the filter. You can find more information on the Java codegen in the documentation.

If you don’t want to use the Java codegen, you need to build the Identifier yourself. You should already know the module name & your template name so the missing piece is the package id. To find that you can run daml damlc inspect-dar --json path/to/yourdar.dar and then use the entry for main_package_id.

While you can query the packages on the ledger via the package service, different packages can have templates with the same template & module name so you cannot use that reliably to detect the package id and you should determine that locally instead either via the codegen or daml damlc inspect-dar.

Thanks Moritz. I progressed in my knowledge but the result is actually the same. I guess i have a small mismatch somewhere:

I have recreated the jar by running “daml build”. Re-uploaded it to the Ledger.
Ran daml codegen java ./.daml/dist/ex-java-bindings-0.0.1.dar --output-directory=.

Copy pasted all the generated classes to the src project, fixed the package declaration everywhere and hit the “run” button with crossed fingers. Looks the same ("PERMISSION_DENIED " )

The Transaction fetch command looks like this now:

Flowable<Transaction> transactions = client.getTransactionsClient().getTransactions(
                LedgerOffset.LedgerEnd.getInstance(),
                filterFor(Pong.TEMPLATE_ID, "TPA"), true, PingPongReactiveMain.tpaPartyAccessToken);

        transactions.forEach(this::processTransaction);

If you’re using Daml hub (which sounds like you are based on your previous statements about the public party & the user admin), you are probably mixing up party ids and the human readable name of that party. You need the party id whenever you need a Daml party. It should look something like ledger-party-8c0ed152-96fd-4b9d-8c2a-501d3ccda304. You should be able to find it in the Identities tab in the daml hub console.

To verify which party/parties your token is valid for, you can decode it via https://jwt.io/ (or something else) and look at the actAs and readAs fields.

1 Like

Thanks Moritz, that helped. Indeed the partyId is the one i should have used rather than its plain text name.

I’m now in the point of creating a command and submitting it.

I also added a token to the submitAndWait API but fails with PERMISSION-DENIED. The token i added is the same as for the transactionFilter one: TPA token

private static void createInitialContracts(LedgerClient client, String sender, String receiver, Identifier pingIdentifier, int numContracts) {

        for (int i = 0; i < numContracts; i++) {
            // command that creates the initial Ping contract with the required parameters according to the model
            CreateCommand createCommand = new CreateCommand(pingIdentifier,
                    new DamlRecord(
                            pingIdentifier,
                            new DamlRecord.Field("sender", new Party(sender)),
                            new DamlRecord.Field("receiver", new Party(receiver)),
                            new DamlRecord.Field("count", new Int64(0))
                    )
            );

            // asynchronously send the commands
            client.getCommandClient().submitAndWait(
                    String.format("Ping-%s-%d", sender, i),
                    APP_ID,
                    UUID.randomUUID().toString(),
                    sender,
                    Collections.singletonList(createCommand),
                    PingPongReactiveMain.tpaPartyAccessToken)
                    .blockingGet();
        }
    }

Should i maybe pass the token of the receiver (Alice) or the sender (Bob)?

You need to pass the token of the signatory which is the sender in your example. Note that sender and receiver also need to be the party ids in the DamlRecord you’re building not the display names.

Does the below looks right? The result is the same. One thing i suspect is the fact that i create a “new party()” while i did it manually in the DAMLhub ledger

for (int i = 0; i < numContracts; i++) {
            // command that creates the initial Ping contract with the required parameters according to the model
            CreateCommand createCommand = new CreateCommand(pingIdentifier,
                    new DamlRecord(
                            pingIdentifier,
                            new DamlRecord.Field("sender", new Party(alicePartyId)), // <--------- Here i create the party using its partyId like you advised
                            new DamlRecord.Field("receiver", new Party(bobPartyId)), // <--------- Same here
                            new DamlRecord.Field("count", new Int64(0))
                    )
            );

            String aliceToken = "*******";
            String bobToken   = "*******";

            // asynchronously send the commands
            client.getCommandClient().submitAndWait(
                    String.format("Ping-%s-%d", sender, i),
                    APP_ID,
                    UUID.randomUUID().toString(),
                    /*sender*/alicePartyId,   //<------ HERE i pass the party id like you advised
                    Collections.singletonList(createCommand),
                    aliceToken)               //<------ HERE i pass the party's token like you advised
                    .blockingGet();
        }

Please don’t share JWT tokens here, they are secrets that allow other people to interact on your behalf with your ledger. You can share the decoded version instead safely to help us debug this.

Your code looks fine, can you share what alicePartyId is set to? Looking at your decoded tokens, it should be ledger-party-f6f78dd4-0898-4036-a1f8-e4698cae29e2.

Alice partyId is exactly what you decoded ledger-party-f6f78dd4-0898-4036-a1f8-e4698cae29e2 :slight_smile:

Not sure what is failing then. Are you sure the token is not expired? And that this is the command that is failing?

The token is still valid.
The specific line that causes the PERMISSION_DENIED error is the submitAndWait() function.
How can i access the server logs to check for elaborated AUTH errors?

The command is like the below screenshot:

The application ID will need to be “damlhub” instead of “PingPongApp”, as that’s what Daml Hub sets in the token that it gives you. Sorry this took a while to track down!

Thanks Davin, that indeed helped and now the pingPongReactive app is running against my real Ledger.

I do however wonder how does this app simulate the “automatic” gRPC observer design pattern by identifying for Alice that Bob sent “Ping” and vise versa (“Pong”)? I added some printing to the console to try and demonstrate the app flow

18:10:57.434 [main] INFO  e.p.reactive.PingPongProcessor - Alice starts reading transactions.
18:10:57.486 [main] INFO  e.p.reactive.PingPongProcessor - Bob starts reading transactions.
18:10:57.487 [main] INFO  e.p.reactive.PingPongReactiveMain - createInitialContracts, contract #0, sender=Alice, receiver=Bob
18:10:57.762 [main] INFO  e.p.reactive.PingPongReactiveMain - submitAndWait: Ping-Alice-0, Party=Alice, Command=CreateCommand{templateId=Identifier{packageId='b8e864550d5d351c0d66cc8d6a899a119d06e900dfedc64396e284f69a8e27e6', moduleName='PingPong', entityName='Ping'}, createArguments=DamlRecord{recordId=Optional[Identifier{packageId='b8e864550d5d351c0d66cc8d6a899a119d06e900dfedc64396e284f69a8e27e6', moduleName='PingPong', entityName='Ping'}], fields=[Field{label=Optional[sender], value=Party{value='ledger-party-f6f78dd4-0898-4036-a1f8-e4698cae29e2'}}, Field{label=Optional[receiver], value=Party{value='ledger-party-74db950a-23f0-4b64-8a17-ee42b7fa9c1d'}}, Field{label=Optional[count], value=Int64{value=0}}]}}
18:10:57.762 [main] INFO  e.p.reactive.PingPongReactiveMain - 
18:10:57.762 [main] INFO  e.p.reactive.PingPongReactiveMain - createInitialContracts, contract #1, sender=Alice, receiver=Bob
18:10:57.989 [main] INFO  e.p.reactive.PingPongReactiveMain - submitAndWait: Ping-Alice-1, Party=Alice, Command=CreateCommand{templateId=Identifier{packageId='b8e864550d5d351c0d66cc8d6a899a119d06e900dfedc64396e284f69a8e27e6', moduleName='PingPong', entityName='Ping'}, createArguments=DamlRecord{recordId=Optional[Identifier{packageId='b8e864550d5d351c0d66cc8d6a899a119d06e900dfedc64396e284f69a8e27e6', moduleName='PingPong', entityName='Ping'}], fields=[Field{label=Optional[sender], value=Party{value='ledger-party-f6f78dd4-0898-4036-a1f8-e4698cae29e2'}}, Field{label=Optional[receiver], value=Party{value='ledger-party-74db950a-23f0-4b64-8a17-ee42b7fa9c1d'}}, Field{label=Optional[count], value=Int64{value=0}}]}}
18:10:57.989 [main] INFO  e.p.reactive.PingPongReactiveMain - 
18:10:57.989 [main] INFO  e.p.reactive.PingPongReactiveMain - 
18:10:57.989 [main] INFO  e.p.reactive.PingPongReactiveMain - 
18:10:57.989 [main] INFO  e.p.reactive.PingPongReactiveMain - createInitialContracts, contract #0, sender=Bob, receiver=Alice
18:10:58.220 [main] INFO  e.p.reactive.PingPongReactiveMain - submitAndWait: Ping-Bob-0, Party=Bob, Command=CreateCommand{templateId=Identifier{packageId='b8e864550d5d351c0d66cc8d6a899a119d06e900dfedc64396e284f69a8e27e6', moduleName='PingPong', entityName='Pong'}, createArguments=DamlRecord{recordId=Optional[Identifier{packageId='b8e864550d5d351c0d66cc8d6a899a119d06e900dfedc64396e284f69a8e27e6', moduleName='PingPong', entityName='Pong'}], fields=[Field{label=Optional[sender], value=Party{value='ledger-party-74db950a-23f0-4b64-8a17-ee42b7fa9c1d'}}, Field{label=Optional[receiver], value=Party{value='ledger-party-f6f78dd4-0898-4036-a1f8-e4698cae29e2'}}, Field{label=Optional[count], value=Int64{value=0}}]}}
18:10:58.220 [main] INFO  e.p.reactive.PingPongReactiveMain - 
18:10:58.220 [main] INFO  e.p.reactive.PingPongReactiveMain - createInitialContracts, contract #1, sender=Bob, receiver=Alice
18:10:58.457 [main] INFO  e.p.reactive.PingPongReactiveMain - submitAndWait: Ping-Bob-1, Party=Bob, Command=CreateCommand{templateId=Identifier{packageId='b8e864550d5d351c0d66cc8d6a899a119d06e900dfedc64396e284f69a8e27e6', moduleName='PingPong', entityName='Pong'}, createArguments=DamlRecord{recordId=Optional[Identifier{packageId='b8e864550d5d351c0d66cc8d6a899a119d06e900dfedc64396e284f69a8e27e6', moduleName='PingPong', entityName='Pong'}], fields=[Field{label=Optional[sender], value=Party{value='ledger-party-74db950a-23f0-4b64-8a17-ee42b7fa9c1d'}}, Field{label=Optional[receiver], value=Party{value='ledger-party-f6f78dd4-0898-4036-a1f8-e4698cae29e2'}}, Field{label=Optional[count], value=Int64{value=0}}]}}
18:10:58.457 [main] INFO  e.p.reactive.PingPongReactiveMain - 

Looks like the app is sending n (defaults to 10) Ping requests from Alice to Bob and than sequently do the same from Bob to Alice (Ping)

I’ll try to be more clear.
Inside the pingPongReactive app there is a method called runIndefinitely which the main calls. I modified it to receive an Identifier (and called it once with Ping and once with Pong) and to support real Ledger auth.

I also changed the filter from anonymous filter (“NoFilter.instance”) to a one which support fetching by TemplateId and party

From:

new FiltersByParty(Collections.singletonMap(party, NoFilter.instance)), true);

to (filterFor method):

        // start the processors asynchronously
        aliceProcessor.runIndefinitely(Ping.TEMPLATE_ID);
        bobProcessor.runIndefinitely(Pong.TEMPLATE_ID);

public void runIndefinitely(Identifier identifier) {
        // assemble the request for the transaction stream
        logger.info("{} starts reading transactions. for entityName={}", party, identifier.getEntityName());

        Flowable<Transaction> transactions = client.getTransactionsClient().getTransactions(
                LedgerOffset.LedgerEnd.getInstance(),
                filterFor(identifier, PingPongReactiveMain.tpaPartyId), true, PingPongReactiveMain.tpaPartyAccessToken);

        logger.info("transactions count for {}", party);

        transactions.forEach(this::processTransaction);
    }

    private static TransactionFilter filterFor(Identifier templateId, String party) {
        InclusiveFilter inclusiveFilter = new InclusiveFilter(Collections.singleton(templateId));
        Map<String, Filter> filter = Collections.singletonMap(party, inclusiveFilter);
        return new FiltersByParty(filter);
    }

private void processTransaction(Transaction tx) {
        logger.info("processTransaction: {}", tx.toString());

        List<Event> exerciseEvents = tx.getEvents();

        List<Command> exerciseCommands = exerciseEvents.stream().filter(e -> e instanceof CreatedEvent).map(e -> (CreatedEvent) e)
                .flatMap(e -> processEvent(tx.getWorkflowId(), e))
                .collect(Collectors.toList());

        if (!exerciseCommands.isEmpty()) {
            client.getCommandClient().submitAndWait(
                    tx.getWorkflowId(),
                    PingPongReactiveMain.APP_ID,
                    UUID.randomUUID().toString(),
                    party,
                    exerciseCommands)
                    .blockingGet();
        }
    }

My question is why processTransaction doesn’t get called. I assume the transactionList is empty - why is that? Whats missing?

In general, there are three common causes for why you might observe an empty transaction stream:

  1. You are subscribing for the wrong parties. In an authorized setup this is fairly unlikely since you would usually get a permission denied error.
  2. You are subscribing for the wrong templates. As long as your identifiers are consistent between the initial creates and the transaction filters, that shouldn’t be an issue but for debugging NoFilter is often still easier which just returns all templates.
  3. Lastly, there is the obvious option of there actually not being any transactions.

I’d start by trying to reproduce this locally against Sandbox. If you can reproduce the issue there, push your code somewhere and I’m sure we can figure it out.

Hi Moritz,

Regarding your answer. I’ve checked that i pulled the correct module (pingPong) under the correct packageId.

Question 1: What does the process of subscription (that you mentioned) for party and templates includes?
It might be implemented it in the pingPongReacive without understanding its essence.
(By the way, i created Alice and Bob parties manually on dumlHub)

The module fetch method is working as expected:

private static boolean containsPingPongModule(GetPackageResponse getPackageResponse) {
        try {
            // parse the archive payload
            DamlLf.ArchivePayload payload = DamlLf.ArchivePayload.parseFrom(getPackageResponse.getArchivePayload());
            // get the DAML LF package
            DamlLf1.Package lfPackage = payload.getDamlLf1();
            // extract module names
            List<DamlLf1.InternedDottedName> internedDottedNamesList =
                    lfPackage.getInternedDottedNamesList();
            ProtocolStringList internedStringsList = lfPackage.getInternedStringsList();

            for (DamlLf1.Module module : lfPackage.getModulesList()) {
                DamlLf1.DottedName name = null;
                switch (module.getNameCase()) {
                    case NAME_DNAME:
                        name = module.getNameDname();
                        break;
                    case NAME_INTERNED_DNAME:
                        List<Integer> nameIndexes = internedDottedNamesList.get(module.getNameInternedDname()).getSegmentsInternedStrList();
                        List<String> nameSegments = nameIndexes.stream().map(internedStringsList::get).collect(Collectors.toList());
                        name = DamlLf1.DottedName.newBuilder().addAllSegments(nameSegments).build();
                        break;
                    case NAME_NOT_SET:
                        break;
                }
                if (name != null && name.getSegmentsList().size() == 1 && name.getSegmentsList().get(0).equals("PingPong")) {
                    return true;
                }
            }

        } catch (InvalidProtocolBufferException e) {
            logger.error("Error parsing DAML-LF package", e);
            throw new RuntimeException(e);
        }
        return false;
    }

After fetching the module, the programs creates 2 com.daml.ledger.javaapi.data.Identifier objects with 2 entityName’s: “Ping” and the second for “Pong”:

Identifier pingIdentifier = new Identifier(packageId, "PingPong", "Ping");
Identifier pongIdentifier = new Identifier(packageId, "PingPong", "Pong");

Now the program starts with the interesting staff:
A dedicated PingPongProcessor is created for both parties and we perform the runIndefinitely on both in an async mode.

Question 2: Which piece of code is responsible to invoke the “Listening” infinitely behaviour on the party?

Question 3: (This question was already asked in previous correspondance in this topic) Why does transactions object always empty although i did see Contracts created successfully (on the Live Data Ledger section) called PING (created by Alice) and Pong (created by Bob)

I didn’t add the processTransaction method body below (last line) since its never called because the transactions object has no transactions
Another worth while info is that i tried using both filters in this fetch action (one includes what you advised which is “NoFilter”) with no change

        // start the processors asynchronously
aliceProcessor.runIndefinitely(Ping.TEMPLATE_ID, alicePartyId, aliceToken);
bobProcessor.runIndefinitely(Pong.TEMPLATE_ID, bobPartyId, bobToken);

public void runIndefinitely(Identifier identifier, String partyId, String token) {
        // assemble the request for the transaction stream
        logger.info("{} starts reading transactions. for entityName={}", party, identifier.getEntityName());

        Flowable<Transaction> transactions = client.getTransactionsClient().getTransactions(
                LedgerOffset.LedgerEnd.getInstance(),
                //new FiltersByParty(Collections.singletonMap(party, NoFilter.instance)), true);
                filterFor(identifier, partyId/*PingPongReactiveMain.tpaPartyId*/), true, token/*PingPongReactiveMain.tpaPartyAccessToken*/);

        transactions.forEach(t-> logger.info("transaction = {}", t.toString()));

        logger.info("transactions count for party {}", party);

        transactions.forEach(this::processTransaction);
    }

private static TransactionFilter filterFor(Identifier templateId, String party) {
        logger.info("Filtering transactions by templateId {} party {}", templateId, party);

        InclusiveFilter inclusiveFilter = new InclusiveFilter(Collections.singleton(templateId));
        Map<String, Filter> filter = Collections.singletonMap(party, inclusiveFilter);
        return new FiltersByParty(filter);
    }

After that comes the last part in the program. The one which actually create the contract by creating a command and submit it to the Ledger.

numInitialContracts = 2; // default value
createInitialContracts(client, ALICE, BOB, pingIdentifier, numInitialContracts);
createInitialContracts(client, BOB, ALICE, pongIdentifier, numInitialContracts);

private static void createInitialContracts(LedgerClient client, String sender, String receiver, Identifier identifier, int numContracts) {

        String senderPartyId = "";
        String receiverPartyId = "";
        String commandSubmitterPartyId = "";
        String commandSubmitterToken = "";

        if(sender.equals("Alice")){
            senderPartyId = alicePartyId;
            receiverPartyId = bobPartyId;
            commandSubmitterPartyId = alicePartyId;
            commandSubmitterToken = aliceToken;
        }else if(sender.equals("Bob")){
            senderPartyId = bobPartyId;
            receiverPartyId = alicePartyId;
            commandSubmitterPartyId = bobPartyId;
            commandSubmitterToken = bobToken;
        }

        for (int i = 0; i < numContracts; i++) {
            logger.info("createInitialContracts, contract #{}, sender={}, receiver={}", i, sender, receiver);

            // command that creates the initial Ping contract with the required parameters according to the model
            CreateCommand createCommand = new CreateCommand(identifier,
                    new DamlRecord(
                            identifier,
                            new DamlRecord.Field("sender", new Party(senderPartyId)), // <--------- Here i create the party using its partyId
                            new DamlRecord.Field("receiver", new Party(receiverPartyId)), // <--------- Same here
                            new DamlRecord.Field("count", new Int64(0))
                    )
            );

            // asynchronously send the commands
            client.getCommandClient().submitAndWait(
                    String.format("Ping-%s-%d", sender, i),
                    APP_ID, <-- Tis was fixed to be "damlHub" in order to avoid PERMISSION_DENIED error
                    UUID.randomUUID().toString(),
                    /*sender*/commandSubmitterPartyId,   //<------ HERE i pass the party id like you advised. The party that is the sender which performs the "submit'
                    Collections.singletonList(createCommand),
                    commandSubmitterToken)               //<------ HERE i pass the party's token like you advised
                    .blockingGet();

            logger.info("submitAndWait: {}, Party={}, Command={}", String.format("Ping-%s-%d", sender, i), sender, createCommand.toString());

        }

The above createInitialContracts method is called twice: For Alice as a sender (and her identifier object) and for Bob as a receiver and vise versa.
It seems like this method is working since i see contracts created. Each run there 4 2 contract created by Alice (“Ping”) and 2 by bob (“Pong”)

The program finishes after a 5 seconds sleep which i extended to 120 seconds in order to try to keep it hanging in a listen mode and navigate to damlHub to manually clicking on the “RespondPong” action for Alice’s contracts (or “RespondPing” action for Bob’s contracts) but i dont see any movement or activity like i would have expected from a listening gRPC server.
Instead an error is thrown but it seems like its related to the connection between the java app and the Ledger since its not happening right after the manual “RespondPing” (or “RespondPong”) action.

Question 4: Why isn’t the program (“pingPongReactive”) simulates any listening mode and react to the Ledger actions on each Party? I would expect the upon each “RespondPing” Bob will wake up and do something and vise versa with Alice’s “RespondPong”.

The error:

io.reactivex.exceptions.OnErrorNotImplementedException: UNAVAILABLE: HTTP status code 502
invalid content-type: text/html; charset=UTF-8
headers: Metadata(:status=502,content-type=text/html; charset=UTF-8,referrer-policy=no-referrer,content-length=332,date=Wed, 23 Feb 2022 18:38:01 GMT,alt-svc=clear)
DATA-----------------------------

502 Server Error

Error: Server Error

The server encountered a temporary error and could not complete your request.

Please try again in 30 seconds.

DATA-----------------------------

at io.reactivex.internal.functions.Functions$OnErrorMissingConsumer.accept(Functions.java:704)
at io.reactivex.internal.functions.Functions$OnErrorMissingConsumer.accept(Functions.java:701)
at io.reactivex.internal.subscribers.LambdaSubscriber.onError(LambdaSubscriber.java:79)
at io.reactivex.internal.operators.flowable.FlowableFlattenIterable$FlattenIterableSubscriber.checkTerminated(FlowableFlattenIterable.java:396)
at io.reactivex.internal.operators.flowable.FlowableFlattenIterable$FlattenIterableSubscriber.drain(FlowableFlattenIterable.java:256)
at io.reactivex.internal.operators.flowable.FlowableFlattenIterable$FlattenIterableSubscriber.onError(FlowableFlattenIterable.java:182)
at io.reactivex.internal.subscribers.BasicFuseableSubscriber.onError(BasicFuseableSubscriber.java:101)
at com.daml.grpc.adapter.client.rs.BufferingResponseObserver.lambda$onError$3(BufferingResponseObserver.java:81)
at java.base/java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1128)
at java.base/java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:628)
at java.base/java.lang.Thread.run(Thread.java:834)

Caused by: io.grpc.StatusRuntimeException: UNAVAILABLE: HTTP status code 502
invalid content-type: text/html; charset=UTF-8
headers: Metadata(:status=502,content-type=text/html; charset=UTF-8,referrer-policy=no-referrer,content-length=332,date=Wed, 23 Feb 2022 18:38:01 GMT,alt-svc=clear)

Sorry for the long question.
My next step is to push my code to a dedicated branch, pull again the original and run it against a Sandbox ledger.