Corda如何支持状态间一对多父子关系?实现疑问与需求
Great question! Your current approach of storing the parent state's LinearId in each child state is actually a strong foundation—let's expand on it to fully satisfy your requirements for lifecycle synchronization and data consistency. Below is a step-by-step implementation tailored to your procurement contract example:
1. 规范状态结构设计
First, define your parent and child states to separate shared vs. unique data, and explicitly link them via the parent's LinearId:
// 枚举:父合同状态 enum class ContractStatus { ACTIVE, CANCELLED, SETTLED } // 枚举:子交付状态 enum class DeliveryStatus { PENDING, SHIPPED, SETTLED } // 父状态:采购合同(存储通用信息) data class PurchaseContractState( val buyer: Party, val seller: Party, val contractTerms: String, override val linearId: UniqueIdentifier, val status: ContractStatus = ContractStatus.ACTIVE ) : LinearState, ContractState { override val participants: List<Party> get() = listOf(buyer, seller) } // 子状态:商品交付(存储专属信息,关联父合同) data class GoodsDeliveryState( val parentLinearId: UniqueIdentifier, // 核心关联字段:父合同的LinearId val goodsId: String, val deliveryLocation: String, val currentHolder: Party, override val linearId: UniqueIdentifier, val status: DeliveryStatus = DeliveryStatus.PENDING ) : LinearState, ContractState { override val participants: List<Party> get() = listOf(currentHolder, seller, buyer) }
2. 实现父子状态的生命周期联动
(1)父状态操作批量联动子状态
When you cancel or terminate the parent contract, you need to update all associated active child states. This logic lives in the parent's flow:
@InitiatingFlow @StartableByRPC class CancelPurchaseContractFlow(private val parentLinearId: UniqueIdentifier) : FlowLogic<Unit>() { override fun call() { // 1. 校验父合同当前状态为可取消 val parentStateRef = serviceHub.vaultService.queryBy<PurchaseContractState>() .states.single { it.state.data.linearId == parentLinearId && it.state.data.status == ContractStatus.ACTIVE } // 2. 查询所有关联的未完成子状态 val childStateRefs = serviceHub.vaultService.queryBy<GoodsDeliveryState>( QueryCriteria.VaultCustomQueryCriteria( builder { GoodsDeliveryState::parentLinearId.equal(parentLinearId.id) } ) ).states.filter { it.state.data.status != DeliveryStatus.SETTLED } // 3. 构建交易:更新父状态为取消,同时批量更新子状态 val txBuilder = TransactionBuilder(parentStateRef.state.notary) .addInputState(parentStateRef) .addOutputState(parentStateRef.state.data.copy(status = ContractStatus.CANCELLED), PurchaseContract.ID) .addCommand(PurchaseContract.Commands.Cancel(), ourIdentity.owningKey) // 批量添加子状态的输入输出 childStateRefs.forEach { ref -> txBuilder.addInputState(ref) txBuilder.addOutputState( ref.state.data.copy(status = DeliveryStatus.CANCELLED), GoodsDeliveryContract.ID ) } txBuilder.addCommand(GoodsDeliveryContract.Commands.Cancel(), ourIdentity.owningKey) // 完成签名与提交 val signedTx = serviceHub.signInitialTransaction(txBuilder) val sessions = (parentStateRef.state.data.participants + childStateRefs.flatMap { it.state.data.participants }) .minus(ourIdentity) .map { initiateFlow(it) } val fullySignedTx = subFlow(CollectSignaturesFlow(signedTx, sessions)) subFlow(FinalityFlow(fullySignedTx, sessions)) } }
(2)子状态更新触发父状态自动收尾
Whenever a child state reaches SETTLED, check if all children are done—if yes, update the parent state to SETTLED:
@InitiatingFlow @StartableByRPC class MarkGoodsAsSettledFlow(private val childLinearId: UniqueIdentifier) : FlowLogic<Unit>() { override fun call() { // 1. 获取当前子状态并校验可更新 val childStateRef = serviceHub.vaultService.queryBy<GoodsDeliveryState>() .states.single { it.state.data.linearId == childLinearId && it.state.data.status == DeliveryStatus.SHIPPED } val parentLinearId = childStateRef.state.data.parentLinearId val updatedChild = childStateRef.state.data.copy(status = DeliveryStatus.SETTLED) // 2. 检查所有关联子状态是否已全部完成 val allChildStates = serviceHub.vaultService.queryBy<GoodsDeliveryState>( QueryCriteria.VaultCustomQueryCriteria( builder { GoodsDeliveryState::parentLinearId.equal(parentLinearId.id) } ) ).states val allSettled = allChildStates.all { it.state.data.status == DeliveryStatus.SETTLED } // 3. 构建交易:更新子状态,按需更新父状态 val txBuilder = TransactionBuilder(childStateRef.state.notary) .addInputState(childStateRef) .addOutputState(updatedChild, GoodsDeliveryContract.ID) .addCommand(GoodsDeliveryContract.Commands.MarkAsSettled(), ourIdentity.owningKey) if (allSettled) { val parentStateRef = serviceHub.vaultService.queryBy<PurchaseContractState>() .states.single { it.state.data.linearId == parentLinearId && it.state.data.status == ContractStatus.ACTIVE } txBuilder.addInputState(parentStateRef) .addOutputState(parentStateRef.state.data.copy(status = ContractStatus.SETTLED), PurchaseContract.ID) .addCommand(PurchaseContract.Commands.MarkAsSettled(), ourIdentity.owningKey) } // 完成签名与提交 val signedTx = serviceHub.signInitialTransaction(txBuilder) val sessions = (updatedChild.participants + (if (allSettled) parentStateRef.state.data.participants else emptyList())) .minus(ourIdentity) .map { initiateFlow(it) } val fullySignedTx = subFlow(CollectSignaturesFlow(signedTx, sessions)) subFlow(FinalityFlow(fullySignedTx, sessions)) } }
3. 合约层面的一致性校验
Add constraints in your contracts to prevent invalid state transitions:
class PurchaseContract : Contract { companion object { const val ID = "com.example.contracts.PurchaseContract" } interface Commands : CommandData { class Create : TypeOnlyCommandData(), Commands class Cancel : TypeOnlyCommandData(), Commands class MarkAsSettled : TypeOnlyCommandData(), Commands } override fun verify(tx: LedgerTransaction) { val command = tx.commands.requireSingleCommand<Commands>() when (command.value) { is Commands.Cancel -> { val inputParent = tx.inputsOfType<PurchaseContractState>().single() val outputParent = tx.outputsOfType<PurchaseContractState>().single() require(inputParent.status == ContractStatus.ACTIVE) { "Only active contracts can be cancelled" } require(outputParent.status == ContractStatus.CANCELLED) { "Contract must be marked as cancelled" } // 校验所有关联子状态都被同步取消 val inputChildren = tx.inputsOfType<GoodsDeliveryState>() val outputChildren = tx.outputsOfType<GoodsDeliveryState>() require(inputChildren.all { it.parentLinearId == inputParent.linearId }) { "All child states must belong to the parent contract" } require(outputChildren.all { it.status == DeliveryStatus.CANCELLED }) { "All child states must be cancelled" } } // 其他命令的校验逻辑... } } }
4. 优化建议
- Use
VaultCustomQueryCriteria(as shown above) instead of filtering in-memory for better query performance. - For large-scale scenarios, consider using a
SchedulableFlowto periodically check parent state statuses, reducing reliance on child flow triggers. - Extract common parent-child synchronization logic into a utility class to avoid code duplication across flows.
内容的提问来源于stack exchange,提问作者Rickky13

