Introducing StartableByRPC and SchedulableFlow annotations, needed by flows started via RPC and schedulable flows respectively.

CordaPluginRegistry.requiredFlows is no longer needed as a result.
This commit is contained in:
Shams Asari
2017-05-10 11:28:25 +01:00
parent b155764023
commit 48f58b6dbc
52 changed files with 401 additions and 434 deletions

View File

@ -8,8 +8,11 @@ UNRELEASED
----------
* API changes:
* Initiating flows (i.e. those which initiate flows in a counterparty) are now required to be annotated with
``InitiatingFlow``.
* ``CordaPluginRegistry.requiredFlows`` is no longer needed. Instead annotate any flows you wish to start via RPC with
``@StartableByRPC`` and any scheduled flows with ``@SchedulableFlow``.
* Flows which initiate flows in their counterparties (an example of which is the ``NotaryFlow.Client``) are now
required to be annotated with ``@InitiatingFlow``.
* ``PluginServiceHub.registerFlowInitiator`` has been deprecated and replaced by ``registerServiceFlow`` with the
marker Class restricted to ``FlowLogic``. In line with the introduction of ``InitiatingFlow``, it throws an

View File

@ -45,19 +45,7 @@ extensions to be created, or registered at startup. In particular:
jars. These static serving directories will not be available if the
bundled web server is not started.
c. The ``requiredFlows`` property is used to declare new protocols in
the plugin jar. Specifically the property must return a map with a key
naming each exposed top level flow class and a value which is a set
naming every parameter class that will be passed to the flow's
constructor. Standard ``java.lang.*`` and ``kotlin.*`` types do not need
to be included, but all other parameter types, or concrete interface
implementations need declaring. Declaring a specific flow in this map
white lists it for activation by the ``FlowLogicRefFactory``. White
listing is not strictly required for ``subFlows`` used internally, but
is required for any top level flow, or a flow which is invoked through
the scheduler.
d. The ``servicePlugins`` property returns a list of classes which will
c. The ``servicePlugins`` property returns a list of classes which will
be instantiated once during the ``AbstractNode.start`` call. These
classes must provide a single argument constructor which will receive a
``PluginServiceHub`` reference. They must also extend the abstract class
@ -90,7 +78,7 @@ extensions to be created, or registered at startup. In particular:
functions inside the node, for instance to initiate workflows when
certain conditions are met.
e. The ``customizeSerialization`` function allows classes to be whitelisted
d. The ``customizeSerialization`` function allows classes to be whitelisted
for object serialisation, over and above those tagged with the ``@CordaSerializable``
annotation. In general the annotation should be preferred. For
instance new state types will need to be explicitly registered. This will be called at

View File

@ -12,11 +12,10 @@ App plugins
To create an app plugin you must extend from `CordaPluginRegistry`_. The JavaDoc contains
specific details of the implementation, but you can extend the server in the following ways:
1. Required flows: Specify which flows will be whitelisted for use in your RPC calls.
2. Service plugins: Register your services (see below).
3. Web APIs: You may register your own endpoints under /api/ of the bundled web server.
4. Static web endpoints: You may register your own static serving directories for serving web content from the web server.
5. Whitelisting your additional contract, state and other classes for object serialization. Any class that forms part
1. Service plugins: Register your services (see below).
2. Web APIs: You may register your own endpoints under /api/ of the bundled web server.
3. Static web endpoints: You may register your own static serving directories for serving web content from the web server.
4. Whitelisting your additional contract, state and other classes for object serialization. Any class that forms part
of a persisted state, that is used in messaging between flows or in RPC needs to be whitelisted.
Services

View File

@ -42,7 +42,8 @@ There are two main steps to implementing scheduled events:
``nextScheduledActivity`` to be implemented which returns an optional ``ScheduledActivity`` instance.
``ScheduledActivity`` captures what ``FlowLogic`` instance each node will run, to perform the activity, and when it
will run is described by a ``java.time.Instant``. Once your state implements this interface and is tracked by the
wallet, it can expect to be queried for the next activity when committed to the wallet.
wallet, it can expect to be queried for the next activity when committed to the wallet. The ``FlowLogic`` must be
annotated with ``@SchedulableFlow``.
* If nothing suitable exists, implement a ``FlowLogic`` to be executed by each node as the activity itself.
The important thing to remember is that in the current implementation, each node that is party to the transaction
will execute the same ``FlowLogic``, so it needs to establish roles in the business process based on the contract
@ -90,10 +91,7 @@ business process and to take on those roles. That ``FlowLogic`` will be handed
rate swap ``State`` in question, as well as a tolerance ``Duration`` of how long to wait after the activity is triggered
for the interest rate before indicating an error.
.. note:: This is a way to create a reference to the FlowLogic class and its constructor parameters to
instantiate. The reference can be checked against a per-node whitelist of approved and allowable types as
part of our overall security sandboxing.
.. note:: This is a way to create a reference to the FlowLogic class and its constructor parameters to instantiate.
As previously mentioned, we currently need a small network handler to assist with session setup until the work to
automate that is complete. See the interest rate swap specific implementation ``FixingSessionInitiationHandler`` which

View File

@ -206,9 +206,10 @@ how to register handlers with the messaging system (see ":doc:`messaging`") and
when messages arrive. It provides the send/receive/sendAndReceive calls that let the code request network
interaction and it will save/restore serialised versions of the fiber at the right times.
Flows can be invoked in several ways. For instance, they can be triggered by scheduled events,
see ":doc:`event-scheduling`" to learn more about this. Or they can be triggered directly via the Java-level node RPC
APIs from your app code.
Flows can be invoked in several ways. For instance, they can be triggered by scheduled events (in which case they need to
be annotated with ``@SchedulableFlow``), see ":doc:`event-scheduling`" to learn more about this. They can also be triggered
directly via the node's RPC API from your app code (in which case they need to be annotated with `StartableByRPC`). It's
possible for a flow to be of both types.
You request a flow to be invoked by using the ``CordaRPCOps.startFlowDynamic`` method. This takes a
Java reflection ``Class`` object that describes the flow class to use (in this case, either ``Buyer`` or ``Seller``).
@ -399,15 +400,35 @@ This code is longer but no more complicated. Here are some things to pay attenti
As you can see, the flow logic is straightforward and does not contain any callbacks or network glue code, despite
the fact that it takes minimal resources and can survive node restarts.
Initiating communication
------------------------
Flow sessions
-------------
Now that we have both sides of the deal negotation implemented as flows we need a way to start things off. We do this by
having one side initiate communication and the other respond to it and start their flow. Initiation is typically done using
RPC with the ``startFlowDynamic`` method. The initiating flow has be to annotated with ``InitiatingFlow``. In our example
it doesn't matter which flow is the initiator and which is the initiated, which is why neither ``Buyer`` nor ``Seller``
are annotated with it. For example, if we choose the seller side as the initiator then we need a seller starter flow that
might look something like this:
Before going any further it will be useful to describe how flows communicate with each other. A node may have many flows
running at the same time, and perhaps communicating with the same counterparty node but for different purposes. Therefore
flows need a way to segregate communication channels so that concurrent conversations between flows on the same set of nodes
do not interfere with each other.
To achieve this the flow framework initiates a new flow session each time a flow starts communicating with a ``Party``
for the first time. A session is simply a pair of IDs, one for each side, to allow the node to route received messages to
the correct flow. If the other side accepts the session request then subsequent sends and receives to that same ``Party``
will use the same session. A session ends when either flow ends, whether as expected or pre-maturely. If a flow ends
pre-maturely then the other side will be notified of that and they will also end, as the whole point of flows is a known
sequence of message transfers. Flows end pre-maturely due to exceptions, and as described above, if that exception is
``FlowException`` or a sub-type then it will propagate to the other side. Any other exception will not propagate.
Taking a step back, we mentioned that the other side has to accept the session request for there to be a communication
channel. A node accepts a session request if it has registered the flow type (the fully-qualified class name) that is
making the request - each session initiation includes the initiating flow type. The registration is done by a CorDapp
which has made available the particular flow communication, using ``PluginServiceHub.registerServiceFlow``. This method
specifies a flow factory for generating the counter-flow to any given initiating flow. If this registration doesn't exist
then no further communication takes place and the initiating flow ends with an exception. The initiating flow has to be
annotated with ``InitiatingFlow``.
Going back to our buyer and seller flows, we need a way to initiate communication between the two. This is typically done
with one side started manually using the ``startFlowDynamic`` RPC and this initiates the counter-flow on the other side.
In this case it doesn't matter which flow is the initiator and which is the initiated, which is why neither ``Buyer`` nor
``Seller`` are annotated with ``InitiatingFlow``. For example, if we choose the seller side as the initiator then we need
to create a simple seller starter flow that has the annotation we need:
.. container:: codeset

View File

