2
votes

is it possible to create and consume the same corda state in one flow or create and consume it in different subflows? I get the following error: Caused by: net.corda.core.flows.NotaryException: Unable to notarise transactionBEDE8C3F8F2D7A646A9F7D1948DAF77CDAFC37F3B086E09FC766F0D412F02690: One or more input states have been used in another transaction

2

2 Answers

2
votes

Yes, you can create and consume the same Corda state in a single flow.

You need to proceed in two steps:

  1. Create a first transaction issuing the new state
  2. Create a second transaction consuming the new state

Note that if you create the second transaction and cause a counterparty to call ResolveTransactionFlow on it before finalising the first transaction, this will cause a TransactionResolutionException, because you don't have the first transaction in your storage to distribute yet. This can occur for example when running CollectSignaturesFlow.

Here is an example of building two transactions in the same flow:

@InitiatingFlow
@StartableByRPC
class TwoTransactionsFlow(val otherParty: Party) : FlowLogic<Unit>() {
    @Suspendable
    override fun call() {
        val otherPartySessions = listOf(initiateFlow(otherParty))

        val transactionBuilderOne = TransactionBuilder()
        // TODO: Add notary and transaction components.
        val partSignedTransactionOne = serviceHub.signInitialTransaction(transactionBuilderOne)
        val fullySignedTransactionOne = subFlow(CollectSignaturesFlow(partSignedTransactionOne, otherPartySessions))
        val notarisedTransactionOne = subFlow(FinalityFlow(fullySignedTransactionOne))

        val transactionOneFirstOutputRef = StateRef(notarisedTransactionOne.id, 0)
        val transactionOneFirstOutput = serviceHub.toStateAndRef<ContractState>(transactionOneFirstOutputRef)

        val transactionBuilderTwo = TransactionBuilder()
                .addInputState(transactionOneFirstOutput)
        // TODO: Add notary and other transaction components.
        val partSignedTransactionTwo = serviceHub.signInitialTransaction(transactionBuilderTwo)
        val fullySignedTransactionTwo = subFlow(CollectSignaturesFlow(partSignedTransactionTwo, otherPartySessions))
        subFlow(FinalityFlow(fullySignedTransactionTwo))
    }
}
1
votes

Some more points to consider

  • In Corda 4, when creating and consuming the state in one flow, the corresponding responder flow should call ReceiveFinalityFlow 2 times(also SignTransactionFlow incase of signers), or else an error will be thrown:-java.util.concurrent.ExecutionException: net.corda.core.flows.UnexpectedFlowEndException: Tried to access ended session SessionId(toLong=1984916257986245538) with empty buffer.

Code snippet of Initiator Flow in Java

...

        // Signing the transaction.
        SignedTransaction signedTx = getServiceHub().signInitialTransaction(txBuilder);

        // Creating a session with the other party.
        FlowSession otherPartySession = initiateFlow(otherParty);

        // Obtaining the counterparty's signature.
        SignedTransaction fullySignedTx = subFlow(new CollectSignaturesFlow(
                signedTx, Arrays.asList(otherPartySession), CollectSignaturesFlow.Companion.tracker()));

        //notarized transaction
        SignedTransaction notraizedtransaction = subFlow(new FinalityFlow(fullySignedTx, otherPartySession));

        //------------------------------------------------------------------------------------------------------------
        // STEP-2:
        //  SINCE NOW WE HAVE A NEW UNCONSUMED RECORD-ANCHOR SO WE MUST MAKE IT CONSUMED ( BY USING THE PREVIOUS OUTPUT AS AN INPUT)
        //
        //------------------------------------------------------------------------------------------------------------

        StateAndRef oldStateref =  getServiceHub().toStateAndRef(new StateRef(notraizedtransaction.getId(),0));

        Command storeCommand = new Command<>(new AnchorStateContract.Commands.ApproveRecAnchorCmd(), requiredSigners);

        TransactionBuilder txBuilder2 = new TransactionBuilder(notary)
                .addInputState(oldStateref)
                .addCommand(storeCommand);

        txBuilder2.verify(getServiceHub());

        // signing
        SignedTransaction signedTx2 = getServiceHub().signInitialTransaction(txBuilder2);

        // Finalising the transaction.
        SignedTransaction fullySignedTx2 = subFlow(new CollectSignaturesFlow(
                signedTx2, Arrays.asList(otherPartySession), CollectSignaturesFlow.Companion.tracker()));


        //notarized transaction
        return subFlow(new FinalityFlow(fullySignedTx2, otherPartySession));
}

Code snippet of ResponderFlow in Java

@InitiatedBy(Initiator.class)
public class Responder extends FlowLogic<SignedTransaction> {
    private FlowSession otherPartySession;

    public Responder(FlowSession otherPartySession) {
        this.otherPartySession = otherPartySession;
    }

    @Suspendable
    @Override
    public SignedTransaction call() throws FlowException
    {

        //this class is used inside call function for the verification purposes before signed by this party
        class SignTxFlow1 extends SignTransactionFlow
        {
            private SignTxFlow1(FlowSession otherPartySession) {
                super(otherPartySession);
            }

            @Override
            protected void checkTransaction(SignedTransaction stx) {
                requireThat(require -> {
                    // Validation Logic
                    return null;
                });
            }
        }

        //this class is used inside call function for the verification purposes before signed by this party
        class SignTxFlow2 extends SignTransactionFlow
        {
            private SignTxFlow2(FlowSession otherPartySession) {
                super(otherPartySession);
            }

            @Override
            protected void checkTransaction(SignedTransaction stx) {
                requireThat(require -> {
                    // Validation Logic
                    return null;
                });
            }
        }
        //Validation, signing and storing of first transaction data
        SecureHash expectedTxId1 = subFlow(new SignTxFlow1(otherPartySession)).getId();
        subFlow(new ReceiveFinalityFlow(otherPartySession, expectedTxId1));

        //Validation, signing and storing of second transaction data
        SecureHash expectedTxId2 = subFlow(new SignTxFlow2(otherPartySession)).getId();
        return subFlow(new ReceiveFinalityFlow(otherPartySession, expectedTxId2));

    }
}