mirror of
https://github.com/corda/corda.git
synced 2025-02-21 09:51:57 +00:00
Updates the transaction API page and cookbook.
This commit is contained in:
parent
0fb4465c10
commit
701c4f3c60
@ -11,12 +11,11 @@ API: Transactions
|
||||
|
||||
Transaction workflow
|
||||
--------------------
|
||||
There are four states the transaction can occupy:
|
||||
At any time, a transaction can occupy one of three states:
|
||||
|
||||
* ``TransactionBuilder``, a builder for a transaction in construction
|
||||
* ``WireTransaction``, an immutable transaction
|
||||
* ``TransactionBuilder``, a builder for an in-construction transaction
|
||||
* ``SignedTransaction``, an immutable transaction with 1+ associated signatures
|
||||
* ``LedgerTransaction``, a transaction that can be checked for validity
|
||||
* ``LedgerTransaction``, an immutable transaction that can be checked for validity
|
||||
|
||||
Here are the possible transitions between transaction states:
|
||||
|
||||
@ -26,8 +25,8 @@ TransactionBuilder
|
||||
------------------
|
||||
Creating a builder
|
||||
^^^^^^^^^^^^^^^^^^
|
||||
The first step when creating a transaction is to instantiate a ``TransactionBuilder``. We can create a builder for each
|
||||
transaction type as follows:
|
||||
The first step when creating a new transaction is to instantiate a ``TransactionBuilder``. We create a builder for a
|
||||
transaction as follows:
|
||||
|
||||
.. container:: codeset
|
||||
|
||||
@ -342,7 +341,7 @@ SignedTransaction
|
||||
-----------------
|
||||
A ``SignedTransaction`` is a combination of:
|
||||
|
||||
* An immutable ``WireTransaction``
|
||||
* An immutable transaction
|
||||
* A list of signatures over that transaction
|
||||
|
||||
.. container:: codeset
|
||||
@ -357,45 +356,9 @@ 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 :doc:`api-flows` for more details.
|
||||
|
||||
When verifying a ``SignedTransaction``, we don't verify the ``SignedTransaction`` *per se*, but rather the
|
||||
``WireTransaction`` it contains. We extract this ``WireTransaction`` as follows:
|
||||
|
||||
.. container:: codeset
|
||||
|
||||
.. literalinclude:: ../../docs/source/example-code/src/main/kotlin/net/corda/docs/FlowCookbook.kt
|
||||
:language: kotlin
|
||||
:start-after: DOCSTART 31
|
||||
:end-before: DOCEND 31
|
||||
:dedent: 12
|
||||
|
||||
.. literalinclude:: ../../docs/source/example-code/src/main/java/net/corda/docs/FlowCookbookJava.java
|
||||
:language: java
|
||||
:start-after: DOCSTART 31
|
||||
:end-before: DOCEND 31
|
||||
:dedent: 12
|
||||
|
||||
However, this still isn't enough. The ``WireTransaction`` 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 ``WireTransaction`` into a ``LedgerTransaction``:
|
||||
|
||||
.. container:: codeset
|
||||
|
||||
.. literalinclude:: ../../docs/source/example-code/src/main/kotlin/net/corda/docs/FlowCookbook.kt
|
||||
:language: kotlin
|
||||
:start-after: DOCSTART 32
|
||||
:end-before: DOCEND 32
|
||||
:dedent: 12
|
||||
|
||||
.. literalinclude:: ../../docs/source/example-code/src/main/java/net/corda/docs/FlowCookbookJava.java
|
||||
:language: java
|
||||
:start-after: DOCSTART 32
|
||||
:end-before: DOCEND 32
|
||||
:dedent: 12
|
||||
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 :doc:`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:
|
||||
@ -414,8 +377,27 @@ states:
|
||||
:end-before: DOCEND 33
|
||||
:dedent: 12
|
||||
|
||||
We will generally also want to conduct some additional validation of the transaction, beyond what is provided for in
|
||||
the contract. Here's an example of how we might do this:
|
||||
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``:
|
||||
|
||||
.. container:: codeset
|
||||
|
||||
.. literalinclude:: ../../docs/source/example-code/src/main/kotlin/net/corda/docs/FlowCookbook.kt
|
||||
:language: kotlin
|
||||
:start-after: DOCSTART 32
|
||||
:end-before: DOCEND 32
|
||||
:dedent: 12
|
||||
|
||||
.. literalinclude:: ../../docs/source/example-code/src/main/java/net/corda/docs/FlowCookbookJava.java
|
||||
:language: java
|
||||
:start-after: DOCSTART 32
|
||||
:end-before: DOCEND 32
|
||||
:dedent: 12
|
||||
|
||||
We can now perform additional verification. Here's a simple example:
|
||||
|
||||
.. container:: codeset
|
||||
|
||||
@ -558,8 +540,8 @@ Or using another one of our public keys, as follows:
|
||||
|
||||
Notarising and recording
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
Notarising and recording a transaction is handled by a built-in flow called ``FinalityFlow``. See
|
||||
:doc:`api-flows` for more details.
|
||||
Notarising and recording a transaction is handled by a built-in flow called ``FinalityFlow``. See :doc:`api-flows` for
|
||||
more details.
|
||||
|
||||
Notary-change transactions
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
@ -28,6 +28,7 @@ import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.security.GeneralSecurityException;
|
||||
import java.security.PublicKey;
|
||||
import java.security.SignatureException;
|
||||
import java.time.Duration;
|
||||
import java.time.Instant;
|
||||
import java.util.List;
|
||||
@ -394,15 +395,18 @@ public class FlowCookbookJava {
|
||||
----------------------------*/
|
||||
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:
|
||||
// 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:
|
||||
// DOCSTART 12
|
||||
subFlow(new SendTransactionFlow(counterparty, twiceSignedTx));
|
||||
|
||||
// Optional request verification to further restrict data access.
|
||||
subFlow(new SendTransactionFlow(counterparty, twiceSignedTx){
|
||||
subFlow(new SendTransactionFlow(counterparty, twiceSignedTx) {
|
||||
@Override
|
||||
protected void verifyDataRequest(@NotNull FetchDataFlow.Request.Data dataRequest) {
|
||||
// Extra request verification.
|
||||
@ -425,41 +429,43 @@ public class FlowCookbookJava {
|
||||
List<StateAndRef<DummyState>> resolvedStateAndRef = subFlow(new ReceiveStateAndRefFlow<DummyState>(counterparty));
|
||||
// DOCEND 14
|
||||
|
||||
// A ``SignedTransaction`` is a pairing of a ``WireTransaction``
|
||||
// with signatures over this ``WireTransaction``. We don't verify
|
||||
// a signed transaction per se, but rather the ``WireTransaction``
|
||||
// it contains.
|
||||
// DOCSTART 31
|
||||
WireTransaction wireTx = twiceSignedTx.getTx();
|
||||
// DOCEND 31
|
||||
// Before we can verify the transaction, we need the
|
||||
// ``ServiceHub`` to use our node's local storage to resolve the
|
||||
// transaction's inputs and attachments into actual objects,
|
||||
// rather than just references. We do this by converting the
|
||||
// ``WireTransaction`` into a ``LedgerTransaction``.
|
||||
// DOCSTART 32
|
||||
LedgerTransaction ledgerTx = wireTx.toLedgerTransaction(getServiceHub());
|
||||
// DOCEND 32
|
||||
// We can now verify the transaction.
|
||||
// DOCSTART 33
|
||||
ledgerTx.verify();
|
||||
// DOCEND 33
|
||||
try {
|
||||
|
||||
// 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.
|
||||
// DOCSTART 34
|
||||
DummyState outputState = (DummyState) wireTx.getOutputs().get(0).getData();
|
||||
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.");
|
||||
// We can now verify the transaction to ensure that it satisfies
|
||||
// the contracts of all the transaction's input and output states.
|
||||
// DOCSTART 33
|
||||
twiceSignedTx.verify(getServiceHub());
|
||||
// DOCEND 33
|
||||
|
||||
// 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.
|
||||
// DOCSTART 32
|
||||
LedgerTransaction ledgerTx = twiceSignedTx.toLedgerTransaction(getServiceHub());
|
||||
// DOCEND 32
|
||||
|
||||
// We can now perform our additional verification.
|
||||
// DOCSTART 34
|
||||
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.");
|
||||
}
|
||||
// DOCEND 34
|
||||
|
||||
} catch (GeneralSecurityException e) {
|
||||
// Handle this as required.
|
||||
}
|
||||
// DOCEND 34
|
||||
|
||||
// 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
|
||||
|
@ -16,7 +16,6 @@ import net.corda.core.node.services.vault.QueryCriteria.VaultQueryCriteria
|
||||
import net.corda.core.transactions.LedgerTransaction
|
||||
import net.corda.core.transactions.SignedTransaction
|
||||
import net.corda.core.transactions.TransactionBuilder
|
||||
import net.corda.core.transactions.WireTransaction
|
||||
import net.corda.core.utilities.ProgressTracker
|
||||
import net.corda.core.utilities.ProgressTracker.Step
|
||||
import net.corda.core.utilities.UntrustworthyData
|
||||
@ -379,10 +378,13 @@ object FlowCookbook {
|
||||
---------------------------**/
|
||||
progressTracker.currentStep = 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:
|
||||
// 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:
|
||||
// DOCSTART 12
|
||||
subFlow(SendTransactionFlow(counterparty, twiceSignedTx))
|
||||
|
||||
@ -401,7 +403,8 @@ object FlowCookbook {
|
||||
val verifiedTransaction = subFlow(ReceiveTransactionFlow(counterparty))
|
||||
// DOCEND 13
|
||||
|
||||
// We can also send and receive a `StateAndRef` dependency chain and automatically resolve its dependencies.
|
||||
// We can also send and receive a `StateAndRef` dependency chain
|
||||
// and automatically resolve its dependencies.
|
||||
// DOCSTART 14
|
||||
subFlow(SendStateAndRefFlow(counterparty, dummyStates))
|
||||
|
||||
@ -409,24 +412,10 @@ object FlowCookbook {
|
||||
val resolvedStateAndRef = subFlow(ReceiveStateAndRefFlow<DummyState>(counterparty))
|
||||
// DOCEND 14
|
||||
|
||||
// A ``SignedTransaction`` is a pairing of a ``WireTransaction``
|
||||
// with signatures over this ``WireTransaction``. We don't verify
|
||||
// a signed transaction per se, but rather the ``WireTransaction``
|
||||
// it contains.
|
||||
// DOCSTART 31
|
||||
val wireTx: WireTransaction = twiceSignedTx.tx
|
||||
// DOCEND 31
|
||||
// Before we can verify the transaction, we need the
|
||||
// ``ServiceHub`` to use our node's local storage to resolve the
|
||||
// transaction's inputs and attachments into actual objects,
|
||||
// rather than just references. We do this by converting the
|
||||
// ``WireTransaction`` into a ``LedgerTransaction``.
|
||||
// DOCSTART 32
|
||||
val ledgerTx: LedgerTransaction = wireTx.toLedgerTransaction(serviceHub)
|
||||
// DOCEND 32
|
||||
// We can now verify the transaction.
|
||||
// We can now verify the transaction to ensure that it satisfies
|
||||
// the contracts of all the transaction's input and output states.
|
||||
// DOCSTART 33
|
||||
ledgerTx.verify()
|
||||
twiceSignedTx.verify(serviceHub)
|
||||
// DOCEND 33
|
||||
|
||||
// We'll often want to perform our own additional verification
|
||||
@ -434,8 +423,18 @@ object FlowCookbook {
|
||||
// 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.
|
||||
// DOCSTART 32
|
||||
val ledgerTx: LedgerTransaction = twiceSignedTx.toLedgerTransaction(serviceHub)
|
||||
// DOCEND 32
|
||||
|
||||
// We can now perform our additional verification.
|
||||
// DOCSTART 34
|
||||
val outputState: DummyState = wireTx.outputsOfType<DummyState>().single()
|
||||
val outputState: DummyState = ledgerTx.outputsOfType<DummyState>().single()
|
||||
if (outputState.magicNumber == 777) {
|
||||
// ``FlowException`` is a special exception type. It will be
|
||||
// propagated back to any counterparty flows waiting for a
|
||||
|
Binary file not shown.
Before Width: | Height: | Size: 287 KiB After Width: | Height: | Size: 199 KiB |
Loading…
x
Reference in New Issue
Block a user