Reissuing states

The state reissuance mechanism described on this page enables you to break transaction backchains by reissuing a state with a guaranteed state replacement.

When a new transaction is created, input states are included in the proposed transaction by reference. These input state references link transactions together over time, forming a transaction backchain.

Long transaction backchains are undesirable for two reasons:

  • Performance: Resolution along the chain slows down significantly.
  • Privacy: All backchain transactions are shared with the new owner.

Prior to Corda 4.7, the only possible approach to resolve the problem with long transaction backchains was for a trusted issuing party to simply reissue the state. This meant that the state could simply be exited from the ledger and then reissued. As there would be no links remaining between the exit transaction and the reissuance transactions, the transaction backchain would be pruned.

However, this approach does not provide a guarantee that the issuing party would reissue the exited state as the actions described above are not atomic.

The new state reissuance functionality provides a state reissuance algorithm that eliminates the risk of being left without a usable state if the issuing party fails to reissue 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.

In addition, a single trusted issuing party is allowed to reissue multiple fungible states at once, provided that all these states are of the same type. For example, you can issue at once a number of tokens with different quantities but with the same TokenType and issued by the same party.

A trusted issuer reissues an encumbered state before the original state is deleted, thus enabling the requester to unlock that reissued state immediately after the original state is deleted.

  • Corda Open Source or Corda Enterprise, version 4.7 or higher
  • Tokens SDK
  1. The issuing party issues an encumbered state (State B) based on the original state (State A).
  2. The original state (State A) is deleted.
  3. Requester unlocks the reissued state (State B) immediately after the original state (State A) is deleted.

The requesting party requests state reissuance using the RequestReIssuance flow with the following arguments:

  • issuer: AbstractParty.
  • stateRefsToReIssue: List<StateRef>.
  • assetIssuanceCommand: CommandData: This command issues the new asset state.
  • extraAssetIssuanceSigners: List<AbstractParty>: An optional list of required state issuance signers, which are different than the issuing party. The default value is an empty list.
  • requester: AbstractParty?: This argument is only required when the requesting party is an account. The default value is null.

The requesting party can use the SendTransactionFlow to send a transaction to the issuing party that wishes to verify it.

When the issuing party gets a state reissuance request, it can reject it or accept it.

To reject a request, the issuing party must call the RejectReIssuanceRequest flow, which simply deletes the request. The flow requires only one parameter to be provided:

  • reIssuanceRequestStateAndRef: StateAndRef<ReIssuanceRequest>.

To accept a request, the issuing party must call the ReIssueStates flow with the following parameters:

  • reIssuanceRequestStateAndRef: StateAndRef<ReIssuanceRequest>.
  • extraAssetExitCommandSigners: List<AbstractParty>: A list of parties, which need to sign the asset state exit transaction. By default this list includes the issuing party - the default value is listOf(reissuanceRequestStateAndRef.state.data.issuer). This is used in asset state exit transaction verification (see Step 5: Consume the reissued states further below).

Before accepting a request, the issuing party needs to make sure the request is valid. They must verify if the states requested for reissuance are valid and also ensure that these states have not already been reissued (see The issuing party is corrupted further below).

When the issuing party accepts a state reissuance request:

  • They generate encumbered copies of the states provided in the request.
  • They generate a lock state, which enforces successful state reissuance and ensures that the entire state reissuance process is successful.
  • The status of the newly created ReIssuanceLock object is set to ACTIVE.
  • The value of the extraAssetExitCommandSigners: List<AbstractParty> property is used for the ReIssuanceLock state.

The requesting party can exit an original state by using the general redeem/delete command for the respective asset. For example, for tokens the respective command would be RedeemTokens.

Constraints:

  • The command to run the transaction, which exits the original state(s) and serves as proof of exit, cannot produce any outputs.
  • There cannot be anything in the contract preventing lock being a transaction input.
  • If the extraAssetExitCommandSigners property of ReIssuanceLock is not empty, it must be signed by the listed parties as per the value of extraAssetExitCommandSigners: List<AbstractParty>.

Depending on whether the original states have been consumed, there are two possible actions.

To unlock a reissued state, call the UnlockReIssuedStates flow. The contract code for reissuance lock objects verifies if the status of the ReIssuanceLock object is updated to INACTIVE, and that the reissued states can be unencumbered. To perform the latter check, an asset exit transaction is added as an attachment - it is serialized and transformed into a Zip stream with an entry called SignedTransaction_[transactionId].

The UnlockReIssuedStates flow has the following arguments:

  • reIssuedStateAndRefs: List<StateAndRef<T>>.
  • reIssuanceLock: StateAndRef<ReIssuanceLock<T>>.
  • assetExitTransactionIds: List<SecureHash>.
  • assetUnencumberCommand: CommandData: This command reverses the encumbrance of asset states.
  • extraAssetUnencumberCommandSigners: List<AbstractParty>: This command performs a required update of signers that are different from the requesting party. The default value is an empty list.

