Corda Reissue — Breaking up those long backchains

Mabel Oza
InsatiableMinds
Published in
13 min readNov 7, 2021

--

A little bit of background on how Corda works

What do Nodes know? Their own business (facts)

Unlike most blockchain networks, in Corda every node doesn’t have a copy of the full ledger, they only know about shared facts (states) that pertain to them. In a multi-party network, each peer sees only a subset of the ledger and no peer would know the entire ledger.

In the picture below Bob has only dealt with 1, 5, 6, and 7, so those are the only facts that are exposed to Bob and in Bob’s database of facts.

What are these shared facts? States

A state is an immutable object representing a fact known by one or more Corda nodes at a specific point in time.

States can represent facts like IOU’s stocks, bonds, art, etc. at a given point in time.

A state representing that Alice owes Bob 10 £10

What governs the State? Smart Contracts

All states have a reference to a contract that governs the evolution of the state over time. Smart contracts tell us the rules of how a state can change. We won’t be going into much detail on this, if you are curious check out the video from Corda below.

If States are immutable, then how do we track changes? State Sequences

State sequences give us the full timeline of a state. In the IOU scenario, after Alice pays Bob £5, we create a new state and mark the previous state where Alice owed Bob £10 as historic.

Where are all the current and historic states tracked? The Vault

The vault is a database where the node records the current (unconsumed) and historic (consumed) states it was involved with.

How do you reference a state in a transaction without consuming it? State References

State Reference are states so they are immutable, tracked by the ledger, and readily accessible. The big difference is that unlike regular states they are not consumed, so other transactions can also use them as a reference. This is very important in Corda because when you create a transaction, you don’t put in the actual states instead you put in the references to the previous states.

An input state reference has a transaction id and index, the transaction id refers to the hash of the transaction which created the state and the index refers to the position of the state in the list of outputs.

Input State Reference = (Transaction ID, Index)

What do you get from all these reference state links? Backchains

Input state references link transactions together forming the transaction backchain, it connects the input back to the original issuance transaction.

Think of the backchain like the history of a document on a sharepoint site. Every time the document (state) is updated with a new modifier (new owner), it’s recorded in the version history (backchain).

Over time, these backchains get long and in turn impact performance and privacy. The performance is impacted because there are more transactions to go through during verification. Privacy is impacted because all the backchain transactions are shared with the new owner. In the SharePoint document example Don Ledoux now knows that Darren Hoyland worked on the document at 4:24 AM and that Darren works way too early or does all-nighters, it’s none of Don’s business and this is why privacy is crucial.

How can backchains grow over time?

A common scenario is if there was tokenized cash is issued, there can infinite number of transfers back and forth before it’s ever redeemed (goes through an exit state).

How do we know if the old state followed the proper contract logic? Backchain Verification

Backchain verification is when we recursively “walk the chain” to verify a valid sequence of transactions and contract logic.

Check out Clyde D’Cruz’s Medium post on Corda Backchain Verification.

I recommend you check out Richard Brown’s blog on better understanding the fundamental concepts behind Corda.

How did we use to deal with long backchains? Exit the state and then reissue the state in 2 separate steps

Prior to Corda release 4.7 the approach to solving this was to have a trusted issuing party re-issue the state, this means exiting the state and issuing it again, thus pruning the backchain. In a token scenario where someone is issued tokens and has exchanged them back and forth with other parties, the trusted issuing party in the background would redeem the tokens to a fiat currency and issue the tokens again. The problem with the approach mentioned above is that it’s not atomic, they are two separate actions and there is no guarantee that the tokens will be issued back after being redeemed.

Today what do we do about long backchains? Re-issue atomically, all in one go

Corda 4.7 brought in a state re-issuance functionality that is a re-issuance flow that reissues states with guaranteed state replacements. It uses encumbrances to make the deletion of the old state that has the backchain and the issuance of the new state happens atomically (all in one go).

What is an encumbrance?

According to Investopedia Encumbrance is a claim against a property by a party that is not the owner. In accounting, it refers to restricted funds inside an account that are reserved for a specific liability.

Applying this definition to the Corda reissuing world, when we encumber a state we are making a copy of the state that is marked as do not use before the state is deleted. Once the state is deleted then the requester is allowed to unlock the encumbered state and now we consider it the re-issue state.

The new state re-issuance functionality provides a state re-issuance algorithm that eliminates the risk of being left without a usable state if the issuing party fails to re-issue the state for some reason (for example, if they are not online at the required time). This is achieved through the reissuing of an encumbered state before the original state is deleted. This allows the requesting party to unlock the reissued state immediately after the original state is deleted.

