20 KiB
API: Transactions
Note
Before reading this page, you should be familiar with the key concepts of key-concepts-transactions
.
Transaction workflow
At any time, a transaction can occupy one of three states:
TransactionBuilder
, a builder for an in-construction transactionSignedTransaction
, an immutable transaction with 1+ associated signaturesLedgerTransaction
, an immutable transaction that can be checked for validity
Here are the possible transitions between transaction states:
TransactionBuilder
Creating a builder
The first step when creating a new transaction is to instantiate a TransactionBuilder
. We create a builder for a transaction as follows:
../../docs/source/example-code/src/main/kotlin/net/corda/docs/FlowCookbook.kt
../../docs/source/example-code/src/main/java/net/corda/docs/FlowCookbookJava.java
Transaction components
Once we have a TransactionBuilder
, we need to gather together the various transaction components the transaction will include.
Input states
Input states are added to a transaction as StateAndRef
instances. A StateAndRef
combines:
- A
ContractState
representing the input state itself - A
StateRef
pointing to the input among the outputs of the transaction that created it
../../docs/source/example-code/src/main/kotlin/net/corda/docs/FlowCookbook.kt
../../docs/source/example-code/src/main/java/net/corda/docs/FlowCookbookJava.java
A StateRef
uniquely identifies an input state, allowing the notary to mark it as historic. It is made up of:
- The hash of the transaction that generated the state
- The state's index in the outputs of that transaction
../../docs/source/example-code/src/main/kotlin/net/corda/docs/FlowCookbook.kt
../../docs/source/example-code/src/main/java/net/corda/docs/FlowCookbookJava.java
The StateRef
create a chain of pointers from the input states back to the transactions that created them. This allows a node to work backwards and verify the entirety of the transaction chain.
Output states
Since a transaction's output states do not exist until the transaction is committed, they cannot be referenced as the outputs of previous transactions. Instead, we create the desired output states as ContractState
instances, and add them to the transaction:
../../docs/source/example-code/src/main/kotlin/net/corda/docs/FlowCookbook.kt
../../docs/source/example-code/src/main/java/net/corda/docs/FlowCookbookJava.java
In many cases (e.g. when we have a transaction that updates an existing state), we may want to create an output by copying from the input state:
../../docs/source/example-code/src/main/kotlin/net/corda/docs/FlowCookbook.kt
../../docs/source/example-code/src/main/java/net/corda/docs/FlowCookbookJava.java
Commands
Commands are added to the transaction as Command
instances. Command
combines:
- A
CommandData
instance representing the type of the command - A list of the command's required signers
../../docs/source/example-code/src/main/kotlin/net/corda/docs/FlowCookbook.kt
../../docs/source/example-code/src/main/java/net/corda/docs/FlowCookbookJava.java
Attachments
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:
../../docs/source/example-code/src/main/kotlin/net/corda/docs/FlowCookbook.kt
../../docs/source/example-code/src/main/java/net/corda/docs/FlowCookbookJava.java
Time-windows
Time windows represent the period of time during which the transaction must be notarised. They can have a start and an end time, or be open at either end:
../../docs/source/example-code/src/main/kotlin/net/corda/docs/FlowCookbook.kt
../../docs/source/example-code/src/main/java/net/corda/docs/FlowCookbookJava.java
We can also define a time window as an Instant
+/- a time tolerance (e.g. 30 seconds):
../../docs/source/example-code/src/main/kotlin/net/corda/docs/FlowCookbook.kt
../../docs/source/example-code/src/main/java/net/corda/docs/FlowCookbookJava.java
Or as a start-time plus a duration:
../../docs/source/example-code/src/main/kotlin/net/corda/docs/FlowCookbook.kt
../../docs/source/example-code/src/main/java/net/corda/docs/FlowCookbookJava.java
Adding items
The transaction builder is mutable. We add items to it using the TransactionBuilder.withItems
method:
../../core/src/main/kotlin/net/corda/core/transactions/TransactionBuilder.kt
withItems
takes a vararg
of objects and adds them to the builder based on their type:
StateAndRef
objects are added as input statesTransactionState
andContractState
objects are added as output statesCommand
objects are added as commands
Passing in objects of any other type will cause an IllegalArgumentException
to be thrown.
Here's an example usage of TransactionBuilder.withItems
:
../../docs/source/example-code/src/main/kotlin/net/corda/docs/FlowCookbook.kt
../../docs/source/example-code/src/main/java/net/corda/docs/FlowCookbookJava.java
You can also pass in objects one-by-one. This is the only way to add attachments:
../../docs/source/example-code/src/main/kotlin/net/corda/docs/FlowCookbook.kt
../../docs/source/example-code/src/main/java/net/corda/docs/FlowCookbookJava.java
To set the transaction builder's time-window, we can either set a time-window directly:
../../docs/source/example-code/src/main/kotlin/net/corda/docs/FlowCookbook.kt
../../docs/source/example-code/src/main/java/net/corda/docs/FlowCookbookJava.java
Or define the time-window as a time plus a duration (e.g. 45 seconds):
../../docs/source/example-code/src/main/kotlin/net/corda/docs/FlowCookbook.kt
../../docs/source/example-code/src/main/java/net/corda/docs/FlowCookbookJava.java
Signing the builder
Once the builder is ready, we finalize it by signing it and converting it into a SignedTransaction
:
../../docs/source/example-code/src/main/kotlin/net/corda/docs/FlowCookbook.kt
../../docs/source/example-code/src/main/java/net/corda/docs/FlowCookbookJava.java
This will sign the transaction with your legal identity key. You can also choose to use another one of your public keys:
../../docs/source/example-code/src/main/kotlin/net/corda/docs/FlowCookbook.kt
../../docs/source/example-code/src/main/java/net/corda/docs/FlowCookbookJava.java
Either way, the outcome of this process is to create a SignedTransaction
, which can no longer be modified.
SignedTransaction
A SignedTransaction
is a combination of:
- An immutable transaction
- A list of signatures over that transaction
../../core/src/main/kotlin/net/corda/core/transactions/SignedTransaction.kt
Before adding our signature to the transaction, we'll want to verify both the transaction's contents and the transaction's signatures.
Verifying the transaction's contents
To verify a transaction, we need to retrieve any states in the transaction chain that our node doesn't currently have in its local storage from the proposer(s) of the transaction. This process is handled by a built-in flow called ReceiveTransactionFlow
. See api-flows
for more details.
We can now verify the transaction to ensure that it satisfies the contracts of all the transaction's input and output states:
../../docs/source/example-code/src/main/kotlin/net/corda/docs/FlowCookbook.kt
../../docs/source/example-code/src/main/java/net/corda/docs/FlowCookbookJava.java
We can also conduct additional validation of the transaction, beyond what is performed by its contracts. However, the SignedTransaction
holds its inputs as StateRef
instances, and its attachments as hashes. These do not provide enough information to properly validate the transaction's contents. To resolve these into actual ContractState
and Attachment
instances, we need to use the ServiceHub
to convert the SignedTransaction
into a LedgerTransaction
:
../../docs/source/example-code/src/main/kotlin/net/corda/docs/FlowCookbook.kt
../../docs/source/example-code/src/main/java/net/corda/docs/FlowCookbookJava.java
We can now perform additional verification. Here's a simple example:
../../docs/source/example-code/src/main/kotlin/net/corda/docs/FlowCookbook.kt
../../docs/source/example-code/src/main/java/net/corda/docs/FlowCookbookJava.java
Verifying the transaction's signatures
We also need to verify that the transaction has all the required signatures, and that these signatures are valid, to prevent tampering. We do this using SignedTransaction.verifyRequiredSignatures
:
../../docs/source/example-code/src/main/kotlin/net/corda/docs/FlowCookbook.kt
../../docs/source/example-code/src/main/java/net/corda/docs/FlowCookbookJava.java
Alternatively, we can use SignedTransaction.verifySignaturesExcept
, which takes a vararg
of the public keys for which the signatures are allowed to be missing:
../../docs/source/example-code/src/main/kotlin/net/corda/docs/FlowCookbook.kt
../../docs/source/example-code/src/main/java/net/corda/docs/FlowCookbookJava.java
If the transaction is missing any signatures without the corresponding public keys being passed in, a SignaturesMissingException
is thrown.
We can also choose to simply verify the signatures that are present:
../../docs/source/example-code/src/main/kotlin/net/corda/docs/FlowCookbook.kt
../../docs/source/example-code/src/main/java/net/corda/docs/FlowCookbookJava.java
However, BE VERY CAREFUL - this function provides no guarantees that the signatures are correct, or that none are missing.
Signing the transaction
Once we are satisfied with the contents and existing signatures over the transaction, we can add our signature to the SignedTransaction
using:
../../docs/source/example-code/src/main/kotlin/net/corda/docs/FlowCookbook.kt
../../docs/source/example-code/src/main/java/net/corda/docs/FlowCookbookJava.java
As with the TransactionBuilder
, we can also choose to sign using another one of our public keys:
../../docs/source/example-code/src/main/kotlin/net/corda/docs/FlowCookbook.kt
../../docs/source/example-code/src/main/java/net/corda/docs/FlowCookbookJava.java
We can also generate a signature over the transaction without adding it to the transaction directly by using:
../../docs/source/example-code/src/main/kotlin/net/corda/docs/FlowCookbook.kt
../../docs/source/example-code/src/main/java/net/corda/docs/FlowCookbookJava.java
Or using another one of our public keys, as follows:
../../docs/source/example-code/src/main/kotlin/net/corda/docs/FlowCookbook.kt
../../docs/source/example-code/src/main/java/net/corda/docs/FlowCookbookJava.java
Notarising and recording
Notarising and recording a transaction is handled by a built-in flow called FinalityFlow
. See api-flows
for more details.
Notary-change transactions
A single Corda network will usually have multiple notary services. To commit a transaction, we require a signature from the notary service associated with each input state. If we tried to commit a transaction where the input states were associated with different notary services, the transaction would require a signature from multiple notary services, creating a complicated multi-phase commit scenario. To prevent this, every input state in a transaction must be associated with the same notary.
However, we will often need to create a transaction involving input states associated with different notaries. Before we can create this transaction, we will need to change the notary service associated with each state by:
- Deciding which notary service we want to notarise the transaction
- Creating a special
NotaryChangeWireTransaction
that consumes the input states pointing to the old notary and produces outputs which are identical but point to the new notary service - Using the outputs of the notary-change transactions as inputs to a standard transaction
In practice, this process is handled automatically by a built-in flow called NotaryChangeFlow
. See api-flows
for more details.