Corda API:Contracts

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

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

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 参数并且不会返回任何内容。这个方法被用来检验一个交易的提议是否有效,包括下边的验证:

  • 我们会搜集这个交易的 input 和 output states 的 contract code
  • 我们会调用每个 contract code 的 verify 方法,将 transaction 作为 input 传进去
  • 这个更新账本的提议仅仅在所有的 verify 方法都没有返回 exception 的情况下才算是有效的

verify 是在一个 sandbox 中执行的:

  • 它没有权限访问内部的内容
  • 针对于它可用的类库被放入白名单来不允许:网络访问像硬盘或数据库 I/O

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

最简单的两个 verify 方法:

  • 一个是接受所有可能的 transactions:
override fun verify(tx: TransactionForContract) {
    // Always accepts!
}
  • 一个是拒绝所有的 transactions:
override fun verify(tx: TransactionForContract) {
    throw IllegalArgumentException("Always rejects!")
}

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() {

其中:

  • inputs 是类型为 List<StateAndRef<ContractState>> 的 transaction 的 inputs
  • outputs 是类型为 List<TransactionState<ContractState>> 的 transaction 的 outputs
  • commands 是类型为 List<CommandWithParties<CommandData>> 的 transaction 的 commands 和相关的签名者
  • attachments 是类型为 List<Attachment> 的 transaction 的 attachments
  • notary 是 transaction 的 notary。这个必须要同所有的 inputs 拥有相同的 notary
  • timeWindow 定义了一笔交易在什么样的时间窗(timewindow)内才会被公正(notarised)

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

  • inputStatesStateAndRef 列表中获得 input ContractState
  • getInput/getOutput/getCommand/getAttachment 通过索引(index)来获得某个组件
  • getAttachment 通过 ID 获得一个附件
  • inputsOfType/inRefsOfType/outputsOfType/outRefsOfType/commandsOfType 基于他们的类型获得相关组件
  • filterInputs/filterInRefs/filterOutputs/filterOutRefs/filterCommands 基于一个条件获得相关组件
  • findInput/findInRef/findOutput/findOutRef/findCommand 获得满足一定条件的单一组件,或者当有多个满足条件的组件的时候抛出异常

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
)

其中:

  • signers 是每个签名者的 PublicKey 的一个列表
  • signingParties 签名者 identities 的列表,如果知道的话
  • value 是被签名的对象(在这里指的是这个 command)

使用 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.
    }
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值