API: Contract Constrains

合约约束 Contract constraints

交易的 states 指定了一个约束可以在合约中被用来验证它。对于一笔有效的交易,每个 state 相关的 verify 方法必须要成功执行。但是,为了能让这些变得安全,仅仅依靠名字来指定 verify 方法是不足够的,因为可能会存在多个具有相同名字的方法签名但他们是不同的实现。合约约束(contract constraints)通过允许一个合约开发者来约束在所有实现的全集中哪个 verify 方法会被使用(比如针对于能够匹配某个签名的所有实现的全集,合约约束能够限定到它的某个子集)。

一个典型的约束就是包含了 contract 和 states 的 CorDapp JAR 的哈希值, 在将来的版本中会包含对于该 JAR 指定的必须的签名者,或者同时要包含签名者和哈希值。约束可以在构造一笔交易的时候来指定,如果没有指定的话,一个自动的约束会被使用。

一个 TransactionState 含有一个 constraint 字段,它代表了 state 的 附件(attachment)约束。当一方构造了一个没有指定约束参数的 TransactionState 的时候,一个默认的值(AutomaticHashConstraint)会被使用。这个约束的默认值对于一个特定的 HashAttachmentConstraint 能够被自动的满足,这个 HashAttachmentConstraint 包含了附件的哈希值,而附件的哈希值又包含了那个 TransactionState 的 contract。这个自动的解决方案会在当一个 TransactionBuilder 被转换为一个 WireTransaction 的时候发生。这个会减少在构建一笔交易的时候为了找到一个指定的哈希值而引入的 boilerplate。

使用其他实现了 AttachmentConstraint 接口的类来显式地指定某个约束是可以做到的。要手动地制定一个哈希值,可以使用 HashAttachmentConstraint,如果不想指定任何的约束的话,可以使用 AlwaysAcceptAttachmentConstraint 尽管这个只是为了测试的目的。下边的例子演示了在一个 flow 中如何通过一个显式指定的哈希值约束来构造一个 TransactionState

// Constructing a transaction with a custom hash constraint on a state
TransactionBuilder tx = new TransactionBuilder()

Party notaryParty = ... // a notary party
DummyState contractState = new DummyState()
SecureHash myAttachmentsHash = serviceHub.cordappProvider.getContractAttachmentID(DummyContract.PROGRAM_ID)
TransactionState transactionState = new TransactionState(contractState, DummyContract.Companion.getPROGRAMID(), notaryParty, new AttachmentHashConstraint(myAttachmentsHash))

tx.addOutputState(transactionState)
WireTransaction wtx = tx.toWireTransaction(serviceHub) // This is where an automatic constraint would be resolved
LedgerTransaction ltx = wtx.toLedgerTransaction(serviceHub)
ltx.verify() // Verifies both the attachment constraints and contracts

这个机制是为了一致性和安全的原因而存在。不要去验证一个错误的合约是非常重要的,如果附加了一个错误版本的合约的话,这是有可能会发生的。更重要的是当验证交易链(transaction chains)的时候,将来的版本中,附件会从网络中加载然后放置到附件的 sandbox 中,被用来验证交易链的有效性。确保被使用的附件是正确的版本通过提供一个假的合约来保证这个验证是防干扰的(tamper-proof)。

CorDapps 作为附件

安装在节点上并包含实现了 Contract 接口的类的 CorDapps JARs 文件(什么是 CorDapp?)当节点启动的时候会被自动加载到 AttachmentStorage

当 CorDapps 被加载到 attachment store 后,节点会创建一个在 contract 类和 attachment 之间创建一个链接。这个使找到任何指定 contract 的 attachment 成为可能。这个就是附件的自动解决方案是如何通过使用 TransactionBuilder 来完成的,并且当确认约束及 contract 的时候,附件是如何被关联至他们对应的 contracts 上的。

AttachmentConstraint 的实现

