Corda API: Contracts

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

注意:阅读本文之前,你应该对 Corda 核心概念 - Contracts 比较熟悉了。

Contract

Contracts 都是实现了 Contract 接口的类。Contract 接口定义如下:

/**
 * Implemented by a program that implements business logic on the shared ledger. All participants run this code for
 * every [net.corda.core.transactions.LedgerTransaction] they see on the network, for every input and output state. All
 * contracts must accept the transaction for it to be accepted: failure of any aborts the entire thing. The time is taken
 * from a trusted time-window attached to the transaction itself i.e. it is NOT necessarily the current time.
 *
 * TODO: Contract serialization is likely to change, so the annotation is likely temporary.
 */
@CordaSerializable
interface Contract {
    /**
     * Takes an object that represents a state transition, and ensures the inputs/outputs/commands make sense.
     * Must throw an exception if there's a problem that should prevent state transition. Takes a single object
     * rather than an argument so that additional data can be added without breaking binary compatibility with
     * existing contract code.
     */
    @Throws(IllegalArgumentException::class)
    fun verify(tx: LedgerTransaction)
}

Contract 只有一个 verify 方法,它会有一个 LedgerTransaction 作为 input 参数并且不会返回任何内容。这个方法被用来检验一个交易的提议是否有效,包括下边的验证:

verify 是在一个 sandbox 中执行的:

这意味着 verify 仅仅能够在决定一个交易是否有效的时候才能够访问 LedgerTransaction 中定义的属性。

最简单的两个 verify 方法:

LedgerTransaction

被传入 verify 方法中的 LedgerTransaction 对象具有以下属性:

@CordaSerializable
data class LedgerTransaction @JvmOverloads constructor(
        /** The resolved input states which will be consumed/invalidated by the execution of this transaction. */
        override val inputs: List<StateAndRef<ContractState>>,
        override val outputs: List<TransactionState<ContractState>>,
        /** Arbitrary data passed to the program of each input state. */
        val commands: List<CommandWithParties<CommandData>>,
        /** A list of [Attachment] objects identified by the transaction that are needed for this transaction to verify. */
        val attachments: List<Attachment>,
        /** The hash of the original serialised WireTransaction. */
        override val id: SecureHash,
        override val notary: Party?,
        val timeWindow: TimeWindow?,
        val privacySalt: PrivacySalt,
        private val networkParameters: NetworkParameters? = null
) : FullTransaction() {

其中:

LedgerTransaction 暴漏了很多 utility 方法来访问交易的内容:

requireThat

verify 能够针对每一个约束手动地抛出异常:

override fun verify(tx: LedgerTransaction) {
    if (tx.inputs.size > 0)
        throw IllegalArgumentException("No inputs should be consumed when issuing an X.")

    if (tx.outputs.size != 1)
        throw IllegalArgumentException("Only one output state should be created.")
}

但是这个定义有些繁琐。我们可以使用 requireThat 来定义一系列的约束:

requireThat {
    "No inputs should be consumed when issuing an X." using (tx.inputs.isEmpty())
    "Only one output state should be created." using (tx.outputs.size == 1)
    val out = tx.outputs.single() as XState
    "The sender and the recipient cannot be the same entity." using (out.sender != out.recipient)
    "All of the participants must be signers." using (command.signers.containsAll(out.participants))
    "The X's value must be non-negative." using (out.x.value > 0)
}

对于 requireThat 中的每一个 <String, Boolean> 对来说,如果 boolean 条件返回的是 false,一个 IllegalArgumentException 会被抛出,包含对应的错误信息。所以这个错误会造成 transaction 被拒绝。

Commands

LedgerTransaction 包含了作为 CommandWithParties 实例列表的 commands。CommandWithParties 将一个 CommandData 和一个所需的签名者列表匹配起来:

/** A [Command] where the signing parties have been looked up if they have a well known/recognised institutional key. */
@CordaSerializable
data class CommandWithParties<out T : CommandData>(
        val signers: List<PublicKey>,
        /** If any public keys were recognised, the looked up institutions are available here */
        val signingParties: List<Party>,
        val value: T
)

其中:

使用 commands 来处理 verify 分支

总的来说,我们希望基于交易的 commands 来定义不同的约束条件。比如我们想要为一个现金发行的 transaction 和 一个现金交换的 transaction 定义不同的合约。

我们可以通过提取这个 command 并在 verify 里使用标准的分支逻辑来实现这个功能。这里我们提取了交易中的类型为 XContract.Commands 的单独的 command,并且相应地对 verify 进行了分支逻辑判断:

class XContract : Contract {
    interface Commands : CommandData {
        class Issue : TypeOnlyCommandData(), Commands
        class Transfer : TypeOnlyCommandData(), Commands
    }

    override fun verify(tx: LedgerTransaction) {
        val command = tx.findCommand<Commands> { true }

        when (command) {
            is Commands.Issue -> {
                // Issuance verification logic.
            }
            is Commands.Transfer -> {
                // Transfer verification logic.
            }
        }
    }
}

Legal prose

一个 Contract 类可以使用 @LegalProseReference 的 annotation。这个 annotation 能够将这个 contract code 同以法律条款的形式定义的文档关联起来,这个文档中定义的是跟 verify 里所定义的约束内容相同的内容。这个并不是必须的,但是当出现意见不统一的情况下,法律条款文件效力会优先于软件中的实现。

@LegalProseReference 仅仅有一个 uri 的输入参数,作为同 contract code 相关联的法律条款文档的标识:

@LegalProseReference(uri = "foo.bar.com/my-legal-doc.html")
class MyContract : Contract {
    override fun verify(tx: LedgerTransaction) {
        // Contract logic.
    }
}