Corda API: States

原文地址:https://docs.corda.net/corda-api.html

在阅读本篇文章前,请阅读 Corda 核心概念 - States

ContractState

在 Corda 中,states 是那些实现了 ContractState 的类实例。ContractState 接口定义如下:

/**
 * 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 {
    /**
     * 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, 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>
}

ContractState 只有一个字段 participantsparticipants 是一个 AbstractPartyList,代表了同这个 state 有关的节点。participants 将会:

ContractState 子接口

state 的行为可以通过实现 ContractState 的子接口被进一步的定制。最常用的两个子接口包括:

LinearState 代表了一个在任何时间都是只有一个当前版本的共享的事实(shared fact)。LinearState states 通过替换自己的方式实现一个线性的改变。而 OwnableState 则代表在任何时候都可以被自由的拆分或者合并的资产。现金 cash 就是一个 OwnableState 的很好的例子 - 两个已经存在 $5 现金 state 可以合并为一个单独的 $10 的现金 state,或者被拆分成 5 个 $1 的现金 state。对于 OwnableState,它的总金额是更重要的,而不是到底有多少份。

我们可以通过下图来表述这个结构:

LinearState

LinearState 接口定义如下:

/**
 * 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
}

记住在 Corda 中,states 是不可变的,并且不能直接的更改的。然而,我们可以使用有序的 LinearState states 来表现一个事实,这些 states 共同分享一个 linearId,并且他们能够代表一个事实的整个生命周期。

当我们想要扩展一个 LinearState 链的时候,我们会:

新创建的 state 现在就成为了这个 state 链的最新的 state,代表了协议的最新的当前 state。

linearId 是一种 UniqueIdentifier 类型,由下边的元素组成:

OwnableState

OwnableState 接口定义如下:

/**
 * Return structure for [OwnableState.withNewOwner]
 */
data class CommandAndState(val command: CommandData, val ownableState: OwnableState)

/**
 * 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): CommandAndState
}

其中:

由于 OwnableState 形成了一个可替换的资产(fungible assets)的模型,这种资产可以合并和拆分,OwnableState 实例没有 linearId。一笔交易产生的 $5 现金和另一笔其他的交易产生的 $5 现金会被看作是同样的 state。

其他接口

你也可以通过实现下边的接口来定制你的 state:

用户定义字段 User-defined fields

除了实现 ContractState 或者子接口外,一个 state 还允许包含任意数量的额外字段和方法。比如下边的代码就定义了一个相对复杂的代表现金 cash 的一个 state:

    /** 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 participants = listOf(owner)

        override fun withNewOwnerAndAmount(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) = CommandAndState(Commands.Move(), copy(owner = newOwner))
        infix fun ownedBy(owner: AbstractParty) = copy(owner = owner)
        infix fun issuedBy(party: AbstractParty) = copy(amount = Amount(amount.quantity, amount.token.copy(issuer = amount.token.issuer.copy(party = party))))
        infix fun issuedBy(deposit: PartyAndReference) = copy(amount = Amount(amount.quantity, amount.token.copy(issuer = deposit)))
        infix fun withDeposit(deposit: PartyAndReference): Cash.State = copy(amount = amount.copy(token = amount.token.copy(issuer = deposit)))

        /** Object Relational Mapping support. */
        override fun generateMappedObject(schema: MappedSchema): PersistentState {
            return when (schema) {
                is CashSchemaV1 -> CashSchemaV1.PersistentCashState(
                        owner = this.owner,
                        pennies = this.amount.quantity,
                        currency = this.amount.token.product.currencyCode,
                        issuerPartyHash = this.amount.token.issuer.party.owningKey.toStringShort(),
                        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, ...) */
    }

Vault

当一个节点记录了一笔新的交易的时候,它还可以选择是否将交易的每一个 output state 存储到它的 vault 中。默认的 vault 实现让这个决定基于以下的规则:

不相关的 states 是不会存储到节点的账本中的。但是节点还是会将创建该 state 的交易信息存储到它的 transaction storage 中。

TransactionState

当一个 ContractState 被添加到一个 TransactionBuilder 之后,它就被包装成了一个 TransactionState

typealias ContractClassName = String

/**
 * A wrapper for [ContractState] containing additional platform-level state information and contract 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,
        /**
         * The contract class name that will verify this state that will be created via reflection.
         * The attachment containing this class will be automatically added to the transaction at transaction creation
         * time.
         *
         * Currently these are loaded from the classpath of the node which includes the cordapp directory - at some
         * point these will also be loaded and run from the attachment store directly, allowing contracts to be
         * sent across, and run, from the network from within a sandbox environment.
         *
         * TODO: Implement the contract sandbox loading of the contract attachments
         * */
        val contract: ContractClassName,
        /** 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,
        /**
         * A validator for the contract attachments on the transaction.
         */
        val constraint: AttachmentConstraint = AutomaticHashConstraint)

其中: