diff --git a/docs/source/api-flows.rst b/docs/source/api-flows.rst index 7c57be9503..47676aefa3 100644 --- a/docs/source/api-flows.rst +++ b/docs/source/api-flows.rst @@ -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 \ No newline at end of file + :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 \ No newline at end of file diff --git a/docs/source/building-a-cordapp-index.rst b/docs/source/building-a-cordapp-index.rst index d5a3137e7d..1e6cb3114d 100644 --- a/docs/source/building-a-cordapp-index.rst +++ b/docs/source/building-a-cordapp-index.rst @@ -7,4 +7,5 @@ Building a CorDapp cordapp-overview writing-cordapps api-index + flow-cookbook cheat-sheet \ No newline at end of file diff --git a/docs/source/example-code/src/main/java/net/corda/docs/FlowCookbookJava.java b/docs/source/example-code/src/main/java/net/corda/docs/FlowCookbookJava.java new file mode 100644 index 0000000000..5ac2bff1ce --- /dev/null +++ b/docs/source/example-code/src/main/java/net/corda/docs/FlowCookbookJava.java @@ -0,0 +1,506 @@ +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.QueryCriteria; +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 net.corda.core.node.services.vault.QueryCriteria.VaultQueryCriteria; +import rx.Observable; + +import java.security.PublicKey; +import java.time.Instant; +import java.util.Collections; +import java.util.HashSet; +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: + Vault.StateStatus status = Vault.StateStatus.UNCONSUMED; + Set<Class<DummyState>> dummyStateTypes = new HashSet<>(ImmutableList.of(DummyState.class)); + + VaultQueryCriteria criteria = new VaultQueryCriteria(status, null, dummyStateTypes); + Vault.Page<DummyState> results = getServiceHub().getVaultService().queryBy(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; + } + } +} \ No newline at end of file diff --git a/docs/source/example-code/src/main/kotlin/net/corda/docs/FlowCookbook.kt b/docs/source/example-code/src/main/kotlin/net/corda/docs/FlowCookbook.kt new file mode 100644 index 0000000000..edc75fbb0e --- /dev/null +++ b/docs/source/example-code/src/main/kotlin/net/corda/docs/FlowCookbook.kt @@ -0,0 +1,470 @@ +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 +import net.corda.core.node.services.vault.QueryCriteria +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.* +import net.corda.core.utilities.ProgressTracker.Step +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 = QueryCriteria.VaultQueryCriteria() // default is UNCONSUMED + val results: Vault.Page<DummyState> = serviceHub.vaultService.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. + } + } +} \ No newline at end of file diff --git a/docs/source/flow-cookbook.rst b/docs/source/flow-cookbook.rst new file mode 100644 index 0000000000..1402c2ea1c --- /dev/null +++ b/docs/source/flow-cookbook.rst @@ -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 \ No newline at end of file