Merge pull request #639 from corda/shams-initiating-flow-annotation

Introducing InitiatingFlow annotation which has to be annotated by in…
This commit is contained in:
Shams Asari 2017-05-09 12:24:33 +01:00 committed by GitHub
commit 60b1f9b0b2
42 changed files with 429 additions and 264 deletions

View File

@ -27,12 +27,20 @@ import rx.Observable
*
* If you'd like to use another FlowLogic class as a component of your own, construct it on the fly and then pass
* it to the [subFlow] method. It will return the result of that flow when it completes.
*
* If your flow (whether it's a top-level flow or a subflow) is supposed to initiate a session with the counterparty
* and request they start their counterpart flow, then make sure it's annotated with [InitiatingFlow]. This annotation
* also has a version property to allow you to version your flow and enables a node to restrict support for the flow to
* that particular version.
*/
abstract class FlowLogic<out T> {
/** This is where you should log things to. */
val logger: Logger get() = stateMachine.logger
/** Returns a wrapped [UUID] object that identifies this state machine run (i.e. subflows have the same identifier as their parents). */
/**
* Returns a wrapped [java.util.UUID] object that identifies this state machine run (i.e. subflows have the same
* identifier as their parents).
*/
val runId: StateMachineRunId get() = stateMachine.id
/**
@ -60,7 +68,9 @@ abstract class FlowLogic<out T> {
*
* @returns an [UntrustworthyData] wrapper around the received object.
*/
inline fun <reified R : Any> sendAndReceive(otherParty: Party, payload: Any) = sendAndReceive(R::class.java, otherParty, payload)
inline fun <reified R : Any> sendAndReceive(otherParty: Party, payload: Any): UntrustworthyData<R> {
return sendAndReceive(R::class.java, otherParty, payload)
}
/**
* Serializes and queues the given [payload] object for sending to the [otherParty]. Suspends until a response
@ -75,11 +85,13 @@ abstract class FlowLogic<out T> {
*/
@Suspendable
open fun <R : Any> sendAndReceive(receiveType: Class<R>, otherParty: Party, payload: Any): UntrustworthyData<R> {
return stateMachine.sendAndReceive(receiveType, otherParty, payload, sessionFlow)
return stateMachine.sendAndReceive(receiveType, otherParty, payload, flowUsedForSessions)
}
/** @see sendAndReceiveWithRetry */
internal inline fun <reified R : Any> sendAndReceiveWithRetry(otherParty: Party, payload: Any) = sendAndReceiveWithRetry(R::class.java, otherParty, payload)
internal inline fun <reified R : Any> sendAndReceiveWithRetry(otherParty: Party, payload: Any): UntrustworthyData<R> {
return sendAndReceiveWithRetry(R::class.java, otherParty, payload)
}
/**
* Similar to [sendAndReceive] but also instructs the `payload` to be redelivered until the expected message is received.
@ -92,7 +104,7 @@ abstract class FlowLogic<out T> {
*/
@Suspendable
internal open fun <R : Any> sendAndReceiveWithRetry(receiveType: Class<R>, otherParty: Party, payload: Any): UntrustworthyData<R> {
return stateMachine.sendAndReceive(receiveType, otherParty, payload, sessionFlow, true)
return stateMachine.sendAndReceive(receiveType, otherParty, payload, flowUsedForSessions, true)
}
/**
@ -115,7 +127,7 @@ abstract class FlowLogic<out T> {
*/
@Suspendable
open fun <R : Any> receive(receiveType: Class<R>, otherParty: Party): UntrustworthyData<R> {
return stateMachine.receive(receiveType, otherParty, sessionFlow)
return stateMachine.receive(receiveType, otherParty, flowUsedForSessions)
}
/**
@ -126,30 +138,27 @@ abstract class FlowLogic<out T> {
* network's event horizon time.
*/
@Suspendable
open fun send(otherParty: Party, payload: Any) = stateMachine.send(otherParty, payload, sessionFlow)
open fun send(otherParty: Party, payload: Any) = stateMachine.send(otherParty, payload, flowUsedForSessions)
/**
* Invokes the given subflow. This function returns once the subflow completes successfully with the result
* returned by that subflows [call] method. If the subflow has a progress tracker, it is attached to the
* returned by that subflow's [call] method. If the subflow has a progress tracker, it is attached to the
* current step in this flow's progress tracker.
*
* @param shareParentSessions In certain situations the need arises to use the same sessions the parent flow has
* already established. However this also prevents the subflow from creating new sessions with those parties.
* For this reason the default value is false.
* If the subflow is not an initiating flow (i.e. not annotated with [InitiatingFlow]) then it will continue to use
* the existing sessions this flow has created with its counterparties. This allows for subflows which can act as
* building blocks for other flows, for example removing the boilerplate of common sequences of sends and receives.
*
* @throws FlowException This is either thrown by [subLogic] itself or propagated from any of the remote
* [FlowLogic]s it communicated with. A subflow retry can be done by catching this exception.
* [FlowLogic]s it communicated with. The subflow can be retried by catching this exception.
*/
// TODO Rethink the default value for shareParentSessions
// TODO shareParentSessions is a bit too low-level and perhaps can be expresed in a better way
@Suspendable
@JvmOverloads
@Throws(FlowException::class)
open fun <R> subFlow(subLogic: FlowLogic<R>, shareParentSessions: Boolean = false): R {
open fun <R> subFlow(subLogic: FlowLogic<R>): R {
subLogic.stateMachine = stateMachine
maybeWireUpProgressTracking(subLogic)
if (shareParentSessions) {
subLogic.sessionFlow = this
if (!subLogic.javaClass.isAnnotationPresent(InitiatingFlow::class.java)) {
subLogic.flowUsedForSessions = flowUsedForSessions
}
logger.debug { "Calling subflow: $subLogic" }
val result = subLogic.call()
@ -171,8 +180,7 @@ abstract class FlowLogic<out T> {
open val progressTracker: ProgressTracker? = null
/**
* This is where you fill out your business logic. The returned object will usually be ignored, but can be
* helpful if this flow is meant to be used as a subflow.
* This is where you fill out your business logic.
*/
@Suspendable
@Throws(FlowException::class)
@ -187,7 +195,7 @@ abstract class FlowLogic<out T> {
fun track(): Pair<String, Observable<String>>? {
// TODO this is not threadsafe, needs an atomic get-step-and-subscribe
return progressTracker?.let {
Pair(it.currentStep.toString(), it.changes.map { it.toString() })
it.currentStep.toString() to it.changes.map { it.toString() }
}
}
@ -197,9 +205,7 @@ abstract class FlowLogic<out T> {
* valid by the local node, but that doesn't imply the vault will consider it relevant.
*/
@Suspendable
fun waitForLedgerCommit(hash: SecureHash): SignedTransaction {
return stateMachine.waitForLedgerCommit(hash, this)
}
fun waitForLedgerCommit(hash: SecureHash): SignedTransaction = stateMachine.waitForLedgerCommit(hash, this)
////////////////////////////////////////////////////////////////////////////////////////////////////////////
@ -216,8 +222,9 @@ abstract class FlowLogic<out T> {
_stateMachine = value
}
// This points to the outermost flow and is changed when a subflow is invoked.
private var sessionFlow: FlowLogic<*> = this
// This is the flow used for managing sessions. It defaults to the current flow but if this is an inlined sub-flow
// then it will point to the flow it's been inlined to.
private var flowUsedForSessions: FlowLogic<*> = this
private fun maybeWireUpProgressTracking(subLogic: FlowLogic<*>) {
val ours = progressTracker

View File

@ -1,18 +0,0 @@
package net.corda.core.flows
/**
* Annotation for initiating [FlowLogic]s to specify the version of their flow protocol. The version is a single integer
* [value] which increments by one whenever a release is made where the flow protocol changes in any manner which is
* backwards incompatible. This may be a change in the sequence of sends and receives between the client and service flows,
* or it could be a change in the meaning. The version is used when a flow first initiates communication with a party to
* inform them what version they are using. For this reason the annotation is not applicable for the initiated flow.
*
* This flow version integer is not the same as Corda's platform version, though it follows a similar semantic.
*
* Note: Only one version of the same flow can currently be loaded at the same time. Any session request by a client flow for
* a different version will be rejected.
*
* Defaults to a flow version of 1 if not specified.
*/
// TODO Add support for multiple versions once CorDapps are loaded in separate class loaders
annotation class FlowVersion(val value: Int)

View File

@ -0,0 +1,27 @@
package net.corda.core.flows
import java.lang.annotation.Inherited
import kotlin.annotation.AnnotationTarget.CLASS
/**
* This annotation is required by any [FlowLogic] which has been designated to initiate communication with a counterparty
* and request they start their side of the flow communication. To ensure that this is correctly applied
* [net.corda.core.node.PluginServiceHub.registerServiceFlow] checks the initiating flow class has this annotation.
*
* There is also an optional [version] property, which defaults to 1, to specify the version of the flow protocol. This
* integer value should be incremented whenever there is a release of this flow which has changes that are not backwards
* compatible with previous releases. This may be a change in the sends and receives that occur, or it could be a change
* in what a send or receive means, etc.
*
* The version is used when a flow first initiates communication with a party to inform them of the version they are using.
* If the other side does not have this flow registered with the same version then the initiation request will be rejected.
* Currently only one version of the same flow can be registered by a node.
*
* The flow version number is similar in concept to Corda's platform version but they are not the same. A flow's version
* number can change independently of the platform version.
*/
// TODO Add support for multiple versions once CorDapps are loaded in separate class loaders
@Target(CLASS)
@Inherited
@MustBeDocumented
annotation class InitiatingFlow(val version: Int = 1)

View File

@ -10,14 +10,15 @@ interface PluginServiceHub : ServiceHub {
/**
* Register the service flow factory to use when an initiating party attempts to communicate with us. The registration
* is done against the [Class] object of the client flow to the service flow. What this means is if a counterparty
* starts a [FlowLogic] represented by [clientFlowClass] and starts communication with us, we will execute the service
* starts a [FlowLogic] represented by [initiatingFlowClass] and starts communication with us, we will execute the service
* flow produced by [serviceFlowFactory]. This service flow has respond correctly to the sends and receives the client
* does.
* @param clientFlowClass [Class] of the client flow involved in this client-server communication.
* @param initiatingFlowClass [Class] of the client flow involved in this client-server communication.
* @param serviceFlowFactory Lambda which produces a new service flow for each new client flow communication. The
* [Party] parameter of the factory is the client's identity.
* @throws IllegalArgumentException If [initiatingFlowClass] is not annotated with [net.corda.core.flows.InitiatingFlow].
*/
fun registerServiceFlow(clientFlowClass: Class<out FlowLogic<*>>, serviceFlowFactory: (Party) -> FlowLogic<*>)
fun registerServiceFlow(initiatingFlowClass: Class<out FlowLogic<*>>, serviceFlowFactory: (Party) -> FlowLogic<*>)
@Suppress("UNCHECKED_CAST")
@Deprecated("This is scheduled to be removed in a future release", ReplaceWith("registerServiceFlow"))

View File

@ -3,6 +3,7 @@ package net.corda.flows
import co.paralleluniverse.fibers.Suspendable
import net.corda.core.crypto.Party
import net.corda.core.flows.FlowLogic
import net.corda.core.flows.InitiatingFlow
import net.corda.core.serialization.CordaSerializable
import net.corda.core.transactions.SignedTransaction
@ -15,6 +16,7 @@ import net.corda.core.transactions.SignedTransaction
* @param participants a list of participants involved in the transaction.
* @return a list of participants who were successfully notified of the transaction.
*/
@InitiatingFlow
class BroadcastTransactionFlow(val notarisedTransaction: SignedTransaction,
val participants: Set<Party>) : FlowLogic<Unit>() {
@CordaSerializable

View File

@ -1,6 +1,7 @@
package net.corda.flows
import net.corda.core.contracts.*
import net.corda.core.flows.InitiatingFlow
import net.corda.core.transactions.SignedTransaction
import net.corda.core.transactions.TransactionBuilder
import java.security.PublicKey
@ -13,6 +14,7 @@ import java.security.PublicKey
* Finally, the transaction containing all signatures is sent back to each participant so they can record it and
* use the new updated state for future transactions.
*/
@InitiatingFlow
class ContractUpgradeFlow<OldState : ContractState, out NewState : ContractState>(
originalState: StateAndRef<OldState>,
newContractClass: Class<out UpgradedContract<OldState, NewState>>

View File

@ -5,6 +5,7 @@ import net.corda.core.contracts.Attachment
import net.corda.core.crypto.Party
import net.corda.core.crypto.SecureHash
import net.corda.core.crypto.sha256
import net.corda.core.flows.InitiatingFlow
import net.corda.core.serialization.SerializationToken
import net.corda.core.serialization.SerializeAsToken
import net.corda.core.serialization.SerializeAsTokenContext
@ -13,6 +14,7 @@ import net.corda.core.serialization.SerializeAsTokenContext
* Given a set of hashes either loads from from local storage or requests them from the other peer. Downloaded
* attachments are saved to local storage automatically.
*/
@InitiatingFlow
class FetchAttachmentsFlow(requests: Set<SecureHash>,
otherSide: Party) : FetchDataFlow<Attachment, ByteArray>(requests, otherSide) {

View File

@ -2,6 +2,7 @@ package net.corda.flows
import net.corda.core.crypto.Party
import net.corda.core.crypto.SecureHash
import net.corda.core.flows.InitiatingFlow
import net.corda.core.transactions.SignedTransaction
/**
@ -12,8 +13,11 @@ import net.corda.core.transactions.SignedTransaction
* results in a [FetchDataFlow.HashNotFound] exception. Note that returned transactions are not inserted into
* the database, because it's up to the caller to actually verify the transactions are valid.
*/
@InitiatingFlow
class FetchTransactionsFlow(requests: Set<SecureHash>, otherSide: Party) :
FetchDataFlow<SignedTransaction, SignedTransaction>(requests, otherSide) {
override fun load(txid: SecureHash): SignedTransaction? = serviceHub.storageService.validatedTransactions.getTransaction(txid)
override fun load(txid: SecureHash): SignedTransaction? {
return serviceHub.storageService.validatedTransactions.getTransaction(txid)
}
}

View File

@ -2,6 +2,7 @@ package net.corda.flows
import net.corda.core.contracts.*
import net.corda.core.crypto.Party
import net.corda.core.flows.InitiatingFlow
import net.corda.core.transactions.SignedTransaction
import net.corda.core.transactions.TransactionBuilder
import net.corda.core.utilities.ProgressTracker
@ -16,6 +17,7 @@ import java.security.PublicKey
* Finally, the transaction containing all signatures is sent back to each participant so they can record it and
* use the new updated state for future transactions.
*/
@InitiatingFlow
class NotaryChangeFlow<out T : ContractState>(
originalState: StateAndRef<T>,
newNotary: Party,

View File

@ -6,6 +6,7 @@ import net.corda.core.contracts.Timestamp
import net.corda.core.crypto.*
import net.corda.core.flows.FlowException
import net.corda.core.flows.FlowLogic
import net.corda.core.flows.InitiatingFlow
import net.corda.core.node.services.TimestampChecker
import net.corda.core.node.services.UniquenessException
import net.corda.core.node.services.UniquenessProvider
@ -26,6 +27,7 @@ object NotaryFlow {
* @throws NotaryException in case the any of the inputs to the transaction have been consumed
* by another transaction or the timestamp is invalid.
*/
@InitiatingFlow
open class Client(private val stx: SignedTransaction,
override val progressTracker: ProgressTracker) : FlowLogic<List<DigitalSignature.WithKey>>() {
constructor(stx: SignedTransaction) : this(stx, Client.tracker())

View File

@ -37,6 +37,7 @@ public class FlowsInJavaTest {
}
@SuppressWarnings("unused")
@InitiatingFlow
private static class SendInUnwrapFlow extends FlowLogic<String> {
private final Party otherParty;

View File

@ -14,6 +14,7 @@ import java.security.cert.Certificate
*/
object TxKeyFlow {
@InitiatingFlow
class Requester(val otherSide: Party,
override val progressTracker: ProgressTracker) : FlowLogic<Pair<PublicKey, Certificate?>>() {
constructor(otherSide: Party) : this(otherSide, tracker())

View File

@ -5,6 +5,7 @@ import net.corda.core.contracts.Attachment
import net.corda.core.crypto.Party
import net.corda.core.crypto.SecureHash
import net.corda.core.flows.FlowLogic
import net.corda.core.flows.InitiatingFlow
import net.corda.core.getOrThrow
import net.corda.core.messaging.RPCOps
import net.corda.core.messaging.SingleMessageRecipient
@ -86,6 +87,7 @@ class AttachmentSerializationTest {
private class ClientResult(internal val attachmentContent: String)
@InitiatingFlow
private abstract class ClientLogic(server: MockNetwork.MockNode) : FlowLogic<ClientResult>() {
internal val server = server.info.legalIdentity
@ -134,7 +136,7 @@ class AttachmentSerializationTest {
}
private fun launchFlow(clientLogic: ClientLogic, rounds: Int) {
server.services.registerFlowInitiator(clientLogic.javaClass, ::ServerLogic)
server.services.registerServiceFlow(clientLogic.javaClass, ::ServerLogic)
client.services.startFlow(clientLogic)
network.runNetwork(rounds)
}

View File

@ -1,22 +1,40 @@
Changelog
=========
Here are brief summaries of what's changed between each snapshot release. This includes guidance on how to upgrade code from the previous milestone release.
Here are brief summaries of what's changed between each snapshot release. This includes guidance on how to upgrade code
from the previous milestone release.
UNRELEASED
----------
* API changes:
* ``PluginServiceHub.registerServiceFlow`` has been deprecated and replaced by ``registerServiceFlow`` with the
marker Class restricted to ``FlowLogic``.
* Initiating flows (i.e. those which initiate flows in a counterparty) are now required to be annotated with
``InitiatingFlow``.
* ``FlowLogic.getCounterpartyMarker`` is no longer used and been deprecated for removal. If you were using this to
manage multiple independent message streams with the same party in the same flow then use sub-flows instead.
* ``PluginServiceHub.registerFlowInitiator`` has been deprecated and replaced by ``registerServiceFlow`` with the
marker Class restricted to ``FlowLogic``. In line with the introduction of ``InitiatingFlow``, it throws an
``IllegalArgumentException`` if the initiating flow class is not annotated with it.
* Also related to ``InitiatingFlow``, the ``shareParentSessions`` boolean parameter of ``FlowLogic.subFlow`` has been
removed. Its purpose was to allow subflows to be inlined with the parent flow - i.e. the subflow does not initiate
new sessions with parties the parent flow has already started. This allowed flows to be used as building blocks. To
achieve the same effect now simply requires the subflow to be *not* annotated wth ``InitiatingFlow`` (i.e. we've made
this the default behaviour). If the subflow is not meant to be inlined, and is supposed to initiate flows on the
other side, the annotation is required.
* ``ContractUpgradeFlow.Instigator`` has been renamed to just ``ContractUpgradeFlow``.
* ``NotaryChangeFlow.Instigator`` has been renamed to just ``NotaryChangeFlow``.
* ``FlowLogic.getCounterpartyMarker`` is no longer used and been deprecated for removal. If you were using this to
manage multiple independent message streams with the same party in the same flow then use sub-flows instead.
* The ``InitiatingFlow`` annotation also has an integer ``version`` property which assigns the initiating flow a version
number, defaulting to 1 if it's specified. The flow version is included in the flow session request and the counterparty
will only respond and start their own flow if the version number matches to the one they've registered with. At some
point we will support the ability for a node to have multiple versions of the same flow registered, enabling backwards
compatibility of CorDapp flows.
Milestone 11.0
--------------
@ -31,20 +49,27 @@ Milestone 11.0
* Moved ``generateSpend`` and ``generateExit`` functions into ``OnLedgerAsset`` from the vault and
``AbstractConserveAmount`` clauses respectively.
* Added ``CompositeSignature`` and ``CompositeSignatureData`` as part of enabling ``java.security`` classes to work with
composite keys and signatures.
* Added ``CompositeSignature`` and ``CompositeSignatureData`` as part of enabling ``java.security`` classes to work
with composite keys and signatures.
* ``CompositeKey`` now implements ``java.security.PublicKey`` interface, so that keys can be used on standard classes such as ``Certificate``.
* ``CompositeKey`` now implements ``java.security.PublicKey`` interface, so that keys can be used on standard classes
such as ``Certificate``.
* There is no longer a need to transform single keys into composite - ``composite`` extension was removed, it is imposible to create ``CompositeKey`` with only one leaf.
* There is no longer a need to transform single keys into composite - ``composite`` extension was removed, it is
imposible to create ``CompositeKey`` with only one leaf.
* Constructor of ``CompositeKey`` class is now private. Use ``CompositeKey.Builder`` to create a composite key. Keys emitted by the builder are normalised so that it's impossible to create a composite key with only one node. (Long chains of single nodes are shortened.)
* Constructor of ``CompositeKey`` class is now private. Use ``CompositeKey.Builder`` to create a composite key.
Keys emitted by the builder are normalised so that it's impossible to create a composite key with only one node.
(Long chains of single nodes are shortened.)
* Use extension function ``PublicKeys.keys`` to access all keys belonging to an instance of ``PublicKey``. For a ``CompositeKey``, this is equivalent to ``CompositeKey.leafKeys``.
* Use extension function ``PublicKeys.keys`` to access all keys belonging to an instance of ``PublicKey``. For a
``CompositeKey``, this is equivalent to ``CompositeKey.leafKeys``.
* Introduced ``containsAny``, ``isFulfilledBy``, ``keys`` extension functions on ``PublicKey`` - ``CompositeKey`` type checking is done there.
* Introduced ``containsAny``, ``isFulfilledBy``, ``keys`` extension functions on ``PublicKey`` - ``CompositeKey``
type checking is done there.
* Corda now requires JDK 8u131 or above in order to run. Our Kotlin now also compiles to JDK8 bytecode, and so you'll need to update your CorDapp projects to do the same. E.g. by adding this to ``build.gradle``:
* Corda now requires JDK 8u131 or above in order to run. Our Kotlin now also compiles to JDK8 bytecode, and so you'll need
to update your CorDapp projects to do the same. E.g. by adding this to ``build.gradle``:
.. parsed-literal::
@ -89,7 +114,8 @@ to Corda in M10.
processor.
* Corda DemoBench:
* DemoBench is a new tool to make it easy to configure and launch local Corda nodes. A very useful tool to demonstrate to your colleagues the fundamentals of Corda in real-time. It has the following features:
* DemoBench is a new tool to make it easy to configure and launch local Corda nodes. A very useful tool to demonstrate
to your colleagues the fundamentals of Corda in real-time. It has the following features:
* Clicking "Add node" creates a new tab that lets you edit the most important configuration properties of the node
before launch, such as its legal name and which CorDapps will be loaded.

View File

@ -11,6 +11,7 @@ import net.corda.core.crypto.Party
import net.corda.core.crypto.SecureHash
import net.corda.core.crypto.sign
import net.corda.core.flows.FlowLogic
import net.corda.core.flows.InitiatingFlow
import net.corda.core.node.PluginServiceHub
import net.corda.core.node.ServiceHub
import net.corda.core.node.services.unconsumedStates
@ -104,6 +105,7 @@ private fun prepareOurInputsAndOutputs(serviceHub: ServiceHub, request: FxReques
// A flow representing creating a transaction that
// carries out exchange of cash assets.
@InitiatingFlow
class ForeignExchangeFlow(val tradeId: String,
val baseCurrencyAmount: Amount<Issued<Currency>>,
val quoteCurrencyAmount: Amount<Issued<Currency>>,

View File

@ -4,6 +4,7 @@ import co.paralleluniverse.fibers.Suspendable
import net.corda.core.contracts.*
import net.corda.core.crypto.*
import net.corda.core.flows.FlowLogic
import net.corda.core.flows.InitiatingFlow
import net.corda.core.node.PluginServiceHub
import net.corda.core.node.ServiceHub
import net.corda.core.node.services.linearHeadsOfType
@ -144,6 +145,7 @@ class SubmitTradeApprovalFlow(val tradeId: String,
* Simple flow to complete a proposal submitted by another party and ensure both nodes
* end up with a fully signed copy of the state either as APPROVED, or REJECTED
*/
@InitiatingFlow
class SubmitCompletionFlow(val ref: StateRef, val verdict: WorkflowState) : FlowLogic<StateAndRef<TradeApprovalContract.State>>() {
init {
require(verdict in setOf(WorkflowState.APPROVED, WorkflowState.REJECTED)) {

View File

@ -228,26 +228,6 @@ These will return a ``FlowProgressHandle``, which is just like a ``FlowHandle``
.. note:: The developer `must` then either subscribe to this ``progress`` observable or invoke the ``notUsed()`` extension
function for it. Otherwise the unused observable will waste resources back in the node.
In a two party flow only one side is to be manually started using ``CordaRPCOps.startFlow``. The other side has to be
registered by its node to respond to the initiating flow via ``PluginServiceHub.registerServiceFlow``. In our example it
doesn't matter which flow is the initiator (i.e. client) and which is the initiated (i.e. service). For example, if we
are to take the seller as the initiator then we would register the buyer as such:
.. container:: codeset
.. sourcecode:: kotlin
val services: PluginServiceHub = TODO()
services.registerServiceFlow(Seller::class.java) { otherParty ->
val notary = services.networkMapCache.notaryNodes[0]
val acceptablePrice = TODO()
val typeToBuy = TODO()
Buyer(otherParty, notary, acceptablePrice, typeToBuy)
}
This is telling the buyer node to fire up an instance of ``Buyer`` (the code in the lambda) when the initiating flow
is a seller (``Seller::class``).
Implementing the seller
-----------------------
@ -419,6 +399,47 @@ This code is longer but no more complicated. Here are some things to pay attenti
As you can see, the flow logic is straightforward and does not contain any callbacks or network glue code, despite
the fact that it takes minimal resources and can survive node restarts.
Initiating communication
------------------------
Now that we have both sides of the deal negotation implemented as flows we need a way to start things off. We do this by
having one side initiate communication and the other respond to it and start their flow. Initiation is typically done using
RPC with the ``startFlowDynamic`` method. The initiating flow has be to annotated with ``InitiatingFlow``. In our example
it doesn't matter which flow is the initiator and which is the initiated, which is why neither ``Buyer`` nor ``Seller``
are annotated with it. For example, if we choose the seller side as the initiator then we need a seller starter flow that
might look something like this:
.. container:: codeset
.. sourcecode:: kotlin
@InitiatingFlow
class SellerStarter(val otherParty: Party, val assetToSell: StateAndRef<OwnableState>, val price: Amount<Currency>) : FlowLogic<SignedTransaction>() {
@Suspendable
override fun call(): SignedTransaction {
val notary: NodeInfo = serviceHub.networkMapCache.notaryNodes[0]
val cpOwnerKey: KeyPair = serviceHub.legalIdentityKey
return subFlow(TwoPartyTradeFlow.Seller(otherParty, notary, assetToSell, price, cpOwnerKey))
}
}
The buyer side would then need to register their flow, perhaps with something like:
.. container:: codeset
.. sourcecode:: kotlin
val services: PluginServiceHub = TODO()
services.registerServiceFlow(SellerStarter::class.java) { otherParty ->
val notary = services.networkMapCache.notaryNodes[0]
val acceptablePrice = TODO()
val typeToBuy = TODO()
Buyer(otherParty, notary, acceptablePrice, typeToBuy)
}
This is telling the buyer node to fire up an instance of ``Buyer`` (the code in the lambda) when the initiating flow
is a seller (``SellerStarter::class.java``).
.. _progress-tracking:
Progress tracking

View File

@ -6,10 +6,9 @@ Here are release notes for each snapshot release from M9 onwards.
Unreleased
----------
Flows can now be versioned using the ``FlowVersion`` annotation, which assigns an integer version number to it. For now
this enables a node to restrict usage of a flow to a specific version. Support for multiple verisons of the same flow,
hence achieving backwards compatibility, will be possible once we start loading CorDapps in separate class loaders. Watch
this space...
We've added the ability for flows to be versioned by their CorDapp developers. This enables a node to support a particular
version of a flow and allows it to reject flow communication with a node which isn't using the same fact. In a future
release we allow a node to have multiple versions of the same flow running to enable backwards compatibility.
Milestone 11
------------
@ -36,7 +35,8 @@ We anticipate enforcing the use of distinguished names in node configurations fr
We have increased the maximum message size that we can send to Corda over RPC from 100 KB to 10 MB.
The Corda node now disables any use of ObjectInputStream to prevent Java deserialisation within flows. This is a security fix, and prevents the node from deserialising arbitrary objects.
The Corda node now disables any use of ObjectInputStream to prevent Java deserialisation within flows. This is a security fix,
and prevents the node from deserialising arbitrary objects.
We've introduced the concept of platform version which is a single integer value which increments by 1 if a release changes
any of the public APIs of the entire Corda platform. This includes the node's public APIs, the messaging protocol,
@ -44,8 +44,9 @@ serialisation, etc. The node exposes the platform version it's on and we envisio
run on older versions of the platform to the one they were compiled against. Platform version borrows heavily from Android's
API Level.
We have revamped the DemoBench user interface. DemoBench will now also be installed as "Corda DemoBench" for both Windows and MacOSX. The original version
was installed as just "DemoBench", and so will not be overwritten automatically by the new version.
We have revamped the DemoBench user interface. DemoBench will now also be installed as "Corda DemoBench" for both Windows
and MacOSX. The original version was installed as just "DemoBench", and so will not be overwritten automatically by the
new version.
Milestone 10
------------

View File

@ -35,10 +35,10 @@ A platform which can be extended with CorDapps also requires the ability to vers
release to release. This allows users of these apps, whether they're other nodes or RPC users, to select which version
they wish to use and enables nodes to control which app versions they support. Flows have their own version numbers,
independent of other versioning, for example of the platform. In particular it is the initiating flow that can be versioned
using the ``FlowVersion`` annotation. This assigns an integer version number, similar in concept to the platform version,
which is used in the session handshake process when a flow communicates with another party for the first time. The other
party will only accept the session request if it, firstly, has that flow loaded, and secondly, for the same version (see
:doc:`flow-state-machine`).
using the ``version`` property of the ``InitiatingFlow`` annotation. This assigns an integer version number, similar in
concept to the platform version, which is used in the session handshake process when a flow communicates with another party
for the first time. The other party will only accept the session request if it, firstly, has that flow loaded, and secondly,
for the same version (see also :doc:`flow-state-machine`).
.. note:: Currently we don't support multiple versions of the same flow loaded in the same node. This will be possible
once we start loading CorDapps in separate class loaders.

View File

@ -5,6 +5,7 @@ import net.corda.core.contracts.*
import net.corda.core.crypto.Party
import net.corda.core.flows.FlowException
import net.corda.core.flows.FlowLogic
import net.corda.core.flows.InitiatingFlow
import net.corda.core.node.PluginServiceHub
import net.corda.core.serialization.CordaSerializable
import net.corda.core.serialization.OpaqueBytes
@ -28,6 +29,7 @@ object IssuerFlow {
* IssuanceRequester should be used by a client to ask a remote node to issue some [FungibleAsset] with the given details.
* Returns the transaction created by the Issuer to move the cash to the Requester.
*/
@InitiatingFlow
class IssuanceRequester(val amount: Amount<Currency>, val issueToParty: Party, val issueToPartyRef: OpaqueBytes,
val issuerBankParty: Party) : FlowLogic<SignedTransaction>() {
@Suspendable

View File

@ -4,6 +4,7 @@ import co.paralleluniverse.fibers.Suspendable
import com.google.common.util.concurrent.Futures
import net.corda.core.crypto.Party
import net.corda.core.flows.FlowLogic
import net.corda.core.flows.InitiatingFlow
import net.corda.core.getOrThrow
import net.corda.core.utilities.ALICE
import net.corda.core.utilities.BOB
@ -23,14 +24,15 @@ class FlowVersioningTest : NodeBasedTest() {
assertThat(resultFuture.getOrThrow()).isEqualTo(2)
}
private open class ClientFlow(val otherParty: Party) : FlowLogic<Any>() {
@InitiatingFlow
private class ClientFlow(val otherParty: Party) : FlowLogic<Any>() {
@Suspendable
override fun call(): Any {
return sendAndReceive<Any>(otherParty, "This is ignored. We only send to kick off the flow on the other side").unwrap { it }
}
}
private open class SendBackPlatformVersionFlow(val otherParty: Party, val otherPartysPlatformVersion: Any) : FlowLogic<Unit>() {
private class SendBackPlatformVersionFlow(val otherParty: Party, val otherPartysPlatformVersion: Int) : FlowLogic<Unit>() {
@Suspendable
override fun call() = send(otherParty, otherPartysPlatformVersion)
}

View File

@ -7,6 +7,7 @@ import net.corda.core.crypto.Party
import net.corda.core.crypto.generateKeyPair
import net.corda.core.crypto.toBase58String
import net.corda.core.flows.FlowLogic
import net.corda.core.flows.InitiatingFlow
import net.corda.core.getOrThrow
import net.corda.core.messaging.CordaRPCOps
import net.corda.core.random63BitValue
@ -222,6 +223,7 @@ abstract class MQSecurityTest : NodeBasedTest() {
return bobParty
}
@InitiatingFlow
private class SendFlow(val otherParty: Party, val payload: Any) : FlowLogic<Unit>() {
@Suspendable
override fun call() = send(otherParty, payload)

View File

@ -14,7 +14,7 @@ import net.corda.core.crypto.X509Utilities
import net.corda.core.crypto.replaceCommonName
import net.corda.core.flows.FlowInitiator
import net.corda.core.flows.FlowLogic
import net.corda.core.flows.FlowVersion
import net.corda.core.flows.InitiatingFlow
import net.corda.core.messaging.CordaRPCOps
import net.corda.core.messaging.RPCOps
import net.corda.core.messaging.SingleMessageRecipient
@ -141,12 +141,13 @@ abstract class AbstractNode(open val configuration: NodeConfiguration,
return serverThread.fetchFrom { smm.add(logic, flowInitiator) }
}
override fun registerServiceFlow(clientFlowClass: Class<out FlowLogic<*>>, serviceFlowFactory: (Party) -> FlowLogic<*>) {
require(clientFlowClass !in serviceFlowFactories) { "${clientFlowClass.name} has already been used to register a service flow" }
val version = clientFlowClass.flowVersion
val info = ServiceFlowInfo.CorDapp(version, serviceFlowFactory)
log.info("Registering service flow for ${clientFlowClass.name}: $info")
serviceFlowFactories[clientFlowClass] = info
override fun registerServiceFlow(initiatingFlowClass: Class<out FlowLogic<*>>, serviceFlowFactory: (Party) -> FlowLogic<*>) {
require(initiatingFlowClass !in serviceFlowFactories) {
"${initiatingFlowClass.name} has already been used to register a service flow"
}
val info = ServiceFlowInfo.CorDapp(initiatingFlowClass.flowVersion, serviceFlowFactory)
log.info("Registering service flow for ${initiatingFlowClass.name}: $info")
serviceFlowFactories[initiatingFlowClass] = info
}
override fun getServiceFlowFactory(clientFlowClass: Class<out FlowLogic<*>>): ServiceFlowInfo? {
@ -259,15 +260,15 @@ abstract class AbstractNode(open val configuration: NodeConfiguration,
}
/**
* @suppress
* Installs a flow that's core to the Corda platform. Unlike CorDapp flows which are versioned individually using
* [FlowVersion], core flows have the same version as the node's platform version. To cater for backwards compatibility
* [serviceFlowFactory] provides a second parameter which is the platform version of the initiating party.
* [InitiatingFlow.version], core flows have the same version as the node's platform version. To cater for backwards
* compatibility [serviceFlowFactory] provides a second parameter which is the platform version of the initiating party.
* @suppress
*/
@VisibleForTesting
fun installCoreFlow(clientFlowClass: KClass<out FlowLogic<*>>, serviceFlowFactory: (Party, Int) -> FlowLogic<*>) {
require(!clientFlowClass.java.isAnnotationPresent(FlowVersion::class.java)) {
"${FlowVersion::class.java.name} not applicable for core flows; their version is the node's platform version"
require(clientFlowClass.java.flowVersion == 1) {
"${InitiatingFlow::class.java.name}.version not applicable for core flows; their version is the node's platform version"
}
serviceFlowFactories[clientFlowClass.java] = ServiceFlowInfo.Core(serviceFlowFactory)
log.debug { "Installed core flow ${clientFlowClass.java.name}" }
@ -302,7 +303,7 @@ abstract class AbstractNode(open val configuration: NodeConfiguration,
// the identity key. But the infrastructure to make that easy isn't here yet.
keyManagement = makeKeyManagementService()
flowLogicFactory = initialiseFlowLogicFactory()
scheduler = NodeSchedulerService(services, flowLogicFactory, unfinishedSchedules = busyNodeLatch)
scheduler = NodeSchedulerService(services, database, flowLogicFactory, unfinishedSchedules = busyNodeLatch)
val tokenizableServices = mutableListOf(storage, net, vault, keyManagement, identity, platformClock, scheduler)
makeAdvertisedServices(tokenizableServices)

View File

@ -63,7 +63,7 @@ class NotifyTransactionHandler(val otherParty: Party) : FlowLogic<Unit>() {
@Suspendable
override fun call() {
val request = receive<BroadcastTransactionFlow.NotifyTxRequest>(otherParty).unwrap { it }
subFlow(ResolveTransactionsFlow(request.tx, otherParty), shareParentSessions = true)
subFlow(ResolveTransactionsFlow(request.tx, otherParty))
serviceHub.recordTransactions(request.tx)
}
}

View File

@ -1,6 +1,5 @@
package net.corda.node.services.events
import co.paralleluniverse.fibers.Suspendable
import com.google.common.util.concurrent.SettableFuture
import net.corda.core.ThreadBox
import net.corda.core.contracts.SchedulableState
@ -10,7 +9,7 @@ import net.corda.core.contracts.StateRef
import net.corda.core.flows.FlowInitiator
import net.corda.core.flows.FlowLogic
import net.corda.core.serialization.SingletonSerializeAsToken
import net.corda.core.utilities.ProgressTracker
import net.corda.core.then
import net.corda.core.utilities.loggerFor
import net.corda.core.utilities.trace
import net.corda.node.services.api.FlowLogicRefFactoryInternal
@ -18,6 +17,7 @@ import net.corda.node.services.api.SchedulerService
import net.corda.node.services.api.ServiceHubInternal
import net.corda.node.utilities.*
import org.apache.activemq.artemis.utils.ReusableLatch
import org.jetbrains.exposed.sql.Database
import org.jetbrains.exposed.sql.ResultRow
import org.jetbrains.exposed.sql.statements.InsertStatement
import java.time.Instant
@ -44,12 +44,15 @@ import javax.annotation.concurrent.ThreadSafe
*/
@ThreadSafe
class NodeSchedulerService(private val services: ServiceHubInternal,
private val database: Database,
private val flowLogicRefFactory: FlowLogicRefFactoryInternal,
private val schedulerTimerExecutor: Executor = Executors.newSingleThreadExecutor(),
private val unfinishedSchedules: ReusableLatch = ReusableLatch())
: SchedulerService, SingletonSerializeAsToken() {
private val log = loggerFor<NodeSchedulerService>()
companion object {
private val log = loggerFor<NodeSchedulerService>()
}
private object Table : JDBCHashedTable("${NODE_DATABASE_PREFIX}scheduled_states") {
val output = stateRef("transaction_id", "output_index")
@ -158,71 +161,62 @@ class NodeSchedulerService(private val services: ServiceHubInternal,
}
private fun onTimeReached(scheduledState: ScheduledStateRef) {
services.startFlow(RunScheduled(scheduledState, this@NodeSchedulerService), FlowInitiator.Scheduled(scheduledState))
database.transaction {
val scheduledFlow = getScheduledFlow(scheduledState)
if (scheduledFlow != null) {
// TODO Because the flow is executed asynchronously, there is a small window between this tx we're in
// committing and the flow's first checkpoint when it starts in which we can lose the flow if the node
// goes down.
// See discussion in https://github.com/corda/corda/pull/639#discussion_r115257437
val future = services.startFlow(scheduledFlow, FlowInitiator.Scheduled(scheduledState)).resultFuture
future.then {
unfinishedSchedules.countDown()
}
}
}
}
class RunScheduled(val scheduledState: ScheduledStateRef, val scheduler: NodeSchedulerService) : FlowLogic<Unit>() {
companion object {
object RUNNING : ProgressTracker.Step("Running scheduled...")
fun tracker() = ProgressTracker(RUNNING)
}
override val progressTracker = tracker()
@Suspendable
override fun call(): Unit {
progressTracker.currentStep = RUNNING
// Ensure we are still scheduled.
val scheduledLogic: FlowLogic<*>? = getScheduledLogic()
if (scheduledLogic != null) {
subFlow(scheduledLogic)
scheduler.unfinishedSchedules.countDown()
}
}
private fun getScheduledaActivity(): ScheduledActivity? {
val txState = serviceHub.loadState(scheduledState.ref)
val state = txState.data as SchedulableState
return try {
// This can throw as running contract code.
state.nextScheduledActivity(scheduledState.ref, scheduler.flowLogicRefFactory)
} catch(e: Exception) {
logger.error("Attempt to run scheduled state $scheduledState resulted in error.", e)
null
}
}
private fun getScheduledLogic(): FlowLogic<*>? {
val scheduledActivity = getScheduledaActivity()
var scheduledLogic: FlowLogic<*>? = null
scheduler.mutex.locked {
// need to remove us from those scheduled, but only if we are still next
scheduledStates.compute(scheduledState.ref) { _, value ->
if (value === scheduledState) {
if (scheduledActivity == null) {
logger.info("Scheduled state $scheduledState has rescheduled to never.")
scheduler.unfinishedSchedules.countDown()
null
} else if (scheduledActivity.scheduledAt.isAfter(serviceHub.clock.instant())) {
logger.info("Scheduled state $scheduledState has rescheduled to ${scheduledActivity.scheduledAt}.")
ScheduledStateRef(scheduledState.ref, scheduledActivity.scheduledAt)
} else {
// TODO: FlowLogicRefFactory needs to sort out the class loader etc
val logic = scheduler.flowLogicRefFactory.toFlowLogic(scheduledActivity.logicRef)
logger.trace { "Scheduler starting FlowLogic $logic" }
scheduledLogic = logic
null
}
private fun getScheduledFlow(scheduledState: ScheduledStateRef): FlowLogic<*>? {
val scheduledActivity = getScheduledActivity(scheduledState)
var scheduledFlow: FlowLogic<*>? = null
mutex.locked {
// need to remove us from those scheduled, but only if we are still next
scheduledStates.compute(scheduledState.ref) { _, value ->
if (value === scheduledState) {
if (scheduledActivity == null) {
log.info("Scheduled state $scheduledState has rescheduled to never.")
unfinishedSchedules.countDown()
null
} else if (scheduledActivity.scheduledAt.isAfter(services.clock.instant())) {
log.info("Scheduled state $scheduledState has rescheduled to ${scheduledActivity.scheduledAt}.")
ScheduledStateRef(scheduledState.ref, scheduledActivity.scheduledAt)
} else {
value
// TODO: FlowLogicRefFactory needs to sort out the class loader etc
val flowLogic = flowLogicRefFactory.toFlowLogic(scheduledActivity.logicRef)
log.trace { "Scheduler starting FlowLogic $flowLogic" }
scheduledFlow = flowLogic
null
}
} else {
value
}
// and schedule the next one
recomputeEarliest()
scheduler.rescheduleWakeUp()
}
return scheduledLogic
// and schedule the next one
recomputeEarliest()
rescheduleWakeUp()
}
return scheduledFlow
}
private fun getScheduledActivity(scheduledState: ScheduledStateRef): ScheduledActivity? {
val txState = services.loadState(scheduledState.ref)
val state = txState.data as SchedulableState
return try {
// This can throw as running contract code.
state.nextScheduledActivity(scheduledState.ref, flowLogicRefFactory)
} catch (e: Exception) {
log.error("Attempt to run scheduled state $scheduledState resulted in error.", e)
null
}
}
}

View File

@ -426,7 +426,9 @@ class FlowStateMachineImpl<R>(override val id: StateMachineRunId,
}
val Class<out FlowLogic<*>>.flowVersion: Int get() {
val flowVersion = getDeclaredAnnotation(FlowVersion::class.java) ?: return 1
require(flowVersion.value > 0) { "Flow versions have to be greater or equal to 1" }
return flowVersion.value
val annotation = requireNotNull(getAnnotation(InitiatingFlow::class.java)) {
"$name as the initiating flow must be annotated with ${InitiatingFlow::class.java.name}"
}
require(annotation.version > 0) { "Flow versions have to be greater or equal to 1" }
return annotation.version
}

View File

@ -368,10 +368,6 @@ class StateMachineManager(val serviceHub: ServiceHubInternal,
is ServiceFlowInfo.Core -> serviceFlowInfo.factory(sender, receivedMessage.platformVersion)
}
if (flow.javaClass.isAnnotationPresent(FlowVersion::class.java)) {
logger.warn("${FlowVersion::class.java.name} is not applicable for service flows: ${flow.javaClass.name}")
}
val fiber = createFiber(flow, FlowInitiator.Peer(sender))
val session = FlowSession(flow, random63BitValue(), sender, FlowSessionState.Initiated(sender, otherPartySessionId))
if (sessionInit.firstPayload != null) {

View File

@ -1,5 +1,6 @@
package net.corda.node.messaging
import co.paralleluniverse.fibers.Suspendable
import net.corda.contracts.CommercialPaper
import net.corda.contracts.asset.*
import net.corda.contracts.testing.fillWithSomeTestCash
@ -8,11 +9,14 @@ import net.corda.core.crypto.AnonymousParty
import net.corda.core.crypto.Party
import net.corda.core.crypto.SecureHash
import net.corda.core.days
import net.corda.core.flows.FlowLogic
import net.corda.core.flows.FlowStateMachine
import net.corda.core.flows.InitiatingFlow
import net.corda.core.flows.StateMachineRunId
import net.corda.core.getOrThrow
import net.corda.core.map
import net.corda.core.messaging.SingleMessageRecipient
import net.corda.core.node.NodeInfo
import net.corda.core.node.services.*
import net.corda.core.rootCause
import net.corda.core.transactions.SignedTransaction
@ -266,7 +270,10 @@ class TwoPartyTradeFlowTests {
// Creates a mock node with an overridden storage service that uses a RecordingMap, that lets us test the order
// of gets and puts.
private fun makeNodeWithTracking(networkMapAddr: SingleMessageRecipient?, name: X500Name, overrideServices: Map<ServiceInfo, KeyPair>? = null): MockNetwork.MockNode {
private fun makeNodeWithTracking(
networkMapAddr: SingleMessageRecipient?,
name: X500Name,
overrideServices: Map<ServiceInfo, KeyPair>? = null): MockNetwork.MockNode {
// Create a node in the mock network ...
return net.createNode(networkMapAddr, -1, object : MockNetwork.Factory {
override fun create(config: NodeConfiguration,
@ -391,7 +398,6 @@ class TwoPartyTradeFlowTests {
@Test
fun `track works`() {
val notaryNode = net.createNotaryNode(null, DUMMY_NOTARY.name)
val aliceNode = makeNodeWithTracking(notaryNode.info.address, ALICE.name)
val bobNode = makeNodeWithTracking(notaryNode.info.address, BOB.name)
@ -445,13 +451,13 @@ class TwoPartyTradeFlowTests {
)
aliceTxStream.expectEvents { aliceTxExpectations }
val aliceMappingExpectations = sequence(
expect { mapping: StateMachineTransactionMapping ->
require(mapping.stateMachineRunId == aliceSmId)
require(mapping.transactionId == bobsFakeCash[0].id)
expect { (stateMachineRunId, transactionId) ->
require(stateMachineRunId == aliceSmId)
require(transactionId == bobsFakeCash[0].id)
},
expect { mapping: StateMachineTransactionMapping ->
require(mapping.stateMachineRunId == aliceSmId)
require(mapping.transactionId == bobsFakeCash[2].id)
expect { (stateMachineRunId, transactionId) ->
require(stateMachineRunId == aliceSmId)
require(transactionId == bobsFakeCash[2].id)
},
expect { mapping: StateMachineTransactionMapping ->
require(mapping.stateMachineRunId == aliceSmId)
@ -487,10 +493,21 @@ class TwoPartyTradeFlowTests {
sellerNode: MockNetwork.MockNode,
buyerNode: MockNetwork.MockNode,
assetToSell: StateAndRef<OwnableState>): RunResult {
val buyerFuture = buyerNode.initiateSingleShotFlow(Seller::class) { otherParty ->
@InitiatingFlow
class SellerRunnerFlow(val buyer: Party, val notary: NodeInfo) : FlowLogic<SignedTransaction>() {
@Suspendable
override fun call(): SignedTransaction = subFlow(Seller(
buyer,
notary,
assetToSell,
1000.DOLLARS,
serviceHub.legalIdentityKey))
}
val buyerFuture = buyerNode.initiateSingleShotFlow(SellerRunnerFlow::class) { otherParty ->
Buyer(otherParty, notaryNode.info.notaryIdentity, 1000.DOLLARS, CommercialPaper.State::class.java)
}.map { it.stateMachine }
val seller = Seller(buyerNode.info.legalIdentity, notaryNode.info, assetToSell, 1000.DOLLARS, sellerNode.services.legalIdentityKey)
val seller = SellerRunnerFlow(buyerNode.info.legalIdentity, notaryNode.info)
val sellerResultFuture = sellerNode.services.startFlow(seller).resultFuture
return RunResult(buyerFuture, sellerResultFuture, seller.stateMachine.id)
}

View File

@ -73,7 +73,7 @@ open class MockServiceHubInternal(
return smm.executor.fetchFrom { smm.add(logic, flowInitiator) }
}
override fun registerServiceFlow(clientFlowClass: Class<out FlowLogic<*>>, serviceFlowFactory: (Party) -> FlowLogic<*>) = Unit
override fun registerServiceFlow(initiatingFlowClass: Class<out FlowLogic<*>>, serviceFlowFactory: (Party) -> FlowLogic<*>) = Unit
override fun getServiceFlowFactory(clientFlowClass: Class<out FlowLogic<*>>): ServiceFlowInfo? = null
}

View File

@ -87,7 +87,7 @@ class NodeSchedulerServiceTest : SingletonSerializeAsToken() {
override val vaultService: VaultService = NodeVaultService(this, dataSourceProps)
override val testReference = this@NodeSchedulerServiceTest
}
scheduler = NodeSchedulerService(services, factory, schedulerGatedExecutor)
scheduler = NodeSchedulerService(services, database, factory, schedulerGatedExecutor)
smmExecutor = AffinityExecutor.ServiceAffinityExecutor("test", 1)
val mockSMM = StateMachineManager(services, listOf(services, scheduler), DBCheckpointStorage(), smmExecutor, database)
mockSMM.changes.subscribe { change ->

View File

@ -8,6 +8,7 @@ import net.corda.core.contracts.TransactionType
import net.corda.core.contracts.USD
import net.corda.core.crypto.Party
import net.corda.core.flows.FlowLogic
import net.corda.core.flows.InitiatingFlow
import net.corda.core.node.services.unconsumedStates
import net.corda.core.transactions.SignedTransaction
import net.corda.core.utilities.DUMMY_NOTARY
@ -95,6 +96,7 @@ class DataVendingServiceTests {
}
@InitiatingFlow
private class NotifyTxFlow(val otherParty: Party, val stx: SignedTransaction) : FlowLogic<Unit>() {
@Suspendable
override fun call() = send(otherParty, NotifyTxRequest(stx))

View File

@ -8,10 +8,11 @@ import net.corda.core.*
import net.corda.core.contracts.DOLLARS
import net.corda.core.contracts.DummyState
import net.corda.core.crypto.Party
import net.corda.core.crypto.SecureHash
import net.corda.core.crypto.generateKeyPair
import net.corda.core.flows.FlowException
import net.corda.core.flows.FlowLogic
import net.corda.core.flows.FlowVersion
import net.corda.core.flows.InitiatingFlow
import net.corda.core.messaging.MessageRecipients
import net.corda.core.node.services.PartyInfo
import net.corda.core.node.services.ServiceInfo
@ -56,7 +57,7 @@ import kotlin.test.assertEquals
import kotlin.test.assertFailsWith
import kotlin.test.assertTrue
class StateMachineManagerTests {
class FlowFrameworkTests {
companion object {
init {
LogHelper.setLevel("+net.corda.flow")
@ -468,13 +469,6 @@ class StateMachineManagerTests {
.withMessage("Chain")
}
private class SendAndReceiveFlow(val otherParty: Party, val payload: Any) : FlowLogic<Unit>() {
@Suspendable
override fun call() {
sendAndReceive<Any>(otherParty, payload)
}
}
@Test
fun `FlowException thrown and there is a 3rd unrelated party flow`() {
val node3 = net.createNode(node1.info.address)
@ -521,6 +515,7 @@ class StateMachineManagerTests {
@Test
fun `retry subFlow due to receiving FlowException`() {
@InitiatingFlow
class AskForExceptionFlow(val otherParty: Party, val throwException: Boolean) : FlowLogic<String>() {
@Suspendable
override fun call(): String = sendAndReceive<String>(otherParty, throwException).unwrap { it }
@ -577,7 +572,7 @@ class StateMachineManagerTests {
@Test
fun `lazy db iterator left on stack during checkpointing`() {
val result = node2.services.startFlow(VaultAccessFlow(node1.info.legalIdentity)).resultFuture
val result = node2.services.startFlow(VaultAccessFlow()).resultFuture
net.runNetwork()
assertThatThrownBy { result.getOrThrow() }.hasMessageContaining("Vault").hasMessageContaining("private method")
}
@ -609,10 +604,20 @@ class StateMachineManagerTests {
}.withMessageContaining("Version")
}
@FlowVersion(2)
private class UpgradedFlow(val otherParty: Party) : FlowLogic<Any>() {
@Suspendable
override fun call(): Any = receive<Any>(otherParty).unwrap { it }
@Test
fun `single inlined sub-flow`() {
node2.registerServiceFlow(SendAndReceiveFlow::class) { SingleInlinedSubFlow(it) }
val result = node1.services.startFlow(SendAndReceiveFlow(node2.info.legalIdentity, "Hello")).resultFuture
net.runNetwork()
assertThat(result.getOrThrow()).isEqualTo("HelloHello")
}
@Test
fun `double inlined sub-flow`() {
node2.registerServiceFlow(SendAndReceiveFlow::class) { DoubleInlinedSubFlow(it) }
val result = node1.services.startFlow(SendAndReceiveFlow(node2.info.legalIdentity, "Hello")).resultFuture
net.runNetwork()
assertThat(result.getOrThrow()).isEqualTo("HelloHello")
}
@ -664,15 +669,13 @@ class StateMachineManagerTests {
}
}
private fun sanitise(message: SessionMessage): SessionMessage {
return when (message) {
is SessionData -> message.copy(recipientSessionId = 0)
is SessionInit -> message.copy(initiatorSessionId = 0)
is SessionConfirm -> message.copy(initiatorSessionId = 0, initiatedSessionId = 0)
is NormalSessionEnd -> message.copy(recipientSessionId = 0)
is ErrorSessionEnd -> message.copy(recipientSessionId = 0)
else -> message
}
private fun sanitise(message: SessionMessage) = when (message) {
is SessionData -> message.copy(recipientSessionId = 0)
is SessionInit -> message.copy(initiatorSessionId = 0)
is SessionConfirm -> message.copy(initiatorSessionId = 0, initiatedSessionId = 0)
is NormalSessionEnd -> message.copy(recipientSessionId = 0)
is ErrorSessionEnd -> message.copy(recipientSessionId = 0)
else -> message
}
private infix fun MockNode.sent(message: SessionMessage): Pair<Int, SessionMessage> = Pair(id, message)
@ -700,6 +703,7 @@ class StateMachineManagerTests {
}
@InitiatingFlow
private open class SendFlow(val payload: String, vararg val otherParties: Party) : FlowLogic<Unit>() {
init {
require(otherParties.isNotEmpty())
@ -712,6 +716,7 @@ class StateMachineManagerTests {
private interface CustomInterface
private class CustomSendFlow(payload: String, otherParty: Party) : CustomInterface, SendFlow(payload, otherParty)
@InitiatingFlow
private class ReceiveFlow(vararg val otherParties: Party) : FlowLogic<Unit>() {
object START_STEP : ProgressTracker.Step("Starting")
object RECEIVED_STEP : ProgressTracker.Step("Received")
@ -740,6 +745,18 @@ class StateMachineManagerTests {
}
}
@InitiatingFlow
private class SendAndReceiveFlow(val otherParty: Party, val payload: Any) : FlowLogic<Any>() {
@Suspendable
override fun call(): Any = sendAndReceive<Any>(otherParty, payload).unwrap { it }
}
private class InlinedSendFlow(val payload: String, val otherParty: Party) : FlowLogic<Unit>() {
@Suspendable
override fun call() = send(otherParty, payload)
}
@InitiatingFlow
private class PingPongFlow(val otherParty: Party, val payload: Long) : FlowLogic<Unit>() {
@Transient var receivedPayload: Long? = null
@Transient var receivedPayload2: Long? = null
@ -770,6 +787,7 @@ class StateMachineManagerTests {
}
private object WaitingFlows {
@InitiatingFlow
class Waiter(val stx: SignedTransaction, val otherParty: Party) : FlowLogic<SignedTransaction>() {
@Suspendable
override fun call(): SignedTransaction {
@ -788,11 +806,32 @@ class StateMachineManagerTests {
}
}
private class VaultAccessFlow(val otherParty: Party) : FlowLogic<Unit>() {
private class VaultAccessFlow : FlowLogic<Unit>() {
@Suspendable
override fun call() {
serviceHub.vaultService.unconsumedStates<Cash.State>().filter { true }
send(otherParty, "Hello")
waitForLedgerCommit(SecureHash.zeroHash)
}
}
@InitiatingFlow(version = 2)
private class UpgradedFlow(val otherParty: Party) : FlowLogic<Any>() {
@Suspendable
override fun call(): Any = receive<Any>(otherParty).unwrap { it }
}
private class SingleInlinedSubFlow(val otherParty: Party) : FlowLogic<Unit>() {
@Suspendable
override fun call() {
val payload = receive<String>(otherParty).unwrap { it }
subFlow(InlinedSendFlow(payload + payload, otherParty))
}
}
private class DoubleInlinedSubFlow(val otherParty: Party) : FlowLogic<Unit>() {
@Suspendable
override fun call() {
subFlow(SingleInlinedSubFlow(otherParty))
}
}

View File

@ -4,6 +4,7 @@ import co.paralleluniverse.fibers.Suspendable
import net.corda.core.contracts.DealState
import net.corda.core.crypto.AbstractParty
import net.corda.core.flows.FlowLogic
import net.corda.core.flows.InitiatingFlow
import net.corda.core.node.CordaPluginRegistry
import net.corda.core.node.PluginServiceHub
import net.corda.core.serialization.SingletonSerializeAsToken
@ -31,10 +32,11 @@ object AutoOfferFlow {
class Service(services: PluginServiceHub) : SingletonSerializeAsToken() {
init {
services.registerServiceFlow(Instigator::class.java) { Acceptor(it) }
services.registerServiceFlow(Requester::class.java) { Acceptor(it) }
}
}
@InitiatingFlow
class Requester(val dealToBeOffered: DealState) : FlowLogic<SignedTransaction>() {
companion object {
@ -74,14 +76,7 @@ object AutoOfferFlow {
}
private fun <T : AbstractParty> notUs(parties: List<T>): List<T> {
val notUsParties: MutableList<T> = arrayListOf()
for (party in parties) {
if (serviceHub.myInfo.legalIdentity != party) {
notUsParties.add(party)
}
}
return notUsParties
return parties.filter { serviceHub.myInfo.legalIdentity != it }
}
}
}

View File

@ -7,6 +7,7 @@ import net.corda.core.crypto.Party
import net.corda.core.crypto.keys
import net.corda.core.crypto.toBase58String
import net.corda.core.flows.FlowLogic
import net.corda.core.flows.InitiatingFlow
import net.corda.core.node.NodeInfo
import net.corda.core.node.PluginServiceHub
import net.corda.core.node.services.ServiceType
@ -24,7 +25,7 @@ object FixingFlow {
class Service(services: PluginServiceHub) {
init {
services.registerServiceFlow(Floater::class.java) { Fixer(it) }
services.registerServiceFlow(FixingRoleDecider::class.java) { Fixer(it) }
}
}
@ -114,12 +115,13 @@ object FixingFlow {
override val myKeyPair: KeyPair get() {
val myPublicKey = serviceHub.myInfo.legalIdentity.owningKey
val myKeys = dealToFix.state.data.parties.filter { it.owningKey == myPublicKey }.single().owningKey.keys
val myKeys = dealToFix.state.data.parties.single { it.owningKey == myPublicKey }.owningKey.keys
return serviceHub.keyManagementService.toKeyPair(myKeys)
}
override val notaryNode: NodeInfo get() =
serviceHub.networkMapCache.notaryNodes.filter { it.notaryIdentity == dealToFix.state.notary }.single()
override val notaryNode: NodeInfo get() {
return serviceHub.networkMapCache.notaryNodes.single { it.notaryIdentity == dealToFix.state.notary }
}
}
@ -131,11 +133,9 @@ object FixingFlow {
* This flow looks at the deal and decides whether to be the Fixer or Floater role in agreeing a fixing.
*
* It is kicked off as an activity on both participant nodes by the scheduler when it's time for a fixing. If the
* Fixer role is chosen, then that will be initiated by the [FixingSession] message sent from the other party and
* handled by the [FixingSessionInitiationHandler].
*
* TODO: Replace [FixingSession] and [FixingSessionInitiationHandler] with generic session initiation logic once it exists.
* Fixer role is chosen, then that will be initiated by the [FixingSession] message sent from the other party.
*/
@InitiatingFlow
class FixingRoleDecider(val ref: StateRef, override val progressTracker: ProgressTracker) : FlowLogic<Unit>() {
@Suppress("unused") // Used via reflection.
constructor(ref: StateRef) : this(ref, tracker())

View File

@ -6,6 +6,7 @@ import net.corda.core.contracts.FixOf
import net.corda.core.crypto.DigitalSignature
import net.corda.core.crypto.Party
import net.corda.core.flows.FlowLogic
import net.corda.core.flows.InitiatingFlow
import net.corda.core.serialization.CordaSerializable
import net.corda.core.transactions.FilteredTransaction
import net.corda.core.transactions.TransactionBuilder
@ -93,6 +94,7 @@ open class RatesFixFlow(protected val tx: TransactionBuilder,
}
// DOCSTART 1
@InitiatingFlow
class FixQueryFlow(val fixOf: FixOf, val oracle: Party) : FlowLogic<Fix>() {
@Suspendable
override fun call(): Fix {
@ -109,6 +111,7 @@ open class RatesFixFlow(protected val tx: TransactionBuilder,
}
}
@InitiatingFlow
class FixSignFlow(val tx: TransactionBuilder, val oracle: Party,
val partialMerkleTx: FilteredTransaction) : FlowLogic<DigitalSignature.LegallyIdentifiable>() {
@Suspendable

View File

@ -3,6 +3,7 @@ package net.corda.irs.flows
import co.paralleluniverse.fibers.Suspendable
import net.corda.core.crypto.Party
import net.corda.core.flows.FlowLogic
import net.corda.core.flows.InitiatingFlow
import net.corda.core.node.CordaPluginRegistry
import net.corda.core.node.NodeInfo
import net.corda.core.node.PluginServiceHub
@ -42,6 +43,7 @@ object UpdateBusinessDayFlow {
}
@InitiatingFlow
class Broadcast(val date: LocalDate, override val progressTracker: ProgressTracker) : FlowLogic<Unit>() {
constructor(date: LocalDate) : this(date, tracker())

View File

@ -1,5 +1,6 @@
package net.corda.simulation
import co.paralleluniverse.fibers.Suspendable
import com.fasterxml.jackson.module.kotlin.readValue
import com.google.common.util.concurrent.FutureCallback
import com.google.common.util.concurrent.Futures
@ -9,8 +10,11 @@ import net.corda.core.RunOnCallerThread
import net.corda.core.contracts.StateAndRef
import net.corda.core.contracts.UniqueIdentifier
import net.corda.core.crypto.AnonymousParty
import net.corda.core.crypto.Party
import net.corda.core.flatMap
import net.corda.core.flows.FlowLogic
import net.corda.core.flows.FlowStateMachine
import net.corda.core.flows.InitiatingFlow
import net.corda.core.map
import net.corda.core.node.services.linearHeadsOfType
import net.corda.core.success
@ -24,6 +28,7 @@ import net.corda.node.utilities.transaction
import net.corda.testing.initiateSingleShotFlow
import net.corda.testing.node.InMemoryMessagingNetwork
import net.corda.testing.node.MockIdentityService
import java.security.KeyPair
import java.time.LocalDate
import java.util.*
@ -120,16 +125,27 @@ class IRSSimulation(networkSendManuallyPumped: Boolean, runAsync: Boolean, laten
irs.fixedLeg.fixedRatePayer = node1.info.legalIdentity.toAnonymous()
irs.floatingLeg.floatingRatePayer = node2.info.legalIdentity.toAnonymous()
@InitiatingFlow
class StartDealFlow(val otherParty: Party,
val payload: AutoOffer,
val myKeyPair: KeyPair) : FlowLogic<SignedTransaction>() {
@Suspendable
override fun call(): SignedTransaction = subFlow(Instigator(otherParty, payload, myKeyPair))
}
@Suppress("UNCHECKED_CAST")
val acceptorTx = node2.initiateSingleShotFlow(Instigator::class) { Acceptor(it) }.flatMap {
val acceptorTx = node2.initiateSingleShotFlow(StartDealFlow::class) { Acceptor(it) }.flatMap {
(it.stateMachine as FlowStateMachine<SignedTransaction>).resultFuture
}
showProgressFor(listOf(node1, node2))
showConsensusFor(listOf(node1, node2, regulators[0]))
val instigator = Instigator(node2.info.legalIdentity, AutoOffer(notary.info.notaryIdentity, irs), node1.services.legalIdentityKey)
val instigatorTx: ListenableFuture<SignedTransaction> = node1.services.startFlow(instigator).resultFuture
val instigator = StartDealFlow(
node2.info.legalIdentity,
AutoOffer(notary.info.notaryIdentity, irs),
node1.services.legalIdentityKey)
val instigatorTx = node1.services.startFlow(instigator).resultFuture
return Futures.allAsList(instigatorTx, acceptorTx).flatMap { instigatorTx }
}

View File

@ -36,8 +36,11 @@ class TradeSimulation(runAsync: Boolean, latencyInjector: InMemoryMessagingNetwo
buyer.services.fillWithSomeTestCash(1500.DOLLARS, notary.info.notaryIdentity)
val issuance = run {
val tx = CommercialPaper().generateIssue(seller.info.legalIdentity.ref(1, 2, 3), 1100.DOLLARS `issued by` DUMMY_CASH_ISSUER,
Instant.now() + 10.days, notary.info.notaryIdentity)
val tx = CommercialPaper().generateIssue(
seller.info.legalIdentity.ref(1, 2, 3),
1100.DOLLARS `issued by` DUMMY_CASH_ISSUER,
Instant.now() + 10.days,
notary.info.notaryIdentity)
tx.setTime(Instant.now(), 30.seconds)
val notaryKey = notary.services.notaryIdentityKey
val sellerKey = seller.services.legalIdentityKey

View File

@ -3,6 +3,7 @@ package net.corda.vega.flows
import co.paralleluniverse.fibers.Suspendable
import net.corda.core.crypto.Party
import net.corda.core.flows.FlowLogic
import net.corda.core.flows.InitiatingFlow
import net.corda.core.node.PluginServiceHub
import net.corda.core.serialization.CordaSerializable
import net.corda.core.transactions.SignedTransaction
@ -13,11 +14,17 @@ import net.corda.vega.contracts.OGTrade
import net.corda.vega.contracts.SwapData
object IRSTradeFlow {
class Service(services: PluginServiceHub) {
init {
services.registerServiceFlow(Requester::class.java, ::Receiver)
}
}
@CordaSerializable
data class OfferMessage(val notary: Party, val dealBeingOffered: IRSState)
@InitiatingFlow
class Requester(val swap: SwapData, val otherParty: Party) : FlowLogic<SignedTransaction>() {
@Suspendable
override fun call(): SignedTransaction {
require(serviceHub.networkMapCache.notaryNodes.isNotEmpty()) { "No notary nodes registered" }
@ -38,18 +45,12 @@ object IRSTradeFlow {
return subFlow(TwoPartyDealFlow.Instigator(
otherParty,
TwoPartyDealFlow.AutoOffer(notary, offer),
serviceHub.legalIdentityKey), shareParentSessions = true)
serviceHub.legalIdentityKey))
}
}
class Service(services: PluginServiceHub) {
init {
services.registerServiceFlow(Requester::class.java, ::Receiver)
}
}
class Receiver(private val replyToParty: Party) : FlowLogic<Unit>() {
@Suspendable
override fun call() {
logger.info("IRSTradeFlow receiver started")
@ -59,7 +60,7 @@ object IRSTradeFlow {
// Automatically agree - in reality we'd vet the offer message
require(serviceHub.networkMapCache.notaryNodes.map { it.notaryIdentity }.contains(offer.notary))
send(replyToParty, true)
subFlow(TwoPartyDealFlow.Acceptor(replyToParty), shareParentSessions = true)
subFlow(TwoPartyDealFlow.Acceptor(replyToParty))
}
}
}

View File

@ -3,9 +3,7 @@ package net.corda.vega.flows
import co.paralleluniverse.fibers.Suspendable
import com.opengamma.strata.basics.ReferenceData
import com.opengamma.strata.basics.currency.Currency
import com.opengamma.strata.basics.currency.MultiCurrencyAmount
import com.opengamma.strata.data.MarketDataFxRateProvider
import com.opengamma.strata.market.param.CurrencyParameterSensitivities
import com.opengamma.strata.pricer.curve.CalibrationMeasures
import com.opengamma.strata.pricer.curve.CurveCalibrator
import com.opengamma.strata.pricer.rate.ImmutableRatesProvider
@ -15,6 +13,7 @@ import net.corda.core.contracts.StateRef
import net.corda.core.crypto.AnonymousParty
import net.corda.core.crypto.Party
import net.corda.core.flows.FlowLogic
import net.corda.core.flows.InitiatingFlow
import net.corda.core.node.PluginServiceHub
import net.corda.core.node.services.dealsWith
import net.corda.core.serialization.CordaSerializable
@ -51,6 +50,7 @@ object SimmFlow {
* Initiates with the other party by sending a portfolio to agree on and then comes to consensus over initial
* margin using SIMM. If there is an existing state it will update and revalue the portfolio agreement.
*/
@InitiatingFlow
class Requester(val otherParty: Party,
val valuationDate: LocalDate,
val existing: StateAndRef<PortfolioState>?)
@ -62,7 +62,7 @@ object SimmFlow {
@Suspendable
override fun call(): RevisionedState<PortfolioState.Update> {
logger.debug("Calling from: ${serviceHub.myInfo.legalIdentity}. Sending to: ${otherParty}")
logger.debug("Calling from: ${serviceHub.myInfo.legalIdentity}. Sending to: $otherParty")
require(serviceHub.networkMapCache.notaryNodes.isNotEmpty()) { "No notary nodes registered" }
notary = serviceHub.networkMapCache.notaryNodes.first().notaryIdentity
myIdentity = serviceHub.myInfo.legalIdentity
@ -88,7 +88,7 @@ object SimmFlow {
send(otherParty, OfferMessage(notary, portfolioState, existing?.ref, valuationDate))
logger.info("Awaiting two party deal acceptor")
subFlow(TwoPartyDealFlow.Acceptor(otherParty), shareParentSessions = true)
subFlow(TwoPartyDealFlow.Acceptor(otherParty))
}
@Suspendable
@ -97,7 +97,7 @@ object SimmFlow {
sendAndReceive<Ack>(otherParty, OfferMessage(notary, stateAndRef.state.data, existing?.ref, valuationDate))
logger.info("Updating portfolio")
val update = PortfolioState.Update(portfolio = portfolio.refs)
subFlow(StateRevisionFlow.Requester(stateAndRef, update), shareParentSessions = true)
subFlow(StateRevisionFlow.Requester(stateAndRef, update))
}
@Suspendable
@ -109,7 +109,7 @@ object SimmFlow {
require(valuer != null) { "Valuer party must be known to this node" }
val valuation = agreeValuation(portfolio, valuationDate, valuer!!)
val update = PortfolioState.Update(valuation = valuation)
return subFlow(StateRevisionFlow.Requester(stateRef, update), shareParentSessions = true).state.data
return subFlow(StateRevisionFlow.Requester(stateRef, update)).state.data
}
@Suspendable
@ -143,10 +143,10 @@ object SimmFlow {
val imBatch = analyticsEngine.calculateMarginBatch(sensBatch, combinedRatesProvider, fxRateProvider, cordaMargin)
val cordaIMMap = imBatch.map { it.key.info.id.get().value to it.value.toCordaCompatible() }.toMap()
require(agree<CordaMarketData>(cordaMarketData))
require(agree<CurrencyParameterSensitivities>(sensitivities.first.toCordaCompatible()))
require(agree<MultiCurrencyAmount>(sensitivities.second.toCordaCompatible()))
require(agree<InitialMarginTriple>(cordaMargin))
require(agree(cordaMarketData))
require(agree(sensitivities.first.toCordaCompatible()))
require(agree(sensitivities.second.toCordaCompatible()))
require(agree(cordaMargin))
return PortfolioValuation(
portfolio.trades.size,
@ -214,7 +214,7 @@ object SimmFlow {
@Suspendable
private fun agree(data: Any): Boolean {
send(replyToParty, data)
return receive<Boolean>(replyToParty).unwrap { it == true }
return receive<Boolean>(replyToParty).unwrap { it }
}
@Suspendable
@ -306,7 +306,7 @@ object SimmFlow {
TwoPartyDealFlow.AutoOffer(offer.notary, offer.dealBeingOffered),
serviceHub.legalIdentityKey)
logger.info("Starting two party deal initiator with: ${replyToParty.name}")
return subFlow(seller, shareParentSessions = true)
return subFlow(seller)
}
@Suspendable
@ -318,7 +318,7 @@ object SimmFlow {
super.verifyProposal(proposal)
if (proposal.modification.portfolio != portfolio.refs) throw StateReplacementException()
}
}, shareParentSessions = true)
})
}
@Suspendable
@ -331,7 +331,7 @@ object SimmFlow {
super.verifyProposal(proposal)
if (proposal.modification.valuation != valuation) throw StateReplacementException()
}
}, shareParentSessions = true)
})
}
}
}

View File

@ -49,7 +49,7 @@ class BuyerFlow(val otherParty: Party,
CommercialPaper.State::class.java)
// This invokes the trading flow and out pops our finished transaction.
val tradeTX: SignedTransaction = subFlow(buyer, shareParentSessions = true)
val tradeTX: SignedTransaction = subFlow(buyer)
// TODO: This should be moved into the flow itself.
serviceHub.recordTransactions(listOf(tradeTX))

View File

@ -9,10 +9,10 @@ import net.corda.core.crypto.SecureHash
import net.corda.core.crypto.generateKeyPair
import net.corda.core.days
import net.corda.core.flows.FlowLogic
import net.corda.core.flows.InitiatingFlow
import net.corda.core.node.NodeInfo
import net.corda.core.seconds
import net.corda.core.transactions.SignedTransaction
import net.corda.core.utilities.DUMMY_BANK_C
import net.corda.core.utilities.ProgressTracker
import net.corda.flows.NotaryFlow
import net.corda.flows.TwoPartyTradeFlow
@ -21,6 +21,7 @@ import java.security.PublicKey
import java.time.Instant
import java.util.*
@InitiatingFlow
class SellerFlow(val otherParty: Party,
val amount: Amount<Currency>,
override val progressTracker: ProgressTracker) : FlowLogic<SignedTransaction>() {
@ -60,7 +61,7 @@ class SellerFlow(val otherParty: Party,
amount,
cpOwnerKey,
progressTracker.getChildProgressTracker(TRADING)!!)
return subFlow(seller, shareParentSessions = true)
return subFlow(seller)
}
@Suspendable