API: States

Note

Before reading this page, you should be familiar with the key concepts of States.

ContractState

In Corda, states are classes that implement ContractState. The ContractState interface is defined as follows:

/**
 * A contract state (or just "state") contains opaque data used by a contract program. It can be thought of as a disk
 * file that the program can use to persist data across transactions. States are immutable: once created they are never
 * updated, instead, any changes must generate a new successor state. States can be updated (consumed) only once: the
 * notary is responsible for ensuring there is no "double spending" by only signing a transaction if the input states
 * are all free.
 */
@CordaSerializable
interface ContractState {
    /**
     * An instance of the contract class that will verify this state.
     *
     * # Discussion
     *
     * This field is not the final design, it's just a piece of temporary scaffolding. Once the contract sandbox is
     * further along, this field will become a description of which attachments are acceptable for defining the
     * contract.
     *
     * Recall that an attachment is a zip file that can be referenced from any transaction. The contents of the
     * attachments are merged together and cannot define any overlapping files, thus for any given transaction there
     * is a miniature file system in which each file can be precisely mapped to the defining attachment.
     *
     * Attachments may contain many things (data files, legal documents, etc) but mostly they contain JVM bytecode.
     * The class files inside define not only [Contract] implementations but also the classes that define the states.
     * Within the rest of a transaction, user-providable components are referenced by name only.
     *
     * This means that a smart contract in Corda does two things:
     *
     * 1. Define the data structures that compose the ledger (the states)
     * 2. Define the rules for updating those structures
     *
     * The first is merely a utility role ... in theory contract code could manually parse byte streams by hand.
     * The second is vital to the integrity of the ledger. So this field needs to be able to express constraints like:
     *
     * - Only attachment 733c350f396a727655be1363c06635ba355036bd54a5ed6e594fd0b5d05f42f6 may be used with this state.
     * - Any attachment signed by public key 2d1ce0e330c52b8055258d776c40 may be used with this state.
     * - Attachments (1, 2, 3) may all be used with this state.
     *
     * and so on. In this way it becomes possible for the business logic governing a state to be evolved, if the
     * constraints are flexible enough.
     *
     * Because contract classes often also define utilities that generate relevant transactions, and because attachments
     * cannot know their own hashes, we will have to provide various utilities to assist with obtaining the right
     * code constraints from within the contract code itself.
     *
     * TODO: Implement the above description. See COR-226
     */
    val contract: Contract

    /**
     * A _participant_ is any party that is able to consume this state in a valid transaction.
     *
     * The list of participants is required for certain types of transactions. For example, when changing the notary
     * for this state ([TransactionType.NotaryChange]), every participant has to be involved and approve the transaction
     * so that they receive the updated state, and don't end up in a situation where they can no longer use a state
     * they possess, since someone consumed that state during the notary change process.
     *
     * The participants list should normally be derived from the contents of the state.
     */
    val participants: List<AbstractParty>
}

Where:

  • contract is the Contract class defining the constraints on transactions involving states of this type
  • participants is a List of the AbstractParty who are considered to have a stake in the state. For example, all the participants will:
    • Need to sign a notary-change transaction for this state
    • Receive any committed transactions involving this state as part of FinalityFlow

The vault

Each node has a vault, where it stores the states that are “relevant” to the node’s owner. Whenever the node sees a new transaction, it performs a relevancy check to decide whether to add each of the transaction’s output states to its vault. The default vault implementation decides whether a state is relevant as follows:

  • The vault will store any state for which it is one of the participants
  • This behavior is overridden for states that implement LinearState or OwnableState (see below)

If a state is not considered relevant, the node will still store the transaction in its local storage, but it will not track the transaction’s states in its vault.

ContractState sub-interfaces

There are two common optional sub-interfaces of ContractState:

  • LinearState, which helps represent objects that have a constant identity over time
  • OwnableState, which helps represent fungible assets

