Skip to content

Chained and Unique States

Matthew Layton edited this page Jun 22, 2021 · 1 revision

Introduction

In Corda, LinearState represents an evolvable contract state that may be identified by its linear ID, sort of like a primary key in a database. Unlike a traditional database, each linear state must be consumed and a new state created (as is the nature of DLT), which results in a historic record of each state transition. Whilst linear states are intended to be unique, there's no guarantee of uniqueness across the ledger, or even on an individual node.

ChainState

The ChainState interface is similar in nature to LinearState, however it provides a stricter state evolution. Each chain state instance implements a previousStateRef property which should point to the state reference of the previous state (or null if it's the first state in the chain), akin to blocks in a blockchain.

Example

The following example demonstrates a ChainState implementation. Note that in order to amend the state, you will have to pass in a reference to the previous state. This can be checked in the contract to ensure that a newly created state refers to the consumed state; or null, if it's the first state in the chain.

@BelongsToContract(MagicNumberContract::class)
class MagicNumber(
    val issuer: AbstractParty, 
    val magicNumber: Int = 0, 
    override val previousStateRef: StateRef? = null
) : ChainState {

    override val participants: List<AbstractParty> 
        get() = listOf(issuer)

    fun amend(previousState: StateAndRef<MagicNumber>, magicNumber: Int): MagicNumber {
        return ExampleChainState(issuer, magicNumber, previousState.ref)
    }
}

class MagicNumberContract : Contract {

    companion object : ContractID

    override fun verify(tx: LedgerTransaction) = tx.allowCommands(
        Issue::class.java,
        Amend::class.java
    )

    interface MagicNumberContractCommand : VerifiedCommandData

    object Issue : MagicNumberContractCommand {
        internal const val CONTRACT_RULE_PREVIOUS_STATE_REF = 
            "On example chain state issuing, the previous state ref must be null."

        override val verify(transaction: LedgerTransaction, signers: Set<PublicKey>) = requireThat {
            val output = transaction.outputsOfType<MagicNumber>().single()

            CONTRACT_RULE_PREVIOUS_STATE_REF using (output.previousStateRef == null)
        }
    }

    object Amend : MagicNumberContractCommand {
        internal const val CONTRACT_RULE_PREVIOUS_STATE_REF = 
            "On example chain state issuing, the previous state ref must point to the previous state."

        override val verify(transaction: LedgerTransaction, signers: Set<PublicKey>) = requireThat {
            val input = transaction.inRefsOfType<MagicNumber>().single()
            val output = transaction.outputsOfType<MagicNumber>().single()

            CONTRACT_RULE_PREVIOUS_STATE_REF using (output.previousStateRef == input.ref)
        }
    }

Hashable

The Hashable interface defines a hash property. Admittedly it's use isn't immediately apparent but when implemented correctly, a state hash could either represent a ledger-wide unique identifier, or when combined with the ChainState interface could represent a per-state unique identifier, where the previous state reference deterministically changes the hash for every newly created state.

Example

The following example demonstrates a Hashable implementation. Note that as this state also implements ChainState this produces a hash that changes deterministically every time the state evolves, thus providing a unique identifier that can be used to query a specific state.

class MagicNumber(
    val issuer: AbstractParty, 
    val magicNumber: Int = 0, 
    override val previousStateRef: StateRef? = null
) : ChainState, Hashable {

    override val hash: SecureHash
        get() = SecureHash.sha256("$issuer$magicNumber$previousStateRef")

    override val participants: List<AbstractParty> 
        get() = listOf(issuer)

    fun amend(previousState: StateAndRef<MagicNumber>, magicNumber: Int): MagicNumber {
        return ExampleChainState(issuer, magicNumber, previousState.ref)
    }
}

Remarks

Note that if the issuer were to issue the same magic number twice, this would result in a duplicate hash. This should be checked in the flow before a new state is created. You could also implement QueryableState and include a unique constraint on the hash column, however this alone isn't an entirely fail-safe solution as states are recorded to the vault before queryable tables are updated, leading to inconsistent data in the vault.