在同一个 Corda 流程中创建多个交易有哪些风险?

Aus*_*art 5 corda

在 Cordapp 中,我想更新第二条链作为正常交易的一部分。由于数据是在两个不同的状态下由不同的参与者跟踪的,因此这需要在两个事务中完成。

为了讨论的目的,我们有 A 和 B 两方。A 向 B 发起交易 1。收到交易 1 后,B 方启动交易 2 以更新另一个状态。如何确保两笔交易均成功完成?

有两种方法可以解决这个问题:

  1. 启动subFlow事务 2 内联流响应程序。
  2. 使用vaultTrack响应已提交的事务 1 并启动subFlowfor 事务 2。

以下是选项 1 的一些示例代码:

class CustomerIssueFlowResponder(val otherPartyFlow: FlowSession) : FlowLogic<SignedTransaction>() {
    @Suspendable
    override fun call(): SignedTransaction {
        val signTransactionFlow = object : SignTransactionFlow(otherPartyFlow) {
            override fun checkTransaction(stx: SignedTransaction) = requireThat {
                val output = stx.tx.outputs.single().data
                "This must be an CustomerState." using (output is CustomerState)
            }
        }
        // signing transaction 1
        val stx = subFlow(signTransactionFlow)
        val customerState = stx.tx.outputs.single().data as CustomerState
        // initiating transaction 2
        subFlow(CustomerIssueOrUpdateFlow(customerState))

        return stx
    }
}
Run Code Online (Sandbox Code Playgroud)

每种方法的优点和缺点是什么?

我对选项 1 的担忧是单个流中的两个事务不是原子的。两个事务之一可能会失败,而另一个会成功,这将使数据处于不一致的状态。例如:subFlow上述响应者内部的事务 2 可能会成功,但事务 1 可能因双重支出问题而因公证而失败。在这种情况下,第二条链可能会被不正确地更新。

使用vaultTrack会更安全,因为事务 1 将成功,但无法保证事务 2 最终完成。

Joe*_*oel 0

首先,你说:

由于数据是在两个不同的状态下由不同的参与者跟踪的,因此这需要在两个事务中完成。

这不一定是真的。具有不同参与者的两个独立状态可以是同一事务的一部分。但是,我们假设您有理由将它们分开(例如隐私)。

从 Corda 4 开始,该平台不提供多事务原子性保证。没有内置方法可以确保仅在提交另一个事务时才提交给定事务(但请参阅下面的 PS)。

所以你的选择都不能保证多事务原子性。我仍然相信选项 1 更好,因为您可以得到流程框架的保证,即事务将被调用。您担心的是,即使第一个事务失败,响应者也会调用创建第二个事务的流程。可以使用waitForLedgerCommit确保在启动流程创建第二个事务之前提交事务 1 来避免这种情况:

class CustomerIssueFlowResponder(val otherPartyFlow: FlowSession) : FlowLogic<SignedTransaction>() {
    @Suspendable
    override fun call(): SignedTransaction {
        val signTransactionFlow = object : SignTransactionFlow(otherPartyFlow) {
            override fun checkTransaction(stx: SignedTransaction) = requireThat {
                val output = stx.tx.outputs.single().data
                "This must be an CustomerState." using (output is CustomerState)
            }
        }
        // signing transaction 1
        val stx = subFlow(signTransactionFlow)
        val customerState = stx.tx.outputs.single().data as CustomerState
        // initiating transaction 2 once transaction 1 is committed
        waitForLedgerCommit(stx.id)
        subFlow(CustomerIssueOrUpdateFlow(customerState))

        return stx
    }
}
Run Code Online (Sandbox Code Playgroud)

PS 实现多事务原子性的一种可能方法是使用产权负担,如下所示:

  • 假设我们有两个交易:Tx1,输出 S1,Tx2,输出 S2
  • 作为 Tx1 的一部分,抵押 S1,以便只有在您知道 Tx2 上的公证人签名时才能使用它,或者在一段时间过后恢复到其原始状态
  • 作为 Tx2 的一部分,抵押 S2,以便只有在您知道 Tx1 上的公证人签名时才能使用它,或者在一段时间过后恢复到其原始状态

然而,我想到的一种攻击是FinalityFlowTx1 的调用者没有在 Tx1 上分发公证人的签名,从而允许他们在不放弃 Tx1 的情况下索取 Tx2。如果公证人将其所有签名发布到某个公告板上,而不是依靠调用者FinalityFlow来分发它们,那么这个问题就可以解决。