Will there be naming conflicts if I upgrade my contracts on Daml on Fabric (or any ledger)?

Mod note: This question was posted on GitHub issues but that’s not the right place for it so it was moved here

I was running daml on fabric.
Had one scenario for question :-

Suppose consider, that I have a template :-

module ExampleModule where

template Example
  with
    admin: Party
    variable: Text
  where
    signatory admin

And now, I have deployed dar file related to this using daml runtime on fabric.

Now, I want to change the template and add additional field (The module name and the template name would be same)

module ExampleModule where

template Example
  with
    admin: Party
    variable: Text
    active: Bool
  where
    signatory admin

Now, I deployed this using daml runtime on the same fabric network as was earlier.

And so, it would have 2 templateIds by the same name (ExampleModule:Example).
So, would it be able to resolve as to which daml contract to create or retrieve?

I last checked, it was not able to resolve as they had similiar names, Is there a workaround for this or is it not right to update templates in this manner?

While I can’t answer your specific question someone else most certainly will and you should check Upgrading Daml applications — Daml SDK 1.10.0 documentation for the recommended upgrade process.

I’m not familiar with the specifics of Fabric, but on a Daml ledger template ids should include the full package id (hash of the entire code), which would allow you to disambiguate. I.e. your template ids would actually be something like:

baf83931e94648a3f20c253a803ab571a9cf6c9e09e84e61d844d5bf8f409529:ExampleModule:Example

and

9ffacafc6f9c539c87ead7e0a1cac426056ca5c1007e663d9f20817c4690bc20:ExampleModule:Example

which are different enough that the ledger won’t be confused.

1 Like

No. Here is a great tutorial that explains how you wold upgrade, and specifically how to work around the name clashes (step 3 of 9):

1 Like

What @Gary_Verhaegen said applies to all ledgers. The Ledger API always applies operates on identifiers of the form packageid:modulename:entityname. There is one caveat:

If you work with two such packages in a project, it can be a bit tricky to disambiguate. See Reference: Daml packages — Daml SDK 1.10.0 documentation for some docs on this.

1 Like

I don’t believe there should be any Fabric unique behaviour here because the packages are just being stored following their usual unique naming as with any other DAML ledger.

So if there are conflicts it would potentially be around the issues that @cocreature mentioned with ambiguity if you have different packages with the same module name in the same project.

@anthony what was the error received with the mentioned “it was not able to resolve as they had similiar names”

1 Like

I don’t know, the above was the full content of their post on GitHub. I encouraged them to post here so hopefully they can provide more context". My guess is maybe directly using the JSON API with template names?

The JSON API allows you to specify names either in the form modulename:templatename or packageid:modulename:templatename. The first works if it’s not ambiguous, if it is you have to use the second. If you use the typescript codegen it will automatically use the second so you never have to worry about this.

1 Like

Hi Everyone.

I went through the tutorials and the docs. I was able to manually upgrade the contracts, but was stuck in the automatic upgrades of all the contracts.

Consider this scenario.

Template - 0.0.1

module CarbonV where

template CarbonCertificate
  with
    issuer : Party
    carbon_metric_tons : Int
  where
    signatory issuer

Corresponding daml.yaml file –

sdk-version: 1.7.0
name: carbon
source: daml
version: 0.0.1
dependencies:
    - daml-prim
    - daml-stdlib
    - daml-script
sandbox-options:
    - --wall-clock-time

Now I created some contracts of the CarbonCertificate Template

And say suppose after some days we decide to add one more attribute to the CarbonCertificate Template.

Template - 0.0.2


module CarbonV where

template CarbonCertificate
  with
    issuer : Party
    carbon_metric_tons : Int
    one_more_attr : Int
  where
    signatory issuer

Corresponding daml.yaml file –

sdk-version: 1.7.0
name: carbon
source: daml
version: 0.0.2
dependencies:
    - daml-prim
    - daml-stdlib
    - daml-script
sandbox-options:
    - --wall-clock-time

After this I used the migration script …

module Migrate where

import qualified V1.CarbonV
import qualified V2.CarbonV

upgradeCarbonV : V1.CarbonV.CarbonCertificate -> V2.CarbonV.CarbonCertificate
upgradeCarbonV V1.CarbonV.CarbonCertificate{..} = V2.CarbonV.CarbonCertificate with one_more_attr = 123, ..

template MigrateCarbonCertificate
  with
    issuer: Party
  where
    signatory issuer


    nonconsuming choice DoMigrateCarbonCertificate : ContractId V2.CarbonV.CarbonCertificate
      with
        ccCid : ContractId V1.CarbonV.CarbonCertificate
      controller issuer
      do
        cc <- fetch ccCid
        archive ccCid
        create $ upgradeCarbonV cc

