mirror of
https://github.com/corda/corda.git
synced 2025-02-22 10:10:59 +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
|
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
|
* ``TransactionBuilder``, a builder for an in-construction transaction
|
||||||
* ``WireTransaction``, an immutable transaction
|
|
||||||
* ``SignedTransaction``, an immutable transaction with 1+ associated signatures
|
* ``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:
|
Here are the possible transitions between transaction states:
|
||||||
|
|
||||||
@ -26,8 +25,8 @@ TransactionBuilder
|
|||||||
------------------
|
------------------
|
||||||
Creating a builder
|
Creating a builder
|
||||||
^^^^^^^^^^^^^^^^^^
|
^^^^^^^^^^^^^^^^^^
|
||||||
The first step when creating a transaction is to instantiate a ``TransactionBuilder``. We can create a builder for each
|
The first step when creating a new transaction is to instantiate a ``TransactionBuilder``. We create a builder for a
|
||||||
transaction type as follows:
|
transaction as follows:
|
||||||
|
|
||||||
.. container:: codeset
|
.. container:: codeset
|
||||||
|
|
||||||
@ -342,7 +341,7 @@ SignedTransaction
|
|||||||
-----------------
|
-----------------
|
||||||
A ``SignedTransaction`` is a combination of:
|
A ``SignedTransaction`` is a combination of:
|
||||||
|
|
||||||
* An immutable ``WireTransaction``
|
* An immutable transaction
|
||||||
* A list of signatures over that transaction
|
* A list of signatures over that transaction
|
||||||
|
|
||||||
.. container:: codeset
|
.. container:: codeset
|
||||||
@ -357,45 +356,9 @@ transaction's signatures.
|
|||||||
|
|
||||||
Verifying the transaction's contents
|
Verifying the transaction's contents
|
||||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||||
To verify a transaction, we need to retrieve any states in the transaction chain that our node doesn't
|
To verify a transaction, we need to retrieve any states in the transaction chain that our node doesn't currently have
|
||||||
currently have in its local storage from the proposer(s) of the transaction. This process is handled by a built-in flow
|
in its local storage from the proposer(s) of the transaction. This process is handled by a built-in flow called
|
||||||
called ``ReceiveTransactionFlow``. See :doc:`api-flows` for more details.
|
``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
|
|
||||||
|
|
||||||
We can now *verify* the transaction to ensure that it satisfies the contracts of all the transaction's input and output
|
We can now *verify* the transaction to ensure that it satisfies the contracts of all the transaction's input and output
|
||||||
states:
|
states:
|
||||||
@ -414,8 +377,27 @@ states:
|
|||||||
:end-before: DOCEND 33
|
:end-before: DOCEND 33
|
||||||
:dedent: 12
|
:dedent: 12
|
||||||
|
|
||||||
We will generally also want to conduct some additional validation of the transaction, beyond what is provided for in
|
We can also conduct additional validation of the transaction, beyond what is performed by its contracts. However, the
|
||||||
the contract. Here's an example of how we might do this:
|
``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
|
.. container:: codeset
|
||||||
|
|
||||||
@ -558,8 +540,8 @@ Or using another one of our public keys, as follows:
|
|||||||
|
|
||||||
Notarising and recording
|
Notarising and recording
|
||||||
^^^^^^^^^^^^^^^^^^^^^^^^
|
^^^^^^^^^^^^^^^^^^^^^^^^
|
||||||
Notarising and recording a transaction is handled by a built-in flow called ``FinalityFlow``. See
|
Notarising and recording a transaction is handled by a built-in flow called ``FinalityFlow``. See :doc:`api-flows` for
|
||||||
:doc:`api-flows` for more details.
|
more details.
|
||||||
|
|
||||||
Notary-change transactions
|
Notary-change transactions
|
||||||
^^^^^^^^^^^^^^^^^^^^^^^^^^
|
^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||||
|
@ -28,6 +28,7 @@ import org.jetbrains.annotations.NotNull;
|
|||||||
|
|
||||||
import java.security.GeneralSecurityException;
|
import java.security.GeneralSecurityException;
|
||||||
import java.security.PublicKey;
|
import java.security.PublicKey;
|
||||||
|
import java.security.SignatureException;
|
||||||
import java.time.Duration;
|
import java.time.Duration;
|
||||||
import java.time.Instant;
|
import java.time.Instant;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
@ -394,15 +395,18 @@ public class FlowCookbookJava {
|
|||||||
----------------------------*/
|
----------------------------*/
|
||||||
progressTracker.setCurrentStep(TX_VERIFICATION);
|
progressTracker.setCurrentStep(TX_VERIFICATION);
|
||||||
|
|
||||||
// Verifying a transaction will also verify every transaction in the transaction's dependency chain, which will require
|
// Verifying a transaction will also verify every transaction in
|
||||||
// transaction data access on counterparty's node. The ``SendTransactionFlow`` can be used to automate the sending
|
// the transaction's dependency chain, which will require
|
||||||
// and data vending process. The ``SendTransactionFlow`` will listen for data request until the transaction
|
// transaction data access on counterparty's node. The
|
||||||
// is resolved and verified on the other side:
|
// ``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
|
// DOCSTART 12
|
||||||
subFlow(new SendTransactionFlow(counterparty, twiceSignedTx));
|
subFlow(new SendTransactionFlow(counterparty, twiceSignedTx));
|
||||||
|
|
||||||
// Optional request verification to further restrict data access.
|
// Optional request verification to further restrict data access.
|
||||||
subFlow(new SendTransactionFlow(counterparty, twiceSignedTx){
|
subFlow(new SendTransactionFlow(counterparty, twiceSignedTx) {
|
||||||
@Override
|
@Override
|
||||||
protected void verifyDataRequest(@NotNull FetchDataFlow.Request.Data dataRequest) {
|
protected void verifyDataRequest(@NotNull FetchDataFlow.Request.Data dataRequest) {
|
||||||
// Extra request verification.
|
// Extra request verification.
|
||||||
@ -425,41 +429,43 @@ public class FlowCookbookJava {
|
|||||||
List<StateAndRef<DummyState>> resolvedStateAndRef = subFlow(new ReceiveStateAndRefFlow<DummyState>(counterparty));
|
List<StateAndRef<DummyState>> resolvedStateAndRef = subFlow(new ReceiveStateAndRefFlow<DummyState>(counterparty));
|
||||||
// DOCEND 14
|
// DOCEND 14
|
||||||
|
|
||||||
// A ``SignedTransaction`` is a pairing of a ``WireTransaction``
|
try {
|
||||||
// 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
|
|
||||||
|
|
||||||
// We'll often want to perform our own additional verification
|
// We can now verify the transaction to ensure that it satisfies
|
||||||
// too. Just because a transaction is valid based on the contract
|
// the contracts of all the transaction's input and output states.
|
||||||
// rules and requires our signature doesn't mean we have to
|
// DOCSTART 33
|
||||||
// sign it! We need to make sure the transaction represents an
|
twiceSignedTx.verify(getServiceHub());
|
||||||
// agreement we actually want to enter into.
|
// DOCEND 33
|
||||||
// DOCSTART 34
|
|
||||||
DummyState outputState = (DummyState) wireTx.getOutputs().get(0).getData();
|
// We'll often want to perform our own additional verification
|
||||||
if (outputState.getMagicNumber() != 777) {
|
// too. Just because a transaction is valid based on the contract
|
||||||
// ``FlowException`` is a special exception type. It will be
|
// rules and requires our signature doesn't mean we have to
|
||||||
// propagated back to any counterparty flows waiting for a
|
// sign it! We need to make sure the transaction represents an
|
||||||
// message from this flow, notifying them that the flow has
|
// agreement we actually want to enter into.
|
||||||
// failed.
|
|
||||||
throw new FlowException("We expected a magic number of 777.");
|
// 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,
|
// 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
|
// 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.LedgerTransaction
|
||||||
import net.corda.core.transactions.SignedTransaction
|
import net.corda.core.transactions.SignedTransaction
|
||||||
import net.corda.core.transactions.TransactionBuilder
|
import net.corda.core.transactions.TransactionBuilder
|
||||||
import net.corda.core.transactions.WireTransaction
|
|
||||||
import net.corda.core.utilities.ProgressTracker
|
import net.corda.core.utilities.ProgressTracker
|
||||||
import net.corda.core.utilities.ProgressTracker.Step
|
import net.corda.core.utilities.ProgressTracker.Step
|
||||||
import net.corda.core.utilities.UntrustworthyData
|
import net.corda.core.utilities.UntrustworthyData
|
||||||
@ -379,10 +378,13 @@ object FlowCookbook {
|
|||||||
---------------------------**/
|
---------------------------**/
|
||||||
progressTracker.currentStep = TX_VERIFICATION
|
progressTracker.currentStep = TX_VERIFICATION
|
||||||
|
|
||||||
// Verifying a transaction will also verify every transaction in the transaction's dependency chain, which will require
|
// Verifying a transaction will also verify every transaction in
|
||||||
// transaction data access on counterparty's node. The ``SendTransactionFlow`` can be used to automate the sending
|
// the transaction's dependency chain, which will require
|
||||||
// and data vending process. The ``SendTransactionFlow`` will listen for data request until the transaction
|
// transaction data access on counterparty's node. The
|
||||||
// is resolved and verified on the other side:
|
// ``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
|
// DOCSTART 12
|
||||||
subFlow(SendTransactionFlow(counterparty, twiceSignedTx))
|
subFlow(SendTransactionFlow(counterparty, twiceSignedTx))
|
||||||
|
|
||||||
@ -401,7 +403,8 @@ object FlowCookbook {
|
|||||||
val verifiedTransaction = subFlow(ReceiveTransactionFlow(counterparty))
|
val verifiedTransaction = subFlow(ReceiveTransactionFlow(counterparty))
|
||||||
// DOCEND 13
|
// 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
|
// DOCSTART 14
|
||||||
subFlow(SendStateAndRefFlow(counterparty, dummyStates))
|
subFlow(SendStateAndRefFlow(counterparty, dummyStates))
|
||||||
|
|
||||||
@ -409,24 +412,10 @@ object FlowCookbook {
|
|||||||
val resolvedStateAndRef = subFlow(ReceiveStateAndRefFlow<DummyState>(counterparty))
|
val resolvedStateAndRef = subFlow(ReceiveStateAndRefFlow<DummyState>(counterparty))
|
||||||
// DOCEND 14
|
// DOCEND 14
|
||||||
|
|
||||||
// A ``SignedTransaction`` is a pairing of a ``WireTransaction``
|
// We can now verify the transaction to ensure that it satisfies
|
||||||
// with signatures over this ``WireTransaction``. We don't verify
|
// the contracts of all the transaction's input and output states.
|
||||||
// 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.
|
|
||||||
// DOCSTART 33
|
// DOCSTART 33
|
||||||
ledgerTx.verify()
|
twiceSignedTx.verify(serviceHub)
|
||||||
// DOCEND 33
|
// DOCEND 33
|
||||||
|
|
||||||
// We'll often want to perform our own additional verification
|
// 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
|
// rules and requires our signature doesn't mean we have to
|
||||||
// sign it! We need to make sure the transaction represents an
|
// sign it! We need to make sure the transaction represents an
|
||||||
// agreement we actually want to enter into.
|
// 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
|
// DOCSTART 34
|
||||||
val outputState: DummyState = wireTx.outputsOfType<DummyState>().single()
|
val outputState: DummyState = ledgerTx.outputsOfType<DummyState>().single()
|
||||||
if (outputState.magicNumber == 777) {
|
if (outputState.magicNumber == 777) {
|
||||||
// ``FlowException`` is a special exception type. It will be
|
// ``FlowException`` is a special exception type. It will be
|
||||||
// propagated back to any counterparty flows waiting for a
|
// 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