在Corda中如何追踪LinearState资产历史状态及供应链历任所有者?
Great question! Tracking LinearState history is super common in Corda, especially for supply chain scenarios where full asset lineage matters. Let's break this down for both your questions.
1. How to Track the Past Lifecycle States of a LinearState Asset?
LinearStates in Corda are built to represent evolving assets—every update creates a new version of the state while keeping the same linearId (the unique identifier for the asset). Here's how you can pull up all its past states:
- Use Vault Historical Queries: The Corda Vault stores every version of a LinearState, both consumed (past versions) and unconsumed (current active version). To retrieve them, build a
VaultQueryCriteriathat includes all state statuses, not just active ones. - Example Query Code:
val targetLinearId = UniqueIdentifier.fromString("your-asset-unique-id") val historyCriteria = VaultQueryCriteria( linearId = listOf(targetLinearId), status = Vault.StateStatus.ALL ) val allStateVersions = serviceHub.vaultService.queryBy<YourLinearStateType>(historyCriteria).states // Loop through versions to inspect lifecycle changes allStateVersions.forEach { stateAndRef -> val stateDetails = stateAndRef.state.data val versionIndex = stateAndRef.ref.index println("Version $versionIndex: ${stateDetails.toString()}") } - Understand State Versions: Each time the asset is updated (transferred, modified, etc.), a new state is recorded with an incremented
indexin theStateRef. The unconsumed state is the latest active version, while consumed states are the historical ones.
2. How to Track All Previous Owners of a LinearState in a Supply Chain Scenario?
For supply chain use cases, you’ll want to map each state version to its owner and compile a full, chronological lineage. Here’s a step-by-step implementation:
Step 1: Define Your LinearState with Owner Metadata
First, make sure your state includes an owner field (plus any other supply chain-specific data you need):
data class SupplyChainAssetState( val assetSerialNumber: String, val assetType: String, val owner: Party, override val linearId: UniqueIdentifier = UniqueIdentifier() ) : LinearState, ContractState { override val participants: List<Party> get() = listOf(owner) }
Step 2: Build Transfer Flows to Create New State Versions
When the asset changes hands, your flow should create a new state version with the new owner, while marking the old state as consumed. Example flow snippet:
@InitiatingFlow @StartableByRPC class TransferSupplyChainAssetFlow( val assetLinearId: UniqueIdentifier, val newOwner: Party ) : FlowLogic<Unit>() { override fun call() { // Fetch the latest active state val activeStateCriteria = VaultQueryCriteria(linearId = listOf(assetLinearId)) val currentStateAndRef = serviceHub.vaultService.queryBy<SupplyChainAssetState>(activeStateCriteria).states.single() val currentState = currentStateAndRef.state.data // Create new state with updated owner val newState = currentState.copy(owner = newOwner) // Build and execute the transaction val txBuilder = TransactionBuilder(notary = currentState.participants.first()) .addInputState(currentStateAndRef) .addOutputState(newState, SupplyChainContract.ID) .addCommand(SupplyChainContract.Commands.Transfer(), ourIdentity.owningKey) txBuilder.verify(serviceHub) val signedTx = serviceHub.signInitialTransaction(txBuilder) val newOwnerSession = initiateFlow(newOwner) val fullySignedTx = subFlow(CollectSignaturesFlow(signedTx, listOf(newOwnerSession))) subFlow(FinalityFlow(fullySignedTx, listOf(newOwnerSession))) } }
Step 3: Query and Compile the Owner History
To pull all previous owners, query all state versions and extract the owner field from each:
fun getAssetOwnerHistory(linearId: UniqueIdentifier): List<Pair<Party, Int>> { val historyCriteria = VaultQueryCriteria( linearId = listOf(linearId), status = Vault.StateStatus.ALL ) val allStateVersions = serviceHub.vaultService.queryBy<SupplyChainAssetState>(historyCriteria).states // Sort by version index (oldest to newest) and map to owner + version return allStateVersions.sortedBy { it.ref.index } .map { stateAndRef -> val state = stateAndRef.state.data val version = stateAndRef.ref.index Pair(state.owner, version) } }
Step 4: Expose History via RPC (Optional)
If you need external systems or users to query the owner history, add an RPC endpoint to your node:
@RpcService class SupplyChainRpcService(val serviceHub: ServiceHub) : RPCServerPluginInterface { @RPCReturnsObservables @RpcFunction fun fetchAssetOwnerHistory(linearId: String): List<Map<String, Any>> { val targetId = UniqueIdentifier.fromString(linearId) val ownerHistory = getAssetOwnerHistory(targetId) return ownerHistory.map { (owner, version) -> mapOf( "version" to version, "ownerOrganization" to owner.name.organisation, "ownerLegalIdentifier" to owner.name.toString() ) } } }
Key Notes:
- State Immutability: Corda states are immutable—you never edit an existing state, only create new versions. This guarantees a tamper-proof history.
- Chronological Order: Always sort by
StateRef.indexto get the correct timeline (index 0 is the initial state, index 1 is the first update, etc.). - Rich Metadata: For deeper supply chain visibility, add fields like
transferTimestamp,transferInitiator, ortransferReasonto your state to capture more context about each lifecycle change.
内容的提问来源于stack exchange,提问作者geekybot

