Virtual Global Ledger

Hi Daml Community,

I am trying to run a canton network with 2 remote participants and 1 remote domain.

How is it possible to have a unique Ledger, (Global virtual Ledger)? Each participant has its own ledger. So how can i connect the 2 ledger (ledger from participant1 and ledger from participant2).

What i want to achieve is one ledger where both participants can create, query and exercise contracts. Right now i have a ledger for participant1 and i can create its own contracts, but i want to exercise with participant2 the contracts that were created by participant1. How can i achieve this, how can i connect the two ledgers, so that the 2 participants can interact with each other?

Thank you

The two participants should be connected to the same domain to be able to talk to each other. The canton getting started guide shows you a simple setup with two participants connected to a shared domain.

Im able to connect the two participants with the domain, and participant1 can create contracts with the JSON API , which are stored in the participant1 ledger.
How can i deploy these contracts in the domain so i can acess them with participant2 using JSON API?

I understand the logic in the documentation. The contracts are created in the console and uploaded in domain. Works like a single ledger. So how can i do the same as documentation but using JSON API and remote participants/domain?

Thank you

I’m not very familiar with Canton yet (I need to update myself…), but my understanding is that if both participants are connected to the same domain, you should be able to create a contract in participant1 where the creator/signatory is a party on participant1, and there is an observer which is a party on participant2, and that would make the contract accessible to the observer when they connect through participant2.

Have you tried that? If so, where does it break down?

The getting started guide I linked to shows you how to create contracts and interact with them from different participants. In that guide, they’re created via the Canton console but you can do the same thing via the JSON API or anything else.

I can create a contract in participant1 where the signatory is a party on participant1, (i used the command to create a party that you suggested in a later discuss). To do so i use JSON API with the command:

DAML_SDK_VERSION=2.1.1 daml json-api --ledger-host 192.168.61.128 --ledger-port 5011 --http-port 7575 --allow-insecure-tokens

Which means i have created a contract in the participant1 ledger in adress 192.168.61.128 port 5011.

Participant2 has its own ledger with a diferent adress and same port (my decision):

DAML_SDK_VERSION=2.1.1 daml json-api --ledger-host 192.168.61.130 --ledger-port 5011 --http-port 7575 --allow-insecure-tokens

With my JAVA API when i create a contract (with participant1) i need to designate the http request to create a contract, that is uploaded to participant1 ledger:

