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

在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.Contract and META-INF/services/net.corda.core.flows.FlowLogic files (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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.21 07:18:01