【问题标题】:What are the risks of creating multiple transactions within the same Corda flow?在同一个 Corda 流中创建多个事务有什么风险?
【发布时间】:2019-01-21 16:45:49
【问题描述】:

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

出于讨论的目的,我们有两方 A 和 B。A 与 B 启动交易 1。收到交易 1 后,B 方启动交易 2 以更新另一个状态。我们如何确保两个事务都成功完成?

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

  1. 为事务 2 启动 subFlow 内联流响应器。
  2. 使用vaultTrack 响应提交的事务1 并为事务2 启动subFlow

这是选项 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
    }
}

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

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

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

【问题讨论】:

  • 您似乎想要一个 Atomic Tx。如果是这样,在 Corda 中,Tx 是原子的。因此,任何必须是原子的都将成为单个 Tx 的一部分。您需要做的是制作一个 Tx。将您想要的所有更改添加到其中,即输入和输出状态,然后提交该 Tx。
  • 根据乔尔的回答,您所说的可能是可能的。如果我的问题中的响应者被转换为发起者并将客户数据发送给他们,他们可以创建一个具有两个输出的 tx:1 是共享的客户数据(两个参与者),1 是他们自己的私有链(发起者作为参与者) .

标签: corda


【解决方案1】:

首先,你说:

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

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

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

所以你的选择都不能保证多事务的原子性。我仍然相信选项 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
    }
}

附:实现多事务原子性的一种可能方法是使用保留,如下所示:

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

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

【讨论】:

  • 更详细一点,我认为这必须是多笔交易的原因是原始交易是由不知道第二笔交易的发行人创建的。响应者正在启动第二条链来跟踪他们自己分类帐中的相同数据。
  • 对于waitForLedgerCommit:如何及时解决?流响应者需要将stx 返回给发起者并让它们运行finalityFlow。我的印象是第二个 subFlow 会阻止进一步的处理,因此发起者永远没有机会恢复。
  • 发起者只是在等待接收签名作为CollectSignaturesFlow 的一部分。然后它将继续运行FinalityFlowFinalityFlow 然后使用启动子流分发事务,因此接收方将事务记录在不同的流响应器中。 CustomerIssueFlowResponder 偶尔醒来检查另一个流已将状态记录到保管库。然后它继续。仅供参考,在 Corda 4 中添加 ReceiveFinalityFlow 简化了事情并消除了对 waitForLedgerCommit 的需求。
  • 尚不清楚的是发起者如何从响应者那里获取签名的交易。响应者不是被waitForLedgerCommit 阻止,然后被最后一个subFlow 阻止吗?在纸面上,这看起来像是一个僵局。
  • 流在命中waitForLedgerCommit时暂停,允许其他流(包括接收并存储响应FinalityFlow的事务的流响应器)运行。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2019-12-04
  • 2018-12-20
  • 2018-07-04
相关资源
最近更新 更多