State encumbrance refers to a state pointing to another state that must also appear as an input to any transaction consuming this state. A state may be encumbered by up to one other state, which is called an “encumbrance” state. The encumbrance state, if present, forces additional controls over the encumbered state, since the encumbrance state contract will also be verified during the execution of the transaction.

How does Reissuing work at a high level?

Reissuing is just like editing rules in a shared filesystem like SharePoint. With re-issuing, we take a copy of the state with the long backchain and we lock it, then we delete the original copy, and unlock the new copy. From a 30,000-foot view reissuing involves 3 steps:

  1. The issuing party issues an encumbered state based on the original state, think of it as a copy no one is allowed to use
  2. The original state is exited and no longer available to use
  3. Requestor unlocks the encumbered state (re-issued state) and can now use it

How does Reissuing work at a detailed level (Give me the meat)?

Re-issue is just a CorDapp that has contracts, states, and workflows.

Check out the implementation of Re-issue at the Github repo below:

To use it you need to install the Reissuance Cordapp like any other CorDapp, create the necessary jars, and then add them to your cordapp grade file. Below are the dependencies you would add to your CorDapp’s build.gradle file

cordapp "$reissuance_release_group:reissuance-cordapp-contracts:$reissuance_release_version"
cordapp "$reissuance_release_group:reissuance-cordapp-workflows:$reissuance_release_version"

Step 1: Requesting Party Creates Reissuance Request

The requesting party requests a state re-issuance using the RequestReissuance workflow to kick things off. RequestReissance has a similar method called RequestReissuanceAndShareRequiredTransactions that has an additional step after the request, it shares transactions proving that the states in the request are valid with the issuing party. The parameters passed into this method are:

  • Issuer (Abstract Party)
  • Requester (Abstract Party) — Required if accounts are used, in the example below accounts aren’t used
  • State References To Reissue (List of State References)
  • Asset Issuance Command (Command Data) — New asset state
  • Asset Issuance Signers (List of Abstract Party) — Optional list of required issuance signers, not the issuing party
@StartableByRPC
class RequestCandyCouponReissuanceAndShareRequiredTransactions(
private val issuer: AbstractParty,
private val stateRefStringsToReissue: List<String>
): FlowLogic<SecureHash>() {

@Suspendable
override fun call(): SecureHash {
val candyCouponTokenType = TokenType("CandyCoupon", 0)
val issuedTokenType = IssuedTokenType(issuer as Party, candyCouponTokenType)

return subFlow(RequestReissuanceAndShareRequiredTransactions<FungibleToken>(
issuer,
stateRefStringsToReissue.map { parseStateReference(it) },
IssueTokenCommand(issuedTokenType, stateRefStringsToReissue.indices.toList()
)
))
}
}

Step 2: Issuing party either accepts or rejects the request

There are three decisions (commands) in the Reissuance Request Contract: Create, Accept, and Reject. Create is what we used in step 1, now the issuer needs to make a decision, do they accept or reject this request.

Corda Contract View for ReissuanceRequestContract

Below are the terms for accepting or rejecting the request.

Acceptance Terms

// command constraints
"Exactly one input of type ReissuanceRequest is expected"
"No outputs of type ReissuanceRequest are allowed"

"Issuer is required signer"

"No inputs of type ReissuanceLock are allowed"
"Exactly one output of type ReissuanceLock is expected"

// universal constraints
"Issuer and requester must be different parties"

Rejection Terms

"Exactly one input of type ReissuanceRequest is expected" 
"No inputs of other than ReissuanceRequest are allowed"
"No outputs are allowed"

"Issuer is required signer"

// universal constraints
"Issuer and requester must be different parties"

If the request is rejected then it must call the RejectReissuanceRequest flow, which deletes the request using a state reference parameter.

@StartableByRPC
class RejectCandyCouponsReissuanceRequest(
private val ReissuanceRequestRefString: String
): FlowLogic<SecureHash>() {

@Suspendable
override fun call(): SecureHash {
val ReissuanceRequestRef = parseStateReference(ReissuanceRequestRefString)
val ReissuanceRequestStateAndRef = serviceHub.vaultService.queryBy<ReissuanceRequest>(
criteria= QueryCriteria.VaultQueryCriteria(stateRefs = listOf(ReissuanceRequestRef))
).states[0]
return subFlow(RejectReissuanceRequest<FungibleToken>(
ReissuanceRequestStateAndRef
))
}
}

If the request is accepted then it must call the ReissueStates flow, using the parameters re-issuance state references and a list of parties that need to sign for an exit transaction.

