Corda 将代币从一个账户转移到另一方(账户托管在不同的一方)

Corda move tokens from account to party (account hosted in different party)

我们的 Corda 网络除了公证人之外还有 3 个节点。 该图显示了每个节点应该做什么。

只有在这种情况下我们才会遇到麻烦“需要将代币从账户持有人转移到乙方”

流码:


    class TransferETokenDiffNodeFlow(val actualHolder: AbstractParty,
                                     val newHolder: AbstractParty,
                                     val numEtokens: Double,
                                     val observables: MutableList = mutableListOf()) :
            FlowLogic() {
    
        private fun notary() = serviceHub.networkMapCache.notaryIdentities.first()
    @Suspendable
    override fun call(): SignedTransaction {
    
        progressTracker.currentStep = INITIALIZING
        val txBuilder = TransactionBuilder(notary())
    
        val actualHolderStateRef = accountService.accountInfo(actualHolder.owningKey)
                ?: throw AccountNotFoundException("Account not found exception.")
        val actualHolderInfo = actualHolderStateRef.state.data
    
        val actualHolderSession = initiateFlow(actualHolderInfo.host)
    
        actualHolderSession.send(numEtokens)
        actualHolderSession.send(actualHolder)
        actualHolderSession.send(newHolder)
    
        val inputs = subFlow(ReceiveStateAndRefFlow(actualHolderSession))
        val tokens: List = actualHolderSession.receive>().unwrap { it -> it}
    
        progressTracker.currentStep = BUILDING
        addMoveTokens(txBuilder, inputs, tokens)
    
        progressTracker.currentStep = SIGNING
        val initialSignedTrnx = serviceHub.signInitialTransaction(txBuilder)
    
        progressTracker.currentStep = GATHERING_SIGS
        val fulySignedTx= subFlow(CollectSignaturesFlow(initialSignedTrnx, listOf(actualHolderSession)))
    
        progressTracker.currentStep = FINALISING_CREATE
        val stx = subFlow(FinalityFlow(fulySignedTx, listOf(actualHolderSession)))
    
        progressTracker.currentStep = FINALISING
        val statesTx = stx.tx.outRefsOfType()
        statesTx.forEach { state ->
            observables.forEach { observable ->
                subFlow(ShareStateAndSyncAccounts(state, observable))
            }
        }
    
        return stx
    }
    }
    
    
    //ResponderFlow code:
    class TransferETokenDiffNodeFlowResponder(val counterpartySession: FlowSession) : FlowLogic() {
    @Suspendable
    override fun call(): SignedTransaction {
    
        val numEtokens = counterpartySession.receive().unwrap { it }
        val actualHolder = counterpartySession.receive().unwrap { it }
        val newHolder = counterpartySession.receive().unwrap { it }
    
        val partyAndAmount = PartyAndAmount(newHolder, numEtokens of EnergyTokenType.getInstance("ENERGY"))
    
        val actualHolderStateRef = accountService.accountInfo(actualHolder.owningKey)
                ?: throw AccountNotFoundException("Account not found exception.")
        val actualHolderInfo = actualHolderStateRef.state.data
        val criteria = QueryCriteria.VaultQueryCriteria(externalIds = listOf(actualHolderInfo.identifier.id),
                status = Vault.StateStatus.UNCONSUMED)
    
        val selector = DatabaseTokenSelection(serviceHub)
    
        val (inputs, outputs) = selector.generateMove(listOf(partyAndAmount).toPairs(),
                actualHolder, TokenQueryBy(queryCriteria = criteria), runId.uuid)
    
        subFlow(SendStateAndRefFlow(counterpartySession, inputs))
    
        counterpartySession.send(outputs)
    
        subFlow(object : SignTransactionFlow(counterpartySession) {
            @Throws(FlowException::class)
            override fun checkTransaction(stx: SignedTransaction) {
            }
        })
    
        return subFlow(ReceiveFinalityFlow(counterpartySession))
    }
    }

需要在丙方执行流程,actualHolder为账户持有人,newHolder为乙方

使用此代码 returns 错误: net.corda.core.CordaRuntimeException:java.lang.IllegalArgumentException:未为以下交易参与者提供流程会话:[O = Party B, L = Curitiba, C = BR]

但是如果我更改代码并添加乙方会话,则returns错误: java.lang.IllegalArgumentException:CollectSignaturesFlow 的发起者必须准确传递签署交易所需的会话。

问题来了,为什么addMoveTokens不把newHolder作为必需的签名者呢? 我该如何解决这个问题?

你的代码中有很多要讨论的地方;让我们从错误开始:

  1. 当您移动代币时,唯一需要的签名者是当前持有者;因此,在您的情况下,您应该只传递一个 FlowSession,即 session 和 PartyA(因为它是令牌的持有者);所以你应该只有:
    CollectSignaturesFlow(initialSignedTrnx, listOf(actualHolderSession))
    
  2. 至于完成交易,session所有参与者必须通过。您案例中所有参与者的联合是代币的当前持有者(即PartyA)和新持有者(即PartyB);所以你应该:
    FinalityFlow(fulySignedTx, listOf(actualHolderSession, partyB))
    
    这就是为什么当你只为 sub-flows 传递 actualHolderSession 时;您收到缺少 session 的信息(因为最终流程也需要 PartyB 的 session);当您为两者添加 PartyB session 时,收集签名流程会抱怨您传递了额外的 session (PartyB 不需要签署移动命令,只有现在的持有者是)。
  3. 由于您的响应程序流程正在处理这两项任务(签名和完成);您必须从发起者向响应者发送一些数据(字符串或您选择的任何内容)以表示响应者的角色,因此对于 PartyA 您将发送 SignerAndFinalizer;而对于 PartyB,您发送 Finalizer;在响应者的开头,您收到“角色”并采取相应行动(即,如果它是 Finalizer,您不调用 SignTransactionFlow,仅调用 ReceiveFinalityFlow)。

现在进入其他主题:

  1. 我不建议一方移动另一方持有的代币。想象一下,您的账户中有钱,而我从您的账户中转移了这笔钱。它应该是持有启动移动的令牌的节点。实际上,这就是 SDK 的现成流程的运作方式。

    如果您查看 AbstractMoveTokensFlow,您会发现他们仅依靠 ObserverAwareFinalityFlow 在本地签署交易(参见 here); to confirm that, you can see that inside ObserverAwareFinalityFlow there is no CollectSignaturesFlow (i.e. it doesn't request signatures from other nodes), only signs locally (see here)。

    这一切意味着,如果您使用 SDK 的就绪流程来移动由与调用移动流程的节点不同的节点持有的代币;你会得到一个错误,因为持有者的签名是必需的,但就绪流不从其他节点收集签名,仅从调用移动流的节点收集签名,该节点不是令牌的持有者。

    我建议遵循 SDK 的方法,因为只有代币持有者才能移动他们的代币。

  2. Accounts 库文档提到的另一件事是不要混合使用帐户和 non-accounts(即只能从一个帐户移动到另一个帐户或从一方到另一方);建议改为为您的节点创建一个“默认”帐户(例如,创建一个名称与您节点的 X500 名称相匹配的帐户),阅读 here.