第三方 签名服务
我花了一段时间才想到一个标题,该标题可以概述本帖子的内容,而不会成为完整的句子。 我想我已经选择了清晰易读的东西。 无论哪种方式,让我澄清一下我实际上在说什么。
我已经看到几个人在Slack中提出类似以下的问题:
在该示例中,它显示了当运行响应者流的节点是必需签名者之一时的响应者流。 但是,当运行响应者流的节点不是必需的签名者(例如,与tx有关的状态的参与者之一)时,情况又如何呢? 我是否需要为此类节点编写响应者流程? 如果是这样,我应该如何编写响应者流程?
换一种说法:
我的州有一组参与者。 其中有些必须签署交易,有些则不能。 如何构造我的流程,尤其是响应者流程以应对呢?
由于响应者流程的工作方式,每个交易对手都在其中运行相同的响应者代码(除非被覆盖)。 样本中找到的简单代码无法处理让一组交易方做某事,而另一方做其他事。 需要构造您的流程以明确处理此问题。
执行此操作的代码相对简单,但是除非您已经使用Corda进行了一段时间的开发,否则它可能并不明显。
到目前为止,我对此问题有两种解决方案。 我很确定这些是当前可用的最佳解决方案,并且我想不出任何其他可行或值得追求的解决方案。 这些解决方案是:
- 向交易对手发送标志以告知他们是否为签名人
- 使用子流的
@InitiatingFlow
收集签名或保存交易
我将在以下各节中进行扩展。
在我接触他们之前,如果您不考虑签署人和参与者之间的差异,会发生什么? 典型的响应者流程将包括:
@InitiatedBy (SendMessageFlow:: class ) class SendMessageResponder( private val session: FlowSession) : FlowLogic() {
@Suspendable
override fun call(): SignedTransaction {
subFlow(object : SignTransactionFlow(session) {
override fun checkTransaction(stx: SignedTransaction) { }
})
return subFlow(ReceiveFinalityFlow(otherSideSession = session))
} }
如果启动流触发此响应者进行非签名交易对手,则会发生错误:
net.corda.core.flows.UnexpectedFlowEndException: Tried to access ended session SessionId(toLong= ) with empty buffer net.corda.core.flows.UnexpectedFlowEndException: Tried to access ended session SessionId(toLong= 3446769309292325575 net.corda.core.flows.UnexpectedFlowEndException: Tried to access ended session SessionId(toLong= 3446769309292325575 ) with empty buffer
at net.corda.node.services.statemachine.FlowStateMachineImpl.processEventsUntilFlowIsResumed(FlowStateMachineImpl.kt: 161 ) ~[corda-node- 4.0 .jar:?]
at net.corda.node.services.statemachine.FlowStateMachineImpl.suspend(FlowStateMachineImpl.kt: 407 ) ~[corda-node- 4.0 .jar:?]
at net.corda.node.services.statemachine.FlowSessionImpl.receive(FlowSessionImpl.kt: 67 ) ~[corda-node- 4.0 .jar:?]
at net.corda.node.services.statemachine.FlowSessionImpl.receive(FlowSessionImpl.kt: 71 ) ~[corda-node- 4.0 .jar:?]
at net.corda.core.flows.SignTransactionFlow.call(CollectSignaturesFlow.kt: 294 ) ~[corda-core- 4.0 .jar:?]
at net.corda.core.flows.SignTransactionFlow.call(CollectSignaturesFlow.kt: 198 ) ~[corda-core- 4.0 .jar:?]
at net.corda.node.services.statemachine.FlowStateMachineImpl.subFlow(FlowStateMachineImpl.kt: 290 ) ~[corda-node- 4.0 .jar:?]
at net.corda.core.flows.FlowLogic.subFlow(FlowLogic.kt: 314 ) ~[corda-core- 4.0 .jar:?]
at dev.lankydan.tutorial.flows.SendMessageResponder.call(SendMessageFlow.kt: 70 ) ~[main/:?]
at dev.lankydan.tutorial.flows.SendMessageResponder.call(SendMessageFlow.kt: 64 ) ~[main/:?]
这是因为非签名者从未发送过交易以进行签名,但是,可惜的是,他们的代码正坐在那里等待对永远不会发生的交易进行签名。 多么难过。 我在这里是为了防止您的交易对手在这里感到悲伤。
这也是一个很好的教学时刻。 如果你看到类似上面的堆栈跟踪,这是最有可能是由于错放send
S和receive
秒。 它们的顺序错误,或者send
或receive
丢失。 逐行运行代码,您应该应该能够查明不匹配的位置。
按标志区分
这种解决方案是我首先想到的,因为它更容易理解。
通知交易对手,告诉他们是否需要签署交易。 然后,他们的响应者流将执行SignTransactionFlow
或跳过它,直接进入ReceiveFinalityFlow
。 这两个路径将始终接收该标志并调用ReceiveFinalityFlow
。
可以在下面找到一个示例:
@InitiatingFlow class SendMessageFlow( private val message: MessageState) :
FlowLogic() {
@Suspendable
override fun call(): SignedTransaction {
val spy = serviceHub.identityService.partiesFromName( "Spy" , false ).first()
val tx = verifyAndSign(transaction(spy))
// initiate sessions with each party
val signingSession = initiateFlow(message.recipient)
val spySession = initiateFlow(spy)
// send signing flags to counterparties
signingSession.send( true )
spySession.send( false )
val stx = collectSignature(tx, listOf(signingSession))
// tell everyone to save the transaction
return subFlow(FinalityFlow(stx, listOf(signingSession, spySession))
}
private fun transaction(spy: Party) =
TransactionBuilder(notary()).apply {
// the spy is added to the messages participants
val spiedOnMessage = message.copy(participants = message.participants + spy)
addOutputState(spiedOnMessage, MessageContract.CONTRACT_ID)
addCommand(Command(Send(), listOf(message.recipient, message.sender).map(Party::owningKey)))
} } @InitiatedBy (SendMessageFlow:: class ) class SendMessageResponder( private val session: FlowSession) : FlowLogic() {
@Suspendable
override fun call(): SignedTransaction {
// receive the flag
val needsToSignTransaction = session.receive().unwrap { it }
// only sign if instructed to do so
if (needsToSignTransaction) {
subFlow(object : SignTransactionFlow(session) {
override fun checkTransaction(stx: SignedTransaction) { }
})
}
// always save the transaction
return subFlow(ReceiveFinalityFlow(otherSideSession = session))
} }
上面代码的重点:
-
spy
(另一方)已添加到该州的participants
列表中 - 将标志发送给参与者,告诉他们是否签名
- 响应者流
receive
s标志,如果告知,则跳过SignTransactionFlow
- 调用
ReceiveFinalityFlow
,等待启动程序调用FinalityFlow
我将在这里留下解释,因为没有太多要说的了。
通过额外的启动流程来分离逻辑
由于不同流相互混合而导致的间接性,因此该解决方案会涉及更多的内容。 在进行任何解释之前,确实需要阅读此解决方案:
@InitiatingFlow @StartableByRPC class SendMessageWithExtraInitiatingFlowFlow( private val message: MessageState) :
FlowLogic() {
@Suspendable
override fun call(): SignedTransaction {
logger.info( "Started sending message ${message.contents}" )
val spy = serviceHub.identityService.partiesFromName( "Spy" , false ).first()
val tx = verifyAndSign(transaction(spy))
// collect signatures from the signer in a new session
val stx = subFlow(CollectSignaturesInitiatingFlow(tx, listOf(message.recipient)))
// initiate new sessions for all parties
val sessions = listOf(message.recipient, spy).map { initiateFlow(it) }
// tell everyone to save the transaction
return subFlow(FinalityFlow(stx, sessions))
}
private fun transaction(spy: Party) =
TransactionBuilder(notary()).apply {
// the spy is added to the messages participants
val spiedOnMessage = message.copy(participants = message.participants + spy)
addOutputState(spiedOnMessage, MessageContract.CONTRACT_ID)
addCommand(Command(Send(), listOf(message.recipient, message.sender).map(Party::owningKey)))
} } @InitiatedBy (SendMessageWithExtraInitiatingFlowFlow:: class ) class SendMessageWithExtraInitiatingFlowResponder( private val session: FlowSession) : FlowLogic() {
@Suspendable
override fun call(): SignedTransaction {
// save the transaction and nothing else
return subFlow(ReceiveFinalityFlow(otherSideSession = session))
} } @InitiatingFlow class CollectSignaturesInitiatingFlow(
private val transaction: SignedTransaction,
private val signers: List ) : FlowLogic() {
@Suspendable
override fun call(): SignedTransaction {
// create new sessions to signers and trigger the signing responder flow
val sessions = signers.map { initiateFlow(it) }
return subFlow(CollectSignaturesFlow(transaction, sessions))
} } @InitiatedBy (CollectSignaturesInitiatingFlow:: class ) class CollectSignaturesResponder( private val session: FlowSession) : FlowLogic() {
@Suspendable
override fun call(): SignedTransaction {
// sign the transaction and nothing else
return subFlow(object : SignTransactionFlow(session) {
override fun checkTransaction(stx: SignedTransaction) { }
})
} }
上面的代码流如下:
-
spy
(另一方)已添加到该州的participants
列表中 - 通过调用
CollectSignaturesInitiatingFlow
收集签名者的签名 -
CollectSignaturesInitiatingFlow
创建一个新会话并调用CollectSignaturesFlow
-
CollectSignaturesResponder
对CollectSignaturesInitiatingFlow
发送的交易进行CollectSignaturesInitiatingFlow
- 为每个参与者发起更多会话
- 调用
FinalityFlow
会触发链接到原始/顶级流的SendMessageWithExtraInitiatingFlowResponder
上面的代码基于以下事实:使用@InitiatingFlow
注释的任何流@InitiatingFlow
将被路由到其@InitiatedBy
合作伙伴,并且是在新会话中完成的 。 利用此功能,可以添加仅针对必需签名者触发的响应者流程。 仍为顶级流( SendMessageWithExtraInitiatingFlowFlow
)创建会话,并在FinalityFlow
中使用该FinalityFlow
。
幕后还发生了其他一些事情,但是本文不需要这些内容。
哪个更好?
目前很难说。 我将不得不做一些性能测试,然后再处理一些代码……
我目前的看法是, 额外的启动流程效果更好。 它消除了从发起方到每个交易对手的跨网络额外行程的需要。 它添加了一些附加的样板代码,但也从其余的响应者/交易方代码中提取了签名逻辑。
老实说,正如我在一分钟前说的那样,我真的需要使用它并提出更复杂的用例,然后才能给出一个好的答案。 我怀疑我是否会解决这个问题……;
结论
无论您选择哪条路线,或者即使您想出另一条路线,也可以在Corda中100%完成保存交易的交易,其中只有一部分交易方是签名人。 如果这不可能,我将非常失望。 只要你有一个过程以改变响应逻辑流程来处理不同的send
S和receive
s表示签约和非签约各方需要。 你应该很好。
如果您喜欢这篇文章或发现它有帮助(或两者都有),请随时在Twitter上@LankyDanDev关注我,并记住与可能对您有用的任何人分享!
翻译自: https://www.javacodegeeks.com/2019/08/saving-transactions-subset-parties-signers.html
第三方 签名服务