URL url = new URL(“http://localhost:7575/v1/create”);

This ledger is from Participant1, and Participant2 has its own ledger, the ledgers are not connected.

Aren’t the two ledgers supposed to be one?
image
I guess i do have a PCS in each participant and i want a Domain ledger only.

I’m not seeing any failed step in your description there. Would you mind trying the following steps and reporting where it breaks down?

  1. Create PartyA on participant1.
  2. Create PartyB on participant2.
  3. Build and push the default Daml project (daml new myroject) as a DAR on both sides.
  4. As PartyA on participant1, create an Asset contract with issuer = PartyA and owner = PartyB.
  5. As PartyB on participant2, exercise the Give choice on the resulting contract with newOwner = PartyA.
1 Like
  1. Main.daml PartyA
module Main where

import Daml.Script

type AssetId = ContractId Asset

data LedgerParties = LedgerParties with
  party : Party

template Asset
with
 issuer : Party
 owner  : Party
 name   : Text
where
 ensure name /= ""
 signatory issuer
 observer owner
 choice Give : AssetId
 with
    newOwner : Party
    controller owner
 do create this with
       owner = newOwner

setup : Script LedgerParties
 setup = script do
-- user_setup_begin
partyA <- allocatePartyWithHint "PartyA" (PartyIdHint "PartyA")
partyId <- validateUserId "party_a"
createUser (User partyId (Some partyA)) [CanActAs partyA]
pure (LedgerParties partyA)
-- user_setup_end

daml.yaml

sdk-version: 2.1.1
name: myproject
source: daml
init-script: Main:setup
version: 0.0.1
dependencies:
  - daml-prim
  - daml-stdlib
  - daml-script

Initiate Canton in participant1, participant2 and domain. Participants connected to the domain.

Run the command to create partyA in participant1:
daml script --dar .daml/dist/myproject-0.0.1.dar --ledger-host 192.168.61.128 --ledger-port 5011 --output-file partyA.json --script-name Main:setup

  1. Main and yaml are equal, except the Hint.
    Run the command to create partyA in participant2:
    daml script --dar .daml/dist/myproject-0.0.1.dar --ledger-host 192.168.61.130 --ledger-port 5011 --output-file partyB.json --script-name Main:setup

  1. Upload DAR:

participant1:

participant2:

Give me just a moment i have to change the code to create contracts

  1. Participant1 creates Asset.

JSON API lauch:

DAML_SDK_VERSION=2.1.1 daml json-api \
 --ledger-host 192.168.61.128 \
 --ledger-port 5011 \
 --http-port 7575 \
 --allow-insecure-tokens

Now i get this error:


I think it is related to the PartyB, because it was not created in participant1

i checked the package id and it is in json api…

What is in your token and assuming it’s a user token what are the rights for your user?

Also what command are you sending, in particular, who are the signatories of the contract you are trying to create?

This is my token for participant1:

{
"https://daml.com/ledger-api": {
  "ledgerId": "participant1",
  "applicationId": "foobar",
  "actAs": ["PartyA::1220640a45a8f038cbc70030c418d4389cfe71d63df467da470e530c96ba2823173d"] ,
  "readAs": ["PartyB::1220921e7b318c055e031968dc16a804b2c34150729830930e2ca8b63784d975f50a"]
  }
}

I show how i get the parties above.

This is my JAVA code to connect the JSON API:

public class myproject {

String partyA = "PartyA::1220640a45a8f038cbc70030c418d4389cfe71d63df467da470e530c96ba2823173d";
String partyB = "PartyB::1220921e7b318c055e031968dc16a804b2c34150729830930e2ca8b63784d975f50a";
String Token;
HttpURLConnection get;
HttpURLConnection post;
HttpURLConnection con;
StringBuilder result = new StringBuilder();
byte[] out;

void setDamlConnection() throws IOException {
    URL url = new URL("http://localhost:7575/v1/create"); //URL
    try {
        con = (HttpURLConnection) url.openConnection();
        generateJWT();
    } catch (IOException e) {
        e.printStackTrace();
    }
    con.setRequestMethod("POST");
    con.setDoOutput(true);
    String input = "{\"templateId\": \"Main:Asset\", \"payload\": {\"issuer\": " + "\"" + partyA + "\"" + ", \"owner\": " + "\"" + partyB + "\"" + ", \"name\": \"house\"}}";
    out = input.getBytes(StandardCharsets.UTF_8);
    int length = out.length;

    con.setFixedLengthStreamingMode(length);
    con.setAuthenticationProperty("Authorization", "Bearer " + Token);
    con.setRequestProperty("Content-Type", "application/json; charset=UTF-8");
    con.connect();

    initiateDamlConnection();
}

void initiateDamlConnection() throws IOException {
    try (OutputStream os = con.getOutputStream()) {
        os.write(out);
    }
}



public void generateJWT() throws UnsupportedEncodingException {
    //LINK: https://dev.to/pradipta/a-problem-with-jjwt-with-java-11-9-2cm4
    //      https://www.programmersought.net/article/326220730.html
    Map<String, Object> claims = new HashMap<String, Object>();
    claims.put("ledgerId", "participant1");
    String[] arrayAlice = new String[]{partyA};
    String[] arrayBob = new String[]{partyB};
    claims.put("actAs", arrayAlice);
    claims.put("readAs", arrayBob);
    claims.put("applicationId", "foobar");

    Map<String, Object> claimsMap = new HashMap<String, Object>();
    claimsMap.put("https://daml.com/ledger-api", claims);

    String jwtToken = Jwts.builder()
            .setClaims(claimsMap)
            .signWith(SignatureAlgorithm.HS256, "sYn2kjibddFAWtnPJ2AFlL8WXmohJMCvigQggaEypa5E=".getBytes("UTF-8"))
            .setHeaderParam("typ", Header.JWT_TYPE)
            .compact();

    System.out.println("The generated jwt token is as follows:" + jwtToken);
    Token = jwtToken;
}

public static void main(String[] args) throws IOException {
    new myproject().setDamlConnection();
}

}

  1. It worked now (i executed the daml script command again, updated PartyA):

So in my understanding i created a contract in participant1 ledger (address: localhost port:5011), it contains issuer = PartyA and owner = PartyB.

To execute step 5 participant2 needs to access this ledger in order to execute the contract, because it is only stored there. Is this the only way for the participant2 to exercise a choice in this contract? Shouldn’t there be a global ledger for all the participants to connect?

Having a virtual global ledger doesn’t mean all participants are created equal. By default each party is hosted on a single participant and submissions for that party must go through that participant. Among other things this is a natural consequence of Daml’s privacy model: If you were able to submit through arbitrary participants, those participants would need to know about the contracts visibile to that party.

Note that by default this also applies to readAs. So your token doesn’t make sense since it refers to a party on a different participant in readAs. This doesn’t matter for this example because you don’t actually rely on seeing that data via readAs.

There is a preview feature for selectively hosting a party on multiple participants.

1 Like

Apologies for being dense here but it’s unclear to me whether you did manage to run step 5 or not. I assume you did manage to run step 4?

As @cocreature mentioned, in order to run step 5 you’d need to connect as PartyB to participant2 using a token that has actAs PartyB, and doesn’t mention PartyA. The token used for step 4 should not mention PartyB, either.

If you’re going through the JSON API, you’ll probably need to run two JSON API instances, one for each participant.

1 Like

Hello again!

I figured out how to make it work. I was not understanding that the contracts created with participant1 → partyA in ledger1 are also visible in participant2 ledger2 as long as they have partyB from participant2 as observer.

I also change my token as you suggested.

I have another question now, is it possible to get the party id of participant2 in participant1, and and vice versa?

Thank you both for your time.

I have another question now, is it possible to get the party id of participant2 in participant1, and and vice versa?

There is nothing builtin for that. You can use alias contracts like used in create-daml-app. I recommend reading through Parties and users in Daml 2.0 to get an idea for how to handle parties.

2 Likes