Automating the Upgrade Process¶
In this section, we are going to automate the upgrade of our carbon certificate process using Daml Script and Daml Triggers. Note that automation for upgrades is specific to an individual application, just like the upgrade models. Nevertheless, we have found that the pattern shown here occurs frequently.
This section describes manual upgrading steps when deploying updated Daml application code, by migrating contracts from one version to another. A production-grade, optimized migration tool that automates this application upgrade process is available, but it requires a Daml Enterprise license. Its PDF manual explains how a Daml developer implements a smart contract upgrade migration. The manual also advises an operations team on how to perform the contract migration.
The tool is designed to accommodate:
- Working with a stale view of the ledger’s state (e.g., a partial migration was recently performed);
- Reduce effort by boilerplate code generation for unchanged templates;
- It has idempotent operations where possible;
- Very large ACS sizes; and
- Robustly handles rejected ledger commands (e.g., archiving a contract in the same Daml transaction thatcreates an upgraded replacement helps to ensure that duplicate replacement contracts are not created).
Please contact your account manager or DA support for access to the upgrade tool.
Structure the Upgrade¶
There are three kinds of actions performed during the upgrade:
- Alice creates
UpgradeCarbonCertProposal
contracts. We assume here, that Alice wants to upgrade allCarbonCert
contracts she has issued. Since theUpgradeCarbonCertProposal
proposal is specific to each owner, Alice has to create oneUpgradeCarbonCertProposal
per owner. There can be potentially many owners but this step only has to be performed once assuming Alice will not issue moreCarbonCert
contracts after this point. - Bob and other owners accept the
UpgradeCarbonCertProposal
. To keep this example simple, we assume that there are only carbon certificates issued by Alice. Therefore, each owner has to accept at most one proposal. - As owners accept upgrade proposals, Alice has to upgrade each certificate. This means that she has to execute the upgrade choice once for each certificate. Owners will not all accept the upgrade at the same time and some might never accept it. Therefore, this should be a long-running process that upgrades all carbon certificates of a given owner as soon as they accept the upgrade.
Given those constraints, we are going to use the following tools for the upgrade:
- A Daml script that will be executed once by Alice and creates an
UpgradeCarbonCertProposal
contract for each owner. - Navigator to accept the
UpgradeCarbonCertProposal
as Bob. While we could also use a Daml script to accept the proposal, this step will often be exposed as part of a web UI so doing it interactively in Navigator resembles that workflow more closely. - A long-running Daml trigger that upgrades all
CarbonCert
contracts for which there is a correspondingUpgradeCarbonCertAgreement
.
Implementation of the Daml Script¶
In our Daml Script, we are first going to query the ACS (Active Contract Set) to find all
CarbonCert
contracts issued by us. Next, we are going to extract the
owner of each of those contracts and remove any duplicates coming from
multiple certificates issued to the same owner. Finally, we iterate over the
owners and create an UpgradeCarbonCertAgreement
contract for each owner.
initiateUpgrade : Setup.Parties -> Script ()
initiateUpgrade Setup.Parties{alice} = do
certs <- query @CarbonCert alice
let myCerts = filter (\(_cid, c) -> c.issuer == alice) certs
let owners = dedup $ map (\(_cid, c) -> c.owner) myCerts
forA_ owners $ \owner -> do
debugRaw ("Creating upgrade proposal for: " <> show owner)
submit alice $ createCmd (UpgradeCarbonCertProposal alice owner)
Implementation of the Daml Trigger¶
Our trigger does not need any custom user state and no heartbeat so the only interesting field in its definition is the rule.
upgradeTrigger : Trigger ()
upgradeTrigger = Trigger with
initialize = pure ()
updateState = \_msg -> pure ()
registeredTemplates = AllTemplates
heartbeat = None
rule = triggerRule
In our rule, we first filter out all agreements and certificates issued by
us. Next, we iterate over all agreements. For each agreement we filter
the certificates by the owner of the agreement and finally upgrade the certificate
by exercising the Upgrade
choice. We mark the certificate as pending
which temporarily removes it from the ACS and therefore stops the
trigger from trying to upgrade the same certificate multiple times if the
rule is triggered in quick succession.
triggerRule : Party -> TriggerA () ()
triggerRule issuer = do
agreements <-
filter (\(_cid, agreement) -> agreement.issuer == issuer) <$>
query @UpgradeCarbonCertAgreement
allCerts <-
filter (\(_cid, cert) -> cert.issuer == issuer) <$>
query @CarbonCert
forA_ agreements $ \(agreementCid, agreement) -> do
let certsForOwner = filter (\(_cid, cert) -> cert.owner == agreement.owner) allCerts
forA_ certsForOwner $ \(certCid, _) ->
emitCommands
[exerciseCmd agreementCid (Upgrade certCid)]
[toAnyContractId certCid]
The trigger is a long-running process and the rule will be executed whenever the state of the ledger changes. So whenever an owner accepts an upgrade proposal, the trigger will run the rule and upgrade all certificates of that owner.
Deploy and Execute the Upgrade¶
Now that we defined our Daml script and our trigger, it is time to use them! If you still have Sandbox running from the previous section, stop it to clear out all data before continuing.
First, we start sandbox passing in the carbon-upgrade
DAR. Since a
DAR includes all transitive dependencies, this includes carbon-1.0.0
and carbon-2.0.0
.
$ cd example/carbon-upgrade
$ daml sandbox --dar .daml/dist/carbon-upgrade-1.0.0.dar
To simplify the setup here, we use a Daml script to create 3 parties
Alice, Bob and Charlie and two CarbonCert
contracts issues by
Alice, one owned by Bob and one owned by Charlie. This Daml script
reuses the Setup.setup
Daml script from the previous section to
create the parties & users.
setup : Script Setup.Parties
setup = do
parties@Setup.Parties{..} <- Setup.setup
bobProposal <- submit alice $ createCmd (CarbonCertProposal alice bob 10)
submit bob $ exerciseCmd bobProposal CarbonCertProposal_Accept
charlieProposal <- submit alice $ createCmd (CarbonCertProposal alice charlie 5)
submit charlie $ exerciseCmd charlieProposal CarbonCertProposal_Accept
pure parties
Run the script as follows:
$ cd example/carbon-initiate-upgrade
$ daml build
$ daml script --dar=.daml/dist/carbon-initiate-upgrade-1.0.0.dar --script-name=InitiateUpgrade:setup --ledger-host=localhost --ledger-port=6865 --output-file parties.json
As before, parties.json
contains the actual party ids we can use later.
If you now start Navigator from the carbon-initiate-upgrade
directory and log in as alice
, you can see the two CarbonCert
contracts.
Next, we run the trigger for Alice. The trigger will keep running throughout the rest of this example.
$ cd example/carbon-upgrade-trigger
$ daml build
$ daml trigger --dar=.daml/dist/carbon-upgrade-trigger-1.0.0.dar --trigger-name=UpgradeTrigger:upgradeTrigger --ledger-host=localhost --ledger-port=6865 --ledger-user=alice
With the trigger running, we can now run the script to create the
UpgradeCarbonCertProposal
contracts (we could also have done that before
starting the trigger). The script takes an argument of type
Parties
corresponding to the result of the previous setup
script.
We can pass this in via the --input-file
argument.
$ cd example/carbon-initiate-upgrade
$ daml build
$ daml script --dar=.daml/dist/carbon-initiate-upgrade-1.0.0.dar --script-name=InitiateUpgrade:initiateUpgrade --ledger-host=localhost --ledger-port=6865 --input-file=parties.json
At this point, our trigger is running and the UpgradeCarbonCertProposal
contracts for Bob and Charlie have been created. What is left to do is
to accept the proposals. Our trigger will then automatically pick them
up and upgrade the CarbonCert
contracts.
First, start Navigator and log in as bob
. Click on the
UpgradeCarbonCertProposal
and accept it. If you now go back to the
contracts tab, you can see that the CarbonCert
contract has been
archived and instead there is a new CarbonCertWithMethod
upgrade. Our
trigger has successfully upgraded the CarbonCert
!
Next, log in as charlie
and accept the UpgradeCarbonCertProposal
. Just
like for Bob, you can see that the CarbonCert
contract has been archived
and instead there is a new CarbonCertWithMethod
contract.
Since we upgraded all CarbonCert
contracts issued by Alice, we can now stop the
trigger and declare the update successful.