Merge pull request #908 from corda/joel-cookbook

Flow cookbook added and merged into flow API page.
This commit is contained in:
Clinton 2017-06-23 17:23:26 +01:00 committed by GitHub
commit cd694b898f
5 changed files with 1386 additions and 119 deletions

View File

@ -9,9 +9,13 @@ API: Flows
.. note:: Before reading this page, you should be familiar with the key concepts of :doc:`key-concepts-flows`.
.. contents::
An example flow
---------------
Let's imagine a flow for agreeing a basic ledger update between Alice and Bob. This flow will have two sides:
Before we discuss the API offered by the flow, let's consider what a standard flow may look like.
Imagine a flow for agreeing a basic ledger update between Alice and Bob. This flow will have two sides:
* An ``Initiator`` side, that will initiate the request to update the ledger
* A ``Responder`` side, that will respond to the request to update the ledger
@ -76,23 +80,42 @@ To respond to these actions, the responder takes the following steps:
FlowLogic
---------
In practice, a flow is implemented as one or more communicating ``FlowLogic`` subclasses. Each ``FlowLogic`` subclass
must override ``FlowLogic.call()``, which describes the actions it will take as part of the flow.
In practice, a flow is implemented as one or more communicating ``FlowLogic`` subclasses. The ``FlowLogic``
subclass's constructor can take any number of arguments of any type. The generic of ``FlowLogic`` (e.g.
``FlowLogic<SignedTransaction>``) indicates the flow's return type.
So in the example above, we would have an ``Initiator`` ``FlowLogic`` subclass and a ``Responder`` ``FlowLogic``
subclass. The actions of the initiator's side of the flow would be defined in ``Initiator.call``, and the actions
of the responder's side of the flow would be defined in ``Responder.call``.
.. container:: codeset
.. sourcecode:: kotlin
class Initiator(val arg1: Boolean,
val arg2: Int,
val counterparty: Party): FlowLogic<SignedTransaction>() { }
class Responder(val otherParty: Party) : FlowLogic<Unit>() { }
.. sourcecode:: java
public static class Initiator extends FlowLogic<SignedTransaction> {
private final boolean arg1;
private final int arg2;
private final Party counterparty;
public Initiator(boolean arg1, int arg2, Party counterparty) {
this.arg1 = arg1;
this.arg2 = arg2;
this.counterparty = counterparty;
}
}
public static class Responder extends FlowLogic<Void> { }
FlowLogic annotations
^^^^^^^^^^^^^^^^^^^^^
---------------------
Any flow that you wish to start either directly via RPC or as a subflow must be annotated with the
``@InitiatingFlow`` annotation. Additionally, if you wish to start the flow via RPC, you must annotate it with the
``@StartableByRPC`` annotation.
Any flow that responds to a message from another flow must be annotated with the ``@InitiatedBy`` annotation.
``@InitiatedBy`` takes the class of the flow it is responding to as its single parameter.
So in our example, we would have:
``@StartableByRPC`` annotation:
.. container:: codeset
@ -100,74 +123,145 @@ So in our example, we would have:
@InitiatingFlow
@StartableByRPC
class Initiator(): FlowLogic<Unit>() {
...
@InitiatedBy(Initiator::class)
class Responder(val otherParty: Party) : FlowLogic<Unit>() {
class Initiator(): FlowLogic<Unit>() { }
.. sourcecode:: java
@InitiatingFlow
@StartableByRPC
public static class Initiator extends FlowLogic<Unit> {
public static class Initiator extends FlowLogic<Unit> { }
...
Meanwhile, any flow that responds to a message from another flow must be annotated with the ``@InitiatedBy`` annotation.
``@InitiatedBy`` takes the class of the flow it is responding to as its single parameter:
.. container:: codeset
.. sourcecode:: kotlin
@InitiatedBy(Initiator::class)
class Responder(val otherParty: Party) : FlowLogic<Unit>() { }
.. sourcecode:: java
@InitiatedBy(Initiator.class)
public static class Responder extends FlowLogic<Void> {
public static class Responder extends FlowLogic<Void> { }
Additionally, any flow that is started by a ``SchedulableState`` must be annotated with the ``@SchedulableFlow``
annotation.
ServiceHub
----------
Within ``FlowLogic.call``, the flow developer has access to the node's ``ServiceHub``, which provides access to the
various services the node provides. See :doc:`api-service-hub` for information about the services the ``ServiceHub``
offers.
Call
----
Each ``FlowLogic`` subclass must override ``FlowLogic.call()``, which describes the actions it will take as part of
the flow. For example, the actions of the initiator's side of the flow would be defined in ``Initiator.call``, and the
actions of the responder's side of the flow would be defined in ``Responder.call``.
Some common tasks performed using the ``ServiceHub`` are:
* Looking up your own identity or the identity of a counterparty using the ``networkMapCache``
* Identifying the providers of a given service (e.g. a notary service) using the ``networkMapCache``
* Retrieving states to use in a transaction using the ``vaultService``
* Retrieving attachments and past transactions to use in a transaction using the ``storageService``
* Creating a timestamp using the ``clock``
* Signing a transaction using the ``keyManagementService``
Common flow tasks
-----------------
There are a number of common tasks that you will need to perform within ``FlowLogic.call`` in order to agree ledger
updates. This section details the API for the most common tasks.
Retrieving information about other nodes
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
We use the network map to retrieve information about other nodes on the network:
In order for nodes to be able to run multiple flows concurrently, and to allow flows to survive node upgrades and
restarts, flows need to be checkpointable and serializable to disk. This is achieved by marking ``FlowLogic.call()``,
as well as any function invoked from within ``FlowLogic.call()``, with an ``@Suspendable`` annotation.
.. container:: codeset
.. sourcecode:: kotlin
val networkMap = serviceHub.networkMapCache
val allNodes = networkMap.partyNodes
val allNotaryNodes = networkMap.notaryNodes
val randomNotaryNode = networkMap.getAnyNotary()
val alice = networkMap.getNodeByLegalName(X500Name("CN=Alice,O=Alice,L=London,C=GB"))
val bob = networkMap.getNodeByLegalIdentityKey(bobsKey)
class Initiator(val counterparty: Party): FlowLogic<Unit>() {
@Suspendable
override fun call() { }
}
.. sourcecode:: java
final NetworkMapCache networkMap = getServiceHub().getNetworkMapCache();
public static class InitiatorFlow extends FlowLogic<Void> {
private final Party counterparty;
final List<NodeInfo> allNodes = networkMap.getPartyNodes();
final List<NodeInfo> allNotaryNodes = networkMap.getNotaryNodes();
final Party randomNotaryNode = networkMap.getAnyNotary(null);
public Initiator(Party counterparty) {
this.counterparty = counterparty;
}
final NodeInfo alice = networkMap.getNodeByLegalName(new X500Name("CN=Alice,O=Alice,L=London,C=GB"));
final NodeInfo bob = networkMap.getNodeByLegalIdentityKey(bobsKey);
@Suspendable
@Override
public Void call() throws FlowException { }
}
ServiceHub
----------
Within ``FlowLogic.call``, the flow developer has access to the node's ``ServiceHub``, which provides access to the
various services the node provides. We will use the ``ServiceHub`` extensively in the examples that follow. You can
also see :doc:`api-service-hub` for information about the services the ``ServiceHub`` offers.
Common flow tasks
-----------------
There are a number of common tasks that you will need to perform within ``FlowLogic.call`` in order to agree ledger
updates. This section details the API for common tasks.
Transaction building
^^^^^^^^^^^^^^^^^^^^
The majority of the work performed during a flow will be to build, verify and sign a transaction. We cover this in
:doc:`api-transactions`.
Retrieving information about other nodes
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
We can retrieve information about other nodes on the network and the services they offer using
``ServiceHub.networkMapCache``.
Notaries
~~~~~~~~
Remember that a transaction generally needs a notary to:
* Prevent double-spends if the transaction has inputs
* Serve as a timestamping authority if the transaction has a time-window
There are several ways to retrieve a notary from the network map:
.. container:: codeset
.. literalinclude:: ../../docs/source/example-code/src/main/kotlin/net/corda/docs/FlowCookbook.kt
:language: kotlin
:start-after: DOCSTART 1
:end-before: DOCEND 1
:dedent: 12
.. literalinclude:: ../../docs/source/example-code/src/main/java/net/corda/docs/FlowCookbookJava.java
:language: java
:start-after: DOCSTART 1
:end-before: DOCEND 1
:dedent: 12
Specific counterparties
~~~~~~~~~~~~~~~~~~~~~~~
We can also use the network map to retrieve a specific counterparty:
.. container:: codeset
.. literalinclude:: ../../docs/source/example-code/src/main/kotlin/net/corda/docs/FlowCookbook.kt
:language: kotlin
:start-after: DOCSTART 2
:end-before: DOCEND 2
:dedent: 12
.. literalinclude:: ../../docs/source/example-code/src/main/java/net/corda/docs/FlowCookbookJava.java
:language: java
:start-after: DOCSTART 2
:end-before: DOCEND 2
:dedent: 12
Specific services
~~~~~~~~~~~~~~~~~
Finally, we can use the map to identify nodes providing a specific service (e.g. a regulator or an oracle):
.. container:: codeset
.. literalinclude:: ../../docs/source/example-code/src/main/kotlin/net/corda/docs/FlowCookbook.kt
:language: kotlin
:start-after: DOCSTART 3
:end-before: DOCEND 3
:dedent: 12
.. literalinclude:: ../../docs/source/example-code/src/main/java/net/corda/docs/FlowCookbookJava.java
:language: java
:start-after: DOCSTART 3
:end-before: DOCEND 3
:dedent: 12
Communication between parties
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
@ -180,50 +274,121 @@ Communication between parties
* ``sendAndReceive(receiveType: Class<R>, otherParty: Party, payload: Any)``
* Sends the ``payload`` object to the ``otherParty``, and receives an object of type ``receiveType`` back
Each ``FlowLogic`` subclass can be annotated to respond to messages from a given *counterparty* flow using the
``@InitiatedBy`` annotation. When a node first receives a message from a given ``FlowLogic.call()`` invocation, it
responds as follows:
* The node checks whether they have a ``FlowLogic`` subclass that is registered to respond to the ``FlowLogic`` that
is sending the message:
a. If yes, the node starts an instance of this ``FlowLogic`` by invoking ``FlowLogic.call()``
b. Otherwise, the node ignores the message
* The counterparty steps through their ``FlowLogic.call()`` method until they encounter a call to ``receive()``, at
which point they process the message from the initiator
Upon calling ``receive()``/``sendAndReceive()``, the ``FlowLogic`` is suspended until it receives a response.
UntrustworthyData
~~~~~~~~~~~~~~~~~
``send()`` and ``sendAndReceive()`` return a payload wrapped in an ``UntrustworthyData`` instance. This is a
reminder that any data received off the wire is untrustworthy and must be verified.
We verify the ``UntrustworthyData`` and retrieve its payload by calling ``unwrap``:
Send
~~~~
We can send arbitrary data to a counterparty:
.. container:: codeset
.. sourcecode:: kotlin
.. literalinclude:: ../../docs/source/example-code/src/main/kotlin/net/corda/docs/FlowCookbook.kt
:language: kotlin
:start-after: DOCSTART 4
:end-before: DOCEND 4
:dedent: 12
val partSignedTx = receive<SignedTransaction>(otherParty).unwrap { partSignedTx ->
val wireTx = partSignedTx.verifySignatures(keyPair.public, notaryPubKey)
wireTx.toLedgerTransaction(serviceHub).verify()
partSignedTx
}
.. literalinclude:: ../../docs/source/example-code/src/main/java/net/corda/docs/FlowCookbookJava.java
:language: java
:start-after: DOCSTART 4
:end-before: DOCEND 4
:dedent: 12
.. sourcecode:: java
If this is the first ``send``, the counterparty will either:
final SignedTransaction partSignedTx = receive(SignedTransaction.class, otherParty)
.unwrap(tx -> {
try {
final WireTransaction wireTx = tx.verifySignatures(keyPair.getPublic(), notaryPubKey);
wireTx.toLedgerTransaction(getServiceHub()).verify();
} catch (SignatureException ex) {
throw new FlowException(tx.getId() + " failed signature checks", ex);
}
return tx;
});
1. Ignore the message if they are not registered to respond to messages from this flow.
2. Start the flow they have registered to respond to this flow, and run the flow until the first call to ``receive``,
at which point they process the message. In other words, we are assuming that the counterparty is registered to
respond to this flow, and has a corresponding ``receive`` call.
Receive
~~~~~~~
We can also wait to receive arbitrary data of a specific type from a counterparty. Again, this implies a corresponding
``send`` call in the counterparty's flow. A few scenarios:
* We never receive a message back. In the current design, the flow is paused until the node's owner kills the flow.
* Instead of sending a message back, the counterparty throws a ``FlowException``. This exception is propagated back
to us, and we can use the error message to establish what happened.
* We receive a message back, but it's of the wrong type. In this case, a ``FlowException`` is thrown.
* We receive back a message of the correct type. All is good.
Upon calling ``receive`` (or ``sendAndReceive``), the ``FlowLogic`` is suspended until it receives a response.
We receive the data wrapped in an ``UntrustworthyData`` instance. This is a reminder that the data we receive may not
be what it appears to be! We must unwrap the ``UntrustworthyData`` using a lambda:
.. container:: codeset
.. literalinclude:: ../../docs/source/example-code/src/main/kotlin/net/corda/docs/FlowCookbook.kt
:language: kotlin
:start-after: DOCSTART 5
:end-before: DOCEND 5
:dedent: 12
.. literalinclude:: ../../docs/source/example-code/src/main/java/net/corda/docs/FlowCookbookJava.java
:language: java
:start-after: DOCSTART 5
:end-before: DOCEND 5
:dedent: 12
We're not limited to sending to and receiving from a single counterparty. A flow can send messages to as many parties
as it likes, and each party can invoke a different response flow:
.. container:: codeset
.. literalinclude:: ../../docs/source/example-code/src/main/kotlin/net/corda/docs/FlowCookbook.kt
:language: kotlin
:start-after: DOCSTART 6
:end-before: DOCEND 6
:dedent: 12
.. literalinclude:: ../../docs/source/example-code/src/main/java/net/corda/docs/FlowCookbookJava.java
:language: java
:start-after: DOCSTART 6
:end-before: DOCEND 6
:dedent: 12
SendAndReceive
~~~~~~~~~~~~~~
We can also use a single call to send data to a counterparty and wait to receive data of a specific type back. The
type of data sent doesn't need to match the type of the data received back:
.. container:: codeset
.. literalinclude:: ../../docs/source/example-code/src/main/kotlin/net/corda/docs/FlowCookbook.kt
:language: kotlin
:start-after: DOCSTART 7
:end-before: DOCEND 7
:dedent: 12
.. literalinclude:: ../../docs/source/example-code/src/main/java/net/corda/docs/FlowCookbookJava.java
:language: java
:start-after: DOCSTART 7
:end-before: DOCEND 7
:dedent: 12
Counterparty response
~~~~~~~~~~~~~~~~~~~~~
Suppose we're now on the ``Responder`` side of the flow. We just received the following series of messages from the
``Initiator``:
1. They sent us an ``Any`` instance
2. They waited to receive an ``Integer`` instance back
3. They sent a ``String`` instance and waited to receive a ``Boolean`` instance back
Our side of the flow must mirror these calls. We could do this as follows:
.. container:: codeset
.. literalinclude:: ../../docs/source/example-code/src/main/kotlin/net/corda/docs/FlowCookbook.kt
:language: kotlin
:start-after: DOCSTART 8
:end-before: DOCEND 8
:dedent: 12
.. literalinclude:: ../../docs/source/example-code/src/main/java/net/corda/docs/FlowCookbookJava.java
:language: java
:start-after: DOCSTART 8
:end-before: DOCEND 8
:dedent: 12
Subflows
--------
@ -236,27 +401,118 @@ Corda provides a number of built-in flows that should be used for handling commo
* ``NotaryChangeFlow``, which should be used to change a state's notary
These flows are designed to be used as building blocks in your own flows. You invoke them by calling
``FlowLogic.subFlow`` from within your flow's ``call`` method. Here is an example from ``TwoPartyDealFlow.kt``:
``FlowLogic.subFlow`` from within your flow's ``call`` method. Let's look at three very common examples.
FinalityFlow
^^^^^^^^^^^^
``FinalityFlow`` allows us to notarise the transaction and get it recorded in the vault of the participants of all
the transaction's states:
.. container:: codeset
.. literalinclude:: ../../core/src/main/kotlin/net/corda/flows/TwoPartyDealFlow.kt
.. literalinclude:: ../../docs/source/example-code/src/main/kotlin/net/corda/docs/FlowCookbook.kt
:language: kotlin
:start-after: DOCSTART 1
:end-before: DOCEND 1
:start-after: DOCSTART 9
:end-before: DOCEND 9
:dedent: 12
In this example, we are starting a ``CollectSignaturesFlow``, passing in a partially signed transaction, and
receiving back a fully-signed version of the same transaction.
.. literalinclude:: ../../docs/source/example-code/src/main/java/net/corda/docs/FlowCookbookJava.java
:language: java
:start-after: DOCSTART 9
:end-before: DOCEND 9
:dedent: 12
Subflows in our example flow
^^^^^^^^^^^^^^^^^^^^^^^^^^^^
In practice, many of the actions in our example flow would be automated using subflows:
We can also choose to send the transaction to additional parties who aren't one of the state's participants:
* Parts 2-4 of ``Initiator.call`` should be automated by invoking ``CollectSignaturesFlow``
* Part 5 of ``Initiator.call`` should be automated by invoking ``FinalityFlow``
* Part 1 of ``Responder.call`` should be automated by invoking ``SignTransactionFlow``
* Part 2 of ``Responder.call`` will be handled automatically when the counterparty invokes ``FinalityFlow``
.. container:: codeset
.. literalinclude:: ../../docs/source/example-code/src/main/kotlin/net/corda/docs/FlowCookbook.kt
:language: kotlin
:start-after: DOCSTART 10
:end-before: DOCEND 10
:dedent: 12
.. literalinclude:: ../../docs/source/example-code/src/main/java/net/corda/docs/FlowCookbookJava.java
:language: java
:start-after: DOCSTART 10
:end-before: DOCEND 10
:dedent: 12
Only one party has to call ``FinalityFlow`` for a given transaction to be recorded by all participants. It does
**not** need to be called by each participant individually.
CollectSignaturesFlow/SignTransactionFlow
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
The list of parties who need to sign a transaction is dictated by the transaction's commands. Once we've signed a
transaction ourselves, we can automatically gather the signatures of the other required signers using
``CollectSignaturesFlow``:
.. container:: codeset
.. literalinclude:: ../../docs/source/example-code/src/main/kotlin/net/corda/docs/FlowCookbook.kt
:language: kotlin
:start-after: DOCSTART 15
:end-before: DOCEND 15
:dedent: 12
.. literalinclude:: ../../docs/source/example-code/src/main/java/net/corda/docs/FlowCookbookJava.java
:language: java
:start-after: DOCSTART 15
:end-before: DOCEND 15
:dedent: 12
Each required signer will need to respond by invoking its own ``SignTransactionFlow`` subclass to check the
transaction and provide their signature if they are satisfied:
.. container:: codeset
.. literalinclude:: ../../docs/source/example-code/src/main/kotlin/net/corda/docs/FlowCookbook.kt
:language: kotlin
:start-after: DOCSTART 16
:end-before: DOCEND 16
:dedent: 12
.. literalinclude:: ../../docs/source/example-code/src/main/java/net/corda/docs/FlowCookbookJava.java
:language: java
:start-after: DOCSTART 16
:end-before: DOCEND 16
:dedent: 12
ResolveTransactionsFlow
^^^^^^^^^^^^^^^^^^^^^^^
Verifying a transaction will also verify every transaction in the transaction's dependency chain. So if we receive a
transaction from a counterparty and it has any dependencies, we'd need to download all of these dependencies
using``ResolveTransactionsFlow`` before verifying it:
.. container:: codeset
.. literalinclude:: ../../docs/source/example-code/src/main/kotlin/net/corda/docs/FlowCookbook.kt
:language: kotlin
:start-after: DOCSTART 13
:end-before: DOCEND 13
:dedent: 12
.. literalinclude:: ../../docs/source/example-code/src/main/java/net/corda/docs/FlowCookbookJava.java
:language: java
:start-after: DOCSTART 13
:end-before: DOCEND 13
:dedent: 12
We can also resolve a `StateRef` dependency chain:
.. container:: codeset
.. literalinclude:: ../../docs/source/example-code/src/main/kotlin/net/corda/docs/FlowCookbook.kt
:language: kotlin
:start-after: DOCSTART 14
:end-before: DOCEND 14
:dedent: 12
.. literalinclude:: ../../docs/source/example-code/src/main/java/net/corda/docs/FlowCookbookJava.java
:language: java
:start-after: DOCSTART 14
:end-before: DOCEND 14
:dedent: 12
FlowException
-------------
@ -283,18 +539,38 @@ There are many scenarios in which throwing a ``FlowException`` would be appropri
* The transaction does not match the parameters of the deal as discussed
* You are reneging on a deal
Suspending flows
----------------
In order for nodes to be able to run multiple flows concurrently, and to allow flows to survive node upgrades and
restarts, flows need to be checkpointable and serializable to disk.
ProgressTracker
---------------
We can give our flow a progress tracker. This allows us to see the flow's progress visually in our node's CRaSH shell.
This is achieved by marking any function invoked from within ``FlowLogic.call()`` with an ``@Suspendable`` annotation.
We can see an example in ``CollectSignaturesFlow``:
To provide a progress tracker, we have to override ``FlowLogic.progressTracker`` in our flow:
.. container:: codeset
.. literalinclude:: ../../core/src/main/kotlin/net/corda/flows/CollectSignaturesFlow.kt
.. literalinclude:: ../../docs/source/example-code/src/main/kotlin/net/corda/docs/FlowCookbook.kt
:language: kotlin
:start-after: DOCSTART 1
:end-before: DOCEND 1
:start-after: DOCSTART 17
:end-before: DOCEND 17
:dedent: 8
.. literalinclude:: ../../docs/source/example-code/src/main/java/net/corda/docs/FlowCookbookJava.java
:language: java
:start-after: DOCSTART 17
:end-before: DOCEND 17
:dedent: 8
We then update the progress tracker's current step as we progress through the flow as follows:
.. container:: codeset
.. literalinclude:: ../../docs/source/example-code/src/main/kotlin/net/corda/docs/FlowCookbook.kt
:language: kotlin
:start-after: DOCSTART 18
:end-before: DOCEND 18
:dedent: 12
.. literalinclude:: ../../docs/source/example-code/src/main/java/net/corda/docs/FlowCookbookJava.java
:language: java
:start-after: DOCSTART 18
:end-before: DOCEND 18
:dedent: 12

View File

@ -7,4 +7,5 @@ Building a CorDapp
cordapp-overview
writing-cordapps
api-index
flow-cookbook
cheat-sheet

View File

@ -0,0 +1,498 @@
package net.corda.docs;
import co.paralleluniverse.fibers.Suspendable;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableSet;
import net.corda.contracts.asset.Cash;
import net.corda.core.contracts.*;
import net.corda.core.contracts.TransactionType.General;
import net.corda.core.contracts.TransactionType.NotaryChange;
import net.corda.core.crypto.SecureHash;
import net.corda.core.flows.*;
import net.corda.core.identity.Party;
import net.corda.core.node.services.ServiceType;
import net.corda.core.node.services.Vault;
import net.corda.core.node.services.Vault.Page;
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;
import net.corda.flows.CollectSignaturesFlow;
import net.corda.flows.FinalityFlow;
import net.corda.flows.ResolveTransactionsFlow;
import net.corda.flows.SignTransactionFlow;
import org.bouncycastle.asn1.x500.X500Name;
import java.security.PublicKey;
import java.time.Instant;
import java.util.List;
import java.util.Set;
import static net.corda.core.contracts.ContractsDSL.requireThat;
import static net.corda.core.utilities.TestConstants.getDUMMY_PUBKEY_1;
// We group our two flows inside a singleton object to indicate that they work
// together.
public class FlowCookbookJava {
// ``InitiatorFlow`` is our first flow, and will communicate with
// ``ResponderFlow``, below.
// We mark ``InitiatorFlow`` as an ``InitiatingFlow``, allowing it to be
// started directly by the node.
@InitiatingFlow
// We also mark ``InitiatorFlow`` as ``StartableByRPC``, allowing the
// node's owner to start the flow via RPC.
@StartableByRPC
// Every flow must subclass ``FlowLogic``. The generic indicates the
// flow's return type.
public static class InitiatorFlow extends FlowLogic<Void> {
private final boolean arg1;
private final int arg2;
private final Party counterparty;
public InitiatorFlow(boolean arg1, int arg2, Party counterparty) {
this.arg1 = arg1;
this.arg2 = arg2;
this.counterparty = counterparty;
}
/*----------------------------------
* WIRING UP THE PROGRESS TRACKER *
----------------------------------*/
// Giving our flow a progress tracker allows us to see the flow's
// progress visually in our node's CRaSH shell.
// DOCSTART 17
private static final Step ID_OTHER_NODES = new Step("Identifying other nodes on the network.");
private static final Step SENDING_AND_RECEIVING_DATA = new Step("Sending data between parties.");
private static final Step EXTRACTING_VAULT_STATES = new Step("Extracting states from the vault.");
private static final Step OTHER_TX_COMPONENTS = new Step("Gathering a transaction's other components.");
private static final Step TX_BUILDING = new Step("Building a transaction.");
private static final Step TX_SIGNING = new Step("Signing a transaction.");
private static final Step TX_VERIFICATION = new Step("Verifying a transaction.");
private static final Step SIGS_GATHERING = new Step("Gathering a transaction's signatures.") {
// Wiring up a child progress tracker allows us to see the
// subflow's progress steps in our flow's progress tracker.
@Override public ProgressTracker childProgressTracker() {
return CollectSignaturesFlow.Companion.tracker();
}
};
private static final Step FINALISATION = new Step("Finalising a transaction.") {
@Override public ProgressTracker childProgressTracker() {
return FinalityFlow.Companion.tracker();
}
};
private final ProgressTracker progressTracker = new ProgressTracker(
ID_OTHER_NODES,
SENDING_AND_RECEIVING_DATA,
EXTRACTING_VAULT_STATES,
OTHER_TX_COMPONENTS,
TX_BUILDING,
TX_SIGNING,
TX_VERIFICATION,
SIGS_GATHERING,
FINALISATION
);
// DOCEND 17
@Suspendable
@Override
public Void call() throws FlowException {
// We'll be using a dummy public key for demonstration purposes.
// These are built in to Corda, and are generally used for writing
// tests.
PublicKey dummyPubKey = getDUMMY_PUBKEY_1();
/*---------------------------
* IDENTIFYING OTHER NODES *
---------------------------*/
// DOCSTART 18
progressTracker.setCurrentStep(ID_OTHER_NODES);
// DOCEND 18
// A transaction generally needs a notary:
// - To prevent double-spends if the transaction has inputs
// - To serve as a timestamping authority if the transaction has a time-window
// We retrieve a notary from the network map.
// DOCSTART 1
Party specificNotary = getServiceHub().getNetworkMapCache().getNotary(new X500Name("CN=Notary Service,O=R3,OU=corda,L=London,C=UK"));
Party anyNotary = getServiceHub().getNetworkMapCache().getAnyNotary(null);
// Unlike the first two methods, ``getNotaryNodes`` returns a
// ``List<NodeInfo>``. We have to extract the notary identity of
// the node we want.
Party firstNotary = getServiceHub().getNetworkMapCache().getNotaryNodes().get(0).getNotaryIdentity();
// DOCEND 1
// We may also need to identify a specific counterparty.
// Again, we do so using the network map.
// DOCSTART 2
Party namedCounterparty = getServiceHub().getNetworkMapCache().getNodeByLegalName(new X500Name("CN=NodeA,O=NodeA,L=London,C=UK")).getLegalIdentity();
Party keyedCounterparty = getServiceHub().getNetworkMapCache().getNodeByLegalIdentityKey(dummyPubKey).getLegalIdentity();
Party firstCounterparty = getServiceHub().getNetworkMapCache().getPartyNodes().get(0).getLegalIdentity();
// DOCEND 2
// Finally, we can use the map to identify nodes providing a
// specific service (e.g. a regulator or an oracle).
// DOCSTART 3
Party regulator = getServiceHub().getNetworkMapCache().getNodesWithService(ServiceType.Companion.getRegulator()).get(0).getLegalIdentity();
// DOCEND 3
/*------------------------------
* SENDING AND RECEIVING DATA *
------------------------------*/
progressTracker.setCurrentStep(SENDING_AND_RECEIVING_DATA);
// We can send arbitrary data to a counterparty.
// If this is the first ``send``, the counterparty will either:
// 1. Ignore the message if they are not registered to respond
// to messages from this flow.
// 2. Start the flow they have registered to respond to this flow,
// and run the flow until the first call to ``receive``, at
// which point they process the message.
// In other words, we are assuming that the counterparty is
// registered to respond to this flow, and has a corresponding
// ``receive`` call.
// DOCSTART 4
send(counterparty, new Object());
// DOCEND 4
// We can wait to receive arbitrary data of a specific type from a
// counterparty. Again, this implies a corresponding ``send`` call
// in the counterparty's flow. A few scenarios:
// - We never receive a message back. In the current design, the
// flow is paused until the node's owner kills the flow.
// - Instead of sending a message back, the counterparty throws a
// ``FlowException``. This exception is propagated back to us,
// and we can use the error message to establish what happened.
// - We receive a message back, but it's of the wrong type. In
// this case, a ``FlowException`` is thrown.
// - We receive back a message of the correct type. All is good.
//
// Upon calling ``receive()`` (or ``sendAndReceive()``), the
// ``FlowLogic`` is suspended until it receives a response.
//
// We receive the data wrapped in an ``UntrustworthyData``
// instance. This is a reminder that the data we receive may not
// be what it appears to be! We must unwrap the
// ``UntrustworthyData`` using a lambda.
// DOCSTART 5
UntrustworthyData<Integer> packet1 = receive(Integer.class, counterparty);
Integer integer = packet1.unwrap(data -> {
// Perform checking on the object received.
// T O D O: Check the received object.
// Return the object.
return data;
});
// DOCEND 5
// We can also use a single call to send data to a counterparty
// and wait to receive data of a specific type back. The type of
// data sent doesn't need to match the type of the data received
// back.
// DOCSTART 7
UntrustworthyData<Boolean> packet2 = sendAndReceive(Boolean.class, counterparty, "You can send and receive any class!");
Boolean bool = packet2.unwrap(data -> {
// Perform checking on the object received.
// T O D O: Check the received object.
// Return the object.
return data;
});
// DOCEND 7
// We're not limited to sending to and receiving from a single
// counterparty. A flow can send messages to as many parties as it
// likes, and each party can invoke a different response flow.
// DOCSTART 6
send(regulator, new Object());
UntrustworthyData<Object> packet3 = receive(Object.class, regulator);
// DOCEND 6
/*------------------------------------
* EXTRACTING STATES FROM THE VAULT *
------------------------------------*/
progressTracker.setCurrentStep(EXTRACTING_VAULT_STATES);
// Let's assume there are already some ``DummyState``s in our
// node's vault, stored there as a result of running past flows,
// and we want to consume them in a transaction. There are many
// ways to extract these states from our vault.
// For example, we would extract any unconsumed ``DummyState``s
// from our vault as follows:
VaultQueryCriteria criteria = new VaultQueryCriteria(Vault.StateStatus.UNCONSUMED);
Page<DummyState> results = getServiceHub().getVaultQueryService().queryBy(DummyState.class, criteria);
List<StateAndRef<DummyState>> dummyStates = results.getStates();
// For a full list of the available ways of extracting states from
// the vault, see the Vault Query docs page.
// 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.
StateRef ourStateRef = new StateRef(SecureHash.sha256("DummyTransactionHash"), 0);
// A ``StateAndRef`` pairs a ``StateRef`` with the state it points to.
StateAndRef ourStateAndRef = getServiceHub().toStateAndRef(ourStateRef);
/*------------------------------------------
* GATHERING OTHER TRANSACTION COMPONENTS *
------------------------------------------*/
progressTracker.setCurrentStep(OTHER_TX_COMPONENTS);
// Output states are constructed from scratch.
DummyState ourOutput = new DummyState();
// Or as copies of other states with some properties changed.
DummyState ourOtherOutput = ourOutput.copy(77);
// 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.
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);
// ``CommandData`` can either be:
// 1. Of type ``TypeOnlyCommandData``, in which case it only
// serves to attach signers to the transaction and possibly
// fork the contract's verification logic.
TypeOnlyCommandData typeOnlyCommandData = new DummyContract.Commands.Create();
// 2. Include additional data which can be used by the contract
// during verification, alongside fulfilling the roles above
CommandData commandDataWithData = new Cash.Commands.Issue(12345678);
// 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.
SecureHash ourAttachment = SecureHash.sha256("DummyAttachment");
// Time windows can have a start and end time, or be open at either end.
TimeWindow ourTimeWindow = TimeWindow.between(Instant.MIN, Instant.MAX);
TimeWindow ourAfter = TimeWindow.fromOnly(Instant.MIN);
TimeWindow ourBefore = TimeWindow.untilOnly(Instant.MAX);
/*------------------------
* TRANSACTION BUILDING *
------------------------*/
progressTracker.setCurrentStep(TX_BUILDING);
// There are two types of transaction (notary-change and general),
// and therefore two types of transaction builder:
TransactionBuilder notaryChangeTxBuilder = new TransactionBuilder(NotaryChange.INSTANCE, specificNotary);
TransactionBuilder regTxBuilder = new TransactionBuilder(General.INSTANCE, specificNotary);
// We add items to the transaction builder using ``TransactionBuilder.withItems``:
regTxBuilder.withItems(
// Inputs, as ``StateRef``s that reference to the outputs of previous transactions
ourStateRef,
// Outputs, as ``ContractState``s
ourOutput,
// Commands, as ``Command``s
ourCommand
);
// We can also add items using methods for the individual components:
regTxBuilder.addInputState(ourStateAndRef);
regTxBuilder.addOutputState(ourOutput);
regTxBuilder.addCommand(ourCommand);
regTxBuilder.addAttachment(ourAttachment);
regTxBuilder.addTimeWindow(ourTimeWindow);
/*-----------------------
* TRANSACTION SIGNING *
-----------------------*/
progressTracker.setCurrentStep(TX_SIGNING);
// We finalise the transaction by signing it,
// converting it into a ``SignedTransaction``.
SignedTransaction onceSignedTx = getServiceHub().signInitialTransaction(regTxBuilder);
// 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);
/*----------------------------
* TRANSACTION VERIFICATION *
----------------------------*/
progressTracker.setCurrentStep(TX_VERIFICATION);
// Verifying a transaction will also verify every transaction in
// the transaction's dependency chain. So if this was a
// transaction we'd received from a counterparty and it had any
// dependencies, we'd need to download all of these dependencies
// using``ResolveTransactionsFlow`` before verifying it.
// DOCSTART 13
subFlow(new ResolveTransactionsFlow(twiceSignedTx, counterparty));
// DOCEND 13
// We can also resolve a `StateRef` dependency chain.
// DOCSTART 14
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.
WireTransaction wireTx = twiceSignedTx.getTx();
// 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``.
LedgerTransaction ledgerTx = wireTx.toLedgerTransaction(getServiceHub());
// We can now verify the transaction.
ledgerTx.verify();
// 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.
DummyState outputState = (DummyState) wireTx.getOutputs().get(0).getData();
if (outputState.getMagicNumber() != 777) {
throw new FlowException("We expected a magic number of 777.");
}
// 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
// requires signatures from all the required signers and is
// contractually valid, it's a valid ledger update.
/*------------------------
* GATHERING SIGNATURES *
------------------------*/
progressTracker.setCurrentStep(SIGS_GATHERING);
// The list of parties who need to sign a transaction is dictated
// by the transaction's commands. Once we've signed a transaction
// ourselves, we can automatically gather the signatures of the
// other required signers using ``CollectSignaturesFlow``.
// The responder flow will need to call ``SignTransactionFlow``.
// DOCSTART 15
SignedTransaction fullySignedTx = subFlow(new CollectSignaturesFlow(twiceSignedTx, SIGS_GATHERING.childProgressTracker()));
// DOCEND 15
/*------------------------------
* FINALISING THE TRANSACTION *
------------------------------*/
progressTracker.setCurrentStep(FINALISATION);
// We notarise the transaction and get it recorded in the vault of
// the participants of all the transaction's states.
// DOCSTART 9
SignedTransaction notarisedTx1 = subFlow(new FinalityFlow(fullySignedTx, FINALISATION.childProgressTracker())).get(0);
// DOCEND 9
// We can also choose to send it to additional parties who aren't one
// of the state's participants.
// DOCSTART 10
Set<Party> additionalParties = ImmutableSet.of(regulator, regulator);
SignedTransaction notarisedTx2 = subFlow(new FinalityFlow(ImmutableList.of(fullySignedTx), additionalParties, FINALISATION.childProgressTracker())).get(0);
// DOCEND 10
return null;
}
}
// ``ResponderFlow`` is our second flow, and will communicate with
// ``InitiatorFlow``.
// We mark ``ResponderFlow`` as an ``InitiatedByFlow``, meaning that it
// can only be started in response to a message from its initiating flow.
// That's ``InitiatorFlow`` in this case.
// Each node also has several flow pairs registered by default - see
// ``AbstractNode.installCoreFlows``.
@InitiatedBy(InitiatorFlow.class)
public static class ResponderFlow extends FlowLogic<Void> {
private final Party counterparty;
public ResponderFlow(Party counterparty) {
this.counterparty = counterparty;
}
private static final Step RECEIVING_AND_SENDING_DATA = new Step("Sending data between parties.");
private static final Step SIGNING = new Step("Responding to CollectSignaturesFlow.");
private static final Step FINALISATION = new Step("Finalising a transaction.");
private final ProgressTracker progressTracker = new ProgressTracker(
RECEIVING_AND_SENDING_DATA,
SIGNING,
FINALISATION
);
@Suspendable
@Override
public Void call() throws FlowException {
// The ``ResponderFlow` has all the same APIs available. It looks
// up network information, sends and receives data, and constructs
// transactions in exactly the same way.
/*------------------------------
* SENDING AND RECEIVING DATA *
-----------------------------*/
progressTracker.setCurrentStep(RECEIVING_AND_SENDING_DATA);
// We need to respond to the messages sent by the initiator:
// 1. They sent us an ``Any`` instance
// 2. They waited to receive an ``Integer`` instance back
// 3. They sent a ``String`` instance and waited to receive a
// ``Boolean`` instance back
// Our side of the flow must mirror these calls.
// DOCSTART 8
Object obj = receive(Object.class, counterparty).unwrap(data -> data);
String string = sendAndReceive(String.class, counterparty, 99).unwrap(data -> data);
send(counterparty, true);
// DOCEND 8
/*-----------------------------------------
* RESPONDING TO COLLECT_SIGNATURES_FLOW *
-----------------------------------------*/
progressTracker.setCurrentStep(SIGNING);
// The responder will often need to respond to a call to
// ``CollectSignaturesFlow``. It does so my invoking its own
// ``SignTransactionFlow`` subclass.
// DOCSTART 16
class SignTxFlow extends SignTransactionFlow {
private SignTxFlow(Party otherParty, ProgressTracker progressTracker) {
super(otherParty, progressTracker);
}
@Override
protected void checkTransaction(SignedTransaction stx) {
requireThat(require -> {
// Any additional checking we see fit...
DummyState outputState = (DummyState) stx.getTx().getOutputs().get(0).getData();
assert (outputState.getMagicNumber() == 777);
return null;
});
}
}
subFlow(new SignTxFlow(counterparty, SignTransactionFlow.Companion.tracker()));
// DOCEND 16
/*------------------------------
* FINALISING THE TRANSACTION *
------------------------------*/
progressTracker.setCurrentStep(FINALISATION);
// Nothing to do here! As long as some other party calls
// ``FinalityFlow``, the recording of the transaction on our node
// we be handled automatically.
return null;
}
}
}

View File

@ -0,0 +1,474 @@
package net.corda.docs
import co.paralleluniverse.fibers.Suspendable
import net.corda.contracts.asset.Cash
import net.corda.core.contracts.*
import net.corda.core.contracts.TransactionType.General
import net.corda.core.contracts.TransactionType.NotaryChange
import net.corda.core.crypto.SecureHash
import net.corda.core.flows.*
import net.corda.core.identity.Party
import net.corda.core.node.services.ServiceType
import net.corda.core.node.services.Vault.Page
import net.corda.core.node.services.queryBy
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.DUMMY_PUBKEY_1
import net.corda.core.utilities.ProgressTracker
import net.corda.core.utilities.ProgressTracker.Step
import net.corda.core.utilities.UntrustworthyData
import net.corda.core.utilities.unwrap
import net.corda.flows.CollectSignaturesFlow
import net.corda.flows.FinalityFlow
import net.corda.flows.ResolveTransactionsFlow
import net.corda.flows.SignTransactionFlow
import org.bouncycastle.asn1.x500.X500Name
import java.security.PublicKey
import java.time.Instant
// We group our two flows inside a singleton object to indicate that they work
// together.
object FlowCookbook {
// ``InitiatorFlow`` is our first flow, and will communicate with
// ``ResponderFlow``, below.
// We mark ``InitiatorFlow`` as an ``InitiatingFlow``, allowing it to be
// started directly by the node.
@InitiatingFlow
// We also mark ``InitiatorFlow`` as ``StartableByRPC``, allowing the
// node's owner to start the flow via RPC.
@StartableByRPC
// Every flow must subclass ``FlowLogic``. The generic indicates the
// flow's return type.
class InitiatorFlow(val arg1: Boolean, val arg2: Int, val counterparty: Party) : FlowLogic<Unit>() {
/**---------------------------------
* WIRING UP THE PROGRESS TRACKER *
---------------------------------**/
// Giving our flow a progress tracker allows us to see the flow's
// progress visually in our node's CRaSH shell.
// DOCSTART 17
companion object {
object ID_OTHER_NODES : Step("Identifying other nodes on the network.")
object SENDING_AND_RECEIVING_DATA : Step("Sending data between parties.")
object EXTRACTING_VAULT_STATES : Step("Extracting states from the vault.")
object OTHER_TX_COMPONENTS : Step("Gathering a transaction's other components.")
object TX_BUILDING : Step("Building a transaction.")
object TX_SIGNING : Step("Signing a transaction.")
object TX_VERIFICATION : Step("Verifying a transaction.")
object SIGS_GATHERING : Step("Gathering a transaction's signatures.") {
// Wiring up a child progress tracker allows us to see the
// subflow's progress steps in our flow's progress tracker.
override fun childProgressTracker() = CollectSignaturesFlow.tracker()
}
object FINALISATION : Step("Finalising a transaction.") {
override fun childProgressTracker() = FinalityFlow.tracker()
}
fun tracker() = ProgressTracker(
ID_OTHER_NODES,
SENDING_AND_RECEIVING_DATA,
EXTRACTING_VAULT_STATES,
OTHER_TX_COMPONENTS,
TX_BUILDING,
TX_SIGNING,
TX_VERIFICATION,
SIGS_GATHERING,
FINALISATION
)
}
// DOCEND 17
override val progressTracker: ProgressTracker = tracker()
@Suspendable
override fun call() {
// We'll be using a dummy public key for demonstration purposes.
// These are built in to Corda, and are generally used for writing
// tests.
val dummyPubKey: PublicKey = DUMMY_PUBKEY_1
/**--------------------------
* IDENTIFYING OTHER NODES *
--------------------------**/
// DOCSTART 18
progressTracker.currentStep = ID_OTHER_NODES
// DOCEND 18
// A transaction generally needs a notary:
// - To prevent double-spends if the transaction has inputs
// - To serve as a timestamping authority if the transaction has a time-window
// We retrieve the notary from the network map.
// DOCSTART 1
val specificNotary: Party? = serviceHub.networkMapCache.getNotary(X500Name("CN=Notary Service,O=R3,OU=corda,L=London,C=UK"))
val anyNotary: Party? = serviceHub.networkMapCache.getAnyNotary()
// Unlike the first two methods, ``getNotaryNodes`` returns a
// ``List<NodeInfo>``. We have to extract the notary identity of
// the node we want.
val firstNotary: Party = serviceHub.networkMapCache.notaryNodes[0].notaryIdentity
// DOCEND 1
// We may also need to identify a specific counterparty. Again, we
// do so using the network map.
// DOCSTART 2
val namedCounterparty: Party? = serviceHub.networkMapCache.getNodeByLegalName(X500Name("CN=NodeA,O=NodeA,L=London,C=UK"))?.legalIdentity
val keyedCounterparty: Party? = serviceHub.networkMapCache.getNodeByLegalIdentityKey(dummyPubKey)?.legalIdentity
val firstCounterparty: Party = serviceHub.networkMapCache.partyNodes[0].legalIdentity
// DOCEND 2
// Finally, we can use the map to identify nodes providing a
// specific service (e.g. a regulator or an oracle).
// DOCSTART 3
val regulator: Party = serviceHub.networkMapCache.getNodesWithService(ServiceType.regulator)[0].legalIdentity
// DOCEND 3
/**-----------------------------
* SENDING AND RECEIVING DATA *
-----------------------------**/
progressTracker.currentStep = SENDING_AND_RECEIVING_DATA
// We can send arbitrary data to a counterparty.
// If this is the first ``send``, the counterparty will either:
// 1. Ignore the message if they are not registered to respond
// to messages from this flow.
// 2. Start the flow they have registered to respond to this flow,
// and run the flow until the first call to ``receive``, at
// which point they process the message.
// In other words, we are assuming that the counterparty is
// registered to respond to this flow, and has a corresponding
// ``receive`` call.
// DOCSTART 4
send(counterparty, Any())
// DOCEND 4
// We can wait to receive arbitrary data of a specific type from a
// counterparty. Again, this implies a corresponding ``send`` call
// in the counterparty's flow. A few scenarios:
// - We never receive a message back. In the current design, the
// flow is paused until the node's owner kills the flow.
// - Instead of sending a message back, the counterparty throws a
// ``FlowException``. This exception is propagated back to us,
// and we can use the error message to establish what happened.
// - We receive a message back, but it's of the wrong type. In
// this case, a ``FlowException`` is thrown.
// - We receive back a message of the correct type. All is good.
//
// Upon calling ``receive()`` (or ``sendAndReceive()``), the
// ``FlowLogic`` is suspended until it receives a response.
//
// We receive the data wrapped in an ``UntrustworthyData``
// instance. This is a reminder that the data we receive may not
// be what it appears to be! We must unwrap the
// ``UntrustworthyData`` using a lambda.
// DOCSTART 5
val packet1: UntrustworthyData<Int> = receive<Int>(counterparty)
val int: Int = packet1.unwrap { data ->
// Perform checking on the object received.
// T O D O: Check the received object.
// Return the object.
data
}
// DOCEND 5
// We can also use a single call to send data to a counterparty
// and wait to receive data of a specific type back. The type of
// data sent doesn't need to match the type of the data received
// back.
// DOCSTART 7
val packet2: UntrustworthyData<Boolean> = sendAndReceive<Boolean>(counterparty, "You can send and receive any class!")
val boolean: Boolean = packet2.unwrap { data ->
// Perform checking on the object received.
// T O D O: Check the received object.
// Return the object.
data
}
// DOCEND 7
// We're not limited to sending to and receiving from a single
// counterparty. A flow can send messages to as many parties as it
// likes, and each party can invoke a different response flow.
// DOCSTART 6
send(regulator, Any())
val packet3: UntrustworthyData<Any> = receive<Any>(regulator)
// DOCEND 6
/**-----------------------------------
* EXTRACTING STATES FROM THE VAULT *
-----------------------------------**/
progressTracker.currentStep = EXTRACTING_VAULT_STATES
// Let's assume there are already some ``DummyState``s in our
// node's vault, stored there as a result of running past flows,
// and we want to consume them in a transaction. There are many
// ways to extract these states from our vault.
// For example, we would extract any unconsumed ``DummyState``s
// from our vault as follows:
val criteria: VaultQueryCriteria = VaultQueryCriteria() // default is UNCONSUMED
val results: Page<DummyState> = serviceHub.vaultQueryService.queryBy<DummyState>(criteria)
val dummyStates: List<StateAndRef<DummyState>> = results.states
// For a full list of the available ways of extracting states from
// the vault, see the Vault Query docs page.
// 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.
val ourStateRef: StateRef = StateRef(SecureHash.sha256("DummyTransactionHash"), 0)
// A ``StateAndRef`` pairs a ``StateRef`` with the state it points to.
val ourStateAndRef: StateAndRef<DummyState> = serviceHub.toStateAndRef<DummyState>(ourStateRef)
/**-----------------------------------------
* GATHERING OTHER TRANSACTION COMPONENTS *
-----------------------------------------**/
progressTracker.currentStep = OTHER_TX_COMPONENTS
// Output states are constructed from scratch.
val ourOutput: DummyState = DummyState()
// Or as copies of other states with some properties changed.
val ourOtherOutput: DummyState = ourOutput.copy(magicNumber = 77)
// 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.
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)
// ``CommandData`` can either be:
// 1. Of type ``TypeOnlyCommandData``, in which case it only
// serves to attach signers to the transaction and possibly
// fork the contract's verification logic.
val typeOnlyCommandData: TypeOnlyCommandData = DummyContract.Commands.Create()
// 2. Include additional data which can be used by the contract
// during verification, alongside fulfilling the roles above
val commandDataWithData: CommandData = Cash.Commands.Issue(nonce = 12345678)
// 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.
val ourAttachment: SecureHash = SecureHash.sha256("DummyAttachment")
// Time windows can have a start and end time, or be open at either end.
val ourTimeWindow: TimeWindow = TimeWindow.between(Instant.MIN, Instant.MAX)
val ourAfter: TimeWindow = TimeWindow.fromOnly(Instant.MIN)
val ourBefore: TimeWindow = TimeWindow.untilOnly(Instant.MAX)
/**-----------------------
* TRANSACTION BUILDING *
-----------------------**/
progressTracker.currentStep = TX_BUILDING
// There are two types of transaction (notary-change and general),
// and therefore two types of transaction builder:
val notaryChangeTxBuilder: TransactionBuilder = TransactionBuilder(NotaryChange, specificNotary)
val regTxBuilder: TransactionBuilder = TransactionBuilder(General, specificNotary)
// We add items to the transaction builder using ``TransactionBuilder.withItems``:
regTxBuilder.withItems(
// Inputs, as ``StateRef``s that reference to the outputs of previous transactions
ourStateRef,
// Outputs, as ``ContractState``s
ourOutput,
// Commands, as ``Command``s
ourCommand
)
// We can also add items using methods for the individual components:
regTxBuilder.addInputState(ourStateAndRef)
regTxBuilder.addOutputState(ourOutput)
regTxBuilder.addCommand(ourCommand)
regTxBuilder.addAttachment(ourAttachment)
regTxBuilder.addTimeWindow(ourTimeWindow)
/**----------------------
* TRANSACTION SIGNING *
----------------------**/
progressTracker.currentStep = TX_SIGNING
// We finalise the transaction by signing it, converting it into a
// ``SignedTransaction``.
val onceSignedTx: SignedTransaction = serviceHub.signInitialTransaction(regTxBuilder)
// 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)
// In practice, however, the process of gathering every signature
// but the first can be automated using ``CollectSignaturesFlow``.
// See the "Gathering Signatures" section below.
/**---------------------------
* TRANSACTION VERIFICATION *
---------------------------**/
progressTracker.currentStep = TX_VERIFICATION
// Verifying a transaction will also verify every transaction in
// the transaction's dependency chain. So if this was a
// transaction we'd received from a counterparty and it had any
// dependencies, we'd need to download all of these dependencies
// using``ResolveTransactionsFlow`` before verifying it.
// DOCSTART 13
subFlow(ResolveTransactionsFlow(twiceSignedTx, counterparty))
// DOCEND 13
// We can also resolve a `StateRef` dependency chain.
// DOCSTART 14
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.
val wireTx: WireTransaction = twiceSignedTx.tx
// 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``.
val ledgerTx: LedgerTransaction = wireTx.toLedgerTransaction(serviceHub)
// We can now verify the transaction.
ledgerTx.verify()
// 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.
val outputState: DummyState = wireTx.outputs.single().data as DummyState
if (outputState.magicNumber == 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 FlowException("We expected a magic number of 777.")
}
// 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
// requires signatures from all the required signers and is
// contractually valid, it's a valid ledger update.
/**-----------------------
* GATHERING SIGNATURES *
-----------------------**/
progressTracker.currentStep = SIGS_GATHERING
// The list of parties who need to sign a transaction is dictated
// by the transaction's commands. Once we've signed a transaction
// ourselves, we can automatically gather the signatures of the
// other required signers using ``CollectSignaturesFlow``.
// The responder flow will need to call ``SignTransactionFlow``.
// DOCSTART 15
val fullySignedTx: SignedTransaction = subFlow(CollectSignaturesFlow(twiceSignedTx, SIGS_GATHERING.childProgressTracker()))
// DOCEND 15
/**-----------------------------
* FINALISING THE TRANSACTION *
-----------------------------**/
progressTracker.currentStep = FINALISATION
// We notarise the transaction and get it recorded in the vault of
// the participants of all the transaction's states.
// DOCSTART 9
val notarisedTx1: SignedTransaction = subFlow(FinalityFlow(fullySignedTx, FINALISATION.childProgressTracker())).single()
// DOCEND 9
// We can also choose to send it to additional parties who aren't one
// of the state's participants.
// DOCSTART 10
val additionalParties: Set<Party> = setOf(regulator)
val notarisedTx2: SignedTransaction = subFlow(FinalityFlow(listOf(fullySignedTx), additionalParties, FINALISATION.childProgressTracker())).single()
// DOCEND 10
}
}
// ``ResponderFlow`` is our second flow, and will communicate with
// ``InitiatorFlow``.
// We mark ``ResponderFlow`` as an ``InitiatedByFlow``, meaning that it
// can only be started in response to a message from its initiating flow.
// That's ``InitiatorFlow`` in this case.
// Each node also has several flow pairs registered by default - see
// ``AbstractNode.installCoreFlows``.
@InitiatedBy(InitiatorFlow::class)
class ResponderFlow(val counterparty: Party) : FlowLogic<Unit>() {
companion object {
object RECEIVING_AND_SENDING_DATA : Step("Sending data between parties.")
object SIGNING : Step("Responding to CollectSignaturesFlow.")
object FINALISATION : Step("Finalising a transaction.")
fun tracker() = ProgressTracker(
RECEIVING_AND_SENDING_DATA,
SIGNING,
FINALISATION
)
}
override val progressTracker: ProgressTracker = tracker()
@Suspendable
override fun call() {
// The ``ResponderFlow` has all the same APIs available. It looks
// up network information, sends and receives data, and constructs
// transactions in exactly the same way.
/**-----------------------------
* SENDING AND RECEIVING DATA *
-----------------------------**/
progressTracker.currentStep = RECEIVING_AND_SENDING_DATA
// We need to respond to the messages sent by the initiator:
// 1. They sent us an ``Any`` instance
// 2. They waited to receive an ``Integer`` instance back
// 3. They sent a ``String`` instance and waited to receive a
// ``Boolean`` instance back
// Our side of the flow must mirror these calls.
// DOCSTART 8
val any: Any = receive<Any>(counterparty).unwrap { data -> data }
val string: String = sendAndReceive<String>(counterparty, 99).unwrap { data -> data }
send(counterparty, true)
// DOCEND 8
/**----------------------------------------
* RESPONDING TO COLLECT_SIGNATURES_FLOW *
----------------------------------------**/
progressTracker.currentStep = SIGNING
// The responder will often need to respond to a call to
// ``CollectSignaturesFlow``. It does so my invoking its own
// ``SignTransactionFlow`` subclass.
// DOCSTART 16
val signTransactionFlow: SignTransactionFlow = object : SignTransactionFlow(counterparty) {
override fun checkTransaction(stx: SignedTransaction) = requireThat {
// Any additional checking we see fit...
val outputState = stx.tx.outputs.single().data as DummyState
assert(outputState.magicNumber == 777)
}
}
subFlow(signTransactionFlow)
// DOCEND 16
/**-----------------------------
* FINALISING THE TRANSACTION *
-----------------------------**/
progressTracker.currentStep = FINALISATION
// Nothing to do here! As long as some other party calls
// ``FinalityFlow``, the recording of the transaction on our node
// we be handled automatically.
}
}
}

View File

@ -0,0 +1,18 @@
.. highlight:: kotlin
.. raw:: html
<script type="text/javascript" src="_static/jquery.js"></script>
<script type="text/javascript" src="_static/codesets.js"></script>
Flow cookbook
=============
This flow showcases how to use Corda's API, in both Java and Kotlin.
.. container:: codeset
.. literalinclude:: example-code/src/main/kotlin/net/corda/docs/FlowCookbook.kt
:language: kotlin
.. literalinclude:: example-code/src/main/java/net/corda/docs/FlowCookbookJava.java
:language: java