mirror of
https://github.com/corda/corda.git
synced 2025-02-01 16:58:27 +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
|
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
|
.. sourcecode:: kotlin
|
||||||
|
|
||||||
@Suspendable
|
@Suspendable
|
||||||
fun changeNotary(originalState: StateAndRef<ContractState>,
|
fun changeNotary(originalState: StateAndRef<ContractState>,
|
||||||
newNotary: Party): StateAndRef<ContractState> {
|
newNotary: Party): StateAndRef<ContractState> {
|
||||||
val protocol = NotaryChangeProtocol.Instigator(originalState, newNotary)
|
val flow = NotaryChangeFlow.Instigator(originalState, newNotary)
|
||||||
return subProtocol(protocol)
|
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
|
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
|
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
|
``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.
|
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:
|
actual work of notarising and broadcasting the transaction. For example:
|
||||||
|
|
||||||
.. sourcecode:: kotlin
|
.. 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
|
// We conclusively cannot have all the signatures, as the notary has not signed yet
|
||||||
val tx = ptx.toSignedTransaction(checkSufficientSignatures = false)
|
val tx = ptx.toSignedTransaction(checkSufficientSignatures = false)
|
||||||
// The empty set would be the trigger events, which are not used here
|
// The empty set would be the trigger events, which are not used here
|
||||||
val protocol = FinalityProtocol(tx, emptySet(), participants)
|
val flow = FinalityFlow(tx, emptySet(), participants)
|
||||||
return serviceHub.startProtocol("protocol.finalisation", protocol)
|
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
|
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
|
.. sourcecode:: kotlin
|
||||||
|
|
||||||
fun getNotarySignature(wtx: WireTransaction): DigitalSignature.LegallyIdentifiable {
|
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
|
.. 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)
|
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
|
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:
|
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).
|
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.
|
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.
|
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
|
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
|
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.
|
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
|
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",
|
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
|
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.
|
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"
|
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.
|
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
|
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>`,
|
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 protocol 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
|
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.
|
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
|
* Have your ``ContractState`` implementation also implement ``SchedulableState``. This requires a method named
|
||||||
``nextScheduledActivity`` to be implemented which returns an optional ``ScheduledActivity`` instance.
|
``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
|
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.
|
||||||
* 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
|
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.
|
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
|
.. 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``.
|
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.
|
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
|
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
|
.. sourcecode:: kotlin
|
||||||
|
|
||||||
override fun nextScheduledActivity(thisStateRef: StateRef,
|
override fun nextScheduledActivity(thisStateRef: StateRef,
|
||||||
protocolLogicRefFactory: ProtocolLogicRefFactory): ScheduledActivity? {
|
flowLogicRefFactory: FlowLogicRefFactory): ScheduledActivity? {
|
||||||
val nextFixingOf = nextFixingOf() ?: return null
|
val nextFixingOf = nextFixingOf() ?: return null
|
||||||
|
|
||||||
val (instant, duration) = suggestInterestRateAnnouncementTimeWindow(index = nextFixingOf.name,
|
val (instant, duration) = suggestInterestRateAnnouncementTimeWindow(index = nextFixingOf.name,
|
||||||
source = floatingLeg.indexSource,
|
source = floatingLeg.indexSource,
|
||||||
date = nextFixingOf.forDay)
|
date = nextFixingOf.forDay)
|
||||||
return ScheduledActivity(protocolLogicRefFactory.create(TwoPartyDealProtocol.FixingRoleDecider::class.java,
|
return ScheduledActivity(flowLogicRefFactory.create(TwoPartyDealFlow.FixingRoleDecider::class.java,
|
||||||
thisStateRef, duration), instant)
|
thisStateRef, duration), instant)
|
||||||
}
|
}
|
||||||
|
|
||||||
The first thing this does is establish if there are any remaining fixings. If there are none, then it returns ``null``
|
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
|
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
|
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
|
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.
|
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
|
instantiate. The reference can be checked against a per-node whitelist of approved and allowable types as
|
||||||
part of our overall security sandboxing.
|
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
|
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
|
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
|
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 protocol. Currently
|
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.
|
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/jquery.js"></script>
|
||||||
<script type="text/javascript" src="_static/codesets.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
|
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 protocol
|
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.
|
which is included in the source.
|
||||||
|
|
||||||
Introduction
|
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
|
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
|
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
|
*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.
|
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
|
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:
|
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
|
* 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.
|
desire to avoid using up a thread for every flow instantiation.
|
||||||
* Surviving node shutdowns/restarts that may occur in the middle of the protocol without complicating things. This
|
* Surviving node shutdowns/restarts that may occur in the middle of the flow without complicating things. This
|
||||||
implies that the state of the protocol must be persisted to disk.
|
implies that the state of the flow must be persisted to disk.
|
||||||
* Error handling.
|
* Error handling.
|
||||||
* Message routing.
|
* Message routing.
|
||||||
* Serialisation.
|
* Serialisation.
|
||||||
* Catching type errors, in which the developer gets temporarily confused and expects to receive/send one type of message
|
* 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.
|
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
|
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
|
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
|
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.
|
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.
|
construction of them that automatically handles many of the concerns outlined above.
|
||||||
|
|
||||||
Theory
|
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
|
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
|
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.
|
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
|
*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
|
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
|
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.
|
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
|
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.
|
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.
|
it lacks a signature from S authorising movement of the asset.
|
||||||
3. S signs it and hands the now finalised ``SignedTransaction`` back to B.
|
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.
|
represents an atomic asset swap.
|
||||||
|
|
||||||
Note that it's the *seller* who initiates contact with the buyer, not vice-versa as you might imagine.
|
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
|
We start by defining a wrapper that namespaces the flow 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
|
of the flow, and two classes that will contain the flow definition. We also pick what data will be used by
|
||||||
each side.
|
each side.
|
||||||
|
|
||||||
.. note:: The code samples in this tutorial are only available in Kotlin, but you can use any JVM language to
|
.. 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
|
.. sourcecode:: kotlin
|
||||||
|
|
||||||
object TwoPartyTradeProtocol {
|
object TwoPartyTradeFlow {
|
||||||
|
|
||||||
class UnacceptablePriceException(val givenPrice: Amount<Currency>) : Exception("Unacceptable price: $givenPrice")
|
class UnacceptablePriceException(val givenPrice: Amount<Currency>) : Exception("Unacceptable price: $givenPrice")
|
||||||
class AssetMismatchException(val expectedTypeName: String, val typeName: String) : Exception() {
|
class AssetMismatchException(val expectedTypeName: String, val typeName: String) : Exception() {
|
||||||
override fun toString() = "The submitted asset didn't match the expected type: $expectedTypeName vs $typeName"
|
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(
|
data class SellerTradeInfo(
|
||||||
val assetForSale: StateAndRef<OwnableState>,
|
val assetForSale: StateAndRef<OwnableState>,
|
||||||
val price: Amount<Currency>,
|
val price: Amount<Currency>,
|
||||||
@ -131,7 +131,7 @@ each side.
|
|||||||
val assetToSell: StateAndRef<OwnableState>,
|
val assetToSell: StateAndRef<OwnableState>,
|
||||||
val price: Amount<Currency>,
|
val price: Amount<Currency>,
|
||||||
val myKeyPair: KeyPair,
|
val myKeyPair: KeyPair,
|
||||||
override val progressTracker: ProgressTracker = Seller.tracker()) : ProtocolLogic<SignedTransaction>() {
|
override val progressTracker: ProgressTracker = Seller.tracker()) : FlowLogic<SignedTransaction>() {
|
||||||
@Suspendable
|
@Suspendable
|
||||||
override fun call(): SignedTransaction {
|
override fun call(): SignedTransaction {
|
||||||
TODO()
|
TODO()
|
||||||
@ -141,7 +141,7 @@ each side.
|
|||||||
open class Buyer(val otherSide: Party,
|
open class Buyer(val otherSide: Party,
|
||||||
val notary: Party,
|
val notary: Party,
|
||||||
val acceptablePrice: Amount<Currency>,
|
val acceptablePrice: Amount<Currency>,
|
||||||
val typeToBuy: Class<out OwnableState>) : ProtocolLogic<SignedTransaction>() {
|
val typeToBuy: Class<out OwnableState>) : FlowLogic<SignedTransaction>() {
|
||||||
@Suspendable
|
@Suspendable
|
||||||
override fun call(): SignedTransaction {
|
override fun call(): SignedTransaction {
|
||||||
TODO()
|
TODO()
|
||||||
@ -149,8 +149,8 @@ each side.
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
This code defines several classes nested inside the main ``TwoPartyTradeProtocol`` singleton. Some of the classes are
|
This code defines several classes nested inside the main ``TwoPartyTradeFlow`` singleton. Some of the classes are
|
||||||
simply protocol messages or exceptions. The other two represent the buyer and seller side of the protocol.
|
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:
|
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
|
- ``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.
|
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
|
- ``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
|
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 protocol in some way. Just
|
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 protocol for
|
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.
|
us. More on that in a moment.
|
||||||
|
|
||||||
Suspendable functions
|
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?
|
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
|
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
|
``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
|
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
|
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.
|
.. 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
|
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
|
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.
|
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
|
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.
|
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
|
You request a flow to be invoked by using the ``ServiceHub.invokeFlowAsync`` method. This takes a
|
||||||
Java reflection ``Class`` object that describes the protocol class to use (in this case, either ``Buyer`` or ``Seller``).
|
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 protocol invocations to
|
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
|
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.
|
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
|
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 protocol via ``ServiceHubInternal.registerProtocolInitiator``.
|
has to be registered by its node to respond to the initiating flow via ``ServiceHubInternal.registerFlowInitiator``.
|
||||||
In our example it doesn't matter which protocol is the initiator and which is the initiated. For example, if we are to
|
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:
|
take the seller as the initiator then we would register the buyer as such:
|
||||||
|
|
||||||
.. container:: codeset
|
.. container:: codeset
|
||||||
@ -220,20 +220,20 @@ take the seller as the initiator then we would register the buyer as such:
|
|||||||
|
|
||||||
val services: ServiceHubInternal = TODO()
|
val services: ServiceHubInternal = TODO()
|
||||||
|
|
||||||
services.registerProtocolInitiator(Seller::class) { otherParty ->
|
services.registerFlowInitiator(Seller::class) { otherParty ->
|
||||||
val notary = services.networkMapCache.notaryNodes[0]
|
val notary = services.networkMapCache.notaryNodes[0]
|
||||||
val acceptablePrice = TODO()
|
val acceptablePrice = TODO()
|
||||||
val typeToBuy = TODO()
|
val typeToBuy = TODO()
|
||||||
Buyer(otherParty, notary, acceptablePrice, typeToBuy)
|
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``).
|
is a seller (``Seller::class``).
|
||||||
|
|
||||||
Implementing the seller
|
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
|
.. 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
|
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
|
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.
|
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.
|
Let's fill out the ``receiveAndCheckProposedTransaction()`` method.
|
||||||
|
|
||||||
@ -265,7 +265,7 @@ Let's fill out the ``receiveAndCheckProposedTransaction()`` method.
|
|||||||
|
|
||||||
@Suspendable
|
@Suspendable
|
||||||
private fun receiveAndCheckProposedTransaction(): SignedTransaction {
|
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 hello = SellerTradeInfo(assetToSell, price, myKeyPair.public)
|
||||||
|
|
||||||
val maybeSTX = sendAndReceive<SignedTransaction>(otherSide, hello)
|
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,
|
// Download and check all the things that this transaction depends on and verify it is contract-valid,
|
||||||
// even though it is missing signatures.
|
// 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)
|
if (wtx.outputs.map { it.data }.sumCashBy(myKeyPair.public).withoutIssuer() != price)
|
||||||
throw IllegalArgumentException("Transaction is not sending us the right amount of cash")
|
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:
|
This function takes a few arguments:
|
||||||
|
|
||||||
- The party on the other side.
|
- The party on the other side.
|
||||||
@ -300,8 +300,8 @@ This function takes a few arguments:
|
|||||||
back something else an exception is thrown.
|
back something else an exception is thrown.
|
||||||
|
|
||||||
Once ``sendAndReceive`` is called, the call method will be suspended into a continuation and saved to persistent
|
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
|
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 protocol that
|
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!
|
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.
|
.. 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
|
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,
|
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
|
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
|
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
|
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.
|
2. We resolve the transaction, which we will cover below.
|
||||||
3. We verify that the transaction is paying us the demanded price.
|
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
|
.. container:: codeset
|
||||||
|
|
||||||
@ -343,18 +343,18 @@ Protocols can be composed via nesting. Invoking a sub-protocol looks similar to
|
|||||||
@Suspendable
|
@Suspendable
|
||||||
private fun getNotarySignature(stx: SignedTransaction): DigitalSignature.LegallyIdentifiable {
|
private fun getNotarySignature(stx: SignedTransaction): DigitalSignature.LegallyIdentifiable {
|
||||||
progressTracker.currentStep = NOTARY
|
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.
|
In this code snippet we are using the ``NotaryFlow.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
|
We simply create the flow object via its constructor, and then pass it to the ``subFlow`` method which
|
||||||
returns the result of the protocol's execution directly. Behind the scenes all this is doing is wiring up progress
|
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
|
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``.
|
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
|
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.
|
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
|
.. 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,
|
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.
|
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.
|
messages.
|
||||||
|
|
||||||
.. warning:: This sample code is **not secure**. Other than not checking for all possible invalid constructions, if the
|
.. 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.
|
// 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.
|
// 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
|
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
|
2. We create a cash spend in the normal way, by using ``Cash().generateSpend``. See the contracts tutorial if this
|
||||||
part isn't clear.
|
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
|
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
|
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.
|
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.
|
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.
|
back to the sender. A thorough error handling and exceptions framework will be in a future version of the platform.
|
||||||
|
|
||||||
Progress tracking
|
Progress tracking
|
||||||
-----------------
|
-----------------
|
||||||
|
|
||||||
Not shown in the code snippets above is the usage of the ``ProgressTracker`` API. Progress tracking exports information
|
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.
|
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
|
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
|
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).
|
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
|
The flow framework is somewhat integrated with this API. Each ``FlowLogic`` may optionally provide a tracker by
|
||||||
overriding the ``protocolTracker`` property (``getProtocolTracker`` method in Java). If the
|
overriding the ``flowTracker`` property (``getFlowTracker`` 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
|
``FlowLogic.subFlow`` method is used, then the tracker of the sub-flow 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
|
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 protocol is finished.
|
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
|
Because a flow may sometimes wish to configure the children in its progress hierarchy _before_ the sub-flow
|
||||||
is constructed, for sub-protocols that always follow the same outline regardless of their parameters it's conventional
|
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
|
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.
|
and linked ahead of time.
|
||||||
|
|
||||||
In future, the progress tracking framework will become a vital part of how exceptions, errors, and other faults are
|
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
|
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
|
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
|
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
|
A good example to examine for learning how to unit test flows is the ``ResolveTransactionsFlow`` tests. This
|
||||||
protocol takes care of downloading and verifying transaction graphs, with all the needed dependencies. We start
|
flow takes care of downloading and verifying transaction graphs, with all the needed dependencies. We start
|
||||||
with this basic skeleton:
|
with this basic skeleton:
|
||||||
|
|
||||||
.. container:: codeset
|
.. container:: codeset
|
||||||
|
|
||||||
.. sourcecode:: kotlin
|
.. sourcecode:: kotlin
|
||||||
|
|
||||||
class ResolveTransactionsProtocolTest {
|
class ResolveTransactionsFlowTest {
|
||||||
lateinit var net: MockNetwork
|
lateinit var net: MockNetwork
|
||||||
lateinit var a: MockNetwork.MockNode
|
lateinit var a: MockNetwork.MockNode
|
||||||
lateinit var b: MockNetwork.MockNode
|
lateinit var b: MockNetwork.MockNode
|
||||||
@ -608,8 +608,8 @@ Next, we write a test case:
|
|||||||
@Test
|
@Test
|
||||||
fun resolveFromTwoHashes() {
|
fun resolveFromTwoHashes() {
|
||||||
val (stx1, stx2) = makeTransactions()
|
val (stx1, stx2) = makeTransactions()
|
||||||
val p = ResolveTransactionsProtocol(setOf(stx2.id), a.info.identity)
|
val p = ResolveTransactionsFlow(setOf(stx2.id), a.info.identity)
|
||||||
val future = b.services.startProtocol("resolve", p)
|
val future = b.services.startFlow("resolve", p)
|
||||||
net.runNetwork()
|
net.runNetwork()
|
||||||
val results = future.get()
|
val results = future.get()
|
||||||
assertEquals(listOf(stx1.id, stx2.id), results.map { it.id })
|
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
|
``SignedTransaction`` objects, the second of which spends the first. Both transactions are known by node A
|
||||||
but not node B.
|
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
|
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
|
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
|
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 protocol had the intended effect
|
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.
|
on the node's persistent state.
|
||||||
|
|
||||||
Here's what ``makeTransactions`` looks like:
|
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
|
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
|
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 protocol can then drain out of the system whilst new versions are
|
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
|
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.
|
copy of the code can be deleted.
|
||||||
|
|
||||||
Whilst kind of ugly, this is a very simple approach that should suffice for now.
|
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
|
.. 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 protocol - whilst
|
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
|
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
|
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:
|
the features we have planned:
|
||||||
|
|
||||||
* Identity based addressing
|
* Identity based addressing
|
||||||
* Exposing progress trackers to local (inside the firewall) clients using message queues and/or WebSockets
|
* 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)
|
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 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.
|
* 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.
|
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.
|
reporting logic, or anything else that might be required as part of a communications lifecycle.
|
@ -16,7 +16,7 @@ Contract
|
|||||||
Corda
|
Corda
|
||||||
A Distributed Ledger for recording and managing financial agreements
|
A Distributed Ledger for recording and managing financial agreements
|
||||||
CorDapp
|
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
|
Cordformation
|
||||||
A gradle plugin that can be configured via your gradle buildscripts to locally deploy a set of Corda nodes
|
A gradle plugin that can be configured via your gradle buildscripts to locally deploy a set of Corda nodes
|
||||||
Counterparty
|
Counterparty
|
||||||
|
@ -65,7 +65,7 @@ Read on to learn:
|
|||||||
tutorial-contract-clauses
|
tutorial-contract-clauses
|
||||||
tutorial-test-dsl
|
tutorial-test-dsl
|
||||||
tutorial-clientrpc-api
|
tutorial-clientrpc-api
|
||||||
protocol-state-machines
|
flow-state-machines
|
||||||
oracles
|
oracles
|
||||||
tutorial-attachments
|
tutorial-attachments
|
||||||
event-scheduling
|
event-scheduling
|
||||||
|
@ -43,7 +43,7 @@ Preliminaries
|
|||||||
ISDA SIMM agreement (if none, then use the demo to enter some simple trades as described below).
|
ISDA SIMM agreement (if none, then use the demo to enter some simple trades as described below).
|
||||||
|
|
||||||
Initial Margin Agreement Process
|
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
|
- 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
|
- 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)
|
- 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 wtx: WireTransaction = partialTx.toWireTransaction()
|
||||||
val ftx = FilteredTransaction.buildMerkleTransaction(wtx, filterFuns)
|
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
|
.. container:: codeset
|
||||||
|
|
||||||
.. sourcecode:: kotlin
|
.. 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.
|
``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.
|
out for alternative implementations.
|
||||||
|
|
||||||
There are multiple ways of interacting with the network. When writing an application you typically won't use the
|
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>`,
|
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 protocols and let you think in terms of identities
|
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.
|
rather than specific network endpoints.
|
||||||
|
|
||||||
Messaging types
|
Messaging types
|
||||||
|
@ -5,7 +5,7 @@ The repository contains a small number of demo programs that run two-node networ
|
|||||||
so far. We have:
|
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
|
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
|
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.
|
rates oracle, all driven via the HTTP API.
|
||||||
3. The IRS demo web interface - a web interface to the IRS demo.
|
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
|
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
|
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
|
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
|
harder to scale up your operations later when you might want to add counterparties quickly and without
|
||||||
extensive vetting.
|
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.
|
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
|
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.
|
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:
|
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
|
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
|
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!
|
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.
|
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
|
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
|
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
|
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!
|
are about to sign, and that nothing has changed in the small print!
|
||||||
|
@ -64,7 +64,7 @@ of state:
|
|||||||
|
|
||||||
``DealState``
|
``DealState``
|
||||||
A LinearState representing an agreement between two or more parties. Intended to simplify implementing generic
|
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``
|
``FixableDealState``
|
||||||
A deal state, with further functions exposed to support fixing of interest rates.
|
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
|
uploaded and downloaded via HTTP, to enable integration with external systems. For instructions on HTTP upload/download
|
||||||
please see ":doc:`node-administration`".
|
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
|
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
|
contract code), so must be fetched before the validation process can run. ``ResolveTransactionsFlow`` calls
|
||||||
``FetchTransactionsProtocol`` to perform the actual retrieval.
|
``FetchTransactionsFlow`` to perform the actual retrieval.
|
||||||
|
|
||||||
It is encouraged that where possible attachments are reusable data, so that nodes can meaningfully cache them.
|
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
|
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.
|
for a tutorial.
|
||||||
|
|
||||||
The demo code is in the file "src/main/kotlin/net.corda.demos/attachment/AttachmentDemo.kt", with the core logic
|
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;
|
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,
|
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
|
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.
|
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) {
|
private fun runRecipient(node: Node) {
|
||||||
val serviceHub = node.services
|
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.
|
// handle receiving it for us.
|
||||||
serviceHub.storageService.validatedTransactions.updates.subscribe { event ->
|
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,
|
// 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.
|
// we have a copy of the attachment.
|
||||||
val tx = event.tx
|
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:
|
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
|
// Send the transaction to the other recipient
|
||||||
val tx = ptx.toSignedTransaction()
|
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 {
|
||||||
Thread.sleep(1000L) // Give the other side time to request the attachment
|
Thread.sleep(1000L) // Give the other side time to request the attachment
|
||||||
node.stop()
|
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.
|
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
|
.. literalinclude:: example-code/src/main/kotlin/net/corda/docs/ClientRpcTutorial.kt
|
||||||
:language: kotlin
|
: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.
|
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!:
|
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.
|
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
|
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
|
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.
|
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
|
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).
|
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
|
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.
|
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.
|
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
|
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
|
Non-asset-oriented smart contracts
|
||||||
----------------------------------
|
----------------------------------
|
||||||
|
@ -21,7 +21,7 @@ A scenario contains:
|
|||||||
|
|
||||||
* A set of participating nodes and their roles.
|
* A set of participating nodes and their roles.
|
||||||
* Some business process you wish to automate (typically simplified from the real thing).
|
* 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.
|
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
|
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.
|
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
|
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
|
business process. This is the job of the flow framework. You can learn how to author these by reading
|
||||||
":doc:`protocol-state-machines`".
|
":doc:`flow-state-machines`".
|
||||||
4. Once you have created your states, transactions and protocols, you need a way to demonstrate them (outside of the
|
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.
|
unit tests, of course). This topic is covered below.
|
||||||
|
|
||||||
The trader demo
|
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:
|
Things you will want to adjust:
|
||||||
|
|
||||||
1. The name of the grouping directory each node role will create its private directory under.
|
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,
|
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.
|
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