Now, what happens in this is I need the contract IDs of every contract that I have created earlier and then every time exercise the DoMigrateCarbonCertificate Choice. And, it would not be feasible to do this for every contract when the number of contracts grows (100+)

So, can anyone help me or guide me to an example that does a similiar thing.

1 Like

You could consider writing a script that does this for you. Something like:

import Daml.Script

upgrade = script do
  cidsAndContracts <- query @V1.CarbonV.CarbonCertificate alice 
  let (cids, _) = unzip cidsAndContracts
  forA_ cids $ submit alice . exerciseCmd migrationCid . DoMigrateCarbonCertificate

(Haven’t checked this compiles).

Then run with daml script

Would this approach be satisfactory?

1 Like

Hi, Thanks for the fast reply

I tried that, but it does not compile.

Also, I tried the following code and resolved some of the issues, but I am not able to resolve this one. :

Migration code :-

template MigrateCarbonCertificate
  with
    issuer: Party
  where
    signatory issuer

    controller issuer can

        nonconsuming DoMigrateCarbonCertificate : [ContractId V2.CarbonV.CarbonCertificate]
            do

                cidsAndContracts <- submit issuer do query @V1.CarbonV.CarbonCertificate
                let (cids, _) = unzip cidsAndContracts
                forA cids (\cid -> 
                                do
                                cc <- fetch cid
                                archive cid
                                create $ upgradeCarbonV cc)

when I try to compile this, the following error pops up :-

Compiling migration to a DAR.
File:     daml/Migrate.daml
Hidden:   no
Range:    79:54-79:89
Source:   typecheck
Severity: DsError
Message: 
  daml/Migrate.daml:79:54: error:
  • Couldn't match type ‘Script
  [(ContractId V1.CarbonV.CarbonCertificate,
  V1.CarbonV.CarbonCertificate)]’
  with ‘[(ContractId V1.CarbonV.CarbonCertificate, b0)]’
  Expected type: p0
  -> [(ContractId V1.CarbonV.CarbonCertificate, b0)]
  Actual type: p0
  -> Script
  [(ContractId V1.CarbonV.CarbonCertificate,
  V1.CarbonV.CarbonCertificate)]
  • In a stmt of a 'do' block: query @V1.CarbonV.CarbonCertificate
  In the second argument of ‘submit’, namely
  ‘do query @V1.CarbonV.CarbonCertificate’
  In a stmt of a 'do' block:
  cidsAndContracts <- submit
  issuer do query @V1.CarbonV.CarbonCertificate

How, can I solve this issue?
Also, I wanted to update the contracts through json APIs and so cannot use the earlier script code and so have to write choices for it

2 Likes

Hi @Harsh_Multani,

What this error is trying to tell you (in an admittedly opaque way) is that you are trying to use a function meant for Scripts (query) in the context of an Update (i.e. from within a template). There is no way (as far as I know) to query for contracts from within a choice; queries are meant to be done by clients before they submit their commands.

Here is a working example using scripts. Note that there is nothing special, as far as Daml is concerned, about upgrades: they are just changing one contract to another. Daml does not know that V2 is a “different version of V1”, as opposed to just any other contract, so for simplicity I have put all the definitions in a single file.

module Main where

import Daml.Script
import DA.Foldable (forA_)

template V1
  with
    owner: Party
    text: Text
  where
    signatory owner

template V2
  with
    owner: Party
    text: Text
    version: Int
  where
    signatory owner

template UpgradeV1_V2
  with
    owner: Party
  where
    signatory owner
    nonconsuming choice DoMigrate : ContractId V2
      with cid : ContractId V1
      controller owner
      do
        V1{..} <- fetch cid
        archive cid
        create $ V2 with version = 2, ..

setup : Script ()
setup = do
  alice <- allocatePartyWithHint "Alice" (PartyIdHint "Alice")
  bob <- allocatePartyWithHint "Bob" (PartyIdHint "Bob")
  return ()

createContracts : (Text, Text, Int) -> Script ()
createContracts (p, prefix, n) = do
  let Some party = partyFromText p
  forA_ [1..n] (\i -> do
    submit party $ createCmd V1 with owner = party, text = prefix <> show i)
  

upgradeContracts : Text -> Script ()
upgradeContracts p = do
  let Some party = partyFromText p
  upgrader <- submit party $ createCmd UpgradeV1_V2 with owner = party
  contracts <- query @V1 party
  forA_ contracts (\(cid, _) -> do
    submit party $ exerciseCmd upgrader DoMigrate with cid)
  submit party $ archiveCmd upgrader

If you create a new project with daml new t and then replace the contents of t/daml/Main.daml with the above code, you should be able to run through the following sequence.

