mirror of
https://github.com/corda/corda.git
synced 2025-04-07 11:27:01 +00:00
Joel integrate cookbook into TX API
This commit is contained in:
parent
984fbd8995
commit
67ccf69dbb
@ -43,10 +43,10 @@ Transaction workflow
|
||||
--------------------
|
||||
There are four states the transaction can occupy:
|
||||
|
||||
* ``TransactionBuilder``, a mutable transaction-in-construction
|
||||
* ``TransactionBuilder``, a builder for a transaction in construction
|
||||
* ``WireTransaction``, an immutable transaction
|
||||
* ``SignedTransaction``, a ``WireTransaction`` with 1+ associated signatures
|
||||
* ``LedgerTransaction``, a resolved ``WireTransaction`` that can be checked for contract validity
|
||||
* ``SignedTransaction``, an immutable transaction with 1+ associated signatures
|
||||
* ``LedgerTransaction``, a transaction that can be checked for validity
|
||||
|
||||
Here are the possible transitions between transaction states:
|
||||
|
||||
@ -56,25 +56,198 @@ TransactionBuilder
|
||||
------------------
|
||||
Creating a builder
|
||||
^^^^^^^^^^^^^^^^^^
|
||||
The first step when building a transaction is to create a ``TransactionBuilder``:
|
||||
The first step when creating a transaction is to instantiate a ``TransactionBuilder``. We can create a builder for each
|
||||
transaction type as follows:
|
||||
|
||||
.. container:: codeset
|
||||
|
||||
.. sourcecode:: kotlin
|
||||
.. literalinclude:: ../../docs/source/example-code/src/main/kotlin/net/corda/docs/FlowCookbook.kt
|
||||
:language: kotlin
|
||||
:start-after: DOCSTART 19
|
||||
:end-before: DOCEND 19
|
||||
:dedent: 12
|
||||
|
||||
// A general transaction builder.
|
||||
val generalTxBuilder = TransactionType.General.Builder()
|
||||
.. literalinclude:: ../../docs/source/example-code/src/main/java/net/corda/docs/FlowCookbookJava.java
|
||||
:language: java
|
||||
:start-after: DOCSTART 19
|
||||
:end-before: DOCEND 19
|
||||
:dedent: 12
|
||||
|
||||
// A notary-change transaction builder.
|
||||
val notaryChangeTxBuilder = TransactionType.NotaryChange.Builder()
|
||||
Transaction components
|
||||
^^^^^^^^^^^^^^^^^^^^^^
|
||||
Once we have a ``TransactionBuilder``, we need to gather together the various transaction components the transaction
|
||||
will include.
|
||||
|
||||
.. sourcecode:: java
|
||||
Input states
|
||||
~~~~~~~~~~~~
|
||||
Input states are added to a transaction as ``StateAndRef`` instances. A ``StateAndRef`` combines:
|
||||
|
||||
// A general transaction builder.
|
||||
final TransactionBuilder generalTxBuilder = new TransactionType.General.Builder();
|
||||
* A ``ContractState`` representing the input state itself
|
||||
* A ``StateRef`` pointing to the input among the outputs of the transaction that created it
|
||||
|
||||
// A notary-change transaction builder.
|
||||
final TransactionBuilder notaryChangeTxBuilder = new TransactionType.NotaryChange.Builder();
|
||||
.. container:: codeset
|
||||
|
||||
.. literalinclude:: ../../docs/source/example-code/src/main/kotlin/net/corda/docs/FlowCookbook.kt
|
||||
:language: kotlin
|
||||
:start-after: DOCSTART 21
|
||||
:end-before: DOCEND 21
|
||||
:dedent: 12
|
||||
|
||||
.. literalinclude:: ../../docs/source/example-code/src/main/java/net/corda/docs/FlowCookbookJava.java
|
||||
:language: java
|
||||
:start-after: DOCSTART 21
|
||||
:end-before: DOCEND 21
|
||||
:dedent: 12
|
||||
|
||||
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
|
||||
|
||||
.. container:: codeset
|
||||
|
||||
.. literalinclude:: ../../docs/source/example-code/src/main/kotlin/net/corda/docs/FlowCookbook.kt
|
||||
:language: kotlin
|
||||
:start-after: DOCSTART 20
|
||||
:end-before: DOCEND 20
|
||||
:dedent: 12
|
||||
|
||||
.. literalinclude:: ../../docs/source/example-code/src/main/java/net/corda/docs/FlowCookbookJava.java
|
||||
:language: java
|
||||
:start-after: DOCSTART 20
|
||||
:end-before: DOCEND 20
|
||||
:dedent: 12
|
||||
|
||||
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:
|
||||
|
||||
.. container:: codeset
|
||||
|
||||
.. literalinclude:: ../../docs/source/example-code/src/main/kotlin/net/corda/docs/FlowCookbook.kt
|
||||
:language: kotlin
|
||||
:start-after: DOCSTART 22
|
||||
:end-before: DOCEND 22
|
||||
:dedent: 12
|
||||
|
||||
.. literalinclude:: ../../docs/source/example-code/src/main/java/net/corda/docs/FlowCookbookJava.java
|
||||
:language: java
|
||||
:start-after: DOCSTART 22
|
||||
:end-before: DOCEND 22
|
||||
:dedent: 12
|
||||
|
||||
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:
|
||||
|
||||
.. container:: codeset
|
||||
|
||||
.. literalinclude:: ../../docs/source/example-code/src/main/kotlin/net/corda/docs/FlowCookbook.kt
|
||||
:language: kotlin
|
||||
:start-after: DOCSTART 23
|
||||
:end-before: DOCEND 23
|
||||
:dedent: 12
|
||||
|
||||
.. literalinclude:: ../../docs/source/example-code/src/main/java/net/corda/docs/FlowCookbookJava.java
|
||||
:language: java
|
||||
:start-after: DOCSTART 23
|
||||
:end-before: DOCEND 23
|
||||
:dedent: 12
|
||||
|
||||
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
|
||||
|
||||
.. container:: codeset
|
||||
|
||||
.. literalinclude:: ../../docs/source/example-code/src/main/kotlin/net/corda/docs/FlowCookbook.kt
|
||||
:language: kotlin
|
||||
:start-after: DOCSTART 24
|
||||
:end-before: DOCEND 24
|
||||
:dedent: 12
|
||||
|
||||
.. literalinclude:: ../../docs/source/example-code/src/main/java/net/corda/docs/FlowCookbookJava.java
|
||||
:language: java
|
||||
:start-after: DOCSTART 24
|
||||
:end-before: DOCEND 24
|
||||
:dedent: 12
|
||||
|
||||
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:
|
||||
|
||||
.. container:: codeset
|
||||
|
||||
.. literalinclude:: ../../docs/source/example-code/src/main/kotlin/net/corda/docs/FlowCookbook.kt
|
||||
:language: kotlin
|
||||
:start-after: DOCSTART 25
|
||||
:end-before: DOCEND 25
|
||||
:dedent: 12
|
||||
|
||||
.. literalinclude:: ../../docs/source/example-code/src/main/java/net/corda/docs/FlowCookbookJava.java
|
||||
:language: java
|
||||
:start-after: DOCSTART 25
|
||||
:end-before: DOCEND 25
|
||||
:dedent: 12
|
||||
|
||||
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:
|
||||
|
||||
.. container:: codeset
|
||||
|
||||
.. literalinclude:: ../../docs/source/example-code/src/main/kotlin/net/corda/docs/FlowCookbook.kt
|
||||
:language: kotlin
|
||||
:start-after: DOCSTART 26
|
||||
:end-before: DOCEND 26
|
||||
:dedent: 12
|
||||
|
||||
.. literalinclude:: ../../docs/source/example-code/src/main/java/net/corda/docs/FlowCookbookJava.java
|
||||
:language: java
|
||||
:start-after: DOCSTART 26
|
||||
:end-before: DOCEND 26
|
||||
:dedent: 12
|
||||
|
||||
We can also define a time window as an ``Instant`` +/- a time tolerance (e.g. 30 seconds):
|
||||
|
||||
.. container:: codeset
|
||||
|
||||
.. literalinclude:: ../../docs/source/example-code/src/main/kotlin/net/corda/docs/FlowCookbook.kt
|
||||
:language: kotlin
|
||||
:start-after: DOCSTART 42
|
||||
:end-before: DOCEND 42
|
||||
:dedent: 12
|
||||
|
||||
.. literalinclude:: ../../docs/source/example-code/src/main/java/net/corda/docs/FlowCookbookJava.java
|
||||
:language: java
|
||||
:start-after: DOCSTART 42
|
||||
:end-before: DOCEND 42
|
||||
:dedent: 12
|
||||
|
||||
Or as a start-time plus a duration:
|
||||
|
||||
.. container:: codeset
|
||||
|
||||
.. literalinclude:: ../../docs/source/example-code/src/main/kotlin/net/corda/docs/FlowCookbook.kt
|
||||
:language: kotlin
|
||||
:start-after: DOCSTART 43
|
||||
:end-before: DOCEND 43
|
||||
:dedent: 12
|
||||
|
||||
.. literalinclude:: ../../docs/source/example-code/src/main/java/net/corda/docs/FlowCookbookJava.java
|
||||
:language: java
|
||||
:start-after: DOCSTART 43
|
||||
:end-before: DOCEND 43
|
||||
:dedent: 12
|
||||
|
||||
Adding items
|
||||
^^^^^^^^^^^^
|
||||
@ -95,56 +268,69 @@ The transaction builder is mutable. We add items to it using the ``TransactionBu
|
||||
|
||||
Passing in objects of any other type will cause an ``IllegalArgumentException`` to be thrown.
|
||||
|
||||
You can also add the following items to the transaction:
|
||||
|
||||
* ``TimeWindow`` objects, using ``TransactionBuilder.setTime``
|
||||
* ``SecureHash`` objects referencing the hash of an attachment stored on the node, using
|
||||
``TransactionBuilder.addAttachment``
|
||||
|
||||
Input states
|
||||
~~~~~~~~~~~~
|
||||
Input states are added to a transaction as ``StateAndRef`` instances, rather than as ``ContractState`` instances.
|
||||
|
||||
A ``StateAndRef`` combines a ``ContractState`` with a pointer to the transaction that created it. This series of
|
||||
pointers from the input states back to the transactions that created them is what allows a node to work backwards and
|
||||
verify the entirety of the transaction chain. It is defined as:
|
||||
Here's an example usage of ``TransactionBuilder.withItems``:
|
||||
|
||||
.. container:: codeset
|
||||
|
||||
.. literalinclude:: ../../core/src/main/kotlin/net/corda/core/contracts/Structures.kt
|
||||
.. literalinclude:: ../../docs/source/example-code/src/main/kotlin/net/corda/docs/FlowCookbook.kt
|
||||
:language: kotlin
|
||||
:start-after: DOCSTART 7
|
||||
:end-before: DOCEND 7
|
||||
:start-after: DOCSTART 27
|
||||
:end-before: DOCEND 27
|
||||
:dedent: 12
|
||||
|
||||
Where ``StateRef`` is defined as:
|
||||
.. literalinclude:: ../../docs/source/example-code/src/main/java/net/corda/docs/FlowCookbookJava.java
|
||||
:language: java
|
||||
:start-after: DOCSTART 27
|
||||
:end-before: DOCEND 27
|
||||
:dedent: 12
|
||||
|
||||
You can also pass in objects one-by-one. This is the only way to add attachments:
|
||||
|
||||
.. container:: codeset
|
||||
|
||||
.. literalinclude:: ../../core/src/main/kotlin/net/corda/core/contracts/Structures.kt
|
||||
.. literalinclude:: ../../docs/source/example-code/src/main/kotlin/net/corda/docs/FlowCookbook.kt
|
||||
:language: kotlin
|
||||
:start-after: DOCSTART 8
|
||||
:end-before: DOCEND 8
|
||||
:start-after: DOCSTART 28
|
||||
:end-before: DOCEND 28
|
||||
:dedent: 12
|
||||
|
||||
``StateRef.index`` is the state's position in the outputs of the transaction that created it. In this way, a
|
||||
``StateRef`` allows a notary service to uniquely identify the existing states that a transaction is marking as historic.
|
||||
.. literalinclude:: ../../docs/source/example-code/src/main/java/net/corda/docs/FlowCookbookJava.java
|
||||
:language: java
|
||||
:start-after: DOCSTART 28
|
||||
:end-before: DOCEND 28
|
||||
:dedent: 12
|
||||
|
||||
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.
|
||||
|
||||
Commands
|
||||
~~~~~~~~
|
||||
Commands are added to the transaction as ``Command`` instances. ``Command`` combines a ``CommandData``
|
||||
instance representing the type of the command with a list of the command's required signers. It is defined as:
|
||||
To set the transaction builder's time-window, we can either set a time-window directly:
|
||||
|
||||
.. container:: codeset
|
||||
|
||||
.. literalinclude:: ../../core/src/main/kotlin/net/corda/core/contracts/Structures.kt
|
||||
.. literalinclude:: ../../docs/source/example-code/src/main/kotlin/net/corda/docs/FlowCookbook.kt
|
||||
:language: kotlin
|
||||
:start-after: DOCSTART 9
|
||||
:end-before: DOCEND 9
|
||||
:start-after: DOCSTART 44
|
||||
:end-before: DOCEND 44
|
||||
:dedent: 12
|
||||
|
||||
.. literalinclude:: ../../docs/source/example-code/src/main/java/net/corda/docs/FlowCookbookJava.java
|
||||
:language: java
|
||||
:start-after: DOCSTART 44
|
||||
:end-before: DOCEND 44
|
||||
:dedent: 12
|
||||
|
||||
Or define the time-window as a time plus a duration (e.g. 45 seconds):
|
||||
|
||||
.. container:: codeset
|
||||
|
||||
.. literalinclude:: ../../docs/source/example-code/src/main/kotlin/net/corda/docs/FlowCookbook.kt
|
||||
:language: kotlin
|
||||
:start-after: DOCSTART 45
|
||||
:end-before: DOCEND 45
|
||||
:dedent: 12
|
||||
|
||||
.. literalinclude:: ../../docs/source/example-code/src/main/java/net/corda/docs/FlowCookbookJava.java
|
||||
:language: java
|
||||
:start-after: DOCSTART 45
|
||||
:end-before: DOCEND 45
|
||||
:dedent: 12
|
||||
|
||||
Signing the builder
|
||||
^^^^^^^^^^^^^^^^^^^
|
||||
@ -152,32 +338,42 @@ Once the builder is ready, we finalize it by signing it and converting it into a
|
||||
|
||||
.. container:: codeset
|
||||
|
||||
.. sourcecode:: kotlin
|
||||
.. literalinclude:: ../../docs/source/example-code/src/main/kotlin/net/corda/docs/FlowCookbook.kt
|
||||
:language: kotlin
|
||||
:start-after: DOCSTART 29
|
||||
:end-before: DOCEND 29
|
||||
:dedent: 12
|
||||
|
||||
// Finalizes the builder by signing it with our primary signing key.
|
||||
val signedTx1 = serviceHub.signInitialTransaction(unsignedTx)
|
||||
.. literalinclude:: ../../docs/source/example-code/src/main/java/net/corda/docs/FlowCookbookJava.java
|
||||
:language: java
|
||||
:start-after: DOCSTART 29
|
||||
:end-before: DOCEND 29
|
||||
:dedent: 12
|
||||
|
||||
// Finalizes the builder by signing it with a different key.
|
||||
val signedTx2 = serviceHub.signInitialTransaction(unsignedTx, otherKey)
|
||||
This will sign the transaction with your legal identity key. You can also choose to use another one of your public keys:
|
||||
|
||||
// Finalizes the builder by signing it with a set of keys.
|
||||
val signedTx3 = serviceHub.signInitialTransaction(unsignedTx, otherKeys)
|
||||
.. container:: codeset
|
||||
|
||||
.. sourcecode:: java
|
||||
.. literalinclude:: ../../docs/source/example-code/src/main/kotlin/net/corda/docs/FlowCookbook.kt
|
||||
:language: kotlin
|
||||
:start-after: DOCSTART 30
|
||||
:end-before: DOCEND 30
|
||||
:dedent: 12
|
||||
|
||||
// Finalizes the builder by signing it with our primary signing key.
|
||||
final SignedTransaction signedTx1 = getServiceHub().signInitialTransaction(unsignedTx);
|
||||
.. literalinclude:: ../../docs/source/example-code/src/main/java/net/corda/docs/FlowCookbookJava.java
|
||||
:language: java
|
||||
:start-after: DOCSTART 30
|
||||
:end-before: DOCEND 30
|
||||
:dedent: 12
|
||||
|
||||
// Finalizes the builder by signing it with a different key.
|
||||
final SignedTransaction signedTx2 = getServiceHub().signInitialTransaction(unsignedTx, otherKey);
|
||||
|
||||
// Finalizes the builder by signing it with a set of keys.
|
||||
final SignedTransaction signedTx3 = getServiceHub().signInitialTransaction(unsignedTx, otherKeys);
|
||||
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 ``WireTransaction`` and a list of signatures over that
|
||||
transaction:
|
||||
A ``SignedTransaction`` is a combination of:
|
||||
|
||||
* An immutable ``WireTransaction``
|
||||
* A list of signatures over that transaction
|
||||
|
||||
.. container:: codeset
|
||||
|
||||
@ -186,114 +382,210 @@ transaction:
|
||||
:start-after: DOCSTART 1
|
||||
:end-before: DOCEND 1
|
||||
|
||||
Before adding our signature to the transaction, we'll want to verify both the transaction itself and its signatures.
|
||||
|
||||
Verifying the transaction
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
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 ``ResolveTransactionsFlow``. 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
|
||||
states:
|
||||
|
||||
.. container:: codeset
|
||||
|
||||
.. literalinclude:: ../../docs/source/example-code/src/main/kotlin/net/corda/docs/FlowCookbook.kt
|
||||
:language: kotlin
|
||||
:start-after: DOCSTART 33
|
||||
:end-before: DOCEND 33
|
||||
:dedent: 12
|
||||
|
||||
.. literalinclude:: ../../docs/source/example-code/src/main/java/net/corda/docs/FlowCookbookJava.java
|
||||
:language: java
|
||||
:start-after: DOCSTART 33
|
||||
: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:
|
||||
|
||||
.. container:: codeset
|
||||
|
||||
.. literalinclude:: ../../docs/source/example-code/src/main/kotlin/net/corda/docs/FlowCookbook.kt
|
||||
:language: kotlin
|
||||
:start-after: DOCSTART 34
|
||||
:end-before: DOCEND 34
|
||||
:dedent: 12
|
||||
|
||||
.. literalinclude:: ../../docs/source/example-code/src/main/java/net/corda/docs/FlowCookbookJava.java
|
||||
:language: java
|
||||
:start-after: DOCSTART 34
|
||||
:end-before: DOCEND 34
|
||||
:dedent: 12
|
||||
|
||||
Verifying the signatures
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
The signatures on a ``SignedTransaction`` have not necessarily been checked for validity. We check them using
|
||||
We also need to verify the signatures over the transaction to prevent tampering. We do this using
|
||||
``SignedTransaction.verifySignatures``:
|
||||
|
||||
.. container:: codeset
|
||||
|
||||
.. literalinclude:: ../../core/src/main/kotlin/net/corda/core/transactions/SignedTransaction.kt
|
||||
.. literalinclude:: ../../docs/source/example-code/src/main/kotlin/net/corda/docs/FlowCookbook.kt
|
||||
:language: kotlin
|
||||
:start-after: DOCSTART 2
|
||||
:end-before: DOCEND 2
|
||||
:start-after: DOCSTART 35
|
||||
:end-before: DOCEND 35
|
||||
:dedent: 12
|
||||
|
||||
``verifySignatures`` takes a ``vararg`` of the public keys for which the signatures are allowed to be missing. If the
|
||||
transaction is missing any signatures without the corresponding public keys being passed in, a
|
||||
.. literalinclude:: ../../docs/source/example-code/src/main/java/net/corda/docs/FlowCookbookJava.java
|
||||
:language: java
|
||||
:start-after: DOCSTART 35
|
||||
:end-before: DOCEND 35
|
||||
:dedent: 12
|
||||
|
||||
Optionally, we can pass ``verifySignatures`` a ``vararg`` of the public keys for which the signatures are allowed
|
||||
to be missing:
|
||||
|
||||
.. container:: codeset
|
||||
|
||||
.. literalinclude:: ../../docs/source/example-code/src/main/kotlin/net/corda/docs/FlowCookbook.kt
|
||||
:language: kotlin
|
||||
:start-after: DOCSTART 36
|
||||
:end-before: DOCEND 36
|
||||
:dedent: 12
|
||||
|
||||
.. literalinclude:: ../../docs/source/example-code/src/main/java/net/corda/docs/FlowCookbookJava.java
|
||||
:language: java
|
||||
:start-after: DOCSTART 36
|
||||
:end-before: DOCEND 36
|
||||
:dedent: 12
|
||||
|
||||
If the transaction is missing any signatures without the corresponding public keys being passed in, a
|
||||
``SignaturesMissingException`` is thrown.
|
||||
|
||||
Verifying the transaction
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
Verifying a transaction is a multi-step process:
|
||||
|
||||
* We check the transaction's signatures:
|
||||
We can also choose to simply verify the signatures that are present:
|
||||
|
||||
.. container:: codeset
|
||||
|
||||
.. sourcecode:: kotlin
|
||||
.. literalinclude:: ../../docs/source/example-code/src/main/kotlin/net/corda/docs/FlowCookbook.kt
|
||||
:language: kotlin
|
||||
:start-after: DOCSTART 37
|
||||
:end-before: DOCEND 37
|
||||
:dedent: 12
|
||||
|
||||
subFlow(ResolveTransactionsFlow(transactionToVerify, partyWithTheFullChain))
|
||||
.. literalinclude:: ../../docs/source/example-code/src/main/java/net/corda/docs/FlowCookbookJava.java
|
||||
:language: java
|
||||
:start-after: DOCSTART 37
|
||||
:end-before: DOCEND 37
|
||||
:dedent: 12
|
||||
|
||||
.. sourcecode:: java
|
||||
|
||||
subFlow(new ResolveTransactionsFlow(transactionToVerify, partyWithTheFullChain));
|
||||
|
||||
* Before verifying the transaction, we need to retrieve from the proposer(s) of the transaction any parts of the
|
||||
transaction chain that our node doesn't currently have in its local storage:
|
||||
|
||||
.. container:: codeset
|
||||
|
||||
.. sourcecode:: kotlin
|
||||
|
||||
subFlow(ResolveTransactionsFlow(transactionToVerify, partyWithTheFullChain))
|
||||
|
||||
.. sourcecode:: java
|
||||
|
||||
subFlow(new ResolveTransactionsFlow(transactionToVerify, partyWithTheFullChain));
|
||||
|
||||
* To verify the transaction, we first need to resolve any state references and attachment hashes by converting the
|
||||
``SignedTransaction`` into a ``LedgerTransaction``. We can then verify the fully-resolved transaction:
|
||||
|
||||
.. container:: codeset
|
||||
|
||||
.. sourcecode:: kotlin
|
||||
|
||||
partSignedTx.tx.toLedgerTransaction(serviceHub).verify()
|
||||
|
||||
.. sourcecode:: java
|
||||
|
||||
partSignedTx.getTx().toLedgerTransaction(getServiceHub()).verify();
|
||||
|
||||
* We will generally also want to conduct some custom validation of the transaction, beyond what is provided for in the
|
||||
contract:
|
||||
|
||||
.. container:: codeset
|
||||
|
||||
.. sourcecode:: kotlin
|
||||
|
||||
val ledgerTransaction = partSignedTx.tx.toLedgerTransaction(serviceHub)
|
||||
val inputStateAndRef = ledgerTransaction.inputs.single()
|
||||
val input = inputStateAndRef.state.data as MyState
|
||||
if (input.value > 1000000) {
|
||||
throw FlowException("Proposed input value too high!")
|
||||
}
|
||||
|
||||
.. sourcecode:: java
|
||||
|
||||
final LedgerTransaction ledgerTransaction = partSignedTx.getTx().toLedgerTransaction(getServiceHub());
|
||||
final StateAndRef inputStateAndRef = ledgerTransaction.getInputs().get(0);
|
||||
final MyState input = (MyState) inputStateAndRef.getState().getData();
|
||||
if (input.getValue() > 1000000) {
|
||||
throw new FlowException("Proposed input value too high!");
|
||||
}
|
||||
However, BE VERY CAREFUL - this function provides no guarantees that the signatures are correct, or that none are
|
||||
missing.
|
||||
|
||||
Signing the transaction
|
||||
^^^^^^^^^^^^^^^^^^^^^^^
|
||||
We add an additional signature to an existing ``SignedTransaction`` using:
|
||||
Once we are satisfied with the contents and existing signatures over the transaction, we can add our signature to the
|
||||
``SignedTransaction`` using:
|
||||
|
||||
.. container:: codeset
|
||||
|
||||
.. sourcecode:: kotlin
|
||||
.. literalinclude:: ../../docs/source/example-code/src/main/kotlin/net/corda/docs/FlowCookbook.kt
|
||||
:language: kotlin
|
||||
:start-after: DOCSTART 38
|
||||
:end-before: DOCEND 38
|
||||
:dedent: 12
|
||||
|
||||
val fullySignedTx = serviceHub.addSignature(partSignedTx)
|
||||
.. literalinclude:: ../../docs/source/example-code/src/main/java/net/corda/docs/FlowCookbookJava.java
|
||||
:language: java
|
||||
:start-after: DOCSTART 38
|
||||
:end-before: DOCEND 38
|
||||
:dedent: 12
|
||||
|
||||
.. sourcecode:: java
|
||||
As with the ``TransactionBuilder``, we can also choose to sign using another one of our public keys:
|
||||
|
||||
SignedTransaction fullySignedTx = getServiceHub().addSignature(partSignedTx);
|
||||
.. container:: codeset
|
||||
|
||||
.. literalinclude:: ../../docs/source/example-code/src/main/kotlin/net/corda/docs/FlowCookbook.kt
|
||||
:language: kotlin
|
||||
:start-after: DOCSTART 39
|
||||
:end-before: DOCEND 39
|
||||
:dedent: 12
|
||||
|
||||
.. literalinclude:: ../../docs/source/example-code/src/main/java/net/corda/docs/FlowCookbookJava.java
|
||||
:language: java
|
||||
:start-after: DOCSTART 39
|
||||
:end-before: DOCEND 39
|
||||
:dedent: 12
|
||||
|
||||
We can also generate a signature over the transaction without adding it to the transaction directly by using:
|
||||
|
||||
.. container:: codeset
|
||||
|
||||
.. sourcecode:: kotlin
|
||||
.. literalinclude:: ../../docs/source/example-code/src/main/kotlin/net/corda/docs/FlowCookbook.kt
|
||||
:language: kotlin
|
||||
:start-after: DOCSTART 40
|
||||
:end-before: DOCEND 40
|
||||
:dedent: 12
|
||||
|
||||
val signature = serviceHub.createSignature(partSignedTx)
|
||||
.. literalinclude:: ../../docs/source/example-code/src/main/java/net/corda/docs/FlowCookbookJava.java
|
||||
:language: java
|
||||
:start-after: DOCSTART 40
|
||||
:end-before: DOCEND 40
|
||||
:dedent: 12
|
||||
|
||||
.. sourcecode:: java
|
||||
Or using another one of our public keys, as follows:
|
||||
|
||||
DigitalSignature.WithKey signature = getServiceHub().createSignature(partSignedTx);
|
||||
.. container:: codeset
|
||||
|
||||
.. literalinclude:: ../../docs/source/example-code/src/main/kotlin/net/corda/docs/FlowCookbook.kt
|
||||
:language: kotlin
|
||||
:start-after: DOCSTART 41
|
||||
:end-before: DOCEND 41
|
||||
:dedent: 12
|
||||
|
||||
.. literalinclude:: ../../docs/source/example-code/src/main/java/net/corda/docs/FlowCookbookJava.java
|
||||
:language: java
|
||||
:start-after: DOCSTART 41
|
||||
:end-before: DOCEND 41
|
||||
:dedent: 12
|
||||
|
||||
Notarising and recording
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
Notarising and recording a transaction is handled by a built-in flow called ``FinalityFlow``. See
|
||||
:doc:`api-flows` for more details.
|
||||
:doc:`api-flows` for more details.
|
||||
|
@ -9,6 +9,7 @@ import net.corda.core.contracts.TransactionType.General;
|
||||
import net.corda.core.contracts.TransactionType.NotaryChange;
|
||||
import net.corda.core.contracts.testing.DummyContract;
|
||||
import net.corda.core.contracts.testing.DummyState;
|
||||
import net.corda.core.crypto.DigitalSignature;
|
||||
import net.corda.core.crypto.SecureHash;
|
||||
import net.corda.core.flows.*;
|
||||
import net.corda.core.identity.Party;
|
||||
@ -30,6 +31,7 @@ import net.corda.flows.SignTransactionFlow;
|
||||
import org.bouncycastle.asn1.x500.X500Name;
|
||||
|
||||
import java.security.PublicKey;
|
||||
import java.security.SignatureException;
|
||||
import java.time.Duration;
|
||||
import java.time.Instant;
|
||||
import java.util.List;
|
||||
@ -83,6 +85,7 @@ public class FlowCookbookJava {
|
||||
return CollectSignaturesFlow.Companion.tracker();
|
||||
}
|
||||
};
|
||||
private static final Step VERIFYING_SIGS = new Step("Verifying a transaction's signatures.");
|
||||
private static final Step FINALISATION = new Step("Finalising a transaction.") {
|
||||
@Override public ProgressTracker childProgressTracker() {
|
||||
return FinalityFlow.Companion.tracker();
|
||||
@ -236,10 +239,16 @@ public class FlowCookbookJava {
|
||||
// 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.
|
||||
// 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.
|
||||
// DOCSTART 20
|
||||
StateRef ourStateRef = new StateRef(SecureHash.sha256("DummyTransactionHash"), 0);
|
||||
// DOCEND 20
|
||||
// A ``StateAndRef`` pairs a ``StateRef`` with the state it points to.
|
||||
// DOCSTART 21
|
||||
StateAndRef ourStateAndRef = getServiceHub().toStateAndRef(ourStateRef);
|
||||
// DOCEND 21
|
||||
|
||||
/*------------------------------------------
|
||||
* GATHERING OTHER TRANSACTION COMPONENTS *
|
||||
@ -247,18 +256,24 @@ public class FlowCookbookJava {
|
||||
progressTracker.setCurrentStep(OTHER_TX_COMPONENTS);
|
||||
|
||||
// Output states are constructed from scratch.
|
||||
// DOCSTART 22
|
||||
DummyState ourOutput = new DummyState();
|
||||
// DOCEND 22
|
||||
// Or as copies of other states with some properties changed.
|
||||
// DOCSTART 23
|
||||
DummyState ourOtherOutput = ourOutput.copy(77);
|
||||
// DOCEND 23
|
||||
|
||||
// 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.
|
||||
// DOCSTART 24
|
||||
CommandData commandData = new DummyContract.Commands.Create();
|
||||
PublicKey ourPubKey = getServiceHub().getLegalIdentityKey();
|
||||
PublicKey counterpartyPubKey = counterparty.getOwningKey();
|
||||
List<PublicKey> requiredSigners = ImmutableList.of(ourPubKey, counterpartyPubKey);
|
||||
Command ourCommand = new Command(commandData, requiredSigners);
|
||||
// DOCEND 24
|
||||
|
||||
// ``CommandData`` can either be:
|
||||
// 1. Of type ``TypeOnlyCommandData``, in which case it only
|
||||
@ -272,12 +287,28 @@ public class FlowCookbookJava {
|
||||
// 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.
|
||||
// DOCSTART 25
|
||||
SecureHash ourAttachment = SecureHash.sha256("DummyAttachment");
|
||||
// DOCEND 25
|
||||
|
||||
// Time windows can have a start and end time, or be open at either end.
|
||||
// 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.
|
||||
// DOCSTART 26
|
||||
TimeWindow ourTimeWindow = TimeWindow.between(Instant.MIN, Instant.MAX);
|
||||
TimeWindow ourAfter = TimeWindow.fromOnly(Instant.MIN);
|
||||
TimeWindow ourBefore = TimeWindow.untilOnly(Instant.MAX);
|
||||
// DOCEND 26
|
||||
|
||||
// We can also define a time window as an ``Instant`` +/- a time
|
||||
// tolerance (e.g. 30 seconds):
|
||||
// DOCSTART 42
|
||||
TimeWindow ourTimeWindow2 = TimeWindow.withTolerance(Instant.now(), Duration.ofSeconds(30));
|
||||
// DOCEND 42
|
||||
// Or as a start-time plus a duration:
|
||||
// DOCSTART 43
|
||||
TimeWindow ourTimeWindow3 = TimeWindow.fromStartAndDuration(Instant.now(), Duration.ofSeconds(30));
|
||||
// DOCEND 43
|
||||
|
||||
/*------------------------
|
||||
* TRANSACTION BUILDING *
|
||||
@ -286,28 +317,40 @@ public class FlowCookbookJava {
|
||||
|
||||
// There are two types of transaction (notary-change and general),
|
||||
// and therefore two types of transaction builder:
|
||||
// DOCSTART 19
|
||||
TransactionBuilder notaryChangeTxBuilder = new TransactionBuilder(NotaryChange.INSTANCE, specificNotary);
|
||||
TransactionBuilder regTxBuilder = new TransactionBuilder(General.INSTANCE, specificNotary);
|
||||
// DOCEND 19
|
||||
|
||||
// We add items to the transaction builder using ``TransactionBuilder.withItems``:
|
||||
// DOCSTART 27
|
||||
regTxBuilder.withItems(
|
||||
// Inputs, as ``StateRef``s that reference to the outputs of previous transactions
|
||||
ourStateRef,
|
||||
ourStateAndRef,
|
||||
// Outputs, as ``ContractState``s
|
||||
ourOutput,
|
||||
// Commands, as ``Command``s
|
||||
ourCommand
|
||||
);
|
||||
// DOCEND 27
|
||||
|
||||
// We can also add items using methods for the individual components:
|
||||
// DOCSTART 28
|
||||
regTxBuilder.addInputState(ourStateAndRef);
|
||||
regTxBuilder.addOutputState(ourOutput);
|
||||
regTxBuilder.addCommand(ourCommand);
|
||||
regTxBuilder.addAttachment(ourAttachment);
|
||||
// DOCEND 28
|
||||
|
||||
// We set the time-window within which the transaction must be notarised using either of:
|
||||
// There are several ways of setting the transaction's time-window.
|
||||
// We can set a time-window directly:
|
||||
// DOCSTART 44
|
||||
regTxBuilder.setTimeWindow(ourTimeWindow);
|
||||
regTxBuilder.setTimeWindow(getServiceHub().getClock().instant(), Duration.ofSeconds(30));
|
||||
// DOCEND 44
|
||||
// Or as a start time plus a duration (e.g. 45 seconds):
|
||||
// DOCSTART 45
|
||||
regTxBuilder.setTimeWindow(Instant.now(), Duration.ofSeconds(45));
|
||||
// DOCEND 45
|
||||
|
||||
/*-----------------------
|
||||
* TRANSACTION SIGNING *
|
||||
@ -316,12 +359,40 @@ public class FlowCookbookJava {
|
||||
|
||||
// We finalise the transaction by signing it,
|
||||
// converting it into a ``SignedTransaction``.
|
||||
// DOCSTART 29
|
||||
SignedTransaction onceSignedTx = getServiceHub().signInitialTransaction(regTxBuilder);
|
||||
// DOCEND 29
|
||||
// We can also sign the transaction using a different public key:
|
||||
// DOCSTART 30
|
||||
PublicKey otherKey = getServiceHub().getKeyManagementService().freshKey();
|
||||
SignedTransaction onceSignedTx2 = getServiceHub().signInitialTransaction(regTxBuilder, otherKey);
|
||||
// DOCEND 30
|
||||
|
||||
// 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, dummyPubKey);
|
||||
// DOCSTART 38
|
||||
SignedTransaction twiceSignedTx = getServiceHub().addSignature(onceSignedTx);
|
||||
// DOCEND 38
|
||||
// Or, if we wanted to use a different public key:
|
||||
PublicKey otherKey2 = getServiceHub().getKeyManagementService().freshKey();
|
||||
// DOCSTART 39
|
||||
SignedTransaction twiceSignedTx2 = getServiceHub().addSignature(onceSignedTx, otherKey2);
|
||||
// DOCEND 39
|
||||
|
||||
// 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.
|
||||
// DOCSTART 40
|
||||
DigitalSignature.WithKey sig = getServiceHub().createSignature(onceSignedTx);
|
||||
// DOCEND 40
|
||||
// And again, if we wanted to use a different public key:
|
||||
// DOCSTART 41
|
||||
DigitalSignature.WithKey sig2 = getServiceHub().createSignature(onceSignedTx, otherKey2);
|
||||
// DOCEND 41
|
||||
|
||||
/*----------------------------
|
||||
* TRANSACTION VERIFICATION *
|
||||
@ -342,34 +413,41 @@ public class FlowCookbookJava {
|
||||
subFlow(new ResolveTransactionsFlow(ImmutableSet.of(ourStateRef.getTxhash()), counterparty));
|
||||
// DOCEND 14
|
||||
|
||||
// We verify a transaction using the following one-liner:
|
||||
twiceSignedTx.getTx().toLedgerTransaction(getServiceHub()).verify();
|
||||
|
||||
// Let's break that down...
|
||||
|
||||
// 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
|
||||
|
||||
// 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.");
|
||||
}
|
||||
// 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
|
||||
@ -390,6 +468,37 @@ public class FlowCookbookJava {
|
||||
SignedTransaction fullySignedTx = subFlow(new CollectSignaturesFlow(twiceSignedTx, SIGS_GATHERING.childProgressTracker()));
|
||||
// DOCEND 15
|
||||
|
||||
/*------------------------
|
||||
* VERIFYING SIGNATURES *
|
||||
------------------------*/
|
||||
progressTracker.setCurrentStep(VERIFYING_SIGS);
|
||||
|
||||
try {
|
||||
|
||||
// We can verify that a transaction has all the required
|
||||
// signatures, and that they're all valid, by running:
|
||||
// DOCSTART 35
|
||||
fullySignedTx.verifySignatures();
|
||||
// DOCEND 35
|
||||
|
||||
// If the transaction is only partially signed, we have to pass in
|
||||
// a list of the public keys corresponding to the missing
|
||||
// signatures, explicitly telling the system not to check them.
|
||||
// DOCSTART 36
|
||||
onceSignedTx.verifySignatures(counterpartyPubKey);
|
||||
// DOCEND 36
|
||||
|
||||
// We can also choose to only check the signatures that are
|
||||
// present. BE VERY CAREFUL - this function provides no guarantees
|
||||
// that the signatures are correct, or that none are missing.
|
||||
// DOCSTART 37
|
||||
twiceSignedTx.checkSignaturesAreValid();
|
||||
// DOCEND 37
|
||||
|
||||
} catch (SignatureException e) {
|
||||
// Handle this as required.
|
||||
}
|
||||
|
||||
/*------------------------------
|
||||
* FINALISING THE TRANSACTION *
|
||||
------------------------------*/
|
||||
@ -501,4 +610,4 @@ public class FlowCookbookJava {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -7,6 +7,7 @@ import net.corda.core.contracts.TransactionType.General
|
||||
import net.corda.core.contracts.TransactionType.NotaryChange
|
||||
import net.corda.core.contracts.testing.DummyContract
|
||||
import net.corda.core.contracts.testing.DummyState
|
||||
import net.corda.core.crypto.DigitalSignature
|
||||
import net.corda.core.crypto.SecureHash
|
||||
import net.corda.core.flows.*
|
||||
import net.corda.core.identity.Party
|
||||
@ -66,6 +67,7 @@ object FlowCookbook {
|
||||
// subflow's progress steps in our flow's progress tracker.
|
||||
override fun childProgressTracker() = CollectSignaturesFlow.tracker()
|
||||
}
|
||||
object VERIFYING_SIGS : Step("Verifying a transaction's signatures.")
|
||||
object FINALISATION : Step("Finalising a transaction.") {
|
||||
override fun childProgressTracker() = FinalityFlow.tracker()
|
||||
}
|
||||
@ -79,6 +81,7 @@ object FlowCookbook {
|
||||
TX_SIGNING,
|
||||
TX_VERIFICATION,
|
||||
SIGS_GATHERING,
|
||||
VERIFYING_SIGS,
|
||||
FINALISATION
|
||||
)
|
||||
}
|
||||
@ -219,10 +222,16 @@ object FlowCookbook {
|
||||
// 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.
|
||||
// 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.
|
||||
// DOCSTART 20
|
||||
val ourStateRef: StateRef = StateRef(SecureHash.sha256("DummyTransactionHash"), 0)
|
||||
// DOCEND 20
|
||||
// A ``StateAndRef`` pairs a ``StateRef`` with the state it points to.
|
||||
// DOCSTART 21
|
||||
val ourStateAndRef: StateAndRef<DummyState> = serviceHub.toStateAndRef<DummyState>(ourStateRef)
|
||||
// DOCEND 21
|
||||
|
||||
/**-----------------------------------------
|
||||
* GATHERING OTHER TRANSACTION COMPONENTS *
|
||||
@ -230,18 +239,24 @@ object FlowCookbook {
|
||||
progressTracker.currentStep = OTHER_TX_COMPONENTS
|
||||
|
||||
// Output states are constructed from scratch.
|
||||
// DOCSTART 22
|
||||
val ourOutput: DummyState = DummyState()
|
||||
// DOCEND 22
|
||||
// Or as copies of other states with some properties changed.
|
||||
// DOCSTART 23
|
||||
val ourOtherOutput: DummyState = ourOutput.copy(magicNumber = 77)
|
||||
// DOCEND 23
|
||||
|
||||
// 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.
|
||||
// DOCSTART 24
|
||||
val commandData: CommandData = DummyContract.Commands.Create()
|
||||
val ourPubKey: PublicKey = serviceHub.legalIdentityKey
|
||||
val counterpartyPubKey: PublicKey = counterparty.owningKey
|
||||
val requiredSigners: List<PublicKey> = listOf(ourPubKey, counterpartyPubKey)
|
||||
val ourCommand: Command = Command(commandData, requiredSigners)
|
||||
// DOCEND 24
|
||||
|
||||
// ``CommandData`` can either be:
|
||||
// 1. Of type ``TypeOnlyCommandData``, in which case it only
|
||||
@ -255,12 +270,26 @@ object FlowCookbook {
|
||||
// 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.
|
||||
// DOCSTART 25
|
||||
val ourAttachment: SecureHash = SecureHash.sha256("DummyAttachment")
|
||||
// DOCEND 25
|
||||
|
||||
// Time windows can have a start and end time, or be open at either end.
|
||||
// DOCSTART 26
|
||||
val ourTimeWindow: TimeWindow = TimeWindow.between(Instant.MIN, Instant.MAX)
|
||||
val ourAfter: TimeWindow = TimeWindow.fromOnly(Instant.MIN)
|
||||
val ourBefore: TimeWindow = TimeWindow.untilOnly(Instant.MAX)
|
||||
// DOCEND 26
|
||||
|
||||
// We can also define a time window as an ``Instant`` +/- a time
|
||||
// tolerance (e.g. 30 seconds):
|
||||
// DOCSTART 42
|
||||
val ourTimeWindow2: TimeWindow = TimeWindow.withTolerance(Instant.now(), Duration.ofSeconds(30))
|
||||
// DOCEND 42
|
||||
// Or as a start-time plus a duration:
|
||||
// DOCSTART 43
|
||||
val ourTimeWindow3: TimeWindow = TimeWindow.fromStartAndDuration(Instant.now(), Duration.ofSeconds(30))
|
||||
// DOCEND 43
|
||||
|
||||
/**-----------------------
|
||||
* TRANSACTION BUILDING *
|
||||
@ -269,28 +298,40 @@ object FlowCookbook {
|
||||
|
||||
// There are two types of transaction (notary-change and general),
|
||||
// and therefore two types of transaction builder:
|
||||
// DOCSTART 19
|
||||
val notaryChangeTxBuilder: TransactionBuilder = TransactionBuilder(NotaryChange, specificNotary)
|
||||
val regTxBuilder: TransactionBuilder = TransactionBuilder(General, specificNotary)
|
||||
// DOCEND 19
|
||||
|
||||
// We add items to the transaction builder using ``TransactionBuilder.withItems``:
|
||||
// DOCSTART 27
|
||||
regTxBuilder.withItems(
|
||||
// Inputs, as ``StateRef``s that reference to the outputs of previous transactions
|
||||
ourStateRef,
|
||||
// Inputs, as ``StateRef``s that reference the outputs of previous transactions
|
||||
ourStateAndRef,
|
||||
// Outputs, as ``ContractState``s
|
||||
ourOutput,
|
||||
// Commands, as ``Command``s
|
||||
ourCommand
|
||||
)
|
||||
// DOCEND 27
|
||||
|
||||
// We can also add items using methods for the individual components:
|
||||
// DOCSTART 28
|
||||
regTxBuilder.addInputState(ourStateAndRef)
|
||||
regTxBuilder.addOutputState(ourOutput)
|
||||
regTxBuilder.addCommand(ourCommand)
|
||||
regTxBuilder.addAttachment(ourAttachment)
|
||||
// DOCEND 28
|
||||
|
||||
// We set the time-window within which the transaction must be notarised using either of:
|
||||
// There are several ways of setting the transaction's time-window.
|
||||
// We can set a time-window directly:
|
||||
// DOCSTART 44
|
||||
regTxBuilder.setTimeWindow(ourTimeWindow)
|
||||
regTxBuilder.setTimeWindow(serviceHub.clock.instant(), Duration.ofSeconds(30))
|
||||
// DOCEND 44
|
||||
// Or as a start time plus a duration (e.g. 45 seconds):
|
||||
// DOCSTART 45
|
||||
regTxBuilder.setTimeWindow(serviceHub.clock.instant(), Duration.ofSeconds(45))
|
||||
// DOCEND 45
|
||||
|
||||
/**----------------------
|
||||
* TRANSACTION SIGNING *
|
||||
@ -299,12 +340,40 @@ object FlowCookbook {
|
||||
|
||||
// We finalise the transaction by signing it, converting it into a
|
||||
// ``SignedTransaction``.
|
||||
// DOCSTART 29
|
||||
val onceSignedTx: SignedTransaction = serviceHub.signInitialTransaction(regTxBuilder)
|
||||
// DOCEND 29
|
||||
// We can also sign the transaction using a different public key:
|
||||
// DOCSTART 30
|
||||
val otherKey: PublicKey = serviceHub.keyManagementService.freshKey()
|
||||
val onceSignedTx2: SignedTransaction = serviceHub.signInitialTransaction(regTxBuilder, otherKey)
|
||||
// DOCEND 30
|
||||
|
||||
// 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:
|
||||
val twiceSignedTx: SignedTransaction = serviceHub.addSignature(onceSignedTx, dummyPubKey)
|
||||
// DOCSTART 38
|
||||
val twiceSignedTx: SignedTransaction = serviceHub.addSignature(onceSignedTx)
|
||||
// DOCEND 38
|
||||
// Or, if we wanted to use a different public key:
|
||||
val otherKey2: PublicKey = serviceHub.keyManagementService.freshKey()
|
||||
// DOCSTART 39
|
||||
val twiceSignedTx2: SignedTransaction = serviceHub.addSignature(onceSignedTx, otherKey2)
|
||||
// DOCEND 39
|
||||
|
||||
// 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.
|
||||
// DOCSTART 40
|
||||
val sig: DigitalSignature.WithKey = serviceHub.createSignature(onceSignedTx)
|
||||
// DOCEND 40
|
||||
// And again, if we wanted to use a different public key:
|
||||
// DOCSTART 41
|
||||
val sig2: DigitalSignature.WithKey = serviceHub.createSignature(onceSignedTx, otherKey2)
|
||||
// DOCEND 41
|
||||
|
||||
// In practice, however, the process of gathering every signature
|
||||
// but the first can be automated using ``CollectSignaturesFlow``.
|
||||
@ -329,30 +398,32 @@ object FlowCookbook {
|
||||
subFlow(ResolveTransactionsFlow(setOf(ourStateRef.txhash), counterparty))
|
||||
// DOCEND 14
|
||||
|
||||
// We verify a transaction using the following one-liner:
|
||||
twiceSignedTx.tx.toLedgerTransaction(serviceHub).verify()
|
||||
|
||||
// Let's break that down...
|
||||
|
||||
// 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.
|
||||
// DOCSTART 33
|
||||
ledgerTx.verify()
|
||||
// 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.
|
||||
// DOCSTART 34
|
||||
val outputState: DummyState = wireTx.outputs.single().data as DummyState
|
||||
if (outputState.magicNumber == 777) {
|
||||
// ``FlowException`` is a special exception type. It will be
|
||||
@ -361,6 +432,7 @@ object FlowCookbook {
|
||||
// failed.
|
||||
throw FlowException("We expected a magic number of 777.")
|
||||
}
|
||||
// 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
|
||||
@ -381,6 +453,31 @@ object FlowCookbook {
|
||||
val fullySignedTx: SignedTransaction = subFlow(CollectSignaturesFlow(twiceSignedTx, SIGS_GATHERING.childProgressTracker()))
|
||||
// DOCEND 15
|
||||
|
||||
/**-----------------------
|
||||
* VERIFYING SIGNATURES *
|
||||
-----------------------**/
|
||||
progressTracker.currentStep = VERIFYING_SIGS
|
||||
|
||||
// We can verify that a transaction has all the required
|
||||
// signatures, and that they're all valid, by running:
|
||||
// DOCSTART 35
|
||||
fullySignedTx.verifySignatures()
|
||||
// DOCEND 35
|
||||
|
||||
// If the transaction is only partially signed, we have to pass in
|
||||
// a list of the public keys corresponding to the missing
|
||||
// signatures, explicitly telling the system not to check them.
|
||||
// DOCSTART 36
|
||||
onceSignedTx.verifySignatures(counterpartyPubKey)
|
||||
// DOCEND 36
|
||||
|
||||
// We can also choose to only check the signatures that are
|
||||
// present. BE VERY CAREFUL - this function provides no guarantees
|
||||
// that the signatures are correct, or that none are missing.
|
||||
// DOCSTART 37
|
||||
twiceSignedTx.checkSignaturesAreValid()
|
||||
// DOCEND 37
|
||||
|
||||
/**-----------------------------
|
||||
* FINALISING THE TRANSACTION *
|
||||
-----------------------------**/
|
||||
@ -477,4 +574,4 @@ object FlowCookbook {
|
||||
// we be handled automatically.
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user