原文地址:https://docs.corda.net/corda-api.html
在阅读本篇文章前,请阅读 Corda 核心概念 - States。
在 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
}
ContractState
只有一个字段 participants
。participants
是一个 AbstractParty
的 List
,代表了同这个 state 有关的节点。participants
将会:
FinalityFlow
的一部分,接收任何涉及到该 state 的最终交易信息state 的行为可以通过实现 ContractState
的子接口被进一步的定制。最常用的两个子接口包括:
LinearState
OwnableState
LinearState
代表了一个在任何时间都是只有一个当前版本的共享的事实(shared fact)。LinearState
states 通过替换自己的方式实现一个线性的改变。而 OwnableState
则代表在任何时候都可以被自由的拆分或者合并的资产。现金 cash 就是一个 OwnableState
的很好的例子 - 两个已经存在 $5 现金 state 可以合并为一个单独的 $10 的现金 state,或者被拆分成 5 个 $1 的现金 state。对于 OwnableState
,它的总金额是更重要的,而不是到底有多少份。
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
链的时候,我们会:
linearId
从账本中获取该 state 链中最新的 statelinearId
的新的 state新创建的 state 现在就成为了这个 state 链的最新的 state,代表了协议的最新的当前 state。
linearId
是一种 UniqueIdentifier
类型,由下边的元素组成:
UUID
,代表了一个全局唯一的 128 bit 的随机数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
}
其中:
owner
是该资产的所有者的公钥 PublicKey
withNewOwner(newOwner: AbstractParty)
创建了一个具有新的所有者的 state 的副本由于 OwnableState
形成了一个可替换的资产(fungible assets)的模型,这种资产可以合并和拆分,OwnableState
实例没有 linearId
。一笔交易产生的 $5 现金和另一笔其他的交易产生的 $5 现金会被看作是同样的 state。
你也可以通过实现下边的接口来定制你的 state:
QueryableState
,这可以让 state 能够在节点的数据库中通过使用自定义的属性来被查询SchedulableState
,可以允许我们对 state 设置一个将来会发生的动作(比如使用优惠券购买债券)除了实现 ContractState
或者子接口外,一个 state 还允许包含任意数量的额外字段和方法。比如下边的代码就定义了一个相对复杂的代表现金 cash 的一个 state:
/** A state representing a cash claim against some party. */
data class State(
override val amount: Amount>,
/** There must be a MoveCommand signed by this key to claim the amount. */
override val owner: AbstractParty
) : FungibleAsset, QueryableState {
constructor(deposit: PartyAndReference, amount: Amount, 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>, newOwner: AbstractParty): FungibleAsset
= 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 = listOf(CashSchemaV1)
/** Additional used schemas would be added here (eg. CashSchemaV2, CashSchemaV3, ...) */
}
当一个节点记录了一笔新的交易的时候,它还可以选择是否将交易的每一个 output state 存储到它的 vault 中。默认的 vault 实现让这个决定基于以下的规则:
OwnableState
,如果该节点是该 state 的 owner
的时候,账本将会记录该 stateparticipants
,那么账本就会记录该 state不相关的 states 是不会存储到节点的账本中的。但是节点还是会将创建该 state 的交易信息存储到它的 transaction storage 中。
当一个 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 @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)
其中:
data
是将被存储到账本中的 statecontract
是一个控制 state 转变的合约notary
是这个 state 的 notary serviceencumbrance
指向了另一个 state,该 state 必须以一个 input 的形式出现在消费此 state 的交易中constraint
是一个该 state 使用的合约代码 contract-code 附件的约束