First, in a separate terminal, run daml start. This will compile the code, start a sandbox, deploy the code to the sandbox, start navigator, and open up navigator in your default browser. At this point you should have two parties, Bob and Alice, as well as three templates, but no contract regardless of who you log in as.

Now we can run our scripts. Because the command is a bit long, I like making up an abbreviation for it, so I would start with:

run=(daml script --dar .daml/dist/t-0.0.1.dar --ledger-host localhost --ledger-port 6865 --wall-clock-time --script-name)

Afterwards, we can run the setup script with:

${run[@]} Main:setup

This should run to completion if you’re running it for the first time; subsequent runs will fail saying the parties already exist.

Once the parties exist, we can use the other two scripts. For example, after running:

${run[@]} Main:createContracts --input-file <(echo '["Alice", "prefix", 5]')

you should see 5 Main:V1 contracts in the navigator. If you run:

${run[@]} Main:createContracts --input-file <(echo '["Bob", "bob", 2]')

you should also see 2 Main:V1 contracts if you log out and log back in as Bob.

Finally, if you run:

${run[@]} Main:upgradeContracts --input-file <(echo '"Alice"')

you should see in navigator that all of Alice’s contracts are now at Main:V2, while Bob’s contracts are unaffected.

If for some reason you do not have access to the gRPC API, that’s not an issue: scripts can run just as well against the JSON API. There is a little bit of setup needed, though. Continuing on the same example, the first step is obviously to start the JSON API. Open up a third terminal to run:

daml json-api --http-port 3000 --ledger-host localhost --ledger-port 6865

Next, you need to create a token for your party. Let’s say we want to run commands as Bob. In order to create a valid token, we need to know the ledger ID, which in this case has been generated by the sandbox at startup. If you scroll up a bit in the sandbox terminal, you should see a line that looks like this:

INFO: Initialized sandbox version 1.11.0-snapshot.20210304.6422.0.d3d5042a with ledger-id = 5a9ac3c8-82e2-490f-bb21-656f0ed4cea0, port = 6865, dar file = List(.daml/dist/t-0.0.1.dar), time mode = wall-clock time, ledger = in-memory, auth-service = AuthServiceWildcard$, contract ids seeding = strong

This tells us that, in m case, the ledger ID is 5a9ac3c8-82e2-490f-bb21-656f0ed4cea0; it will be different for you. Now that we have the ledger ID, we can create a token by visiting jwt.io and pasting this in the second box on the right (this process is described in more details in the JSON API documentation):

{
  "https://daml.com/ledger-api": {
    "ledgerId": "5a9ac3c8-82e2-490f-bb21-656f0ed4cea0",
    "applicationId": "foobar",
    "actAs": [
      "Bob"
    ]
  }
}

In my case this gives the following value:

eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJodHRwczovL2RhbWwuY29tL2xlZGdlci1hcGkiOnsibGVkZ2VySWQiOiI1YTlhYzNjOC04MmUyLTQ5MGYtYmIyMS02NTZmMGVkNGNlYTAiLCJhcHBsaWNhdGlvbklkIjoiZm9vYmFyIiwiYWN0QXMiOlsiQm9iIl19fQ.Uo0Y90ngUQAjsDiy8g9ufu2XmbH1chtSY5MC8_gn2OM

which I saved as the bob_token file:

echo "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJodHRwczovL2RhbWwuY29tL2xlZGdlci1hcGkiOnsibGVkZ2VySWQiOiI1YTlhYzNjOC04MmUyLTQ5MGYtYmIyMS02NTZmMGVkNGNlYTAiLCJhcHBsaWNhdGlvbklkIjoiZm9vYmFyIiwiYWN0QXMiOlsiQm9iIl19fQ.Uo0Y90ngUQAjsDiy8g9ufu2XmbH1chtSY5MC8_gn2OM" > bob_token

With the setup out of the way, we can go back to the active terminal (where we ran the daml script commands via ${run[@]}) and type, for example;

daml script  --ledger-host http://localhost \
             --ledger-port 3000 \
             --json-api \
             --dar .daml/dist/t-0.0.1.dar \
             --wall-clock-time \
             --script-name Main:upgradeContracts \
             --input-file <(echo '"Bob"') \
             --access-token-file bob_token

Hope that helped.

1 Like

Hi @Gary_Verhaegen, Thanks for the idea. It did work on local sandbox ledger.

But for my solution above, when I am trying to upgrade contracts in fabric ledger using daml runtime :-

My Migration script is :-

module Migrate where

import Daml.Script
import DA.Foldable (forA_)

import qualified V1.CarbonV
import qualified V2.CarbonV

upgradeCarbonV : V1.CarbonV.CarbonCertificate -> V2.CarbonV.CarbonCertificate
upgradeCarbonV V1.CarbonV.CarbonCertificate{..} = V2.CarbonV.CarbonCertificate with one_more_attr = 123, ..

