The Corda flow cookbook: Chapter 3

Corda Aug 12 2021 By: David Awad
Comments

0 Comments

Views

992 Views

David Awad
David Awad Developer
Share this post:
Copied

You can use our “Flow Cookbook” to do lots of exciting things — from identifying other nodes to gathering signatures. 

This series will introduce you to some key recipes in the cookbook. You’ll learn how they work and what you can use them for.  

In this chapter, you’ll learn how to:

  • Verify transactions 
  • Gather and verify signatures
  • Finalize transactions. 

Verifying transactions 

To make things easier, Corda provides a library of subflows to handle common tasks such as this. To verify a transaction within a CorDapp, simply call the verify() method, and pass a reference to the Corda service hub.

    
progressTracker.setCurrentStep(TX_VERIFICATION);

// Verifying a transaction will also verify every transaction in
// the transaction's dependency chain, which will require
// transaction data access on counterparty's node. The
// ``SendTransactionFlow`` can be used to automate the sending and
// data vending process. The ``SendTransactionFlow`` will listen
// for data request until the transaction is resolved and verified
// on the other side:
subFlow(new SendTransactionFlow(counterpartySession, twiceSignedTx));

// Optional request verification to further restrict data access.
subFlow(new SendTransactionFlow(counterpartySession, twiceSignedTx) {
    @Override
    protected void verifyDataRequest(@NotNull FetchDataFlow.Request.Data dataRequest) {
        // Extra request verification.
    }
});

// We can receive the transaction using ``ReceiveTransactionFlow``,
// which will automatically download all the dependencies and verify
// the transaction and then record in our vault
SignedTransaction verifiedTransaction = subFlow(new ReceiveTransactionFlow(counterpartySession));

// We can also send and receive a `StateAndRef` dependency chain and automatically resolve its dependencies.
subFlow(new SendStateAndRefFlow(counterpartySession, dummyStates));

// On the receive side ...
List> resolvedStateAndRef = subFlow(new ReceiveStateAndRefFlow<>(counterpartySession));

try {
    // We can now verify the transaction to ensure that it satisfies
    // the contracts of all the transaction's input and output states.
    twiceSignedTx.verify(getServiceHub());

    // We'll often want to perform our own additional verification
    // too. Just because a transaction is valid based on the contract
    // rules and requires our signature doesn't mean we have to
    // sign it! We need to make sure the transaction represents an
    // agreement we actually want to enter into.

    // To do this, we need to convert our ``SignedTransaction``
    // into a ``LedgerTransaction``. This will use our ServiceHub
    // to resolve the transaction's inputs and attachments into
    // actual objects, rather than just references.
    LedgerTransaction ledgerTx = twiceSignedTx.toLedgerTransaction(getServiceHub());

    // We can now perform our additional verification.
    DummyState outputState = ledgerTx.outputsOfType(DummyState.class).get(0);
    if (outputState.getMagicNumber() != 777) {
        // ``FlowException`` is a special exception type. It will be
        // propagated back to any counterparty flows waiting for a
        // message from this flow, notifying them that the flow has
        // failed.
        throw new FlowException("We expected a magic number of 777.");
    }
} catch (GeneralSecurityException e) {
    // Handle this as required.
}

// Of course, if you are not a required signer on the transaction,
// you have no power to decide whether it is valid or not. If it
// requires signatures from all the required signers and is
// contractually valid, it's a valid ledger update.


Gathering and verifying signatures 

Gathering signatures is the process of requesting each party to acknowledge the transaction. To do this, you call CollectSignaturesFlow

To verify those signatures use verifyRequiredSigners. You also have the option to only check the signatures you have on the transaction or to ignore signatures you may not need. In these circumstances, use verifySignaturesExcept.


progressTracker.setCurrentStep(SIGS_GATHERING);

// The list of parties who need to sign a transaction is dictated
// by the transaction's commands. Once we've signed a transaction
// ourselves, we can automatically gather the signatures of the
// other required signers using ``CollectSignaturesFlow``.
// The responder flow will need to call ``SignTransactionFlow``.
SignedTransaction fullySignedTx = subFlow(new CollectSignaturesFlow(twiceSignedTx, emptySet(), SIGS_GATHERING.childProgressTracker()));

progressTracker.setCurrentStep(VERIFYING_SIGS);

try {

    // We can verify that a transaction has all the required
    // signatures, and that they're all valid, by running:
    fullySignedTx.verifyRequiredSignatures();

    // If the transaction is only partially signed, we have to pass in
    // a vararg of the public keys corresponding to the missing
    // signatures, explicitly telling the system not to check them.
    onceSignedTx.verifySignaturesExcept(counterpartyPubKey);

    // There is also an overload of ``verifySignaturesExcept`` which accepts
    // a ``Collection`` of the public keys corresponding to the missing
    // signatures. In the example below, we could also use
    // ``Arrays.asList(counterpartyPubKey)`` instead of
    // ``Collections.singletonList(counterpartyPubKey)``.
    onceSignedTx.verifySignaturesExcept(singletonList(counterpartyPubKey));

    // We can also choose to only check the signatures that are
    // present. BE VERY CAREFUL - this function provides no guarantees
    // that the signatures are correct, or that none are missing.
    twiceSignedTx.checkSignaturesAreValid();

} catch (GeneralSecurityException e) {
    // Handle this as required.
}

Finalizing transactions

Finalizing a transaction can feel like one of the most daunting aspects of building a blockchain application. We mentioned earlier that Corda provides a library of subflows to make things a lot easier. Most CorDapps can use FinalityFlow and pass the relevant counterparty sessions as a list, Corda then handles the rest. 

    
progressTracker.setCurrentStep(FINALISATION);

// We notarise the transaction and get it recorded in the vault of
// the participants of all the transaction's states.
SignedTransaction notarisedTx1 = subFlow(new FinalityFlow(fullySignedTx, singleton(counterpartySession), FINALISATION.childProgressTracker()));

// We can also choose to send it to additional parties who aren't one
// of the state's participants.
List partySessions = Arrays.asList(counterpartySession, initiateFlow(regulator));
SignedTransaction notarisedTx2 = subFlow(new FinalityFlow(fullySignedTx, partySessions, FINALISATION.childProgressTracker()));

In this chapter of the cookbook, we’ve finished our discussion on transactions and spent a bit of time on signatures. In the next and final chapter of this mini-series, we’ll look at the other side of this whole process and see how responder flows work on the receiving end of Corda transactions. 

Feel free to reach out or leave a comment if you have any questions.

David Awad
David Awad David Awad is a Developer Evangelist at R3, an enterprise blockchain software firm working with a global ecosystem of more than 350 participants across multiple industries from both the private and public sectors to develop on Corda, its open-source blockchain platform, and Corda Enterprise, a commercial version of Corda for enterprise usage. Follow David on Twitter here.

Leave a Reply

Subscribe to our newsletter to stay up to date on the latest developer news, tools, and articles.