@ -3,13 +3,13 @@
Client RPC API tutorial
=======================
In this tutorial we will build a simple command line utility that
connects to a node, creates some Cash transactions and meanwhile dumps
the transaction graph to the standard output. We will then put some
simple visualisation on top. For an explanation on how the RPC works
see :doc:`clientrpc`.
In this tutorial we will build a simple command line utility that connects to a node, creates some Cash transactions and
meanwhile dumps the transaction graph to the standard output. We will then put some simple visualisation on top. For an
explanation on how the RPC works see :doc:`clientrpc`.
We start off by connecting to the node itself. For the purposes of the tutorial we will use the Driver to start up a notary and a node that issues/exits and moves Cash around for herself. To authenticate we will use the certificates of the nodes directly.
We start off by connecting to the node itself. For the purposes of the tutorial we will use the Driver to start up a notary
and a node that issues/exits and moves Cash around for herself. To authenticate we will use the certificates of the nodes
directly.
Note how we configure the node to create a user that has permission to start the CashFlow.
@ -25,14 +25,16 @@ Now we can connect to the node itself using a valid RPC login. We login using th
:start-after: START 2
:end-before: END 2
We start generating transactions in a different thread (``generateTransactions`` to be defined later) using ``proxy``, which exposes the full RPC interface of the node:
We start generating transactions in a different thread (``generateTransactions`` to be defined later) using ``proxy``,
which exposes the full RPC interface of the node:
.. literalinclude:: ../../core/src/main/kotlin/net/corda/core/messaging/CordaRPCOps.kt
:language: kotlin
:start-after: interface CordaRPCOps
:end-before: }
.. warning:: This API is evolving and will continue to grow as new functionality and features added to Corda are made available to RPC clients.
.. warning:: This API is evolving and will continue to grow as new functionality and features added to Corda are made
available to RPC clients.
The one we need in order to dump the transaction graph is ``verifiedTransactions``. The type signature tells us that the
RPC will return a list of transactions and an Observable stream. This is a general pattern, we query some data and the
@ -61,13 +63,19 @@ Now we just need to create the transactions themselves!
:start-after: START 6
:end-before: END 6
We utilise several RPC functions here to query things like the notaries in the node cluster or our own vault. These RPC functions also return ``Observable`` objects so that the node can send us updated values. However, we don't need updates here and so we mark these observables as ``notUsed``. (As a rule, you should always either subscribe to an ``Observable`` or mark it as not used. Failing to do this will leak resources in the node.)
We utilise several RPC functions here to query things like the notaries in the node cluster or our own vault. These RPC
functions also return ``Observable`` objects so that the node can send us updated values. However, we don't need updates
here and so we mark these observables as ``notUsed``. (As a rule, you should always either subscribe to an ``Observable``
or mark it as not used. Failing to do this will leak resources in the node.)
Then in a loop we generate randomly either an Issue, a Pay or an Exit transaction.
The RPC we need to initiate a Cash transaction is ``startFlowDynamic`` which may start an arbitrary flow, given sufficient permissions to do so. We won't use this function directly, but rather a type-safe wrapper around it ``startFlow`` that type-checks the arguments for us.
The RPC we need to initiate a Cash transaction is ``startFlowDynamic`` which may start an arbitrary flow, given sufficient
permissions to do so. We won't use this function directly, but rather a type-safe wrapper around it ``startFlow`` that
type-checks the arguments for us.
Finally we have everything in place: we start a couple of nodes, connect to them, and start creating transactions while listening on successfully created ones, which are dumped to the console. We just need to run it!:
Finally we have everything in place: we start a couple of nodes, connect to them, and start creating transactions while
listening on successfully created ones, which are dumped to the console. We just need to run it!:
.. code-block:: text
@ -106,9 +114,10 @@ RPC credentials associated with a Client must match the permission set configure
This refers to both authentication (username and password) and role-based authorisation (a permissioned set of RPC operations an
authenticated user is entitled to run).
.. note:: Permissions are represented as *String's* to allow RPC implementations to add their own permissioning.
Currently the only permission type defined is *StartFlow*, which defines a list of whitelisted flows an authenticated use may execute.
An administrator user (or a developer) may also be assigned the ``ALL`` permission, which grants access to any flow.
.. note:: Permissions are represented as *String's* to allow RPC implementations to add their own permissioning. Currently
the only permission type defined is *StartFlow*, which defines a list of whitelisted flows an authenticated use may
execute. An administrator user (or a developer) may also be assigned the ``ALL`` permission, which grants access to
any flow.
In the instructions above the server node permissions are configured programmatically in the driver code:
@ -126,7 +135,8 @@ When starting a standalone node using a configuration file we must supply the RP
{ username=user, password=password, permissions=[ StartFlow.net.corda.flows.CashFlow ] }
]
When using the gradle Cordformation plugin to configure and deploy a node you must supply the RPC credentials in a similar manner:
When using the gradle Cordformation plugin to configure and deploy a node you must supply the RPC credentials in a similar
manner:
.. code-block:: text
@ -148,5 +158,8 @@ You can then deploy and launch the nodes (Notary and Alice) as follows:
./docs/source/example-code/build/install/docs/source/example-code/bin/client-rpc-tutorial Print
./docs/source/example-code/build/install/docs/source/example-code/bin/client-rpc-tutorial Visualise
With regards to the start flow RPCs, there is an extra layer of security whereby the flow to be executed has to be
annotated with ``@StartableByRPC``. Flows without this annotation cannot execute using RPC.
See more on security in :doc:`secure-coding-guidelines`, node configuration in :doc:`corda-configuration-file` and
Cordformation in :doc:`creating-a-cordapp`