template MigrateCarbonCertificate
  with
    issuer: Party
  where
    signatory issuer

    controller issuer can

        nonconsuming DoMigrateCarbonCertificate : ContractId V2.CarbonV.CarbonCertificate
            with
                ccCid : ContractId V1.CarbonV.CarbonCertificate
            do
                cc <- fetch ccCid
                archive ccCid
                create $ upgradeCarbonV cc

upgradeContracts : Text -> Script ()
upgradeContracts p = do
  let Some party = partyFromText p
  upgrader <- submit party $ createCmd MigrateCarbonCertificate with issuer = party
  contracts <- query @V1.CarbonV.CarbonCertificate party
  forA_ contracts (\(ccCid, _) -> do
    submit party $ exerciseCmd upgrader DoMigrateCarbonCertificate with ccCid)
  submit party $ archiveCmd upgrader

The daml.yaml file related to this is :-

sdk-version: 1.7.0
name: migration
source: daml
parties:
- Alice
- Bob
- Charlie
version: 0.0.1
dependencies:
- daml-prim
- daml-stdlib
- daml-script
sandbox-options:
- --wall-clock-time
data-dependencies:
- ./carbon-0.0.1.dar
- ./carbon-0.0.2.dar
build-options:
- '--package'
- 'carbon-0.0.1 with(CarbonV as V1.CarbonV)'
- '--package'
- 'carbon-0.0.2 with (CarbonV as V2.CarbonV)'

I have deployed this succesfully to fabric ledger using daml runtime

Now when I run the command :-

daml script --ledger-host http://0.0.0.0 --ledger-port 6865 --json-api --dar .daml/dist/migration-0.0.1.dar --wall-clock-time --script-name Migrate:upgradeContracts --input-file <(echo '"Admin"') --access-token-file admin_token

To upgrade all of my contracts :-

It give me the following error :-

akka.http.scaladsl.model.IllegalResponseException: The server-side HTTP version is not supported.

Can you help me here?

1 Like

Hi @Harsh_Multani,

I’m not super familiar with Fabric but hopefully this should be independent of the underlying ledger storage.

The first issue I can see in your CLI invocation is the address of the server. http://0.0.0.0 is not a valid IP address. This is a bit confusing because services can (validly) declare to be listening on 0.0.0.0, but when a service says that it does not mean that the service’s address is 0.0.0.0, instead it means that the service will accept connections on any IP address (rather than a specific one).

So my advice would be to find out what the actual IP address of your JSON API is, and then use that as the --ledger-host parameter. Hopefully that should work, but if it doesn’t we should at least get a more informative error message.

1 Like

Sorry, for this,

But I also tried with localhost.

daml script --ledger-host http://localhost --ledger-port 6865 --json-api --dar .daml/dist/migration-0.0.1.dar --wall-clock-time --script-name Migrate:upgradeContracts --input-file <(echo '"Admin"') --access-token-file admin_token

And still the same issue occurs.

1 Like

You’ll need to remove the “http://” prefix, at the very least. That’s probably a valid host name, technically, but it won’t resolve. The hostname is “localhost”. And incidentally, Daml Script doesn’t communicate over HTTP at all.

Try:

$ daml script --ledger-host localhost --ledger-port 6865 ...
1 Like

Hi @SamirTalwar, I tried this.

It gives an IllegalUriException :-

Exception in thread "main" akka.http.scaladsl.model.IllegalUriException: Cannot determine request scheme and target endpoint as HttpMethod(POST) request to //:6865localhost/v1/create doesn't have an absolute URI

Another Question - If Script cannot communicate over http then how it is upgrading contracts in local sandbox ledger? I tried to upgrade contract in local sandbox ledger using the daml script command over daml json APIs - It was working.

Also, If this is the case that script cannot communicate over http and as @Gary_Verhaegen said that “query command” cannot be used in choices, so how to automatically upgrade contracts that are already there in fabric ledger?

1 Like

6865 is the port of the gRPC Ledger API. You either have to remove the --json-api option from your daml script command or connect to the JSON API which by default runs on port 7575. If you do have access to the gRPC API I would generally recommend to connect daml script to that. The --json-api option is really only intended for cases where you don’t have access to the gRPC API.

1 Like

Daml Script can run either over gRPC or over HTTP. If you specify --json-api, it will run over http and the --ledger-host value needs to start with http://. If you do not specify --json-api, Daml Script will instead connect over gRPC and the --ledger-host value must be a “naked” domain name (or IP address), without any URL scheme.

1 Like

Hi, Thanks @Gary_Verhaegen, @cocreature, @SamirTalwar, @Luciano, @anthony, @sormeter.

It works when I don’t communicate over json-api and directly access the gRPC Ledger API.

1 Like