Corda中的附件不仅仅是随事务一起发送的PDF。 实际上,可以在运行流程时甚至在合同的verify
功能内部以编程方式使用它们。 您为什么要这么做? when考虑时,答案很有意义。 让我们以包含CSV数据的附件为例。 实际上,这就是这篇文章的主题。 无论如何。 附件可以包含允许状态具有的所有有效ID(或其他任何信息)。 现在,可以在代码中完成此操作,但是对于需要随时间变化的系统而言,这是不切实际的。 也许需要添加新的ID。 如果它们位于代码内,则只要允许新值,就需要编译并分发更新的CorDapp。 不完全实用。 但是,上载包含数据的文件的新版本可以使验证随时间变化,而无需重新编译。 那是一个很合理的想法。
现在我们同意根据附件数据验证事务的内容是一个好主意。 下一个问题是进行验证的最佳位置在哪里。 如前所述,有两种选择。 在流程内部还是在合同中。 在这里将其放入合同中最有意义,因为接收交易的所有各方都必须运行附件的验证。 保证他们都对交易的有效性达成共识🤝。
知道您可以使用附件来验证合同是一件很重要的事情,但是在实现之前,仍有一些代码需要编写。 不过不要担心,我得到了你。
与附件建立交易
保持简短简短,因为可以在docs中找到有关此内容的更多信息。 以下是来自流程的一些代码,该流程构建包含附件的事务(假定附件已经存在):
private fun transaction(): TransactionBuilder =
TransactionBuilder(notary()).apply {
val attachmentId = attachment()
addOutputState(message)
addCommand(Command(Send(attachmentId), message.participants.map(Party::owningKey)))
addAttachment(attachmentId)
}
private fun attachment(): AttachmentId {
return serviceHub.attachments.queryAttachments(
AttachmentQueryCriteria.AttachmentsQueryCriteria(
filenameCondition = Builder.equal(
attachmentName
)
)
).first()
}
将附件添加到事务中不包含任何特殊代码。 检索附件要复杂得多,但也不难将它们组合在一起。 在此示例中,按名称查询AttachmentId
,然后将返回的AttachmentId
传递到TransactionBuilder
的addAttachment
(它使用AttachmentId
而不是附件本身)。 我倾向于使用附件的名称,但是也可以传入AttachmentId
,前提是您事先知道。
您可能还没有捕获到另一段偷偷摸摸的代码。 我将AttachmentId
传递Send
命令。 这样做可以使附件的哈希值已知,并使以后从事务中检索它变得容易得多。 我将快速向您展示我是如何做到的:
class MessageContract : Contract {
interface Commands : CommandData {
class Send(attachmentId: AttachmentId) : CommandWithAttachmentId(attachmentId), Commands
}
abstract class CommandWithAttachmentId(val attachmentId: AttachmentId) : CommandData {
override fun equals(other: Any?) = other?.javaClass == javaClass
override fun hashCode() = javaClass.name.hashCode()
}
}
确认合同
这就是魔法🧙♀️发生的地方。 通过使用先前添加到事务中的附件,可以提取其中的数据并将其用于验证事务中的状态。 以下是合同的verify
功能:
override fun verify(tx: LedgerTransaction) {
val command = tx.commands.requireSingleCommand<Commands>()
when (command.value) {
is Commands.Send -> requireThat {
"No inputs should be consumed when sending a message." using (tx.inputs.isEmpty())
"Only one output state should be created when sending a message." using (tx.outputs.size == 1)
}
is Commands.Reply -> requireThat {
"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)
}
}
require(isMessageInCsv(tx)) {
"The output message must be contained within the csv of valid messages. " +
"See attachment with hash = ${tx.attachments.first().id} for its contents"
}
}
private fun isMessageInCsv(tx: LedgerTransaction): Boolean {
val message = tx.outputsOfType<MessageState>().first()
val attachmentId = tx.commandsOfType<CommandWithAttachmentId>().single().value.attachmentId
return tx.getAttachment(attachmentId).openAsJAR().use { zipInputStream: JarInputStream ->
zipInputStream.nextJarEntry.name
val csv = CSVFormat.DEFAULT.withHeader("valid_messages")
.withFirstRecordAsHeader()
.parse(InputStreamReader(zipInputStream))
csv.records.any { row -> row.get("valid messages") == message.contents }
}
}
在此示例中,有趣的内容位于isMessageInCsv
函数内。 它可能看起来有些令人生畏,但也许我只需要整理一下即可……忘了我说的😉。 它只需要一点解释(或者也许是一些代码注释)。
附件可以通过事务的commands
属性或getAttachment
函数(采用position或AttachmentId
/ SecureHash
)从事务中检索。 在此示例中,包含CSV数据的附件是使用getAttachment
以及先前传递到命令中的ID来检索的。
至此,附件已被检索到。 现在,需要解析其中的CSV数据并将其与交易状态进行比较。 为了简化这一过程,我使用了Apache Commons CSV 。 使用openAsJAR
打开附件(附件存储为zip),并利用该库读取其中的数据。 读取每一行时,它将检查MessageState
的contents
是否与当前行匹配。 如果有的话,那就太好了。 该州通过了测试,该合同被视为有效。 如果不匹配,将输出以下错误,您将需要再次尝试。
net.corda.core.contracts.TransactionVerificationException$ContractRejection: Contract verification failed:
The output message must be contained within the csv of valid messages. See attachment with hash = 3E3031BA98F3F01843E8FD0A1B34E21C599C9C8F09765C2F820E45D6E8770948 for its contents,
contract: com.lankydanblog.tutorial.contracts.MessageContract, transaction: 5C8963497E493684A78C0A95A30E4C30029E6C36A792DE8A1B1733DE5358BB15
该代码确实假定CSV的内容遵循某种格式。 换句话说, "valid messages"
被硬编码为文件中的标头。 如果不存在,那么验证将变得毫无意义。 如果发生这种情况,它将失败,这实际上是一件好事,但是如果您还记得我在引言中所说的话。 这引入了更多的硬编码,并降低了合同的灵活性。 如果您想改善这一点,则可以将命令中要使用的行的名称传递给命令,就像我之前在其中添加AttachmentId
一样。
包起来
如您所见,使用CSV附件中的数据验证交易不需要太多工作。 实际上,我敢打赌,如果您想自己实现这一点,您的代码将与我的代码几乎相同。 我的意思是,只有这么多种方法可以做到这一点。 通过委托给图书馆,大部分工作将为您完成。
要总结这些步骤,需要使用一些CSV数据验证交易:
- 将CSV上载到节点
- 在流内部,检索它并将其添加到事务中
- 在合同内部,从交易中获取CSV,最后将交易内容与其进行比较。
这样做将使您的验证可以随时间变化,而无需重新编译CorDapp。 听起来不错吧? 我认同。 如果您不这样做,则应该这样做,因为它绝对很棒😎。
这篇文章中使用的代码可以在我的GitHub上找到 。
翻译自: https://www.javacodegeeks.com/2019/08/verifying-a-contract-with-csv-data.html