Updates the transaction API page and cookbook.

This commit is contained in:
Joel Dudley 2017-08-25 17:15:39 +01:00 committed by GitHub
parent 0fb4465c10
commit 701c4f3c60
4 changed files with 99 additions and 112 deletions

View File

@ -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
^^^^^^^^^^^^^^^^^^^^^^^^^^ ^^^^^^^^^^^^^^^^^^^^^^^^^^

View File

@ -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

View File

@ -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