@StartableByRPC
class ReissueCandyCoupons(
private val reissuanceRequestRefString: String
): FlowLogic<SecureHash>() {

@Suspendable
override fun call(): SecureHash {
val rejectReissuanceRequestRef = parseStateReference(reissuanceRequestRefString)
val rejectReissuanceRequestStateAndRef = serviceHub.vaultService.queryBy<ReissuanceRequest>(
criteria= QueryCriteria.VaultQueryCriteria(stateRefs = listOf(rejectReissuanceRequestRef))
).states[0]
return subFlow(ReissueStates<FungibleToken>(
rejectReissuanceRequestStateAndRef,
extraAssetExitCommandSigners = listOf(rejectReissuanceRequestStateAndRef.state.data.issuer)
))
}
}

Step 3: Reissuing of state begins

There are three decisions (commands) in the Reissuance Lock Contract: Create, Deactivate, and Delete. This is where we deal with state encumbrance, we create the copied state that’s activated and delete the original state.

Corda Contract View for ReissuanceLockContract

Create

The basic idea with the create command is to check that the original state and the reissued states are the same and define what can and can’t be encumbered.

Below are the terms for a create command (verifyCreateCommand):

// verify number of inputs and outputs of a given type
"Exactly one input of type ReissuanceRequest is expected"
"No outputs of type ReissuanceRequest are allowed"

"No inputs of type ReissuanceLock are allowed"
"Exactly one output of type ReissuanceLock is expected"

"No inputs other than ReissuanceRequest and ReissuanceLock are expected"
"At least one output other than ReissuanceRequest and ReissuanceLock is expected"

// verify requester & issuer
"Requester is the same in both ReissuanceRequest and ReissuanceLock"
"Issuer is the same in both ReissuanceRequest and ReissuanceLock"

// participants for all re-issued states must be the same
"Participants in state to be re-issued ${reissuedState.ref} must be the same as participants in the first state to be re-issued ${reissuedState.ref}"

// all re-issued states must be of the same type
"State to be re-issued ${reissuedState.ref} must be of the same type as the first state to be re-issued ${reissuedState.ref}"

// verify signers
"Issuer is required signer"

//// state constraints

// verify status
"Re-issuance lock status is ACTIVE"

// verify state data
"StatesAndRef objects in ReissuanceLock must be the same as re-issued states"


// verify encumbrance
"Original states can't be encumbered"
"Output other than ReissuanceRequest and ReissuanceLock must be encumbered"

Deactivate

The Deactivate command is the meat of the encumbrance activity. There are two major functions required for this operation getAttachedLedgerTransaction and generateWireTransactionMerkleTree.

Get Attached Ledger Transactions

Basically this function takes a ledger transaction and opens up the attachment that’s not a contract, called nonContractAttachments. If the nonContractAttachments is a SignedTransaction then we deserialize the attachment.

private fun getAttachedLedgerTransaction(tx: LedgerTransaction): List<SignedTransaction> {
// Constraints on the included attachments.
val nonContractAttachments = tx.attachments.filter { it !is ContractAttachment }
"The transaction should have at least one non-contract attachment" using (nonContractAttachments.isNotEmpty())

var attachedSignedTransactions = mutableListOf<SignedTransaction>()
nonContractAttachments.forEach { attachment ->
val attachmentJar = attachment.openAsJAR()
var nextEntry = attachmentJar.nextEntry
while (nextEntry != null && !nextEntry.name.startsWith("SignedTransaction")) {
// Calling `attachmentJar.nextEntry` causes us to scroll through the JAR.
nextEntry = attachmentJar.nextEntry
}

if(nextEntry != null) {
val transactionBytes = attachmentJar.readBytes()
attachedSignedTransactions.add(transactionBytes.deserialize<SignedTransaction>())
}

}

return attachedSignedTransactions
}

Generate Wire Transaction Merkle Tree

This function like the names says generates a wire transaction merkle tree.

Before we get into this code a little info on what merkle trees are.

What is a Merkle Tree?

Merkle trees are a type of binary tree data structure that contains SHA256 hashes derived from its children, the parent’s hash is always derived from its children and the pattern continue up until we get to the root of the tree, the very top of the tree.

They are foundational in blockchain systems because they make it efficient to verify past transactions in a block.

See the image below for an illustration of one.

Image taken from: https://hackernoon.com/merkle-trees-181cb4bc30b4

This post won’t get into the details on what Merkle trees are how are they used in blockchain. Refer to the video below by Gaurav Sen for further information on how merkle trees work.

In Corda the merkle tree is constructed from a transaction by splitting the transaction into leaves, where each leaf contains either an input, an output, a command, or an attachment. The final nested tree structure also contains the other fields of the transaction, such as the time-window, the notary and the required signers. Each component type (i.e. inputs, outputs, attachments) has its own nested Merkle tree, the roots of these nested trees all roll up to the root of the entire tree that’s represented as the transaction id TX_ID. Below is a wire transaction merkle tree.

