在Cordapp中使用多状态类的实现问题及示例请求
Absolutely! Having multiple distinct state types in a single Corda application is totally feasible—it’s a standard approach for modeling diverse business assets or workflows within one CorDapp. Let’s walk through a complete implementation example, including fixes for common ClientRPC pitfalls you might be hitting.
Step 1: Define Your Two State Types
First, create two separate ContractState implementations, each representing your different business entities. For example, let’s model an InvoiceState and a PaymentState:
// InvoiceState.kt @BelongsToContract(InvoiceContract::class) data class InvoiceState( val amount: Amount<Currency>, val issuer: Party, val recipient: Party, override val participants: List<Party> = listOf(issuer, recipient) ) : ContractState // PaymentState.kt @BelongsToContract(PaymentContract::class) data class PaymentState( val invoiceId: UniqueIdentifier, val paidAmount: Amount<Currency>, val payer: Party, val payee: Party, override val participants: List<Party> = listOf(payer, payee) ) : ContractState
Step 2: Build Contracts for Each State
Each state needs its own contract to enforce business rules. Here’s a simplified example for both:
// InvoiceContract.kt class InvoiceContract : Contract { companion object { const val ID = "com.yourpackage.InvoiceContract" } override fun verify(tx: LedgerTransaction) { // Add invoice-specific validation logic (e.g., amount > 0) val command = tx.commands.requireSingleCommand<Commands.Create>() requireThat { "Invoice amount must be positive" using (tx.outputsOfType<InvoiceState>().single().amount.quantity > 0) "Issuer and recipient cannot be the same" using (tx.outputsOfType<InvoiceState>().single().issuer != tx.outputsOfType<InvoiceState>().single().recipient) } } interface Commands : CommandData { class Create : Commands } } // PaymentContract.kt class PaymentContract : Contract { companion object { const val ID = "com.yourpackage.PaymentContract" } override fun verify(tx: LedgerTransaction) { // Add payment-specific validation logic (e.g., paid amount matches invoice) val command = tx.commands.requireSingleCommand<Commands.Record>() requireThat { "Payment amount must be positive" using (tx.outputsOfType<PaymentState>().single().paidAmount.quantity > 0) } } interface Commands : CommandData { class Record : Commands } }
Step 3: Implement Flows for Creating Each State
Create separate flows to instantiate and record each state on the ledger. Note that each flow should target its respective state and contract:
// CreateInvoiceFlow.kt @InitiatingFlow @StartableByRPC class CreateInvoiceFlow( private val amount: Amount<Currency>, private val recipient: Party ) : FlowLogic<SignedTransaction>() { override fun call(): SignedTransaction { val notary = serviceHub.networkMapCache.notaryIdentities.first() val invoiceState = InvoiceState(amount, ourIdentity, recipient) val command = Command(InvoiceContract.Commands.Create(), listOf(ourIdentity.owningKey)) val txBuilder = TransactionBuilder(notary) .addOutputState(invoiceState, InvoiceContract.ID) .addCommand(command) txBuilder.verify(serviceHub) val signedTx = serviceHub.signInitialTransaction(txBuilder) val sessions = listOf(initiateFlow(recipient)) val fullySignedTx = subFlow(CollectSignaturesFlow(signedTx, sessions)) return subFlow(FinalityFlow(fullySignedTx, sessions)) } } // CreatePaymentFlow.kt @InitiatingFlow @StartableByRPC class CreatePaymentFlow( private val invoiceId: UniqueIdentifier, private val paidAmount: Amount<Currency>, private val payee: Party ) : FlowLogic<SignedTransaction>() { override fun call(): SignedTransaction { val notary = serviceHub.networkMapCache.notaryIdentities.first() val paymentState = PaymentState(invoiceId, paidAmount, ourIdentity, payee) val command = Command(PaymentContract.Commands.Record(), listOf(ourIdentity.owningKey)) val txBuilder = TransactionBuilder(notary) .addOutputState(paymentState, PaymentContract.ID) .addCommand(command) txBuilder.verify(serviceHub) val signedTx = serviceHub.signInitialTransaction(txBuilder) val sessions = listOf(initiateFlow(payee)) val fullySignedTx = subFlow(CollectSignaturesFlow(signedTx, sessions)) return subFlow(FinalityFlow(fullySignedTx, sessions)) } }
Step 4: Correct ClientRPC Setup
The most common issue when calling multiple flows via RPC is either forgetting to register flows or passing incorrect parameters. Here’s how to properly call both flows from a client:
// ClientRPC Example fun main() { val rpcAddress = NetworkHostAndPort("localhost", 10006) val rpcClient = CordaRPCClient(rpcAddress) rpcClient.start("user1", "test").use { connection -> val proxy = connection.proxy // Call CreateInvoiceFlow val recipient = proxy.wellKnownPartyFromX500Name(CordaX500Name.parse("O=Recipient,L=London,C=GB"))!! val invoiceAmount = Amount(100000, Currency.getInstance("GBP")) val invoiceTxHash = proxy.startFlow(::CreateInvoiceFlow, invoiceAmount, recipient).returnValue.get() println("Created invoice with hash: ${invoiceTxHash.id}") // Call CreatePaymentFlow val payee = proxy.wellKnownPartyFromX500Name(CordaX500Name.parse("O=Recipient,L=London,C=GB"))!! val paymentAmount = Amount(100000, Currency.getInstance("GBP")) val invoiceId = UniqueIdentifier() // Replace with actual invoice ID from the created state val paymentTxHash = proxy.startFlow(::CreatePaymentFlow, invoiceId, paymentAmount, payee).returnValue.get() println("Created payment with hash: ${paymentTxHash.id}") } }
Key Fixes for Your RPC Errors
- Ensure both flows are annotated with
@StartableByRPC—this marks them as accessible via RPC. - Double-check that all states, contracts, and flows are included in your CorDapp’s
META-INF/services/net.corda.core.contracts.ContractandMETA-INF/services/net.corda.core.flows.FlowLogicfiles (Corda usually generates these automatically if you’re using the Gradle plugin, but it’s worth verifying). - Make sure you’re passing the correct parameter types to each flow—mismatched types will throw serialization errors.
内容的提问来源于stack exchange,提问作者Pe4eN