For example, a cash is an OwnableState - you don’t have a specific piece of cash you are tracking over time, but rather a total amount of cash that you can combine and divide at will. A contract, on the other hand, cannot be merged with other contracts of the same type - it has a unique separate identity over time.

We can picture the hierarchy as follows:

_images/state-hierarchy.png

LinearState

LinearState models facts that have a constant identity over time. Remember that in Corda, states are immutable and can’t be updated directly. Instead, we represent an evolving fact as a sequence of states where every state is a LinearState that shares the same linearId. Each sequence of linear states represents the lifecycle of a given fact up to the current point in time. It represents the historic audit trail of how the fact evolved over time to its current “state”.

The LinearState interface is defined as follows:

/**
 * A state that evolves by superseding itself, all of which share the common "linearId".
 *
 * This simplifies the job of tracking the current version of certain types of state in e.g. a vault.
 */
interface LinearState : ContractState {
    /**
     * Unique id shared by all LinearState states throughout history within the vaults of all parties.
     * Verify methods should check that one input and one output share the id in a transaction,
     * except at issuance/termination.
     */
    val linearId: UniqueIdentifier

    /**
     * True if this should be tracked by our vault(s).
     */
    fun isRelevant(ourKeys: Set<PublicKey>): Boolean

    /**
     * Standard clause to verify the LinearState safety properties.
     */
    @CordaSerializable
    class ClauseVerifier<in S : LinearState, C : CommandData> : Clause<S, C, Unit>() {
        override fun verify(tx: LedgerTransaction,
                            inputs: List<S>,
                            outputs: List<S>,
                            commands: List<AuthenticatedObject<C>>,
                            groupingKey: Unit?): Set<C> {
            val inputIds = inputs.map { it.linearId }.distinct()
            val outputIds = outputs.map { it.linearId }.distinct()
            requireThat {
                "LinearStates are not merged" using (inputIds.count() == inputs.count())
                "LinearStates are not split" using (outputIds.count() == outputs.count())
            }
            return emptySet()
        }
    }
}

Where:

  • linearId is a UniqueIdentifier that:
    • Allows the successive versions of the fact to be linked over time
    • Provides an externalId for referencing the state in external systems
  • isRelevant(ourKeys: Set<PublicKey>) overrides the default vault implementation’s relevancy check. You would generally override it to check whether ourKeys is relevant to the state at hand in some way.

The vault tracks the head (i.e. the most recent version) of each LinearState chain (i.e. each sequence of states all sharing a linearId). To create a transaction updating a LinearState, we retrieve the state from the vault using its linearId.

UniqueIdentifier

UniqueIdentifier is a combination of a (Java) UUID representing a globally unique 128 bit random number, and an arbitrary string which can be paired with it. For instance the string may represent an existing “weak” (not guaranteed unique) identifier for convenience purposes.

OwnableState

OwnableState models fungible assets. Fungible assets are assets for which it’s the total amount held that is important, rather than the actual units held. US dollars are an example of a fungible asset - we do not track the individual dollar bills held, but rather the total amount of dollars.

The OwnableState interface is defined as follows:

/**
 * A contract state that can have a single owner.
 */
interface OwnableState : ContractState {
    /** There must be a MoveCommand signed by this key to claim the amount */
    val owner: AbstractParty

    /** Copies the underlying data structure, replacing the owner field with this new value and leaving the rest alone */
    fun withNewOwner(newOwner: AbstractParty): Pair<CommandData, OwnableState>
}

Where:

  • owner is the PublicKey of the asset’s owner
    • OwnableState also override the default behavior of the vault’s relevancy check. The default vault implementation will track any OwnableState of which it is the owner.
  • withNewOwner(newOwner: PublicKey) creates an identical copy of the state, only with a new owner

Other interfaces

ContractState has several more sub-interfaces that can optionally be implemented:

  • QueryableState, which allows the state to be queried in the node’s database using SQL (see API: Persistence)
  • SchedulableState, which allows us to schedule future actions for the state (e.g. a coupon on a bond) (see Event scheduling)