Source: https://docs.r3.com/en/platform/corda/4.6/open-source/key-concepts-tearoffs.html#transaction-merkle-trees

Below is the code for generating a merkle tree for a transaction.

private fun generateWireTransactionMerkleTree(
wireTransaction: WireTransaction
): MerkleTree {
//Creating a map (list) of component hashes
val availableComponentNonces: Map<Int, List<SecureHash>> by lazy {
wireTransaction.componentGroups.map { Pair(it.groupIndex, it.components.mapIndexed {
internalIndex, internalIt ->
componentHash(internalIt, wireTransaction.privacySalt, it.groupIndex, internalIndex) })
}.toMap()
}
//Creating a map (list) of component hashes
val availableComponentHashes = wireTransaction.componentGroups.map {
Pair(it.groupIndex, it.components.mapIndexed {
internalIndex, internalIt ->
componentHash(availableComponentNonces[it.groupIndex]!![internalIndex], internalIt) })
}.toMap()

val groupsMerkleRoots: Map<Int, SecureHash> by lazy {
availableComponentHashes.map {
Pair(it.key, MerkleTree.getMerkleTree(it.value).hash)
}.toMap()
}
val groupHashes: List<SecureHash> by lazy {
val listOfLeaves = mutableListOf<SecureHash>()
// Even if empty and not used, we should at least send oneHashes for each known
// or received but unknown (thus, bigger than known ordinal) component groups.
for (i in 0..wireTransaction.componentGroups.map { it.groupIndex }.max()!!) {
val root = groupsMerkleRoots[i] ?: SecureHash.allOnesHash
listOfLeaves.add(root)
}
listOfLeaves
}
return MerkleTree.getMerkleTree(groupHashes)

}

We use that function to verify that the attached wire transaction is valid.

"Merkle tree of attached transaction ${attachedSignedTransaction.id} is valid" using (
generateWireTransactionMerkleTree(attachedWireTransaction) == attachedWireTransaction.merkleTree)

Below are the terms for the Deactivate command:

// verify number of inputs and outputs of a given type
"Exactly one input of type ReissuanceLock is expected"
"Exactly one output of type ReissuanceLock is expected"

"At least one input other than lock is expected"
"The same number of inputs and outputs other than lock is expected"

// verify status
"Input re-issuance lock status is ACTIVE"
"Output re-issuance lock status is INACTIVE"

"Re-issuance lock properties hasn't change except for status"

"All locked states are inputs of attached transactions"
"Attached transactions don't have any outputs" "Attached CoreTransaction ${attachedSignedTransaction.id} can be cast to WireTransaction" "Id of attached SignedTransaction ${attachedSignedTransaction.id} is equal to id of WireTransaction" "Id of attached WireTransaction ${attachedSignedTransaction.id} is equal to merkle tree hash" "Merkle tree of attached transaction ${attachedSignedTransaction.id} is valid" "Notary is provided for attached transaction ${attachedSignedTransaction.id}" "Notary of the attached transaction ${attachedSignedTransaction.id} is the same as the notary of the transaction being verified" "Requester is a signer of attached transaction ${attachedSignedTransaction.id}"

"Attached transaction ${attachedSignedTransaction.id} is notarised"
"Attached transaction ${attachedSignedTransaction.id} is signed with ${requiredSigner.owningKey}"

// verify encumbrance
"Inputs other than ReissuanceLock must be encumbered"
"Outputs other than ReissuanceLock can't be encumbered" "Input data other than ReissuanceLock are the same as output data other than ReissuanceLock"

// verify signers
"Requester is required signer"

Delete

Delete is fairly straightforward after the encumbrance dance above we need to delete the original state.

Below are the terms for deleteing:

"Exactly one input of type ReissuanceLock is expected" 
"Number of other inputs is equal to originalStates length"
"No outputs are allowed"

// verify status
"Input re-issuance lock status is ACTIVE"
// verify encumbrance
"Input of type ReissuanceLock must be encumbered"
"Input ${it.ref} of type other than ReissuanceLock must be encumbered"

// verify signers
"Requester is required signer"
"Issuer is required signer"

Corda Design Language of the Reissue Cordapp

Reissuance state machine CDL (Corda Design Language) chart

Additional Resources

If you would like further learn about Reissuance check out the resources below:

Check out the official Corda documentation on the re-issuing States below.

See how reissuance works by checking out the sample reissuance project on Github.

--

--

Mabel Oza
InsatiableMinds

Making the financial world more secure, accessible, and transparent.