0
votes

I am creating a new state in the flow and then I am trying to consume the state by using reference input. But every time I see in the result as unconsumed state, though I was providing the reference state in the transaction's input.

public SignedTransaction call() throws FlowException {

    //------------------------------------------------------------------------------------------------------------
    //  STEP-1:
    //  FIRST FLOW MUST CREATE THE NEW STATE WHICH HAS NO INPUT ( THIS WILL CREATE NEW RECORD-ANCHOR  WITH LINEARID )
    //
    //------------------------------------------------------------------------------------------------------------

    // We retrieve the notary identity from the network map.
    Party notary = getServiceHub().getNetworkMapCache().getNotaryIdentities().get(0);

    // We create the transaction components.
    AnchorState outputState = new
            AnchorState(ownerId,contentHash,description,classid,timestamp,expiry, getOurIdentity(), otherParty,new UniqueIdentifier());

    //required signers
    List<PublicKey> requiredSigners = Arrays.asList(getOurIdentity().getOwningKey(),otherParty.getOwningKey());

    //send create command with required signer signatures as below
    Command command = new Command<>(new AnchorStateContract.Commands.CreateRecAnchorCmd(), requiredSigners);

    // We create a transaction builder and add the components.
    TransactionBuilder txBuilder = new TransactionBuilder(notary)
            .addOutputState(outputState, AnchorStateContract.ID)
            .addCommand(command);

    // Verifying the transaction.
    txBuilder.verify(getServiceHub());

    // 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)
            .addOutputState(outputState, AnchorStateContract.ID)
            .addCommand(storeCommand);

    txBuilder2.verify(getServiceHub());

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

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

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

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

In my flow initiator class I am first creating new state of a hash which I am calling as AnchorState. This state is coming from one of the participants and then it requests to the other participant to sign. afterward the signed record is stored in the ledger but its reference used as an input for a new state change, I simply want to make this state as consumed rather than unconsumed.

The responding flow class of participant B is as below

public SignedTransaction call() throws FlowException
{

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

        @Override
        protected void checkTransaction(SignedTransaction stx) {
            requireThat(require -> {
                ContractState output = stx.getTx().getOutputs().get(0).getData();
                require.using("This must be an AnchorState transaction.", output instanceof AnchorState);
                AnchorState state = (AnchorState) output;
                require.using("The AnchorState's value should be more than 6 characters", state.getContentHash().length() > 6);
                return null;
            });
        }
    }


    SecureHash expectedTxId = subFlow(new SignTxFlow(otherPartySession)).getId();

    return subFlow(new ReceiveFinalityFlow(otherPartySession, expectedTxId));

}

This flow successfully runs and returns me unique id for the transaction but I tried everything and could not found how to change the state from unconsumed to consumed?

AFTER FIX

I realized that the vaultQuery on the CordaOS by default returns unconsumed state. Which is now clear why I was not able to get the consumed state in the first place. One more issue which I found, was lack of resources in CORDA for java though I found many kotlin based answers for a transaction with "creation and consumption" in single workflow however converting them into JAVA required some efforts.

Kotlin Based answer

Some differences I observed between Java and Kotlin approach

1) When I have tried to use the same session in my second transaction which was used in the first transaction then I get this error

java.util.concurrent.ExecutionException: net.corda.core.flows.UnexpectedFlowEndException: Tried to access ended session SessionId(toLong=1984916257986245538) with empty buffer at java.util.concurrent.CompletableFuture.reportGet(CompletableFuture.java:357) at java.util.concurrent.CompletableFuture.get(CompletableFuture.java:1895) at net.corda.core.internal.concurrent.CordaFutureImpl.get(CordaFutureImpl.kt)

Which means we have to create new session every time for the new transaction regardless if they are in the single workflow.

2) As I understood by looking at the Kotlin solution that we don't need to add output in the transaction if we just want to make it consumed. However when I do not add an output state in the second transaction then I get the following error which means even for the consumed state I must add the same output inside the transaction. Otherwise, the following error will get erupted again.

ava.util.concurrent.ExecutionException: net.corda.core.flows.UnexpectedFlowEndException: Counter-flow errored at java.util.concurrent.CompletableFuture.reportGet(CompletableFuture.java:357) at java.util.concurrent.CompletableFuture.get(CompletableFuture.java:1895) at net.corda.core.internal.concurrent.CordaFutureImpl.get(CordaFutureImpl.kt) at com.etasjil.Client.testFlow(Client.java:92)

So it is clear that unlike kotlin, in java we need to explicitly add the output state and new session if we want to create and consume a state within same workflow.

Note: Since this is a new learning curve for me therefore, if I made any mistake in the above realization then kindly correct me. This answer could be good for the new comers in Corda who wants to code in Java rather than Kotlin.

1

1 Answers

0
votes

State

@BelongsToContract(AnchorStateContract.class)
public class AnchorState implements LinearState {

    public String ownerId,contentHash,description,classid,timestamp,expiry;
    public Party initiatorParty, otherParty;
    public UniqueIdentifier linearId;

    @Override
    public List<AbstractParty> getParticipants() {
        return Arrays.asList(initiatorParty, otherParty);
    }

    public AnchorState() {
    }

    @ConstructorForDeserialization
    public AnchorState(String ownerId, String contentHash, String description, String classid, String timestamp, String expiry, Party initiatorParty, Party otherParty, UniqueIdentifier linearId) {
        this.ownerId = ownerId;
        this.contentHash = contentHash;
        this.description = description;
        this.classid = classid;
        this.timestamp = timestamp;
        this.expiry = expiry;
        this.initiatorParty = initiatorParty;
        this.otherParty = otherParty;
        this.linearId = linearId;
    }
...

FlowTest case

...
...
@Test
    public void test1() {
        Future data = a.startFlow(new Initiator("Owner1", "1234567", "Description", "c1", Instant.now().toString(), Instant.MAX.toString(), b.getInfo().getLegalIdentities().get(0).getName().toString()));
        network.runNetwork();
        try {
            System.out.println(data.get());
        }catch (Exception e){
            System.out.println(e.getMessage());
        }

        QueryCriteria.VaultQueryCriteria criteria1 = new QueryCriteria.VaultQueryCriteria(Vault.StateStatus.CONSUMED);
        Vault.Page<AnchorState> results1 = a.getServices().getVaultService().queryBy(AnchorState.class, criteria1);
        System.out.println("--------------------- "+ results1.getStates().size());

        QueryCriteria.VaultQueryCriteria criteria2 = new QueryCriteria.VaultQueryCriteria(Vault.StateStatus.ALL);
        Vault.Page<AnchorState> results2 = a.getServices().getVaultService().queryBy(AnchorState.class, criteria2);
        System.out.println("--------------------- "+ results2.getStates().size());

        QueryCriteria.VaultQueryCriteria criteria3 = new QueryCriteria.VaultQueryCriteria(Vault.StateStatus.CONSUMED);
        Vault.Page<AnchorState> results3 = b.getServices().getVaultService().queryBy(AnchorState.class, criteria3);
        System.out.println("--------------------- "+ results3.getStates().size());

        QueryCriteria.VaultQueryCriteria criteria4 = new QueryCriteria.VaultQueryCriteria(Vault.StateStatus.ALL);
        Vault.Page<AnchorState> results4 = b.getServices().getVaultService().queryBy(AnchorState.class, criteria4);
        System.out.println("--------------------- "+ results4.getStates().size());
    }

I got 1,2,1,2 as the outputs which tells 1 consumed state in node a & b, totally 2 states in node a and b(1 consumed and 1 unconsumed).