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:
- Query the vault
- Gather transaction components
- Build transactions
- Sign transactions
Querying the vault
The first recipe is relatively straightforward—it demonstrates how to properly query the vault. This allows you to access states that are stored on a Corda node, so it’s a good one to know.
progressTracker.setCurrentStep(EXTRACTING_VAULT_STATES); // Let's assume there are already some DummyState in our // node's vault, stored there as a result of running past flows, // and we want to consume them in a transaction. There are many // ways to extract these states from our vault. // For example, we would extract any unconsumed DummyState // from our vault as follows: VaultQueryCriteria criteria = new VaultQueryCriteria(Vault.StateStatus.UNCONSUMED); Page
results = getServiceHub().getVaultService().queryBy(DummyState.class, criteria); List > dummyStates = results.getStates(); // When building a transaction, input states are passed in as // ``StateRef`` instances, which pair the hash of the transaction // that generated the state with the state's index in the outputs // of that transaction. In practice, we'd pass the transaction hash // or the ``StateRef`` as a parameter to the flow, or extract the // ``StateRef`` from our vault. StateRef ourStateRef = new StateRef(SecureHash.sha256("DummyTransactionHash"), 0); // A ``StateAndRef`` pairs a ``StateRef`` with the state it points to. StateAndRef ourStateAndRef = getServiceHub().toStateAndRef(ourStateRef);
Extracting transaction components
How to extract transactions is a crucial part of writing a transaction monitoring tool.
The recipe below breaks down how you can access some of the different components of a Corda transaction. You can expect every transaction to contain a state which is associated with a contract command that happens within a specific time window.
progressTracker.setCurrentStep(OTHER_TX_COMPONENTS); // Reference input states are constructed from StateAndRefs. ReferencedStateAndRef referenceState = ourStateAndRef.referenced(); // Output states are constructed from scratch. DummyState ourOutputState = new DummyState(); // Or as copies of other states with some properties changed. DummyState ourOtherOutputState = ourOutputState.copy(77); // We then need to pair our output state with a contract. StateAndContract ourOutput = new StateAndContract(ourOutputState, DummyContract.PROGRAM_ID); // Commands pair a ``CommandData`` instance with a list of // public keys. To be valid, the transaction requires a signature // matching every public key in all of the transaction's commands. DummyContract.Commands.Create commandData = new DummyContract.Commands.Create(); PublicKey ourPubKey = getServiceHub().getMyInfo().getLegalIdentitiesAndCerts().get(0).getOwningKey(); PublicKey counterpartyPubKey = counterparty.getOwningKey(); List
requiredSigners = ImmutableList.of(ourPubKey, counterpartyPubKey); Command ourCommand = new Command<>(commandData, requiredSigners); // ``CommandData`` can either be: // 1. Of type ``TypeOnlyCommandData``, in which case it only // serves to attach signers to the transaction and possibly // fork the contract's verification logic. TypeOnlyCommandData typeOnlyCommandData = new DummyContract.Commands.Create(); // 2. Include additional data which can be used by the contract // during verification, alongside fulfilling the roles above CommandData commandDataWithData = new Cash.Commands.Issue(); // Attachments are identified by their hash. // The attachment with the corresponding hash must have been // uploaded ahead of time via the node's RPC interface. SecureHash ourAttachment = SecureHash.sha256("DummyAttachment"); // Time windows represent the period of time during which a // transaction must be notarised. They can have a start and an end // time, or be open at either end. TimeWindow ourTimeWindow = TimeWindow.between(Instant.MIN, Instant.MAX); TimeWindow ourAfter = TimeWindow.fromOnly(Instant.MIN); TimeWindow ourBefore = TimeWindow.untilOnly(Instant.MAX); // We can also define a time window as an ``Instant`` +/- a time // tolerance (e.g. 30 seconds): TimeWindow ourTimeWindow2 = TimeWindow.withTolerance(getServiceHub().getClock().instant(), Duration.ofSeconds(30)); // Or as a start-time plus a duration: TimeWindow ourTimeWindow3 = TimeWindow.fromStartAndDuration(getServiceHub().getClock().instant(), Duration.ofSeconds(30));
Now that you understand what the components of a transaction are, you need to know how to specify within a transaction the input and output states.
Use the following to create a state, add a command, and specify a time window:
progressTracker.setCurrentStep(TX_BUILDING); // If our transaction has input states or a time-window, we must instantiate it with a // notary. TransactionBuilder txBuilder = new TransactionBuilder(specificNotary); // Otherwise, we can choose to instantiate it without one: TransactionBuilder txBuilderNoNotary = new TransactionBuilder(); // We add items to the transaction builder using ``TransactionBuilder.withItems``: txBuilder.withItems( // Inputs, as ``StateAndRef``s that reference to the outputs of previous transactions ourStateAndRef, // Outputs, as ``StateAndContract``s ourOutput, // Commands, as ``Command``s ourCommand, // Attachments, as ``SecureHash``es ourAttachment, // A time-window, as ``TimeWindow`` ourTimeWindow ); // We can also add items using methods for the individual components. // The individual methods for adding input states and attachments: txBuilder.addInputState(ourStateAndRef); txBuilder.addAttachment(ourAttachment); // An output state can be added as a ``ContractState``, contract class name and notary. txBuilder.addOutputState(ourOutputState, DummyContract.PROGRAM_ID, specificNotary); // We can also leave the notary field blank, in which case the transaction's default // notary is used. txBuilder.addOutputState(ourOutputState, DummyContract.PROGRAM_ID); // Or we can add the output state as a ``TransactionState``, which already specifies // the output's contract and notary. TransactionState txState = new TransactionState<>(ourOutputState, DummyContract.PROGRAM_ID, specificNotary); // Commands can be added as ``Command``s. txBuilder.addCommand(ourCommand); // Or as ``CommandData`` and a ``vararg PublicKey``. txBuilder.addCommand(commandData, ourPubKey, counterpartyPubKey); // We can set a time-window directly. txBuilder.setTimeWindow(ourTimeWindow); // Or as a start time plus a duration (e.g. 45 seconds). txBuilder.setTimeWindow(getServiceHub().getClock().instant(), Duration.ofSeconds(45));
Of course, building a transaction doesn’t mean anything if it’s not validated by the network and approved by its participants.
In this final recipe, we specify the identities of the required signers for a transaction.
It’s also possible to prepare signatures without using the transaction builder if you really wanted, but I certainly wouldn’t recommend it! That being said, the snippet is below.
progressTracker.setCurrentStep(TX_SIGNING); // We finalise the transaction by signing it, // converting it into a ``SignedTransaction``. SignedTransaction onceSignedTx = getServiceHub().signInitialTransaction(txBuilder); // We can also sign the transaction using a different public key: PartyAndCertificate otherIdentity = getServiceHub().getKeyManagementService().freshKeyAndCert(getOurIdentityAndCert(), false); SignedTransaction onceSignedTx2 = getServiceHub().signInitialTransaction(txBuilder, otherIdentity.getOwningKey()); // If instead this was a ``SignedTransaction`` that we'd received // from a counterparty and we needed to sign it, we would add our // signature using: SignedTransaction twiceSignedTx = getServiceHub().addSignature(onceSignedTx); // Or, if we wanted to use a different public key: PartyAndCertificate otherIdentity2 = getServiceHub().getKeyManagementService().freshKeyAndCert(getOurIdentityAndCert(), false); SignedTransaction twiceSignedTx2 = getServiceHub().addSignature(onceSignedTx, otherIdentity2.getOwningKey()); // We can also generate a signature over the transaction without // adding it to the transaction itself. We may do this when // sending just the signature in a flow instead of returning the // entire transaction with our signature. This way, the receiving // node does not need to check we haven't changed anything in the // transaction. TransactionSignature sig = getServiceHub().createSignature(onceSignedTx); // And again, if we wanted to use a different public key: TransactionSignature sig2 = getServiceHub().createSignature(onceSignedTx, otherIdentity2.getOwningKey());
In this chapter, we focused on transactions. In chapter 3 of this series, we’ll look at how signatures are gathered and verified within transactions.
Get in touch or leave a comment if you have any questions.