User-defined fields

Beyond implementing LinearState or OwnableState, the definition of the state is up to the CorDapp developer. You can define any additional class fields and methods you see fit.

For example, here is a relatively complex state definition, for a state representing cash:

    /** A state representing a cash claim against some party. */
    data class State(
            override val amount: Amount<Issued<Currency>>,

            /** There must be a MoveCommand signed by this key to claim the amount. */
            override val owner: AbstractParty
    ) : FungibleAsset<Currency>, QueryableState {
        constructor(deposit: PartyAndReference, amount: Amount<Currency>, owner: AbstractParty)
                : this(Amount(amount.quantity, Issued(deposit, amount.token)), owner)

        override val exitKeys = setOf(owner.owningKey, amount.token.issuer.party.owningKey)
        override val contract = CASH_PROGRAM_ID
        override val participants = listOf(owner)

        override fun move(newAmount: Amount<Issued<Currency>>, newOwner: AbstractParty): FungibleAsset<Currency>
                = copy(amount = amount.copy(newAmount.quantity), owner = newOwner)

        override fun toString() = "${Emoji.bagOfCash}Cash($amount at ${amount.token.issuer} owned by $owner)"

        override fun withNewOwner(newOwner: AbstractParty) = Pair(Commands.Move(), copy(owner = newOwner))

        /** Object Relational Mapping support. */
        override fun generateMappedObject(schema: MappedSchema): PersistentState {
            return when (schema) {
                is CashSchemaV1 -> CashSchemaV1.PersistentCashState(
                        owner = this.owner.owningKey.toBase58String(),
                        pennies = this.amount.quantity,
                        currency = this.amount.token.product.currencyCode,
                        issuerParty = this.amount.token.issuer.party.owningKey.toBase58String(),
                        issuerRef = this.amount.token.issuer.reference.bytes
                )
                /** Additional schema mappings would be added here (eg. CashSchemaV2, CashSchemaV3, ...) */
                else -> throw IllegalArgumentException("Unrecognised schema $schema")
            }
        }

        /** Object Relational Mapping support. */
        override fun supportedSchemas(): Iterable<MappedSchema> = listOf(CashSchemaV1)
        /** Additional used schemas would be added here (eg. CashSchemaV2, CashSchemaV3, ...) */
    }

TransactionState

When a ContractState is added to a TransactionBuilder, it is wrapped in a TransactionState:

/**
 * A wrapper for [ContractState] containing additional platform-level state information.
 * This is the definitive state that is stored on the ledger and used in transaction outputs.
 */
@CordaSerializable
data class TransactionState<out T : ContractState> @JvmOverloads constructor(
        /** The custom contract state */
        val data: T,
        /** Identity of the notary that ensures the state is not used as an input to a transaction more than once */
        val notary: Party,
        /**
         * All contract states may be _encumbered_ by up to one other state.
         *
         * The encumbrance state, if present, forces additional controls over the encumbered state, since the platform checks
         * that the encumbrance state is present as an input in the same transaction that consumes the encumbered state, and
         * the contract code and rules of the encumbrance state will also be verified during the execution of the transaction.
         * For example, a cash contract state could be encumbered with a time-lock contract state; the cash state is then only
         * processable in a transaction that verifies that the time specified in the encumbrance time-lock has passed.
         *
         * The encumbered state refers to another by index, and the referred encumbrance state
         * is an output state in a particular position on the same transaction that created the encumbered state. An alternative
         * implementation would be encumbering by reference to a [StateRef], which would allow the specification of encumbrance
         * by a state created in a prior transaction.
         *
         * Note that an encumbered state that is being consumed must have its encumbrance consumed in the same transaction,
         * otherwise the transaction is not valid.
         */
        val encumbrance: Int? = null)

Where:

  • data is the state to be stored on-ledger
  • notary is the notary service for this state
  • encumbrance points to another state that must also appear as an input to any transaction consuming this state