Search code examples
verificationcontractcorda

Cash contract verification


I am encountering an issue during the collect signatures flow where the cash contract verification fails - providing the error message

Contract verification failed: Failed requirement: for reference [01] at issuer C=US,L=New York,O=PartyB the amounts balance: 300000 - 0 != 0, contract: net.corda.finance.contracts.asset.Cash@7d43e51b

It appears this is due to the fact that the input amount of cash is not equal to the output + the amount exiting the ledger.

"for reference ${issuer.reference} at issuer ${issuer.party} the amounts balance: ${inputAmount.quantity} - ${amountExitingLedger.quantity} != ${outputAmount.quantity}" using
                            (inputAmount == outputAmount + amountExitingLedger)

However to generate the cash states within the transaction I am simply using the Cash.generateSpend() function, so I am not sure what this error could be caused by. The cash being used is self issued at the start of our simulations, and may have been moved between nodes before the flow causing this issue is invoked.

I cant see the exact value of the cash states being inputted, but I can see that there are 2 states. The output of cash is then the amount being spent ($3102) plus two other states returning to the party spending the $3102 , with one being worth $97991898 and $97995000. The party was originally issued $98000000 at the start of the simulations - so having this third output state of $97995000 seems very odd. It seems likely that the two input states sum to $97995000 since $97991898 + $3102 = $97995000 and that this party has already spent $5000 in another flow - making this third cash output of $97995000 seemingly appear from nowhere.

Could this issue be caused by something going wrong in Cash.generateSpend()? I am not adding any other input or output states of cash (I have attached the flow below). I also noted that this issue only occurs if I let the flow making that $5000 spend happen (i.e Party A pays B $5000 which is finalized and recorded on the ledger, then tries to make another payment to B here in a separate flow).

//STEP 4: Build the transaction
val notary = serviceHub.networkMapCache.notaryIdentities.first()
val builder = TransactionBuilder(notary)
val builder2 = TermDeposit().generateRedeem(builder, TermDeposit)
//Add our required cash
val (tx, cashKeys) = Cash.generateSpend(serviceHub, builder2, Amount((TermDeposit.state.data.depositAmount.quantity * (100+TermDeposit.state.data.interestPercent)/100).toLong(), USD), flow.counterparty)
println("Redeem added cash ${Amount((TermDeposit.state.data.depositAmount.quantity * (100+TermDeposit.state.data.interestPercent)/100).toLong(), USD)}")

//STEP 5: Get the client to sign the transaction
println("Inputs ${tx.inputStates()}")
println("Outputs ${tx.outputStates()}")
val partSignedTxn = serviceHub.signInitialTransaction(tx, cashKeys)
println("Before collect sigs")
val otherPartySig = subFlow(CollectSignaturesFlow(partSignedTxn, listOf(flow), CollectSignaturesFlow.tracker()))
println("after collect sigs")

//STEP 6: Merge all signatures and commit this to the ledger
val twiceSignedTx = partSignedTxn.plus(otherPartySig.sigs) 
println("Redeem before finality flow")
return subFlow(FinalityFlow(twiceSignedTx))

I know this question is very open ended, but any pointers on areas I could be going wrong would be greatly appreciated.


Solution

  • Here's the relevant part of Cash.verify():

    val groups = tx.groupStates { it: Cash.State -> it.amount.token }
    
    for ((inputs, outputs, key) in groups) {
        val issuer = key.issuer
        val currency = key.product
    
        val inputAmount = inputs.sumCashOrNull() ?: throw IllegalArgumentException("there is at least one cash input for this group")
        val outputAmount = outputs.sumCashOrZero(Issued(issuer, currency))
    
        val exitKeys = inputs.flatMap { it.exitKeys }.toSet()
        val exitCommand = tx.commands.select<Commands.Exit>(parties = null, signers = exitKeys).filter { it.value.amount.token == key }.singleOrNull()
        val amountExitingLedger = exitCommand?.value?.amount ?: Amount(0, Issued(issuer, currency))
    
        requireThat {
            "for reference ${issuer.reference} at issuer ${issuer.party} the amounts balance: " +
                "${inputAmount.quantity} - ${amountExitingLedger.quantity} != ${outputAmount.quantity}" using
                (inputAmount == outputAmount + amountExitingLedger)
        }
    }
    

    groupStates returns a list-of-lists, where each list contains the inputs and outputs corresponding to a given Issuer<Currency>.

    Cash states associated with different Issuer<Currency>s are not fungible. In other words, the transaction has to output as much cash for each Issuer<Currency> as it takes in (ignoring exits).

    The most likely issue here is that you're inputting some cash and outputting it with a different Issuer<Currency>. If you debug your flow and examine the contents of the transaction before it is verified, you should be able to determine whether this is the case.