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作为必需的签名者呢?
我该如何解决这个问题?
你的代码中有很多要讨论的地方;让我们从错误开始:
- 当您移动代币时,唯一需要的签名者是当前持有者;因此,在您的情况下,您应该只传递一个
FlowSession
,即 session 和 PartyA
(因为它是令牌的持有者);所以你应该只有:
CollectSignaturesFlow(initialSignedTrnx, listOf(actualHolderSession))
- 至于完成交易,session所有参与者必须通过。您案例中所有参与者的联合是代币的当前持有者(即
PartyA
)和新持有者(即PartyB
);所以你应该:
FinalityFlow(fulySignedTx, listOf(actualHolderSession, partyB))
这就是为什么当你只为 sub-flows 传递 actualHolderSession
时;您收到缺少 session 的信息(因为最终流程也需要 PartyB
的 session);当您为两者添加 PartyB
session 时,收集签名流程会抱怨您传递了额外的 session (PartyB
不需要签署移动命令,只有现在的持有者是)。
- 由于您的响应程序流程正在处理这两项任务(签名和完成);您必须从发起者向响应者发送一些数据(字符串或您选择的任何内容)以表示响应者的角色,因此对于
PartyA
您将发送 SignerAndFinalizer
;而对于 PartyB
,您发送 Finalizer
;在响应者的开头,您收到“角色”并采取相应行动(即,如果它是 Finalizer
,您不调用 SignTransactionFlow
,仅调用 ReceiveFinalityFlow
)。
现在进入其他主题:
我不建议一方移动另一方持有的代币。想象一下,您的账户中有钱,而我从您的账户中转移了这笔钱。它应该是持有启动移动的令牌的节点。实际上,这就是 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 的方法,因为只有代币持有者才能移动他们的代币。
Accounts 库文档提到的另一件事是不要混合使用帐户和 non-accounts(即只能从一个帐户移动到另一个帐户或从一方到另一方);建议改为为您的节点创建一个“默认”帐户(例如,创建一个名称与您节点的 X500 名称相匹配的帐户),阅读 here.
我们的 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作为必需的签名者呢? 我该如何解决这个问题?
你的代码中有很多要讨论的地方;让我们从错误开始:
- 当您移动代币时,唯一需要的签名者是当前持有者;因此,在您的情况下,您应该只传递一个
FlowSession
,即 session 和PartyA
(因为它是令牌的持有者);所以你应该只有:CollectSignaturesFlow(initialSignedTrnx, listOf(actualHolderSession))
- 至于完成交易,session所有参与者必须通过。您案例中所有参与者的联合是代币的当前持有者(即
PartyA
)和新持有者(即PartyB
);所以你应该:
这就是为什么当你只为 sub-flows 传递FinalityFlow(fulySignedTx, listOf(actualHolderSession, partyB))
actualHolderSession
时;您收到缺少 session 的信息(因为最终流程也需要PartyB
的 session);当您为两者添加PartyB
session 时,收集签名流程会抱怨您传递了额外的 session (PartyB
不需要签署移动命令,只有现在的持有者是)。 - 由于您的响应程序流程正在处理这两项任务(签名和完成);您必须从发起者向响应者发送一些数据(字符串或您选择的任何内容)以表示响应者的角色,因此对于
PartyA
您将发送SignerAndFinalizer
;而对于PartyB
,您发送Finalizer
;在响应者的开头,您收到“角色”并采取相应行动(即,如果它是Finalizer
,您不调用SignTransactionFlow
,仅调用ReceiveFinalityFlow
)。
现在进入其他主题:
我不建议一方移动另一方持有的代币。想象一下,您的账户中有钱,而我从您的账户中转移了这笔钱。它应该是持有启动移动的令牌的节点。实际上,这就是 SDK 的现成流程的运作方式。
如果您查看
AbstractMoveTokensFlow
,您会发现他们仅依靠ObserverAwareFinalityFlow
在本地签署交易(参见 here); to confirm that, you can see that insideObserverAwareFinalityFlow
there is noCollectSignaturesFlow
(i.e. it doesn't request signatures from other nodes), only signs locally (see here)。这一切意味着,如果您使用 SDK 的就绪流程来移动由与调用移动流程的节点不同的节点持有的代币;你会得到一个错误,因为持有者的签名是必需的,但就绪流不从其他节点收集签名,仅从调用移动流的节点收集签名,该节点不是令牌的持有者。
我建议遵循 SDK 的方法,因为只有代币持有者才能移动他们的代币。
Accounts 库文档提到的另一件事是不要混合使用帐户和 non-accounts(即只能从一个帐户移动到另一个帐户或从一方到另一方);建议改为为您的节点创建一个“默认”帐户(例如,创建一个名称与您节点的 X500 名称相匹配的帐户),阅读 here.