mirror of
https://github.com/corda/corda.git
synced 2025-06-12 20:28:18 +00:00
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:
@ -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
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
||||
|
@ -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`
|
||||
|
Reference in New Issue
Block a user