【问题标题】:If a StateRef is consumed how can I locate the Transaction that consumed it?如果 StateRef 被消耗,我如何找到消耗它的事务?
【发布时间】:2020-10-28 03:09:28
【问题描述】:

背景:

已在herehere 提出并回答了这个问题。两个回复都表示无法找到使用当前 API 消耗 State 的 Transaction。

问题:

我正在考虑在 Corda 上构建的任何应用程序都需要用户检查状态的“生命周期”以用于审计/法律目的。

事务对于拼凑审计线索至关重要。如果此功能尚未内置到 API 中,我担心 R3 试图告诉我用户/CorDapps 应该被允许在他们的保管库中搜索交易。例如,我目前大量使用已弃用的CordaRPCOps.internalFindVerifiedTransaction()

是否有特定原因导致我们无法使用搜索状态等丰富的工具来搜索交易?我是否忽略了一些安全问题?如果我无法提取显示哪一方收到资产及其签名的交易,我该如何向律师证明我已经转移了资产?

【问题讨论】:

    标签: corda


    【解决方案1】:
    • 在您引用的第一个问题中,Ashutosh 提供了一个高级解决方案,但发布问题的人没有跟进(不酷也不专业);第二个问题参考 Corda 3;我们现在是 Corda 4.6!这是很久以前的事了。
    • CordaRPSOps 没有特定的 API,并不意味着您无法实现目标。
    • 您可以编写一个流程来执行您的要求,然后使用您的 RPC 代理调用该流程:
    List<SignedTransaction> auditTrail = proxy.startFlowDynamic(AuditTrailFlow.class,
                                                                linearId);
    
    • 在您的流程中,您可以执行以下操作:
    // Criteria to get CONSUMED and UNCONSUMED states.
    QueryCriteria statusAllCriteria = new VaultQueryCriteria(Vault.StateStatus.ALL);
    
    // Criteria to get a certain LinearState by its linearId.
    LinearStateQueryCriteria linearStateCriteria = new LinearStateQueryCriteria()
            .withExternalId(Collections.singletonList(linearId));
    
    // By default Corda returns the results in pages of 200 records, 
    // you can modify that default size; and you must loop through the pages 
    // to get all of the results.
    Vault.Page<YourState> results = getServiceHub().getVaultService()
            .queryBy(YourState.class, statusAllCriteria.and(linearStateCriteria));
    
    // This is a simplified version where I get the first record in the result set; 
    // again, you must loop through all the pages if you have more than 200 records 
    // (it's highly unlikely that your state was updated more than 200 times though).
    SecureHash txHash = results.getStates().get(0).getRef().getTxhash();
    
    // Now you have the transaction that created your state 
    // (i.e. the transaction that consumed a previous version of your state
    //  and created the new version of your state).
    SignedTransaction tx = getServiceHub().getValidatedTransactions()
            .getTransaction(txHash);
    
    • 现在由您来决定您的流程返回什么,例如,它可以返回一个 List&lt;SignedTransaction&gt;,其中包含构成您所在州的审计跟踪的所有交易。

    【讨论】:

    • 阿德尔 cmets 部分太小无法回复,所以我刚刚在您的下方创建了一个答案。非常感谢您的见解。
    【解决方案2】:

    Adel 提供的方法实际上是我创建这个问题的原因。我正在按照他的概述进行操作,并偶然发现它仅在大约 75% 的时间内有效。让我详细说明……

    Adel 是正确的——只要您需要尚未通过 API 提供但可以使用 ServiceHub 的 RPC 客户端功能,您可以创建一个流,使用 @StartableByRPC 对其进行注释并执行必要的步骤返回一个结果。然后只需从您的 RPC 客户端调用该流。我强调了 cordaRPCOps.internalFindVerifiedTransaction() more 的弃用,这表明 R3 正在朝着减少从 RPC 搜索事务的能力而不是更多的能力发展。

    我对 Corda 还是很陌生,但我的州通常遵循一致的“生命周期”。对于令牌尤其如此。让我们讨论每个阶段以及 I/Adel 使用的方法是否有效:

    第 1 阶段 - 创建/发布

    此方法将始终有效,因为交易将产生一个您将成为参与者的输出状态,因此您将其保存在您的保险库中。当您搜索LinearID 时,将找到此状态,您可以交叉引用事务。

    第 2 阶段 - 更新/移动

    此方法可能起作用,具体取决于您的 CordApp 的性质以及您编写流程的方式。例如,TokenSDK 中的 MoveNonFungibleTokens() 流将某些东西从您的保管库移出到其他人的保管库。在这个新状态下,您将不是Participant,因此您的保管库中不会有它的记录。当您在您的保管库中搜索 LinearID 时,它不会返回您可以交叉引用此交易的任何状态。

    我认为,如果您以不同的方式构建流程,则可以避免这种情况。例如,通过在FinalityFlow 中包含StatesToRecord.ALL_VISIBLE,您可以强制存储您不是Participant 的状态。我还没有查看代码,但我认为 R3 选择不使用 MoveNonFungibleTokens() 流。

    第 3 阶段 - 删除/赎回

    这个方法不起作用,因为没有产生输出状态。因此,当您在您的保管库中搜索 LinearID 时,它不会返回任何您可以交叉引用此交易的状态。我想出找到这些交易的唯一方法是:

    1. 获取从QueryCriteria.VaultQueryCriteria 返回的StateMetaData 中的ConsumedTime
    2. 利用这段时间通过TIMESTAMP查询NODE_TRANSACTIONS表并抓取TX_ID
    3. 检查从该查询返回的每个事务的输入,看看它们是否包含我正在寻找的LinearID

    (如果有人有更好的想法,请告诉我)

    我希望我在分析/测试中没有犯一些新手错误。如果 Corda 将消费事务 ID 与其他 MetaData 一起保存,那就太好了。这将使在任何情况下都可以轻松定位交易。

    Adel 非常感谢您花时间回答我的问题。我不会将其标记为已解决,因为我希望您或其他人可以告诉我我忽略了某些东西。如果没有更好的方法,也许 R3 的人会看到并考虑它。

    附录

    下面我包含了一些我用来帮助分析事务的日志记录。如果您刚刚进入 Corda,这是一种很好的可视化方式来查看我正在谈论的一些示例。如果它令人困惑,请忽略它。

    这是一个交易,其中甲方将一个 NonFungibleToken 移动到乙方。请注意,输出状态没有将 PartyA 列为 Participant,这意味着 PartyA 不会存储此状态。所以如果你搜索LinearID 什么都找不到:

       ______________________________________________________________________________________
        SignedTransaction Info from node: PartyA...  
       _____________________________________________________________________________________
        - getID: 8AD8CEE3340EE23BA1751C24D0652253A8ABB2EA19755704C6A8F42466B14A72 (SecureHash)
    
        - Notes: []
    
        - getNotary: O=Notary, L=London, C=GB (Party)
    
        - # of RequiredSigningKeys: 2 (Set<PublicKey>)
            PartyA with PublicKey: DL363eMqRKM8FegtiyEbF1F1zpq4nLp4paaTrKYbMjdKKg (Base58)
            Notary with PublicKey: DL47z5vzXwPzFeMnfHsPiWru7wpWi7c5QmthPw4P8az7Ay (Base58)
    
        - # of MissingSigners: 0 (Set<PublicKey>)
         
       ______________________________________________________________________________________
        WireTransaction Info from node: PartyA...        
       ______________________________________________________________________________________
            - getID: 8AD8CEE3340EE23BA1751C24D0652253A8ABB2EA19755704C6A8F42466B14A72 (SecureHash)
        
            - # of Commands: 1 (List<Command>)
                    MoveTokenCommand
                        # of Signers: 1 List<PublicKey>
                            PartyA with PublicKey: DL363eMqRKM8FegtiyEbF1F1zpq4nLp4paaTrKYbMjdKKg (Base58)
        
            - # of Inputs: 1 (List<StateRef>)
                SecureHash: B4AD7096BDF29628E1D30907DCFF84E68C1AF8344F1781DE7D549B96D112A836 Index: 0
                    TransactionState<NonFungibleToken>
                        getData <ContractState>: TokenType(tokenIdentifier='Ruby-3b1f1ccd-b175-48f1-af0f-e4d82015d31e', fractionDigits=0) issued by PartyA held by PartyA
                            Participants: PartyA with PublicKey: DL363eMqRKM8FegtiyEbF1F1zpq4nLp4paaTrKYbMjdKKg
                        getContract (String): com.r3.corda.lib.tokens.contracts.NonFungibleTokenContract
                        getNotary (Party): O=Notary, L=London, C=GB
                        getConstraint (AttachmentConstraint): Type - SignatureAttachmentConstraint,  PublicKey - DLGqTr8CsXnGGh9d8uve8NWhHsHNm5b6eKzUpNPGqeNVMH (Base58)
                        getEncumbrance (Int, max allowed = 1): null
        
            - # of Outputs: 1 (List<TransactionState<ContractState>>)
                Index 0: 
                    TransactionState<NonFungibleToken>
                        getData <ContractState>: TokenType(tokenIdentifier='Ruby-3b1f1ccd-b175-48f1-af0f-e4d82015d31e', fractionDigits=0) issued by PartyA held by PartyB
                            Participants: PartyB with PublicKey: DL5nGmwTZ6jF6FrpNRZ4hbDAW48ut8DxjJ8YZTQw4xY339    !!! PartyA is not listed !!!
                        getContract (String): com.r3.corda.lib.tokens.contracts.NonFungibleTokenContract
                        getNotary (Party): O=Notary, L=London, C=GB
                        getConstraint (AttachmentConstraint): Type - SignatureAttachmentConstraint,  PublicKey - DLGqTr8CsXnGGh9d8uve8NWhHsHNm5b6eKzUpNPGqeNVMH (Base58)
                        getEncumbrance (Int, max allowed = 1): null
        
            - # of Attachments: 1 (List<SecureHash>)
                SecureHash: 34705DC9A2C8599998BBDBA7C3D13609AF04D8B3A772F7134D685ECC926D8320 (CordaRPC successfully located Attachment in storage)
        
            - getTimeWindow: null (TimeWindow)
    

    这里是删除LinearState 的示例(兑换令牌看起来相同)。请注意,没有输出,因此如果您搜索LinearID,将找不到任何状态,从而无法交叉引用此交易:

         __________________________________________________________________________________
          SignedTransaction Info from node: PartyA...        
         __________________________________________________________________________________
            - getID: 4D09C6CBACD104821BEB3095E90C350FE4EA690FD7F8CEA24B71E847EEE10684 (SecureHash)
        
            - Notes: []
        
            - getNotary: O=Notary, L=London, C=GB (Party)
        
            - # of RequiredSigningKeys: 2 (Set<PublicKey>)
                PartyA with PublicKey: DL363eMqRKM8FegtiyEbF1F1zpq4nLp4paaTrKYbMjdKKg (Base58)
                Notary with PublicKey: DL47z5vzXwPzFeMnfHsPiWru7wpWi7c5QmthPw4P8az7Ay (Base58)
        
            - # of MissingSigners: 0 (Set<PublicKey>)
        
             
       ______________________________________________________________________________________
        WireTransaction Info from node: PartyA...        
       ______________________________________________________________________________________
            - getID: 4D09C6CBACD104821BEB3095E90C350FE4EA690FD7F8CEA24B71E847EEE10684 (SecureHash)
        
            - # of Commands: 1 (List<Command>)
                    Delete
                        # of Signers: 1 List<PublicKey>
                            PartyA with PublicKey: DL363eMqRKM8FegtiyEbF1F1zpq4nLp4paaTrKYbMjdKKg (Base58)
        
            - # of Inputs: 1 (List<StateRef>)
                SecureHash: A45446E1764AB7055B348BC6AAB5E9332BCCFEF21E157A1B28360CC986CDD459 Index: 0
                    TransactionState<MyLinearState>
                        getData <ContractState>: linearID: 73fac293-2c89-42fa-8572-7d31e0de567f ExternalID: LinearSate-123 administrator: O=PartyA, L=London, C=GB badPeople: []
                            Participants: PartyA with PublicKey: DL363eMqRKM8FegtiyEbF1F1zpq4nLp4paaTrKYbMjdKKg
                        getContract (String): com.template.contracts.MyLinearStateContract
                        getNotary (Party): O=Notary, L=London, C=GB
                        getConstraint (AttachmentConstraint): Type - SignatureAttachmentConstraint,  PublicKey - DLCTymWiNxvGWE117QUKSjeW71yaaGc5tNL2i8Zp8dFUvE (Base58)
                        getEncumbrance (Int, max allowed = 1): null
        
            - # of Outputs: 0 (List<TransactionState<ContractState>>)   !!! No Outputs !!!
        
            - # of Attachments: 1 (List<SecureHash>)
                SecureHash: DB05B1A5B285C73C3C48F81E8D07B8DDF51E7EE9852219D4D65EE934F9524527 (CordaRPC successfully located Attachment in storage)
        
            - getTimeWindow: 22-Oct-2020 15:24:40 to 22-Oct-2020 15:24:50 (TimeWindow)
    

    【讨论】:

    • 当响应者流调用SignTransactionFlow时,该流将隐式调用ReceiveTransactionFlow,这将解决所有事务依赖关系并验证它们;这意味着您将获得整个图表,Corda 将证明您收到的内容是正确的。因此,如果 A 创建了一个状态,则将其移至 B,然后 B 移至 C,然后 C 将其移至 D;当 D 调用 SignTransactionFlow 时,它将接收所有导致创建它接收到的状态的事务(即被移动到它)。
    • 默认ReceiveTransactionFlow不会存储那些接收到的依赖;你必须做一些改变(见here)。所以我认为你需要编写一个调用ReceiveTransactionFlow 并存储整个事务图的流程;然后你可以回到我之前的解决方案,循环遍历状态的演变并提取创造这种演变的交易,因为你的保险库应该有所有的信息。
    【解决方案3】:

    我只是想记录下我的决定:

    如果我从头开始构建自己的 Flows,我可以在 FinalityFlow 中使用 StatesToRecord.ALL_VISIBLE 强制存储所有状态,然后使用 Adel 概述的内容将状态交叉引用到事务。这将涵盖创建/发布和更改/移动交易。由于缺少输出状态,它不适用于删除/赎回交易。

    如果我使用像 TokenSDK 中这样的预构建流程,那就更难了。如果开发者没有添加参数来设置 StatesToRecord 可能没有解决方案。

    TokenSDK documentation 表示,如果某人是观察者,他们的记录策略将是 StatesToRecord.ALL_VISIBLE。我确实对此进行了测试,除非您是执行移动/问题的Party,否则它可以工作。看来你作为观察者被过滤掉了。

    为了解决这个问题,我改用内联版本,即MoveNonFungibleTokensFlow/MoveFungibleTokensHandler。虽然这些流没有StatesToRecord 的参数,但我发现在执行移动后,我可以使用StatesToRecord.ALL_VISIBLE 调用FinalityFlow/ReceiveFinalityFlow...

    移动节点:

    SignedTransaction signedTransactionMove = subFlow(new MoveNonFungibleTokensFlow(partyAndToken, participantSessions, observerSessions, queryCriteria));
    return subFlow(new FinalityFlow(signedTransactionMove, participantSessions, StatesToRecord.ALL_VISIBLE)); 
    

    接收节点:

    subFlow(new MoveFungibleTokensHandler(otherSideSession));
    subFlow(new ReceiveFinalityFlow(otherSideSession, null, StatesToRecord.ALL_VISIBLE));
    

    我真的很惊讶这没有导致错误。我以为公证人会抱怨,但事实并非如此。这确保了移动器将保存移动状态(通常它们不会因为它们不是Participant)。然后,您可以使用 Adel 概述的解决方案。

    您也可以在发行时这样做(即,当您直接向另一方发行时,您将不是该状态的参与者,因此不会保存该状态)...

    发行者节点:

    SignedTransaction signedTransactionIssueToken = subFlow(new IssueTokensFlow(nonFungibleToken, participantSessions, observerSessions));
    return subFlow(new FinalityFlow(signedTransactionIssueToken, participantSessions, StatesToRecord.ALL_VISIBLE));
    

    接收节点:

    subFlow(new IssueTokensFlowHandler(otherSideSession));
    subFlow(new ReceiveFinalityFlow(otherSideSession, null, StatesToRecord.ALL_VISIBLE));
    

    对于没有输出的“退出”类型事务,即RedeemNonFungibleTokens(),我没有解决方案,因为没有可以使用StatesToRecord.ALL_VISIBLE 保存的输出状态。如果您在交易时没有将 TransactionID 保存在 Corda 之外,则无法使用 API 找到该交易。

    理想情况下,R3 在记录CONSUMED_TIMESTAMP 时会将消费事务 ID 保存在VAULT_STATES 表中。然后它将很容易在保险库查询的元数据中获得。我正在考虑将此作为请求。

    【讨论】:

      猜你喜欢
      • 2012-03-02
      • 1970-01-01
      • 2012-06-08
      • 2012-10-08
      • 1970-01-01
      • 1970-01-01
      • 2013-09-11
      • 2022-10-04
      • 1970-01-01
      相关资源
      最近更新 更多