Upgrading contracts and states in Corda

Amol Pednekar
5 min readJan 9, 2019

I recently submitted a CorDapp sample to R3 Corda’s community CorDapps ecosystem, which demonstrates upgrading the contracts and states of a CorDapp. In this article, I’m going to walk you through this process, and show how to upgrade states & contracts simultaneously.

Lets discuss some key concepts around CorDapp upgrades first.

There are two types of contract/state upgrades:

  1. Implicit: By allowing multiple implementations of the contract ahead of time, using constraints. The advantage of pre-authorizing upgrades using constraints is that you don’t need the heavyweight process of creating upgrade transactions for every state on the ledger. The disadvantage is that you place more faith in third parties, who could potentially change the app in ways you did not expect or agree with.
  2. Explicit: By creating a special contract upgrade transaction and getting all participants of a state to sign it using the contract upgrade flows. The advantage of using the explicit upgrade approach is that you can upgrade states regardless of their constraint, including in cases where you didn’t anticipate a need to do so.

In this article, we’re going to explore Explicit upgrades only.

In an explicit upgrade, contracts and states can be changed in arbitrary ways, if and only if all of the state’ s participants agree to the proposed upgrade. The following combinations of upgrades are possible:

  1. A contract is upgraded while the state definition remains the same.
  2. A state is upgraded while the contract stays the same.
  3. The state and the contract are upgraded simultaneously.

While we’re only going to cover the 3rd case, I will also list references for the other scenarios at the end of the article (they follow a very similar flow)

The Upgrade Process

Start by upgrading the contract and/or state definitions. There are no restrictions on how states are upgraded. However, upgraded contracts must implement the UpgradedContract interface. This interface is defined as:

interface UpgradedContract<in OldState : ContractState, out NewState : ContractState> : Contract {
/**
* Name of the contract this is an upgraded version of, used as part of verification of upgrade transactions.
*/
val legacyContract: ContractClassName
/**
* Upgrade contract's state object to a new state object.
*
*
@throws IllegalArgumentException if the given state object is not one that can be upgraded. This can be either
* that the class is incompatible, or that the data inside the state object cannot be upgraded for some reason.
*/
fun upgrade(state: OldState): NewState
}

This interface can upgrade state objects issued by a contract to a new state object issued by a different contract. The upgraded contract should specify the legacy contract class name, and provide an upgrade function that will convert legacy contract states into states defined by this contract.

By default this new contract will only be able to upgrade legacy states which are constrained by the zone whitelist (see API: Contract Constraints). If hash or other constraint types are used, the new contract should implement UpgradedContractWithLegacyConstraint instead, and specify the constraint explicitly:

interface UpgradedContractWithLegacyConstraint<in OldState : ContractState, out NewState : ContractState> : UpgradedContract<OldState, NewState> {
/**
* A validator for the legacy (pre-upgrade) contract attachments on the transaction.
*/
val legacyContractConstraint: AttachmentConstraint
}

An example

Now lets take a look at our example CorDapp.

The old contract and state definition is defined below:

The OldState implements ContractState and takes input of two participants “a” and “b”. The OldContract has the ID “ com.upgrade.OldContract”, and the verify method always returns true. We define one command called “Action” that is used during state transformations.

Defining the new contract and state

The new state definition (line #14) adds an attribute “value” of type Integer.

Since we are upgrading the state as well, the upgrade function (line #9) is overridden to update existing states to this new state and setting a default value for the newly added field.

Next, we define the new contract ID as “com.upgrade.NewContractAndState”, and update the contract’s verify function to add a simple check if the ‘value’ attribute is greater than zero. The command stays the same as earlier, but you can add or remove commands from here just the same!

That’s it. Now that we have our new contract and state, it’s time to see how the upgrade process works!

Create some old states using the old contract (these will be upgraded subsequently)

A TransactionState has a constraint field that represents that state’s attachment constraint. When a party constructs a TransactionState, or adds a state using TransactionBuilder.addOutput(ContractState) without specifying the constraint parameter, a default value (AutomaticHashConstraint) is used. This default will be automatically resolved to a specific HashAttachmentConstraint or a WhitelistedByZoneAttachmentConstraint(we will see this in our output later).

This client starts a flow that issues OldState using OldContract. We wait 5 seconds for the transaction to propagate.

Authorizing the upgrade

The next step is for all nodes to run the ContractUpgradeFlow.Authorise flow. This flow takes a StateAndRef of the state to update as well as a reference to the new contract, which must implement the UpgradedContract interface.

At any point, a node administrator may de-authorize a contract upgrade by running the ContractUpgradeFlow.Deauthorise flow.

Performing the upgrade

Once all nodes have performed the authorization process, a participant must be chosen to initiate the upgrade via theContractUpgradeFlow.Initiate flow for each state object.

Once the flow ends successfully, all the participants of the old state object should have the upgraded state object which references the new contract code.

Log the state to show that its contract and state has been upgraded using

partyAProxy.vaultQuery(NewContractAndState.NewState::class.java).states.forEach { logger.info("{}", it.state) }

You should see a message of the form:

```I 09:47:54 1 UpgradeContractStateClient.main - TransactionState(data=NewState(a=O=PartyA, L=London, C=GB, b=O=PartyB, L=New York, C=US, value=1), 
contract=com.upgrade.NewContractAndState, notary=O=Notary, L=London, C=GB, encumbrance=null, constraint=net.corda.core.contracts.WhitelistedByZoneAttachmentConstraint@649b5891)```

And that’s it! We have upgraded our contract and state. You can view the full example HERE.

Here are examples that show the other combinations, which follow a similar approach.

  1. Upgrading the state without upgrading the contract: LINK
  2. Upgrading contract whilst keeping the old state: LINK

Documentation reference: https://docs.corda.net/upgrading-cordapps.html#contract-and-state-versioning

If you found this post helpful, then you can follow me on Twitter at _amolpednekar to give suggestions for future articles and collaboration opportunities.

--

--

Amol Pednekar

Building the future, one BLOCK at a time. Blockchain R&D engineer @PersistentSys.