Contract Upgrades and Constraints in Corda

September 27, 2019

Upgrades are inevitable, no matter how much planning or effort you put into developing something incredible, you can’t avoid changes in the future. The change could be because of a small bug that you might have overlooked, or because of some new cool feature. No matter what, it is bound to happen at some point.

DLT (Distributed Ledger Technology) and blockchain with all its promises of a better, safe, and secure future, still face a challenge with upgrades. You probably already know that one of the properties of blockchain that makes it special is immutability, which means nothing can change once recorded. This particular property which potentially makes blockchain a great solution also makes upgrades particularly difficult.

How do upgrades work in other Blockchains?

Upgrades in Bitcoin happen via voting, essentially a lot more complex than it sounds, but put simply: whenever a new version is proposed by the development team, the nodes vote on whether or not they are upgrading to the newer version, and if a majority of the nodes agree to upgrade, the new version becomes the new network. It seems very simple in theory but is practically very difficult to achieve.

Ethereum, on the other hand, has smart contracts. Smart contracts are just business rules coded and stored in the Ethereum blockchain, which means once coded and deployed they can’t be changed. Upgrades are managed by versioning, for every change, a new version of the smart contract is deployed, and applications interacting with the smart contract are updated to point to the new version. Extreme care must be taken to decouple data and business logic in order to avoid data migration for every new upgrade.

Upgrades in Corda

Since you are reading about upgrades, I am assuming you have an understanding of the key concepts of Corda especially states, contracts and flows.

We will concentrate on contract constraints in this blog. Constraints are the way that Corda manages contract upgrades. It imposes rules on which contracts should be accepted by the node and, prevents attacks if a node installs a modified contract which could potentially benefit them.

Constraints ease the pain of upgrading contracts in Corda, multiple versions of a contract can exist in a network provided they satisfy the constraints. Therefore, nodes in the network can co-exist and transact (in most cases) with different versions of the same contract. This allows nodes to delay their upgrades (for whatever reason) and run an older version of the contract and still transact within the network until they get an opportunity to upgrade.

Types of Constraints:

Hash Constraints: A legacy constraint used in Corda. Only one version of an app can be used since the hash of the CorDapp jar is used as a constraint. Any updates to the jar would change the hash and hence the constraint would be violated. Upgrades can only be done explicitly, as discussed later.

Compatibility Zone Whitelisted Constraint: Introduced with Corda 3, which also opened the way for implicit upgrades, which made upgrades lot easier. The compatibility zone operator provides a list of acceptable contract versions as part of the network parameters. Any contract can be used as long as it is present in the whitelist.

Signature Constraint: The most recent addition as part of Corda 4, this is also the recommended approach as of Corda 4. Any version of a contract can be used as long as the CorDapp jar is signed by a specific composite key. This was an improvement which made implicit upgrades even easier, however, caution should be taken to maintain backward compatibility.

Always Accept Constraint: Finally the always accept constraint, which means no constraint at all, any versions can be used. This is only suitable for test environments and should not be used in a real deployment.

Upgrades in Corda can happen in two ways: Implicit or Explicit.

Explicit Upgrades

The explicit upgrade process is the hard way of doing it. It’s a heavyweight process where all parties must authorize the upgrade before it can happen. Explicit upgrades allow bypassing of the constraints in place. As mentioned earlier, contracts using hash constraint can only be upgraded using this approach as the hash of the CorDapp jar is bound to change when upgraded.

Typically, a contract upgrade transaction is created and all parties are required to sign to authorize the transaction. This is done by initiating the ContractUpgradeFlow.Authorise flow. Once authorized, any party can initiate the upgrade using the ContractUpgradeFlow.Initiate flow. Both these flow takes the old states and the new contract class as the input parameters.

A few points to consider:

  • If you are developing in Corda 4, ensure CorDapp signing is disabled. Corda 4 uses signature constraint by default, so signing is enabled by default. Explicit upgrades are not supported for contracts using signature constraints as of Corda 4. You can disable signing in your build.gradle file.
cordapp {
    signing {
        enabled false
    }
}
  • If you are using Compatibility Zone Whitelisted Constraint the new contract must extend the UpgradedContract class. It requires you to override the getLegacyContract() method and the upgrade() method other than the verify() method in your new contract. The getLegacyContract() method should return the identifier of the old contract and the upgrade() method is where you define how your old states transform or upgrade to the new state. See example below:

  • If you are using a Hash Constraint, the new contract must extend the UpgradedContractWithLegacyConstraint class. Along with the getLegacyContract(), upgrade() and verify() methods an additional getLegacyContractConstraint() method is required to be implemented. This method should return the HashAttachmentConstraint of the previous version of the CorDapp jar. The HashAttachmentConstraint requires the SHA256 hash of the older CorDapp jar.

Implicit Upgrades

Implicit upgrades are applicable to Compatibility Zone Whitelisted Constraint and Signature Constraints. In this approach, we pre-authorize multiple versions of the contract beforehand. This makes upgrades extremely easy in Corda. It doesn’t require all the parties to upgrade at once, but the parties upgrade individually and can wait until they are ready to upgrade while still being able to transact with the older contract versions.

Essentially an old version of the CorDapp jar is replaced with the newer one. However, a word of caution here: maintaining backwards compatibility is essential, meaning the older issued states should be compatible with the new contracts and flows. For this purpose, all new fields introduced in the new version of the state should be made Nullable, so that the older issued states still remain compatible with the new one and there is no issue faced while deserializing the states with the newer version of the code.

While parties running the older version of the contracts can still initiate transactions with counterparties as those running the newer versions, they can’t respond to a transaction initiated with the newer version of the contract. This is because they would not be able to validate the transaction unless they have upgraded to the version the transaction was initiated with.

Flow Upgrades

Starting in Corda 4, the recommended approach of developing CorDapps is to have separate modules for contracts-states and flows.

The preferred naming of these modules are:

  • workflows- The module containing flows and other business logic
  • contracts- The module containing both contracts and states (formally named contracts-and-states in most Corda samples).

See Structuring a CorDapp for more details. This is done in order to separate the flow logic from the contracts and states, as changes to a number of flows may not necessarily require a contract upgrade.

A good idea while upgrading a flow is to use flow versioning. The @InitiatingFlow annotation provides a version property which defaults to 1. This helps to identify which version of the flow the counterparty is running and you could run the correct flow logic corresponding to that version while maintaining backward compatibility.

Shown below is an example of a backwards-compatible flow which runs CollectSignaturesFlow only if the counterparty has the correct version of the flow to respond to it.

Ensure that the Initiator and the Responder flows are always in line with the sequence of actions (send and receive sequence). Any change in sequence may break backwards compatibility and the flow might hang indefinitely or cause an exception.

Sample code to practice

Refer here for sample code on implicit and explicit upgrades in Corda. It covers various scenarios that you may face which upgrading CorDapps.

ashutoshmeher-r3/corda-samples

Demo

Check out below video on Contract Upgrade on Youtube to see it in action:

Further reading

More information on Contract Constraints can be found on the Corda Docs site. Extra detail not included in this post can be found there.

If you have come this far it means you are genuinely interested in Corda. You may consider joining us in our public slack channel if you have questions or are interested in learning more about Corda. Other ways to connect.

— Ashutosh Meher, Developer Relations at R3


Contract Upgrades and Constraints in Corda was originally published in Corda on Medium, where people are continuing the conversation by highlighting and responding to this story.