You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Corda如何支持状态间一对多父子关系?实现疑问与需求

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 SchedulableFlow to 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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.15 08:31:42