If the original states have already been consumed, it is impossible to unlock reissued states. Reissued states and corresponding ReIssuanceLock objects are redundant and should be deleted from the ledger.

To do that, DeleteReIssuedStatesAndLock flow should be called with the following arguments:

  • reIssuanceLockStateAndRef: StateAndRef<ReIssuanceLock<T>>.
  • reIssuedStateAndRefs: List<StateAndRef<T>>.
  • assetExitCommand: CommandData - command which should be used to exit encumbered asset states.
  • assetExitSigners: List<AbstractParty> - assetExitCommand signers, by default a list of issuer and requester.

As business logic differs per asset, customised responders should be implemented for the following flows: ReissueStates, UnlockReissuedStates, and DeleteReissuedStatesAndLock.

You can derive custom responder implementations from the abstract responder classes ReissueStatesResponder, UnlockReissuedStatesResponder, and DeleteReissuedStatesAndLock, which handle signing flags and contain basic checks.

To verify if the transaction backchain has really been pruned, use the new GetTransactionBackChain flow.

It has a single argument: transactionId: SecureHash.

This flow returns a set of backchain transactions. Based on the fact that state reference is created as concatenated transaction id and index, transaction inputs can be used recursively to generate a directed graph of backchain transactions.

State machine CDL chart:

State reissuance - state machine CDL chart
State reissuance - state machine CDL chart

State evolution CDL chart:

State reissuance - state evolution CDL chart
State reissuance - state evolution CDL chart

You cannot issue an encumbered state based on another encumbered state serving as your original state.

In some cases the issuing party is not the only issuance command signer and signatures must be collected from other parties.

If only some participants are signers, the implementation of responders is achieved through sending a flag. The alternative solution to use an additional flow that collects signatures is not possible because there is no way to create a state that stores workflow data.

In the contract code, the CoreTransaction, which is part of the SignedTransaction (which is an asset exit proof), is casted to a WireTransaction.

Transactions of type FilteredTransaction cannot be used as asset exit proof.

The requesting party cannot be trusted to provide valid states for reissuance. Therefore, the request must only contain state references - the issuing party then reissues states available for them. If the issuing party is not a participant, the requesting party must share with the issuing party the transactions proving that those states are valid.

To prove that the original state or states have been exited from the ledger, the requesting party must attach one or more notarised SignedTransaction.

  • The inputs of all notarised SignedTransactions must contain references of all the original states.
  • A notarised SignedTransaction must not contain any outputs.

As state references are compared in the contract code, it is impossible to cheat by implementing an equals function that always returns true. So any such attempts are detected.

If the issuing party is not a participant, they do not get updated if the original state is consumed. It is therefore possible for the issuing party to reissue a state, which has already been consumed. However, in that case the requesting party will not be able to prove that such a state has been exited, and therefore be unable to unlock (consume) it.

The issuing party must be a trusted party and is expected to act in an appropriate way. The use cases described below provide recommendations on state implementations that could prevent other cheating possibilities.

If the issuing party is not a participant and issuer information is not included in state to be reissued, any party can reissue a state. Therefore, R3 recommends to include issuer information inside the state.

The issuing party, contrary to a requesting party, is trusted to start the appropriate flows. If the issuing party is corrupted, they can implement their own version of a reissuance flow that does not check if the requested states have already been reissued. If such verification is disabled, the requesting party can create many reissuance requests, pointing to the same state, and use the same exit proof to unlock all of them.

State reissuance CorDapp: https://github.com/corda/reissue-cordapp.

State reissuance sample CorDapp: https://github.com/corda/reissue-sample-cordapp.

Was this page helpful?

Thanks for your feedback!

Chat with us

Chat with us on our #docs channel on slack. You can also join a lot of other slack channels there and have access to 1-on-1 communication with members of the R3 team and the online community.

Propose documentation improvements directly

Help us to improve the docs by contributing directly. It's simple - just fork this repository and raise a PR of your own - R3's Technical Writers will review it and apply the relevant suggestions.

We're sorry this page wasn't helpful. Let us know how we can make it better!

Chat with us

Chat with us on our #docs channel on slack. You can also join a lot of other slack channels there and have access to 1-on-1 communication with members of the R3 team and the online community.

Create an issue

Create a new GitHub issue in this repository - submit technical feedback, draw attention to a potential documentation bug, or share ideas for improvement and general feedback.

Propose documentation improvements directly

Help us to improve the docs by contributing directly. It's simple - just fork this repository and raise a PR of your own - R3's Technical Writers will review it and apply the relevant suggestions.