mirror of
https://github.com/corda/corda.git
synced 2025-02-01 08:48:09 +00:00
Rename protocol to flow in docs
This commit is contained in:
parent
1f148cfc95
commit
6c3476e89f
@ -59,18 +59,18 @@ Ensuring that all input states point to the same notary is the responsibility of
|
||||
Changing notaries
|
||||
~~~~~~~~~~~~~~~~~
|
||||
|
||||
To change the notary for an input state, use the ``NotaryChangeProtocol``. For example:
|
||||
To change the notary for an input state, use the ``NotaryChangeFlow``. For example:
|
||||
|
||||
.. sourcecode:: kotlin
|
||||
|
||||
@Suspendable
|
||||
fun changeNotary(originalState: StateAndRef<ContractState>,
|
||||
newNotary: Party): StateAndRef<ContractState> {
|
||||
val protocol = NotaryChangeProtocol.Instigator(originalState, newNotary)
|
||||
return subProtocol(protocol)
|
||||
val flow = NotaryChangeFlow.Instigator(originalState, newNotary)
|
||||
return subFlow(flow)
|
||||
}
|
||||
|
||||
The protocol will:
|
||||
The flow will:
|
||||
|
||||
1. Construct a transaction with the old state as the input and the new state as the output
|
||||
|
||||
@ -152,10 +152,10 @@ At present we have two basic implementations that store committed input states i
|
||||
Obtaining a signature
|
||||
---------------------
|
||||
|
||||
Once a transaction is built and ready to be finalised, normally you would call ``FinalityProtocol`` passing in a
|
||||
Once a transaction is built and ready to be finalised, normally you would call ``FinalityFlow`` passing in a
|
||||
``SignedTransaction`` (including signatures from the participants) and a list of participants to notify. This requests a
|
||||
notary signature if needed, and then sends a copy of the notarised transaction to all participants for them to store.
|
||||
``FinalityProtocol`` delegates to ``NotaryProtocol.Client`` followed by ``BroadcastTransactionProtocol`` to do the
|
||||
``FinalityFlow`` delegates to ``NotaryFlow.Client`` followed by ``BroadcastTransactionFlow`` to do the
|
||||
actual work of notarising and broadcasting the transaction. For example:
|
||||
|
||||
.. sourcecode:: kotlin
|
||||
@ -165,21 +165,21 @@ actual work of notarising and broadcasting the transaction. For example:
|
||||
// We conclusively cannot have all the signatures, as the notary has not signed yet
|
||||
val tx = ptx.toSignedTransaction(checkSufficientSignatures = false)
|
||||
// The empty set would be the trigger events, which are not used here
|
||||
val protocol = FinalityProtocol(tx, emptySet(), participants)
|
||||
return serviceHub.startProtocol("protocol.finalisation", protocol)
|
||||
val flow = FinalityFlow(tx, emptySet(), participants)
|
||||
return serviceHub.startFlow("flow.finalisation", flow)
|
||||
}
|
||||
|
||||
To manually obtain a signature from a notary you can call ``NotaryProtocol.Client`` directly. The protocol will work out
|
||||
To manually obtain a signature from a notary you can call ``NotaryFlow.Client`` directly. The flow will work out
|
||||
which notary needs to be called based on the input states and the timestamp command. For example, the following snippet
|
||||
can be used when writing a custom protocol:
|
||||
can be used when writing a custom flow:
|
||||
|
||||
.. sourcecode:: kotlin
|
||||
|
||||
fun getNotarySignature(wtx: WireTransaction): DigitalSignature.LegallyIdentifiable {
|
||||
return subProtocol(NotaryProtocol.Client(wtx))
|
||||
return subFlow(NotaryFlow.Client(wtx))
|
||||
}
|
||||
|
||||
On conflict the ``NotaryProtocol`` with throw a ``NotaryException`` containing the conflict details:
|
||||
On conflict the ``NotaryFlow`` with throw a ``NotaryException`` containing the conflict details:
|
||||
|
||||
.. sourcecode:: kotlin
|
||||
|
||||
@ -192,4 +192,4 @@ On conflict the ``NotaryProtocol`` with throw a ``NotaryException`` containing t
|
||||
*/
|
||||
data class ConsumingTx(val id: SecureHash, val inputIndex: Int, val requestingParty: Party)
|
||||
|
||||
Conflict handling and resolution is currently the responsibility of the protocol author.
|
||||
Conflict handling and resolution is currently the responsibility of the flow author.
|
||||
|
@ -12,7 +12,7 @@ App Plugins
|
||||
To create an app plugin you must 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 protocols: Specify which protocols will be whitelisted for use in your web APIs.
|
||||
1. Required flows: Specify which flows will be whitelisted for use in your web APIs.
|
||||
2. Service plugins: Register your services (see below).
|
||||
3. Web APIs: You may register your own endpoints under /api/ of the built-in web server.
|
||||
4. Static web endpoints: You may register your own static serving directories for serving web content.
|
||||
@ -22,7 +22,7 @@ Services
|
||||
--------
|
||||
|
||||
Services are classes which are constructed after the node has started. It is provided a `ServiceHubInternal`_ which
|
||||
allows a richer API than the `ServiceHub`_ exposed to contracts. It enables adding protocols, registering
|
||||
allows a richer API than the `ServiceHub`_ exposed to contracts. It enables adding flows, registering
|
||||
message handlers and more. The service does not run in a separate thread, so the only entry point to the service is during
|
||||
construction, where message handlers should be registered and threads started.
|
||||
|
||||
|
@ -254,7 +254,7 @@ including things like the shape of the transaction. It may seem that it's an ove
|
||||
just immediately respend the big output back to yourself in order to split it? And yes, you could, until you hit
|
||||
scenarios like "the machine requesting the payment doesn't have the keys needed to spend it",
|
||||
which turn out to be very common. So it's really more effective for a recipient to be able to say to the
|
||||
sender, "here's the kind of transaction I want you to send me". The :doc:`protocol framework <protocol-state-machines>`
|
||||
sender, "here's the kind of transaction I want you to send me". The :doc:`flow framework <flow-state-machines>`
|
||||
may provide a vehicle to make such negotiations simpler.
|
||||
|
||||
A further challenge is privacy. Whilst our goal of not sending transactions to nodes that don't "need to know"
|
||||
|
@ -28,8 +28,8 @@ due. If a contract state is consumed in the UTXO model, then what *was* the nex
|
||||
and the next time sensitive event is determined by any successor contract state.
|
||||
|
||||
Knowing when the next time sensitive event is due to occur is useful, but typically some *activity* is expected to take
|
||||
place when this event occurs. We already have a model for business processes in the form of :doc:`protocols <protocol-state-machines>`,
|
||||
so in the platform we have introduced the concept of *scheduled activities* that can invoke protocol state machines
|
||||
place when this event occurs. We already have a model for business processes in the form of :doc:`flows <flow-state-machines>`,
|
||||
so in the platform we have introduced the concept of *scheduled activities* that can invoke flow state machines
|
||||
at a scheduled time. A contract state can optionally described the next scheduled activity for itself. If it omits
|
||||
to do so, then nothing will be scheduled.
|
||||
|
||||
@ -40,18 +40,18 @@ There are two main steps to implementing scheduled events:
|
||||
|
||||
* Have your ``ContractState`` implementation also implement ``SchedulableState``. This requires a method named
|
||||
``nextScheduledActivity`` to be implemented which returns an optional ``ScheduledActivity`` instance.
|
||||
``ScheduledActivity`` captures what ``ProtocolLogic`` instance each node will run, to perform the activity, and when it
|
||||
``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.
|
||||
* If nothing suitable exists, implement a ``ProtocolLogic`` to be executed by each node as the activity itself.
|
||||
* 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 ``ProtocolLogic``, so it needs to establish roles in the business process based on the contract
|
||||
will execute the same ``FlowLogic``, so it needs to establish roles in the business process based on the contract
|
||||
state and the node it is running on. Each side will follow different but complementary paths through the business logic.
|
||||
|
||||
.. note:: The scheduler's clock always operates in the UTC time zone for uniformity, so any time zone logic must be
|
||||
performed by the contract, using ``ZonedDateTime``.
|
||||
|
||||
In the short term, until we have automatic protocol session set up, you will also likely need to install a network
|
||||
In the short term, until we have automatic flow session set up, you will also likely need to install a network
|
||||
handler to help with obtaining a unqiue and secure random session. An example is described below.
|
||||
|
||||
The production and consumption of ``ContractStates`` is observed by the scheduler and the activities associated with
|
||||
@ -73,30 +73,30 @@ Let's take an example of the interest rate swap fixings for our scheduled events
|
||||
.. sourcecode:: kotlin
|
||||
|
||||
override fun nextScheduledActivity(thisStateRef: StateRef,
|
||||
protocolLogicRefFactory: ProtocolLogicRefFactory): ScheduledActivity? {
|
||||
flowLogicRefFactory: FlowLogicRefFactory): ScheduledActivity? {
|
||||
val nextFixingOf = nextFixingOf() ?: return null
|
||||
|
||||
val (instant, duration) = suggestInterestRateAnnouncementTimeWindow(index = nextFixingOf.name,
|
||||
source = floatingLeg.indexSource,
|
||||
date = nextFixingOf.forDay)
|
||||
return ScheduledActivity(protocolLogicRefFactory.create(TwoPartyDealProtocol.FixingRoleDecider::class.java,
|
||||
return ScheduledActivity(flowLogicRefFactory.create(TwoPartyDealFlow.FixingRoleDecider::class.java,
|
||||
thisStateRef, duration), instant)
|
||||
}
|
||||
|
||||
The first thing this does is establish if there are any remaining fixings. If there are none, then it returns ``null``
|
||||
to indicate that there is no activity to schedule. Otherwise it calculates the ``Instant`` at which the interest rate
|
||||
should become available and schedules an activity at that time to work out what roles each node will take in the fixing
|
||||
business process and to take on those roles. That ``ProtocolLogic`` will be handed the ``StateRef`` for the interest
|
||||
business process and to take on those roles. That ``FlowLogic`` will be handed the ``StateRef`` for the interest
|
||||
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 ProtocolLogic class and its constructor parameters to
|
||||
.. 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.
|
||||
|
||||
|
||||
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
|
||||
is responsible for starting a ``ProtocolLogic`` to perform one role in the fixing protocol with the ``sessionID`` sent
|
||||
by the ``FixingRoleDecider`` on the other node which then launches the other role in the fixing protocol. Currently
|
||||
is responsible for starting a ``FlowLogic`` to perform one role in the fixing flow with the ``sessionID`` sent
|
||||
by the ``FixingRoleDecider`` on the other node which then launches the other role in the fixing flow. Currently
|
||||
the handler needs to be manually installed in the node.
|
||||
|
@ -4,11 +4,11 @@
|
||||
<script type="text/javascript" src="_static/jquery.js"></script>
|
||||
<script type="text/javascript" src="_static/codesets.js"></script>
|
||||
|
||||
Protocol state machines
|
||||
=======================
|
||||
Flow state machines
|
||||
===================
|
||||
|
||||
This article explains our experimental approach to modelling financial protocols in code. It explains how the
|
||||
platform's state machine framework is used, and takes you through the code for a simple 2-party asset trading protocol
|
||||
This article explains our experimental approach to modelling financial flows in code. It explains how the
|
||||
platform's state machine framework is used, and takes you through the code for a simple 2-party asset trading flow
|
||||
which is included in the source.
|
||||
|
||||
Introduction
|
||||
@ -21,23 +21,23 @@ shared ledger, and transactions may alter many states simultaneously and atomica
|
||||
Blockchain systems such as Bitcoin support the idea of building up a finished, signed transaction by passing around
|
||||
partially signed invalid transactions outside of the main network, and by doing this you can implement
|
||||
*delivery versus payment* such that there is no chance of settlement failure, because the movement of cash and the
|
||||
traded asset are performed atomically by the same transaction. To perform such a trade involves a multi-step protocol
|
||||
traded asset are performed atomically by the same transaction. To perform such a trade involves a multi-step flow
|
||||
in which messages are passed back and forth privately between parties, checked, signed and so on.
|
||||
|
||||
Despite how useful these protocols are, platforms such as Bitcoin and Ethereum do not assist the developer with the rather
|
||||
Despite how useful these flows are, platforms such as Bitcoin and Ethereum do not assist the developer with the rather
|
||||
tricky task of actually building them. That is unfortunate. There are many awkward problems in their implementation
|
||||
that a good platform would take care of for you, problems like:
|
||||
|
||||
* Avoiding "callback hell" in which code that should ideally be sequential is turned into an unreadable mess due to the
|
||||
desire to avoid using up a thread for every protocol instantiation.
|
||||
* Surviving node shutdowns/restarts that may occur in the middle of the protocol without complicating things. This
|
||||
implies that the state of the protocol must be persisted to disk.
|
||||
desire to avoid using up a thread for every flow instantiation.
|
||||
* Surviving node shutdowns/restarts that may occur in the middle of the flow without complicating things. This
|
||||
implies that the state of the flow must be persisted to disk.
|
||||
* Error handling.
|
||||
* Message routing.
|
||||
* Serialisation.
|
||||
* Catching type errors, in which the developer gets temporarily confused and expects to receive/send one type of message
|
||||
when actually they need to receive/send another.
|
||||
* Unit testing of the finished protocol.
|
||||
* Unit testing of the finished flow.
|
||||
|
||||
Actor frameworks can solve some of the above but they are often tightly bound to a particular messaging layer, and
|
||||
we would like to keep a clean separation. Additionally, they are typically not type safe, and don't make persistence or
|
||||
@ -49,7 +49,7 @@ Java and took over a month of full time work to develop. Most of that code is co
|
||||
message passing, lifecycle management, error handling and callback management. Because the business logic is quite
|
||||
spread out the code can be difficult to read and debug.
|
||||
|
||||
As small contract-specific trading protocols are a common occurence in finance, we provide a framework for the
|
||||
As small contract-specific trading flows are a common occurence in finance, we provide a framework for the
|
||||
construction of them that automatically handles many of the concerns outlined above.
|
||||
|
||||
Theory
|
||||
@ -70,19 +70,19 @@ We use continuations for the following reasons:
|
||||
|
||||
A *state machine* is a piece of code that moves through various *states*. These are not the same as states in the data
|
||||
model (that represent facts about the world on the ledger), but rather indicate different stages in the progression
|
||||
of a multi-stage protocol. Typically writing a state machine would require the use of a big switch statement and some
|
||||
of a multi-stage flow. Typically writing a state machine would require the use of a big switch statement and some
|
||||
explicit variables to keep track of where you're up to. The use of continuations avoids this hassle.
|
||||
|
||||
A two party trading protocol
|
||||
----------------------------
|
||||
A two party trading flow
|
||||
------------------------
|
||||
|
||||
We would like to implement the "hello world" of shared transaction building protocols: a seller wishes to sell some
|
||||
We would like to implement the "hello world" of shared transaction building flows: a seller wishes to sell some
|
||||
*asset* (e.g. some commercial paper) in return for *cash*. The buyer wishes to purchase the asset using his cash. They
|
||||
want the trade to be atomic so neither side is exposed to the risk of settlement failure. We assume that the buyer
|
||||
and seller have found each other and arranged the details on some exchange, or over the counter. The details of how
|
||||
the trade is arranged isn't covered in this article.
|
||||
|
||||
Our protocol has two parties (B and S for buyer and seller) and will proceed as follows:
|
||||
Our flow has two parties (B and S for buyer and seller) and will proceed as follows:
|
||||
|
||||
1. S sends a ``StateAndRef`` pointing to the state they want to sell to B, along with info about the price they require
|
||||
B to pay.
|
||||
@ -91,15 +91,15 @@ Our protocol has two parties (B and S for buyer and seller) and will proceed as
|
||||
it lacks a signature from S authorising movement of the asset.
|
||||
3. S signs it and hands the now finalised ``SignedTransaction`` back to B.
|
||||
|
||||
You can find the implementation of this protocol in the file ``finance/src/main/kotlin/net.corda.protocols/TwoPartyTradeProtocol.kt``.
|
||||
You can find the implementation of this flow in the file ``finance/src/main/kotlin/net.corda.flows/TwoPartyTradeFlow.kt``.
|
||||
|
||||
Assuming no malicious termination, they both end the protocol being in posession of a valid, signed transaction that
|
||||
Assuming no malicious termination, they both end the flow being in posession of a valid, signed transaction that
|
||||
represents an atomic asset swap.
|
||||
|
||||
Note that it's the *seller* who initiates contact with the buyer, not vice-versa as you might imagine.
|
||||
|
||||
We start by defining a wrapper that namespaces the protocol code, two functions to start either the buy or sell side
|
||||
of the protocol, and two classes that will contain the protocol definition. We also pick what data will be used by
|
||||
We start by defining a wrapper that namespaces the flow code, two functions to start either the buy or sell side
|
||||
of the flow, and two classes that will contain the flow definition. We also pick what data will be used by
|
||||
each side.
|
||||
|
||||
.. note:: The code samples in this tutorial are only available in Kotlin, but you can use any JVM language to
|
||||
@ -109,14 +109,14 @@ each side.
|
||||
|
||||
.. sourcecode:: kotlin
|
||||
|
||||
object TwoPartyTradeProtocol {
|
||||
object TwoPartyTradeFlow {
|
||||
|
||||
class UnacceptablePriceException(val givenPrice: Amount<Currency>) : Exception("Unacceptable price: $givenPrice")
|
||||
class AssetMismatchException(val expectedTypeName: String, val typeName: String) : Exception() {
|
||||
override fun toString() = "The submitted asset didn't match the expected type: $expectedTypeName vs $typeName"
|
||||
}
|
||||
|
||||
// This object is serialised to the network and is the first protocol message the seller sends to the buyer.
|
||||
// This object is serialised to the network and is the first flow message the seller sends to the buyer.
|
||||
data class SellerTradeInfo(
|
||||
val assetForSale: StateAndRef<OwnableState>,
|
||||
val price: Amount<Currency>,
|
||||
@ -131,7 +131,7 @@ each side.
|
||||
val assetToSell: StateAndRef<OwnableState>,
|
||||
val price: Amount<Currency>,
|
||||
val myKeyPair: KeyPair,
|
||||
override val progressTracker: ProgressTracker = Seller.tracker()) : ProtocolLogic<SignedTransaction>() {
|
||||
override val progressTracker: ProgressTracker = Seller.tracker()) : FlowLogic<SignedTransaction>() {
|
||||
@Suspendable
|
||||
override fun call(): SignedTransaction {
|
||||
TODO()
|
||||
@ -141,7 +141,7 @@ each side.
|
||||
open class Buyer(val otherSide: Party,
|
||||
val notary: Party,
|
||||
val acceptablePrice: Amount<Currency>,
|
||||
val typeToBuy: Class<out OwnableState>) : ProtocolLogic<SignedTransaction>() {
|
||||
val typeToBuy: Class<out OwnableState>) : FlowLogic<SignedTransaction>() {
|
||||
@Suspendable
|
||||
override fun call(): SignedTransaction {
|
||||
TODO()
|
||||
@ -149,8 +149,8 @@ each side.
|
||||
}
|
||||
}
|
||||
|
||||
This code defines several classes nested inside the main ``TwoPartyTradeProtocol`` singleton. Some of the classes are
|
||||
simply protocol messages or exceptions. The other two represent the buyer and seller side of the protocol.
|
||||
This code defines several classes nested inside the main ``TwoPartyTradeFlow`` singleton. Some of the classes are
|
||||
simply flow messages or exceptions. The other two represent the buyer and seller side of the flow.
|
||||
|
||||
Going through the data needed to become a seller, we have:
|
||||
|
||||
@ -166,11 +166,11 @@ And for the buyer:
|
||||
- ``acceptablePrice: Amount<Currency>`` - the price that was agreed upon out of band. If the seller specifies
|
||||
a price less than or equal to this, then the trade will go ahead.
|
||||
- ``typeToBuy: Class<out OwnableState>`` - the type of state that is being purchased. This is used to check that the
|
||||
sell side of the protocol isn't trying to sell us the wrong thing, whether by accident or on purpose.
|
||||
sell side of the flow isn't trying to sell us the wrong thing, whether by accident or on purpose.
|
||||
|
||||
Alright, so using this protocol shouldn't be too hard: in the simplest case we can just create a Buyer or Seller
|
||||
with the details of the trade, depending on who we are. We then have to start the protocol in some way. Just
|
||||
calling the ``call`` function ourselves won't work: instead we need to ask the framework to start the protocol for
|
||||
Alright, so using this flow shouldn't be too hard: in the simplest case we can just create a Buyer or Seller
|
||||
with the details of the trade, depending on who we are. We then have to start the flow in some way. Just
|
||||
calling the ``call`` function ourselves won't work: instead we need to ask the framework to start the flow for
|
||||
us. More on that in a moment.
|
||||
|
||||
Suspendable functions
|
||||
@ -178,9 +178,9 @@ Suspendable functions
|
||||
|
||||
The ``call`` function of the buyer/seller classes is marked with the ``@Suspendable`` annotation. What does this mean?
|
||||
|
||||
As mentioned above, our protocol framework will at points suspend the code and serialise it to disk. For this to work,
|
||||
As mentioned above, our flow framework will at points suspend the code and serialise it to disk. For this to work,
|
||||
any methods on the call stack must have been pre-marked as ``@Suspendable`` so the bytecode rewriter knows to modify
|
||||
the underlying code to support this new feature. A protocol is suspended when calling either ``receive``, ``send`` or
|
||||
the underlying code to support this new feature. A flow is suspended when calling either ``receive``, ``send`` or
|
||||
``sendAndReceive`` which we will learn more about below. For now, just be aware that when one of these methods is
|
||||
invoked, all methods on the stack must have been marked. If you forget, then in the unit test environment you will
|
||||
get a useful error message telling you which methods you didn't mark. The fix is simple enough: just add the annotation
|
||||
@ -188,30 +188,30 @@ and try again.
|
||||
|
||||
.. note:: Java 9 is likely to remove this pre-marking requirement completely.
|
||||
|
||||
Starting your protocol
|
||||
----------------------
|
||||
Starting your flow
|
||||
------------------
|
||||
|
||||
The ``StateMachineManager`` is the class responsible for taking care of all running protocols in a node. It knows
|
||||
The ``StateMachineManager`` is the class responsible for taking care of all running flows in a node. It knows
|
||||
how to register handlers with the messaging system (see ":doc:`messaging`") and iterate the right state machine
|
||||
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.
|
||||
|
||||
Protocols can be invoked in several ways. For instance, they can be triggered by scheduled events,
|
||||
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 via the HTTP API. Or they can
|
||||
be triggered directly via the Java-level node APIs from your app code.
|
||||
|
||||
You request a protocol to be invoked by using the ``ServiceHub.invokeProtocolAsync`` method. This takes a
|
||||
Java reflection ``Class`` object that describes the protocol class to use (in this case, either ``Buyer`` or ``Seller``).
|
||||
It also takes a set of arguments to pass to the constructor. Because it's possible for protocol invocations to
|
||||
You request a flow to be invoked by using the ``ServiceHub.invokeFlowAsync`` method. This takes a
|
||||
Java reflection ``Class`` object that describes the flow class to use (in this case, either ``Buyer`` or ``Seller``).
|
||||
It also takes a set of arguments to pass to the constructor. Because it's possible for flow invocations to
|
||||
be requested by untrusted code (e.g. a state that you have been sent), the types that can be passed into the
|
||||
protocol are checked against a whitelist, which can be extended by apps themselves at load time.
|
||||
flow are checked against a whitelist, which can be extended by apps themselves at load time.
|
||||
|
||||
The process of starting a protocol returns a ``ListenableFuture`` that you can use to either block waiting for
|
||||
The process of starting a flow returns a ``ListenableFuture`` that you can use to either block waiting for
|
||||
the result, or register a callback that will be invoked when the result is ready.
|
||||
|
||||
In a two party protocol only one side is to be manually started using ``ServiceHub.invokeProtocolAsync``. The other side
|
||||
has to be registered by its node to respond to the initiating protocol via ``ServiceHubInternal.registerProtocolInitiator``.
|
||||
In our example it doesn't matter which protocol is the initiator and which is the initiated. For example, if we are to
|
||||
In a two party flow only one side is to be manually started using ``ServiceHub.invokeFlowAsync``. The other side
|
||||
has to be registered by its node to respond to the initiating flow via ``ServiceHubInternal.registerFlowInitiator``.
|
||||
In our example it doesn't matter which flow is the initiator and which is the initiated. For example, if we are to
|
||||
take the seller as the initiator then we would register the buyer as such:
|
||||
|
||||
.. container:: codeset
|
||||
@ -220,20 +220,20 @@ take the seller as the initiator then we would register the buyer as such:
|
||||
|
||||
val services: ServiceHubInternal = TODO()
|
||||
|
||||
services.registerProtocolInitiator(Seller::class) { otherParty ->
|
||||
services.registerFlowInitiator(Seller::class) { otherParty ->
|
||||
val notary = services.networkMapCache.notaryNodes[0]
|
||||
val acceptablePrice = TODO()
|
||||
val typeToBuy = TODO()
|
||||
Buyer(otherParty, notary, acceptablePrice, typeToBuy)
|
||||
}
|
||||
|
||||
This is telling the buyer node to fire up an instance of ``Buyer`` (the code in the lambda) when the initiating protocol
|
||||
This is telling the buyer node to fire up an instance of ``Buyer`` (the code in the lambda) when the initiating flow
|
||||
is a seller (``Seller::class``).
|
||||
|
||||
Implementing the seller
|
||||
-----------------------
|
||||
|
||||
Let's implement the ``Seller.call`` method. This will be run when the protocol is invoked.
|
||||
Let's implement the ``Seller.call`` method. This will be run when the flow is invoked.
|
||||
|
||||
.. container:: codeset
|
||||
|
||||
@ -255,7 +255,7 @@ now signed by both the buyer and the seller. We then send this request to a nota
|
||||
timestamp in the transaction (if any) is valid and there are no double spends, and send back both
|
||||
our signature and the notaries signature. Note we should not send to the notary until all other required signatures have been appended
|
||||
as the notary may validate the signatures as well as verifying for itself the transactional integrity.
|
||||
Finally, we hand back to the code that invoked the protocol the finished transaction.
|
||||
Finally, we hand back to the code that invoked the flow the finished transaction.
|
||||
|
||||
Let's fill out the ``receiveAndCheckProposedTransaction()`` method.
|
||||
|
||||
@ -265,7 +265,7 @@ Let's fill out the ``receiveAndCheckProposedTransaction()`` method.
|
||||
|
||||
@Suspendable
|
||||
private fun receiveAndCheckProposedTransaction(): SignedTransaction {
|
||||
// Make the first message we'll send to kick off the protocol.
|
||||
// Make the first message we'll send to kick off the flow.
|
||||
val hello = SellerTradeInfo(assetToSell, price, myKeyPair.public)
|
||||
|
||||
val maybeSTX = sendAndReceive<SignedTransaction>(otherSide, hello)
|
||||
@ -282,7 +282,7 @@ Let's fill out the ``receiveAndCheckProposedTransaction()`` method.
|
||||
|
||||
// Download and check all the things that this transaction depends on and verify it is contract-valid,
|
||||
// even though it is missing signatures.
|
||||
subProtocol(ResolveTransactionsProtocol(wtx, otherSide))
|
||||
subFlow(ResolveTransactionsFlow(wtx, otherSide))
|
||||
|
||||
if (wtx.outputs.map { it.data }.sumCashBy(myKeyPair.public).withoutIssuer() != price)
|
||||
throw IllegalArgumentException("Transaction is not sending us the right amount of cash")
|
||||
@ -291,7 +291,7 @@ Let's fill out the ``receiveAndCheckProposedTransaction()`` method.
|
||||
}
|
||||
}
|
||||
|
||||
Let's break this down. We fill out the initial protocol message with the trade info, and then call ``sendAndReceive``.
|
||||
Let's break this down. We fill out the initial flow message with the trade info, and then call ``sendAndReceive``.
|
||||
This function takes a few arguments:
|
||||
|
||||
- The party on the other side.
|
||||
@ -300,8 +300,8 @@ This function takes a few arguments:
|
||||
back something else an exception is thrown.
|
||||
|
||||
Once ``sendAndReceive`` is called, the call method will be suspended into a continuation and saved to persistent
|
||||
storage. If the node crashes or is restarted, the protocol will effectively continue as if nothing had happened. Your
|
||||
code may remain blocked inside such a call for seconds, minutes, hours or even days in the case of a protocol that
|
||||
storage. If the node crashes or is restarted, the flow will effectively continue as if nothing had happened. Your
|
||||
code may remain blocked inside such a call for seconds, minutes, hours or even days in the case of a flow that
|
||||
needs human interaction!
|
||||
|
||||
.. note:: There are a couple of rules you need to bear in mind when writing a class that will be used as a continuation.
|
||||
@ -311,7 +311,7 @@ needs human interaction!
|
||||
The second is that as well as being kept on the heap, objects reachable from the stack will be serialised. The state
|
||||
of the function call may be resurrected much later! Kryo doesn't require objects be marked as serialisable, but even so,
|
||||
doing things like creating threads from inside these calls would be a bad idea. They should only contain business
|
||||
logic and only do I/O via the methods exposed by the protocol framework.
|
||||
logic and only do I/O via the methods exposed by the flow framework.
|
||||
|
||||
It's OK to keep references around to many large internal node services though: these will be serialised using a
|
||||
special token that's recognised by the platform, and wired up to the right instance when the continuation is
|
||||
@ -331,10 +331,10 @@ Our "scrubbing" has three parts:
|
||||
2. We resolve the transaction, which we will cover below.
|
||||
3. We verify that the transaction is paying us the demanded price.
|
||||
|
||||
Subprotocols
|
||||
------------
|
||||
Sub-flows
|
||||
---------
|
||||
|
||||
Protocols can be composed via nesting. Invoking a sub-protocol looks similar to an ordinary function call:
|
||||
Flows can be composed via nesting. Invoking a sub-flow looks similar to an ordinary function call:
|
||||
|
||||
.. container:: codeset
|
||||
|
||||
@ -343,18 +343,18 @@ Protocols can be composed via nesting. Invoking a sub-protocol looks similar to
|
||||
@Suspendable
|
||||
private fun getNotarySignature(stx: SignedTransaction): DigitalSignature.LegallyIdentifiable {
|
||||
progressTracker.currentStep = NOTARY
|
||||
return subProtocol(NotaryProtocol.Client(stx))
|
||||
return subFlow(NotaryFlow.Client(stx))
|
||||
}
|
||||
|
||||
In this code snippet we are using the ``NotaryProtocol.Client`` to request notarisation of the transaction.
|
||||
We simply create the protocol object via its constructor, and then pass it to the ``subProtocol`` method which
|
||||
returns the result of the protocol's execution directly. Behind the scenes all this is doing is wiring up progress
|
||||
In this code snippet we are using the ``NotaryFlow.Client`` to request notarisation of the transaction.
|
||||
We simply create the flow object via its constructor, and then pass it to the ``subFlow`` method which
|
||||
returns the result of the flow's execution directly. Behind the scenes all this is doing is wiring up progress
|
||||
tracking (discussed more below) and then running the objects ``call`` method. Because this little helper method can
|
||||
be on the stack when network IO takes place, we mark it as ``@Suspendable``.
|
||||
|
||||
Going back to the previous code snippet, we use a subprotocol called ``ResolveTransactionsProtocol``. This is
|
||||
Going back to the previous code snippet, we use a sub-flow called ``ResolveTransactionsFlow``. This is
|
||||
responsible for downloading and checking all the dependencies of a transaction, which in Corda are always retrievable
|
||||
from the party that sent you a transaction that uses them. This protocol returns a list of ``LedgerTransaction``
|
||||
from the party that sent you a transaction that uses them. This flow returns a list of ``LedgerTransaction``
|
||||
objects, but we don't need them here so we just ignore the return value.
|
||||
|
||||
.. note:: Transaction dependency resolution assumes that the peer you got the transaction from has all of the
|
||||
@ -392,7 +392,7 @@ There is an overload for the + operator so signatures can be added to a SignedTr
|
||||
two signatures in a simple wrapper message class and send it back. The send won't block waiting for an acknowledgement,
|
||||
but the underlying message queue software will retry delivery if the other side has gone away temporarily.
|
||||
|
||||
You can also see that every protocol instance has a logger (using the SLF4J API) which you can use to log progress
|
||||
You can also see that every flow instance has a logger (using the SLF4J API) which you can use to log progress
|
||||
messages.
|
||||
|
||||
.. warning:: This sample code is **not secure**. Other than not checking for all possible invalid constructions, if the
|
||||
@ -443,7 +443,7 @@ OK, let's do the same for the buyer side:
|
||||
|
||||
// Check the transaction that contains the state which is being resolved.
|
||||
// We only have a hash here, so if we don't know it already, we have to ask for it.
|
||||
subProtocol(ResolveTransactionsProtocol(setOf(it.assetForSale.ref.txhash), otherSide))
|
||||
subFlow(ResolveTransactionsFlow(setOf(it.assetForSale.ref.txhash), otherSide))
|
||||
|
||||
return it
|
||||
}
|
||||
@ -499,21 +499,21 @@ This code is longer but no more complicated. Here are some things to pay attenti
|
||||
2. We create a cash spend in the normal way, by using ``Cash().generateSpend``. See the contracts tutorial if this
|
||||
part isn't clear.
|
||||
3. We access the *service hub* when we need it to access things that are transient and may change or be recreated
|
||||
whilst a protocol is suspended, things like the wallet or the network map.
|
||||
whilst a flow is suspended, things like the wallet or the network map.
|
||||
4. Finally, we send the unfinished, invalid transaction to the seller so they can sign it. They are expected to send
|
||||
back to us a ``SignaturesFromSeller``, which once we verify it, should be the final outcome of the trade.
|
||||
|
||||
As you can see, the protocol logic is straightforward and does not contain any callbacks or network glue code, despite
|
||||
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.
|
||||
|
||||
.. warning:: In the current version of the platform, exceptions thrown during protocol execution are not propagated
|
||||
.. warning:: In the current version of the platform, exceptions thrown during flow execution are not propagated
|
||||
back to the sender. A thorough error handling and exceptions framework will be in a future version of the platform.
|
||||
|
||||
Progress tracking
|
||||
-----------------
|
||||
|
||||
Not shown in the code snippets above is the usage of the ``ProgressTracker`` API. Progress tracking exports information
|
||||
from a protocol about where it's got up to in such a way that observers can render it in a useful manner to humans who
|
||||
from a flow about where it's got up to in such a way that observers can render it in a useful manner to humans who
|
||||
may need to be informed. It may be rendered via an API, in a GUI, onto a terminal window, etc.
|
||||
|
||||
A ``ProgressTracker`` is constructed with a series of ``Step`` objects, where each step is an object representing a
|
||||
@ -544,16 +544,16 @@ observable exposes all the events generated by its children as well. The changes
|
||||
whether the change is one of position (i.e. progress), structure (i.e. new subtasks being added/removed) or some other
|
||||
aspect of rendering (i.e. a step has changed in some way and is requesting a re-render).
|
||||
|
||||
The protocol framework is somewhat integrated with this API. Each ``ProtocolLogic`` may optionally provide a tracker by
|
||||
overriding the ``protocolTracker`` property (``getProtocolTracker`` method in Java). If the
|
||||
``ProtocolLogic.subProtocol`` method is used, then the tracker of the sub-protocol will be made a child of the current
|
||||
step in the parent protocol automatically, if the parent is using tracking in the first place. The framework will also
|
||||
automatically set the current step to ``DONE`` for you, when the protocol is finished.
|
||||
The flow framework is somewhat integrated with this API. Each ``FlowLogic`` may optionally provide a tracker by
|
||||
overriding the ``flowTracker`` property (``getFlowTracker`` method in Java). If the
|
||||
``FlowLogic.subFlow`` method is used, then the tracker of the sub-flow will be made a child of the current
|
||||
step in the parent flow automatically, if the parent is using tracking in the first place. The framework will also
|
||||
automatically set the current step to ``DONE`` for you, when the flow is finished.
|
||||
|
||||
Because a protocol may sometimes wish to configure the children in its progress hierarchy _before_ the sub-protocol
|
||||
is constructed, for sub-protocols that always follow the same outline regardless of their parameters it's conventional
|
||||
Because a flow may sometimes wish to configure the children in its progress hierarchy _before_ the sub-flow
|
||||
is constructed, for sub-flows that always follow the same outline regardless of their parameters it's conventional
|
||||
to define a companion object/static method (for Kotlin/Java respectively) that constructs a tracker, and then allow
|
||||
the sub-protocol to have the tracker it will use be passed in as a parameter. This allows all trackers to be built
|
||||
the sub-flow to have the tracker it will use be passed in as a parameter. This allows all trackers to be built
|
||||
and linked ahead of time.
|
||||
|
||||
In future, the progress tracking framework will become a vital part of how exceptions, errors, and other faults are
|
||||
@ -562,19 +562,19 @@ surfaced to human operators for investigation and resolution.
|
||||
Unit testing
|
||||
------------
|
||||
|
||||
A protocol can be a fairly complex thing that interacts with many services and other parties over the network. That
|
||||
A flow can be a fairly complex thing that interacts with many services and other parties over the network. That
|
||||
means unit testing one requires some infrastructure to provide lightweight mock implementations. The MockNetwork
|
||||
provides this testing infrastructure layer; you can find this class in the node module
|
||||
|
||||
A good example to examine for learning how to unit test protocols is the ``ResolveTransactionsProtocol`` tests. This
|
||||
protocol takes care of downloading and verifying transaction graphs, with all the needed dependencies. We start
|
||||
A good example to examine for learning how to unit test flows is the ``ResolveTransactionsFlow`` tests. This
|
||||
flow takes care of downloading and verifying transaction graphs, with all the needed dependencies. We start
|
||||
with this basic skeleton:
|
||||
|
||||
.. container:: codeset
|
||||
|
||||
.. sourcecode:: kotlin
|
||||
|
||||
class ResolveTransactionsProtocolTest {
|
||||
class ResolveTransactionsFlowTest {
|
||||
lateinit var net: MockNetwork
|
||||
lateinit var a: MockNetwork.MockNode
|
||||
lateinit var b: MockNetwork.MockNode
|
||||
@ -608,8 +608,8 @@ Next, we write a test case:
|
||||
@Test
|
||||
fun resolveFromTwoHashes() {
|
||||
val (stx1, stx2) = makeTransactions()
|
||||
val p = ResolveTransactionsProtocol(setOf(stx2.id), a.info.identity)
|
||||
val future = b.services.startProtocol("resolve", p)
|
||||
val p = ResolveTransactionsFlow(setOf(stx2.id), a.info.identity)
|
||||
val future = b.services.startFlow("resolve", p)
|
||||
net.runNetwork()
|
||||
val results = future.get()
|
||||
assertEquals(listOf(stx1.id, stx2.id), results.map { it.id })
|
||||
@ -621,11 +621,11 @@ We'll take a look at the ``makeTransactions`` function in a moment. For now, it'
|
||||
``SignedTransaction`` objects, the second of which spends the first. Both transactions are known by node A
|
||||
but not node B.
|
||||
|
||||
The test logic is simple enough: we create the protocol, giving it node A's identity as the target to talk to.
|
||||
The test logic is simple enough: we create the flow, giving it node A's identity as the target to talk to.
|
||||
Then we start it on node B and use the ``net.runNetwork()`` method to bounce messages around until things have
|
||||
settled (i.e. there are no more messages waiting to be delivered). All this is done using an in memory message
|
||||
routing implementation that is fast to initialise and use. Finally, we obtain the result of the protocol and do
|
||||
some tests on it. We also check the contents of node B's database to see that the protocol had the intended effect
|
||||
routing implementation that is fast to initialise and use. Finally, we obtain the result of the flow and do
|
||||
some tests on it. We also check the contents of node B's database to see that the flow had the intended effect
|
||||
on the node's persistent state.
|
||||
|
||||
Here's what ``makeTransactions`` looks like:
|
||||
@ -665,30 +665,30 @@ Versioning
|
||||
----------
|
||||
|
||||
Fibers involve persisting object-serialised stack frames to disk. Although we may do some R&D into in-place upgrades
|
||||
in future, for now the upgrade process for protocols is simple: you duplicate the code and rename it so it has a
|
||||
new set of class names. Old versions of the protocol can then drain out of the system whilst new versions are
|
||||
in future, for now the upgrade process for flows is simple: you duplicate the code and rename it so it has a
|
||||
new set of class names. Old versions of the flow can then drain out of the system whilst new versions are
|
||||
initiated. When enough time has passed that no old versions are still waiting for anything to happen, the previous
|
||||
copy of the code can be deleted.
|
||||
|
||||
Whilst kind of ugly, this is a very simple approach that should suffice for now.
|
||||
|
||||
.. warning:: Protocols are not meant to live for months or years, and by implication they are not meant to implement entire deal
|
||||
lifecycles. For instance, implementing the entire life cycle of an interest rate swap as a single protocol - whilst
|
||||
.. warning:: Flows are not meant to live for months or years, and by implication they are not meant to implement entire deal
|
||||
lifecycles. For instance, implementing the entire life cycle of an interest rate swap as a single flow - whilst
|
||||
technically possible - would not be a good idea. The platform provides a job scheduler tool that can invoke
|
||||
protocols for this reason (see ":doc:`event-scheduling`")
|
||||
flows for this reason (see ":doc:`event-scheduling`")
|
||||
|
||||
Future features
|
||||
---------------
|
||||
|
||||
The protocol framework is a key part of the platform and will be extended in major ways in future. Here are some of
|
||||
The flow framework is a key part of the platform and will be extended in major ways in future. Here are some of
|
||||
the features we have planned:
|
||||
|
||||
* Identity based addressing
|
||||
* Exposing progress trackers to local (inside the firewall) clients using message queues and/or WebSockets
|
||||
* Exception propagation and management, with a "protocol hospital" tool to manually provide solutions to unavoidable
|
||||
* Exception propagation and management, with a "flow hospital" tool to manually provide solutions to unavoidable
|
||||
problems (e.g. the other side doesn't know the trade)
|
||||
* Being able to interact with internal apps and tools via HTTP and similar
|
||||
* Being able to interact with people, either via some sort of external ticketing system, or email, or a custom UI.
|
||||
For example to implement human transaction authorisations.
|
||||
* A standard library of protocols that can be easily sub-classed by local developers in order to integrate internal
|
||||
* A standard library of flows that can be easily sub-classed by local developers in order to integrate internal
|
||||
reporting logic, or anything else that might be required as part of a communications lifecycle.
|
@ -16,7 +16,7 @@ Contract
|
||||
Corda
|
||||
A Distributed Ledger for recording and managing financial agreements
|
||||
CorDapp
|
||||
A Corda Distributed Application. A shared ledger application on Corda consisting of components from: State objects (data), Contract Code (allowable operations), Flows (aka Transaction Protocols, the business logic choreography), any necessary APIs, wallet plugins, and UI components.
|
||||
A Corda Distributed Application. A shared ledger application on Corda consisting of components from: State objects (data), Contract Code (allowable operations), Flows (aka Transaction Flows, the business logic choreography), any necessary APIs, wallet plugins, and UI components.
|
||||
Cordformation
|
||||
A gradle plugin that can be configured via your gradle buildscripts to locally deploy a set of Corda nodes
|
||||
Counterparty
|
||||
|
@ -65,7 +65,7 @@ Read on to learn:
|
||||
tutorial-contract-clauses
|
||||
tutorial-test-dsl
|
||||
tutorial-clientrpc-api
|
||||
protocol-state-machines
|
||||
flow-state-machines
|
||||
oracles
|
||||
tutorial-attachments
|
||||
event-scheduling
|
||||
|
@ -43,7 +43,7 @@ Preliminaries
|
||||
ISDA SIMM agreement (if none, then use the demo to enter some simple trades as described below).
|
||||
|
||||
Initial Margin Agreement Process
|
||||
- Agree that one will be performing the margining calculation against a portfolio of trades with another party, and agree the trades in that portfolio. In practice, one node will start the protocol but it does not matter which node does.
|
||||
- Agree that one will be performing the margining calculation against a portfolio of trades with another party, and agree the trades in that portfolio. In practice, one node will start the flow but it does not matter which node does.
|
||||
- Individually (at the node level), identify the data (static, reference etc) one will need in order to be able to calculate the metrics on those trades
|
||||
- Confirm with the other counterparty the dataset from the above set
|
||||
- Calculate any intermediary steps and values needed for the margin calculation (ie sensitivities to risk factors)
|
||||
|
@ -73,13 +73,13 @@ commands of type ``Fix`` as in IRSDemo example. Then we can construct ``Filtered
|
||||
val wtx: WireTransaction = partialTx.toWireTransaction()
|
||||
val ftx = FilteredTransaction.buildMerkleTransaction(wtx, filterFuns)
|
||||
|
||||
In the Oracle example this step takes place in ``RatesFixProtocol``:
|
||||
In the Oracle example this step takes place in ``RatesFixFlow``:
|
||||
|
||||
.. container:: codeset
|
||||
|
||||
.. sourcecode:: kotlin
|
||||
|
||||
val protocol = RatesFixProtocol(partialTx, filterFuns, oracle, fixOf, "0.675".bd, "0.1".bd)
|
||||
val flow = RatesFixFlow(partialTx, filterFuns, oracle, fixOf, "0.675".bd, "0.1".bd)
|
||||
|
||||
``FilteredTransaction`` holds ``filteredLeaves`` (data that we wanted to reveal) and Merkle branch for them.
|
||||
|
||||
|
@ -13,8 +13,8 @@ unit tests and visualisation tools.
|
||||
out for alternative implementations.
|
||||
|
||||
There are multiple ways of interacting with the network. When writing an application you typically won't use the
|
||||
messaging subsystem directly. Instead you will build on top of the :doc:`protocol framework <protocol-state-machines>`,
|
||||
which adds a layer on top of raw messaging to manage multi-step protocols and let you think in terms of identities
|
||||
messaging subsystem directly. Instead you will build on top of the :doc:`flow framework <flow-state-machines>`,
|
||||
which adds a layer on top of raw messaging to manage multi-step flows and let you think in terms of identities
|
||||
rather than specific network endpoints.
|
||||
|
||||
Messaging types
|
||||
|
@ -5,7 +5,7 @@ The repository contains a small number of demo programs that run two-node networ
|
||||
so far. We have:
|
||||
|
||||
1. The trader demo, which shows a delivery-vs-payment atomic swap of commercial paper for cash. You can learn more about
|
||||
how this works in :doc:`protocol-state-machines`.
|
||||
how this works in :doc:`flow-state-machines`.
|
||||
2. The IRS demo, which shows two nodes establishing an interest rate swap between them and performing fixings with a
|
||||
rates oracle, all driven via the HTTP API.
|
||||
3. The IRS demo web interface - a web interface to the IRS demo.
|
||||
|
@ -4,22 +4,22 @@ Secure coding guidelines
|
||||
The platform does what it can to be secure by default and safe by design. Unfortunately the platform cannot
|
||||
prevent every kind of security mistake. This document describes what to think about when writing applications
|
||||
to block various kinds of attack. Whilst it may be tempting to just assume no reasonable counterparty would
|
||||
attempt to subvert your trades using protocol level attacks, relying on trust for software security makes it
|
||||
attempt to subvert your trades using flow level attacks, relying on trust for software security makes it
|
||||
harder to scale up your operations later when you might want to add counterparties quickly and without
|
||||
extensive vetting.
|
||||
|
||||
Protocols
|
||||
---------
|
||||
Flows
|
||||
-----
|
||||
|
||||
:doc:`protocol-state-machines` are how your app communicates with other parties on the network. Therefore they
|
||||
:doc:`flow-state-machines` are how your app communicates with other parties on the network. Therefore they
|
||||
are the typical entry point for malicious data into your app and must be treated with care.
|
||||
|
||||
The ``receive`` methods return data wrapped in the ``UntrustworthyData<T>`` marker type. This type doesn't add
|
||||
any functionality, it's only there to remind you to properly validate everything that you get from the network.
|
||||
Remember that the other side may *not* be running the code you provide to take part in the protocol: they are
|
||||
Remember that the other side may *not* be running the code you provide to take part in the flow: they are
|
||||
allowed to do anything! Things to watch out for:
|
||||
|
||||
* A transaction that doesn't match a partial transaction built or proposed earlier in the protocol, for instance,
|
||||
* A transaction that doesn't match a partial transaction built or proposed earlier in the flow, for instance,
|
||||
if you propose to trade a cash state worth $100 for an asset, and the transaction to sign comes back from the
|
||||
other side, you must check that it points to the state you actually requested. Otherwise the attacker could
|
||||
get you to sign a transaction that spends a much larger state to you, if they know the ID of one!
|
||||
@ -30,7 +30,7 @@ allowed to do anything! Things to watch out for:
|
||||
could re-run the builder logic and do a comparison of the resulting states to ensure that it's what you expected.
|
||||
For instance if the data needed to construct the next state is available to both parties, the function to
|
||||
calculate the transaction you want to mutually agree could be shared between both classes implementing both
|
||||
sides of the protocol.
|
||||
sides of the flow.
|
||||
|
||||
The theme should be clear: signing is a very sensitive operation, so you need to be sure you know what it is you
|
||||
are about to sign, and that nothing has changed in the small print!
|
||||
|
@ -64,7 +64,7 @@ of state:
|
||||
|
||||
``DealState``
|
||||
A LinearState representing an agreement between two or more parties. Intended to simplify implementing generic
|
||||
protocols that manipulate many agreement types.
|
||||
flows that manipulate many agreement types.
|
||||
|
||||
``FixableDealState``
|
||||
A deal state, with further functions exposed to support fixing of interest rates.
|
||||
|
@ -17,10 +17,10 @@ which returns a unique ID that can be added using ``TransactionBuilder.addAttach
|
||||
uploaded and downloaded via HTTP, to enable integration with external systems. For instructions on HTTP upload/download
|
||||
please see ":doc:`node-administration`".
|
||||
|
||||
Normally attachments on transactions are fetched automatically via the ``ResolveTransactionsProtocol`` when verifying
|
||||
Normally attachments on transactions are fetched automatically via the ``ResolveTransactionsFlow`` when verifying
|
||||
received transactions. Attachments are needed in order to validate a transaction (they include, for example, the
|
||||
contract code), so must be fetched before the validation process can run. ``ResolveTransactionsProtocol`` calls
|
||||
``FetchTransactionsProtocol`` to perform the actual retrieval.
|
||||
contract code), so must be fetched before the validation process can run. ``ResolveTransactionsFlow`` calls
|
||||
``FetchTransactionsFlow`` to perform the actual retrieval.
|
||||
|
||||
It is encouraged that where possible attachments are reusable data, so that nodes can meaningfully cache them.
|
||||
|
||||
@ -28,13 +28,13 @@ Attachments demo
|
||||
----------------
|
||||
|
||||
There is a worked example of attachments, which relays a simple document from one node to another. The "two party
|
||||
trade protocol" also includes an attachment, however it is a significantly more complex demo, and less well suited
|
||||
trade flow" also includes an attachment, however it is a significantly more complex demo, and less well suited
|
||||
for a tutorial.
|
||||
|
||||
The demo code is in the file "src/main/kotlin/net.corda.demos/attachment/AttachmentDemo.kt", with the core logic
|
||||
contained within the two functions ``runRecipient()`` and ``runSender()``. We'll look at the recipient function first;
|
||||
this subscribes to notifications of new validated transactions, and if it receives a transaction containing attachments,
|
||||
loads the first attachment from storage, and checks it matches the expected attachment ID. ``ResolveTransactionsProtocol``
|
||||
loads the first attachment from storage, and checks it matches the expected attachment ID. ``ResolveTransactionsFlow``
|
||||
has already fetched all attachments from the remote node, and as such the attachments are available from the node's
|
||||
storage service. Once the attachment is verified, the node shuts itself down.
|
||||
|
||||
@ -43,10 +43,10 @@ storage service. Once the attachment is verified, the node shuts itself down.
|
||||
private fun runRecipient(node: Node) {
|
||||
val serviceHub = node.services
|
||||
|
||||
// Normally we would receive the transaction from a more specific protocol, but in this case we let [FinalityProtocol]
|
||||
// Normally we would receive the transaction from a more specific flow, but in this case we let [FinalityFlow]
|
||||
// handle receiving it for us.
|
||||
serviceHub.storageService.validatedTransactions.updates.subscribe { event ->
|
||||
// When the transaction is received, it's passed through [ResolveTransactionsProtocol], which first fetches any
|
||||
// When the transaction is received, it's passed through [ResolveTransactionsFlow], which first fetches any
|
||||
// attachments for us, then verifies the transaction. As such, by the time it hits the validated transaction store,
|
||||
// we have a copy of the attachment.
|
||||
val tx = event.tx
|
||||
@ -62,7 +62,7 @@ storage service. Once the attachment is verified, the node shuts itself down.
|
||||
}
|
||||
}
|
||||
|
||||
The sender correspondingly builds a transaction with the attachment, then calls ``FinalityProtocol`` to complete the
|
||||
The sender correspondingly builds a transaction with the attachment, then calls ``FinalityFlow`` to complete the
|
||||
transaction and send it to the recipient node:
|
||||
|
||||
|
||||
@ -88,7 +88,7 @@ transaction and send it to the recipient node:
|
||||
|
||||
// Send the transaction to the other recipient
|
||||
val tx = ptx.toSignedTransaction()
|
||||
serviceHub.startProtocol(LOG_SENDER, FinalityProtocol(tx, emptySet(), setOf(otherSide))).success {
|
||||
serviceHub.startFlow(LOG_SENDER, FinalityFlow(tx, emptySet(), setOf(otherSide))).success {
|
||||
thread {
|
||||
Thread.sleep(1000L) // Give the other side time to request the attachment
|
||||
node.stop()
|
||||
|
@ -11,7 +11,7 @@ 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.
|
||||
|
||||
Note how we configure the node to create a user that has permission to start the CashProtocol.
|
||||
Note how we configure the node to create a user that has permission to start the CashFlow.
|
||||
|
||||
.. literalinclude:: example-code/src/main/kotlin/net/corda/docs/ClientRpcTutorial.kt
|
||||
:language: kotlin
|
||||
@ -62,7 +62,7 @@ We utilise several RPC functions here to query things like the notaries in the n
|
||||
|
||||
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 ``startProtocolDynamic`` which may start an arbitrary protocol, given sufficient permissions to do so. We won't use this function directly, but rather a type-safe wrapper around it ``startProtocol`` 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!:
|
||||
|
||||
@ -81,7 +81,7 @@ Now let's try to visualise the transaction graph. We will use a graph drawing li
|
||||
If we run the client with ``Visualise`` we should see a simple random graph being drawn as new transactions are being created.
|
||||
|
||||
Registering classes from your Cordapp with RPC Kryo
|
||||
--------------------------------------------------
|
||||
---------------------------------------------------
|
||||
|
||||
As described in :doc:`clientrpc`, you currently have to register any additional classes you add that are needed in RPC
|
||||
requests or responses with the `Kryo` instance RPC uses. Here's an example of how you do this for an example class.
|
||||
|
@ -19,7 +19,7 @@ Kotlin syntax works.
|
||||
Where to put your code
|
||||
----------------------
|
||||
|
||||
A CorDapp is a collection of contracts, state definitions, protocols and other ways to extend the server. To create
|
||||
A CorDapp is a collection of contracts, state definitions, flows and other ways to extend the server. To create
|
||||
one you would just create a Java-style project as normal, with your choice of build system (Maven, Gradle, etc).
|
||||
Then add a dependency on ``net.corda.core:0.X`` where X is the milestone number you are depending on. The core
|
||||
module defines the base classes used in this tutorial.
|
||||
@ -694,7 +694,7 @@ a multi-party transaction, you're on your own. In Corda data is transmitted only
|
||||
multi-party transactions are a way of life, so we provide lots of support for managing them.
|
||||
|
||||
You can learn how transactions are moved between peers and taken through the build-sign-notarise-broadcast
|
||||
process in a separate tutorial, :doc:`protocol-state-machines`.
|
||||
process in a separate tutorial, :doc:`flow-state-machines`.
|
||||
|
||||
Non-asset-oriented smart contracts
|
||||
----------------------------------
|
||||
|
@ -21,7 +21,7 @@ A scenario contains:
|
||||
|
||||
* A set of participating nodes and their roles.
|
||||
* Some business process you wish to automate (typically simplified from the real thing).
|
||||
* The smart contracts and protocols that will automate that process.
|
||||
* The smart contracts and flows that will automate that process.
|
||||
|
||||
It may also specify a REST/JSON API, but this is optional.
|
||||
|
||||
@ -40,9 +40,9 @@ The process of implementing a scenario looks like this:
|
||||
in Java and Kotlin. The file should define your state classes and your contract class, which will define the
|
||||
allowable state transitions. You can learn how these are constructed by reading the ":doc:`tutorial-contract`" tutorial.
|
||||
3. It isn't enough to just define static data and logic that controls what's allowed. You must also orchestrate the
|
||||
business process. This is the job of the protocol framework. You can learn how to author these by reading
|
||||
":doc:`protocol-state-machines`".
|
||||
4. Once you have created your states, transactions and protocols, you need a way to demonstrate them (outside of the
|
||||
business process. This is the job of the flow framework. You can learn how to author these by reading
|
||||
":doc:`flow-state-machines`".
|
||||
4. Once you have created your states, transactions and flows, you need a way to demonstrate them (outside of the
|
||||
unit tests, of course). This topic is covered below.
|
||||
|
||||
The trader demo
|
||||
@ -63,7 +63,7 @@ demo drivers and then customise them, as much of the code would end up being sha
|
||||
Things you will want to adjust:
|
||||
|
||||
1. The name of the grouping directory each node role will create its private directory under.
|
||||
2. The demo protocols that just wrap the real business process in some kind of fake trading logic.
|
||||
2. The demo flows that just wrap the real business process in some kind of fake trading logic.
|
||||
|
||||
The IRS driver program registers REST APIs, but as this is seriously in flux right now and the APIs will change a lot,
|
||||
we do not recommend you try this as part of your initial explorations unless you are feeling adventurous.
|
Loading…
x
Reference in New Issue
Block a user