有三种 AttachmentConstraint 的实现,以后会增加更多的。

  • AlwaysAcceptAttachmentConstraint:任何的附件(除了没有添加附件)都会满足这个约束
  • AutomaticHashConstraint:这个会在一个 TransactionBuilder 被转换成 WireTransaction 的时候被处理成一个 HashAttachmentConstraintHashAttachmentConstraint 会包含 CorDapp 的附件的哈希值,这个哈希值包含了在 TransactionState.contract 字段中的 ContractState
  • HashAttachmentConstraint:会要求包含 contract 的附件的哈希值要和约束中定义的哈希值相匹配。

我们计划会添加一个未来的 AttachmentConstraint,它仅仅在提供在附件 JAR 上的签名的时候才能够被满足。这个允许了相信一个附件是由信任的节点发过来的。

限制 Limitations

一个 AttachmentConstraint 是通过运行 AttachmentConstraint.isSatisfiedBy 来进行验证的。当它被调用的时候,正在验证它的那笔交易能够提供相关的附件。

测试

因为所有涉及 transactions 的测试现在都要求要有附件,也要求必须在测试中加载正确的附件。JVM 生态系统中的单元测试环境趋向于使用类目录(class directories)而不是 JARs,所以 CorDapp JARs 通常不会被创建来做测试。这些要求将会给构建 Corda 和 CorDapp 带来巨大的难度,所以测试套件有一套方便的方法来从包名字(package names)或者当测试中引入的 CorDapp(s) 已经存在的时候,通过指定 JAR URLs 来生成 CorDapps。

MockNetwork/MockNode

确认一个 MockNode 生成正确的 CorDapps 的最简单的方式就是在 MockNetwork/Node 被创建之前调用 setCordappPackages 并且在测试结束的时候调用 unsetCordappPackages。这些调用会使 AbstractNode 使用这个指定的包来作为 CorDapps 的 source。这个包里的所有文件会被 zip 成一个 JAR 并被添加到附件中然后被 CordappLoader 作为 CorDapps 被加载。下边是一个实例:

class SomeTestClass {
     MockNetwork network = null

     @Before
     void setup() {
         // The ordering of the two below lines is important - if the MockNetwork is created before the nodes and network
         // are created the CorDapps will not be loaded into the MockNodes correctly.
         setCordappPackages(Arrays.asList("com.domain.cordapp"))
         network = new MockNetwork()
     }

     @After
     void teardown() {
         // This must be called at the end otherwise the global state set by setCordappPackages may leak into future
         // tests in the same test runner environment.
         unsetCordappPackages()
     }

     ... // Your tests go here
}

MockServices

如果你直接使用了 MockServices,你可以使用一个带有一个包列表的构造器来实例化它,它会使用 cordappPackages 这个参数作为 CorDapps 来使用。

MockServices mockServices = new MockServices(Arrays.asList("com.domain.cordapp"))

Driver

Driver 会带有一个叫做 extraCordappPackagesToScan 的参数,这个参数是一个可以作为 CorDapps 来使用的包列表。

driver(new DriverParameters().setExtraCordappPackagesToScan(Arrays.asList("com.domain.cordapp"))) ...

Full Nodes

当测试全节点的时候,只需要简单地把你的 CorDapp 放置到节点的 plugins 路径下即可。

Debugging

如果一个附件的约束无法满足的话,一个 MissingContractAttachments 的异常会被触发。以下是两个常见的 MissingContractAttachments 一场的 source:

  • Not setting CorDapp package in tests:你正在运行着测试,并且没有指定扫描哪些 CorDapp 包。参考上边的介绍
  • Wrong fully-qualified contract name:你错误地指定了合约的 full-qualified 名字。例如,你在 com.mycompany.myapp.contracts 这个包中定义了 MyContract,但是你给 TransactionBuilder 传递的 fully-qualified 合约名称是 com.mycompany.myapp.MyContract(而不是 com.mycompany.myapp.contracts.MyContract

发表评论

电子邮件地址不会被公开。 必填项已用*标注