Corda非常灵活,可以让您组合编写许多复杂工作流程所需的代码。 这种灵活性确实具有一个缺点。 您(开发人员)需要花一些时间和思想来设计应用程序。 没有更多的空白合同。 没有更多的响应者流可以对收到的任何内容进行签名。 当您的应用程序在生产中无缝运行时,您将为您付出的努力感到高兴。 幸运的是,我在这里是为了保护您的保险柜免遭狡猾的交易和州的无效开支。
在我以前的帖子中, 向外部组织广播事务 ,我向您展示了如何与网络中的任何人共享事务及其内部的状态。 为国家以未曾预见的方式花费潜在的途径。 可能导致CorDapp定义的业务流程崩溃。
我为您提供了解决这种情况的知识,现在我正试图防止您犯下愚蠢的,可能是重大的错误。 换句话说,我给了您汽车的钥匙,现在我正试图防止您撞车或撞人。
甚至,
拥有权利的同时也被赋予了重大的责任。
在本文中,我将介绍您应添加到合同和流程中的检查种类,以防止其他节点从应用程序中的潜在监督中获得好处。 更确切地说,防止从广播交易中收到的状态以未预期的方式花费。 进行这些更改后,状态只能由应用程序完全打算允许的各方使用。
我将使用我在所有Corda帖子中都包含的MessageState
( LinearState
)。 为了清楚起见,在下面添加了:
@BelongsToContract (MessageContract:: class ) data class MessageState(
val sender: Party,
val recipient: Party,
val contents: String,
override val linearId: UniqueIdentifier,
override val participants: List<Party> = listOf(sender, recipient) ) : LinearState
以这种状态为例,我可以帮助您考虑自己的合同的设计以及流程中的验证。 我们将要研究的工作流程是答复两个节点之间发送的消息的过程。
合同验证
我希望你已经知道这一点。 合同验证很重要。 这是由交易的每个签名者以及以后需要验证同一笔交易的任何人运行的验证。 国家只能以其合同设计所允许的方式发展。 谁可以创建它们,谁可以花费它们,它们可以拥有什么价值,每笔交易有多少价值,等等。 定义这些规则由您决定。
以下是一些我认为对确保合同在州上设置必要界限至关重要的一般要求。 沿着可能的路线引导状态创建和转换:
override fun verify(tx: LedgerTransaction) {
val commandWithParties: CommandWithParties<Commands> = tx.commands.requireSingleCommand()
when (commandWithParties.value) {
is Commands.Send -> // validation for sending
is Commands.Reply -> requireThat {
val inputPublicKeys = tx.inputs.flatMap { it.state.data.participants.map(AbstractParty::owningKey) }.toSet()
using commandWithParties.signers.containsAll(inputPublicKeys) "The input participant keys are a subset of the signing keys"
val outputPublicKeys = tx.outputStates.flatMap { it.participants.map(AbstractParty::owningKey) }.toSet()
using commandWithParties.signers.containsAll(outputPublicKeys) "The output participant keys are a subset of the signing keys"
}
} }
尽管此验证是针对LinearState
,但实际上我还是从ContractsDSL.verifyMoveCommand
LinearState
了规则。 也许这表明我的MessageState
实际上是一个OwnableState
(一个拥有单个所有者的状态),但是让我们在“#55357;&#56904;&#55357;&#56906;”下进行梳理。
上面代码段中的规则检查交易是否将由输入和输出状态的所有参与者签名。 确保所有相关方都有机会验证交易。 换句话说,该流必须在命令的必需签名者中包括输入和输出状态所提及的各方,以及为向其发送事务而打开的会话。
由于这些规则,如果没有输入和输出状态的参与者对交易进行签名,就无法将交易提交给任何人的保险库。 在合同看来,这样做是非法的。 让我将其改写为更易于理解的解释。 如果不将状态通知给原始参与者,则无法创建任何消耗MessageState
的事务。 此外,如果没有他们的发言,就无法创建答复并将其存储在一方的保险库中。
这些规则是建立您自己的合同的有效依据。 明确说明哪些当事人必须签署交易是证明其有效性的重要要求。 可以根据需要添加其他规则。
例如,可以将以下检查添加到上面的示例中:
override fun verify(tx: LedgerTransaction) {
val commandWithParties: CommandWithParties<Commands> = tx.commands.requireSingleCommand()
val command = commandWithParties.value
when (command) {
is Commands.Send -> // validation for sending
is Commands.Reply -> requireThat {
// general requirements from previous snippet
// precise requirements for `MessageState`
"One input should be consumed when replying to a message." using (tx.inputs.size == 1 )
"Only one output state should be created when replying to a message." using (tx.outputs.size == 1 )
val output = tx.outputsOfType<MessageState>().single()
val input = tx.inputsOfType<MessageState>().single()
using (output.sender == input.recipient) "Only the original message's recipient can reply to the message"
using (output.recipient == input.sender) "The reply must be sent to the original sender"
// covered by the general requirements
using commandWithParties.signers.contains(input.sender.owningKey) "The original sender must be included in the required signers" using commandWithParties.signers.contains(input.sender.owningKey) "The original sender must be included in the required signers"
using commandWithParties.signers.contains(input.recipient.owningKey) "The original recipient must be included in the required signers"
}
} }
这些规则特定于MessageState
的用例。 限制可以回复邮件的各方。 更准确地说,答复的发送者必须是原始消息的接收者,并且只能发送回原始发送者。 在这种情况下,这些规则是有意义的。
即使有了这些规则,狡猾的交易仍然可以进行。 合同根据交易的状态以及状态的内容检查签名。 但是,仍然有一些解决方法。
话虽如此,其中某些情况只有在交易已发送给最初不参与交易的各方的情况下才会发生。 正如我在引言和向外部组织广播交易中提到的那样,广播交易会产生这种情况。
现在我已经做好了准备,现在让我解释一下上面合同中的一个。 没有什么可以阻止第三方创建对原始消息的模仿答复。 这样,回复的发送者和接收者是有效的,但是消息是由实际上不是发送者的其他人创建的。 有点麻烦,所以您可能需要阅读该句子几次。
为了防止这种情况发生,需要在创建此事务的流程中添加一些逻辑。 这将是下一节的主题。
在继续之前,我想再次强调一下,这种情况只有在原始交易已与其他方共享的情况下才会发生。 因此,这可能是永远不会发生的情况。 但是可以。 在这些情况下覆盖基础的确取决于您的用例以及您对用户调皮的偏执程度。
流程验证
流程为您提供了一个空间,可以添加合同之外的交易验证。 这些规则可以是特定于单个组织的规则(我在“ 扩展流程”中已进行了介绍,以自定义交易验证 ),或者依赖于合同不可用的信息。
在上一节中,我提到了合同验证中的一些。 下面的响应者流程添加了两个约束来修补它们:
@InitiatedBy (ReplyToMessageFlow:: class ) class ReplyToMessageResponder( private val session: FlowSession) : FlowLogic<SignedTransaction>() {
@Suspendable
override fun call(): SignedTransaction {
val stx = subFlow(object : SignTransactionFlow(session) {
override fun checkTransaction(stx: SignedTransaction) {
val message = stx.coreTransaction.outputsOfType<MessageState>().single()
require(message.sender != ourIdentity) {
"The sender of the new message cannot have my identity when I am not the creator of the transaction"
}
require(message.sender == session.counterparty) {
"The sender of the reply must must be the party creating this transaction"
}
}
})
return subFlow(
ReceiveFinalityFlow(
otherSideSession = session,
expectedTxId = stx.id
)
)
} }
此示例中的require
块在合同内部是不可能的,因为它们需要合同不包含的信息。 这种额外的环境至关重要。 合同只能根据所提供的信息进行验证。 另一方面,该流程知道它在提议的交易的接收端,谁是发送交易的对手。
使用此知识,流程可以添加两个新规则。
- 答复的发送者不能具有流的身份。 防止另一方冒充其身份并试图代表他们发送回复。
- 答复的发送者必须是交易对手会话的所有者。 防止另一方模仿发送者的身份。
尽管第二条规则是第一条规则的超集,但将它们分开可以提供更好的错误消息。 如果消息对您不重要,则可以删除第一个require
语句。
得益于合同,交易必须先收集此方的签名,然后才能继续。 为他们的响应者流程提供机会以添加其他验证,并在需要时拒绝它。 通过合同和流程的团队合作,可以大大减少非法使用流程的机会。
结论
由于Corda的灵活性,作为CorDapp开发人员,您有责任充分限制您的应用程序以防止无效使用。 与原始交互中不涉及的节点共享事务的能力使这一点更加复杂。 如果未将CorDapp妥善组合在一起,则交易的州可能会由对该州没有直接所有权的一方使用。 在合同和流程中找到的验证对于防止发生这些情况至关重要。
但是,重要的是要记住,它确实取决于您的用例。 也许您想允许各方从与他们共享的交易中花费他们不拥有的状态。 在这种情况下,您可以放宽CorDapp的限制。 只要是设计使然,而不是疏忽大意。
翻译自: https://www.javacodegeeks.com/2019/07/preventing-invalid-spending-broadcasted-states.html