mirror of
https://github.com/corda/corda.git
synced 2024-12-18 20:47:57 +00:00
Rename protocol to flow.
This commit is contained in:
parent
d8e09f7174
commit
f68529d1fd
@ -7,24 +7,24 @@ import net.corda.core.contracts.Amount
|
||||
import net.corda.core.contracts.Issued
|
||||
import net.corda.core.contracts.PartyAndReference
|
||||
import net.corda.core.contracts.USD
|
||||
import net.corda.core.flows.StateMachineRunId
|
||||
import net.corda.core.node.NodeInfo
|
||||
import net.corda.core.node.services.NetworkMapCache
|
||||
import net.corda.core.node.services.ServiceInfo
|
||||
import net.corda.core.node.services.StateMachineTransactionMapping
|
||||
import net.corda.core.node.services.Vault
|
||||
import net.corda.core.protocols.StateMachineRunId
|
||||
import net.corda.core.serialization.OpaqueBytes
|
||||
import net.corda.core.transactions.SignedTransaction
|
||||
import net.corda.flows.CashCommand
|
||||
import net.corda.flows.CashFlow
|
||||
import net.corda.node.driver.driver
|
||||
import net.corda.node.services.User
|
||||
import net.corda.node.services.config.configureTestSSL
|
||||
import net.corda.node.services.messaging.ArtemisMessagingComponent
|
||||
import net.corda.node.services.messaging.StateMachineUpdate
|
||||
import net.corda.node.services.network.NetworkMapService
|
||||
import net.corda.node.services.startProtocolPermission
|
||||
import net.corda.node.services.startFlowPermission
|
||||
import net.corda.node.services.transactions.SimpleNotaryService
|
||||
import net.corda.protocols.CashCommand
|
||||
import net.corda.protocols.CashProtocol
|
||||
import net.corda.testing.expect
|
||||
import net.corda.testing.expectEvents
|
||||
import net.corda.testing.sequence
|
||||
@ -56,7 +56,7 @@ class NodeMonitorModelTest {
|
||||
val driverStarted = CountDownLatch(1)
|
||||
driverThread = thread {
|
||||
driver {
|
||||
val cashUser = User("user1", "test", permissions = setOf(startProtocolPermission<CashProtocol>()))
|
||||
val cashUser = User("user1", "test", permissions = setOf(startFlowPermission<CashFlow>()))
|
||||
val aliceNodeFuture = startNode("Alice", rpcUsers = listOf(cashUser))
|
||||
val notaryNodeFuture = startNode("Notary", advertisedServices = setOf(ServiceInfo(SimpleNotaryService.type)))
|
||||
|
||||
|
@ -5,7 +5,7 @@ import net.corda.core.contracts.*
|
||||
import net.corda.core.crypto.Party
|
||||
import net.corda.core.serialization.OpaqueBytes
|
||||
import net.corda.core.transactions.TransactionBuilder
|
||||
import net.corda.protocols.CashCommand
|
||||
import net.corda.flows.CashCommand
|
||||
|
||||
/**
|
||||
* [Generator]s for incoming/outgoing events to/from the [WalletMonitorService]. Internally it keeps track of owned
|
||||
|
@ -1,18 +1,18 @@
|
||||
package net.corda.client.model
|
||||
|
||||
import javafx.beans.property.SimpleObjectProperty
|
||||
import javafx.beans.value.ObservableValue
|
||||
import javafx.collections.ObservableList
|
||||
import javafx.collections.ObservableMap
|
||||
import net.corda.client.fxutils.*
|
||||
import net.corda.core.contracts.ContractState
|
||||
import net.corda.core.contracts.StateAndRef
|
||||
import net.corda.core.contracts.StateRef
|
||||
import net.corda.core.crypto.SecureHash
|
||||
import net.corda.core.flows.StateMachineRunId
|
||||
import net.corda.core.node.services.StateMachineTransactionMapping
|
||||
import net.corda.core.protocols.StateMachineRunId
|
||||
import net.corda.core.transactions.SignedTransaction
|
||||
import net.corda.node.services.messaging.StateMachineUpdate
|
||||
import javafx.beans.property.SimpleObjectProperty
|
||||
import javafx.beans.value.ObservableValue
|
||||
import javafx.collections.ObservableList
|
||||
import javafx.collections.ObservableMap
|
||||
import org.fxmisc.easybind.EasyBind
|
||||
|
||||
data class GatheredTransactionData(
|
||||
@ -59,7 +59,7 @@ sealed class TransactionCreateStatus(val message: String?) {
|
||||
override fun toString(): String = message ?: javaClass.simpleName
|
||||
}
|
||||
|
||||
data class ProtocolStatus(
|
||||
data class FlowStatus(
|
||||
val status: String
|
||||
)
|
||||
|
||||
@ -71,12 +71,12 @@ sealed class StateMachineStatus(val stateMachineName: String) {
|
||||
|
||||
data class StateMachineData(
|
||||
val id: StateMachineRunId,
|
||||
val protocolStatus: ObservableValue<ProtocolStatus?>,
|
||||
val flowStatus: ObservableValue<FlowStatus?>,
|
||||
val stateMachineStatus: ObservableValue<StateMachineStatus>
|
||||
)
|
||||
|
||||
/**
|
||||
* This model provides an observable list of transactions and what state machines/protocols recorded them
|
||||
* This model provides an observable list of transactions and what state machines/flows recorded them
|
||||
*/
|
||||
class GatheredTransactionDataModel {
|
||||
|
||||
@ -92,7 +92,7 @@ class GatheredTransactionDataModel {
|
||||
when (update) {
|
||||
is StateMachineUpdate.Added -> {
|
||||
val added: SimpleObjectProperty<StateMachineStatus> =
|
||||
SimpleObjectProperty(StateMachineStatus.Added(update.stateMachineInfo.protocolLogicClassName))
|
||||
SimpleObjectProperty(StateMachineStatus.Added(update.stateMachineInfo.flowLogicClassName))
|
||||
map[update.id] = added
|
||||
}
|
||||
is StateMachineUpdate.Removed -> {
|
||||
@ -103,7 +103,7 @@ class GatheredTransactionDataModel {
|
||||
}
|
||||
}
|
||||
private val stateMachineDataList = LeftOuterJoinedMap(stateMachineStatus, progressEvents) { id, status, progress ->
|
||||
StateMachineData(id, progress.map { it?.let { ProtocolStatus(it.message) } }, status)
|
||||
StateMachineData(id, progress.map { it?.let { FlowStatus(it.message) } }, status)
|
||||
}.getObservableValues()
|
||||
private val stateMachineDataMap = stateMachineDataList.associateBy(StateMachineData::id)
|
||||
private val smTxMappingList = stateMachineTransactionMapping.recordInSequence()
|
||||
|
@ -3,18 +3,18 @@ package net.corda.client.model
|
||||
import com.google.common.net.HostAndPort
|
||||
import javafx.beans.property.SimpleObjectProperty
|
||||
import net.corda.client.CordaRPCClient
|
||||
import net.corda.core.flows.StateMachineRunId
|
||||
import net.corda.core.node.services.NetworkMapCache
|
||||
import net.corda.core.node.services.StateMachineTransactionMapping
|
||||
import net.corda.core.node.services.Vault
|
||||
import net.corda.core.protocols.StateMachineRunId
|
||||
import net.corda.core.transactions.SignedTransaction
|
||||
import net.corda.flows.CashCommand
|
||||
import net.corda.flows.CashFlow
|
||||
import net.corda.node.services.config.NodeSSLConfiguration
|
||||
import net.corda.node.services.messaging.CordaRPCOps
|
||||
import net.corda.node.services.messaging.StateMachineInfo
|
||||
import net.corda.node.services.messaging.StateMachineUpdate
|
||||
import net.corda.node.services.messaging.startProtocol
|
||||
import net.corda.protocols.CashCommand
|
||||
import net.corda.protocols.CashProtocol
|
||||
import net.corda.node.services.messaging.startFlow
|
||||
import rx.Observable
|
||||
import rx.subjects.PublishSubject
|
||||
|
||||
@ -63,7 +63,7 @@ class NodeMonitorModel {
|
||||
val proxy = client.proxy()
|
||||
|
||||
val (stateMachines, stateMachineUpdates) = proxy.stateMachinesAndUpdates()
|
||||
// Extract the protocol tracking stream
|
||||
// Extract the flow tracking stream
|
||||
// TODO is there a nicer way of doing this? Stream of streams in general results in code like this...
|
||||
val currentProgressTrackerUpdates = stateMachines.mapNotNull { stateMachine ->
|
||||
ProgressTrackingEvent.createStreamFromStateMachineInfo(stateMachine)
|
||||
@ -100,7 +100,7 @@ class NodeMonitorModel {
|
||||
|
||||
// Client -> Service
|
||||
clientToServiceSource.subscribe {
|
||||
proxy.startProtocol(::CashProtocol, it)
|
||||
proxy.startFlow(::CashFlow, it)
|
||||
}
|
||||
proxyObservable.set(proxy)
|
||||
}
|
||||
|
@ -4,9 +4,9 @@ import net.corda.core.contracts.clauses.Clause
|
||||
import net.corda.core.crypto.CompositeKey
|
||||
import net.corda.core.crypto.Party
|
||||
import net.corda.core.crypto.SecureHash
|
||||
import net.corda.core.flows.FlowLogicRef
|
||||
import net.corda.core.flows.FlowLogicRefFactory
|
||||
import net.corda.core.node.services.ServiceType
|
||||
import net.corda.core.protocols.ProtocolLogicRef
|
||||
import net.corda.core.protocols.ProtocolLogicRefFactory
|
||||
import net.corda.core.serialization.OpaqueBytes
|
||||
import net.corda.core.serialization.serialize
|
||||
import net.corda.core.transactions.TransactionBuilder
|
||||
@ -206,16 +206,16 @@ data class ScheduledStateRef(val ref: StateRef, override val scheduledAt: Instan
|
||||
|
||||
/**
|
||||
* This class represents the lifecycle activity that a contract state of type [LinearState] would like to perform at a given point in time.
|
||||
* e.g. run a fixing protocol.
|
||||
* e.g. run a fixing flow.
|
||||
*
|
||||
* Note the use of [ProtocolLogicRef] to represent a safe way to transport a [ProtocolLogic] out of the contract sandbox.
|
||||
* Note the use of [FlowLogicRef] to represent a safe way to transport a [FlowLogic] out of the contract sandbox.
|
||||
*
|
||||
* Currently we support only protocol based activities as we expect there to be a transaction generated off the back of
|
||||
* Currently we support only flow based activities as we expect there to be a transaction generated off the back of
|
||||
* the activity, otherwise we have to start tracking secondary state on the platform of which scheduled activities
|
||||
* for a particular [ContractState] have been processed/fired etc. If the activity is not "on ledger" then the
|
||||
* scheduled activity shouldn't be either.
|
||||
*/
|
||||
data class ScheduledActivity(val logicRef: ProtocolLogicRef, override val scheduledAt: Instant) : Scheduled
|
||||
data class ScheduledActivity(val logicRef: FlowLogicRef, override val scheduledAt: Instant) : Scheduled
|
||||
|
||||
/**
|
||||
* A state that evolves by superseding itself, all of which share the common "linearId".
|
||||
@ -261,16 +261,16 @@ interface SchedulableState : ContractState {
|
||||
* [ContractState], what that activity is and at what point in time it should be initiated.
|
||||
* This can be used to implement deadlines for payment or processing of financial instruments according to a schedule.
|
||||
*
|
||||
* The state has no reference to it's own StateRef, so supply that for use as input to any ProtocolLogic constructed.
|
||||
* The state has no reference to it's own StateRef, so supply that for use as input to any FlowLogic constructed.
|
||||
*
|
||||
* @return null if there is no activity to schedule.
|
||||
*/
|
||||
fun nextScheduledActivity(thisStateRef: StateRef, protocolLogicRefFactory: ProtocolLogicRefFactory): ScheduledActivity?
|
||||
fun nextScheduledActivity(thisStateRef: StateRef, flowLogicRefFactory: FlowLogicRefFactory): ScheduledActivity?
|
||||
}
|
||||
|
||||
/**
|
||||
* Interface representing an agreement that exposes various attributes that are common. Implementing it simplifies
|
||||
* implementation of general protocols that manipulate many agreement types.
|
||||
* implementation of general flows that manipulate many agreement types.
|
||||
*/
|
||||
interface DealState : LinearState {
|
||||
/** Human readable well known reference (e.g. trade reference) */
|
||||
@ -293,7 +293,7 @@ interface DealState : LinearState {
|
||||
|
||||
/**
|
||||
* Generate a partial transaction representing an agreement (command) to this deal, allowing a general
|
||||
* deal/agreement protocol to generate the necessary transaction for potential implementations.
|
||||
* deal/agreement flow to generate the necessary transaction for potential implementations.
|
||||
*
|
||||
* TODO: Currently this is the "inception" transaction but in future an offer of some description might be an input state ref
|
||||
*
|
||||
|
@ -1,4 +1,4 @@
|
||||
package net.corda.core.protocols
|
||||
package net.corda.core.flows
|
||||
|
||||
import co.paralleluniverse.fibers.Suspendable
|
||||
import net.corda.core.crypto.Party
|
||||
@ -9,9 +9,9 @@ import org.slf4j.Logger
|
||||
import rx.Observable
|
||||
|
||||
/**
|
||||
* A sub-class of [ProtocolLogic<T>] implements a protocol flow using direct, straight line blocking code. Thus you
|
||||
* can write complex protocol logic in an ordinary fashion, without having to think about callbacks, restarting after
|
||||
* a node crash, how many instances of your protocol there are running and so on.
|
||||
* A sub-class of [FlowLogic<T>] implements a flow using direct, straight line blocking code. Thus you
|
||||
* can write complex flow logic in an ordinary fashion, without having to think about callbacks, restarting after
|
||||
* a node crash, how many instances of your flow there are running and so on.
|
||||
*
|
||||
* Invoking the network will cause the call stack to be suspended onto the heap and then serialized to a database using
|
||||
* the Quasar fibers framework. Because of this, if you need access to data that might change over time, you should
|
||||
@ -19,32 +19,32 @@ import rx.Observable
|
||||
* service across calls to send/receive/sendAndReceive because the world might change in arbitrary ways out from
|
||||
* underneath you, for instance, if the node is restarted or reconfigured!
|
||||
*
|
||||
* Additionally, be aware of what data you pin either via the stack or in your [ProtocolLogic] implementation. Very large
|
||||
* Additionally, be aware of what data you pin either via the stack or in your [FlowLogic] implementation. Very large
|
||||
* objects or datasets will hurt performance by increasing the amount of data stored in each checkpoint.
|
||||
*
|
||||
* If you'd like to use another ProtocolLogic class as a component of your own, construct it on the fly and then pass
|
||||
* it to the [subProtocol] method. It will return the result of that protocol when it completes.
|
||||
* 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.
|
||||
*/
|
||||
abstract class ProtocolLogic<out T> {
|
||||
abstract class FlowLogic<out T> {
|
||||
|
||||
/** Reference to the [Fiber] instance that is the top level controller for the entire flow. */
|
||||
lateinit var psm: ProtocolStateMachine<*>
|
||||
lateinit var fsm: FlowStateMachine<*>
|
||||
|
||||
/** This is where you should log things to. */
|
||||
val logger: Logger get() = psm.logger
|
||||
val logger: Logger get() = fsm.logger
|
||||
|
||||
/**
|
||||
* Provides access to big, heavy classes that may be reconstructed from time to time, e.g. across restarts. It is
|
||||
* only available once the protocol has started, which means it cannnot be accessed in the constructor. Either
|
||||
* only available once the flow has started, which means it cannnot be accessed in the constructor. Either
|
||||
* access this lazily or from inside [call].
|
||||
*/
|
||||
val serviceHub: ServiceHub get() = psm.serviceHub
|
||||
val serviceHub: ServiceHub get() = fsm.serviceHub
|
||||
|
||||
private var sessionProtocol: ProtocolLogic<*> = this
|
||||
private var sessionFlow: FlowLogic<*> = this
|
||||
|
||||
/**
|
||||
* Return the marker [Class] which [party] has used to register the counterparty protocol that is to execute on the
|
||||
* other side. The default implementation returns the class object of this ProtocolLogic, but any [Class] instance
|
||||
* Return the marker [Class] which [party] has used to register the counterparty flow that is to execute on the
|
||||
* other side. The default implementation returns the class object of this FlowLogic, but any [Class] instance
|
||||
* will do as long as the other side registers with it.
|
||||
*/
|
||||
open fun getCounterpartyMarker(party: Party): Class<*> = javaClass
|
||||
@ -56,44 +56,44 @@ abstract class ProtocolLogic<out T> {
|
||||
|
||||
@Suspendable
|
||||
fun <T : Any> sendAndReceive(otherParty: Party, payload: Any, receiveType: Class<T>): UntrustworthyData<T> {
|
||||
return psm.sendAndReceive(otherParty, payload, receiveType, sessionProtocol)
|
||||
return fsm.sendAndReceive(otherParty, payload, receiveType, sessionFlow)
|
||||
}
|
||||
|
||||
inline fun <reified T : Any> receive(otherParty: Party): UntrustworthyData<T> = receive(otherParty, T::class.java)
|
||||
|
||||
@Suspendable
|
||||
fun <T : Any> receive(otherParty: Party, receiveType: Class<T>): UntrustworthyData<T> {
|
||||
return psm.receive(otherParty, receiveType, sessionProtocol)
|
||||
return fsm.receive(otherParty, receiveType, sessionFlow)
|
||||
}
|
||||
|
||||
@Suspendable
|
||||
fun send(otherParty: Party, payload: Any) {
|
||||
psm.send(otherParty, payload, sessionProtocol)
|
||||
fsm.send(otherParty, payload, sessionFlow)
|
||||
}
|
||||
|
||||
/**
|
||||
* Invokes the given subprotocol by simply passing through this [ProtocolLogic]s reference to the
|
||||
* [ProtocolStateMachine] and then calling the [call] method.
|
||||
* @param shareParentSessions In certain situations the need arises to use the same sessions the parent protocol has
|
||||
* already established. However this also prevents the subprotocol from creating new sessions with those parties.
|
||||
* Invokes the given subflow by simply passing through this [FlowLogic]s reference to the
|
||||
* [FlowStateMachine] and then calling the [call] method.
|
||||
* @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.
|
||||
*/
|
||||
// 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
|
||||
fun <R> subProtocol(subLogic: ProtocolLogic<R>, shareParentSessions: Boolean = false): R {
|
||||
subLogic.psm = psm
|
||||
fun <R> subFlow(subLogic: FlowLogic<R>, shareParentSessions: Boolean = false): R {
|
||||
subLogic.fsm = fsm
|
||||
maybeWireUpProgressTracking(subLogic)
|
||||
if (shareParentSessions) {
|
||||
subLogic.sessionProtocol = this
|
||||
subLogic.sessionFlow = this
|
||||
}
|
||||
val result = subLogic.call()
|
||||
// It's easy to forget this when writing protocols so we just step it to the DONE state when it completes.
|
||||
// It's easy to forget this when writing flows so we just step it to the DONE state when it completes.
|
||||
subLogic.progressTracker?.currentStep = ProgressTracker.DONE
|
||||
return result
|
||||
}
|
||||
|
||||
private fun maybeWireUpProgressTracking(subLogic: ProtocolLogic<*>) {
|
||||
private fun maybeWireUpProgressTracking(subLogic: FlowLogic<*>) {
|
||||
val ours = progressTracker
|
||||
|
||||
val theirs = subLogic.progressTracker
|
||||
@ -108,11 +108,11 @@ abstract class ProtocolLogic<out T> {
|
||||
|
||||
/**
|
||||
* Override this to provide a [ProgressTracker]. If one is provided and stepped, the framework will do something
|
||||
* helpful with the progress reports. If this protocol is invoked as a sub-protocol of another, then the
|
||||
* tracker will be made a child of the current step in the parent. If it's null, this protocol doesn't track
|
||||
* helpful with the progress reports. If this flow is invoked as a sub-flow of another, then the
|
||||
* tracker will be made a child of the current step in the parent. If it's null, this flow doesn't track
|
||||
* progress.
|
||||
*
|
||||
* Note that this has to return a tracker before the protocol is invoked. You can't change your mind half way
|
||||
* Note that this has to return a tracker before the flow is invoked. You can't change your mind half way
|
||||
* through.
|
||||
*/
|
||||
open val progressTracker: ProgressTracker? = null
|
@ -1,13 +1,10 @@
|
||||
package net.corda.core.protocols
|
||||
package net.corda.core.flows
|
||||
|
||||
import com.google.common.primitives.Primitives
|
||||
import net.corda.core.contracts.StateRef
|
||||
import net.corda.core.crypto.SecureHash
|
||||
import net.corda.core.serialization.SingletonSerializeAsToken
|
||||
import net.corda.protocols.TwoPartyDealProtocol
|
||||
import java.lang.reflect.ParameterizedType
|
||||
import java.lang.reflect.Type
|
||||
import java.time.Duration
|
||||
import java.util.*
|
||||
import kotlin.reflect.KFunction
|
||||
import kotlin.reflect.KParameter
|
||||
@ -15,63 +12,63 @@ import kotlin.reflect.jvm.javaType
|
||||
import kotlin.reflect.primaryConstructor
|
||||
|
||||
/**
|
||||
* A class for conversion to and from [ProtocolLogic] and [ProtocolLogicRef] instances.
|
||||
* A class for conversion to and from [FlowLogic] and [FlowLogicRef] instances.
|
||||
*
|
||||
* Validation of types is performed on the way in and way out in case this object is passed between JVMs which might have differing
|
||||
* whitelists.
|
||||
*
|
||||
* TODO: Ways to populate whitelist of "blessed" protocols per node/party
|
||||
* TODO: Ways to populate whitelist of "blessed" flows per node/party
|
||||
* TODO: Ways to populate argument types whitelist. Per node/party or global?
|
||||
* TODO: Align with API related logic for passing in ProtocolLogic references (ProtocolRef)
|
||||
* TODO: Align with API related logic for passing in FlowLogic references (FlowRef)
|
||||
* TODO: Actual support for AppContext / AttachmentsClassLoader
|
||||
*/
|
||||
class ProtocolLogicRefFactory(private val protocolWhitelist: Map<String, Set<String>>) : SingletonSerializeAsToken() {
|
||||
class FlowLogicRefFactory(private val flowWhitelist: Map<String, Set<String>>) : SingletonSerializeAsToken() {
|
||||
|
||||
constructor() : this(mapOf())
|
||||
|
||||
// Pending real dependence on AppContext for class loading etc
|
||||
@Suppress("UNUSED_PARAMETER")
|
||||
private fun validateProtocolClassName(className: String, appContext: AppContext) {
|
||||
private fun validateFlowClassName(className: String, appContext: AppContext) {
|
||||
// TODO: make this specific to the attachments in the [AppContext] by including [SecureHash] in whitelist check
|
||||
require(protocolWhitelist.containsKey(className)) { "${ProtocolLogic::class.java.simpleName} of ${ProtocolLogicRef::class.java.simpleName} must have type on the whitelist: $className" }
|
||||
require(flowWhitelist.containsKey(className)) { "${FlowLogic::class.java.simpleName} of ${FlowLogicRef::class.java.simpleName} must have type on the whitelist: $className" }
|
||||
}
|
||||
|
||||
// Pending real dependence on AppContext for class loading etc
|
||||
@Suppress("UNUSED_PARAMETER")
|
||||
private fun validateArgClassName(className: String, argClassName: String, appContext: AppContext) {
|
||||
// TODO: consider more carefully what to whitelist and how to secure protocols
|
||||
// TODO: consider more carefully what to whitelist and how to secure flows
|
||||
// For now automatically accept standard java.lang.* and kotlin.* types.
|
||||
// All other types require manual specification at ProtocolLogicRefFactory construction time.
|
||||
// All other types require manual specification at FlowLogicRefFactory construction time.
|
||||
if (argClassName.startsWith("java.lang.") || argClassName.startsWith("kotlin.")) {
|
||||
return
|
||||
}
|
||||
// TODO: make this specific to the attachments in the [AppContext] by including [SecureHash] in whitelist check
|
||||
require(protocolWhitelist[className]!!.contains(argClassName)) { "Args to $className must have types on the args whitelist: $argClassName, but it has ${protocolWhitelist[className]}" }
|
||||
require(flowWhitelist[className]!!.contains(argClassName)) { "Args to $className must have types on the args whitelist: $argClassName, but it has ${flowWhitelist[className]}" }
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a [ProtocolLogicRef] for the Kotlin primary constructor of a named [ProtocolLogic]
|
||||
* Create a [FlowLogicRef] for the Kotlin primary constructor of a named [FlowLogic]
|
||||
*/
|
||||
fun createKotlin(protocolLogicClassName: String, args: Map<String,Any?>, attachments: List<SecureHash> = emptyList()): ProtocolLogicRef {
|
||||
fun createKotlin(flowLogicClassName: String, args: Map<String, Any?>, attachments: List<SecureHash> = emptyList()): FlowLogicRef {
|
||||
val context = AppContext(attachments)
|
||||
validateProtocolClassName(protocolLogicClassName, context)
|
||||
validateFlowClassName(flowLogicClassName, context)
|
||||
for(arg in args.values.filterNotNull()) {
|
||||
validateArgClassName(protocolLogicClassName, arg.javaClass.name, context)
|
||||
validateArgClassName(flowLogicClassName, arg.javaClass.name, context)
|
||||
}
|
||||
val clazz = Class.forName(protocolLogicClassName)
|
||||
require(ProtocolLogic::class.java.isAssignableFrom(clazz)) { "$protocolLogicClassName is not a ProtocolLogic" }
|
||||
val clazz = Class.forName(flowLogicClassName)
|
||||
require(FlowLogic::class.java.isAssignableFrom(clazz)) { "$flowLogicClassName is not a FlowLogic" }
|
||||
@Suppress("UNCHECKED_CAST")
|
||||
val logic = clazz as Class<ProtocolLogic<ProtocolLogic<*>>>
|
||||
val logic = clazz as Class<FlowLogic<FlowLogic<*>>>
|
||||
return createKotlin(logic, args)
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a [ProtocolLogicRef] for the Kotlin primary constructor or Java constructor and the given args.
|
||||
* Create a [FlowLogicRef] for the Kotlin primary constructor or Java constructor and the given args.
|
||||
*/
|
||||
fun create(type: Class<out ProtocolLogic<*>>, vararg args: Any?): ProtocolLogicRef {
|
||||
fun create(type: Class<out FlowLogic<*>>, vararg args: Any?): FlowLogicRef {
|
||||
val constructor = type.kotlin.primaryConstructor ?: return createJava(type, *args)
|
||||
if (constructor.parameters.size < args.size) {
|
||||
throw IllegalProtocolLogicException(type, "due to too many arguments supplied to kotlin primary constructor")
|
||||
throw IllegalFlowLogicException(type, "due to too many arguments supplied to kotlin primary constructor")
|
||||
}
|
||||
// Build map of args from array
|
||||
val argsMap = args.zip(constructor.parameters).map { Pair(it.second.name!!, it.first) }.toMap()
|
||||
@ -79,24 +76,24 @@ class ProtocolLogicRefFactory(private val protocolWhitelist: Map<String, Set<Str
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a [ProtocolLogicRef] by trying to find a Kotlin constructor that matches the given args.
|
||||
* Create a [FlowLogicRef] by trying to find a Kotlin constructor that matches the given args.
|
||||
*
|
||||
* TODO: Rethink language specific naming.
|
||||
*/
|
||||
fun createKotlin(type: Class<out ProtocolLogic<*>>, args: Map<String, Any?>): ProtocolLogicRef {
|
||||
fun createKotlin(type: Class<out FlowLogic<*>>, args: Map<String, Any?>): FlowLogicRef {
|
||||
// TODO: we need to capture something about the class loader or "application context" into the ref,
|
||||
// perhaps as some sort of ThreadLocal style object. For now, just create an empty one.
|
||||
val appContext = AppContext(emptyList())
|
||||
validateProtocolClassName(type.name, appContext)
|
||||
validateFlowClassName(type.name, appContext)
|
||||
// Check we can find a constructor and populate the args to it, but don't call it
|
||||
createConstructor(appContext, type, args)
|
||||
return ProtocolLogicRef(type.name, appContext, args)
|
||||
return FlowLogicRef(type.name, appContext, args)
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a [ProtocolLogicRef] by trying to find a Java constructor that matches the given args.
|
||||
* Create a [FlowLogicRef] by trying to find a Java constructor that matches the given args.
|
||||
*/
|
||||
private fun createJava(type: Class<out ProtocolLogic<*>>, vararg args: Any?): ProtocolLogicRef {
|
||||
private fun createJava(type: Class<out FlowLogic<*>>, vararg args: Any?): FlowLogicRef {
|
||||
// Build map for each
|
||||
val argsMap = HashMap<String, Any?>(args.size)
|
||||
var index = 0
|
||||
@ -104,22 +101,22 @@ class ProtocolLogicRefFactory(private val protocolWhitelist: Map<String, Set<Str
|
||||
return createKotlin(type, argsMap)
|
||||
}
|
||||
|
||||
fun toProtocolLogic(ref: ProtocolLogicRef): ProtocolLogic<*> {
|
||||
validateProtocolClassName(ref.protocolLogicClassName, ref.appContext)
|
||||
val klass = Class.forName(ref.protocolLogicClassName, true, ref.appContext.classLoader).asSubclass(ProtocolLogic::class.java)
|
||||
fun toFlowLogic(ref: FlowLogicRef): FlowLogic<*> {
|
||||
validateFlowClassName(ref.flowLogicClassName, ref.appContext)
|
||||
val klass = Class.forName(ref.flowLogicClassName, true, ref.appContext.classLoader).asSubclass(FlowLogic::class.java)
|
||||
return createConstructor(ref.appContext, klass, ref.args)()
|
||||
}
|
||||
|
||||
private fun createConstructor(appContext: AppContext, clazz: Class<out ProtocolLogic<*>>, args: Map<String, Any?>): () -> ProtocolLogic<*> {
|
||||
private fun createConstructor(appContext: AppContext, clazz: Class<out FlowLogic<*>>, args: Map<String, Any?>): () -> FlowLogic<*> {
|
||||
for (constructor in clazz.kotlin.constructors) {
|
||||
val params = buildParams(appContext, clazz, constructor, args) ?: continue
|
||||
// If we get here then we matched every parameter
|
||||
return { constructor.callBy(params) }
|
||||
}
|
||||
throw IllegalProtocolLogicException(clazz, "as could not find matching constructor for: $args")
|
||||
throw IllegalFlowLogicException(clazz, "as could not find matching constructor for: $args")
|
||||
}
|
||||
|
||||
private fun buildParams(appContext: AppContext, clazz: Class<out ProtocolLogic<*>>, constructor: KFunction<ProtocolLogic<*>>, args: Map<String, Any?>): HashMap<KParameter, Any?>? {
|
||||
private fun buildParams(appContext: AppContext, clazz: Class<out FlowLogic<*>>, constructor: KFunction<FlowLogic<*>>, args: Map<String, Any?>): HashMap<KParameter, Any?>? {
|
||||
val params = hashMapOf<KParameter, Any?>()
|
||||
val usedKeys = hashSetOf<String>()
|
||||
for (parameter in constructor.parameters) {
|
||||
@ -164,15 +161,15 @@ class ProtocolLogicRefFactory(private val protocolWhitelist: Map<String, Set<Str
|
||||
}
|
||||
}
|
||||
|
||||
class IllegalProtocolLogicException(type: Class<*>, msg: String) : IllegalArgumentException("${ProtocolLogicRef::class.java.simpleName} cannot be constructed for ${ProtocolLogic::class.java.simpleName} of type ${type.name} $msg")
|
||||
class IllegalFlowLogicException(type: Class<*>, msg: String) : IllegalArgumentException("${FlowLogicRef::class.java.simpleName} cannot be constructed for ${FlowLogic::class.java.simpleName} of type ${type.name} $msg")
|
||||
|
||||
/**
|
||||
* A class representing a [ProtocolLogic] instance which would be possible to safely pass out of the contract sandbox.
|
||||
* A class representing a [FlowLogic] instance which would be possible to safely pass out of the contract sandbox.
|
||||
*
|
||||
* Only allows a String reference to the ProtocolLogic class, and only allows restricted argument types as per [ProtocolLogicRefFactory].
|
||||
* Only allows a String reference to the FlowLogic class, and only allows restricted argument types as per [FlowLogicRefFactory].
|
||||
*/
|
||||
// TODO: align this with the existing [ProtocolRef] in the bank-side API (probably replace some of the API classes)
|
||||
data class ProtocolLogicRef internal constructor(val protocolLogicClassName: String, val appContext: AppContext, val args: Map<String, Any?>)
|
||||
// TODO: align this with the existing [FlowRef] in the bank-side API (probably replace some of the API classes)
|
||||
data class FlowLogicRef internal constructor(val flowLogicClassName: String, val appContext: AppContext, val args: Map<String, Any?>)
|
||||
|
||||
/**
|
||||
* This is just some way to track what attachments need to be in the class loader, but may later include some app
|
@ -1,4 +1,4 @@
|
||||
package net.corda.core.protocols
|
||||
package net.corda.core.flows
|
||||
|
||||
import co.paralleluniverse.fibers.Suspendable
|
||||
import com.google.common.util.concurrent.ListenableFuture
|
||||
@ -19,25 +19,25 @@ data class StateMachineRunId private constructor(val uuid: UUID) {
|
||||
}
|
||||
|
||||
/**
|
||||
* A ProtocolStateMachine instance is a suspendable fiber that delegates all actual logic to a [ProtocolLogic] instance.
|
||||
* For any given flow there is only one PSM, even if that protocol invokes subprotocols.
|
||||
* A FlowStateMachine instance is a suspendable fiber that delegates all actual logic to a [FlowLogic] instance.
|
||||
* For any given flow there is only one PSM, even if that flow invokes subflows.
|
||||
*
|
||||
* These classes are created by the [StateMachineManager] when a new protocol is started at the topmost level. If
|
||||
* a protocol invokes a sub-protocol, then it will pass along the PSM to the child. The call method of the topmost
|
||||
* These classes are created by the [StateMachineManager] when a new flow is started at the topmost level. If
|
||||
* a flow invokes a sub-flow, then it will pass along the PSM to the child. The call method of the topmost
|
||||
* logic element gets to return the value that the entire state machine resolves to.
|
||||
*/
|
||||
interface ProtocolStateMachine<R> {
|
||||
interface FlowStateMachine<R> {
|
||||
@Suspendable
|
||||
fun <T : Any> sendAndReceive(otherParty: Party,
|
||||
payload: Any,
|
||||
receiveType: Class<T>,
|
||||
sessionProtocol: ProtocolLogic<*>): UntrustworthyData<T>
|
||||
sessionFlow: FlowLogic<*>): UntrustworthyData<T>
|
||||
|
||||
@Suspendable
|
||||
fun <T : Any> receive(otherParty: Party, receiveType: Class<T>, sessionProtocol: ProtocolLogic<*>): UntrustworthyData<T>
|
||||
fun <T : Any> receive(otherParty: Party, receiveType: Class<T>, sessionFlow: FlowLogic<*>): UntrustworthyData<T>
|
||||
|
||||
@Suspendable
|
||||
fun send(otherParty: Party, payload: Any, sessionProtocol: ProtocolLogic<*>)
|
||||
fun send(otherParty: Party, payload: Any, sessionFlow: FlowLogic<*>)
|
||||
|
||||
val serviceHub: ServiceHub
|
||||
val logger: Logger
|
||||
@ -48,4 +48,4 @@ interface ProtocolStateMachine<R> {
|
||||
val resultFuture: ListenableFuture<R>
|
||||
}
|
||||
|
||||
class ProtocolSessionException(message: String) : Exception(message)
|
||||
class FlowSessionException(message: String) : Exception(message)
|
@ -66,7 +66,7 @@ interface MessagingService {
|
||||
* available via type casting. Once this function returns the message is queued for delivery but not necessarily
|
||||
* delivered: if the recipients are offline then the message could be queued hours or days later.
|
||||
*
|
||||
* There is no way to know if a message has been received. If your protocol requires this, you need the recipient
|
||||
* There is no way to know if a message has been received. If your flow requires this, you need the recipient
|
||||
* to send an ACK message back.
|
||||
*/
|
||||
fun send(message: Message, target: MessageRecipients)
|
||||
|
@ -22,19 +22,19 @@ abstract class CordaPluginRegistry {
|
||||
open val staticServeDirs: Map<String, String> = emptyMap()
|
||||
|
||||
/**
|
||||
* A Map with an entry for each consumed protocol used by the webAPIs.
|
||||
* The key of each map entry should contain the ProtocolLogic<T> class name.
|
||||
* The associated map values are the union of all concrete class names passed to the protocol constructor.
|
||||
* A Map with an entry for each consumed flow used by the webAPIs.
|
||||
* The key of each map entry should contain the FlowLogic<T> class name.
|
||||
* The associated map values are the union of all concrete class names passed to the flow constructor.
|
||||
* Standard java.lang.* and kotlin.* types do not need to be included explicitly.
|
||||
* This is used to extend the white listed protocols that can be initiated from the ServiceHub invokeProtocolAsync method.
|
||||
* This is used to extend the white listed flows that can be initiated from the ServiceHub invokeFlowAsync method.
|
||||
*/
|
||||
open val requiredProtocols: Map<String, Set<String>> = emptyMap()
|
||||
open val requiredFlows: Map<String, Set<String>> = emptyMap()
|
||||
|
||||
/**
|
||||
* List of additional long lived services to be hosted within the node.
|
||||
* They are expected to have a single parameter constructor that takes a [PluginServiceHub] as input.
|
||||
* The [PluginServiceHub] will be fully constructed before the plugin service is created and will
|
||||
* allow access to the protocol factory and protocol initiation entry points there.
|
||||
* allow access to the flow factory and flow initiation entry points there.
|
||||
*/
|
||||
open val servicePlugins: List<Class<*>> = emptyList()
|
||||
|
||||
|
@ -7,7 +7,7 @@ import net.corda.core.node.services.ServiceType
|
||||
|
||||
/**
|
||||
* Information for an advertised service including the service specific identity information.
|
||||
* The identity can be used in protocols and is distinct from the Node's legalIdentity
|
||||
* The identity can be used in flows and is distinct from the Node's legalIdentity
|
||||
*/
|
||||
data class ServiceEntry(val info: ServiceInfo, val identity: Party)
|
||||
|
||||
|
@ -1,7 +1,7 @@
|
||||
package net.corda.core.node
|
||||
|
||||
import net.corda.core.crypto.Party
|
||||
import net.corda.core.protocols.ProtocolLogic
|
||||
import net.corda.core.flows.FlowLogic
|
||||
import kotlin.reflect.KClass
|
||||
|
||||
/**
|
||||
@ -9,22 +9,22 @@ import kotlin.reflect.KClass
|
||||
*/
|
||||
interface PluginServiceHub : ServiceHub {
|
||||
/**
|
||||
* Register the protocol factory we wish to use when a initiating party attempts to communicate with us. The
|
||||
* Register the flow factory we wish to use when a initiating party attempts to communicate with us. The
|
||||
* registration is done against a marker [KClass] which is sent in the session handshake by the other party. If this
|
||||
* marker class has been registered then the corresponding factory will be used to create the protocol which will
|
||||
* marker class has been registered then the corresponding factory will be used to create the flow which will
|
||||
* communicate with the other side. If there is no mapping then the session attempt is rejected.
|
||||
* @param markerClass The marker [KClass] present in a session initiation attempt, which is a 1:1 mapping to a [Class]
|
||||
* using the <pre>::class</pre> construct. Conventionally this is a [ProtocolLogic] subclass, however any class can
|
||||
* be used, with the default being the class of the initiating protocol. This enables the registration to be of the
|
||||
* form: registerProtocolInitiator(InitiatorProtocol::class, ::InitiatedProtocol)
|
||||
* @param protocolFactory The protocol factory generating the initiated protocol.
|
||||
* using the <pre>::class</pre> construct. Conventionally this is a [FlowLogic] subclass, however any class can
|
||||
* be used, with the default being the class of the initiating flow. This enables the registration to be of the
|
||||
* form: registerFlowInitiator(InitiatorFlow::class, ::InitiatedFlow)
|
||||
* @param flowFactory The flow factory generating the initiated flow.
|
||||
*/
|
||||
|
||||
// TODO: remove dependency on Kotlin relfection (Kotlin KClass -> Java Class).
|
||||
fun registerProtocolInitiator(markerClass: KClass<*>, protocolFactory: (Party) -> ProtocolLogic<*>)
|
||||
fun registerFlowInitiator(markerClass: KClass<*>, flowFactory: (Party) -> FlowLogic<*>)
|
||||
|
||||
/**
|
||||
* Return the protocol factory that has been registered with [markerClass], or null if no factory is found.
|
||||
* Return the flow factory that has been registered with [markerClass], or null if no factory is found.
|
||||
*/
|
||||
fun getProtocolFactory(markerClass: Class<*>): ((Party) -> ProtocolLogic<*>)?
|
||||
fun getFlowFactory(markerClass: Class<*>): ((Party) -> FlowLogic<*>)?
|
||||
}
|
||||
|
@ -3,10 +3,10 @@ package net.corda.core.node
|
||||
import net.corda.core.contracts.StateRef
|
||||
import net.corda.core.contracts.TransactionResolutionException
|
||||
import net.corda.core.contracts.TransactionState
|
||||
import net.corda.core.flows.FlowLogic
|
||||
import net.corda.core.flows.FlowStateMachine
|
||||
import net.corda.core.messaging.MessagingService
|
||||
import net.corda.core.node.services.*
|
||||
import net.corda.core.protocols.ProtocolLogic
|
||||
import net.corda.core.protocols.ProtocolStateMachine
|
||||
import net.corda.core.transactions.SignedTransaction
|
||||
import java.security.KeyPair
|
||||
import java.time.Clock
|
||||
@ -16,7 +16,7 @@ import java.time.Clock
|
||||
* mocked out. This class is useful to pass to chunks of pluggable code that might have need of many different kinds of
|
||||
* functionality and you don't want to hard-code which types in the interface.
|
||||
*
|
||||
* Any services exposed to protocols (public view) need to implement [SerializeAsToken] or similar to avoid their internal
|
||||
* Any services exposed to flows (public view) need to implement [SerializeAsToken] or similar to avoid their internal
|
||||
* state from being serialized in checkpoints.
|
||||
*/
|
||||
interface ServiceHub {
|
||||
@ -49,16 +49,16 @@ interface ServiceHub {
|
||||
}
|
||||
|
||||
/**
|
||||
* Will check [logicType] and [args] against a whitelist and if acceptable then construct and initiate the protocol.
|
||||
* Will check [logicType] and [args] against a whitelist and if acceptable then construct and initiate the flow.
|
||||
*
|
||||
* @throws IllegalProtocolLogicException or IllegalArgumentException if there are problems with the [logicType] or [args].
|
||||
* @throws IllegalFlowLogicException or IllegalArgumentException if there are problems with the [logicType] or [args].
|
||||
*/
|
||||
fun <T : Any> invokeProtocolAsync(logicType: Class<out ProtocolLogic<T>>, vararg args: Any?): ProtocolStateMachine<T>
|
||||
fun <T : Any> invokeFlowAsync(logicType: Class<out FlowLogic<T>>, vararg args: Any?): FlowStateMachine<T>
|
||||
|
||||
/**
|
||||
* Helper property to shorten code for fetching the Node's KeyPair associated with the
|
||||
* public legalIdentity Party from the key management service.
|
||||
* Typical use is during signing in protocols and for unit test signing.
|
||||
* Typical use is during signing in flows and for unit test signing.
|
||||
*
|
||||
* TODO: legalIdentity can now be composed of multiple keys, should we return a list of keyPairs here? Right now
|
||||
* the logic assumes the legal identity has a composite key with only one node
|
||||
@ -70,7 +70,7 @@ interface ServiceHub {
|
||||
* public notaryIdentity Party from the key management service. It is assumed that this is only
|
||||
* used in contexts where the Node knows it is hosting a Notary Service. Otherwise, it will throw
|
||||
* an IllegalArgumentException.
|
||||
* Typical use is during signing in protocols and for unit test signing.
|
||||
* Typical use is during signing in flows and for unit test signing.
|
||||
*/
|
||||
val notaryIdentityKey: KeyPair get() = this.keyManagementService.toKeyPair(this.myInfo.notaryIdentity.owningKey.keys)
|
||||
|
||||
|
@ -220,7 +220,7 @@ interface KeyManagementService {
|
||||
|
||||
/**
|
||||
* A sketch of an interface to a simple key/value storage system. Intended for persistence of simple blobs like
|
||||
* transactions, serialised protocol state machines and so on. Again, this isn't intended to imply lack of SQL or
|
||||
* transactions, serialised flow state machines and so on. Again, this isn't intended to imply lack of SQL or
|
||||
* anything like that, this interface is only big enough to support the prototyping work.
|
||||
*/
|
||||
interface StorageService {
|
||||
|
@ -1,14 +1,14 @@
|
||||
package net.corda.core.node.services
|
||||
|
||||
import net.corda.core.crypto.SecureHash
|
||||
import net.corda.core.protocols.StateMachineRunId
|
||||
import net.corda.core.flows.StateMachineRunId
|
||||
import rx.Observable
|
||||
|
||||
data class StateMachineTransactionMapping(val stateMachineRunId: StateMachineRunId, val transactionId: SecureHash)
|
||||
|
||||
/**
|
||||
* This is the interface to storage storing state machine -> recorded tx mappings. Any time a transaction is recorded
|
||||
* during a protocol run [addMapping] should be called.
|
||||
* during a flow run [addMapping] should be called.
|
||||
*/
|
||||
interface StateMachineRecordedTransactionMappingStorage {
|
||||
fun addMapping(stateMachineRunId: StateMachineRunId, transactionId: SecureHash)
|
||||
|
@ -58,7 +58,7 @@ open class TransactionBuilder(
|
||||
/**
|
||||
* Places a [TimestampCommand] in this transaction, removing any existing command if there is one.
|
||||
* The command requires a signature from the Notary service, which acts as a Timestamp Authority.
|
||||
* The signature can be obtained using [NotaryProtocol].
|
||||
* The signature can be obtained using [NotaryFlow].
|
||||
*
|
||||
* The window of time in which the final timestamp may lie is defined as [time] +/- [timeTolerance].
|
||||
* If you want a non-symmetrical time window you must add the command via [addCommand] yourself. The tolerance
|
||||
|
@ -69,7 +69,7 @@ class WireTransaction(
|
||||
|
||||
/**
|
||||
* Looks up identities and attachments from storage to generate a [LedgerTransaction]. A transaction is expected to
|
||||
* have been fully resolved using the resolution protocol by this point.
|
||||
* have been fully resolved using the resolution flow by this point.
|
||||
*
|
||||
* @throws FileNotFoundException if a required attachment was not found in storage.
|
||||
* @throws TransactionResolutionException if an input points to a transaction not found in storage.
|
||||
|
@ -1,4 +1,4 @@
|
||||
package net.corda.protocols
|
||||
package net.corda.flows
|
||||
|
||||
import co.paralleluniverse.fibers.Suspendable
|
||||
import net.corda.core.contracts.ContractState
|
||||
@ -8,26 +8,26 @@ import net.corda.core.crypto.CompositeKey
|
||||
import net.corda.core.crypto.DigitalSignature
|
||||
import net.corda.core.crypto.Party
|
||||
import net.corda.core.crypto.signWithECDSA
|
||||
import net.corda.core.flows.FlowLogic
|
||||
import net.corda.core.node.recordTransactions
|
||||
import net.corda.core.protocols.ProtocolLogic
|
||||
import net.corda.core.transactions.SignedTransaction
|
||||
import net.corda.core.transactions.WireTransaction
|
||||
import net.corda.core.utilities.ProgressTracker
|
||||
import net.corda.core.utilities.UntrustworthyData
|
||||
import net.corda.protocols.AbstractStateReplacementProtocol.Acceptor
|
||||
import net.corda.protocols.AbstractStateReplacementProtocol.Instigator
|
||||
import net.corda.flows.AbstractStateReplacementFlow.Acceptor
|
||||
import net.corda.flows.AbstractStateReplacementFlow.Instigator
|
||||
|
||||
/**
|
||||
* Abstract protocol to be used for replacing one state with another, for example when changing the notary of a state.
|
||||
* Abstract flow to be used for replacing one state with another, for example when changing the notary of a state.
|
||||
* Notably this requires a one to one replacement of states, states cannot be split, merged or issued as part of these
|
||||
* protocols.
|
||||
* flows.
|
||||
*
|
||||
* The [Instigator] assembles the transaction for state replacement and sends out change proposals to all participants
|
||||
* ([Acceptor]) of that state. If participants agree to the proposed change, they each sign the transaction.
|
||||
* Finally, [Instigator] sends the transaction containing all signatures back to each participant so they can record it and
|
||||
* use the new updated state for future transactions.
|
||||
*/
|
||||
abstract class AbstractStateReplacementProtocol<T> {
|
||||
abstract class AbstractStateReplacementFlow<T> {
|
||||
interface Proposal<out T> {
|
||||
val stateRef: StateRef
|
||||
val modification: T
|
||||
@ -36,7 +36,7 @@ abstract class AbstractStateReplacementProtocol<T> {
|
||||
|
||||
abstract class Instigator<out S : ContractState, T>(val originalState: StateAndRef<S>,
|
||||
val modification: T,
|
||||
override val progressTracker: ProgressTracker = tracker()) : ProtocolLogic<StateAndRef<S>>() {
|
||||
override val progressTracker: ProgressTracker = tracker()) : FlowLogic<StateAndRef<S>>() {
|
||||
companion object {
|
||||
|
||||
object SIGNING : ProgressTracker.Step("Requesting signatures from other parties")
|
||||
@ -107,12 +107,12 @@ abstract class AbstractStateReplacementProtocol<T> {
|
||||
@Suspendable
|
||||
private fun getNotarySignature(stx: SignedTransaction): DigitalSignature.WithKey {
|
||||
progressTracker.currentStep = NOTARY
|
||||
return subProtocol(NotaryProtocol.Client(stx))
|
||||
return subFlow(NotaryFlow.Client(stx))
|
||||
}
|
||||
}
|
||||
|
||||
abstract class Acceptor<T>(val otherSide: Party,
|
||||
override val progressTracker: ProgressTracker = tracker()) : ProtocolLogic<Unit>() {
|
||||
override val progressTracker: ProgressTracker = tracker()) : FlowLogic<Unit>() {
|
||||
|
||||
companion object {
|
||||
object VERIFYING : ProgressTracker.Step("Verifying state replacement proposal")
|
||||
@ -194,7 +194,7 @@ abstract class AbstractStateReplacementProtocol<T> {
|
||||
|
||||
@Suspendable
|
||||
private fun checkDependenciesValid(stx: SignedTransaction) {
|
||||
subProtocol(ResolveTransactionsProtocol(stx.tx, otherSide))
|
||||
subFlow(ResolveTransactionsFlow(stx.tx, otherSide))
|
||||
}
|
||||
|
||||
private fun sign(stx: SignedTransaction): DigitalSignature.WithKey {
|
||||
@ -203,7 +203,7 @@ abstract class AbstractStateReplacementProtocol<T> {
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: similar classes occur in other places (NotaryProtocol), need to consolidate
|
||||
// TODO: similar classes occur in other places (NotaryFlow), need to consolidate
|
||||
data class Result private constructor(val sig: DigitalSignature.WithKey?, val error: StateReplacementRefused?) {
|
||||
companion object {
|
||||
fun withError(error: StateReplacementRefused) = Result(null, error)
|
@ -1,22 +1,22 @@
|
||||
package net.corda.protocols
|
||||
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.node.recordTransactions
|
||||
import net.corda.core.protocols.ProtocolLogic
|
||||
import net.corda.core.transactions.SignedTransaction
|
||||
|
||||
|
||||
/**
|
||||
* Notify all involved parties about a transaction, including storing a copy. Normally this would be called via
|
||||
* [FinalityProtocol].
|
||||
* [FinalityFlow].
|
||||
*
|
||||
* @param notarisedTransaction transaction which has been notarised (if needed) and is ready to notify nodes about.
|
||||
* @param participants a list of participants involved in the transaction.
|
||||
* @return a list of participants who were successfully notified of the transaction.
|
||||
*/
|
||||
class BroadcastTransactionProtocol(val notarisedTransaction: SignedTransaction,
|
||||
val participants: Set<Party>) : ProtocolLogic<Unit>() {
|
||||
class BroadcastTransactionFlow(val notarisedTransaction: SignedTransaction,
|
||||
val participants: Set<Party>) : FlowLogic<Unit>() {
|
||||
|
||||
data class NotifyTxRequest(val tx: SignedTransaction)
|
||||
|
@ -1,4 +1,4 @@
|
||||
package net.corda.protocols
|
||||
package net.corda.flows
|
||||
|
||||
import net.corda.core.contracts.Attachment
|
||||
import net.corda.core.crypto.Party
|
||||
@ -11,8 +11,8 @@ import java.io.InputStream
|
||||
* 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.
|
||||
*/
|
||||
class FetchAttachmentsProtocol(requests: Set<SecureHash>,
|
||||
otherSide: Party) : FetchDataProtocol<Attachment, ByteArray>(requests, otherSide) {
|
||||
class FetchAttachmentsFlow(requests: Set<SecureHash>,
|
||||
otherSide: Party) : FetchDataFlow<Attachment, ByteArray>(requests, otherSide) {
|
||||
|
||||
override fun load(txid: SecureHash): Attachment? = serviceHub.storageService.attachments.openAttachment(txid)
|
||||
|
@ -1,17 +1,17 @@
|
||||
package net.corda.protocols
|
||||
package net.corda.flows
|
||||
|
||||
import co.paralleluniverse.fibers.Suspendable
|
||||
import net.corda.core.contracts.NamedByHash
|
||||
import net.corda.core.crypto.Party
|
||||
import net.corda.core.crypto.SecureHash
|
||||
import net.corda.core.protocols.ProtocolLogic
|
||||
import net.corda.core.flows.FlowLogic
|
||||
import net.corda.core.utilities.UntrustworthyData
|
||||
import net.corda.protocols.FetchDataProtocol.DownloadedVsRequestedDataMismatch
|
||||
import net.corda.protocols.FetchDataProtocol.HashNotFound
|
||||
import net.corda.flows.FetchDataFlow.DownloadedVsRequestedDataMismatch
|
||||
import net.corda.flows.FetchDataFlow.HashNotFound
|
||||
import java.util.*
|
||||
|
||||
/**
|
||||
* An abstract protocol for fetching typed data from a remote peer.
|
||||
* An abstract flow for fetching typed data from a remote peer.
|
||||
*
|
||||
* Given a set of hashes (IDs), either loads them from local disk or asks the remote peer to provide them.
|
||||
*
|
||||
@ -26,9 +26,9 @@ import java.util.*
|
||||
* @param T The ultimate type of the data being fetched.
|
||||
* @param W The wire type of the data being fetched, for when it isn't the same as the ultimate type.
|
||||
*/
|
||||
abstract class FetchDataProtocol<T : NamedByHash, in W : Any>(
|
||||
abstract class FetchDataFlow<T : NamedByHash, in W : Any>(
|
||||
protected val requests: Set<SecureHash>,
|
||||
protected val otherSide: Party) : ProtocolLogic<FetchDataProtocol.Result<T>>() {
|
||||
protected val otherSide: Party) : FlowLogic<FetchDataFlow.Result<T>>() {
|
||||
|
||||
open class BadAnswer : Exception()
|
||||
class HashNotFound(val requested: SecureHash) : BadAnswer()
|
||||
@ -88,7 +88,7 @@ abstract class FetchDataProtocol<T : NamedByHash, in W : Any>(
|
||||
}
|
||||
val answers = response.requireNoNulls().map { convert(it) }
|
||||
// Check transactions actually hash to what we requested, if this fails the remote node
|
||||
// is a malicious protocol violator or buggy.
|
||||
// is a malicious flow violator or buggy.
|
||||
for ((index, item) in answers.withIndex())
|
||||
if (item.id != requests[index])
|
||||
throw DownloadedVsRequestedDataMismatch(requests[index], item.id)
|
@ -1,4 +1,4 @@
|
||||
package net.corda.protocols
|
||||
package net.corda.flows
|
||||
|
||||
import net.corda.core.crypto.Party
|
||||
import net.corda.core.crypto.SecureHash
|
||||
@ -8,12 +8,12 @@ import net.corda.core.transactions.SignedTransaction
|
||||
* Given a set of tx hashes (IDs), either loads them from local disk or asks the remote peer to provide them.
|
||||
*
|
||||
* A malicious response in which the data provided by the remote peer does not hash to the requested hash results in
|
||||
* [FetchDataProtocol.DownloadedVsRequestedDataMismatch] being thrown. If the remote peer doesn't have an entry, it
|
||||
* results in a [FetchDataProtocol.HashNotFound] exception. Note that returned transactions are not inserted into
|
||||
* [FetchDataFlow.DownloadedVsRequestedDataMismatch] being thrown. If the remote peer doesn't have an entry, it
|
||||
* 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.
|
||||
*/
|
||||
class FetchTransactionsProtocol(requests: Set<SecureHash>, otherSide: Party) :
|
||||
FetchDataProtocol<SignedTransaction, SignedTransaction>(requests, otherSide) {
|
||||
class FetchTransactionsFlow(requests: Set<SecureHash>, otherSide: Party) :
|
||||
FetchDataFlow<SignedTransaction, SignedTransaction>(requests, otherSide) {
|
||||
|
||||
override fun load(txid: SecureHash): SignedTransaction? = serviceHub.storageService.validatedTransactions.getTransaction(txid)
|
||||
}
|
@ -1,8 +1,8 @@
|
||||
package net.corda.protocols
|
||||
package net.corda.flows
|
||||
|
||||
import co.paralleluniverse.fibers.Suspendable
|
||||
import net.corda.core.crypto.Party
|
||||
import net.corda.core.protocols.ProtocolLogic
|
||||
import net.corda.core.flows.FlowLogic
|
||||
import net.corda.core.transactions.SignedTransaction
|
||||
import net.corda.core.utilities.ProgressTracker
|
||||
|
||||
@ -14,9 +14,9 @@ import net.corda.core.utilities.ProgressTracker
|
||||
* @param participants a list of participants involved in the transaction.
|
||||
* @return a list of participants who were successfully notified of the transaction.
|
||||
*/
|
||||
class FinalityProtocol(val transaction: SignedTransaction,
|
||||
val participants: Set<Party>,
|
||||
override val progressTracker: ProgressTracker = tracker()): ProtocolLogic<Unit>() {
|
||||
class FinalityFlow(val transaction: SignedTransaction,
|
||||
val participants: Set<Party>,
|
||||
override val progressTracker: ProgressTracker = tracker()) : FlowLogic<Unit>() {
|
||||
companion object {
|
||||
object NOTARISING : ProgressTracker.Step("Requesting signature by notary service")
|
||||
object BROADCASTING : ProgressTracker.Step("Broadcasting transaction to participants")
|
||||
@ -31,7 +31,7 @@ class FinalityProtocol(val transaction: SignedTransaction,
|
||||
progressTracker.currentStep = NOTARISING
|
||||
// Notarise the transaction if needed
|
||||
val notarisedTransaction = if (needsNotarySignature(transaction)) {
|
||||
val notarySig = subProtocol(NotaryProtocol.Client(transaction))
|
||||
val notarySig = subFlow(NotaryFlow.Client(transaction))
|
||||
transaction.withAdditionalSignature(notarySig)
|
||||
} else {
|
||||
transaction
|
||||
@ -39,7 +39,7 @@ class FinalityProtocol(val transaction: SignedTransaction,
|
||||
|
||||
// Let everyone else know about the transaction
|
||||
progressTracker.currentStep = BROADCASTING
|
||||
subProtocol(BroadcastTransactionProtocol(notarisedTransaction, participants))
|
||||
subFlow(BroadcastTransactionFlow(notarisedTransaction, participants))
|
||||
}
|
||||
|
||||
private fun needsNotarySignature(stx: SignedTransaction) = stx.tx.notary != null && hasNoNotarySignature(stx)
|
@ -1,4 +1,4 @@
|
||||
package net.corda.protocols
|
||||
package net.corda.flows
|
||||
|
||||
import co.paralleluniverse.fibers.Suspendable
|
||||
import net.corda.core.contracts.ContractState
|
||||
@ -10,11 +10,11 @@ import net.corda.core.crypto.Party
|
||||
import net.corda.core.transactions.SignedTransaction
|
||||
import net.corda.core.utilities.ProgressTracker
|
||||
import net.corda.core.utilities.UntrustworthyData
|
||||
import net.corda.protocols.NotaryChangeProtocol.Acceptor
|
||||
import net.corda.protocols.NotaryChangeProtocol.Instigator
|
||||
import net.corda.flows.NotaryChangeFlow.Acceptor
|
||||
import net.corda.flows.NotaryChangeFlow.Instigator
|
||||
|
||||
/**
|
||||
* A protocol to be used for changing a state's Notary. This is required since all input states to a transaction
|
||||
* A flow to be used for changing a state's Notary. This is required since all input states to a transaction
|
||||
* must point to the same notary.
|
||||
*
|
||||
* The [Instigator] assembles the transaction for notary replacement and sends out change proposals to all participants
|
||||
@ -22,18 +22,18 @@ import net.corda.protocols.NotaryChangeProtocol.Instigator
|
||||
* Finally, [Instigator] sends the transaction containing all signatures back to each participant so they can record it and
|
||||
* use the new updated state for future transactions.
|
||||
*/
|
||||
object NotaryChangeProtocol: AbstractStateReplacementProtocol<Party>() {
|
||||
object NotaryChangeFlow : AbstractStateReplacementFlow<Party>() {
|
||||
|
||||
data class Proposal(override val stateRef: StateRef,
|
||||
override val modification: Party,
|
||||
override val stx: SignedTransaction) : AbstractStateReplacementProtocol.Proposal<Party>
|
||||
override val stx: SignedTransaction) : AbstractStateReplacementFlow.Proposal<Party>
|
||||
|
||||
class Instigator<T : ContractState>(originalState: StateAndRef<T>,
|
||||
newNotary: Party,
|
||||
progressTracker: ProgressTracker = tracker())
|
||||
: AbstractStateReplacementProtocol.Instigator<T, Party>(originalState, newNotary, progressTracker) {
|
||||
: AbstractStateReplacementFlow.Instigator<T, Party>(originalState, newNotary, progressTracker) {
|
||||
|
||||
override fun assembleProposal(stateRef: StateRef, modification: Party, stx: SignedTransaction): AbstractStateReplacementProtocol.Proposal<Party>
|
||||
override fun assembleProposal(stateRef: StateRef, modification: Party, stx: SignedTransaction): AbstractStateReplacementFlow.Proposal<Party>
|
||||
= Proposal(stateRef, modification, stx)
|
||||
|
||||
override fun assembleTx(): Pair<SignedTransaction, List<CompositeKey>> {
|
||||
@ -51,7 +51,7 @@ object NotaryChangeProtocol: AbstractStateReplacementProtocol<Party>() {
|
||||
|
||||
class Acceptor(otherSide: Party,
|
||||
override val progressTracker: ProgressTracker = tracker())
|
||||
: AbstractStateReplacementProtocol.Acceptor<Party>(otherSide) {
|
||||
: AbstractStateReplacementFlow.Acceptor<Party>(otherSide) {
|
||||
|
||||
/**
|
||||
* Check the notary change proposal.
|
||||
@ -61,7 +61,7 @@ object NotaryChangeProtocol: AbstractStateReplacementProtocol<Party>() {
|
||||
* TODO: In more difficult cases this should call for human attention to manually verify and approve the proposal
|
||||
*/
|
||||
@Suspendable
|
||||
override fun verifyProposal(maybeProposal: UntrustworthyData<AbstractStateReplacementProtocol.Proposal<Party>>): AbstractStateReplacementProtocol.Proposal<Party> {
|
||||
override fun verifyProposal(maybeProposal: UntrustworthyData<AbstractStateReplacementFlow.Proposal<Party>>): AbstractStateReplacementFlow.Proposal<Party> {
|
||||
return maybeProposal.unwrap { proposal ->
|
||||
val newNotary = proposal.modification
|
||||
val isNotary = serviceHub.networkMapCache.notaryNodes.any { it.notaryIdentity == newNotary }
|
@ -1,28 +1,28 @@
|
||||
package net.corda.protocols
|
||||
package net.corda.flows
|
||||
|
||||
import co.paralleluniverse.fibers.Suspendable
|
||||
import net.corda.core.crypto.*
|
||||
import net.corda.core.flows.FlowLogic
|
||||
import net.corda.core.node.services.TimestampChecker
|
||||
import net.corda.core.node.services.UniquenessException
|
||||
import net.corda.core.node.services.UniquenessProvider
|
||||
import net.corda.core.protocols.ProtocolLogic
|
||||
import net.corda.core.serialization.serialize
|
||||
import net.corda.core.transactions.SignedTransaction
|
||||
import net.corda.core.transactions.WireTransaction
|
||||
import net.corda.core.utilities.ProgressTracker
|
||||
import net.corda.core.utilities.UntrustworthyData
|
||||
|
||||
object NotaryProtocol {
|
||||
object NotaryFlow {
|
||||
|
||||
/**
|
||||
* A protocol to be used for obtaining a signature from a [NotaryService] ascertaining the transaction
|
||||
* A flow to be used for obtaining a signature from a [NotaryService] ascertaining the transaction
|
||||
* timestamp is correct and none of its inputs have been used in another completed transaction.
|
||||
*
|
||||
* @throws NotaryException in case the any of the inputs to the transaction have been consumed
|
||||
* by another transaction or the timestamp is invalid.
|
||||
*/
|
||||
open class Client(private val stx: SignedTransaction,
|
||||
override val progressTracker: ProgressTracker = Client.tracker()) : ProtocolLogic<DigitalSignature.WithKey>() {
|
||||
override val progressTracker: ProgressTracker = Client.tracker()) : FlowLogic<DigitalSignature.WithKey>() {
|
||||
|
||||
companion object {
|
||||
|
||||
@ -91,7 +91,7 @@ object NotaryProtocol {
|
||||
*/
|
||||
open class Service(val otherSide: Party,
|
||||
val timestampChecker: TimestampChecker,
|
||||
val uniquenessProvider: UniquenessProvider) : ProtocolLogic<Unit>() {
|
||||
val uniquenessProvider: UniquenessProvider) : FlowLogic<Unit>() {
|
||||
|
||||
@Suspendable
|
||||
override fun call() {
|
@ -1,22 +1,22 @@
|
||||
package net.corda.protocols
|
||||
package net.corda.flows
|
||||
|
||||
import co.paralleluniverse.fibers.Suspendable
|
||||
import net.corda.core.checkedAdd
|
||||
import net.corda.core.crypto.Party
|
||||
import net.corda.core.crypto.SecureHash
|
||||
import net.corda.core.flows.FlowLogic
|
||||
import net.corda.core.node.recordTransactions
|
||||
import net.corda.core.protocols.ProtocolLogic
|
||||
import net.corda.core.transactions.LedgerTransaction
|
||||
import net.corda.core.transactions.SignedTransaction
|
||||
import net.corda.core.transactions.WireTransaction
|
||||
import java.util.*
|
||||
|
||||
// TODO: This code is currently unit tested by TwoPartyTradeProtocolTests, it should have its own tests.
|
||||
// TODO: This code is currently unit tested by TwoPartyTradeFlowTests, it should have its own tests.
|
||||
|
||||
// TODO: It may be a clearer API if we make the primary c'tor private here, and only allow a single tx to be "resolved".
|
||||
|
||||
/**
|
||||
* This protocol is used to verify the validity of a transaction by recursively checking the validity of all the
|
||||
* This flow is used to verify the validity of a transaction by recursively checking the validity of all the
|
||||
* dependencies. Once a transaction is checked it's inserted into local storage so it can be relayed and won't be
|
||||
* checked again.
|
||||
*
|
||||
@ -24,12 +24,12 @@ import java.util.*
|
||||
* transaction are resolved and then the transaction itself is verified. Again, if successful, the results are inserted
|
||||
* into the database as long as a [SignedTransaction] was provided. If only the [WireTransaction] form was provided
|
||||
* then this isn't enough to put into the local database, so only the dependencies are checked and inserted. This way
|
||||
* to use the protocol is helpful when resolving and verifying a finished but partially signed transaction.
|
||||
* to use the flow is helpful when resolving and verifying a finished but partially signed transaction.
|
||||
*
|
||||
* The protocol returns a list of verified [LedgerTransaction] objects, in a depth-first order.
|
||||
* The flow returns a list of verified [LedgerTransaction] objects, in a depth-first order.
|
||||
*/
|
||||
class ResolveTransactionsProtocol(private val txHashes: Set<SecureHash>,
|
||||
private val otherSide: Party) : ProtocolLogic<List<LedgerTransaction>>() {
|
||||
class ResolveTransactionsFlow(private val txHashes: Set<SecureHash>,
|
||||
private val otherSide: Party) : FlowLogic<List<LedgerTransaction>>() {
|
||||
|
||||
companion object {
|
||||
private fun dependencyIDs(wtx: WireTransaction) = wtx.inputs.map { it.txhash }.toSet()
|
||||
@ -75,7 +75,7 @@ class ResolveTransactionsProtocol(private val txHashes: Set<SecureHash>,
|
||||
private var wtx: WireTransaction? = null
|
||||
|
||||
// TODO: Figure out a more appropriate DOS limit here, 5000 is simply a very bad guess.
|
||||
/** The maximum number of transactions this protocol will try to download before bailing out. */
|
||||
/** The maximum number of transactions this flow will try to download before bailing out. */
|
||||
var transactionCountLimit = 5000
|
||||
|
||||
/**
|
||||
@ -110,7 +110,7 @@ class ResolveTransactionsProtocol(private val txHashes: Set<SecureHash>,
|
||||
result += ltx
|
||||
}
|
||||
|
||||
// If this protocol is resolving a specific transaction, make sure we have its attachments and then verify
|
||||
// If this flow is resolving a specific transaction, make sure we have its attachments and then verify
|
||||
// it as well, but don't insert to the database. Note that when we were given a SignedTransaction (stx != null)
|
||||
// we *could* insert, because successful verification implies we have everything we need here, and it might
|
||||
// be a clearer API if we do that. But for consistency with the other c'tor we currently do not.
|
||||
@ -134,7 +134,7 @@ class ResolveTransactionsProtocol(private val txHashes: Set<SecureHash>,
|
||||
//
|
||||
// TODO: This approach has two problems. Analyze and resolve them:
|
||||
//
|
||||
// (1) This protocol leaks private data. If you download a transaction and then do NOT request a
|
||||
// (1) This flow leaks private data. If you download a transaction and then do NOT request a
|
||||
// dependency, it means you already have it, which in turn means you must have been involved with it before
|
||||
// somehow, either in the tx itself or in any following spend of it. If there were no following spends, then
|
||||
// your peer knows for sure that you were involved ... this is bad! The only obvious ways to fix this are
|
||||
@ -162,7 +162,7 @@ class ResolveTransactionsProtocol(private val txHashes: Set<SecureHash>,
|
||||
break
|
||||
|
||||
// Request the standalone transaction data (which may refer to things we don't yet have).
|
||||
val downloads: List<SignedTransaction> = subProtocol(FetchTransactionsProtocol(notAlreadyFetched, otherSide)).downloaded
|
||||
val downloads: List<SignedTransaction> = subFlow(FetchTransactionsFlow(notAlreadyFetched, otherSide)).downloaded
|
||||
|
||||
fetchMissingAttachments(downloads.map { it.tx })
|
||||
|
||||
@ -192,6 +192,6 @@ class ResolveTransactionsProtocol(private val txHashes: Set<SecureHash>,
|
||||
wtx.attachments.filter { serviceHub.storageService.attachments.openAttachment(it) == null }
|
||||
}
|
||||
if (missingAttachments.isNotEmpty())
|
||||
subProtocol(FetchAttachmentsProtocol(missingAttachments.toSet(), otherSide))
|
||||
subFlow(FetchAttachmentsFlow(missingAttachments.toSet(), otherSide))
|
||||
}
|
||||
}
|
@ -1,4 +1,4 @@
|
||||
package net.corda.protocols
|
||||
package net.corda.flows
|
||||
|
||||
import com.google.common.util.concurrent.ListenableFuture
|
||||
import net.corda.core.messaging.MessagingService
|
@ -1,14 +1,14 @@
|
||||
package net.corda.protocols
|
||||
package net.corda.flows
|
||||
|
||||
import co.paralleluniverse.fibers.Suspendable
|
||||
import net.corda.core.contracts.ContractState
|
||||
import net.corda.core.contracts.DealState
|
||||
import net.corda.core.contracts.StateRef
|
||||
import net.corda.core.crypto.*
|
||||
import net.corda.core.flows.FlowLogic
|
||||
import net.corda.core.node.NodeInfo
|
||||
import net.corda.core.node.recordTransactions
|
||||
import net.corda.core.node.services.ServiceType
|
||||
import net.corda.core.protocols.ProtocolLogic
|
||||
import net.corda.core.seconds
|
||||
import net.corda.core.transactions.SignedTransaction
|
||||
import net.corda.core.transactions.TransactionBuilder
|
||||
@ -21,14 +21,14 @@ import java.security.KeyPair
|
||||
/**
|
||||
* Classes for manipulating a two party deal or agreement.
|
||||
*
|
||||
* TODO: The subclasses should probably be broken out into individual protocols rather than making this an ever expanding collection of subclasses.
|
||||
* TODO: The subclasses should probably be broken out into individual flows rather than making this an ever expanding collection of subclasses.
|
||||
*
|
||||
* TODO: Also, the term Deal is used here where we might prefer Agreement.
|
||||
*
|
||||
* TODO: Consider whether we can merge this with [TwoPartyTradeProtocol]
|
||||
* TODO: Consider whether we can merge this with [TwoPartyTradeFlow]
|
||||
*
|
||||
*/
|
||||
object TwoPartyDealProtocol {
|
||||
object TwoPartyDealFlow {
|
||||
|
||||
class DealMismatchException(val expectedDeal: ContractState, val actualDeal: ContractState) : Exception() {
|
||||
override fun toString() = "The submitted deal didn't match the expected: $expectedDeal vs $actualDeal"
|
||||
@ -38,7 +38,7 @@ object TwoPartyDealProtocol {
|
||||
override fun toString() = "The submitted deal didn't match the expected: $expectedDeal vs $actualDeal"
|
||||
}
|
||||
|
||||
// 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 Handshake<out T>(val payload: T, val publicKey: CompositeKey)
|
||||
|
||||
class SignaturesFromPrimary(val sellerSig: DigitalSignature.WithKey, val notarySig: DigitalSignature.WithKey)
|
||||
@ -47,15 +47,15 @@ object TwoPartyDealProtocol {
|
||||
* [Primary] at the end sends the signed tx to all the regulator parties. This a seperate workflow which needs a
|
||||
* sepearate session with the regulator. This interface is used to do that in [Primary.getCounterpartyMarker].
|
||||
*/
|
||||
interface MarkerForBogusRegulatorProtocol
|
||||
interface MarkerForBogusRegulatorFlow
|
||||
|
||||
/**
|
||||
* Abstracted bilateral deal protocol participant that initiates communication/handshake.
|
||||
* Abstracted bilateral deal flow participant that initiates communication/handshake.
|
||||
*
|
||||
* There's a good chance we can push at least some of this logic down into core protocol logic
|
||||
* There's a good chance we can push at least some of this logic down into core flow logic
|
||||
* and helper methods etc.
|
||||
*/
|
||||
abstract class Primary(override val progressTracker: ProgressTracker = Primary.tracker()) : ProtocolLogic<SignedTransaction>() {
|
||||
abstract class Primary(override val progressTracker: ProgressTracker = Primary.tracker()) : FlowLogic<SignedTransaction>() {
|
||||
|
||||
companion object {
|
||||
object AWAITING_PROPOSAL : ProgressTracker.Step("Handshaking and awaiting transaction proposal")
|
||||
@ -76,7 +76,7 @@ object TwoPartyDealProtocol {
|
||||
|
||||
override fun getCounterpartyMarker(party: Party): Class<*> {
|
||||
return if (serviceHub.networkMapCache.regulators.any { it.legalIdentity == party }) {
|
||||
MarkerForBogusRegulatorProtocol::class.java
|
||||
MarkerForBogusRegulatorFlow::class.java
|
||||
} else {
|
||||
super.getCounterpartyMarker(party)
|
||||
}
|
||||
@ -86,7 +86,7 @@ object TwoPartyDealProtocol {
|
||||
fun getPartialTransaction(): UntrustworthyData<SignedTransaction> {
|
||||
progressTracker.currentStep = AWAITING_PROPOSAL
|
||||
|
||||
// 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 = Handshake(payload, myKeyPair.public.composite)
|
||||
val maybeSTX = sendAndReceive<SignedTransaction>(otherParty, hello)
|
||||
|
||||
@ -117,7 +117,7 @@ object TwoPartyDealProtocol {
|
||||
// once we implement state pairing.
|
||||
//
|
||||
// but the goal of this code is not to be fully secure (yet), but rather, just to find good ways to
|
||||
// express protocol state machines on top of the messaging layer.
|
||||
// express flow state machines on top of the messaging layer.
|
||||
|
||||
return stx
|
||||
}
|
||||
@ -128,7 +128,7 @@ object TwoPartyDealProtocol {
|
||||
// Download and check all the transactions that this transaction depends on, but do not check this
|
||||
// transaction itself.
|
||||
val dependencyTxIDs = stx.tx.inputs.map { it.txhash }.toSet()
|
||||
subProtocol(ResolveTransactionsProtocol(dependencyTxIDs, otherParty))
|
||||
subFlow(ResolveTransactionsFlow(dependencyTxIDs, otherParty))
|
||||
}
|
||||
|
||||
@Suspendable
|
||||
@ -162,7 +162,7 @@ object TwoPartyDealProtocol {
|
||||
@Suspendable
|
||||
private fun getNotarySignature(stx: SignedTransaction): DigitalSignature.WithKey {
|
||||
progressTracker.currentStep = NOTARY
|
||||
return subProtocol(NotaryProtocol.Client(stx))
|
||||
return subFlow(NotaryFlow.Client(stx))
|
||||
}
|
||||
|
||||
open fun computeOurSignature(partialTX: SignedTransaction): DigitalSignature.WithKey {
|
||||
@ -185,12 +185,12 @@ object TwoPartyDealProtocol {
|
||||
|
||||
|
||||
/**
|
||||
* Abstracted bilateral deal protocol participant that is recipient of initial communication.
|
||||
* Abstracted bilateral deal flow participant that is recipient of initial communication.
|
||||
*
|
||||
* There's a good chance we can push at least some of this logic down into core protocol logic
|
||||
* There's a good chance we can push at least some of this logic down into core flow logic
|
||||
* and helper methods etc.
|
||||
*/
|
||||
abstract class Secondary<U>(override val progressTracker: ProgressTracker = Secondary.tracker()) : ProtocolLogic<SignedTransaction>() {
|
||||
abstract class Secondary<U>(override val progressTracker: ProgressTracker = Secondary.tracker()) : FlowLogic<SignedTransaction>() {
|
||||
|
||||
companion object {
|
||||
object RECEIVING : ProgressTracker.Step("Waiting for deal info")
|
||||
@ -267,7 +267,7 @@ object TwoPartyDealProtocol {
|
||||
|
||||
|
||||
/**
|
||||
* One side of the protocol for inserting a pre-agreed deal.
|
||||
* One side of the flow for inserting a pre-agreed deal.
|
||||
*/
|
||||
open class Instigator(override val otherParty: Party,
|
||||
override val payload: AutoOffer,
|
||||
@ -279,7 +279,7 @@ object TwoPartyDealProtocol {
|
||||
}
|
||||
|
||||
/**
|
||||
* One side of the protocol for inserting a pre-agreed deal.
|
||||
* One side of the flow for inserting a pre-agreed deal.
|
||||
*/
|
||||
open class Acceptor(override val otherParty: Party,
|
||||
override val progressTracker: ProgressTracker = Secondary.tracker()) : Secondary<AutoOffer>() {
|
@ -1,4 +1,4 @@
|
||||
package net.corda.protocols
|
||||
package net.corda.flows
|
||||
|
||||
import co.paralleluniverse.fibers.Suspendable
|
||||
import net.corda.core.contracts.TransactionVerificationException
|
||||
@ -10,15 +10,15 @@ import net.corda.core.transactions.WireTransaction
|
||||
import java.security.SignatureException
|
||||
|
||||
/**
|
||||
* A notary commit protocol that makes sure a given transaction is valid before committing it. This does mean that the calling
|
||||
* A notary commit flow that makes sure a given transaction is valid before committing it. This does mean that the calling
|
||||
* party has to reveal the whole transaction history; however, we avoid complex conflict resolution logic where a party
|
||||
* has its input states "blocked" by a transaction from another party, and needs to establish whether that transaction was
|
||||
* indeed valid.
|
||||
*/
|
||||
class ValidatingNotaryProtocol(otherSide: Party,
|
||||
timestampChecker: TimestampChecker,
|
||||
uniquenessProvider: UniquenessProvider) :
|
||||
NotaryProtocol.Service(otherSide, timestampChecker, uniquenessProvider) {
|
||||
class ValidatingNotaryFlow(otherSide: Party,
|
||||
timestampChecker: TimestampChecker,
|
||||
uniquenessProvider: UniquenessProvider) :
|
||||
NotaryFlow.Service(otherSide, timestampChecker, uniquenessProvider) {
|
||||
|
||||
@Suspendable
|
||||
override fun beforeCommit(stx: SignedTransaction, reqIdentity: Party) {
|
||||
@ -46,6 +46,6 @@ class ValidatingNotaryProtocol(otherSide: Party,
|
||||
|
||||
@Suspendable
|
||||
private fun resolveTransaction(reqIdentity: Party, wtx: WireTransaction) {
|
||||
subProtocol(ResolveTransactionsProtocol(wtx, reqIdentity))
|
||||
subFlow(ResolveTransactionsFlow(wtx, reqIdentity))
|
||||
}
|
||||
}
|
@ -1,4 +1,4 @@
|
||||
package net.corda.core.protocols;
|
||||
package net.corda.core.flows;
|
||||
|
||||
import org.junit.Test;
|
||||
|
||||
@ -7,7 +7,7 @@ import java.util.HashSet;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
public class ProtocolLogicRefFromJavaTest {
|
||||
public class FlowLogicRefFromJavaTest {
|
||||
|
||||
private static class ParamType1 {
|
||||
final int value;
|
||||
@ -25,9 +25,9 @@ public class ProtocolLogicRefFromJavaTest {
|
||||
}
|
||||
}
|
||||
|
||||
private static class JavaProtocolLogic extends ProtocolLogic<Void> {
|
||||
private static class JavaFlowLogic extends FlowLogic<Void> {
|
||||
|
||||
public JavaProtocolLogic(ParamType1 A, ParamType2 b) {
|
||||
public JavaFlowLogic(ParamType1 A, ParamType2 b) {
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -36,9 +36,9 @@ public class ProtocolLogicRefFromJavaTest {
|
||||
}
|
||||
}
|
||||
|
||||
private static class JavaNoArgProtocolLogic extends ProtocolLogic<Void> {
|
||||
private static class JavaNoArgFlowLogic extends FlowLogic<Void> {
|
||||
|
||||
public JavaNoArgProtocolLogic() {
|
||||
public JavaNoArgFlowLogic() {
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -53,16 +53,16 @@ public class ProtocolLogicRefFromJavaTest {
|
||||
Set<String> argsList = new HashSet<>();
|
||||
argsList.add(ParamType1.class.getName());
|
||||
argsList.add(ParamType2.class.getName());
|
||||
whiteList.put(JavaProtocolLogic.class.getName(), argsList);
|
||||
ProtocolLogicRefFactory factory = new ProtocolLogicRefFactory(whiteList);
|
||||
factory.create(JavaProtocolLogic.class, new ParamType1(1), new ParamType2("Hello Jack"));
|
||||
whiteList.put(JavaFlowLogic.class.getName(), argsList);
|
||||
FlowLogicRefFactory factory = new FlowLogicRefFactory(whiteList);
|
||||
factory.create(JavaFlowLogic.class, new ParamType1(1), new ParamType2("Hello Jack"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testNoArg() {
|
||||
Map<String, Set<String>> whiteList = new HashMap<>();
|
||||
whiteList.put(JavaNoArgProtocolLogic.class.getName(), new HashSet<>());
|
||||
ProtocolLogicRefFactory factory = new ProtocolLogicRefFactory(whiteList);
|
||||
factory.create(JavaNoArgProtocolLogic.class);
|
||||
whiteList.put(JavaNoArgFlowLogic.class.getName(), new HashSet<>());
|
||||
FlowLogicRefFactory factory = new FlowLogicRefFactory(whiteList);
|
||||
factory.create(JavaNoArgFlowLogic.class);
|
||||
}
|
||||
}
|
@ -1,4 +1,4 @@
|
||||
package net.corda.core.protocols
|
||||
package net.corda.core.flows
|
||||
|
||||
import com.esotericsoftware.kryo.io.Input
|
||||
import com.pholser.junit.quickcheck.From
|
||||
@ -10,12 +10,12 @@ import com.pholser.junit.quickcheck.runner.JUnitQuickcheck
|
||||
import net.corda.contracts.testing.SignedTransactionGenerator
|
||||
import net.corda.core.serialization.createKryo
|
||||
import net.corda.core.serialization.serialize
|
||||
import net.corda.protocols.BroadcastTransactionProtocol.NotifyTxRequest
|
||||
import net.corda.flows.BroadcastTransactionFlow.NotifyTxRequest
|
||||
import org.junit.runner.RunWith
|
||||
import kotlin.test.assertEquals
|
||||
|
||||
@RunWith(JUnitQuickcheck::class)
|
||||
class BroadcastTransactionProtocolTest {
|
||||
class BroadcastTransactionFlowTest {
|
||||
|
||||
class NotifyTxRequestMessageGenerator : Generator<NotifyTxRequest>(NotifyTxRequest::class.java) {
|
||||
override fun generate(random: SourceOfRandomness, status: GenerationStatus): NotifyTxRequest {
|
@ -1,17 +1,17 @@
|
||||
package net.corda.core.protocols
|
||||
package net.corda.core.flows
|
||||
|
||||
import net.corda.core.days
|
||||
import org.junit.Before
|
||||
import org.junit.Test
|
||||
import java.time.Duration
|
||||
|
||||
class ProtocolLogicRefTest {
|
||||
class FlowLogicRefTest {
|
||||
|
||||
data class ParamType1(val value: Int)
|
||||
data class ParamType2(val value: String)
|
||||
|
||||
@Suppress("UNUSED_PARAMETER", "unused") // Things are used via reflection.
|
||||
class KotlinProtocolLogic(A: ParamType1, b: ParamType2) : ProtocolLogic<Unit>() {
|
||||
class KotlinFlowLogic(A: ParamType1, b: ParamType2) : FlowLogic<Unit>() {
|
||||
constructor() : this(ParamType1(1), ParamType2("2"))
|
||||
|
||||
constructor(C: ParamType2) : this(ParamType1(1), C)
|
||||
@ -25,71 +25,71 @@ class ProtocolLogicRefTest {
|
||||
override fun call() = Unit
|
||||
}
|
||||
|
||||
class KotlinNoArgProtocolLogic : ProtocolLogic<Unit>() {
|
||||
class KotlinNoArgFlowLogic : FlowLogic<Unit>() {
|
||||
override fun call() = Unit
|
||||
}
|
||||
|
||||
@Suppress("UNUSED_PARAMETER") // We will never use A or b
|
||||
class NotWhiteListedKotlinProtocolLogic(A: Int, b: String) : ProtocolLogic<Unit>() {
|
||||
class NotWhiteListedKotlinFlowLogic(A: Int, b: String) : FlowLogic<Unit>() {
|
||||
override fun call() = Unit
|
||||
}
|
||||
|
||||
lateinit var factory: ProtocolLogicRefFactory
|
||||
lateinit var factory: FlowLogicRefFactory
|
||||
|
||||
@Before
|
||||
fun setup() {
|
||||
// We have to allow Java boxed primitives but Kotlin warns we shouldn't be using them
|
||||
factory = ProtocolLogicRefFactory(mapOf(Pair(KotlinProtocolLogic::class.java.name, setOf(ParamType1::class.java.name, ParamType2::class.java.name)),
|
||||
Pair(KotlinNoArgProtocolLogic::class.java.name, setOf())))
|
||||
factory = FlowLogicRefFactory(mapOf(Pair(KotlinFlowLogic::class.java.name, setOf(ParamType1::class.java.name, ParamType2::class.java.name)),
|
||||
Pair(KotlinNoArgFlowLogic::class.java.name, setOf())))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testCreateKotlinNoArg() {
|
||||
factory.create(KotlinNoArgProtocolLogic::class.java)
|
||||
factory.create(KotlinNoArgFlowLogic::class.java)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testCreateKotlin() {
|
||||
val args = mapOf(Pair("A", ParamType1(1)), Pair("b", ParamType2("Hello Jack")))
|
||||
factory.createKotlin(KotlinProtocolLogic::class.java, args)
|
||||
factory.createKotlin(KotlinFlowLogic::class.java, args)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testCreatePrimary() {
|
||||
factory.create(KotlinProtocolLogic::class.java, ParamType1(1), ParamType2("Hello Jack"))
|
||||
factory.create(KotlinFlowLogic::class.java, ParamType1(1), ParamType2("Hello Jack"))
|
||||
}
|
||||
|
||||
@Test(expected = IllegalArgumentException::class)
|
||||
fun testCreateNotWhiteListed() {
|
||||
factory.create(NotWhiteListedKotlinProtocolLogic::class.java, ParamType1(1), ParamType2("Hello Jack"))
|
||||
factory.create(NotWhiteListedKotlinFlowLogic::class.java, ParamType1(1), ParamType2("Hello Jack"))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testCreateKotlinVoid() {
|
||||
factory.createKotlin(KotlinProtocolLogic::class.java, emptyMap())
|
||||
factory.createKotlin(KotlinFlowLogic::class.java, emptyMap())
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testCreateKotlinNonPrimary() {
|
||||
val args = mapOf(Pair("C", ParamType2("Hello Jack")))
|
||||
factory.createKotlin(KotlinProtocolLogic::class.java, args)
|
||||
factory.createKotlin(KotlinFlowLogic::class.java, args)
|
||||
}
|
||||
|
||||
@Test(expected = IllegalArgumentException::class)
|
||||
fun testCreateArgNotWhiteListed() {
|
||||
val args = mapOf(Pair("illegal", 1.days))
|
||||
factory.createKotlin(KotlinProtocolLogic::class.java, args)
|
||||
factory.createKotlin(KotlinFlowLogic::class.java, args)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testCreateJavaPrimitiveNoRegistrationRequired() {
|
||||
val args = mapOf(Pair("primitive", "A string"))
|
||||
factory.createKotlin(KotlinProtocolLogic::class.java, args)
|
||||
factory.createKotlin(KotlinFlowLogic::class.java, args)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testCreateKotlinPrimitiveNoRegistrationRequired() {
|
||||
val args = mapOf(Pair("kotlinType", 3))
|
||||
factory.createKotlin(KotlinProtocolLogic::class.java, args)
|
||||
factory.createKotlin(KotlinFlowLogic::class.java, args)
|
||||
}
|
||||
}
|
@ -1,4 +1,4 @@
|
||||
package net.corda.core.protocols
|
||||
package net.corda.core.flows
|
||||
|
||||
import net.corda.core.contracts.DummyContract
|
||||
import net.corda.core.crypto.NullSignature
|
||||
@ -8,8 +8,8 @@ import net.corda.core.node.recordTransactions
|
||||
import net.corda.core.serialization.opaque
|
||||
import net.corda.core.transactions.SignedTransaction
|
||||
import net.corda.core.utilities.DUMMY_NOTARY_KEY
|
||||
import net.corda.flows.ResolveTransactionsFlow
|
||||
import net.corda.node.utilities.databaseTransaction
|
||||
import net.corda.protocols.ResolveTransactionsProtocol
|
||||
import net.corda.testing.MEGA_CORP
|
||||
import net.corda.testing.MEGA_CORP_KEY
|
||||
import net.corda.testing.MINI_CORP_PUBKEY
|
||||
@ -24,7 +24,7 @@ import kotlin.test.assertFailsWith
|
||||
import kotlin.test.assertNotNull
|
||||
import kotlin.test.assertNull
|
||||
|
||||
class ResolveTransactionsProtocolTest {
|
||||
class ResolveTransactionsFlowTest {
|
||||
lateinit var net: MockNetwork
|
||||
lateinit var a: MockNetwork.MockNode
|
||||
lateinit var b: MockNetwork.MockNode
|
||||
@ -48,8 +48,8 @@ class ResolveTransactionsProtocolTest {
|
||||
@Test
|
||||
fun `resolve from two hashes`() {
|
||||
val (stx1, stx2) = makeTransactions()
|
||||
val p = ResolveTransactionsProtocol(setOf(stx2.id), a.info.legalIdentity)
|
||||
val future = b.services.startProtocol(p).resultFuture
|
||||
val p = ResolveTransactionsFlow(setOf(stx2.id), a.info.legalIdentity)
|
||||
val future = b.services.startFlow(p).resultFuture
|
||||
net.runNetwork()
|
||||
val results = future.get()
|
||||
assertEquals(listOf(stx1.id, stx2.id), results.map { it.id })
|
||||
@ -62,8 +62,8 @@ class ResolveTransactionsProtocolTest {
|
||||
@Test
|
||||
fun `dependency with an error`() {
|
||||
val stx = makeTransactions(signFirstTX = false).second
|
||||
val p = ResolveTransactionsProtocol(setOf(stx.id), a.info.legalIdentity)
|
||||
val future = b.services.startProtocol(p).resultFuture
|
||||
val p = ResolveTransactionsFlow(setOf(stx.id), a.info.legalIdentity)
|
||||
val future = b.services.startFlow(p).resultFuture
|
||||
net.runNetwork()
|
||||
assertFailsWith(SignatureException::class) {
|
||||
rootCauseExceptions { future.get() }
|
||||
@ -73,8 +73,8 @@ class ResolveTransactionsProtocolTest {
|
||||
@Test
|
||||
fun `resolve from a signed transaction`() {
|
||||
val (stx1, stx2) = makeTransactions()
|
||||
val p = ResolveTransactionsProtocol(stx2, a.info.legalIdentity)
|
||||
val future = b.services.startProtocol(p).resultFuture
|
||||
val p = ResolveTransactionsFlow(stx2, a.info.legalIdentity)
|
||||
val future = b.services.startFlow(p).resultFuture
|
||||
net.runNetwork()
|
||||
future.get()
|
||||
databaseTransaction(b.database) {
|
||||
@ -99,11 +99,11 @@ class ResolveTransactionsProtocolTest {
|
||||
}
|
||||
cursor = stx
|
||||
}
|
||||
val p = ResolveTransactionsProtocol(setOf(cursor.id), a.info.legalIdentity)
|
||||
val p = ResolveTransactionsFlow(setOf(cursor.id), a.info.legalIdentity)
|
||||
p.transactionCountLimit = 40
|
||||
val future = b.services.startProtocol(p).resultFuture
|
||||
val future = b.services.startFlow(p).resultFuture
|
||||
net.runNetwork()
|
||||
assertFailsWith<ResolveTransactionsProtocol.ExcessivelyLargeTransactionGraph> {
|
||||
assertFailsWith<ResolveTransactionsFlow.ExcessivelyLargeTransactionGraph> {
|
||||
rootCauseExceptions { future.get() }
|
||||
}
|
||||
}
|
||||
@ -128,8 +128,8 @@ class ResolveTransactionsProtocolTest {
|
||||
a.services.recordTransactions(stx2, stx3)
|
||||
}
|
||||
|
||||
val p = ResolveTransactionsProtocol(setOf(stx3.id), a.info.legalIdentity)
|
||||
val future = b.services.startProtocol(p).resultFuture
|
||||
val p = ResolveTransactionsFlow(setOf(stx3.id), a.info.legalIdentity)
|
||||
val future = b.services.startFlow(p).resultFuture
|
||||
net.runNetwork()
|
||||
future.get()
|
||||
}
|
||||
@ -138,8 +138,8 @@ class ResolveTransactionsProtocolTest {
|
||||
fun attachment() {
|
||||
val id = a.services.storageService.attachments.importAttachment("Some test file".toByteArray().opaque().open())
|
||||
val stx2 = makeTransactions(withAttachment = id).second
|
||||
val p = ResolveTransactionsProtocol(stx2, a.info.legalIdentity)
|
||||
val future = b.services.startProtocol(p).resultFuture
|
||||
val p = ResolveTransactionsFlow(stx2, a.info.legalIdentity)
|
||||
val future = b.services.startFlow(p).resultFuture
|
||||
net.runNetwork()
|
||||
future.get()
|
||||
assertNotNull(b.services.storageService.attachments.openAttachment(id))
|
@ -12,16 +12,16 @@ import net.corda.core.node.CordaPluginRegistry
|
||||
import net.corda.core.node.services.ServiceInfo
|
||||
import net.corda.core.serialization.OpaqueBytes
|
||||
import net.corda.core.transactions.SignedTransaction
|
||||
import net.corda.flows.CashCommand
|
||||
import net.corda.flows.CashFlow
|
||||
import net.corda.node.driver.driver
|
||||
import net.corda.node.services.User
|
||||
import net.corda.node.services.config.FullNodeConfiguration
|
||||
import net.corda.node.services.config.NodeSSLConfiguration
|
||||
import net.corda.node.services.messaging.CordaRPCOps
|
||||
import net.corda.node.services.messaging.startProtocol
|
||||
import net.corda.node.services.startProtocolPermission
|
||||
import net.corda.node.services.messaging.startFlow
|
||||
import net.corda.node.services.startFlowPermission
|
||||
import net.corda.node.services.transactions.ValidatingNotaryService
|
||||
import net.corda.protocols.CashCommand
|
||||
import net.corda.protocols.CashProtocol
|
||||
import org.graphstream.graph.Edge
|
||||
import org.graphstream.graph.Node
|
||||
import org.graphstream.graph.implementations.MultiGraph
|
||||
@ -47,7 +47,7 @@ fun main(args: Array<String>) {
|
||||
val printOrVisualise = PrintOrVisualise.valueOf(args[0])
|
||||
|
||||
val baseDirectory = Paths.get("build/rpc-api-tutorial")
|
||||
val user = User("user", "password", permissions = setOf(startProtocolPermission<CashProtocol>()))
|
||||
val user = User("user", "password", permissions = setOf(startFlowPermission<CashFlow>()))
|
||||
|
||||
driver(driverDirectory = baseDirectory) {
|
||||
startNode("Notary", advertisedServices = setOf(ServiceInfo(ValidatingNotaryService.type)))
|
||||
@ -125,14 +125,14 @@ fun generateTransactions(proxy: CordaRPCOps) {
|
||||
val n = random.nextDouble()
|
||||
if (ownedQuantity > 10000 && n > 0.8) {
|
||||
val quantity = Math.abs(random.nextLong()) % 2000
|
||||
proxy.startProtocol(::CashProtocol, CashCommand.ExitCash(Amount(quantity, USD), issueRef))
|
||||
proxy.startFlow(::CashFlow, CashCommand.ExitCash(Amount(quantity, USD), issueRef))
|
||||
ownedQuantity -= quantity
|
||||
} else if (ownedQuantity > 1000 && n < 0.7) {
|
||||
val quantity = Math.abs(random.nextLong() % Math.min(ownedQuantity, 2000))
|
||||
proxy.startProtocol(::CashProtocol, CashCommand.PayCash(Amount(quantity, Issued(meAndRef, USD)), me))
|
||||
proxy.startFlow(::CashFlow, CashCommand.PayCash(Amount(quantity, Issued(meAndRef, USD)), me))
|
||||
} else {
|
||||
val quantity = Math.abs(random.nextLong() % 1000)
|
||||
proxy.startProtocol(::CashProtocol, CashCommand.IssueCash(Amount(quantity, USD), issueRef, me, notary))
|
||||
proxy.startFlow(::CashFlow, CashCommand.IssueCash(Amount(quantity, USD), issueRef, me, notary))
|
||||
ownedQuantity += quantity
|
||||
}
|
||||
}
|
||||
|
@ -1,4 +1,4 @@
|
||||
package net.corda.protocols
|
||||
package net.corda.flows
|
||||
|
||||
import co.paralleluniverse.fibers.Suspendable
|
||||
import net.corda.contracts.asset.Cash
|
||||
@ -6,8 +6,8 @@ import net.corda.core.contracts.*
|
||||
import net.corda.core.crypto.Party
|
||||
import net.corda.core.crypto.keys
|
||||
import net.corda.core.crypto.toStringShort
|
||||
import net.corda.core.protocols.ProtocolLogic
|
||||
import net.corda.core.protocols.StateMachineRunId
|
||||
import net.corda.core.flows.FlowLogic
|
||||
import net.corda.core.flows.StateMachineRunId
|
||||
import net.corda.core.serialization.OpaqueBytes
|
||||
import net.corda.core.transactions.SignedTransaction
|
||||
import net.corda.core.transactions.TransactionBuilder
|
||||
@ -15,14 +15,14 @@ import java.security.KeyPair
|
||||
import java.util.*
|
||||
|
||||
/**
|
||||
* Initiates a protocol that produces an Issue/Move or Exit Cash transaction.
|
||||
* Initiates a flow that produces an Issue/Move or Exit Cash transaction.
|
||||
*
|
||||
* @param command Indicates what Cash transaction to create with what parameters.
|
||||
*/
|
||||
class CashProtocol(val command: CashCommand): ProtocolLogic<CashProtocolResult>() {
|
||||
class CashFlow(val command: CashCommand) : FlowLogic<CashFlowResult>() {
|
||||
|
||||
@Suspendable
|
||||
override fun call(): CashProtocolResult {
|
||||
override fun call(): CashFlowResult {
|
||||
return when (command) {
|
||||
is CashCommand.IssueCash -> issueCash(command)
|
||||
is CashCommand.PayCash -> initiatePayment(command)
|
||||
@ -32,7 +32,7 @@ class CashProtocol(val command: CashCommand): ProtocolLogic<CashProtocolResult>(
|
||||
|
||||
// TODO check with the recipient if they want to accept the cash.
|
||||
@Suspendable
|
||||
private fun initiatePayment(req: CashCommand.PayCash): CashProtocolResult {
|
||||
private fun initiatePayment(req: CashCommand.PayCash): CashFlowResult {
|
||||
val builder: TransactionBuilder = TransactionType.General.Builder(null)
|
||||
// TODO: Have some way of restricting this to states the caller controls
|
||||
try {
|
||||
@ -45,20 +45,20 @@ class CashProtocol(val command: CashCommand): ProtocolLogic<CashProtocolResult>(
|
||||
}
|
||||
|
||||
val tx = spendTX.toSignedTransaction(checkSufficientSignatures = false)
|
||||
val protocol = FinalityProtocol(tx, setOf(req.recipient))
|
||||
subProtocol(protocol)
|
||||
return CashProtocolResult.Success(
|
||||
psm.id,
|
||||
val flow = FinalityFlow(tx, setOf(req.recipient))
|
||||
subFlow(flow)
|
||||
return CashFlowResult.Success(
|
||||
fsm.id,
|
||||
tx,
|
||||
"Cash payment transaction generated"
|
||||
)
|
||||
} catch(ex: InsufficientBalanceException) {
|
||||
return CashProtocolResult.Failed(ex.message ?: "Insufficient balance")
|
||||
return CashFlowResult.Failed(ex.message ?: "Insufficient balance")
|
||||
}
|
||||
}
|
||||
|
||||
@Suspendable
|
||||
private fun exitCash(req: CashCommand.ExitCash): CashProtocolResult {
|
||||
private fun exitCash(req: CashCommand.ExitCash): CashFlowResult {
|
||||
val builder: TransactionBuilder = TransactionType.General.Builder(null)
|
||||
try {
|
||||
val issuer = PartyAndReference(serviceHub.myInfo.legalIdentity, req.issueRef)
|
||||
@ -81,19 +81,19 @@ class CashProtocol(val command: CashCommand): ProtocolLogic<CashProtocolResult>(
|
||||
|
||||
// Commit the transaction
|
||||
val tx = builder.toSignedTransaction(checkSufficientSignatures = false)
|
||||
subProtocol(FinalityProtocol(tx, participants))
|
||||
return CashProtocolResult.Success(
|
||||
psm.id,
|
||||
subFlow(FinalityFlow(tx, participants))
|
||||
return CashFlowResult.Success(
|
||||
fsm.id,
|
||||
tx,
|
||||
"Cash destruction transaction generated"
|
||||
)
|
||||
} catch (ex: InsufficientBalanceException) {
|
||||
return CashProtocolResult.Failed(ex.message ?: "Insufficient balance")
|
||||
return CashFlowResult.Failed(ex.message ?: "Insufficient balance")
|
||||
}
|
||||
}
|
||||
|
||||
@Suspendable
|
||||
private fun issueCash(req: CashCommand.IssueCash): CashProtocolResult {
|
||||
private fun issueCash(req: CashCommand.IssueCash): CashFlowResult {
|
||||
val builder: TransactionBuilder = TransactionType.General.Builder(notary = null)
|
||||
val issuer = PartyAndReference(serviceHub.myInfo.legalIdentity, req.issueRef)
|
||||
Cash().generateIssue(builder, req.amount.issuedBy(issuer), req.recipient.owningKey, req.notary)
|
||||
@ -101,9 +101,9 @@ class CashProtocol(val command: CashCommand): ProtocolLogic<CashProtocolResult>(
|
||||
builder.signWith(myKey)
|
||||
val tx = builder.toSignedTransaction(checkSufficientSignatures = true)
|
||||
// Issuance transactions do not need to be notarised, so we can skip directly to broadcasting it
|
||||
subProtocol(BroadcastTransactionProtocol(tx, setOf(req.recipient)))
|
||||
return CashProtocolResult.Success(
|
||||
psm.id,
|
||||
subFlow(BroadcastTransactionFlow(tx, setOf(req.recipient)))
|
||||
return CashFlowResult.Success(
|
||||
fsm.id,
|
||||
tx,
|
||||
"Cash issuance completed"
|
||||
)
|
||||
@ -113,7 +113,7 @@ class CashProtocol(val command: CashCommand): ProtocolLogic<CashProtocolResult>(
|
||||
}
|
||||
|
||||
/**
|
||||
* A command to initiate the Cash protocol with.
|
||||
* A command to initiate the Cash flow with.
|
||||
*/
|
||||
sealed class CashCommand {
|
||||
/**
|
||||
@ -147,11 +147,11 @@ sealed class CashCommand {
|
||||
class ExitCash(val amount: Amount<Currency>, val issueRef: OpaqueBytes) : CashCommand()
|
||||
}
|
||||
|
||||
sealed class CashProtocolResult {
|
||||
sealed class CashFlowResult {
|
||||
/**
|
||||
* @param transaction the transaction created as a result, in the case where the protocol completed successfully.
|
||||
* @param transaction the transaction created as a result, in the case where the flow completed successfully.
|
||||
*/
|
||||
class Success(val id: StateMachineRunId, val transaction: SignedTransaction?, val message: String?) : CashProtocolResult() {
|
||||
class Success(val id: StateMachineRunId, val transaction: SignedTransaction?, val message: String?) : CashFlowResult() {
|
||||
override fun toString() = "Success($message)"
|
||||
}
|
||||
|
||||
@ -159,7 +159,7 @@ sealed class CashProtocolResult {
|
||||
* State indicating the action undertaken failed, either directly (it is not something which requires a
|
||||
* state machine), or before a state machine was started.
|
||||
*/
|
||||
class Failed(val message: String?) : CashProtocolResult() {
|
||||
class Failed(val message: String?) : CashFlowResult() {
|
||||
override fun toString() = "Failed($message)"
|
||||
}
|
||||
}
|
@ -1,11 +1,11 @@
|
||||
package net.corda.protocols
|
||||
package net.corda.flows
|
||||
|
||||
import co.paralleluniverse.fibers.Suspendable
|
||||
import net.corda.contracts.asset.sumCashBy
|
||||
import net.corda.core.contracts.*
|
||||
import net.corda.core.crypto.*
|
||||
import net.corda.core.flows.FlowLogic
|
||||
import net.corda.core.node.NodeInfo
|
||||
import net.corda.core.protocols.ProtocolLogic
|
||||
import net.corda.core.seconds
|
||||
import net.corda.core.transactions.SignedTransaction
|
||||
import net.corda.core.transactions.TransactionBuilder
|
||||
@ -16,7 +16,7 @@ import java.security.KeyPair
|
||||
import java.util.*
|
||||
|
||||
/**
|
||||
* This asset trading protocol implements a "delivery vs payment" type swap. It has two parties (B and S for buyer
|
||||
* This asset trading flow implements a "delivery vs payment" type swap. It has two parties (B and S for buyer
|
||||
* and seller) and the following steps:
|
||||
*
|
||||
* 1. S sends the [StateAndRef] pointing to what they want to sell to B, along with info about the price they require
|
||||
@ -26,28 +26,28 @@ import java.util.*
|
||||
* it lacks a signature from S authorising movement of the asset.
|
||||
* 3. S signs it and hands the now finalised SignedWireTransaction back to B.
|
||||
*
|
||||
* Assuming no malicious termination, they both end the protocol being in posession of a valid, signed transaction
|
||||
* Assuming no malicious termination, they both end the flow being in posession of a valid, signed transaction
|
||||
* that represents an atomic asset swap.
|
||||
*
|
||||
* Note that it's the *seller* who initiates contact with the buyer, not vice-versa as you might imagine.
|
||||
*
|
||||
* To initiate the protocol, use either the [runBuyer] or [runSeller] methods, depending on which side of the trade
|
||||
* To initiate the flow, use either the [runBuyer] or [runSeller] methods, depending on which side of the trade
|
||||
* your node is taking. These methods return a future which will complete once the trade is over and a fully signed
|
||||
* transaction is available: you can either block your thread waiting for the protocol to complete by using
|
||||
* transaction is available: you can either block your thread waiting for the flow to complete by using
|
||||
* [ListenableFuture.get] or more usefully, register a callback that will be invoked when the time comes.
|
||||
*
|
||||
* To see an example of how to use this class, look at the unit tests.
|
||||
*/
|
||||
// TODO: Common elements in multi-party transaction consensus and signing should be refactored into a superclass of this
|
||||
// and [AbstractStateReplacementProtocol].
|
||||
object TwoPartyTradeProtocol {
|
||||
// and [AbstractStateReplacementFlow].
|
||||
object TwoPartyTradeFlow {
|
||||
|
||||
class UnacceptablePriceException(val givenPrice: Amount<Currency>) : Exception("Unacceptable price: $givenPrice")
|
||||
class AssetMismatchException(val expectedTypeName: String, val typeName: String) : Exception() {
|
||||
override fun toString() = "The submitted asset didn't match the expected type: $expectedTypeName vs $typeName"
|
||||
}
|
||||
|
||||
// This object is serialised to the network and is the first protocol message the seller sends to the buyer.
|
||||
// This object is serialised to the network and is the first flow message the seller sends to the buyer.
|
||||
data class SellerTradeInfo(
|
||||
val assetForSale: StateAndRef<OwnableState>,
|
||||
val price: Amount<Currency>,
|
||||
@ -62,7 +62,7 @@ object TwoPartyTradeProtocol {
|
||||
val assetToSell: StateAndRef<OwnableState>,
|
||||
val price: Amount<Currency>,
|
||||
val myKeyPair: KeyPair,
|
||||
override val progressTracker: ProgressTracker = Seller.tracker()) : ProtocolLogic<SignedTransaction>() {
|
||||
override val progressTracker: ProgressTracker = Seller.tracker()) : FlowLogic<SignedTransaction>() {
|
||||
|
||||
companion object {
|
||||
object AWAITING_PROPOSAL : ProgressTracker.Step("Awaiting transaction proposal")
|
||||
@ -92,7 +92,7 @@ object TwoPartyTradeProtocol {
|
||||
@Suspendable
|
||||
private fun getNotarySignature(stx: SignedTransaction): DigitalSignature.WithKey {
|
||||
progressTracker.currentStep = NOTARY
|
||||
return subProtocol(NotaryProtocol.Client(stx))
|
||||
return subFlow(NotaryFlow.Client(stx))
|
||||
}
|
||||
|
||||
@Suspendable
|
||||
@ -100,7 +100,7 @@ object TwoPartyTradeProtocol {
|
||||
progressTracker.currentStep = AWAITING_PROPOSAL
|
||||
|
||||
val myPublicKey = myKeyPair.public.composite
|
||||
// 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, myPublicKey)
|
||||
|
||||
val maybeSTX = sendAndReceive<SignedTransaction>(otherParty, hello)
|
||||
@ -116,7 +116,7 @@ object TwoPartyTradeProtocol {
|
||||
|
||||
// Download and check all the things that this transaction depends on and verify it is contract-valid,
|
||||
// even though it is missing signatures.
|
||||
subProtocol(ResolveTransactionsProtocol(wtx, otherParty))
|
||||
subFlow(ResolveTransactionsFlow(wtx, otherParty))
|
||||
|
||||
if (wtx.outputs.map { it.data }.sumCashBy(myPublicKey).withoutIssuer() != price)
|
||||
throw IllegalArgumentException("Transaction is not sending us the right amount of cash")
|
||||
@ -129,7 +129,7 @@ object TwoPartyTradeProtocol {
|
||||
// once we implement state pairing.
|
||||
//
|
||||
// but the goal of this code is not to be fully secure (yet), but rather, just to find good ways to
|
||||
// express protocol state machines on top of the messaging layer.
|
||||
// express flow state machines on top of the messaging layer.
|
||||
|
||||
return it
|
||||
}
|
||||
@ -156,7 +156,7 @@ object TwoPartyTradeProtocol {
|
||||
open class Buyer(val otherParty: Party,
|
||||
val notary: Party,
|
||||
val acceptablePrice: Amount<Currency>,
|
||||
val typeToBuy: Class<out OwnableState>) : ProtocolLogic<SignedTransaction>() {
|
||||
val typeToBuy: Class<out OwnableState>) : FlowLogic<SignedTransaction>() {
|
||||
|
||||
object RECEIVING : ProgressTracker.Step("Waiting for seller trading info")
|
||||
|
||||
@ -207,7 +207,7 @@ object TwoPartyTradeProtocol {
|
||||
|
||||
// Check the transaction that contains the state which is being resolved.
|
||||
// We only have a hash here, so if we don't know it already, we have to ask for it.
|
||||
subProtocol(ResolveTransactionsProtocol(setOf(it.assetForSale.ref.txhash), otherParty))
|
||||
subFlow(ResolveTransactionsFlow(setOf(it.assetForSale.ref.txhash), otherParty))
|
||||
|
||||
return it
|
||||
}
|
@ -14,6 +14,9 @@ import net.corda.core.node.services.ServiceInfo
|
||||
import net.corda.core.random63BitValue
|
||||
import net.corda.core.serialization.serialize
|
||||
import net.corda.core.utilities.LogHelper
|
||||
import net.corda.flows.NotaryError
|
||||
import net.corda.flows.NotaryException
|
||||
import net.corda.flows.NotaryFlow
|
||||
import net.corda.node.internal.AbstractNode
|
||||
import net.corda.node.internal.Node
|
||||
import net.corda.node.services.config.ConfigHelper
|
||||
@ -21,9 +24,6 @@ import net.corda.node.services.config.FullNodeConfiguration
|
||||
import net.corda.node.services.network.NetworkMapService
|
||||
import net.corda.node.services.transactions.RaftValidatingNotaryService
|
||||
import net.corda.node.utilities.databaseTransaction
|
||||
import net.corda.protocols.NotaryError
|
||||
import net.corda.protocols.NotaryException
|
||||
import net.corda.protocols.NotaryProtocol
|
||||
import net.corda.testing.freeLocalHostAndPort
|
||||
import org.junit.After
|
||||
import org.junit.Before
|
||||
@ -76,12 +76,12 @@ class DistributedNotaryTests {
|
||||
tx.toSignedTransaction(false)
|
||||
}
|
||||
|
||||
val buildProtocol = { NotaryProtocol.Client(stx) }
|
||||
val buildFlow = { NotaryFlow.Client(stx) }
|
||||
|
||||
val firstSpend = alice.services.startProtocol(buildProtocol())
|
||||
val firstSpend = alice.services.startFlow(buildFlow())
|
||||
firstSpend.resultFuture.get()
|
||||
|
||||
val secondSpend = alice.services.startProtocol(buildProtocol())
|
||||
val secondSpend = alice.services.startFlow(buildFlow())
|
||||
|
||||
val ex = assertFailsWith(ExecutionException::class) { secondSpend.resultFuture.get() }
|
||||
val error = (ex.cause as NotaryException).error as NotaryError.Conflict
|
||||
|
@ -91,31 +91,31 @@ interface APIServer {
|
||||
fun commitTransaction(tx: SerializedBytes<WireTransaction>, signatures: List<DigitalSignature.WithKey>): SecureHash
|
||||
|
||||
/**
|
||||
* This method would not return until the protocol is finished (hence the "Sync").
|
||||
* This method would not return until the flow is finished (hence the "Sync").
|
||||
*
|
||||
* Longer term we'd add an Async version that returns some kind of ProtocolInvocationRef that could be queried and
|
||||
* Longer term we'd add an Async version that returns some kind of FlowInvocationRef that could be queried and
|
||||
* would appear on some kind of event message that is broadcast informing of progress.
|
||||
*
|
||||
* Will throw exception if protocol fails.
|
||||
* Will throw exception if flow fails.
|
||||
*/
|
||||
fun invokeProtocolSync(type: ProtocolRef, args: Map<String, Any?>): Any?
|
||||
fun invokeFlowSync(type: FlowRef, args: Map<String, Any?>): Any?
|
||||
|
||||
// fun invokeProtocolAsync(type: ProtocolRef, args: Map<String, Any?>): ProtocolInstanceRef
|
||||
// fun invokeFlowAsync(type: FlowRef, args: Map<String, Any?>): FlowInstanceRef
|
||||
|
||||
/**
|
||||
* Fetch protocols that require a response to some prompt/question by a human (on the "bank" side).
|
||||
* Fetch flows that require a response to some prompt/question by a human (on the "bank" side).
|
||||
*/
|
||||
fun fetchProtocolsRequiringAttention(query: StatesQuery): Map<StateRef, ProtocolRequiringAttention>
|
||||
fun fetchFlowsRequiringAttention(query: StatesQuery): Map<StateRef, FlowRequiringAttention>
|
||||
|
||||
/**
|
||||
* Provide the response that a protocol is waiting for.
|
||||
* Provide the response that a flow is waiting for.
|
||||
*
|
||||
* @param protocol Should refer to a previously supplied ProtocolRequiringAttention.
|
||||
* @param stepId Which step of the protocol are we referring too.
|
||||
* @param choice Should be one of the choices presented in the ProtocolRequiringAttention.
|
||||
* @param flow Should refer to a previously supplied FlowRequiringAttention.
|
||||
* @param stepId Which step of the flow are we referring too.
|
||||
* @param choice Should be one of the choices presented in the FlowRequiringAttention.
|
||||
* @param args Any arguments required.
|
||||
*/
|
||||
fun provideProtocolResponse(protocol: ProtocolInstanceRef, choice: SecureHash, args: Map<String, Any?>)
|
||||
fun provideFlowResponse(flow: FlowInstanceRef, choice: SecureHash, args: Map<String, Any?>)
|
||||
|
||||
}
|
||||
|
||||
@ -131,20 +131,20 @@ data class ContractLedgerRef(val hash: SecureHash) : ContractDefRef
|
||||
|
||||
|
||||
/**
|
||||
* Encapsulates the protocol to be instantiated. e.g. TwoPartyTradeProtocol.Buyer.
|
||||
* Encapsulates the flow to be instantiated. e.g. TwoPartyTradeFlow.Buyer.
|
||||
*/
|
||||
interface ProtocolRef {
|
||||
interface FlowRef {
|
||||
|
||||
}
|
||||
|
||||
data class ProtocolClassRef(val className: String) : ProtocolRef
|
||||
data class FlowClassRef(val className: String) : FlowRef
|
||||
|
||||
data class ProtocolInstanceRef(val protocolInstance: SecureHash, val protocolClass: ProtocolClassRef, val protocolStepId: String)
|
||||
data class FlowInstanceRef(val flowInstance: SecureHash, val flowClass: FlowClassRef, val flowStepId: String)
|
||||
|
||||
/**
|
||||
* Thinking that Instant is OK for short lived protocol deadlines.
|
||||
* Thinking that Instant is OK for short lived flow deadlines.
|
||||
*/
|
||||
data class ProtocolRequiringAttention(val ref: ProtocolInstanceRef, val prompt: String, val choiceIdsToMessages: Map<SecureHash, String>, val dueBy: Instant)
|
||||
data class FlowRequiringAttention(val ref: FlowInstanceRef, val prompt: String, val choiceIdsToMessages: Map<SecureHash, String>, val dueBy: Instant)
|
||||
|
||||
|
||||
/**
|
||||
|
@ -1,7 +1,10 @@
|
||||
package net.corda.node.internal
|
||||
|
||||
import com.google.common.util.concurrent.ListenableFuture
|
||||
import net.corda.core.contracts.*
|
||||
import net.corda.core.contracts.ContractState
|
||||
import net.corda.core.contracts.DealState
|
||||
import net.corda.core.contracts.StateRef
|
||||
import net.corda.core.contracts.TransactionState
|
||||
import net.corda.core.crypto.DigitalSignature
|
||||
import net.corda.core.crypto.SecureHash
|
||||
import net.corda.core.node.services.linearHeadsOfType
|
||||
@ -64,25 +67,25 @@ class APIServerImpl(val node: AbstractNode) : APIServer {
|
||||
throw UnsupportedOperationException()
|
||||
}
|
||||
|
||||
override fun invokeProtocolSync(type: ProtocolRef, args: Map<String, Any?>): Any? {
|
||||
return invokeProtocolAsync(type, args).get()
|
||||
override fun invokeFlowSync(type: FlowRef, args: Map<String, Any?>): Any? {
|
||||
return invokeFlowAsync(type, args).get()
|
||||
}
|
||||
|
||||
private fun invokeProtocolAsync(type: ProtocolRef, args: Map<String, Any?>): ListenableFuture<out Any?> {
|
||||
if (type is ProtocolClassRef) {
|
||||
val protocolLogicRef = node.services.protocolLogicRefFactory.createKotlin(type.className, args)
|
||||
val protocolInstance = node.services.protocolLogicRefFactory.toProtocolLogic(protocolLogicRef)
|
||||
return node.services.startProtocol(protocolInstance).resultFuture
|
||||
private fun invokeFlowAsync(type: FlowRef, args: Map<String, Any?>): ListenableFuture<out Any?> {
|
||||
if (type is FlowClassRef) {
|
||||
val flowLogicRef = node.services.flowLogicRefFactory.createKotlin(type.className, args)
|
||||
val flowInstance = node.services.flowLogicRefFactory.toFlowLogic(flowLogicRef)
|
||||
return node.services.startFlow(flowInstance).resultFuture
|
||||
} else {
|
||||
throw UnsupportedOperationException("Unsupported ProtocolRef type: $type")
|
||||
throw UnsupportedOperationException("Unsupported FlowRef type: $type")
|
||||
}
|
||||
}
|
||||
|
||||
override fun fetchProtocolsRequiringAttention(query: StatesQuery): Map<StateRef, ProtocolRequiringAttention> {
|
||||
override fun fetchFlowsRequiringAttention(query: StatesQuery): Map<StateRef, FlowRequiringAttention> {
|
||||
throw UnsupportedOperationException()
|
||||
}
|
||||
|
||||
override fun provideProtocolResponse(protocol: ProtocolInstanceRef, choice: SecureHash, args: Map<String, Any?>) {
|
||||
override fun provideFlowResponse(flow: FlowInstanceRef, choice: SecureHash, args: Map<String, Any?>) {
|
||||
throw UnsupportedOperationException()
|
||||
}
|
||||
|
||||
|
@ -8,17 +8,20 @@ import com.google.common.util.concurrent.SettableFuture
|
||||
import net.corda.core.*
|
||||
import net.corda.core.crypto.Party
|
||||
import net.corda.core.crypto.X509Utilities
|
||||
import net.corda.core.flows.FlowLogic
|
||||
import net.corda.core.flows.FlowLogicRefFactory
|
||||
import net.corda.core.flows.FlowStateMachine
|
||||
import net.corda.core.messaging.SingleMessageRecipient
|
||||
import net.corda.core.node.*
|
||||
import net.corda.core.node.services.*
|
||||
import net.corda.core.node.services.NetworkMapCache.MapChangeType
|
||||
import net.corda.core.protocols.ProtocolLogic
|
||||
import net.corda.core.protocols.ProtocolLogicRefFactory
|
||||
import net.corda.core.protocols.ProtocolStateMachine
|
||||
import net.corda.core.serialization.SingletonSerializeAsToken
|
||||
import net.corda.core.serialization.deserialize
|
||||
import net.corda.core.serialization.serialize
|
||||
import net.corda.core.transactions.SignedTransaction
|
||||
import net.corda.flows.CashCommand
|
||||
import net.corda.flows.CashFlow
|
||||
import net.corda.flows.sendRequest
|
||||
import net.corda.node.api.APIServer
|
||||
import net.corda.node.services.api.*
|
||||
import net.corda.node.services.config.NodeConfiguration
|
||||
@ -30,7 +33,7 @@ import net.corda.node.services.keys.PersistentKeyManagementService
|
||||
import net.corda.node.services.messaging.RPCOps
|
||||
import net.corda.node.services.network.InMemoryNetworkMapCache
|
||||
import net.corda.node.services.network.NetworkMapService
|
||||
import net.corda.node.services.network.NetworkMapService.Companion.REGISTER_PROTOCOL_TOPIC
|
||||
import net.corda.node.services.network.NetworkMapService.Companion.REGISTER_FLOW_TOPIC
|
||||
import net.corda.node.services.network.NetworkMapService.RegistrationResponse
|
||||
import net.corda.node.services.network.NodeRegistration
|
||||
import net.corda.node.services.network.PersistentNetworkMapService
|
||||
@ -45,9 +48,6 @@ import net.corda.node.utilities.AddOrRemove
|
||||
import net.corda.node.utilities.AffinityExecutor
|
||||
import net.corda.node.utilities.configureDatabase
|
||||
import net.corda.node.utilities.databaseTransaction
|
||||
import net.corda.protocols.CashCommand
|
||||
import net.corda.protocols.CashProtocol
|
||||
import net.corda.protocols.sendRequest
|
||||
import org.jetbrains.exposed.sql.Database
|
||||
import org.slf4j.Logger
|
||||
import java.nio.file.FileAlreadyExistsException
|
||||
@ -66,7 +66,7 @@ import net.corda.core.crypto.generateKeyPair as cryptoGenerateKeyPair
|
||||
* I/O), or a mock implementation suitable for unit test environments.
|
||||
*
|
||||
* Marked as SingletonSerializeAsToken to prevent the invisible reference to AbstractNode in the ServiceHub accidentally
|
||||
* sweeping up the Node into the Kryo checkpoint serialization via any protocols holding a reference to ServiceHub.
|
||||
* sweeping up the Node into the Kryo checkpoint serialization via any flows holding a reference to ServiceHub.
|
||||
*/
|
||||
// TODO: Where this node is the initial network map service, currently no networkMapService is provided.
|
||||
// In theory the NodeInfo for the node should be passed in, instead, however currently this is constructed by the
|
||||
@ -95,7 +95,7 @@ abstract class AbstractNode(open val configuration: NodeConfiguration, val netwo
|
||||
protected val _servicesThatAcceptUploads = ArrayList<AcceptsFileUpload>()
|
||||
val servicesThatAcceptUploads: List<AcceptsFileUpload> = _servicesThatAcceptUploads
|
||||
|
||||
private val protocolFactories = ConcurrentHashMap<Class<*>, (Party) -> ProtocolLogic<*>>()
|
||||
private val flowFactories = ConcurrentHashMap<Class<*>, (Party) -> FlowLogic<*>>()
|
||||
protected val partyKeys = mutableSetOf<KeyPair>()
|
||||
|
||||
val services = object : ServiceHubInternal() {
|
||||
@ -112,18 +112,18 @@ abstract class AbstractNode(open val configuration: NodeConfiguration, val netwo
|
||||
|
||||
// Internal only
|
||||
override val monitoringService: MonitoringService = MonitoringService(MetricRegistry())
|
||||
override val protocolLogicRefFactory: ProtocolLogicRefFactory get() = protocolLogicFactory
|
||||
override val flowLogicRefFactory: FlowLogicRefFactory get() = flowLogicFactory
|
||||
|
||||
override fun <T> startProtocol(logic: ProtocolLogic<T>): ProtocolStateMachine<T> = smm.add(logic)
|
||||
override fun <T> startFlow(logic: FlowLogic<T>): FlowStateMachine<T> = smm.add(logic)
|
||||
|
||||
override fun registerProtocolInitiator(markerClass: KClass<*>, protocolFactory: (Party) -> ProtocolLogic<*>) {
|
||||
require(markerClass !in protocolFactories) { "${markerClass.java.name} has already been used to register a protocol" }
|
||||
log.info("Registering protocol ${markerClass.java.name}")
|
||||
protocolFactories[markerClass.java] = protocolFactory
|
||||
override fun registerFlowInitiator(markerClass: KClass<*>, flowFactory: (Party) -> FlowLogic<*>) {
|
||||
require(markerClass !in flowFactories) { "${markerClass.java.name} has already been used to register a flow" }
|
||||
log.info("Registering flow ${markerClass.java.name}")
|
||||
flowFactories[markerClass.java] = flowFactory
|
||||
}
|
||||
|
||||
override fun getProtocolFactory(markerClass: Class<*>): ((Party) -> ProtocolLogic<*>)? {
|
||||
return protocolFactories[markerClass]
|
||||
override fun getFlowFactory(markerClass: Class<*>): ((Party) -> FlowLogic<*>)? {
|
||||
return flowFactories[markerClass]
|
||||
}
|
||||
|
||||
override fun recordTransactions(txs: Iterable<SignedTransaction>) {
|
||||
@ -149,7 +149,7 @@ abstract class AbstractNode(open val configuration: NodeConfiguration, val netwo
|
||||
lateinit var netMapCache: NetworkMapCache
|
||||
lateinit var api: APIServer
|
||||
lateinit var scheduler: NodeSchedulerService
|
||||
lateinit var protocolLogicFactory: ProtocolLogicRefFactory
|
||||
lateinit var flowLogicFactory: FlowLogicRefFactory
|
||||
lateinit var schemas: SchemaService
|
||||
val customServices: ArrayList<Any> = ArrayList()
|
||||
protected val runOnStop: ArrayList<Runnable> = ArrayList()
|
||||
@ -204,8 +204,8 @@ abstract class AbstractNode(open val configuration: NodeConfiguration, val netwo
|
||||
// the identity key. But the infrastructure to make that easy isn't here yet.
|
||||
keyManagement = makeKeyManagementService()
|
||||
api = APIServerImpl(this@AbstractNode)
|
||||
protocolLogicFactory = initialiseProtocolLogicFactory()
|
||||
scheduler = NodeSchedulerService(database, services, protocolLogicFactory)
|
||||
flowLogicFactory = initialiseFlowLogicFactory()
|
||||
scheduler = NodeSchedulerService(database, services, flowLogicFactory)
|
||||
|
||||
val tokenizableServices = mutableListOf(storage, net, vault, keyManagement, identity, platformClock, scheduler)
|
||||
|
||||
@ -309,31 +309,32 @@ abstract class AbstractNode(open val configuration: NodeConfiguration, val netwo
|
||||
}
|
||||
}
|
||||
|
||||
private val defaultProtocolWhiteList: Map<Class<out ProtocolLogic<*>>, Set<Class<*>>> = mapOf(
|
||||
CashProtocol::class.java to setOf(
|
||||
private val defaultFlowWhiteList: Map<Class<out FlowLogic<*>>, Set<Class<*>>> = mapOf(
|
||||
CashFlow::class.java to setOf(
|
||||
CashCommand.IssueCash::class.java,
|
||||
CashCommand.PayCash::class.java,
|
||||
CashCommand.ExitCash::class.java
|
||||
)
|
||||
)
|
||||
private fun initialiseProtocolLogicFactory(): ProtocolLogicRefFactory {
|
||||
val protocolWhitelist = HashMap<String, Set<String>>()
|
||||
|
||||
for ((protocolClass, extraArgumentTypes) in defaultProtocolWhiteList) {
|
||||
private fun initialiseFlowLogicFactory(): FlowLogicRefFactory {
|
||||
val flowWhitelist = HashMap<String, Set<String>>()
|
||||
|
||||
for ((flowClass, extraArgumentTypes) in defaultFlowWhiteList) {
|
||||
val argumentWhitelistClassNames = HashSet(extraArgumentTypes.map { it.name })
|
||||
protocolClass.constructors.forEach {
|
||||
flowClass.constructors.forEach {
|
||||
it.parameters.mapTo(argumentWhitelistClassNames) { it.type.name }
|
||||
}
|
||||
protocolWhitelist.merge(protocolClass.name, argumentWhitelistClassNames, { x, y -> x + y })
|
||||
flowWhitelist.merge(flowClass.name, argumentWhitelistClassNames, { x, y -> x + y })
|
||||
}
|
||||
|
||||
for (plugin in pluginRegistries) {
|
||||
for ((className, classWhitelist) in plugin.requiredProtocols) {
|
||||
protocolWhitelist.merge(className, classWhitelist, { x, y -> x + y })
|
||||
for ((className, classWhitelist) in plugin.requiredFlows) {
|
||||
flowWhitelist.merge(className, classWhitelist, { x, y -> x + y })
|
||||
}
|
||||
}
|
||||
|
||||
return ProtocolLogicRefFactory(protocolWhitelist)
|
||||
return FlowLogicRefFactory(flowWhitelist)
|
||||
}
|
||||
|
||||
private fun buildPluginServices(tokenizableServices: MutableList<Any>): List<Any> {
|
||||
@ -406,7 +407,7 @@ abstract class AbstractNode(open val configuration: NodeConfiguration, val netwo
|
||||
val reg = NodeRegistration(info, instant.toEpochMilli(), type, expires)
|
||||
val legalIdentityKey = obtainLegalIdentityKey()
|
||||
val request = NetworkMapService.RegistrationRequest(reg.toWire(legalIdentityKey.private), net.myAddress)
|
||||
return net.sendRequest(REGISTER_PROTOCOL_TOPIC, request, networkMapAddr)
|
||||
return net.sendRequest(REGISTER_FLOW_TOPIC, request, networkMapAddr)
|
||||
}
|
||||
|
||||
protected open fun makeKeyManagementService(): KeyManagementService = PersistentKeyManagementService(partyKeys)
|
||||
|
@ -3,20 +3,18 @@ package net.corda.node.internal
|
||||
import net.corda.core.contracts.ContractState
|
||||
import net.corda.core.contracts.StateAndRef
|
||||
import net.corda.core.crypto.SecureHash
|
||||
import net.corda.core.crypto.keys
|
||||
import net.corda.core.crypto.toStringShort
|
||||
import net.corda.core.flows.FlowLogic
|
||||
import net.corda.core.node.NodeInfo
|
||||
import net.corda.core.node.ServiceHub
|
||||
import net.corda.core.node.services.NetworkMapCache
|
||||
import net.corda.core.node.services.StateMachineTransactionMapping
|
||||
import net.corda.core.node.services.Vault
|
||||
import net.corda.core.protocols.ProtocolLogic
|
||||
import net.corda.core.toObservable
|
||||
import net.corda.core.transactions.SignedTransaction
|
||||
import net.corda.core.utilities.ProgressTracker
|
||||
import net.corda.node.services.messaging.*
|
||||
import net.corda.node.services.startProtocolPermission
|
||||
import net.corda.node.services.statemachine.ProtocolStateMachineImpl
|
||||
import net.corda.node.services.startFlowPermission
|
||||
import net.corda.node.services.statemachine.FlowStateMachineImpl
|
||||
import net.corda.node.services.statemachine.StateMachineManager
|
||||
import net.corda.node.utilities.databaseTransaction
|
||||
import org.jetbrains.exposed.sql.Database
|
||||
@ -52,7 +50,7 @@ class CordaRPCOpsImpl(
|
||||
override fun stateMachinesAndUpdates(): Pair<List<StateMachineInfo>, Observable<StateMachineUpdate>> {
|
||||
val (allStateMachines, changes) = smm.track()
|
||||
return Pair(
|
||||
allStateMachines.map { StateMachineInfo.fromProtocolStateMachineImpl(it) },
|
||||
allStateMachines.map { StateMachineInfo.fromFlowStateMachineImpl(it) },
|
||||
changes.map { StateMachineUpdate.fromStateMachineChange(it) }
|
||||
)
|
||||
}
|
||||
@ -79,11 +77,11 @@ class CordaRPCOpsImpl(
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: Check that this protocol is annotated as being intended for RPC invocation
|
||||
override fun <T: Any> startProtocolDynamic(logicType: Class<out ProtocolLogic<T>>, vararg args: Any?): ProtocolHandle<T> {
|
||||
requirePermission(startProtocolPermission(logicType))
|
||||
val stateMachine = services.invokeProtocolAsync(logicType, *args) as ProtocolStateMachineImpl<T>
|
||||
return ProtocolHandle(
|
||||
// TODO: Check that this flow is annotated as being intended for RPC invocation
|
||||
override fun <T : Any> startFlowDynamic(logicType: Class<out FlowLogic<T>>, vararg args: Any?): FlowHandle<T> {
|
||||
requirePermission(startFlowPermission(logicType))
|
||||
val stateMachine = services.invokeFlowAsync(logicType, *args) as FlowStateMachineImpl<T>
|
||||
return FlowHandle(
|
||||
id = stateMachine.id,
|
||||
progress = stateMachine.logic.progressTracker?.changes ?: Observable.empty<ProgressTracker.Change>(),
|
||||
returnValue = stateMachine.resultFuture.toObservable()
|
||||
|
@ -61,7 +61,7 @@ class ConfigurationException(message: String) : Exception(message)
|
||||
* network map service, while bootstrapping a network.
|
||||
* @param advertisedServices The services this node advertises. This must be a subset of the services it runs,
|
||||
* but nodes are not required to advertise services they run (hence subset).
|
||||
* @param clock The clock used within the node and by all protocols etc.
|
||||
* @param clock The clock used within the node and by all flows etc.
|
||||
*/
|
||||
class Node(override val configuration: FullNodeConfiguration, networkMapAddress: SingleMessageRecipient?,
|
||||
advertisedServices: Set<ServiceInfo>, clock: Clock = NodeClock()) : AbstractNode(configuration, networkMapAddress, advertisedServices, clock) {
|
||||
@ -102,7 +102,7 @@ class Node(override val configuration: FullNodeConfiguration, networkMapAddress:
|
||||
// layer, which can then react to the backpressure. Artemis MQ in particular knows how to do flow control by paging
|
||||
// messages to disk rather than letting us run out of RAM.
|
||||
//
|
||||
// The primary work done by the server thread is execution of protocol logics, and related
|
||||
// The primary work done by the server thread is execution of flow logics, and related
|
||||
// serialisation/deserialisation work.
|
||||
override val serverThread = AffinityExecutor.ServiceAffinityExecutor("Node thread", 1)
|
||||
|
||||
|
@ -3,8 +3,7 @@ package net.corda.node.services
|
||||
import net.corda.core.node.CordaPluginRegistry
|
||||
import net.corda.core.node.PluginServiceHub
|
||||
import net.corda.core.serialization.SingletonSerializeAsToken
|
||||
import net.corda.node.services.api.ServiceHubInternal
|
||||
import net.corda.protocols.NotaryChangeProtocol
|
||||
import net.corda.flows.NotaryChangeFlow
|
||||
|
||||
object NotaryChange {
|
||||
class Plugin : CordaPluginRegistry() {
|
||||
@ -13,11 +12,11 @@ object NotaryChange {
|
||||
|
||||
/**
|
||||
* A service that monitors the network for requests for changing the notary of a state,
|
||||
* and immediately runs the [NotaryChangeProtocol] if the auto-accept criteria are met.
|
||||
* and immediately runs the [NotaryChangeFlow] if the auto-accept criteria are met.
|
||||
*/
|
||||
class Service(services: PluginServiceHub) : SingletonSerializeAsToken() {
|
||||
init {
|
||||
services.registerProtocolInitiator(NotaryChangeProtocol.Instigator::class) { NotaryChangeProtocol.Acceptor(it) }
|
||||
services.registerFlowInitiator(NotaryChangeFlow.Instigator::class) { NotaryChangeFlow.Acceptor(it) }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,7 +1,7 @@
|
||||
package net.corda.node.services
|
||||
|
||||
import com.typesafe.config.Config
|
||||
import net.corda.core.protocols.ProtocolLogic
|
||||
import net.corda.core.flows.FlowLogic
|
||||
import net.corda.node.services.config.getListOrElse
|
||||
|
||||
/**
|
||||
@ -41,5 +41,5 @@ data class User(val username: String, val password: String, val permissions: Set
|
||||
override fun toString(): String = "${javaClass.simpleName}($username, permissions=$permissions)"
|
||||
}
|
||||
|
||||
fun <P : ProtocolLogic<*>> startProtocolPermission(clazz: Class<P>) = "StartProtocol.${clazz.name}"
|
||||
inline fun <reified P : ProtocolLogic<*>> startProtocolPermission(): String = startProtocolPermission(P::class.java)
|
||||
fun <P : FlowLogic<*>> startFlowPermission(clazz: Class<P>) = "StartFlow.${clazz.name}"
|
||||
inline fun <reified P : FlowLogic<*>> startFlowPermission(): String = startFlowPermission(P::class.java)
|
||||
|
@ -7,7 +7,7 @@ import net.corda.core.node.services.DEFAULT_SESSION_ID
|
||||
import net.corda.core.serialization.SingletonSerializeAsToken
|
||||
import net.corda.core.serialization.deserialize
|
||||
import net.corda.core.serialization.serialize
|
||||
import net.corda.protocols.ServiceRequestMessage
|
||||
import net.corda.flows.ServiceRequestMessage
|
||||
import javax.annotation.concurrent.ThreadSafe
|
||||
|
||||
/**
|
||||
|
@ -2,7 +2,7 @@ package net.corda.node.services.api
|
||||
|
||||
import net.corda.core.crypto.SecureHash
|
||||
import net.corda.core.serialization.SerializedBytes
|
||||
import net.corda.node.services.statemachine.ProtocolStateMachineImpl
|
||||
import net.corda.node.services.statemachine.FlowStateMachineImpl
|
||||
|
||||
/**
|
||||
* Thread-safe storage of fiber checkpoints.
|
||||
@ -30,7 +30,7 @@ interface CheckpointStorage {
|
||||
}
|
||||
|
||||
// This class will be serialised, so everything it points to transitively must also be serialisable (with Kryo).
|
||||
class Checkpoint(val serializedFiber: SerializedBytes<ProtocolStateMachineImpl<*>>) {
|
||||
class Checkpoint(val serializedFiber: SerializedBytes<FlowStateMachineImpl<*>>) {
|
||||
|
||||
val id: SecureHash get() = serializedFiber.hash
|
||||
|
||||
|
@ -1,14 +1,14 @@
|
||||
package net.corda.node.services.api
|
||||
|
||||
import com.google.common.util.concurrent.ListenableFuture
|
||||
import net.corda.core.flows.FlowLogic
|
||||
import net.corda.core.flows.FlowLogicRefFactory
|
||||
import net.corda.core.flows.FlowStateMachine
|
||||
import net.corda.core.messaging.MessagingService
|
||||
import net.corda.core.node.PluginServiceHub
|
||||
import net.corda.core.node.services.TxWritableStorageService
|
||||
import net.corda.core.protocols.ProtocolLogic
|
||||
import net.corda.core.protocols.ProtocolLogicRefFactory
|
||||
import net.corda.core.protocols.ProtocolStateMachine
|
||||
import net.corda.core.transactions.SignedTransaction
|
||||
import net.corda.node.services.statemachine.ProtocolStateMachineImpl
|
||||
import net.corda.node.services.statemachine.FlowStateMachineImpl
|
||||
import org.slf4j.LoggerFactory
|
||||
|
||||
interface MessagingServiceInternal : MessagingService {
|
||||
@ -38,7 +38,7 @@ private val log = LoggerFactory.getLogger(ServiceHubInternal::class.java)
|
||||
|
||||
abstract class ServiceHubInternal : PluginServiceHub {
|
||||
abstract val monitoringService: MonitoringService
|
||||
abstract val protocolLogicRefFactory: ProtocolLogicRefFactory
|
||||
abstract val flowLogicRefFactory: FlowLogicRefFactory
|
||||
abstract val schemaService: SchemaService
|
||||
|
||||
abstract override val networkService: MessagingServiceInternal
|
||||
@ -51,7 +51,7 @@ abstract class ServiceHubInternal : PluginServiceHub {
|
||||
* @param txs The transactions to record.
|
||||
*/
|
||||
internal fun recordTransactionsInternal(writableStorageService: TxWritableStorageService, txs: Iterable<SignedTransaction>) {
|
||||
val stateMachineRunId = ProtocolStateMachineImpl.currentStateMachine()?.id
|
||||
val stateMachineRunId = FlowStateMachineImpl.currentStateMachine()?.id
|
||||
if (stateMachineRunId != null) {
|
||||
txs.forEach {
|
||||
storageService.stateMachineRecordedTransactionMapping.addMapping(stateMachineRunId, it.id)
|
||||
@ -68,12 +68,12 @@ abstract class ServiceHubInternal : PluginServiceHub {
|
||||
* between SMM and the scheduler. That particular problem should also be resolved by the service manager work
|
||||
* itself, at which point this method would not be needed (by the scheduler).
|
||||
*/
|
||||
abstract fun <T> startProtocol(logic: ProtocolLogic<T>): ProtocolStateMachine<T>
|
||||
abstract fun <T> startFlow(logic: FlowLogic<T>): FlowStateMachine<T>
|
||||
|
||||
override fun <T : Any> invokeProtocolAsync(logicType: Class<out ProtocolLogic<T>>, vararg args: Any?): ProtocolStateMachine<T> {
|
||||
val logicRef = protocolLogicRefFactory.create(logicType, *args)
|
||||
override fun <T : Any> invokeFlowAsync(logicType: Class<out FlowLogic<T>>, vararg args: Any?): FlowStateMachine<T> {
|
||||
val logicRef = flowLogicRefFactory.create(logicType, *args)
|
||||
@Suppress("UNCHECKED_CAST")
|
||||
val logic = protocolLogicRefFactory.toProtocolLogic(logicRef) as ProtocolLogic<T>
|
||||
return startProtocol(logic)
|
||||
val logic = flowLogicRefFactory.toFlowLogic(logicRef) as FlowLogic<T>
|
||||
return startFlow(logic)
|
||||
}
|
||||
}
|
||||
|
@ -2,21 +2,21 @@ package net.corda.node.services.events
|
||||
|
||||
import co.paralleluniverse.fibers.Suspendable
|
||||
import com.google.common.util.concurrent.SettableFuture
|
||||
import kotlinx.support.jdk8.collections.compute
|
||||
import net.corda.core.ThreadBox
|
||||
import net.corda.core.contracts.SchedulableState
|
||||
import net.corda.core.contracts.ScheduledActivity
|
||||
import net.corda.core.contracts.ScheduledStateRef
|
||||
import net.corda.core.contracts.StateRef
|
||||
import net.corda.core.flows.FlowLogic
|
||||
import net.corda.core.flows.FlowLogicRefFactory
|
||||
import net.corda.core.node.services.SchedulerService
|
||||
import net.corda.core.protocols.ProtocolLogic
|
||||
import net.corda.core.protocols.ProtocolLogicRefFactory
|
||||
import net.corda.core.serialization.SingletonSerializeAsToken
|
||||
import net.corda.core.utilities.ProgressTracker
|
||||
import net.corda.core.utilities.loggerFor
|
||||
import net.corda.core.utilities.trace
|
||||
import net.corda.node.services.api.ServiceHubInternal
|
||||
import net.corda.node.utilities.*
|
||||
import kotlinx.support.jdk8.collections.compute
|
||||
import org.jetbrains.exposed.sql.Database
|
||||
import org.jetbrains.exposed.sql.ResultRow
|
||||
import org.jetbrains.exposed.sql.statements.InsertStatement
|
||||
@ -38,14 +38,14 @@ import javax.annotation.concurrent.ThreadSafe
|
||||
* but that starts to sound a lot like off-ledger state.
|
||||
*
|
||||
* @param services Core node services.
|
||||
* @param protocolLogicRefFactory Factory for restoring [ProtocolLogic] instances from references.
|
||||
* @param flowLogicRefFactory Factory for restoring [FlowLogic] instances from references.
|
||||
* @param schedulerTimerExecutor The executor the scheduler blocks on waiting for the clock to advance to the next
|
||||
* activity. Only replace this for unit testing purposes. This is not the executor the [ProtocolLogic] is launched on.
|
||||
* activity. Only replace this for unit testing purposes. This is not the executor the [FlowLogic] is launched on.
|
||||
*/
|
||||
@ThreadSafe
|
||||
class NodeSchedulerService(private val database: Database,
|
||||
private val services: ServiceHubInternal,
|
||||
private val protocolLogicRefFactory: ProtocolLogicRefFactory,
|
||||
private val flowLogicRefFactory: FlowLogicRefFactory,
|
||||
private val schedulerTimerExecutor: Executor = Executors.newSingleThreadExecutor())
|
||||
: SchedulerService, SingletonSerializeAsToken() {
|
||||
|
||||
@ -87,7 +87,7 @@ class NodeSchedulerService(private val database: Database,
|
||||
|
||||
private val mutex = ThreadBox(InnerState())
|
||||
|
||||
// We need the [StateMachineManager] to be constructed before this is called in case it schedules a protocol.
|
||||
// We need the [StateMachineManager] to be constructed before this is called in case it schedules a flow.
|
||||
fun start() {
|
||||
mutex.locked {
|
||||
recomputeEarliest()
|
||||
@ -152,10 +152,10 @@ class NodeSchedulerService(private val database: Database,
|
||||
}
|
||||
|
||||
private fun onTimeReached(scheduledState: ScheduledStateRef) {
|
||||
services.startProtocol(RunScheduled(scheduledState, this@NodeSchedulerService))
|
||||
services.startFlow(RunScheduled(scheduledState, this@NodeSchedulerService))
|
||||
}
|
||||
|
||||
class RunScheduled(val scheduledState: ScheduledStateRef, val scheduler: NodeSchedulerService) : ProtocolLogic<Unit>() {
|
||||
class RunScheduled(val scheduledState: ScheduledStateRef, val scheduler: NodeSchedulerService) : FlowLogic<Unit>() {
|
||||
companion object {
|
||||
object RUNNING : ProgressTracker.Step("Running scheduled...")
|
||||
|
||||
@ -169,9 +169,9 @@ class NodeSchedulerService(private val database: Database,
|
||||
progressTracker.currentStep = RUNNING
|
||||
|
||||
// Ensure we are still scheduled.
|
||||
val scheduledLogic: ProtocolLogic<*>? = getScheduledLogic()
|
||||
val scheduledLogic: FlowLogic<*>? = getScheduledLogic()
|
||||
if(scheduledLogic != null) {
|
||||
subProtocol(scheduledLogic)
|
||||
subFlow(scheduledLogic)
|
||||
}
|
||||
}
|
||||
|
||||
@ -180,16 +180,16 @@ class NodeSchedulerService(private val database: Database,
|
||||
val state = txState.data as SchedulableState
|
||||
return try {
|
||||
// This can throw as running contract code.
|
||||
state.nextScheduledActivity(scheduledState.ref, scheduler.protocolLogicRefFactory)
|
||||
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(): ProtocolLogic<*>? {
|
||||
private fun getScheduledLogic(): FlowLogic<*>? {
|
||||
val scheduledActivity = getScheduledaActivity()
|
||||
var scheduledLogic: ProtocolLogic<*>? = null
|
||||
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) { ref, value ->
|
||||
@ -201,11 +201,11 @@ class NodeSchedulerService(private val database: Database,
|
||||
logger.info("Scheduled state $scheduledState has rescheduled to ${scheduledActivity.scheduledAt}.")
|
||||
ScheduledStateRef(scheduledState.ref, scheduledActivity.scheduledAt)
|
||||
} else {
|
||||
// TODO: ProtocolLogicRefFactory needs to sort out the class loader etc
|
||||
val logic = scheduler.protocolLogicRefFactory.toProtocolLogic(scheduledActivity.logicRef)
|
||||
logger.trace { "Scheduler starting ProtocolLogic $logic" }
|
||||
// ProtocolLogic will be checkpointed by the time this returns.
|
||||
//scheduler.services.startProtocolAndForget(logic)
|
||||
// TODO: FlowLogicRefFactory needs to sort out the class loader etc
|
||||
val logic = scheduler.flowLogicRefFactory.toFlowLogic(scheduledActivity.logicRef)
|
||||
logger.trace { "Scheduler starting FlowLogic $logic" }
|
||||
// FlowLogic will be checkpointed by the time this returns.
|
||||
//scheduler.services.startFlowAndForget(logic)
|
||||
scheduledLogic = logic
|
||||
null
|
||||
}
|
||||
|
@ -4,7 +4,7 @@ import net.corda.core.contracts.ContractState
|
||||
import net.corda.core.contracts.SchedulableState
|
||||
import net.corda.core.contracts.ScheduledStateRef
|
||||
import net.corda.core.contracts.StateAndRef
|
||||
import net.corda.core.protocols.ProtocolLogicRefFactory
|
||||
import net.corda.core.flows.FlowLogicRefFactory
|
||||
import net.corda.node.services.api.ServiceHubInternal
|
||||
|
||||
/**
|
||||
@ -15,14 +15,14 @@ class ScheduledActivityObserver(val services: ServiceHubInternal) {
|
||||
init {
|
||||
services.vaultService.updates.subscribe { update ->
|
||||
update.consumed.forEach { services.schedulerService.unscheduleStateActivity(it) }
|
||||
update.produced.forEach { scheduleStateActivity(it, services.protocolLogicRefFactory) }
|
||||
update.produced.forEach { scheduleStateActivity(it, services.flowLogicRefFactory) }
|
||||
}
|
||||
}
|
||||
|
||||
private fun scheduleStateActivity(produced: StateAndRef<ContractState>, protocolLogicRefFactory: ProtocolLogicRefFactory) {
|
||||
private fun scheduleStateActivity(produced: StateAndRef<ContractState>, flowLogicRefFactory: FlowLogicRefFactory) {
|
||||
val producedState = produced.state.data
|
||||
if (producedState is SchedulableState) {
|
||||
val scheduledAt = sandbox { producedState.nextScheduledActivity(produced.ref, protocolLogicRefFactory)?.scheduledAt } ?: return
|
||||
val scheduledAt = sandbox { producedState.nextScheduledActivity(produced.ref, flowLogicRefFactory)?.scheduledAt } ?: return
|
||||
services.schedulerService.scheduleStateActivity(ScheduledStateRef(produced.ref, scheduledAt))
|
||||
}
|
||||
}
|
||||
|
@ -15,7 +15,7 @@ import javax.annotation.concurrent.ThreadSafe
|
||||
*
|
||||
* - Probably be accessed via the network layer as an internal node service i.e. via a message queue, so it can run
|
||||
* on a separate/firewalled service.
|
||||
* - Use the protocol framework so requests to fetch keys can be suspended whilst a human signs off on the request.
|
||||
* - Use the flow framework so requests to fetch keys can be suspended whilst a human signs off on the request.
|
||||
* - Use deterministic key derivation.
|
||||
* - Possibly have some sort of TREZOR-like two-factor authentication ability.
|
||||
*
|
||||
|
@ -158,7 +158,7 @@ class ArtemisMessagingServer(override val config: NodeConfiguration,
|
||||
log.error("Queue created for a peer that we don't know from the network map: $queueName")
|
||||
}
|
||||
} catch (e: AddressFormatException) {
|
||||
log.error("Protocol violation: Could not parse queue name as Base 58: $queueName")
|
||||
log.error("Flow violation: Could not parse queue name as Base 58: $queueName")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -3,29 +3,29 @@ package net.corda.node.services.messaging
|
||||
import net.corda.core.contracts.ContractState
|
||||
import net.corda.core.contracts.StateAndRef
|
||||
import net.corda.core.crypto.SecureHash
|
||||
import net.corda.core.flows.FlowLogic
|
||||
import net.corda.core.flows.StateMachineRunId
|
||||
import net.corda.core.node.NodeInfo
|
||||
import net.corda.core.node.services.NetworkMapCache
|
||||
import net.corda.core.node.services.StateMachineTransactionMapping
|
||||
import net.corda.core.node.services.Vault
|
||||
import net.corda.core.protocols.ProtocolLogic
|
||||
import net.corda.core.protocols.StateMachineRunId
|
||||
import net.corda.core.transactions.SignedTransaction
|
||||
import net.corda.core.utilities.ProgressTracker
|
||||
import net.corda.node.services.statemachine.ProtocolStateMachineImpl
|
||||
import net.corda.node.services.statemachine.FlowStateMachineImpl
|
||||
import net.corda.node.services.statemachine.StateMachineManager
|
||||
import net.corda.node.utilities.AddOrRemove
|
||||
import rx.Observable
|
||||
|
||||
data class StateMachineInfo(
|
||||
val id: StateMachineRunId,
|
||||
val protocolLogicClassName: String,
|
||||
val flowLogicClassName: String,
|
||||
val progressTrackerStepAndUpdates: Pair<String, Observable<String>>?
|
||||
) {
|
||||
companion object {
|
||||
fun fromProtocolStateMachineImpl(psm: ProtocolStateMachineImpl<*>): StateMachineInfo {
|
||||
fun fromFlowStateMachineImpl(psm: FlowStateMachineImpl<*>): StateMachineInfo {
|
||||
return StateMachineInfo(
|
||||
id = psm.id,
|
||||
protocolLogicClassName = psm.logic.javaClass.simpleName,
|
||||
flowLogicClassName = psm.logic.javaClass.simpleName,
|
||||
progressTrackerStepAndUpdates = psm.logic.track()
|
||||
)
|
||||
}
|
||||
@ -42,7 +42,7 @@ sealed class StateMachineUpdate(val id: StateMachineRunId) {
|
||||
AddOrRemove.ADD -> {
|
||||
val stateMachineInfo = StateMachineInfo(
|
||||
id = change.id,
|
||||
protocolLogicClassName = change.logic.javaClass.simpleName,
|
||||
flowLogicClassName = change.logic.javaClass.simpleName,
|
||||
progressTrackerStepAndUpdates = change.logic.track()
|
||||
)
|
||||
StateMachineUpdate.Added(stateMachineInfo)
|
||||
@ -92,11 +92,11 @@ interface CordaRPCOps : RPCOps {
|
||||
fun networkMapUpdates(): Pair<List<NodeInfo>, Observable<NetworkMapCache.MapChange>>
|
||||
|
||||
/**
|
||||
* Start the given protocol with the given arguments, returning an [Observable] with a single observation of the
|
||||
* result of running the protocol.
|
||||
* Start the given flow with the given arguments, returning an [Observable] with a single observation of the
|
||||
* result of running the flow.
|
||||
*/
|
||||
@RPCReturnsObservables
|
||||
fun <T: Any> startProtocolDynamic(logicType: Class<out ProtocolLogic<T>>, vararg args: Any?): ProtocolHandle<T>
|
||||
fun <T : Any> startFlowDynamic(logicType: Class<out FlowLogic<T>>, vararg args: Any?): FlowHandle<T>
|
||||
|
||||
/**
|
||||
* Returns Node's identity, assuming this will not change while the node is running.
|
||||
@ -115,46 +115,50 @@ interface CordaRPCOps : RPCOps {
|
||||
}
|
||||
|
||||
/**
|
||||
* These allow type safe invocations of protocols from Kotlin, e.g.:
|
||||
* These allow type safe invocations of flows from Kotlin, e.g.:
|
||||
*
|
||||
* val rpc: CordaRPCOps = (..)
|
||||
* rpc.startProtocol(::ResolveTransactionsProtocol, setOf<SecureHash>(), aliceIdentity)
|
||||
* rpc.startFlow(::ResolveTransactionsFlow, setOf<SecureHash>(), aliceIdentity)
|
||||
*
|
||||
* Note that the passed in constructor function is only used for unification of other type parameters and reification of
|
||||
* the Class instance of the protocol. This could be changed to use the constructor function directly.
|
||||
* the Class instance of the flow. This could be changed to use the constructor function directly.
|
||||
*/
|
||||
inline fun <T : Any, reified R : ProtocolLogic<T>> CordaRPCOps.startProtocol(
|
||||
inline fun <T : Any, reified R : FlowLogic<T>> CordaRPCOps.startFlow(
|
||||
@Suppress("UNUSED_PARAMETER")
|
||||
protocolConstructor: () -> R
|
||||
) = startProtocolDynamic(R::class.java)
|
||||
inline fun <T : Any, A, reified R : ProtocolLogic<T>> CordaRPCOps.startProtocol(
|
||||
flowConstructor: () -> R
|
||||
) = startFlowDynamic(R::class.java)
|
||||
|
||||
inline fun <T : Any, A, reified R : FlowLogic<T>> CordaRPCOps.startFlow(
|
||||
@Suppress("UNUSED_PARAMETER")
|
||||
protocolConstructor: (A) -> R,
|
||||
flowConstructor: (A) -> R,
|
||||
arg0: A
|
||||
) = startProtocolDynamic(R::class.java, arg0)
|
||||
inline fun <T : Any, A, B, reified R : ProtocolLogic<T>> CordaRPCOps.startProtocol(
|
||||
) = startFlowDynamic(R::class.java, arg0)
|
||||
|
||||
inline fun <T : Any, A, B, reified R : FlowLogic<T>> CordaRPCOps.startFlow(
|
||||
@Suppress("UNUSED_PARAMETER")
|
||||
protocolConstructor: (A, B) -> R,
|
||||
flowConstructor: (A, B) -> R,
|
||||
arg0: A,
|
||||
arg1: B
|
||||
) = startProtocolDynamic(R::class.java, arg0, arg1)
|
||||
inline fun <T : Any, A, B, C, reified R: ProtocolLogic<T>> CordaRPCOps.startProtocol(
|
||||
) = startFlowDynamic(R::class.java, arg0, arg1)
|
||||
|
||||
inline fun <T : Any, A, B, C, reified R : FlowLogic<T>> CordaRPCOps.startFlow(
|
||||
@Suppress("UNUSED_PARAMETER")
|
||||
protocolConstructor: (A, B, C) -> R,
|
||||
flowConstructor: (A, B, C) -> R,
|
||||
arg0: A,
|
||||
arg1: B,
|
||||
arg2: C
|
||||
) = startProtocolDynamic(R::class.java, arg0, arg1, arg2)
|
||||
inline fun <T : Any, A, B, C, D, reified R : ProtocolLogic<T>> CordaRPCOps.startProtocol(
|
||||
) = startFlowDynamic(R::class.java, arg0, arg1, arg2)
|
||||
|
||||
inline fun <T : Any, A, B, C, D, reified R : FlowLogic<T>> CordaRPCOps.startFlow(
|
||||
@Suppress("UNUSED_PARAMETER")
|
||||
protocolConstructor: (A, B, C, D) -> R,
|
||||
flowConstructor: (A, B, C, D) -> R,
|
||||
arg0: A,
|
||||
arg1: B,
|
||||
arg2: C,
|
||||
arg3: D
|
||||
) = startProtocolDynamic(R::class.java, arg0, arg1, arg2, arg3)
|
||||
) = startFlowDynamic(R::class.java, arg0, arg1, arg2, arg3)
|
||||
|
||||
data class ProtocolHandle<A>(
|
||||
data class FlowHandle<A>(
|
||||
val id: StateMachineRunId,
|
||||
val progress: Observable<ProgressTracker.Change>,
|
||||
val returnValue: Observable<A>
|
||||
|
@ -19,14 +19,14 @@ import net.corda.core.crypto.CompositeKey
|
||||
import net.corda.core.crypto.DigitalSignature
|
||||
import net.corda.core.crypto.Party
|
||||
import net.corda.core.crypto.SecureHash
|
||||
import net.corda.core.flows.StateMachineRunId
|
||||
import net.corda.core.node.*
|
||||
import net.corda.core.node.services.*
|
||||
import net.corda.core.protocols.StateMachineRunId
|
||||
import net.corda.core.serialization.*
|
||||
import net.corda.core.transactions.SignedTransaction
|
||||
import net.corda.core.transactions.WireTransaction
|
||||
import net.corda.flows.CashFlowResult
|
||||
import net.corda.node.services.User
|
||||
import net.corda.protocols.CashProtocolResult
|
||||
import net.i2p.crypto.eddsa.EdDSAPrivateKey
|
||||
import net.i2p.crypto.eddsa.EdDSAPublicKey
|
||||
import org.apache.activemq.artemis.api.core.SimpleString
|
||||
@ -181,8 +181,8 @@ private class RPCKryo(observableSerializer: Serializer<Observable<Any>>? = null)
|
||||
register(Cash.Clauses.ConserveAmount::class.java)
|
||||
register(listOf(Unit).javaClass) // SingletonList
|
||||
register(setOf(Unit).javaClass) // SingletonSet
|
||||
register(CashProtocolResult.Success::class.java)
|
||||
register(CashProtocolResult.Failed::class.java)
|
||||
register(CashFlowResult.Success::class.java)
|
||||
register(CashFlowResult.Failed::class.java)
|
||||
register(ServiceEntry::class.java)
|
||||
register(NodeInfo::class.java)
|
||||
register(PhysicalLocation::class.java)
|
||||
@ -215,7 +215,7 @@ private class RPCKryo(observableSerializer: Serializer<Observable<Any>>? = null)
|
||||
register(Array<StackTraceElement>::class.java, read = { kryo, input -> emptyArray() }, write = { kryo, output, o -> })
|
||||
register(Collections.unmodifiableList(emptyList<String>()).javaClass)
|
||||
register(PermissionException::class.java)
|
||||
register(ProtocolHandle::class.java)
|
||||
register(FlowHandle::class.java)
|
||||
register(KryoException::class.java)
|
||||
register(StringBuffer::class.java)
|
||||
pluginRegistries.forEach { it.registerRPCKryoTypes(this) }
|
||||
|
@ -22,12 +22,12 @@ import net.corda.core.randomOrNull
|
||||
import net.corda.core.serialization.SingletonSerializeAsToken
|
||||
import net.corda.core.serialization.deserialize
|
||||
import net.corda.core.serialization.serialize
|
||||
import net.corda.node.services.network.NetworkMapService.Companion.FETCH_PROTOCOL_TOPIC
|
||||
import net.corda.node.services.network.NetworkMapService.Companion.SUBSCRIPTION_PROTOCOL_TOPIC
|
||||
import net.corda.flows.sendRequest
|
||||
import net.corda.node.services.network.NetworkMapService.Companion.FETCH_FLOW_TOPIC
|
||||
import net.corda.node.services.network.NetworkMapService.Companion.SUBSCRIPTION_FLOW_TOPIC
|
||||
import net.corda.node.services.network.NetworkMapService.FetchMapResponse
|
||||
import net.corda.node.services.network.NetworkMapService.SubscribeResponse
|
||||
import net.corda.node.utilities.AddOrRemove
|
||||
import net.corda.protocols.sendRequest
|
||||
import rx.Observable
|
||||
import rx.subjects.PublishSubject
|
||||
import java.security.SignatureException
|
||||
@ -106,10 +106,10 @@ open class InMemoryNetworkMapCache : SingletonSerializeAsToken(), NetworkMapCach
|
||||
ifChangedSinceVer: Int?): ListenableFuture<Unit> {
|
||||
if (subscribe && !registeredForPush) {
|
||||
// Add handler to the network, for updates received from the remote network map service.
|
||||
net.addMessageHandler(NetworkMapService.PUSH_PROTOCOL_TOPIC, DEFAULT_SESSION_ID) { message, r ->
|
||||
net.addMessageHandler(NetworkMapService.PUSH_FLOW_TOPIC, DEFAULT_SESSION_ID) { message, r ->
|
||||
try {
|
||||
val req = message.data.deserialize<NetworkMapService.Update>()
|
||||
val ackMessage = net.createMessage(NetworkMapService.PUSH_ACK_PROTOCOL_TOPIC, DEFAULT_SESSION_ID,
|
||||
val ackMessage = net.createMessage(NetworkMapService.PUSH_ACK_FLOW_TOPIC, DEFAULT_SESSION_ID,
|
||||
NetworkMapService.UpdateAcknowledge(req.mapVersion, net.myAddress).serialize().bytes)
|
||||
net.send(ackMessage, req.replyTo)
|
||||
processUpdatePush(req)
|
||||
@ -124,7 +124,7 @@ open class InMemoryNetworkMapCache : SingletonSerializeAsToken(), NetworkMapCach
|
||||
|
||||
// Fetch the network map and register for updates at the same time
|
||||
val req = NetworkMapService.FetchMapRequest(subscribe, ifChangedSinceVer, net.myAddress)
|
||||
val future = net.sendRequest<FetchMapResponse>(FETCH_PROTOCOL_TOPIC, req, networkMapAddress).map { resp ->
|
||||
val future = net.sendRequest<FetchMapResponse>(FETCH_FLOW_TOPIC, req, networkMapAddress).map { resp ->
|
||||
// We may not receive any nodes back, if the map hasn't changed since the version specified
|
||||
resp.nodes?.forEach { processRegistration(it) }
|
||||
Unit
|
||||
@ -161,7 +161,7 @@ open class InMemoryNetworkMapCache : SingletonSerializeAsToken(), NetworkMapCach
|
||||
override fun deregisterForUpdates(net: MessagingService, service: NodeInfo): ListenableFuture<Unit> {
|
||||
// Fetch the network map and register for updates at the same time
|
||||
val req = NetworkMapService.SubscribeRequest(false, net.myAddress)
|
||||
val future = net.sendRequest<SubscribeResponse>(SUBSCRIPTION_PROTOCOL_TOPIC, req, service.address).map {
|
||||
val future = net.sendRequest<SubscribeResponse>(SUBSCRIPTION_FLOW_TOPIC, req, service.address).map {
|
||||
if (it.confirmed) Unit else throw NetworkCacheError.DeregistrationFailed()
|
||||
}
|
||||
_registrationFuture.setFuture(future)
|
||||
|
@ -20,10 +20,10 @@ import net.corda.core.serialization.SerializedBytes
|
||||
import net.corda.core.serialization.deserialize
|
||||
import net.corda.core.serialization.serialize
|
||||
import net.corda.core.utilities.loggerFor
|
||||
import net.corda.flows.ServiceRequestMessage
|
||||
import net.corda.node.services.api.AbstractNodeService
|
||||
import net.corda.node.services.api.ServiceHubInternal
|
||||
import net.corda.node.utilities.AddOrRemove
|
||||
import net.corda.protocols.ServiceRequestMessage
|
||||
import java.security.PrivateKey
|
||||
import java.security.SignatureException
|
||||
import java.time.Instant
|
||||
@ -51,15 +51,15 @@ interface NetworkMapService {
|
||||
|
||||
companion object {
|
||||
val DEFAULT_EXPIRATION_PERIOD = Period.ofWeeks(4)
|
||||
val FETCH_PROTOCOL_TOPIC = "platform.network_map.fetch"
|
||||
val QUERY_PROTOCOL_TOPIC = "platform.network_map.query"
|
||||
val REGISTER_PROTOCOL_TOPIC = "platform.network_map.register"
|
||||
val SUBSCRIPTION_PROTOCOL_TOPIC = "platform.network_map.subscribe"
|
||||
val FETCH_FLOW_TOPIC = "platform.network_map.fetch"
|
||||
val QUERY_FLOW_TOPIC = "platform.network_map.query"
|
||||
val REGISTER_FLOW_TOPIC = "platform.network_map.register"
|
||||
val SUBSCRIPTION_FLOW_TOPIC = "platform.network_map.subscribe"
|
||||
// Base topic used when pushing out updates to the network map. Consumed, for example, by the map cache.
|
||||
// When subscribing to these updates, remember they must be acknowledged
|
||||
val PUSH_PROTOCOL_TOPIC = "platform.network_map.push"
|
||||
val PUSH_FLOW_TOPIC = "platform.network_map.push"
|
||||
// Base topic for messages acknowledging pushed updates
|
||||
val PUSH_ACK_PROTOCOL_TOPIC = "platform.network_map.push_ack"
|
||||
val PUSH_ACK_FLOW_TOPIC = "platform.network_map.push_ack"
|
||||
|
||||
val logger = loggerFor<NetworkMapService>()
|
||||
|
||||
@ -142,19 +142,19 @@ abstract class AbstractNetworkMapService
|
||||
|
||||
protected fun setup() {
|
||||
// Register message handlers
|
||||
handlers += addMessageHandler(NetworkMapService.FETCH_PROTOCOL_TOPIC,
|
||||
handlers += addMessageHandler(NetworkMapService.FETCH_FLOW_TOPIC,
|
||||
{ req: NetworkMapService.FetchMapRequest -> processFetchAllRequest(req) }
|
||||
)
|
||||
handlers += addMessageHandler(NetworkMapService.QUERY_PROTOCOL_TOPIC,
|
||||
handlers += addMessageHandler(NetworkMapService.QUERY_FLOW_TOPIC,
|
||||
{ req: NetworkMapService.QueryIdentityRequest -> processQueryRequest(req) }
|
||||
)
|
||||
handlers += addMessageHandler(NetworkMapService.REGISTER_PROTOCOL_TOPIC,
|
||||
handlers += addMessageHandler(NetworkMapService.REGISTER_FLOW_TOPIC,
|
||||
{ req: NetworkMapService.RegistrationRequest -> processRegistrationChangeRequest(req) }
|
||||
)
|
||||
handlers += addMessageHandler(NetworkMapService.SUBSCRIPTION_PROTOCOL_TOPIC,
|
||||
handlers += addMessageHandler(NetworkMapService.SUBSCRIPTION_FLOW_TOPIC,
|
||||
{ req: NetworkMapService.SubscribeRequest -> processSubscriptionRequest(req) }
|
||||
)
|
||||
handlers += net.addMessageHandler(NetworkMapService.PUSH_ACK_PROTOCOL_TOPIC, DEFAULT_SESSION_ID) { message, r ->
|
||||
handlers += net.addMessageHandler(NetworkMapService.PUSH_ACK_FLOW_TOPIC, DEFAULT_SESSION_ID) { message, r ->
|
||||
val req = message.data.deserialize<NetworkMapService.UpdateAcknowledge>()
|
||||
processAcknowledge(req)
|
||||
}
|
||||
@ -200,7 +200,7 @@ abstract class AbstractNetworkMapService
|
||||
// to a MessageRecipientGroup that nodes join/leave, rather than the network map
|
||||
// service itself managing the group
|
||||
val update = NetworkMapService.Update(wireReg, mapVersion, net.myAddress).serialize().bytes
|
||||
val message = net.createMessage(NetworkMapService.PUSH_PROTOCOL_TOPIC, DEFAULT_SESSION_ID, update)
|
||||
val message = net.createMessage(NetworkMapService.PUSH_FLOW_TOPIC, DEFAULT_SESSION_ID, update)
|
||||
|
||||
subscribers.locked {
|
||||
val toRemove = mutableListOf<SingleMessageRecipient>()
|
||||
|
@ -3,9 +3,9 @@ package net.corda.node.services.persistence
|
||||
import net.corda.core.ThreadBox
|
||||
import net.corda.core.bufferUntilSubscribed
|
||||
import net.corda.core.crypto.SecureHash
|
||||
import net.corda.core.flows.StateMachineRunId
|
||||
import net.corda.core.node.services.StateMachineRecordedTransactionMappingStorage
|
||||
import net.corda.core.node.services.StateMachineTransactionMapping
|
||||
import net.corda.core.protocols.StateMachineRunId
|
||||
import net.corda.node.utilities.*
|
||||
import org.jetbrains.exposed.sql.ResultRow
|
||||
import org.jetbrains.exposed.sql.statements.InsertStatement
|
||||
@ -17,7 +17,7 @@ import javax.annotation.concurrent.ThreadSafe
|
||||
* Database storage of a txhash -> state machine id mapping.
|
||||
*
|
||||
* Mappings are added as transactions are persisted by [ServiceHub.recordTransaction], and never deleted. Used in the
|
||||
* RPC API to correlate transaction creation with protocols.
|
||||
* RPC API to correlate transaction creation with flows.
|
||||
*
|
||||
*/
|
||||
@ThreadSafe
|
||||
|
@ -2,14 +2,13 @@ package net.corda.node.services.persistence
|
||||
|
||||
import co.paralleluniverse.fibers.Suspendable
|
||||
import net.corda.core.crypto.Party
|
||||
import net.corda.core.flows.FlowLogic
|
||||
import net.corda.core.node.CordaPluginRegistry
|
||||
import net.corda.core.node.PluginServiceHub
|
||||
import net.corda.core.node.recordTransactions
|
||||
import net.corda.core.protocols.ProtocolLogic
|
||||
import net.corda.core.serialization.SingletonSerializeAsToken
|
||||
import net.corda.core.utilities.loggerFor
|
||||
import net.corda.node.services.api.ServiceHubInternal
|
||||
import net.corda.protocols.*
|
||||
import net.corda.flows.*
|
||||
import java.io.InputStream
|
||||
import javax.annotation.concurrent.ThreadSafe
|
||||
|
||||
@ -41,16 +40,16 @@ object DataVending {
|
||||
class TransactionRejectedError(msg: String) : Exception(msg)
|
||||
|
||||
init {
|
||||
services.registerProtocolInitiator(FetchTransactionsProtocol::class, ::FetchTransactionsHandler)
|
||||
services.registerProtocolInitiator(FetchAttachmentsProtocol::class, ::FetchAttachmentsHandler)
|
||||
services.registerProtocolInitiator(BroadcastTransactionProtocol::class, ::NotifyTransactionHandler)
|
||||
services.registerFlowInitiator(FetchTransactionsFlow::class, ::FetchTransactionsHandler)
|
||||
services.registerFlowInitiator(FetchAttachmentsFlow::class, ::FetchAttachmentsHandler)
|
||||
services.registerFlowInitiator(BroadcastTransactionFlow::class, ::NotifyTransactionHandler)
|
||||
}
|
||||
|
||||
|
||||
private class FetchTransactionsHandler(val otherParty: Party) : ProtocolLogic<Unit>() {
|
||||
private class FetchTransactionsHandler(val otherParty: Party) : FlowLogic<Unit>() {
|
||||
@Suspendable
|
||||
override fun call() {
|
||||
val request = receive<FetchDataProtocol.Request>(otherParty).unwrap {
|
||||
val request = receive<FetchDataFlow.Request>(otherParty).unwrap {
|
||||
require(it.hashes.isNotEmpty())
|
||||
it
|
||||
}
|
||||
@ -66,10 +65,10 @@ object DataVending {
|
||||
|
||||
|
||||
// TODO: Use Artemis message streaming support here, called "large messages". This avoids the need to buffer.
|
||||
private class FetchAttachmentsHandler(val otherParty: Party) : ProtocolLogic<Unit>() {
|
||||
private class FetchAttachmentsHandler(val otherParty: Party) : FlowLogic<Unit>() {
|
||||
@Suspendable
|
||||
override fun call() {
|
||||
val request = receive<FetchDataProtocol.Request>(otherParty).unwrap {
|
||||
val request = receive<FetchDataFlow.Request>(otherParty).unwrap {
|
||||
require(it.hashes.isNotEmpty())
|
||||
it
|
||||
}
|
||||
@ -91,11 +90,11 @@ object DataVending {
|
||||
// includes us in any outside that list. Potentially just if it includes any outside that list at all.
|
||||
// TODO: Do we want to be able to reject specific transactions on more complex rules, for example reject incoming
|
||||
// cash without from unknown parties?
|
||||
class NotifyTransactionHandler(val otherParty: Party) : ProtocolLogic<Unit>() {
|
||||
class NotifyTransactionHandler(val otherParty: Party) : FlowLogic<Unit>() {
|
||||
@Suspendable
|
||||
override fun call() {
|
||||
val request = receive<BroadcastTransactionProtocol.NotifyTxRequest>(otherParty).unwrap { it }
|
||||
subProtocol(ResolveTransactionsProtocol(request.tx, otherParty), shareParentSessions = true)
|
||||
val request = receive<BroadcastTransactionFlow.NotifyTxRequest>(otherParty).unwrap { it }
|
||||
subFlow(ResolveTransactionsFlow(request.tx, otherParty), shareParentSessions = true)
|
||||
serviceHub.recordTransactions(request.tx)
|
||||
}
|
||||
}
|
||||
|
@ -5,7 +5,7 @@ import net.corda.core.bufferUntilSubscribed
|
||||
import net.corda.core.crypto.SecureHash
|
||||
import net.corda.core.node.services.StateMachineRecordedTransactionMappingStorage
|
||||
import net.corda.core.node.services.StateMachineTransactionMapping
|
||||
import net.corda.core.protocols.StateMachineRunId
|
||||
import net.corda.core.flows.StateMachineRunId
|
||||
import rx.Observable
|
||||
import rx.subjects.PublishSubject
|
||||
import java.util.*
|
||||
|
@ -7,10 +7,10 @@ import co.paralleluniverse.strands.Strand
|
||||
import com.google.common.util.concurrent.ListenableFuture
|
||||
import com.google.common.util.concurrent.SettableFuture
|
||||
import net.corda.core.crypto.Party
|
||||
import net.corda.core.protocols.ProtocolLogic
|
||||
import net.corda.core.protocols.ProtocolSessionException
|
||||
import net.corda.core.protocols.ProtocolStateMachine
|
||||
import net.corda.core.protocols.StateMachineRunId
|
||||
import net.corda.core.flows.FlowLogic
|
||||
import net.corda.core.flows.FlowSessionException
|
||||
import net.corda.core.flows.FlowStateMachine
|
||||
import net.corda.core.flows.StateMachineRunId
|
||||
import net.corda.core.random63BitValue
|
||||
import net.corda.core.utilities.UntrustworthyData
|
||||
import net.corda.core.utilities.trace
|
||||
@ -28,9 +28,9 @@ import java.sql.SQLException
|
||||
import java.util.*
|
||||
import java.util.concurrent.ExecutionException
|
||||
|
||||
class ProtocolStateMachineImpl<R>(override val id: StateMachineRunId,
|
||||
val logic: ProtocolLogic<R>,
|
||||
scheduler: FiberScheduler) : Fiber<R>("protocol", scheduler), ProtocolStateMachine<R> {
|
||||
class FlowStateMachineImpl<R>(override val id: StateMachineRunId,
|
||||
val logic: FlowLogic<R>,
|
||||
scheduler: FiberScheduler) : Fiber<R>("flow", scheduler), FlowStateMachine<R> {
|
||||
|
||||
companion object {
|
||||
// Used to work around a small limitation in Quasar.
|
||||
@ -41,14 +41,14 @@ class ProtocolStateMachineImpl<R>(override val id: StateMachineRunId,
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the current [ProtocolStateMachineImpl] or null if executing outside of one.
|
||||
* Return the current [FlowStateMachineImpl] or null if executing outside of one.
|
||||
*/
|
||||
fun currentStateMachine(): ProtocolStateMachineImpl<*>? = Strand.currentStrand() as? ProtocolStateMachineImpl<*>
|
||||
fun currentStateMachine(): FlowStateMachineImpl<*>? = Strand.currentStrand() as? FlowStateMachineImpl<*>
|
||||
}
|
||||
|
||||
// These fields shouldn't be serialised, so they are marked @Transient.
|
||||
@Transient lateinit override var serviceHub: ServiceHubInternal
|
||||
@Transient internal lateinit var actionOnSuspend: (ProtocolIORequest) -> Unit
|
||||
@Transient internal lateinit var actionOnSuspend: (FlowIORequest) -> Unit
|
||||
@Transient internal lateinit var actionOnEnd: () -> Unit
|
||||
@Transient internal lateinit var database: Database
|
||||
@Transient internal var fromCheckpoint: Boolean = false
|
||||
@ -73,10 +73,10 @@ class ProtocolStateMachineImpl<R>(override val id: StateMachineRunId,
|
||||
}
|
||||
}
|
||||
|
||||
internal val openSessions = HashMap<Pair<ProtocolLogic<*>, Party>, ProtocolSession>()
|
||||
internal val openSessions = HashMap<Pair<FlowLogic<*>, Party>, FlowSession>()
|
||||
|
||||
init {
|
||||
logic.psm = this
|
||||
logic.fsm = this
|
||||
name = id.toString()
|
||||
}
|
||||
|
||||
@ -122,8 +122,8 @@ class ProtocolStateMachineImpl<R>(override val id: StateMachineRunId,
|
||||
override fun <T : Any> sendAndReceive(otherParty: Party,
|
||||
payload: Any,
|
||||
receiveType: Class<T>,
|
||||
sessionProtocol: ProtocolLogic<*>): UntrustworthyData<T> {
|
||||
val (session, new) = getSession(otherParty, sessionProtocol, payload)
|
||||
sessionFlow: FlowLogic<*>): UntrustworthyData<T> {
|
||||
val (session, new) = getSession(otherParty, sessionFlow, payload)
|
||||
val receivedSessionData = if (new) {
|
||||
// Only do a receive here as the session init has carried the payload
|
||||
receiveInternal<SessionData>(session)
|
||||
@ -137,48 +137,48 @@ class ProtocolStateMachineImpl<R>(override val id: StateMachineRunId,
|
||||
@Suspendable
|
||||
override fun <T : Any> receive(otherParty: Party,
|
||||
receiveType: Class<T>,
|
||||
sessionProtocol: ProtocolLogic<*>): UntrustworthyData<T> {
|
||||
val session = getSession(otherParty, sessionProtocol, null).first
|
||||
sessionFlow: FlowLogic<*>): UntrustworthyData<T> {
|
||||
val session = getSession(otherParty, sessionFlow, null).first
|
||||
val receivedSessionData = receiveInternal<SessionData>(session)
|
||||
return UntrustworthyData(receiveType.cast(receivedSessionData.payload))
|
||||
}
|
||||
|
||||
@Suspendable
|
||||
override fun send(otherParty: Party, payload: Any, sessionProtocol: ProtocolLogic<*>) {
|
||||
val (session, new) = getSession(otherParty, sessionProtocol, payload)
|
||||
override fun send(otherParty: Party, payload: Any, sessionFlow: FlowLogic<*>) {
|
||||
val (session, new) = getSession(otherParty, sessionFlow, payload)
|
||||
if (!new) {
|
||||
// Don't send the payload again if it was already piggy-backed on a session init
|
||||
sendInternal(session, createSessionData(session, payload))
|
||||
}
|
||||
}
|
||||
|
||||
private fun createSessionData(session: ProtocolSession, payload: Any): SessionData {
|
||||
private fun createSessionData(session: FlowSession, payload: Any): SessionData {
|
||||
val otherPartySessionId = session.otherPartySessionId
|
||||
?: throw IllegalStateException("We've somehow held onto an unconfirmed session: $session")
|
||||
return SessionData(otherPartySessionId, payload)
|
||||
}
|
||||
|
||||
@Suspendable
|
||||
private fun sendInternal(session: ProtocolSession, message: SessionMessage) {
|
||||
private fun sendInternal(session: FlowSession, message: SessionMessage) {
|
||||
suspend(SendOnly(session, message))
|
||||
}
|
||||
|
||||
@Suspendable
|
||||
private inline fun <reified M : SessionMessage> receiveInternal(session: ProtocolSession): M {
|
||||
private inline fun <reified M : SessionMessage> receiveInternal(session: FlowSession): M {
|
||||
return suspendAndExpectReceive(ReceiveOnly(session, M::class.java))
|
||||
}
|
||||
|
||||
private inline fun <reified M : SessionMessage> sendAndReceiveInternal(session: ProtocolSession, message: SessionMessage): M {
|
||||
private inline fun <reified M : SessionMessage> sendAndReceiveInternal(session: FlowSession, message: SessionMessage): M {
|
||||
return suspendAndExpectReceive(SendAndReceive(session, message, M::class.java))
|
||||
}
|
||||
|
||||
@Suspendable
|
||||
private fun getSession(otherParty: Party, sessionProtocol: ProtocolLogic<*>, firstPayload: Any?): Pair<ProtocolSession, Boolean> {
|
||||
val session = openSessions[Pair(sessionProtocol, otherParty)]
|
||||
private fun getSession(otherParty: Party, sessionFlow: FlowLogic<*>, firstPayload: Any?): Pair<FlowSession, Boolean> {
|
||||
val session = openSessions[Pair(sessionFlow, otherParty)]
|
||||
return if (session != null) {
|
||||
Pair(session, false)
|
||||
} else {
|
||||
Pair(startNewSession(otherParty, sessionProtocol, firstPayload), true)
|
||||
Pair(startNewSession(otherParty, sessionFlow, firstPayload), true)
|
||||
}
|
||||
}
|
||||
|
||||
@ -189,21 +189,21 @@ class ProtocolStateMachineImpl<R>(override val id: StateMachineRunId,
|
||||
* multiple public keys, but we **don't support multiple nodes advertising the same legal identity**.
|
||||
*/
|
||||
@Suspendable
|
||||
private fun startNewSession(otherParty: Party, sessionProtocol: ProtocolLogic<*>, firstPayload: Any?) : ProtocolSession {
|
||||
private fun startNewSession(otherParty: Party, sessionFlow: FlowLogic<*>, firstPayload: Any?): FlowSession {
|
||||
val node = serviceHub.networkMapCache.getRepresentativeNode(otherParty) ?: throw IllegalArgumentException("Don't know about party $otherParty")
|
||||
val nodeIdentity = node.legalIdentity
|
||||
logger.trace { "Initiating a new session with $nodeIdentity (representative of $otherParty)" }
|
||||
val session = ProtocolSession(sessionProtocol, nodeIdentity, random63BitValue(), null)
|
||||
openSessions[Pair(sessionProtocol, nodeIdentity)] = session
|
||||
val counterpartyProtocol = sessionProtocol.getCounterpartyMarker(nodeIdentity).name
|
||||
val sessionInit = SessionInit(session.ourSessionId, serviceHub.myInfo.legalIdentity, counterpartyProtocol, firstPayload)
|
||||
val session = FlowSession(sessionFlow, nodeIdentity, random63BitValue(), null)
|
||||
openSessions[Pair(sessionFlow, nodeIdentity)] = session
|
||||
val counterpartyFlow = sessionFlow.getCounterpartyMarker(nodeIdentity).name
|
||||
val sessionInit = SessionInit(session.ourSessionId, serviceHub.myInfo.legalIdentity, counterpartyFlow, firstPayload)
|
||||
val sessionInitResponse = sendAndReceiveInternal<SessionInitResponse>(session, sessionInit)
|
||||
if (sessionInitResponse is SessionConfirm) {
|
||||
session.otherPartySessionId = sessionInitResponse.initiatedSessionId
|
||||
return session
|
||||
} else {
|
||||
sessionInitResponse as SessionReject
|
||||
throw ProtocolSessionException("Party $nodeIdentity rejected session attempt: ${sessionInitResponse.errorMessage}")
|
||||
throw FlowSessionException("Party $nodeIdentity rejected session attempt: ${sessionInitResponse.errorMessage}")
|
||||
}
|
||||
}
|
||||
|
||||
@ -227,7 +227,7 @@ class ProtocolStateMachineImpl<R>(override val id: StateMachineRunId,
|
||||
|
||||
if (receivedMessage is SessionEnd) {
|
||||
openSessions.values.remove(receiveRequest.session)
|
||||
throw ProtocolSessionException("Counterparty on ${receiveRequest.session.otherParty} has prematurely ended on $receiveRequest")
|
||||
throw FlowSessionException("Counterparty on ${receiveRequest.session.otherParty} has prematurely ended on $receiveRequest")
|
||||
} else if (receiveRequest.receiveType.isInstance(receivedMessage)) {
|
||||
return receiveRequest.receiveType.cast(receivedMessage)
|
||||
} else {
|
||||
@ -236,7 +236,7 @@ class ProtocolStateMachineImpl<R>(override val id: StateMachineRunId,
|
||||
}
|
||||
|
||||
@Suspendable
|
||||
private fun suspend(ioRequest: ProtocolIORequest) {
|
||||
private fun suspend(ioRequest: FlowIORequest) {
|
||||
// we have to pass the Thread local Transaction across via a transient field as the Fiber Park swaps them out.
|
||||
txTrampoline = TransactionManager.currentOrNull()
|
||||
StrandLocalTransactionManager.setThreadLocalTx(null)
|
@ -1,38 +1,38 @@
|
||||
package net.corda.node.services.statemachine
|
||||
|
||||
import net.corda.node.services.statemachine.StateMachineManager.ProtocolSession
|
||||
import net.corda.node.services.statemachine.StateMachineManager.FlowSession
|
||||
import net.corda.node.services.statemachine.StateMachineManager.SessionMessage
|
||||
|
||||
// TODO revisit when Kotlin 1.1 is released and data classes can extend other classes
|
||||
interface ProtocolIORequest {
|
||||
interface FlowIORequest {
|
||||
// This is used to identify where we suspended, in case of message mismatch errors and other things where we
|
||||
// don't have the original stack trace because it's in a suspended fiber.
|
||||
val stackTraceInCaseOfProblems: StackSnapshot
|
||||
val session: ProtocolSession
|
||||
val session: FlowSession
|
||||
}
|
||||
|
||||
interface SendRequest : ProtocolIORequest {
|
||||
interface SendRequest : FlowIORequest {
|
||||
val message: SessionMessage
|
||||
}
|
||||
|
||||
interface ReceiveRequest<T : SessionMessage> : ProtocolIORequest {
|
||||
interface ReceiveRequest<T : SessionMessage> : FlowIORequest {
|
||||
val receiveType: Class<T>
|
||||
}
|
||||
|
||||
data class SendAndReceive<T : SessionMessage>(override val session: ProtocolSession,
|
||||
data class SendAndReceive<T : SessionMessage>(override val session: FlowSession,
|
||||
override val message: SessionMessage,
|
||||
override val receiveType: Class<T>) : SendRequest, ReceiveRequest<T> {
|
||||
@Transient
|
||||
override val stackTraceInCaseOfProblems: StackSnapshot = StackSnapshot()
|
||||
}
|
||||
|
||||
data class ReceiveOnly<T : SessionMessage>(override val session: ProtocolSession,
|
||||
data class ReceiveOnly<T : SessionMessage>(override val session: FlowSession,
|
||||
override val receiveType: Class<T>) : ReceiveRequest<T> {
|
||||
@Transient
|
||||
override val stackTraceInCaseOfProblems: StackSnapshot = StackSnapshot()
|
||||
}
|
||||
|
||||
data class SendOnly(override val session: ProtocolSession, override val message: SessionMessage) : SendRequest {
|
||||
data class SendOnly(override val session: FlowSession, override val message: SessionMessage) : SendRequest {
|
||||
@Transient
|
||||
override val stackTraceInCaseOfProblems: StackSnapshot = StackSnapshot()
|
||||
}
|
||||
|
@ -11,11 +11,11 @@ import kotlinx.support.jdk8.collections.removeIf
|
||||
import net.corda.core.ThreadBox
|
||||
import net.corda.core.abbreviate
|
||||
import net.corda.core.crypto.Party
|
||||
import net.corda.core.flows.FlowLogic
|
||||
import net.corda.core.flows.FlowStateMachine
|
||||
import net.corda.core.flows.StateMachineRunId
|
||||
import net.corda.core.messaging.TopicSession
|
||||
import net.corda.core.messaging.send
|
||||
import net.corda.core.protocols.ProtocolLogic
|
||||
import net.corda.core.protocols.ProtocolStateMachine
|
||||
import net.corda.core.protocols.StateMachineRunId
|
||||
import net.corda.core.random63BitValue
|
||||
import net.corda.core.serialization.*
|
||||
import net.corda.core.then
|
||||
@ -41,8 +41,8 @@ import java.util.concurrent.ExecutionException
|
||||
import javax.annotation.concurrent.ThreadSafe
|
||||
|
||||
/**
|
||||
* A StateMachineManager is responsible for coordination and persistence of multiple [ProtocolStateMachine] objects.
|
||||
* Each such object represents an instantiation of a (two-party) protocol that has reached a particular point.
|
||||
* A StateMachineManager is responsible for coordination and persistence of multiple [FlowStateMachine] objects.
|
||||
* Each such object represents an instantiation of a (two-party) flow that has reached a particular point.
|
||||
*
|
||||
* An implementation of this class will persist state machines to long term storage so they can survive process restarts
|
||||
* and, if run with a single-threaded executor, will ensure no two state machines run concurrently with each other
|
||||
@ -51,7 +51,7 @@ import javax.annotation.concurrent.ThreadSafe
|
||||
* A "state machine" is a class with a single call method. The call method and any others it invokes are rewritten by
|
||||
* a bytecode rewriting engine called Quasar, to ensure the code can be suspended and resumed at any point.
|
||||
*
|
||||
* The SMM will always invoke the protocol fibers on the given [AffinityExecutor], regardless of which thread actually
|
||||
* The SMM will always invoke the flow fibers on the given [AffinityExecutor], regardless of which thread actually
|
||||
* starts them via [add].
|
||||
*
|
||||
* TODO: Consider the issue of continuation identity more deeply: is it a safe assumption that a serialised
|
||||
@ -79,7 +79,7 @@ class StateMachineManager(val serviceHub: ServiceHubInternal,
|
||||
val scheduler = FiberScheduler()
|
||||
|
||||
data class Change(
|
||||
val logic: ProtocolLogic<*>,
|
||||
val logic: FlowLogic<*>,
|
||||
val addOrRemove: AddOrRemove,
|
||||
val id: StateMachineRunId
|
||||
)
|
||||
@ -88,10 +88,10 @@ class StateMachineManager(val serviceHub: ServiceHubInternal,
|
||||
// property.
|
||||
private val mutex = ThreadBox(object {
|
||||
var started = false
|
||||
val stateMachines = LinkedHashMap<ProtocolStateMachineImpl<*>, Checkpoint>()
|
||||
val stateMachines = LinkedHashMap<FlowStateMachineImpl<*>, Checkpoint>()
|
||||
val changesPublisher = PublishSubject.create<Change>()
|
||||
|
||||
fun notifyChangeObservers(psm: ProtocolStateMachineImpl<*>, addOrRemove: AddOrRemove) {
|
||||
fun notifyChangeObservers(psm: FlowStateMachineImpl<*>, addOrRemove: AddOrRemove) {
|
||||
changesPublisher.onNext(Change(psm.logic, addOrRemove, psm.id))
|
||||
}
|
||||
})
|
||||
@ -105,35 +105,35 @@ class StateMachineManager(val serviceHub: ServiceHubInternal,
|
||||
private val metrics = serviceHub.monitoringService.metrics
|
||||
|
||||
init {
|
||||
metrics.register("Protocols.InFlight", Gauge<Int> { mutex.content.stateMachines.size })
|
||||
metrics.register("Flows.InFlight", Gauge<Int> { mutex.content.stateMachines.size })
|
||||
}
|
||||
|
||||
private val checkpointingMeter = metrics.meter("Protocols.Checkpointing Rate")
|
||||
private val totalStartedProtocols = metrics.counter("Protocols.Started")
|
||||
private val totalFinishedProtocols = metrics.counter("Protocols.Finished")
|
||||
private val checkpointingMeter = metrics.meter("Flows.Checkpointing Rate")
|
||||
private val totalStartedFlows = metrics.counter("Flows.Started")
|
||||
private val totalFinishedFlows = metrics.counter("Flows.Finished")
|
||||
|
||||
private val openSessions = ConcurrentHashMap<Long, ProtocolSession>()
|
||||
private val openSessions = ConcurrentHashMap<Long, FlowSession>()
|
||||
private val recentlyClosedSessions = ConcurrentHashMap<Long, Party>()
|
||||
|
||||
// Context for tokenized services in checkpoints
|
||||
private val serializationContext = SerializeAsTokenContext(tokenizableServices, quasarKryo())
|
||||
|
||||
/** Returns a list of all state machines executing the given protocol logic at the top level (subprotocols do not count) */
|
||||
fun <P : ProtocolLogic<T>, T> findStateMachines(protocolClass: Class<P>): List<Pair<P, ListenableFuture<T>>> {
|
||||
/** Returns a list of all state machines executing the given flow logic at the top level (subflows do not count) */
|
||||
fun <P : FlowLogic<T>, T> findStateMachines(flowClass: Class<P>): List<Pair<P, ListenableFuture<T>>> {
|
||||
@Suppress("UNCHECKED_CAST")
|
||||
return mutex.locked {
|
||||
stateMachines.keys
|
||||
.map { it.logic }
|
||||
.filterIsInstance(protocolClass)
|
||||
.map { it to (it.psm as ProtocolStateMachineImpl<T>).resultFuture }
|
||||
.filterIsInstance(flowClass)
|
||||
.map { it to (it.fsm as FlowStateMachineImpl<T>).resultFuture }
|
||||
}
|
||||
}
|
||||
|
||||
val allStateMachines: List<ProtocolLogic<*>>
|
||||
val allStateMachines: List<FlowLogic<*>>
|
||||
get() = mutex.locked { stateMachines.keys.map { it.logic } }
|
||||
|
||||
/**
|
||||
* An observable that emits triples of the changing protocol, the type of change, and a process-specific ID number
|
||||
* An observable that emits triples of the changing flow, the type of change, and a process-specific ID number
|
||||
* which may change across restarts.
|
||||
*/
|
||||
val changes: Observable<Change>
|
||||
@ -141,7 +141,7 @@ class StateMachineManager(val serviceHub: ServiceHubInternal,
|
||||
|
||||
init {
|
||||
Fiber.setDefaultUncaughtExceptionHandler { fiber, throwable ->
|
||||
(fiber as ProtocolStateMachineImpl<*>).logger.error("Caught exception from protocol", throwable)
|
||||
(fiber as FlowStateMachineImpl<*>).logger.error("Caught exception from flow", throwable)
|
||||
}
|
||||
}
|
||||
|
||||
@ -179,7 +179,7 @@ class StateMachineManager(val serviceHub: ServiceHubInternal,
|
||||
* Atomic get snapshot + subscribe. This is needed so we don't miss updates between subscriptions to [changes] and
|
||||
* calls to [allStateMachines]
|
||||
*/
|
||||
fun track(): Pair<List<ProtocolStateMachineImpl<*>>, Observable<Change>> {
|
||||
fun track(): Pair<List<FlowStateMachineImpl<*>>, Observable<Change>> {
|
||||
return mutex.locked {
|
||||
val bufferedChanges = UnicastSubject.create<Change>()
|
||||
changesPublisher.subscribe(bufferedChanges)
|
||||
@ -190,7 +190,7 @@ class StateMachineManager(val serviceHub: ServiceHubInternal,
|
||||
private fun restoreFibersFromCheckpoints() {
|
||||
mutex.locked {
|
||||
checkpointStorage.forEach {
|
||||
// If a protocol is added before start() then don't attempt to restore it
|
||||
// If a flow is added before start() then don't attempt to restore it
|
||||
if (!stateMachines.containsValue(it)) {
|
||||
val fiber = deserializeFiber(it.serializedFiber)
|
||||
initFiber(fiber)
|
||||
@ -216,7 +216,7 @@ class StateMachineManager(val serviceHub: ServiceHubInternal,
|
||||
}
|
||||
}
|
||||
|
||||
private fun resumeRestoredFiber(fiber: ProtocolStateMachineImpl<*>) {
|
||||
private fun resumeRestoredFiber(fiber: FlowStateMachineImpl<*>) {
|
||||
fiber.openSessions.values.forEach { openSessions[it.ourSessionId] = it }
|
||||
if (fiber.openSessions.values.any { it.waitingForResponse }) {
|
||||
fiber.logger.info("Restored, pending on receive")
|
||||
@ -260,23 +260,23 @@ class StateMachineManager(val serviceHub: ServiceHubInternal,
|
||||
val otherParty = sessionInit.initiatorParty
|
||||
val otherPartySessionId = sessionInit.initiatorSessionId
|
||||
try {
|
||||
val markerClass = Class.forName(sessionInit.protocolName)
|
||||
val protocolFactory = serviceHub.getProtocolFactory(markerClass)
|
||||
if (protocolFactory != null) {
|
||||
val protocol = protocolFactory(otherParty)
|
||||
val psm = createFiber(protocol)
|
||||
val session = ProtocolSession(protocol, otherParty, random63BitValue(), otherPartySessionId)
|
||||
val markerClass = Class.forName(sessionInit.flowName)
|
||||
val flowFactory = serviceHub.getFlowFactory(markerClass)
|
||||
if (flowFactory != null) {
|
||||
val flow = flowFactory(otherParty)
|
||||
val psm = createFiber(flow)
|
||||
val session = FlowSession(flow, otherParty, random63BitValue(), otherPartySessionId)
|
||||
if (sessionInit.firstPayload != null) {
|
||||
session.receivedMessages += SessionData(session.ourSessionId, sessionInit.firstPayload)
|
||||
}
|
||||
openSessions[session.ourSessionId] = session
|
||||
psm.openSessions[Pair(protocol, otherParty)] = session
|
||||
psm.openSessions[Pair(flow, otherParty)] = session
|
||||
updateCheckpoint(psm)
|
||||
sendSessionMessage(otherParty, SessionConfirm(otherPartySessionId, session.ourSessionId), psm)
|
||||
psm.logger.debug { "Initiated from $sessionInit on $session" }
|
||||
startFiber(psm)
|
||||
} else {
|
||||
logger.warn("Unknown protocol marker class in $sessionInit")
|
||||
logger.warn("Unknown flow marker class in $sessionInit")
|
||||
sendSessionMessage(otherParty, SessionReject(otherPartySessionId, "Don't know ${markerClass.name}"), null)
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
@ -285,14 +285,14 @@ class StateMachineManager(val serviceHub: ServiceHubInternal,
|
||||
}
|
||||
}
|
||||
|
||||
private fun serializeFiber(fiber: ProtocolStateMachineImpl<*>): SerializedBytes<ProtocolStateMachineImpl<*>> {
|
||||
private fun serializeFiber(fiber: FlowStateMachineImpl<*>): SerializedBytes<FlowStateMachineImpl<*>> {
|
||||
val kryo = quasarKryo()
|
||||
// add the map of tokens -> tokenizedServices to the kyro context
|
||||
SerializeAsTokenSerializer.setContext(kryo, serializationContext)
|
||||
return fiber.serialize(kryo)
|
||||
}
|
||||
|
||||
private fun deserializeFiber(serialisedFiber: SerializedBytes<ProtocolStateMachineImpl<*>>): ProtocolStateMachineImpl<*> {
|
||||
private fun deserializeFiber(serialisedFiber: SerializedBytes<FlowStateMachineImpl<*>>): FlowStateMachineImpl<*> {
|
||||
val kryo = quasarKryo()
|
||||
// put the map of token -> tokenized into the kryo context
|
||||
SerializeAsTokenSerializer.setContext(kryo, serializationContext)
|
||||
@ -304,12 +304,12 @@ class StateMachineManager(val serviceHub: ServiceHubInternal,
|
||||
return createKryo(serializer.kryo)
|
||||
}
|
||||
|
||||
private fun <T> createFiber(logic: ProtocolLogic<T>): ProtocolStateMachineImpl<T> {
|
||||
private fun <T> createFiber(logic: FlowLogic<T>): FlowStateMachineImpl<T> {
|
||||
val id = StateMachineRunId.createRandom()
|
||||
return ProtocolStateMachineImpl(id, logic, scheduler).apply { initFiber(this) }
|
||||
return FlowStateMachineImpl(id, logic, scheduler).apply { initFiber(this) }
|
||||
}
|
||||
|
||||
private fun initFiber(psm: ProtocolStateMachineImpl<*>) {
|
||||
private fun initFiber(psm: FlowStateMachineImpl<*>) {
|
||||
psm.database = database
|
||||
psm.serviceHub = serviceHub
|
||||
psm.actionOnSuspend = { ioRequest ->
|
||||
@ -325,7 +325,7 @@ class StateMachineManager(val serviceHub: ServiceHubInternal,
|
||||
psm.logic.progressTracker?.currentStep = ProgressTracker.DONE
|
||||
mutex.locked {
|
||||
stateMachines.remove(psm)?.let { checkpointStorage.removeCheckpoint(it) }
|
||||
totalFinishedProtocols.inc()
|
||||
totalFinishedFlows.inc()
|
||||
notifyChangeObservers(psm, AddOrRemove.REMOVE)
|
||||
}
|
||||
endAllFiberSessions(psm)
|
||||
@ -334,12 +334,12 @@ class StateMachineManager(val serviceHub: ServiceHubInternal,
|
||||
}
|
||||
}
|
||||
mutex.locked {
|
||||
totalStartedProtocols.inc()
|
||||
totalStartedFlows.inc()
|
||||
notifyChangeObservers(psm, AddOrRemove.ADD)
|
||||
}
|
||||
}
|
||||
|
||||
private fun endAllFiberSessions(psm: ProtocolStateMachineImpl<*>) {
|
||||
private fun endAllFiberSessions(psm: FlowStateMachineImpl<*>) {
|
||||
openSessions.values.removeIf { session ->
|
||||
if (session.psm == psm) {
|
||||
val otherPartySessionId = session.otherPartySessionId
|
||||
@ -354,17 +354,17 @@ class StateMachineManager(val serviceHub: ServiceHubInternal,
|
||||
}
|
||||
}
|
||||
|
||||
private fun startFiber(fiber: ProtocolStateMachineImpl<*>) {
|
||||
private fun startFiber(fiber: FlowStateMachineImpl<*>) {
|
||||
try {
|
||||
resumeFiber(fiber)
|
||||
} catch (e: ExecutionException) {
|
||||
// There are two ways we can take exceptions in this method:
|
||||
//
|
||||
// 1) A bug in the SMM code itself whilst setting up the new protocol. In that case the exception will
|
||||
// 1) A bug in the SMM code itself whilst setting up the new flow. In that case the exception will
|
||||
// propagate out of this method as it would for any method.
|
||||
//
|
||||
// 2) An exception in the first part of the fiber after it's been invoked for the first time via
|
||||
// fiber.start(). In this case the exception will be caught and stashed in the protocol logic future,
|
||||
// fiber.start(). In this case the exception will be caught and stashed in the flow logic future,
|
||||
// then sent to the unhandled exception handler above which logs it, and is then rethrown wrapped
|
||||
// in an ExecutionException or RuntimeException+EE so we can just catch it here and ignore it.
|
||||
} catch (e: RuntimeException) {
|
||||
@ -378,10 +378,10 @@ class StateMachineManager(val serviceHub: ServiceHubInternal,
|
||||
* The state machine will be persisted when it suspends, with automated restart if the StateMachineManager is
|
||||
* restarted with checkpointed state machines in the storage service.
|
||||
*/
|
||||
fun <T> add(logic: ProtocolLogic<T>): ProtocolStateMachine<T> {
|
||||
fun <T> add(logic: FlowLogic<T>): FlowStateMachine<T> {
|
||||
val fiber = createFiber(logic)
|
||||
// We swap out the parent transaction context as using this frequently leads to a deadlock as we wait
|
||||
// on the protocol completion future inside that context. The problem is that any progress checkpoints are
|
||||
// on the flow completion future inside that context. The problem is that any progress checkpoints are
|
||||
// unable to acquire the table lock and move forward till the calling transaction finishes.
|
||||
// Committing in line here on a fresh context ensure we can progress.
|
||||
isolatedTransaction(database) {
|
||||
@ -396,7 +396,7 @@ class StateMachineManager(val serviceHub: ServiceHubInternal,
|
||||
return fiber
|
||||
}
|
||||
|
||||
private fun updateCheckpoint(psm: ProtocolStateMachineImpl<*>) {
|
||||
private fun updateCheckpoint(psm: FlowStateMachineImpl<*>) {
|
||||
check(psm.state != Strand.State.RUNNING) { "Fiber cannot be running when checkpointing" }
|
||||
val newCheckpoint = Checkpoint(serializeFiber(psm))
|
||||
val previousCheckpoint = mutex.locked { stateMachines.put(psm, newCheckpoint) }
|
||||
@ -407,7 +407,7 @@ class StateMachineManager(val serviceHub: ServiceHubInternal,
|
||||
checkpointingMeter.mark()
|
||||
}
|
||||
|
||||
private fun resumeFiber(psm: ProtocolStateMachineImpl<*>) {
|
||||
private fun resumeFiber(psm: FlowStateMachineImpl<*>) {
|
||||
// Avoid race condition when setting stopping to true and then checking liveFibers
|
||||
incrementLiveFibers()
|
||||
if (!stopping) executor.executeASAP {
|
||||
@ -418,7 +418,7 @@ class StateMachineManager(val serviceHub: ServiceHubInternal,
|
||||
}
|
||||
}
|
||||
|
||||
private fun processIORequest(ioRequest: ProtocolIORequest) {
|
||||
private fun processIORequest(ioRequest: FlowIORequest) {
|
||||
if (ioRequest is SendRequest) {
|
||||
if (ioRequest.message is SessionInit) {
|
||||
openSessions[ioRequest.session.ourSessionId] = ioRequest.session
|
||||
@ -431,7 +431,7 @@ class StateMachineManager(val serviceHub: ServiceHubInternal,
|
||||
}
|
||||
}
|
||||
|
||||
private fun sendSessionMessage(party: Party, message: SessionMessage, psm: ProtocolStateMachineImpl<*>?) {
|
||||
private fun sendSessionMessage(party: Party, message: SessionMessage, psm: FlowStateMachineImpl<*>?) {
|
||||
val node = serviceHub.networkMapCache.getNodeByCompositeKey(party.owningKey)
|
||||
?: throw IllegalArgumentException("Don't know about party $party")
|
||||
val logger = psm?.logger ?: logger
|
||||
@ -448,7 +448,7 @@ class StateMachineManager(val serviceHub: ServiceHubInternal,
|
||||
|
||||
data class SessionInit(val initiatorSessionId: Long,
|
||||
val initiatorParty: Party,
|
||||
val protocolName: String,
|
||||
val flowName: String,
|
||||
val firstPayload: Any?) : SessionMessage
|
||||
|
||||
interface SessionInitResponse : ExistingSessionMessage
|
||||
@ -470,14 +470,14 @@ class StateMachineManager(val serviceHub: ServiceHubInternal,
|
||||
data class SessionEnd(override val recipientSessionId: Long) : ExistingSessionMessage
|
||||
|
||||
|
||||
data class ProtocolSession(val protocol: ProtocolLogic<*>,
|
||||
val otherParty: Party,
|
||||
val ourSessionId: Long,
|
||||
var otherPartySessionId: Long?,
|
||||
@Volatile var waitingForResponse: Boolean = false) {
|
||||
data class FlowSession(val flow: FlowLogic<*>,
|
||||
val otherParty: Party,
|
||||
val ourSessionId: Long,
|
||||
var otherPartySessionId: Long?,
|
||||
@Volatile var waitingForResponse: Boolean = false) {
|
||||
|
||||
val receivedMessages = ConcurrentLinkedQueue<ExistingSessionMessage>()
|
||||
val psm: ProtocolStateMachineImpl<*> get() = protocol.psm as ProtocolStateMachineImpl<*>
|
||||
val psm: FlowStateMachineImpl<*> get() = flow.fsm as FlowStateMachineImpl<*>
|
||||
|
||||
}
|
||||
|
||||
|
@ -1,11 +1,9 @@
|
||||
package net.corda.node.services.transactions
|
||||
|
||||
import net.corda.core.crypto.Party
|
||||
import net.corda.core.node.services.ServiceType
|
||||
import net.corda.core.serialization.SingletonSerializeAsToken
|
||||
import net.corda.flows.NotaryFlow
|
||||
import net.corda.node.services.api.ServiceHubInternal
|
||||
import net.corda.protocols.NotaryProtocol
|
||||
import kotlin.reflect.KClass
|
||||
|
||||
/**
|
||||
* A Notary service acts as the final signer of a transaction ensuring two things:
|
||||
@ -14,15 +12,15 @@ import kotlin.reflect.KClass
|
||||
*O
|
||||
* A transaction has to be signed by a Notary to be considered valid (except for output-only transactions without a timestamp).
|
||||
*
|
||||
* This is the base implementation that can be customised with specific Notary transaction commit protocol.
|
||||
* This is the base implementation that can be customised with specific Notary transaction commit flow.
|
||||
*/
|
||||
abstract class NotaryService(services: ServiceHubInternal) : SingletonSerializeAsToken() {
|
||||
|
||||
init {
|
||||
services.registerProtocolInitiator(NotaryProtocol.Client::class) { createProtocol(it) }
|
||||
services.registerFlowInitiator(NotaryFlow.Client::class) { createFlow(it) }
|
||||
}
|
||||
|
||||
/** Implement a factory that specifies the transaction commit protocol for the notary service to use */
|
||||
abstract fun createProtocol(otherParty: Party): NotaryProtocol.Service
|
||||
/** Implement a factory that specifies the transaction commit flow for the notary service to use */
|
||||
abstract fun createFlow(otherParty: Party): NotaryFlow.Service
|
||||
|
||||
}
|
||||
|
@ -2,8 +2,8 @@ package net.corda.node.services.transactions
|
||||
|
||||
import net.corda.core.crypto.Party
|
||||
import net.corda.core.node.services.TimestampChecker
|
||||
import net.corda.flows.ValidatingNotaryFlow
|
||||
import net.corda.node.services.api.ServiceHubInternal
|
||||
import net.corda.protocols.ValidatingNotaryProtocol
|
||||
|
||||
/** A validating notary service operated by a group of mutually trusting parties, uses the Raft algorithm to achieve consensus. */
|
||||
class RaftValidatingNotaryService(services: ServiceHubInternal,
|
||||
@ -13,7 +13,7 @@ class RaftValidatingNotaryService(services: ServiceHubInternal,
|
||||
val type = ValidatingNotaryService.type.getSubType("raft")
|
||||
}
|
||||
|
||||
override fun createProtocol(otherParty: Party): ValidatingNotaryProtocol {
|
||||
return ValidatingNotaryProtocol(otherParty, timestampChecker, uniquenessProvider)
|
||||
override fun createFlow(otherParty: Party): ValidatingNotaryFlow {
|
||||
return ValidatingNotaryFlow(otherParty, timestampChecker, uniquenessProvider)
|
||||
}
|
||||
}
|
||||
|
@ -4,9 +4,8 @@ import net.corda.core.crypto.Party
|
||||
import net.corda.core.node.services.ServiceType
|
||||
import net.corda.core.node.services.TimestampChecker
|
||||
import net.corda.core.node.services.UniquenessProvider
|
||||
import net.corda.core.utilities.loggerFor
|
||||
import net.corda.flows.NotaryFlow
|
||||
import net.corda.node.services.api.ServiceHubInternal
|
||||
import net.corda.protocols.NotaryProtocol
|
||||
|
||||
/** A simple Notary service that does not perform transaction validation */
|
||||
class SimpleNotaryService(services: ServiceHubInternal,
|
||||
@ -16,7 +15,7 @@ class SimpleNotaryService(services: ServiceHubInternal,
|
||||
val type = ServiceType.notary.getSubType("simple")
|
||||
}
|
||||
|
||||
override fun createProtocol(otherParty: Party): NotaryProtocol.Service {
|
||||
return NotaryProtocol.Service(otherParty, timestampChecker, uniquenessProvider)
|
||||
override fun createFlow(otherParty: Party): NotaryFlow.Service {
|
||||
return NotaryFlow.Service(otherParty, timestampChecker, uniquenessProvider)
|
||||
}
|
||||
}
|
||||
|
@ -4,8 +4,8 @@ import net.corda.core.crypto.Party
|
||||
import net.corda.core.node.services.ServiceType
|
||||
import net.corda.core.node.services.TimestampChecker
|
||||
import net.corda.core.node.services.UniquenessProvider
|
||||
import net.corda.flows.ValidatingNotaryFlow
|
||||
import net.corda.node.services.api.ServiceHubInternal
|
||||
import net.corda.protocols.ValidatingNotaryProtocol
|
||||
|
||||
/** A Notary service that validates the transaction chain of he submitted transaction before committing it */
|
||||
class ValidatingNotaryService(services: ServiceHubInternal,
|
||||
@ -15,7 +15,7 @@ class ValidatingNotaryService(services: ServiceHubInternal,
|
||||
val type = ServiceType.notary.getSubType("validating")
|
||||
}
|
||||
|
||||
override fun createProtocol(otherParty: Party): ValidatingNotaryProtocol {
|
||||
return ValidatingNotaryProtocol(otherParty, timestampChecker, uniquenessProvider)
|
||||
override fun createFlow(otherParty: Party): ValidatingNotaryFlow {
|
||||
return ValidatingNotaryFlow(otherParty, timestampChecker, uniquenessProvider)
|
||||
}
|
||||
}
|
||||
|
@ -1,28 +1,28 @@
|
||||
package net.corda.node.utilities
|
||||
|
||||
import net.corda.core.ThreadBox
|
||||
import net.corda.core.protocols.ProtocolLogic
|
||||
import net.corda.core.flows.FlowLogic
|
||||
import net.corda.core.utilities.ProgressTracker
|
||||
import net.corda.node.services.statemachine.StateMachineManager
|
||||
import java.util.*
|
||||
|
||||
/**
|
||||
* This observes the [StateMachineManager] and follows the progress of [ProtocolLogic]s until they complete in the order
|
||||
* This observes the [StateMachineManager] and follows the progress of [FlowLogic]s until they complete in the order
|
||||
* they are added to the [StateMachineManager].
|
||||
*/
|
||||
class ANSIProgressObserver(val smm: StateMachineManager) {
|
||||
init {
|
||||
smm.changes.subscribe { change ->
|
||||
when (change.addOrRemove) {
|
||||
AddOrRemove.ADD -> addProtocolLogic(change.logic)
|
||||
AddOrRemove.REMOVE -> removeProtocolLogic(change.logic)
|
||||
AddOrRemove.ADD -> addFlowLogic(change.logic)
|
||||
AddOrRemove.REMOVE -> removeFlowLogic(change.logic)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private class Content {
|
||||
var currentlyRendering: ProtocolLogic<*>? = null
|
||||
val pending = ArrayDeque<ProtocolLogic<*>>()
|
||||
var currentlyRendering: FlowLogic<*>? = null
|
||||
val pending = ArrayDeque<FlowLogic<*>>()
|
||||
}
|
||||
|
||||
private val state = ThreadBox(Content())
|
||||
@ -39,18 +39,18 @@ class ANSIProgressObserver(val smm: StateMachineManager) {
|
||||
}
|
||||
}
|
||||
|
||||
private fun removeProtocolLogic(protocolLogic: ProtocolLogic<*>) {
|
||||
private fun removeFlowLogic(flowLogic: FlowLogic<*>) {
|
||||
state.locked {
|
||||
protocolLogic.progressTracker?.currentStep = ProgressTracker.DONE
|
||||
if (currentlyRendering == protocolLogic) {
|
||||
flowLogic.progressTracker?.currentStep = ProgressTracker.DONE
|
||||
if (currentlyRendering == flowLogic) {
|
||||
wireUpProgressRendering()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun addProtocolLogic(protocolLogic: ProtocolLogic<*>) {
|
||||
private fun addFlowLogic(flowLogic: FlowLogic<*>) {
|
||||
state.locked {
|
||||
pending.add(protocolLogic)
|
||||
pending.add(flowLogic)
|
||||
if ((currentlyRendering?.progressTracker?.currentStep ?: ProgressTracker.DONE) == ProgressTracker.DONE) {
|
||||
wireUpProgressRendering()
|
||||
}
|
||||
|
@ -27,9 +27,9 @@ import kotlin.concurrent.withLock
|
||||
* or testing.
|
||||
*
|
||||
* Currently this is intended for use within a node as a simplified way for Oracles to implement subscriptions for changing
|
||||
* data by running a protocol internally to implement the request handler (see [NodeInterestRates.Oracle]), which can then
|
||||
* data by running a flow internally to implement the request handler (see [NodeInterestRates.Oracle]), which can then
|
||||
* effectively relinquish control until the data becomes available. This isn't the most scalable design and is intended
|
||||
* to be temporary. In addition, it's enitrely possible to envisage a time when we want public [ProtocolLogic]
|
||||
* to be temporary. In addition, it's enitrely possible to envisage a time when we want public [FlowLogic]
|
||||
* implementations to be able to wait for some condition to become true outside of message send/receive. At that point
|
||||
* we may revisit this implementation and indeed the whole model for this, when we understand that requirement more fully.
|
||||
*
|
||||
|
@ -2,23 +2,23 @@ package net.corda.node
|
||||
|
||||
import net.corda.contracts.asset.Cash
|
||||
import net.corda.core.contracts.*
|
||||
import net.corda.core.flows.StateMachineRunId
|
||||
import net.corda.core.node.services.ServiceInfo
|
||||
import net.corda.core.node.services.Vault
|
||||
import net.corda.core.protocols.StateMachineRunId
|
||||
import net.corda.core.serialization.OpaqueBytes
|
||||
import net.corda.core.transactions.SignedTransaction
|
||||
import net.corda.flows.CashCommand
|
||||
import net.corda.flows.CashFlow
|
||||
import net.corda.node.internal.CordaRPCOpsImpl
|
||||
import net.corda.node.services.User
|
||||
import net.corda.node.services.messaging.CURRENT_RPC_USER
|
||||
import net.corda.node.services.messaging.PermissionException
|
||||
import net.corda.node.services.messaging.StateMachineUpdate
|
||||
import net.corda.node.services.messaging.startProtocol
|
||||
import net.corda.node.services.messaging.startFlow
|
||||
import net.corda.node.services.network.NetworkMapService
|
||||
import net.corda.node.services.startProtocolPermission
|
||||
import net.corda.node.services.startFlowPermission
|
||||
import net.corda.node.services.transactions.SimpleNotaryService
|
||||
import net.corda.node.utilities.databaseTransaction
|
||||
import net.corda.protocols.CashCommand
|
||||
import net.corda.protocols.CashProtocol
|
||||
import net.corda.testing.expect
|
||||
import net.corda.testing.expectEvents
|
||||
import net.corda.testing.node.MockNetwork
|
||||
@ -48,7 +48,7 @@ class CordaRPCOpsImplTest {
|
||||
aliceNode = network.createNode(networkMapAddress = networkMap.info.address)
|
||||
notaryNode = network.createNode(advertisedServices = ServiceInfo(SimpleNotaryService.type), networkMapAddress = networkMap.info.address)
|
||||
rpc = CordaRPCOpsImpl(aliceNode.services, aliceNode.smm, aliceNode.database)
|
||||
CURRENT_RPC_USER.set(User("user", "pwd", permissions = setOf(startProtocolPermission<CashProtocol>())))
|
||||
CURRENT_RPC_USER.set(User("user", "pwd", permissions = setOf(startFlowPermission<CashFlow>())))
|
||||
|
||||
stateMachineUpdates = rpc.stateMachinesAndUpdates().second
|
||||
transactions = rpc.verifiedTransactions().second
|
||||
@ -68,7 +68,7 @@ class CordaRPCOpsImplTest {
|
||||
// Tell the monitoring service node to issue some cash
|
||||
val recipient = aliceNode.info.legalIdentity
|
||||
val outEvent = CashCommand.IssueCash(Amount(quantity, GBP), ref, recipient, notaryNode.info.notaryIdentity)
|
||||
rpc.startProtocol(::CashProtocol, outEvent)
|
||||
rpc.startFlow(::CashFlow, outEvent)
|
||||
network.runNetwork()
|
||||
|
||||
val expectedState = Cash.State(Amount(quantity,
|
||||
@ -105,7 +105,7 @@ class CordaRPCOpsImplTest {
|
||||
@Test
|
||||
fun `issue and move`() {
|
||||
|
||||
rpc.startProtocol(::CashProtocol, CashCommand.IssueCash(
|
||||
rpc.startFlow(::CashFlow, CashCommand.IssueCash(
|
||||
amount = Amount(100, USD),
|
||||
issueRef = OpaqueBytes(ByteArray(1, { 1 })),
|
||||
recipient = aliceNode.info.legalIdentity,
|
||||
@ -114,7 +114,7 @@ class CordaRPCOpsImplTest {
|
||||
|
||||
network.runNetwork()
|
||||
|
||||
rpc.startProtocol(::CashProtocol, CashCommand.PayCash(
|
||||
rpc.startFlow(::CashFlow, CashCommand.PayCash(
|
||||
amount = Amount(100, Issued(PartyAndReference(aliceNode.info.legalIdentity, OpaqueBytes(ByteArray(1, { 1 }))), USD)),
|
||||
recipient = aliceNode.info.legalIdentity
|
||||
))
|
||||
@ -186,7 +186,7 @@ class CordaRPCOpsImplTest {
|
||||
fun `cash command by user not permissioned for cash`() {
|
||||
CURRENT_RPC_USER.set(User("user", "pwd", permissions = emptySet()))
|
||||
assertThatExceptionOfType(PermissionException::class.java).isThrownBy {
|
||||
rpc.startProtocol(::CashProtocol, CashCommand.IssueCash(
|
||||
rpc.startFlow(::CashFlow, CashCommand.IssueCash(
|
||||
amount = Amount(100, USD),
|
||||
issueRef = OpaqueBytes(ByteArray(1, { 1 })),
|
||||
recipient = aliceNode.info.legalIdentity,
|
||||
|
@ -6,12 +6,12 @@ import net.corda.core.crypto.sha256
|
||||
import net.corda.core.messaging.SingleMessageRecipient
|
||||
import net.corda.core.node.services.ServiceInfo
|
||||
import net.corda.core.write
|
||||
import net.corda.flows.FetchAttachmentsFlow
|
||||
import net.corda.flows.FetchDataFlow
|
||||
import net.corda.node.services.config.NodeConfiguration
|
||||
import net.corda.node.services.network.NetworkMapService
|
||||
import net.corda.node.services.persistence.NodeAttachmentService
|
||||
import net.corda.node.services.transactions.SimpleNotaryService
|
||||
import net.corda.protocols.FetchAttachmentsProtocol
|
||||
import net.corda.protocols.FetchDataProtocol
|
||||
import net.corda.testing.node.MockNetwork
|
||||
import net.corda.testing.rootCauseExceptions
|
||||
import org.junit.Before
|
||||
@ -49,9 +49,9 @@ class AttachmentTests {
|
||||
// Insert an attachment into node zero's store directly.
|
||||
val id = n0.storage.attachments.importAttachment(ByteArrayInputStream(fakeAttachment()))
|
||||
|
||||
// Get node one to run a protocol to fetch it and insert it.
|
||||
// Get node one to run a flow to fetch it and insert it.
|
||||
network.runNetwork()
|
||||
val f1 = n1.services.startProtocol(FetchAttachmentsProtocol(setOf(id), n0.info.legalIdentity))
|
||||
val f1 = n1.services.startFlow(FetchAttachmentsFlow(setOf(id), n0.info.legalIdentity))
|
||||
network.runNetwork()
|
||||
assertEquals(0, f1.resultFuture.get().fromDisk.size)
|
||||
|
||||
@ -62,7 +62,7 @@ class AttachmentTests {
|
||||
// Shut down node zero and ensure node one can still resolve the attachment.
|
||||
n0.stop()
|
||||
|
||||
val response: FetchDataProtocol.Result<Attachment> = n1.services.startProtocol(FetchAttachmentsProtocol(setOf(id), n0.info.legalIdentity)).resultFuture.get()
|
||||
val response: FetchDataFlow.Result<Attachment> = n1.services.startFlow(FetchAttachmentsFlow(setOf(id), n0.info.legalIdentity)).resultFuture.get()
|
||||
assertEquals(attachment, response.fromDisk[0])
|
||||
}
|
||||
|
||||
@ -73,9 +73,9 @@ class AttachmentTests {
|
||||
// Get node one to fetch a non-existent attachment.
|
||||
val hash = SecureHash.randomSHA256()
|
||||
network.runNetwork()
|
||||
val f1 = n1.services.startProtocol(FetchAttachmentsProtocol(setOf(hash), n0.info.legalIdentity))
|
||||
val f1 = n1.services.startFlow(FetchAttachmentsFlow(setOf(hash), n0.info.legalIdentity))
|
||||
network.runNetwork()
|
||||
val e = assertFailsWith<FetchDataProtocol.HashNotFound> { rootCauseExceptions { f1.resultFuture.get() } }
|
||||
val e = assertFailsWith<FetchDataFlow.HashNotFound> { rootCauseExceptions { f1.resultFuture.get() } }
|
||||
assertEquals(hash, e.requested)
|
||||
}
|
||||
|
||||
@ -104,9 +104,9 @@ class AttachmentTests {
|
||||
|
||||
// Get n1 to fetch the attachment. Should receive corrupted bytes.
|
||||
network.runNetwork()
|
||||
val f1 = n1.services.startProtocol(FetchAttachmentsProtocol(setOf(id), n0.info.legalIdentity))
|
||||
val f1 = n1.services.startFlow(FetchAttachmentsFlow(setOf(id), n0.info.legalIdentity))
|
||||
network.runNetwork()
|
||||
assertFailsWith<FetchDataProtocol.DownloadedVsRequestedDataMismatch> {
|
||||
assertFailsWith<FetchDataFlow.DownloadedVsRequestedDataMismatch> {
|
||||
rootCauseExceptions { f1.resultFuture.get() }
|
||||
}
|
||||
}
|
||||
|
@ -9,11 +9,11 @@ import net.corda.core.crypto.Party
|
||||
import net.corda.core.crypto.SecureHash
|
||||
import net.corda.core.crypto.composite
|
||||
import net.corda.core.days
|
||||
import net.corda.core.flows.FlowStateMachine
|
||||
import net.corda.core.flows.StateMachineRunId
|
||||
import net.corda.core.map
|
||||
import net.corda.core.messaging.SingleMessageRecipient
|
||||
import net.corda.core.node.services.*
|
||||
import net.corda.core.protocols.ProtocolStateMachine
|
||||
import net.corda.core.protocols.StateMachineRunId
|
||||
import net.corda.core.transactions.SignedTransaction
|
||||
import net.corda.core.transactions.TransactionBuilder
|
||||
import net.corda.core.transactions.WireTransaction
|
||||
@ -21,6 +21,8 @@ import net.corda.core.utilities.DUMMY_NOTARY
|
||||
import net.corda.core.utilities.DUMMY_NOTARY_KEY
|
||||
import net.corda.core.utilities.LogHelper
|
||||
import net.corda.core.utilities.TEST_TX_TIME
|
||||
import net.corda.flows.TwoPartyTradeFlow.Buyer
|
||||
import net.corda.flows.TwoPartyTradeFlow.Seller
|
||||
import net.corda.node.internal.AbstractNode
|
||||
import net.corda.node.services.config.NodeConfiguration
|
||||
import net.corda.node.services.persistence.DBTransactionStorage
|
||||
@ -28,8 +30,6 @@ import net.corda.node.services.persistence.NodeAttachmentService
|
||||
import net.corda.node.services.persistence.StorageServiceImpl
|
||||
import net.corda.node.services.persistence.checkpoints
|
||||
import net.corda.node.utilities.databaseTransaction
|
||||
import net.corda.protocols.TwoPartyTradeProtocol.Buyer
|
||||
import net.corda.protocols.TwoPartyTradeProtocol.Seller
|
||||
import net.corda.testing.*
|
||||
import net.corda.testing.node.InMemoryMessagingNetwork
|
||||
import net.corda.testing.node.MockNetwork
|
||||
@ -58,7 +58,7 @@ import kotlin.test.assertTrue
|
||||
*
|
||||
* We assume that Alice and Bob already found each other via some market, and have agreed the details already.
|
||||
*/
|
||||
class TwoPartyTradeProtocolTests {
|
||||
class TwoPartyTradeFlowTests {
|
||||
|
||||
lateinit var net: MockNetwork
|
||||
lateinit var notaryNode: MockNetwork.MockNode
|
||||
@ -146,7 +146,7 @@ class TwoPartyTradeProtocolTests {
|
||||
insertFakeTransactions(alicesFakePaper, aliceNode, aliceKey, notaryKey)
|
||||
val aliceFuture = runBuyerAndSeller("alice's paper".outputStateAndRef()).sellerResult
|
||||
|
||||
// Everything is on this thread so we can now step through the protocol one step at a time.
|
||||
// Everything is on this thread so we can now step through the flow one step at a time.
|
||||
// Seller Alice already sent a message to Buyer Bob. Pump once:
|
||||
bobNode.pumpReceive()
|
||||
|
||||
@ -408,18 +408,18 @@ class TwoPartyTradeProtocolTests {
|
||||
|
||||
private data class RunResult(
|
||||
// The buyer is not created immediately, only when the seller starts running
|
||||
val buyer: Future<ProtocolStateMachine<*>>,
|
||||
val buyer: Future<FlowStateMachine<*>>,
|
||||
val sellerResult: Future<SignedTransaction>,
|
||||
val sellerId: StateMachineRunId
|
||||
)
|
||||
|
||||
private fun runBuyerAndSeller(assetToSell: StateAndRef<OwnableState>) : RunResult {
|
||||
val buyerFuture = bobNode.initiateSingleShotProtocol(Seller::class) { otherParty ->
|
||||
val buyerFuture = bobNode.initiateSingleShotFlow(Seller::class) { otherParty ->
|
||||
Buyer(otherParty, notaryNode.info.notaryIdentity, 1000.DOLLARS, CommercialPaper.State::class.java)
|
||||
}.map { it.psm }
|
||||
}.map { it.fsm }
|
||||
val seller = Seller(bobNode.info.legalIdentity, notaryNode.info, assetToSell, 1000.DOLLARS, ALICE_KEY)
|
||||
val sellerResultFuture = aliceNode.smm.add(seller).resultFuture
|
||||
return RunResult(buyerFuture, sellerResultFuture, seller.psm.id)
|
||||
return RunResult(buyerFuture, sellerResultFuture, seller.fsm.id)
|
||||
}
|
||||
|
||||
private fun LedgerDSL<TestTransactionDSLInterpreter, TestLedgerDSLInterpreter>.runWithError(
|
||||
|
@ -152,7 +152,7 @@ class ArtemisMessagingTests {
|
||||
val message = messagingClient.createMessage(topic, DEFAULT_SESSION_ID, "first msg".toByteArray())
|
||||
messagingClient.send(message, messagingClient.myAddress)
|
||||
|
||||
val networkMapMessage = messagingClient.createMessage(NetworkMapService.FETCH_PROTOCOL_TOPIC, DEFAULT_SESSION_ID, "second msg".toByteArray())
|
||||
val networkMapMessage = messagingClient.createMessage(NetworkMapService.FETCH_FLOW_TOPIC, DEFAULT_SESSION_ID, "second msg".toByteArray())
|
||||
messagingClient.send(networkMapMessage, messagingClient.myAddress)
|
||||
|
||||
val actual: Message = receivedMessages.take()
|
||||
@ -179,7 +179,7 @@ class ArtemisMessagingTests {
|
||||
messagingClient.send(message, messagingClient.myAddress)
|
||||
}
|
||||
|
||||
val networkMapMessage = messagingClient.createMessage(NetworkMapService.FETCH_PROTOCOL_TOPIC, DEFAULT_SESSION_ID, "second msg".toByteArray())
|
||||
val networkMapMessage = messagingClient.createMessage(NetworkMapService.FETCH_FLOW_TOPIC, DEFAULT_SESSION_ID, "second msg".toByteArray())
|
||||
messagingClient.send(networkMapMessage, messagingClient.myAddress)
|
||||
|
||||
val actual: Message = receivedMessages.take()
|
||||
@ -212,7 +212,7 @@ class ArtemisMessagingTests {
|
||||
messagingClient.addMessageHandler(topic) { message, r ->
|
||||
receivedMessages.add(message)
|
||||
}
|
||||
messagingClient.addMessageHandler(NetworkMapService.FETCH_PROTOCOL_TOPIC) { message, r ->
|
||||
messagingClient.addMessageHandler(NetworkMapService.FETCH_FLOW_TOPIC) { message, r ->
|
||||
receivedMessages.add(message)
|
||||
}
|
||||
// Run after the handlers are added, otherwise (some of) the messages get delivered and discarded / dead-lettered.
|
||||
|
@ -4,17 +4,17 @@ import com.google.common.util.concurrent.ListenableFuture
|
||||
import net.corda.core.map
|
||||
import net.corda.core.messaging.send
|
||||
import net.corda.core.node.services.DEFAULT_SESSION_ID
|
||||
import net.corda.flows.sendRequest
|
||||
import net.corda.node.services.network.AbstractNetworkMapService
|
||||
import net.corda.node.services.network.InMemoryNetworkMapService
|
||||
import net.corda.node.services.network.NetworkMapService
|
||||
import net.corda.node.services.network.NetworkMapService.*
|
||||
import net.corda.node.services.network.NetworkMapService.Companion.FETCH_PROTOCOL_TOPIC
|
||||
import net.corda.node.services.network.NetworkMapService.Companion.PUSH_ACK_PROTOCOL_TOPIC
|
||||
import net.corda.node.services.network.NetworkMapService.Companion.REGISTER_PROTOCOL_TOPIC
|
||||
import net.corda.node.services.network.NetworkMapService.Companion.SUBSCRIPTION_PROTOCOL_TOPIC
|
||||
import net.corda.node.services.network.NetworkMapService.Companion.FETCH_FLOW_TOPIC
|
||||
import net.corda.node.services.network.NetworkMapService.Companion.PUSH_ACK_FLOW_TOPIC
|
||||
import net.corda.node.services.network.NetworkMapService.Companion.REGISTER_FLOW_TOPIC
|
||||
import net.corda.node.services.network.NetworkMapService.Companion.SUBSCRIPTION_FLOW_TOPIC
|
||||
import net.corda.node.services.network.NodeRegistration
|
||||
import net.corda.node.utilities.AddOrRemove
|
||||
import net.corda.protocols.sendRequest
|
||||
import net.corda.testing.node.MockNetwork
|
||||
import net.corda.testing.node.MockNetwork.MockNode
|
||||
import org.junit.Before
|
||||
@ -165,23 +165,23 @@ abstract class AbstractNetworkMapServiceTest {
|
||||
|
||||
private fun MockNode.registration(mapServiceNode: MockNode, reg: NodeRegistration, privateKey: PrivateKey): ListenableFuture<RegistrationResponse> {
|
||||
val req = RegistrationRequest(reg.toWire(privateKey), services.networkService.myAddress)
|
||||
return services.networkService.sendRequest(REGISTER_PROTOCOL_TOPIC, req, mapServiceNode.info.address)
|
||||
return services.networkService.sendRequest(REGISTER_FLOW_TOPIC, req, mapServiceNode.info.address)
|
||||
}
|
||||
|
||||
private fun MockNode.subscribe(mapServiceNode: MockNode, subscribe: Boolean): ListenableFuture<SubscribeResponse> {
|
||||
val req = SubscribeRequest(subscribe, services.networkService.myAddress)
|
||||
return services.networkService.sendRequest(SUBSCRIPTION_PROTOCOL_TOPIC, req, mapServiceNode.info.address)
|
||||
return services.networkService.sendRequest(SUBSCRIPTION_FLOW_TOPIC, req, mapServiceNode.info.address)
|
||||
}
|
||||
|
||||
private fun MockNode.updateAcknowlege(mapServiceNode: MockNode, mapVersion: Int) {
|
||||
val req = UpdateAcknowledge(mapVersion, services.networkService.myAddress)
|
||||
services.networkService.send(PUSH_ACK_PROTOCOL_TOPIC, DEFAULT_SESSION_ID, req, mapServiceNode.info.address)
|
||||
services.networkService.send(PUSH_ACK_FLOW_TOPIC, DEFAULT_SESSION_ID, req, mapServiceNode.info.address)
|
||||
}
|
||||
|
||||
private fun MockNode.fetchMap(mapServiceNode: MockNode, subscribe: Boolean, ifChangedSinceVersion: Int? = null): Future<Collection<NodeRegistration>?> {
|
||||
val net = services.networkService
|
||||
val req = FetchMapRequest(subscribe, ifChangedSinceVersion, net.myAddress)
|
||||
return net.sendRequest<FetchMapResponse>(FETCH_PROTOCOL_TOPIC, req, mapServiceNode.info.address).map { it.nodes }
|
||||
return net.sendRequest<FetchMapResponse>(FETCH_FLOW_TOPIC, req, mapServiceNode.info.address).map { it.nodes }
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -2,11 +2,11 @@ package net.corda.node.services
|
||||
|
||||
import com.codahale.metrics.MetricRegistry
|
||||
import net.corda.core.crypto.Party
|
||||
import net.corda.core.flows.FlowLogic
|
||||
import net.corda.core.flows.FlowLogicRefFactory
|
||||
import net.corda.core.flows.FlowStateMachine
|
||||
import net.corda.core.node.NodeInfo
|
||||
import net.corda.core.node.services.*
|
||||
import net.corda.core.protocols.ProtocolLogic
|
||||
import net.corda.core.protocols.ProtocolLogicRefFactory
|
||||
import net.corda.core.protocols.ProtocolStateMachine
|
||||
import net.corda.core.transactions.SignedTransaction
|
||||
import net.corda.node.serialization.NodeClock
|
||||
import net.corda.node.services.api.MessagingServiceInternal
|
||||
@ -34,7 +34,7 @@ open class MockServiceHubInternal(
|
||||
val mapCache: NetworkMapCache? = MockNetworkMapCache(),
|
||||
val scheduler: SchedulerService? = null,
|
||||
val overrideClock: Clock? = NodeClock(),
|
||||
val protocolFactory: ProtocolLogicRefFactory? = ProtocolLogicRefFactory(),
|
||||
val flowFactory: FlowLogicRefFactory? = FlowLogicRefFactory(),
|
||||
val schemas: SchemaService? = NodeSchemaService()
|
||||
) : ServiceHubInternal() {
|
||||
override val vaultService: VaultService = customVault ?: NodeVaultService(this)
|
||||
@ -56,8 +56,8 @@ open class MockServiceHubInternal(
|
||||
get() = throw UnsupportedOperationException()
|
||||
|
||||
override val monitoringService: MonitoringService = MonitoringService(MetricRegistry())
|
||||
override val protocolLogicRefFactory: ProtocolLogicRefFactory
|
||||
get() = protocolFactory ?: throw UnsupportedOperationException()
|
||||
override val flowLogicRefFactory: FlowLogicRefFactory
|
||||
get() = flowFactory ?: throw UnsupportedOperationException()
|
||||
override val schemaService: SchemaService
|
||||
get() = schemas ?: throw UnsupportedOperationException()
|
||||
|
||||
@ -65,7 +65,7 @@ open class MockServiceHubInternal(
|
||||
private val txStorageService: TxWritableStorageService
|
||||
get() = storage ?: throw UnsupportedOperationException()
|
||||
|
||||
private val protocolFactories = ConcurrentHashMap<Class<*>, (Party) -> ProtocolLogic<*>>()
|
||||
private val flowFactories = ConcurrentHashMap<Class<*>, (Party) -> FlowLogic<*>>()
|
||||
|
||||
lateinit var smm: StateMachineManager
|
||||
|
||||
@ -79,13 +79,13 @@ open class MockServiceHubInternal(
|
||||
|
||||
override fun recordTransactions(txs: Iterable<SignedTransaction>) = recordTransactionsInternal(txStorageService, txs)
|
||||
|
||||
override fun <T> startProtocol(logic: ProtocolLogic<T>): ProtocolStateMachine<T> = smm.add(logic)
|
||||
override fun <T> startFlow(logic: FlowLogic<T>): FlowStateMachine<T> = smm.add(logic)
|
||||
|
||||
override fun registerProtocolInitiator(markerClass: KClass<*>, protocolFactory: (Party) -> ProtocolLogic<*>) {
|
||||
protocolFactories[markerClass.java] = protocolFactory
|
||||
override fun registerFlowInitiator(markerClass: KClass<*>, flowFactory: (Party) -> FlowLogic<*>) {
|
||||
flowFactories[markerClass.java] = flowFactory
|
||||
}
|
||||
|
||||
override fun getProtocolFactory(markerClass: Class<*>): ((Party) -> ProtocolLogic<*>)? {
|
||||
return protocolFactories[markerClass]
|
||||
override fun getFlowFactory(markerClass: Class<*>): ((Party) -> FlowLogic<*>)? {
|
||||
return flowFactories[markerClass]
|
||||
}
|
||||
}
|
||||
|
@ -4,11 +4,11 @@ import net.corda.core.contracts.*
|
||||
import net.corda.core.crypto.CompositeKey
|
||||
import net.corda.core.crypto.composite
|
||||
import net.corda.core.days
|
||||
import net.corda.core.flows.FlowLogic
|
||||
import net.corda.core.flows.FlowLogicRef
|
||||
import net.corda.core.flows.FlowLogicRefFactory
|
||||
import net.corda.core.node.ServiceHub
|
||||
import net.corda.core.node.recordTransactions
|
||||
import net.corda.core.protocols.ProtocolLogic
|
||||
import net.corda.core.protocols.ProtocolLogicRef
|
||||
import net.corda.core.protocols.ProtocolLogicRefFactory
|
||||
import net.corda.core.serialization.SingletonSerializeAsToken
|
||||
import net.corda.core.utilities.DUMMY_NOTARY
|
||||
import net.corda.node.services.events.NodeSchedulerService
|
||||
@ -47,7 +47,7 @@ class NodeSchedulerServiceTest : SingletonSerializeAsToken() {
|
||||
|
||||
// We have to allow Java boxed primitives but Kotlin warns we shouldn't be using them
|
||||
@Suppress("PLATFORM_CLASS_MAPPED_TO_KOTLIN")
|
||||
val factory = ProtocolLogicRefFactory(mapOf(Pair(TestProtocolLogic::class.java.name, setOf(NodeSchedulerServiceTest::class.java.name, Integer::class.java.name))))
|
||||
val factory = FlowLogicRefFactory(mapOf(Pair(TestFlowLogic::class.java.name, setOf(NodeSchedulerServiceTest::class.java.name, Integer::class.java.name))))
|
||||
|
||||
lateinit var services: MockServiceHubInternal
|
||||
|
||||
@ -56,12 +56,12 @@ class NodeSchedulerServiceTest : SingletonSerializeAsToken() {
|
||||
lateinit var dataSource: Closeable
|
||||
lateinit var database: Database
|
||||
lateinit var countDown: CountDownLatch
|
||||
lateinit var smmHasRemovedAllProtocols: CountDownLatch
|
||||
lateinit var smmHasRemovedAllFlows: CountDownLatch
|
||||
|
||||
var calls: Int = 0
|
||||
|
||||
/**
|
||||
* Have a reference to this test added to [ServiceHub] so that when the [ProtocolLogic] runs it can access the test instance.
|
||||
* Have a reference to this test added to [ServiceHub] so that when the [FlowLogic] runs it can access the test instance.
|
||||
* The [TestState] is serialized and deserialized so attempting to use a transient field won't work, as it just
|
||||
* results in NPE.
|
||||
*/
|
||||
@ -73,7 +73,7 @@ class NodeSchedulerServiceTest : SingletonSerializeAsToken() {
|
||||
@Before
|
||||
fun setup() {
|
||||
countDown = CountDownLatch(1)
|
||||
smmHasRemovedAllProtocols = CountDownLatch(1)
|
||||
smmHasRemovedAllFlows = CountDownLatch(1)
|
||||
calls = 0
|
||||
val dataSourceAndDatabase = configureDatabase(makeTestDataSourceProperties())
|
||||
dataSource = dataSourceAndDatabase.first
|
||||
@ -90,7 +90,7 @@ class NodeSchedulerServiceTest : SingletonSerializeAsToken() {
|
||||
val mockSMM = StateMachineManager(services, listOf(services, scheduler), DBCheckpointStorage(), smmExecutor, database)
|
||||
mockSMM.changes.subscribe { change ->
|
||||
if (change.addOrRemove == AddOrRemove.REMOVE && mockSMM.allStateMachines.isEmpty()) {
|
||||
smmHasRemovedAllProtocols.countDown()
|
||||
smmHasRemovedAllFlows.countDown()
|
||||
}
|
||||
}
|
||||
mockSMM.start()
|
||||
@ -103,14 +103,14 @@ class NodeSchedulerServiceTest : SingletonSerializeAsToken() {
|
||||
fun tearDown() {
|
||||
// We need to make sure the StateMachineManager is done before shutting down executors.
|
||||
if (services.smm.allStateMachines.isNotEmpty()) {
|
||||
smmHasRemovedAllProtocols.await()
|
||||
smmHasRemovedAllFlows.await()
|
||||
}
|
||||
smmExecutor.shutdown()
|
||||
smmExecutor.awaitTermination(60, TimeUnit.SECONDS)
|
||||
dataSource.close()
|
||||
}
|
||||
|
||||
class TestState(val protocolLogicRef: ProtocolLogicRef, val instant: Instant) : LinearState, SchedulableState {
|
||||
class TestState(val flowLogicRef: FlowLogicRef, val instant: Instant) : LinearState, SchedulableState {
|
||||
override val participants: List<CompositeKey>
|
||||
get() = throw UnsupportedOperationException()
|
||||
|
||||
@ -118,13 +118,13 @@ class NodeSchedulerServiceTest : SingletonSerializeAsToken() {
|
||||
|
||||
override fun isRelevant(ourKeys: Set<PublicKey>): Boolean = true
|
||||
|
||||
override fun nextScheduledActivity(thisStateRef: StateRef, protocolLogicRefFactory: ProtocolLogicRefFactory): ScheduledActivity? = ScheduledActivity(protocolLogicRef, instant)
|
||||
override fun nextScheduledActivity(thisStateRef: StateRef, flowLogicRefFactory: FlowLogicRefFactory): ScheduledActivity? = ScheduledActivity(flowLogicRef, instant)
|
||||
|
||||
override val contract: Contract
|
||||
get() = throw UnsupportedOperationException()
|
||||
}
|
||||
|
||||
class TestProtocolLogic(val increment: Int = 1) : ProtocolLogic<Unit>() {
|
||||
class TestFlowLogic(val increment: Int = 1) : FlowLogic<Unit>() {
|
||||
override fun call() {
|
||||
(serviceHub as TestReference).testReference.calls += increment
|
||||
(serviceHub as TestReference).testReference.countDown.countDown()
|
||||
@ -267,7 +267,7 @@ class NodeSchedulerServiceTest : SingletonSerializeAsToken() {
|
||||
databaseTransaction(database) {
|
||||
apply {
|
||||
val freshKey = services.keyManagementService.freshKey()
|
||||
val state = TestState(factory.create(TestProtocolLogic::class.java, increment), instant)
|
||||
val state = TestState(factory.create(TestFlowLogic::class.java, increment), instant)
|
||||
val usefulTX = TransactionType.General.Builder(null).apply {
|
||||
addOutputState(state, DUMMY_NOTARY)
|
||||
addCommand(Command(), freshKey.public.composite)
|
||||
|
@ -7,12 +7,12 @@ import net.corda.core.node.services.ServiceInfo
|
||||
import net.corda.core.seconds
|
||||
import net.corda.core.utilities.DUMMY_NOTARY
|
||||
import net.corda.core.utilities.DUMMY_NOTARY_KEY
|
||||
import net.corda.flows.NotaryChangeFlow.Instigator
|
||||
import net.corda.flows.StateReplacementException
|
||||
import net.corda.flows.StateReplacementRefused
|
||||
import net.corda.node.internal.AbstractNode
|
||||
import net.corda.node.services.network.NetworkMapService
|
||||
import net.corda.node.services.transactions.SimpleNotaryService
|
||||
import net.corda.protocols.NotaryChangeProtocol.Instigator
|
||||
import net.corda.protocols.StateReplacementException
|
||||
import net.corda.protocols.StateReplacementRefused
|
||||
import net.corda.testing.node.MockNetwork
|
||||
import org.junit.Before
|
||||
import org.junit.Test
|
||||
@ -48,8 +48,8 @@ class NotaryChangeTests {
|
||||
fun `should change notary for a state with single participant`() {
|
||||
val state = issueState(clientNodeA, oldNotaryNode)
|
||||
val newNotary = newNotaryNode.info.notaryIdentity
|
||||
val protocol = Instigator(state, newNotary)
|
||||
val future = clientNodeA.services.startProtocol(protocol)
|
||||
val flow = Instigator(state, newNotary)
|
||||
val future = clientNodeA.services.startFlow(flow)
|
||||
|
||||
net.runNetwork()
|
||||
|
||||
@ -61,8 +61,8 @@ class NotaryChangeTests {
|
||||
fun `should change notary for a state with multiple participants`() {
|
||||
val state = issueMultiPartyState(clientNodeA, clientNodeB, oldNotaryNode)
|
||||
val newNotary = newNotaryNode.info.notaryIdentity
|
||||
val protocol = Instigator(state, newNotary)
|
||||
val future = clientNodeA.services.startProtocol(protocol)
|
||||
val flow = Instigator(state, newNotary)
|
||||
val future = clientNodeA.services.startFlow(flow)
|
||||
|
||||
net.runNetwork()
|
||||
|
||||
@ -77,8 +77,8 @@ class NotaryChangeTests {
|
||||
fun `should throw when a participant refuses to change Notary`() {
|
||||
val state = issueMultiPartyState(clientNodeA, clientNodeB, oldNotaryNode)
|
||||
val newEvilNotary = Party("Evil Notary", generateKeyPair().public)
|
||||
val protocol = Instigator(state, newEvilNotary)
|
||||
val future = clientNodeA.services.startProtocol(protocol)
|
||||
val flow = Instigator(state, newEvilNotary)
|
||||
val future = clientNodeA.services.startFlow(flow)
|
||||
|
||||
net.runNetwork()
|
||||
|
||||
@ -87,7 +87,7 @@ class NotaryChangeTests {
|
||||
assertTrue(error is StateReplacementRefused)
|
||||
}
|
||||
|
||||
// TODO: Add more test cases once we have a general protocol/service exception handling mechanism:
|
||||
// TODO: Add more test cases once we have a general flow/service exception handling mechanism:
|
||||
// - A participant is offline/can't be found on the network
|
||||
// - The requesting party is not a participant
|
||||
// - The requesting party wants to change additional state fields
|
||||
|
@ -11,12 +11,12 @@ import net.corda.core.seconds
|
||||
import net.corda.core.transactions.SignedTransaction
|
||||
import net.corda.core.utilities.DUMMY_NOTARY
|
||||
import net.corda.core.utilities.DUMMY_NOTARY_KEY
|
||||
import net.corda.flows.NotaryError
|
||||
import net.corda.flows.NotaryException
|
||||
import net.corda.flows.NotaryFlow
|
||||
import net.corda.node.internal.AbstractNode
|
||||
import net.corda.node.services.network.NetworkMapService
|
||||
import net.corda.node.services.transactions.SimpleNotaryService
|
||||
import net.corda.protocols.NotaryError
|
||||
import net.corda.protocols.NotaryException
|
||||
import net.corda.protocols.NotaryProtocol
|
||||
import net.corda.testing.MINI_CORP_KEY
|
||||
import net.corda.testing.node.MockNetwork
|
||||
import org.junit.Before
|
||||
@ -94,10 +94,10 @@ class NotaryServiceTests {
|
||||
tx.toSignedTransaction(false)
|
||||
}
|
||||
|
||||
val firstSpend = NotaryProtocol.Client(stx)
|
||||
val secondSpend = NotaryProtocol.Client(stx)
|
||||
clientNode.services.startProtocol(firstSpend)
|
||||
val future = clientNode.services.startProtocol(secondSpend)
|
||||
val firstSpend = NotaryFlow.Client(stx)
|
||||
val secondSpend = NotaryFlow.Client(stx)
|
||||
clientNode.services.startFlow(firstSpend)
|
||||
val future = clientNode.services.startFlow(secondSpend)
|
||||
|
||||
net.runNetwork()
|
||||
|
||||
@ -109,8 +109,8 @@ class NotaryServiceTests {
|
||||
|
||||
|
||||
private fun runNotaryClient(stx: SignedTransaction): ListenableFuture<DigitalSignature.WithKey> {
|
||||
val protocol = NotaryProtocol.Client(stx)
|
||||
val future = clientNode.services.startProtocol(protocol).resultFuture
|
||||
val flow = NotaryFlow.Client(stx)
|
||||
val future = clientNode.services.startFlow(flow).resultFuture
|
||||
net.runNetwork()
|
||||
return future
|
||||
}
|
||||
|
@ -8,12 +8,12 @@ import net.corda.core.node.services.ServiceInfo
|
||||
import net.corda.core.transactions.SignedTransaction
|
||||
import net.corda.core.utilities.DUMMY_NOTARY
|
||||
import net.corda.core.utilities.DUMMY_NOTARY_KEY
|
||||
import net.corda.flows.NotaryError
|
||||
import net.corda.flows.NotaryException
|
||||
import net.corda.flows.NotaryFlow
|
||||
import net.corda.node.internal.AbstractNode
|
||||
import net.corda.node.services.network.NetworkMapService
|
||||
import net.corda.node.services.transactions.ValidatingNotaryService
|
||||
import net.corda.protocols.NotaryError
|
||||
import net.corda.protocols.NotaryException
|
||||
import net.corda.protocols.NotaryProtocol
|
||||
import net.corda.testing.MEGA_CORP_KEY
|
||||
import net.corda.testing.MINI_CORP_KEY
|
||||
import net.corda.testing.node.MockNetwork
|
||||
@ -79,8 +79,8 @@ class ValidatingNotaryServiceTests {
|
||||
}
|
||||
|
||||
private fun runClient(stx: SignedTransaction): ListenableFuture<DigitalSignature.WithKey> {
|
||||
val protocol = NotaryProtocol.Client(stx)
|
||||
val future = clientNode.services.startProtocol(protocol).resultFuture
|
||||
val flow = NotaryFlow.Client(stx)
|
||||
val future = clientNode.services.startFlow(flow).resultFuture
|
||||
net.runNetwork()
|
||||
return future
|
||||
}
|
||||
|
@ -7,12 +7,12 @@ import net.corda.core.contracts.Issued
|
||||
import net.corda.core.contracts.TransactionType
|
||||
import net.corda.core.contracts.USD
|
||||
import net.corda.core.crypto.Party
|
||||
import net.corda.core.protocols.ProtocolLogic
|
||||
import net.corda.core.flows.FlowLogic
|
||||
import net.corda.core.transactions.SignedTransaction
|
||||
import net.corda.core.utilities.DUMMY_NOTARY
|
||||
import net.corda.flows.BroadcastTransactionFlow.NotifyTxRequest
|
||||
import net.corda.node.services.persistence.DataVending.Service.NotifyTransactionHandler
|
||||
import net.corda.node.utilities.databaseTransaction
|
||||
import net.corda.protocols.BroadcastTransactionProtocol.NotifyTxRequest
|
||||
import net.corda.testing.MEGA_CORP
|
||||
import net.corda.testing.node.MockNetwork
|
||||
import net.corda.testing.node.MockNetwork.MockNode
|
||||
@ -88,13 +88,13 @@ class DataVendingServiceTests {
|
||||
}
|
||||
|
||||
private fun MockNode.sendNotifyTx(tx: SignedTransaction, walletServiceNode: MockNode) {
|
||||
walletServiceNode.services.registerProtocolInitiator(NotifyTxProtocol::class, ::NotifyTransactionHandler)
|
||||
services.startProtocol(NotifyTxProtocol(walletServiceNode.info.legalIdentity, tx))
|
||||
walletServiceNode.services.registerFlowInitiator(NotifyTxFlow::class, ::NotifyTransactionHandler)
|
||||
services.startFlow(NotifyTxFlow(walletServiceNode.info.legalIdentity, tx))
|
||||
network.runNetwork()
|
||||
}
|
||||
|
||||
|
||||
private class NotifyTxProtocol(val otherParty: Party, val stx: SignedTransaction) : ProtocolLogic<Unit>() {
|
||||
private class NotifyTxFlow(val otherParty: Party, val stx: SignedTransaction) : FlowLogic<Unit>() {
|
||||
@Suspendable
|
||||
override fun call() = send(otherParty, NotifyTxRequest(stx))
|
||||
}
|
||||
|
@ -4,14 +4,14 @@ import co.paralleluniverse.fibers.Fiber
|
||||
import co.paralleluniverse.fibers.Suspendable
|
||||
import com.google.common.util.concurrent.ListenableFuture
|
||||
import net.corda.core.crypto.Party
|
||||
import net.corda.core.protocols.ProtocolLogic
|
||||
import net.corda.core.protocols.ProtocolSessionException
|
||||
import net.corda.core.flows.FlowLogic
|
||||
import net.corda.core.flows.FlowSessionException
|
||||
import net.corda.core.random63BitValue
|
||||
import net.corda.core.serialization.deserialize
|
||||
import net.corda.node.services.persistence.checkpoints
|
||||
import net.corda.node.services.statemachine.StateMachineManager.*
|
||||
import net.corda.node.utilities.databaseTransaction
|
||||
import net.corda.testing.initiateSingleShotProtocol
|
||||
import net.corda.testing.initiateSingleShotFlow
|
||||
import net.corda.testing.node.InMemoryMessagingNetwork
|
||||
import net.corda.testing.node.InMemoryMessagingNetwork.MessageTransfer
|
||||
import net.corda.testing.node.MockNetwork
|
||||
@ -49,29 +49,29 @@ class StateMachineManagerTests {
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `newly added protocol is preserved on restart`() {
|
||||
node1.smm.add(NoOpProtocol(nonTerminating = true))
|
||||
fun `newly added flow is preserved on restart`() {
|
||||
node1.smm.add(NoOpFlow(nonTerminating = true))
|
||||
node1.acceptableLiveFiberCountOnStop = 1
|
||||
val restoredProtocol = node1.restartAndGetRestoredProtocol<NoOpProtocol>()
|
||||
assertThat(restoredProtocol.protocolStarted).isTrue()
|
||||
val restoredFlow = node1.restartAndGetRestoredFlow<NoOpFlow>()
|
||||
assertThat(restoredFlow.flowStarted).isTrue()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `protocol can lazily use the serviceHub in its constructor`() {
|
||||
val protocol = object : ProtocolLogic<Unit>() {
|
||||
fun `flow can lazily use the serviceHub in its constructor`() {
|
||||
val flow = object : FlowLogic<Unit>() {
|
||||
val lazyTime by lazy { serviceHub.clock.instant() }
|
||||
@Suspendable
|
||||
override fun call() = Unit
|
||||
}
|
||||
node1.smm.add(protocol)
|
||||
assertThat(protocol.lazyTime).isNotNull()
|
||||
node1.smm.add(flow)
|
||||
assertThat(flow.lazyTime).isNotNull()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `protocol restarted just after receiving payload`() {
|
||||
node2.services.registerProtocolInitiator(SendProtocol::class) { ReceiveThenSuspendProtocol(it) }
|
||||
fun `flow restarted just after receiving payload`() {
|
||||
node2.services.registerFlowInitiator(SendFlow::class) { ReceiveThenSuspendFlow(it) }
|
||||
val payload = random63BitValue()
|
||||
node1.smm.add(SendProtocol(payload, node2.info.legalIdentity))
|
||||
node1.smm.add(SendFlow(payload, node2.info.legalIdentity))
|
||||
|
||||
// We push through just enough messages to get only the payload sent
|
||||
node2.pumpReceive()
|
||||
@ -79,60 +79,60 @@ class StateMachineManagerTests {
|
||||
node2.acceptableLiveFiberCountOnStop = 1
|
||||
node2.stop()
|
||||
net.runNetwork()
|
||||
val restoredProtocol = node2.restartAndGetRestoredProtocol<ReceiveThenSuspendProtocol>(node1)
|
||||
assertThat(restoredProtocol.receivedPayloads[0]).isEqualTo(payload)
|
||||
val restoredFlow = node2.restartAndGetRestoredFlow<ReceiveThenSuspendFlow>(node1)
|
||||
assertThat(restoredFlow.receivedPayloads[0]).isEqualTo(payload)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `protocol added before network map does run after init`() {
|
||||
fun `flow added before network map does run after init`() {
|
||||
val node3 = net.createNode(node1.info.address) //create vanilla node
|
||||
val protocol = NoOpProtocol()
|
||||
node3.smm.add(protocol)
|
||||
assertEquals(false, protocol.protocolStarted) // Not started yet as no network activity has been allowed yet
|
||||
val flow = NoOpFlow()
|
||||
node3.smm.add(flow)
|
||||
assertEquals(false, flow.flowStarted) // Not started yet as no network activity has been allowed yet
|
||||
net.runNetwork() // Allow network map messages to flow
|
||||
assertEquals(true, protocol.protocolStarted) // Now we should have run the protocol
|
||||
assertEquals(true, flow.flowStarted) // Now we should have run the flow
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `protocol added before network map will be init checkpointed`() {
|
||||
fun `flow added before network map will be init checkpointed`() {
|
||||
var node3 = net.createNode(node1.info.address) //create vanilla node
|
||||
val protocol = NoOpProtocol()
|
||||
node3.smm.add(protocol)
|
||||
assertEquals(false, protocol.protocolStarted) // Not started yet as no network activity has been allowed yet
|
||||
val flow = NoOpFlow()
|
||||
node3.smm.add(flow)
|
||||
assertEquals(false, flow.flowStarted) // Not started yet as no network activity has been allowed yet
|
||||
node3.disableDBCloseOnStop()
|
||||
node3.stop()
|
||||
|
||||
node3 = net.createNode(node1.info.address, forcedID = node3.id)
|
||||
val restoredProtocol = node3.getSingleProtocol<NoOpProtocol>().first
|
||||
assertEquals(false, restoredProtocol.protocolStarted) // Not started yet as no network activity has been allowed yet
|
||||
val restoredFlow = node3.getSingleFlow<NoOpFlow>().first
|
||||
assertEquals(false, restoredFlow.flowStarted) // Not started yet as no network activity has been allowed yet
|
||||
net.runNetwork() // Allow network map messages to flow
|
||||
node3.smm.executor.flush()
|
||||
assertEquals(true, restoredProtocol.protocolStarted) // Now we should have run the protocol and hopefully cleared the init checkpoint
|
||||
assertEquals(true, restoredFlow.flowStarted) // Now we should have run the flow and hopefully cleared the init checkpoint
|
||||
node3.disableDBCloseOnStop()
|
||||
node3.stop()
|
||||
|
||||
// Now it is completed the protocol should leave no Checkpoint.
|
||||
// Now it is completed the flow should leave no Checkpoint.
|
||||
node3 = net.createNode(node1.info.address, forcedID = node3.id)
|
||||
net.runNetwork() // Allow network map messages to flow
|
||||
node3.smm.executor.flush()
|
||||
assertTrue(node3.smm.findStateMachines(NoOpProtocol::class.java).isEmpty())
|
||||
assertTrue(node3.smm.findStateMachines(NoOpFlow::class.java).isEmpty())
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `protocol loaded from checkpoint will respond to messages from before start`() {
|
||||
fun `flow loaded from checkpoint will respond to messages from before start`() {
|
||||
val payload = random63BitValue()
|
||||
node1.services.registerProtocolInitiator(ReceiveThenSuspendProtocol::class) { SendProtocol(payload, it) }
|
||||
node2.smm.add(ReceiveThenSuspendProtocol(node1.info.legalIdentity)) // Prepare checkpointed receive protocol
|
||||
node1.services.registerFlowInitiator(ReceiveThenSuspendFlow::class) { SendFlow(payload, it) }
|
||||
node2.smm.add(ReceiveThenSuspendFlow(node1.info.legalIdentity)) // Prepare checkpointed receive flow
|
||||
// Make sure the add() has finished initial processing.
|
||||
node2.smm.executor.flush()
|
||||
node2.disableDBCloseOnStop()
|
||||
node2.stop() // kill receiver
|
||||
val restoredProtocol = node2.restartAndGetRestoredProtocol<ReceiveThenSuspendProtocol>(node1)
|
||||
assertThat(restoredProtocol.receivedPayloads[0]).isEqualTo(payload)
|
||||
val restoredFlow = node2.restartAndGetRestoredFlow<ReceiveThenSuspendFlow>(node1)
|
||||
assertThat(restoredFlow.receivedPayloads[0]).isEqualTo(payload)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `protocol with send will resend on interrupted restart`() {
|
||||
fun `flow with send will resend on interrupted restart`() {
|
||||
val payload = random63BitValue()
|
||||
val payload2 = random63BitValue()
|
||||
|
||||
@ -140,11 +140,11 @@ class StateMachineManagerTests {
|
||||
net.messagingNetwork.sentMessages.toSessionTransfers().filter { it.isPayloadTransfer }.forEach { sentCount++ }
|
||||
|
||||
val node3 = net.createNode(node1.info.address)
|
||||
val secondProtocol = node3.initiateSingleShotProtocol(PingPongProtocol::class) { PingPongProtocol(it, payload2) }
|
||||
val secondFlow = node3.initiateSingleShotFlow(PingPongFlow::class) { PingPongFlow(it, payload2) }
|
||||
net.runNetwork()
|
||||
|
||||
// Kick off first send and receive
|
||||
node2.smm.add(PingPongProtocol(node3.info.legalIdentity, payload))
|
||||
node2.smm.add(PingPongFlow(node3.info.legalIdentity, payload))
|
||||
databaseTransaction(node2.database) {
|
||||
assertEquals(1, node2.checkpointStorage.checkpoints().size)
|
||||
}
|
||||
@ -158,55 +158,55 @@ class StateMachineManagerTests {
|
||||
}
|
||||
val node2b = net.createNode(node1.info.address, node2.id, advertisedServices = *node2.advertisedServices.toTypedArray())
|
||||
node2.manuallyCloseDB()
|
||||
val (firstAgain, fut1) = node2b.getSingleProtocol<PingPongProtocol>()
|
||||
// Run the network which will also fire up the second protocol. First message should get deduped. So message data stays in sync.
|
||||
val (firstAgain, fut1) = node2b.getSingleFlow<PingPongFlow>()
|
||||
// Run the network which will also fire up the second flow. First message should get deduped. So message data stays in sync.
|
||||
net.runNetwork()
|
||||
node2b.smm.executor.flush()
|
||||
fut1.get()
|
||||
|
||||
val receivedCount = sessionTransfers.count { it.isPayloadTransfer }
|
||||
// Check protocols completed cleanly and didn't get out of phase
|
||||
assertEquals(4, receivedCount, "Protocol should have exchanged 4 unique messages")// Two messages each way
|
||||
// Check flows completed cleanly and didn't get out of phase
|
||||
assertEquals(4, receivedCount, "Flow should have exchanged 4 unique messages")// Two messages each way
|
||||
// can't give a precise value as every addMessageHandler re-runs the undelivered messages
|
||||
assertTrue(sentCount > receivedCount, "Node restart should have retransmitted messages")
|
||||
databaseTransaction(node2b.database) {
|
||||
assertEquals(0, node2b.checkpointStorage.checkpoints().size, "Checkpoints left after restored protocol should have ended")
|
||||
assertEquals(0, node2b.checkpointStorage.checkpoints().size, "Checkpoints left after restored flow should have ended")
|
||||
}
|
||||
databaseTransaction(node3.database) {
|
||||
assertEquals(0, node3.checkpointStorage.checkpoints().size, "Checkpoints left after restored protocol should have ended")
|
||||
assertEquals(0, node3.checkpointStorage.checkpoints().size, "Checkpoints left after restored flow should have ended")
|
||||
}
|
||||
assertEquals(payload2, firstAgain.receivedPayload, "Received payload does not match the first value on Node 3")
|
||||
assertEquals(payload2 + 1, firstAgain.receivedPayload2, "Received payload does not match the expected second value on Node 3")
|
||||
assertEquals(payload, secondProtocol.get().receivedPayload, "Received payload does not match the (restarted) first value on Node 2")
|
||||
assertEquals(payload + 1, secondProtocol.get().receivedPayload2, "Received payload does not match the expected second value on Node 2")
|
||||
assertEquals(payload, secondFlow.get().receivedPayload, "Received payload does not match the (restarted) first value on Node 2")
|
||||
assertEquals(payload + 1, secondFlow.get().receivedPayload2, "Received payload does not match the expected second value on Node 2")
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `sending to multiple parties`() {
|
||||
val node3 = net.createNode(node1.info.address)
|
||||
net.runNetwork()
|
||||
node2.services.registerProtocolInitiator(SendProtocol::class) { ReceiveThenSuspendProtocol(it) }
|
||||
node3.services.registerProtocolInitiator(SendProtocol::class) { ReceiveThenSuspendProtocol(it) }
|
||||
node2.services.registerFlowInitiator(SendFlow::class) { ReceiveThenSuspendFlow(it) }
|
||||
node3.services.registerFlowInitiator(SendFlow::class) { ReceiveThenSuspendFlow(it) }
|
||||
val payload = random63BitValue()
|
||||
node1.smm.add(SendProtocol(payload, node2.info.legalIdentity, node3.info.legalIdentity))
|
||||
node1.smm.add(SendFlow(payload, node2.info.legalIdentity, node3.info.legalIdentity))
|
||||
net.runNetwork()
|
||||
val node2Protocol = node2.getSingleProtocol<ReceiveThenSuspendProtocol>().first
|
||||
val node3Protocol = node3.getSingleProtocol<ReceiveThenSuspendProtocol>().first
|
||||
assertThat(node2Protocol.receivedPayloads[0]).isEqualTo(payload)
|
||||
assertThat(node3Protocol.receivedPayloads[0]).isEqualTo(payload)
|
||||
val node2Flow = node2.getSingleFlow<ReceiveThenSuspendFlow>().first
|
||||
val node3Flow = node3.getSingleFlow<ReceiveThenSuspendFlow>().first
|
||||
assertThat(node2Flow.receivedPayloads[0]).isEqualTo(payload)
|
||||
assertThat(node3Flow.receivedPayloads[0]).isEqualTo(payload)
|
||||
|
||||
assertSessionTransfers(node2,
|
||||
node1 sent sessionInit(node1, SendProtocol::class, payload) to node2,
|
||||
node1 sent sessionInit(node1, SendFlow::class, payload) to node2,
|
||||
node2 sent sessionConfirm() to node1,
|
||||
node1 sent sessionEnd() to node2
|
||||
//There's no session end from the other protocols as they're manually suspended
|
||||
//There's no session end from the other flows as they're manually suspended
|
||||
)
|
||||
|
||||
assertSessionTransfers(node3,
|
||||
node1 sent sessionInit(node1, SendProtocol::class, payload) to node3,
|
||||
node1 sent sessionInit(node1, SendFlow::class, payload) to node3,
|
||||
node3 sent sessionConfirm() to node1,
|
||||
node1 sent sessionEnd() to node3
|
||||
//There's no session end from the other protocols as they're manually suspended
|
||||
//There's no session end from the other flows as they're manually suspended
|
||||
)
|
||||
|
||||
node2.acceptableLiveFiberCountOnStop = 1
|
||||
@ -219,24 +219,24 @@ class StateMachineManagerTests {
|
||||
net.runNetwork()
|
||||
val node2Payload = random63BitValue()
|
||||
val node3Payload = random63BitValue()
|
||||
node2.services.registerProtocolInitiator(ReceiveThenSuspendProtocol::class) { SendProtocol(node2Payload, it) }
|
||||
node3.services.registerProtocolInitiator(ReceiveThenSuspendProtocol::class) { SendProtocol(node3Payload, it) }
|
||||
val multiReceiveProtocol = ReceiveThenSuspendProtocol(node2.info.legalIdentity, node3.info.legalIdentity)
|
||||
node1.smm.add(multiReceiveProtocol)
|
||||
node2.services.registerFlowInitiator(ReceiveThenSuspendFlow::class) { SendFlow(node2Payload, it) }
|
||||
node3.services.registerFlowInitiator(ReceiveThenSuspendFlow::class) { SendFlow(node3Payload, it) }
|
||||
val multiReceiveFlow = ReceiveThenSuspendFlow(node2.info.legalIdentity, node3.info.legalIdentity)
|
||||
node1.smm.add(multiReceiveFlow)
|
||||
node1.acceptableLiveFiberCountOnStop = 1
|
||||
net.runNetwork()
|
||||
assertThat(multiReceiveProtocol.receivedPayloads[0]).isEqualTo(node2Payload)
|
||||
assertThat(multiReceiveProtocol.receivedPayloads[1]).isEqualTo(node3Payload)
|
||||
assertThat(multiReceiveFlow.receivedPayloads[0]).isEqualTo(node2Payload)
|
||||
assertThat(multiReceiveFlow.receivedPayloads[1]).isEqualTo(node3Payload)
|
||||
|
||||
assertSessionTransfers(node2,
|
||||
node1 sent sessionInit(node1, ReceiveThenSuspendProtocol::class) to node2,
|
||||
node1 sent sessionInit(node1, ReceiveThenSuspendFlow::class) to node2,
|
||||
node2 sent sessionConfirm() to node1,
|
||||
node2 sent sessionData(node2Payload) to node1,
|
||||
node2 sent sessionEnd() to node1
|
||||
)
|
||||
|
||||
assertSessionTransfers(node3,
|
||||
node1 sent sessionInit(node1, ReceiveThenSuspendProtocol::class) to node3,
|
||||
node1 sent sessionInit(node1, ReceiveThenSuspendFlow::class) to node3,
|
||||
node3 sent sessionConfirm() to node1,
|
||||
node3 sent sessionData(node3Payload) to node1,
|
||||
node3 sent sessionEnd() to node1
|
||||
@ -245,12 +245,12 @@ class StateMachineManagerTests {
|
||||
|
||||
@Test
|
||||
fun `both sides do a send as their first IO request`() {
|
||||
node2.services.registerProtocolInitiator(PingPongProtocol::class) { PingPongProtocol(it, 20L) }
|
||||
node1.smm.add(PingPongProtocol(node2.info.legalIdentity, 10L))
|
||||
node2.services.registerFlowInitiator(PingPongFlow::class) { PingPongFlow(it, 20L) }
|
||||
node1.smm.add(PingPongFlow(node2.info.legalIdentity, 10L))
|
||||
net.runNetwork()
|
||||
|
||||
assertSessionTransfers(
|
||||
node1 sent sessionInit(node1, PingPongProtocol::class, 10L) to node2,
|
||||
node1 sent sessionInit(node1, PingPongFlow::class, 10L) to node2,
|
||||
node2 sent sessionConfirm() to node1,
|
||||
node2 sent sessionData(20L) to node1,
|
||||
node1 sent sessionData(11L) to node2,
|
||||
@ -261,18 +261,18 @@ class StateMachineManagerTests {
|
||||
|
||||
@Test
|
||||
fun `exception thrown on other side`() {
|
||||
node2.services.registerProtocolInitiator(ReceiveThenSuspendProtocol::class) { ExceptionProtocol }
|
||||
val future = node1.smm.add(ReceiveThenSuspendProtocol(node2.info.legalIdentity)).resultFuture
|
||||
node2.services.registerFlowInitiator(ReceiveThenSuspendFlow::class) { ExceptionFlow }
|
||||
val future = node1.smm.add(ReceiveThenSuspendFlow(node2.info.legalIdentity)).resultFuture
|
||||
net.runNetwork()
|
||||
assertThatThrownBy { future.get() }.hasCauseInstanceOf(ProtocolSessionException::class.java)
|
||||
assertThatThrownBy { future.get() }.hasCauseInstanceOf(FlowSessionException::class.java)
|
||||
assertSessionTransfers(
|
||||
node1 sent sessionInit(node1, ReceiveThenSuspendProtocol::class) to node2,
|
||||
node1 sent sessionInit(node1, ReceiveThenSuspendFlow::class) to node2,
|
||||
node2 sent sessionConfirm() to node1,
|
||||
node2 sent sessionEnd() to node1
|
||||
)
|
||||
}
|
||||
|
||||
private inline fun <reified P : ProtocolLogic<*>> MockNode.restartAndGetRestoredProtocol(
|
||||
private inline fun <reified P : FlowLogic<*>> MockNode.restartAndGetRestoredFlow(
|
||||
networkMapNode: MockNode? = null): P {
|
||||
disableDBCloseOnStop() //Handover DB to new node copy
|
||||
stop()
|
||||
@ -280,15 +280,15 @@ class StateMachineManagerTests {
|
||||
newNode.acceptableLiveFiberCountOnStop = 1
|
||||
manuallyCloseDB()
|
||||
mockNet.runNetwork() // allow NetworkMapService messages to stabilise and thus start the state machine
|
||||
return newNode.getSingleProtocol<P>().first
|
||||
return newNode.getSingleFlow<P>().first
|
||||
}
|
||||
|
||||
private inline fun <reified P : ProtocolLogic<*>> MockNode.getSingleProtocol(): Pair<P, ListenableFuture<*>> {
|
||||
private inline fun <reified P : FlowLogic<*>> MockNode.getSingleFlow(): Pair<P, ListenableFuture<*>> {
|
||||
return smm.findStateMachines(P::class.java).single()
|
||||
}
|
||||
|
||||
private fun sessionInit(initiatorNode: MockNode, protocolMarker: KClass<*>, payload: Any? = null): SessionInit {
|
||||
return SessionInit(0, initiatorNode.info.legalIdentity, protocolMarker.java.name, payload)
|
||||
private fun sessionInit(initiatorNode: MockNode, flowMarker: KClass<*>, payload: Any? = null): SessionInit {
|
||||
return SessionInit(0, initiatorNode.info.legalIdentity, flowMarker.java.name, payload)
|
||||
}
|
||||
|
||||
private fun sessionConfirm() = SessionConfirm(0, 0)
|
||||
@ -334,13 +334,13 @@ class StateMachineManagerTests {
|
||||
private infix fun Pair<Int, SessionMessage>.to(node: MockNode): SessionTransfer = SessionTransfer(first, second, node.id)
|
||||
|
||||
|
||||
private class NoOpProtocol(val nonTerminating: Boolean = false) : ProtocolLogic<Unit>() {
|
||||
private class NoOpFlow(val nonTerminating: Boolean = false) : FlowLogic<Unit>() {
|
||||
|
||||
@Transient var protocolStarted = false
|
||||
@Transient var flowStarted = false
|
||||
|
||||
@Suspendable
|
||||
override fun call() {
|
||||
protocolStarted = true
|
||||
flowStarted = true
|
||||
if (nonTerminating) {
|
||||
Fiber.park()
|
||||
}
|
||||
@ -348,7 +348,7 @@ class StateMachineManagerTests {
|
||||
}
|
||||
|
||||
|
||||
private class SendProtocol(val payload: Any, vararg val otherParties: Party) : ProtocolLogic<Unit>() {
|
||||
private class SendFlow(val payload: Any, vararg val otherParties: Party) : FlowLogic<Unit>() {
|
||||
|
||||
init {
|
||||
require(otherParties.isNotEmpty())
|
||||
@ -359,7 +359,7 @@ class StateMachineManagerTests {
|
||||
}
|
||||
|
||||
|
||||
private class ReceiveThenSuspendProtocol(vararg val otherParties: Party) : ProtocolLogic<Unit>() {
|
||||
private class ReceiveThenSuspendFlow(vararg val otherParties: Party) : FlowLogic<Unit>() {
|
||||
|
||||
init {
|
||||
require(otherParties.isNotEmpty())
|
||||
@ -375,7 +375,7 @@ class StateMachineManagerTests {
|
||||
}
|
||||
}
|
||||
|
||||
private class PingPongProtocol(val otherParty: Party, val payload: Long) : ProtocolLogic<Unit>() {
|
||||
private class PingPongFlow(val otherParty: Party, val payload: Long) : FlowLogic<Unit>() {
|
||||
|
||||
@Transient var receivedPayload: Long? = null
|
||||
@Transient var receivedPayload2: Long? = null
|
||||
@ -383,13 +383,13 @@ class StateMachineManagerTests {
|
||||
@Suspendable
|
||||
override fun call() {
|
||||
receivedPayload = sendAndReceive<Long>(otherParty, payload).unwrap { it }
|
||||
println("${psm.id} Received $receivedPayload")
|
||||
println("${fsm.id} Received $receivedPayload")
|
||||
receivedPayload2 = sendAndReceive<Long>(otherParty, payload + 1).unwrap { it }
|
||||
println("${psm.id} Received $receivedPayload2")
|
||||
println("${fsm.id} Received $receivedPayload2")
|
||||
}
|
||||
}
|
||||
|
||||
private object ExceptionProtocol : ProtocolLogic<Nothing>() {
|
||||
private object ExceptionFlow : FlowLogic<Nothing>() {
|
||||
override fun call(): Nothing = throw Exception()
|
||||
}
|
||||
|
||||
|
@ -8,7 +8,7 @@ import net.corda.core.success
|
||||
import net.corda.core.utilities.ApiUtils
|
||||
import net.corda.core.utilities.Emoji
|
||||
import net.corda.core.utilities.loggerFor
|
||||
import net.corda.protocols.FinalityProtocol
|
||||
import net.corda.flows.FinalityFlow
|
||||
import net.corda.testing.ALICE_KEY
|
||||
import java.util.concurrent.CompletableFuture
|
||||
import javax.ws.rs.*
|
||||
@ -49,10 +49,10 @@ class AttachmentDemoApi(val services: ServiceHub) {
|
||||
|
||||
// Send the transaction to the other recipient
|
||||
val tx = ptx.toSignedTransaction()
|
||||
services.invokeProtocolAsync(FinalityProtocol::class.java, tx, setOf(it)).resultFuture.success {
|
||||
println("Successfully sent attachment with the FinalityProtocol")
|
||||
services.invokeFlowAsync<Unit>(FinalityFlow::class.java, tx, setOf(it)).resultFuture.success {
|
||||
println("Successfully sent attachment with the FinalityFlow")
|
||||
}.failure {
|
||||
logger.error("Failed to send attachment with the FinalityProtocol", it)
|
||||
logger.error("Failed to send attachment with the FinalityFlow")
|
||||
}
|
||||
|
||||
Response.accepted().build()
|
||||
@ -64,10 +64,10 @@ class AttachmentDemoApi(val services: ServiceHub) {
|
||||
@Consumes(MediaType.APPLICATION_JSON)
|
||||
fun runRecipient(): Response {
|
||||
val future = CompletableFuture<Response>()
|
||||
// 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.
|
||||
services.storageService.validatedTransactions.updates.subscribe { event ->
|
||||
// When the transaction is received, it's passed through [ResolveTransactionsProtocol], which first fetches any
|
||||
// When the transaction is received, it's passed through [ResolveTransactionsFlow], which first fetches any
|
||||
// attachments for us, then verifies the transaction. As such, by the time it hits the validated transaction store,
|
||||
// we have a copy of the attachment.
|
||||
val tx = event.tx
|
||||
|
@ -3,14 +3,14 @@ package net.corda.attachmentdemo.plugin
|
||||
import net.corda.attachmentdemo.api.AttachmentDemoApi
|
||||
import net.corda.core.node.CordaPluginRegistry
|
||||
import net.corda.core.transactions.SignedTransaction
|
||||
import net.corda.protocols.FinalityProtocol
|
||||
import net.corda.flows.FinalityFlow
|
||||
|
||||
class AttachmentDemoPlugin : CordaPluginRegistry() {
|
||||
// A list of classes that expose web APIs.
|
||||
override val webApis: List<Class<*>> = listOf(AttachmentDemoApi::class.java)
|
||||
// A list of protocols that are required for this cordapp
|
||||
override val requiredProtocols: Map<String, Set<String>> = mapOf(
|
||||
FinalityProtocol::class.java.name to setOf(SignedTransaction::class.java.name, setOf(Unit).javaClass.name, setOf(Unit).javaClass.name)
|
||||
// A list of flows that are required for this cordapp
|
||||
override val requiredFlows: Map<String, Set<String>> = mapOf(
|
||||
FinalityFlow::class.java.name to setOf(SignedTransaction::class.java.name, setOf(Unit).javaClass.name, setOf(Unit).javaClass.name)
|
||||
)
|
||||
override val servicePlugins: List<Class<*>> = listOf()
|
||||
}
|
||||
|
@ -4,9 +4,9 @@ import net.corda.core.node.ServiceHub
|
||||
import net.corda.core.node.services.linearHeadsOfType
|
||||
import net.corda.core.utilities.loggerFor
|
||||
import net.corda.irs.contract.InterestRateSwap
|
||||
import net.corda.irs.protocols.AutoOfferProtocol
|
||||
import net.corda.irs.protocols.ExitServerProtocol
|
||||
import net.corda.irs.protocols.UpdateBusinessDayProtocol
|
||||
import net.corda.irs.flows.AutoOfferFlow
|
||||
import net.corda.irs.flows.ExitServerFlow
|
||||
import net.corda.irs.flows.UpdateBusinessDayFlow
|
||||
import java.net.URI
|
||||
import java.time.LocalDate
|
||||
import java.time.LocalDateTime
|
||||
@ -65,7 +65,7 @@ class InterestRateSwapAPI(val services: ServiceHub) {
|
||||
@Consumes(MediaType.APPLICATION_JSON)
|
||||
fun storeDeal(newDeal: InterestRateSwap.State): Response {
|
||||
try {
|
||||
services.invokeProtocolAsync(AutoOfferProtocol.Requester::class.java, newDeal).resultFuture.get()
|
||||
services.invokeFlowAsync(AutoOfferFlow.Requester::class.java, newDeal).resultFuture.get()
|
||||
return Response.created(URI.create(generateDealLink(newDeal))).build()
|
||||
} catch (ex: Throwable) {
|
||||
logger.info("Exception when creating deal: $ex")
|
||||
@ -92,7 +92,7 @@ class InterestRateSwapAPI(val services: ServiceHub) {
|
||||
val priorDemoDate = fetchDemoDate()
|
||||
// Can only move date forwards
|
||||
if (newDemoDate.isAfter(priorDemoDate)) {
|
||||
services.invokeProtocolAsync(UpdateBusinessDayProtocol.Broadcast::class.java, newDemoDate).resultFuture.get()
|
||||
services.invokeFlowAsync(UpdateBusinessDayFlow.Broadcast::class.java, newDemoDate).resultFuture.get()
|
||||
return Response.ok().build()
|
||||
}
|
||||
val msg = "demodate is already $priorDemoDate and can only be updated with a later date"
|
||||
@ -111,7 +111,7 @@ class InterestRateSwapAPI(val services: ServiceHub) {
|
||||
@Path("restart")
|
||||
@Consumes(MediaType.APPLICATION_JSON)
|
||||
fun exitServer(): Response {
|
||||
services.invokeProtocolAsync(ExitServerProtocol.Broadcast::class.java, 83).resultFuture.get()
|
||||
services.invokeFlowAsync(ExitServerFlow.Broadcast::class.java, 83).resultFuture.get()
|
||||
return Response.ok().build()
|
||||
}
|
||||
}
|
||||
|
@ -4,18 +4,18 @@ import co.paralleluniverse.fibers.Suspendable
|
||||
import net.corda.core.RetryableException
|
||||
import net.corda.core.contracts.*
|
||||
import net.corda.core.crypto.*
|
||||
import net.corda.core.flows.FlowLogic
|
||||
import net.corda.core.math.CubicSplineInterpolator
|
||||
import net.corda.core.math.Interpolator
|
||||
import net.corda.core.math.InterpolatorFactory
|
||||
import net.corda.core.node.CordaPluginRegistry
|
||||
import net.corda.core.node.PluginServiceHub
|
||||
import net.corda.core.node.services.ServiceType
|
||||
import net.corda.core.protocols.ProtocolLogic
|
||||
import net.corda.core.serialization.SingletonSerializeAsToken
|
||||
import net.corda.core.transactions.FilteredTransaction
|
||||
import net.corda.core.utilities.ProgressTracker
|
||||
import net.corda.irs.protocols.FixingProtocol
|
||||
import net.corda.irs.protocols.RatesFixProtocol
|
||||
import net.corda.irs.flows.FixingFlow
|
||||
import net.corda.irs.flows.RatesFixFlow
|
||||
import net.corda.node.services.api.AcceptsFileUpload
|
||||
import net.corda.node.utilities.AbstractJDBCHashSet
|
||||
import net.corda.node.utilities.FiberBox
|
||||
@ -46,10 +46,10 @@ object NodeInterestRates {
|
||||
val type = ServiceType.corda.getSubType("interest_rates")
|
||||
|
||||
/**
|
||||
* Register the protocol that is used with the Fixing integration tests.
|
||||
* Register the flow that is used with the Fixing integration tests.
|
||||
*/
|
||||
class Plugin : CordaPluginRegistry() {
|
||||
override val requiredProtocols: Map<String, Set<String>> = mapOf(Pair(FixingProtocol.FixingRoleDecider::class.java.name, setOf(Duration::class.java.name, StateRef::class.java.name)))
|
||||
override val requiredFlows: Map<String, Set<String>> = mapOf(Pair(FixingFlow.FixingRoleDecider::class.java.name, setOf(Duration::class.java.name, StateRef::class.java.name)))
|
||||
override val servicePlugins: List<Class<*>> = listOf(Service::class.java)
|
||||
}
|
||||
|
||||
@ -67,20 +67,20 @@ object NodeInterestRates {
|
||||
init {
|
||||
// Note access to the singleton oracle property is via the registered SingletonSerializeAsToken Service.
|
||||
// Otherwise the Kryo serialisation of the call stack in the Quasar Fiber extends to include
|
||||
// the framework Oracle and the protocol will crash.
|
||||
services.registerProtocolInitiator(RatesFixProtocol.FixSignProtocol::class) { FixSignHandler(it, this) }
|
||||
services.registerProtocolInitiator(RatesFixProtocol.FixQueryProtocol::class) { FixQueryHandler(it, this) }
|
||||
// the framework Oracle and the flow will crash.
|
||||
services.registerFlowInitiator(RatesFixFlow.FixSignFlow::class) { FixSignHandler(it, this) }
|
||||
services.registerFlowInitiator(RatesFixFlow.FixQueryFlow::class) { FixQueryHandler(it, this) }
|
||||
}
|
||||
|
||||
private class FixSignHandler(val otherParty: Party, val service: Service) : ProtocolLogic<Unit>() {
|
||||
private class FixSignHandler(val otherParty: Party, val service: Service) : FlowLogic<Unit>() {
|
||||
@Suspendable
|
||||
override fun call() {
|
||||
val request = receive<RatesFixProtocol.SignRequest>(otherParty).unwrap { it }
|
||||
val request = receive<RatesFixFlow.SignRequest>(otherParty).unwrap { it }
|
||||
send(otherParty, service.oracle.sign(request.ftx, request.rootHash))
|
||||
}
|
||||
}
|
||||
|
||||
private class FixQueryHandler(val otherParty: Party, val service: Service) : ProtocolLogic<Unit>() {
|
||||
private class FixQueryHandler(val otherParty: Party, val service: Service) : FlowLogic<Unit>() {
|
||||
|
||||
companion object {
|
||||
object RECEIVED : ProgressTracker.Step("Received fix request")
|
||||
@ -95,7 +95,7 @@ object NodeInterestRates {
|
||||
|
||||
@Suspendable
|
||||
override fun call(): Unit {
|
||||
val request = receive<RatesFixProtocol.QueryRequest>(otherParty).unwrap { it }
|
||||
val request = receive<RatesFixFlow.QueryRequest>(otherParty).unwrap { it }
|
||||
val answers = service.oracle.query(request.queries, request.deadline)
|
||||
progressTracker.currentStep = SENDING
|
||||
send(otherParty, answers)
|
||||
|
@ -5,10 +5,10 @@ import net.corda.core.contracts.clauses.*
|
||||
import net.corda.core.crypto.CompositeKey
|
||||
import net.corda.core.crypto.Party
|
||||
import net.corda.core.crypto.SecureHash
|
||||
import net.corda.core.flows.FlowLogicRefFactory
|
||||
import net.corda.core.node.services.ServiceType
|
||||
import net.corda.core.protocols.ProtocolLogicRefFactory
|
||||
import net.corda.core.transactions.TransactionBuilder
|
||||
import net.corda.irs.protocols.FixingProtocol
|
||||
import net.corda.irs.flows.FixingFlow
|
||||
import net.corda.irs.utilities.suggestInterestRateAnnouncementTimeWindow
|
||||
import org.apache.commons.jexl3.JexlBuilder
|
||||
import org.apache.commons.jexl3.MapContext
|
||||
@ -674,12 +674,12 @@ class InterestRateSwap() : Contract {
|
||||
override val parties: List<Party>
|
||||
get() = listOf(fixedLeg.fixedRatePayer, floatingLeg.floatingRatePayer)
|
||||
|
||||
override fun nextScheduledActivity(thisStateRef: StateRef, protocolLogicRefFactory: ProtocolLogicRefFactory): ScheduledActivity? {
|
||||
override fun nextScheduledActivity(thisStateRef: StateRef, flowLogicRefFactory: FlowLogicRefFactory): ScheduledActivity? {
|
||||
val nextFixingOf = nextFixingOf() ?: return null
|
||||
|
||||
// This is perhaps not how we should determine the time point in the business day, but instead expect the schedule to detail some of these aspects
|
||||
val instant = suggestInterestRateAnnouncementTimeWindow(index = nextFixingOf.name, source = floatingLeg.indexSource, date = nextFixingOf.forDay).start
|
||||
return ScheduledActivity(protocolLogicRefFactory.create(FixingProtocol.FixingRoleDecider::class.java, thisStateRef), instant)
|
||||
return ScheduledActivity(flowLogicRefFactory.create(FixingFlow.FixingRoleDecider::class.java, thisStateRef), instant)
|
||||
}
|
||||
|
||||
override fun generateAgreement(notary: Party): TransactionBuilder = InterestRateSwap().generateAgreement(floatingLeg, fixedLeg, calculation, common, notary)
|
||||
|
@ -1,28 +1,27 @@
|
||||
package net.corda.irs.protocols
|
||||
package net.corda.irs.flows
|
||||
|
||||
import co.paralleluniverse.fibers.Suspendable
|
||||
import net.corda.core.contracts.DealState
|
||||
import net.corda.core.crypto.Party
|
||||
import net.corda.core.flows.FlowLogic
|
||||
import net.corda.core.node.CordaPluginRegistry
|
||||
import net.corda.core.node.PluginServiceHub
|
||||
import net.corda.core.protocols.ProtocolLogic
|
||||
import net.corda.core.serialization.SingletonSerializeAsToken
|
||||
import net.corda.core.transactions.SignedTransaction
|
||||
import net.corda.core.utilities.ProgressTracker
|
||||
import net.corda.node.services.api.ServiceHubInternal
|
||||
import net.corda.protocols.TwoPartyDealProtocol
|
||||
import net.corda.protocols.TwoPartyDealProtocol.Acceptor
|
||||
import net.corda.protocols.TwoPartyDealProtocol.AutoOffer
|
||||
import net.corda.protocols.TwoPartyDealProtocol.Instigator
|
||||
import net.corda.flows.TwoPartyDealFlow
|
||||
import net.corda.flows.TwoPartyDealFlow.Acceptor
|
||||
import net.corda.flows.TwoPartyDealFlow.AutoOffer
|
||||
import net.corda.flows.TwoPartyDealFlow.Instigator
|
||||
|
||||
/**
|
||||
* This whole class is really part of a demo just to initiate the agreement of a deal with a simple
|
||||
* API call from a single party without bi-directional access to the database of offers etc.
|
||||
*
|
||||
* In the "real world", we'd probably have the offers sitting in the platform prior to the agreement step
|
||||
* or the protocol would have to reach out to external systems (or users) to verify the deals.
|
||||
* or the flow would have to reach out to external systems (or users) to verify the deals.
|
||||
*/
|
||||
object AutoOfferProtocol {
|
||||
object AutoOfferFlow {
|
||||
|
||||
class Plugin : CordaPluginRegistry() {
|
||||
override val servicePlugins: List<Class<*>> = listOf(Service::class.java)
|
||||
@ -31,24 +30,24 @@ object AutoOfferProtocol {
|
||||
|
||||
class Service(services: PluginServiceHub) : SingletonSerializeAsToken() {
|
||||
|
||||
object DEALING : ProgressTracker.Step("Starting the deal protocol") {
|
||||
override fun childProgressTracker(): ProgressTracker = TwoPartyDealProtocol.Secondary.tracker()
|
||||
object DEALING : ProgressTracker.Step("Starting the deal flow") {
|
||||
override fun childProgressTracker(): ProgressTracker = TwoPartyDealFlow.Secondary.tracker()
|
||||
}
|
||||
|
||||
init {
|
||||
services.registerProtocolInitiator(Instigator::class) { Acceptor(it) }
|
||||
services.registerFlowInitiator(Instigator::class) { Acceptor(it) }
|
||||
}
|
||||
}
|
||||
|
||||
class Requester(val dealToBeOffered: DealState) : ProtocolLogic<SignedTransaction>() {
|
||||
class Requester(val dealToBeOffered: DealState) : FlowLogic<SignedTransaction>() {
|
||||
|
||||
companion object {
|
||||
object RECEIVED : ProgressTracker.Step("Received API call")
|
||||
object DEALING : ProgressTracker.Step("Starting the deal protocol") {
|
||||
override fun childProgressTracker(): ProgressTracker = TwoPartyDealProtocol.Primary.tracker()
|
||||
object DEALING : ProgressTracker.Step("Starting the deal flow") {
|
||||
override fun childProgressTracker(): ProgressTracker = TwoPartyDealFlow.Primary.tracker()
|
||||
}
|
||||
|
||||
// We vend a progress tracker that already knows there's going to be a TwoPartyTradingProtocol involved at some
|
||||
// We vend a progress tracker that already knows there's going to be a TwoPartyTradingFlow involved at some
|
||||
// point: by setting up the tracker in advance, the user can see what's coming in more detail, instead of being
|
||||
// surprised when it appears as a new set of tasks below the current one.
|
||||
fun tracker() = ProgressTracker(RECEIVED, DEALING)
|
||||
@ -74,7 +73,7 @@ object AutoOfferProtocol {
|
||||
myKey,
|
||||
progressTracker.getChildProgressTracker(DEALING)!!
|
||||
)
|
||||
val stx = subProtocol(instigator)
|
||||
val stx = subFlow(instigator)
|
||||
return stx
|
||||
}
|
||||
|
@ -1,23 +1,22 @@
|
||||
package net.corda.irs.protocols
|
||||
package net.corda.irs.flows
|
||||
|
||||
import co.paralleluniverse.fibers.Suspendable
|
||||
import co.paralleluniverse.strands.Strand
|
||||
import net.corda.core.crypto.Party
|
||||
import net.corda.core.flows.FlowLogic
|
||||
import net.corda.core.node.CordaPluginRegistry
|
||||
import net.corda.core.node.NodeInfo
|
||||
import net.corda.core.node.PluginServiceHub
|
||||
import net.corda.core.protocols.ProtocolLogic
|
||||
import net.corda.node.services.api.ServiceHubInternal
|
||||
import net.corda.testing.node.MockNetworkMapCache
|
||||
import java.util.concurrent.TimeUnit
|
||||
|
||||
object ExitServerProtocol {
|
||||
object ExitServerFlow {
|
||||
|
||||
// Will only be enabled if you install the Handler
|
||||
@Volatile private var enabled = false
|
||||
|
||||
// This is not really a HandshakeMessage but needs to be so that the send uses the default session ID. This will
|
||||
// resolve itself when the protocol session stuff is done.
|
||||
// resolve itself when the flow session stuff is done.
|
||||
data class ExitMessage(val exitCode: Int)
|
||||
|
||||
class Plugin: CordaPluginRegistry() {
|
||||
@ -26,13 +25,13 @@ object ExitServerProtocol {
|
||||
|
||||
class Service(services: PluginServiceHub) {
|
||||
init {
|
||||
services.registerProtocolInitiator(Broadcast::class, ::ExitServerHandler)
|
||||
services.registerFlowInitiator(Broadcast::class, ::ExitServerHandler)
|
||||
enabled = true
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private class ExitServerHandler(val otherParty: Party) : ProtocolLogic<Unit>() {
|
||||
private class ExitServerHandler(val otherParty: Party) : FlowLogic<Unit>() {
|
||||
override fun call() {
|
||||
// Just to validate we got the message
|
||||
if (enabled) {
|
||||
@ -47,7 +46,7 @@ object ExitServerProtocol {
|
||||
* This takes a Java Integer rather than Kotlin Int as that is what we end up with in the calling map and currently
|
||||
* we do not support coercing numeric types in the reflective search for matching constructors.
|
||||
*/
|
||||
class Broadcast(val exitCode: Int) : ProtocolLogic<Boolean>() {
|
||||
class Broadcast(val exitCode: Int) : FlowLogic<Boolean>() {
|
||||
|
||||
@Suspendable
|
||||
override fun call(): Boolean {
|
@ -1,45 +1,45 @@
|
||||
package net.corda.irs.protocols
|
||||
package net.corda.irs.flows
|
||||
|
||||
import co.paralleluniverse.fibers.Suspendable
|
||||
import net.corda.core.TransientProperty
|
||||
import net.corda.core.contracts.*
|
||||
import net.corda.core.crypto.CompositeKey
|
||||
import net.corda.core.crypto.Party
|
||||
import net.corda.core.flows.FlowLogic
|
||||
import net.corda.core.node.NodeInfo
|
||||
import net.corda.core.node.PluginServiceHub
|
||||
import net.corda.core.node.services.ServiceType
|
||||
import net.corda.core.protocols.ProtocolLogic
|
||||
import net.corda.core.seconds
|
||||
import net.corda.core.transactions.FilterFuns
|
||||
import net.corda.core.transactions.TransactionBuilder
|
||||
import net.corda.core.utilities.ProgressTracker
|
||||
import net.corda.core.utilities.trace
|
||||
import net.corda.protocols.TwoPartyDealProtocol
|
||||
import net.corda.flows.TwoPartyDealFlow
|
||||
import java.math.BigDecimal
|
||||
import java.security.KeyPair
|
||||
|
||||
object FixingProtocol {
|
||||
object FixingFlow {
|
||||
|
||||
class Service(services: PluginServiceHub) {
|
||||
init {
|
||||
services.registerProtocolInitiator(Floater::class) { Fixer(it) }
|
||||
services.registerFlowInitiator(Floater::class) { Fixer(it) }
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* One side of the fixing protocol for an interest rate swap, but could easily be generalised further.
|
||||
* One side of the fixing flow for an interest rate swap, but could easily be generalised further.
|
||||
*
|
||||
* Do not infer too much from the name of the class. This is just to indicate that it is the "side"
|
||||
* of the protocol that is run by the party with the fixed leg of swap deal, which is the basis for deciding
|
||||
* who does what in the protocol.
|
||||
* of the flow that is run by the party with the fixed leg of swap deal, which is the basis for deciding
|
||||
* who does what in the flow.
|
||||
*/
|
||||
class Fixer(override val otherParty: Party,
|
||||
override val progressTracker: ProgressTracker = TwoPartyDealProtocol.Secondary.tracker()) : TwoPartyDealProtocol.Secondary<FixingSession>() {
|
||||
override val progressTracker: ProgressTracker = TwoPartyDealFlow.Secondary.tracker()) : TwoPartyDealFlow.Secondary<FixingSession>() {
|
||||
|
||||
private lateinit var txState: TransactionState<*>
|
||||
private lateinit var deal: FixableDealState
|
||||
|
||||
override fun validateHandshake(handshake: TwoPartyDealProtocol.Handshake<FixingSession>): TwoPartyDealProtocol.Handshake<FixingSession> {
|
||||
override fun validateHandshake(handshake: TwoPartyDealFlow.Handshake<FixingSession>): TwoPartyDealFlow.Handshake<FixingSession> {
|
||||
logger.trace { "Got fixing request for: ${handshake.payload}" }
|
||||
|
||||
txState = serviceHub.loadState(handshake.payload.ref)
|
||||
@ -55,7 +55,7 @@ object FixingProtocol {
|
||||
}
|
||||
|
||||
@Suspendable
|
||||
override fun assembleSharedTX(handshake: TwoPartyDealProtocol.Handshake<FixingSession>): Pair<TransactionBuilder, List<CompositeKey>> {
|
||||
override fun assembleSharedTX(handshake: TwoPartyDealFlow.Handshake<FixingSession>): Pair<TransactionBuilder, List<CompositeKey>> {
|
||||
@Suppress("UNCHECKED_CAST")
|
||||
val fixOf = deal.nextFixingOf()!!
|
||||
|
||||
@ -70,10 +70,10 @@ object FixingProtocol {
|
||||
val oracle = serviceHub.networkMapCache.get(handshake.payload.oracleType).first()
|
||||
val oracleParty = oracle.serviceIdentities(handshake.payload.oracleType).first()
|
||||
|
||||
// TODO Could it be solved in better way, move filtering here not in RatesFixProtocol?
|
||||
// TODO Could it be solved in better way, move filtering here not in RatesFixFlow?
|
||||
fun filterCommands(c: Command) = oracleParty.owningKey in c.signers && c.value is Fix
|
||||
val filterFuns = FilterFuns(filterCommands = ::filterCommands)
|
||||
val addFixing = object : RatesFixProtocol(ptx, filterFuns, oracleParty, fixOf, BigDecimal.ZERO, BigDecimal.ONE) {
|
||||
val addFixing = object : RatesFixFlow(ptx, filterFuns, oracleParty, fixOf, BigDecimal.ZERO, BigDecimal.ONE) {
|
||||
@Suspendable
|
||||
override fun beforeSigning(fix: Fix) {
|
||||
newDeal.generateFix(ptx, StateAndRef(txState, handshake.payload.ref), fix)
|
||||
@ -83,21 +83,21 @@ object FixingProtocol {
|
||||
ptx.setTime(serviceHub.clock.instant(), 30.seconds)
|
||||
}
|
||||
}
|
||||
subProtocol(addFixing)
|
||||
subFlow(addFixing)
|
||||
return Pair(ptx, arrayListOf(myOldParty.owningKey))
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* One side of the fixing protocol for an interest rate swap, but could easily be generalised furher.
|
||||
* One side of the fixing flow for an interest rate swap, but could easily be generalised furher.
|
||||
*
|
||||
* As per the [Fixer], do not infer too much from this class name in terms of business roles. This
|
||||
* is just the "side" of the protocol run by the party with the floating leg as a way of deciding who
|
||||
* does what in the protocol.
|
||||
* is just the "side" of the flow run by the party with the floating leg as a way of deciding who
|
||||
* does what in the flow.
|
||||
*/
|
||||
class Floater(override val otherParty: Party,
|
||||
override val payload: FixingSession,
|
||||
override val progressTracker: ProgressTracker = TwoPartyDealProtocol.Primary.tracker()) : TwoPartyDealProtocol.Primary() {
|
||||
override val progressTracker: ProgressTracker = TwoPartyDealFlow.Primary.tracker()) : TwoPartyDealFlow.Primary() {
|
||||
|
||||
@Suppress("UNCHECKED_CAST")
|
||||
internal val dealToFix: StateAndRef<FixableDealState> by TransientProperty {
|
||||
@ -120,7 +120,7 @@ object FixingProtocol {
|
||||
data class FixingSession(val ref: StateRef, val oracleType: ServiceType)
|
||||
|
||||
/**
|
||||
* This protocol looks at the deal and decides whether to be the Fixer or Floater role in agreeing a fixing.
|
||||
* 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
|
||||
@ -129,7 +129,7 @@ object FixingProtocol {
|
||||
* TODO: Replace [FixingSession] and [FixingSessionInitiationHandler] with generic session initiation logic once it exists.
|
||||
*/
|
||||
class FixingRoleDecider(val ref: StateRef,
|
||||
override val progressTracker: ProgressTracker = tracker()) : ProtocolLogic<Unit>() {
|
||||
override val progressTracker: ProgressTracker = tracker()) : FlowLogic<Unit>() {
|
||||
|
||||
companion object {
|
||||
class LOADING() : ProgressTracker.Step("Loading state to decide fixing role")
|
||||
@ -147,7 +147,7 @@ object FixingProtocol {
|
||||
if (sortedParties[0].name == serviceHub.myInfo.legalIdentity.name) {
|
||||
val fixing = FixingSession(ref, fixableDeal.oracleType)
|
||||
// Start the Floater which will then kick-off the Fixer
|
||||
subProtocol(Floater(sortedParties[1], fixing))
|
||||
subFlow(Floater(sortedParties[1], fixing))
|
||||
}
|
||||
}
|
||||
}
|
@ -1,17 +1,17 @@
|
||||
package net.corda.irs.protocols
|
||||
package net.corda.irs.flows
|
||||
|
||||
import co.paralleluniverse.fibers.Suspendable
|
||||
import net.corda.core.contracts.Fix
|
||||
import net.corda.core.contracts.FixOf
|
||||
import net.corda.core.contracts.Timestamp
|
||||
import net.corda.core.crypto.DigitalSignature
|
||||
import net.corda.core.crypto.Party
|
||||
import net.corda.core.crypto.SecureHash
|
||||
import net.corda.core.protocols.ProtocolLogic
|
||||
import net.corda.core.flows.FlowLogic
|
||||
import net.corda.core.transactions.FilterFuns
|
||||
import net.corda.core.transactions.FilteredTransaction
|
||||
import net.corda.core.transactions.TransactionBuilder
|
||||
import net.corda.core.utilities.ProgressTracker
|
||||
import net.corda.irs.flows.RatesFixFlow.FixOutOfRange
|
||||
import net.corda.irs.utilities.suggestInterestRateAnnouncementTimeWindow
|
||||
import java.math.BigDecimal
|
||||
import java.time.Instant
|
||||
@ -20,21 +20,21 @@ import java.util.*
|
||||
// This code is unit tested in NodeInterestRates.kt
|
||||
|
||||
/**
|
||||
* This protocol queries the given oracle for an interest rate fix, and if it is within the given tolerance embeds the
|
||||
* This flow queries the given oracle for an interest rate fix, and if it is within the given tolerance embeds the
|
||||
* fix in the transaction and then proceeds to get the oracle to sign it. Although the [call] method combines the query
|
||||
* and signing step, you can run the steps individually by constructing this object and then using the public methods
|
||||
* for each step.
|
||||
*
|
||||
* @throws FixOutOfRange if the returned fix was further away from the expected rate by the given amount.
|
||||
*/
|
||||
open class RatesFixProtocol(protected val tx: TransactionBuilder,
|
||||
open class RatesFixFlow(protected val tx: TransactionBuilder,
|
||||
// Filtering functions over transaction, used to build partial transaction presented to oracle.
|
||||
private val filterFuns: FilterFuns,
|
||||
private val oracle: Party,
|
||||
private val fixOf: FixOf,
|
||||
private val expectedRate: BigDecimal,
|
||||
private val rateTolerance: BigDecimal,
|
||||
override val progressTracker: ProgressTracker = RatesFixProtocol.tracker(fixOf.name)) : ProtocolLogic<Unit>() {
|
||||
private val filterFuns: FilterFuns,
|
||||
private val oracle: Party,
|
||||
private val fixOf: FixOf,
|
||||
private val expectedRate: BigDecimal,
|
||||
private val rateTolerance: BigDecimal,
|
||||
override val progressTracker: ProgressTracker = RatesFixFlow.tracker(fixOf.name)) : FlowLogic<Unit>() {
|
||||
|
||||
companion object {
|
||||
class QUERYING(val name: String) : ProgressTracker.Step("Querying oracle for $name interest rate")
|
||||
@ -52,13 +52,13 @@ open class RatesFixProtocol(protected val tx: TransactionBuilder,
|
||||
@Suspendable
|
||||
override fun call() {
|
||||
progressTracker.currentStep = progressTracker.steps[1]
|
||||
val fix = subProtocol(FixQueryProtocol(fixOf, oracle))
|
||||
val fix = subFlow(FixQueryFlow(fixOf, oracle))
|
||||
progressTracker.currentStep = WORKING
|
||||
checkFixIsNearExpected(fix)
|
||||
tx.addCommand(fix, oracle.owningKey)
|
||||
beforeSigning(fix)
|
||||
progressTracker.currentStep = SIGNING
|
||||
val signature = subProtocol(FixSignProtocol(tx, oracle, filterFuns))
|
||||
val signature = subFlow(FixSignFlow(tx, oracle, filterFuns))
|
||||
tx.addSignatureUnchecked(signature)
|
||||
}
|
||||
|
||||
@ -79,7 +79,7 @@ open class RatesFixProtocol(protected val tx: TransactionBuilder,
|
||||
}
|
||||
|
||||
|
||||
class FixQueryProtocol(val fixOf: FixOf, val oracle: Party) : ProtocolLogic<Fix>() {
|
||||
class FixQueryFlow(val fixOf: FixOf, val oracle: Party) : FlowLogic<Fix>() {
|
||||
@Suspendable
|
||||
override fun call(): Fix {
|
||||
val deadline = suggestInterestRateAnnouncementTimeWindow(fixOf.name, oracle.name, fixOf.forDay).end
|
||||
@ -95,7 +95,7 @@ open class RatesFixProtocol(protected val tx: TransactionBuilder,
|
||||
}
|
||||
}
|
||||
|
||||
class FixSignProtocol(val tx: TransactionBuilder, val oracle: Party, val filterFuns: FilterFuns) : ProtocolLogic<DigitalSignature.LegallyIdentifiable>() {
|
||||
class FixSignFlow(val tx: TransactionBuilder, val oracle: Party, val filterFuns: FilterFuns) : FlowLogic<DigitalSignature.LegallyIdentifiable>() {
|
||||
@Suspendable
|
||||
override fun call(): DigitalSignature.LegallyIdentifiable {
|
||||
val wtx = tx.toWireTransaction()
|
@ -1,24 +1,23 @@
|
||||
package net.corda.irs.protocols
|
||||
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.node.CordaPluginRegistry
|
||||
import net.corda.core.node.NodeInfo
|
||||
import net.corda.core.node.PluginServiceHub
|
||||
import net.corda.core.protocols.ProtocolLogic
|
||||
import net.corda.core.utilities.ProgressTracker
|
||||
import net.corda.node.utilities.TestClock
|
||||
import net.corda.node.services.api.ServiceHubInternal
|
||||
import net.corda.testing.node.MockNetworkMapCache
|
||||
import java.time.LocalDate
|
||||
|
||||
/**
|
||||
* This is a less temporary, demo-oriented way of initiating processing of temporal events.
|
||||
*/
|
||||
object UpdateBusinessDayProtocol {
|
||||
object UpdateBusinessDayFlow {
|
||||
|
||||
// This is not really a HandshakeMessage but needs to be so that the send uses the default session ID. This will
|
||||
// resolve itself when the protocol session stuff is done.
|
||||
// resolve itself when the flow session stuff is done.
|
||||
data class UpdateBusinessDayMessage(val date: LocalDate)
|
||||
|
||||
class Plugin: CordaPluginRegistry() {
|
||||
@ -27,11 +26,11 @@ object UpdateBusinessDayProtocol {
|
||||
|
||||
class Service(services: PluginServiceHub) {
|
||||
init {
|
||||
services.registerProtocolInitiator(Broadcast::class, ::UpdateBusinessDayHandler)
|
||||
services.registerFlowInitiator(Broadcast::class, ::UpdateBusinessDayHandler)
|
||||
}
|
||||
}
|
||||
|
||||
private class UpdateBusinessDayHandler(val otherParty: Party) : ProtocolLogic<Unit>() {
|
||||
private class UpdateBusinessDayHandler(val otherParty: Party) : FlowLogic<Unit>() {
|
||||
override fun call() {
|
||||
val message = receive<UpdateBusinessDayMessage>(otherParty).unwrap { it }
|
||||
(serviceHub.clock as TestClock).updateDate(message.date)
|
||||
@ -40,7 +39,7 @@ object UpdateBusinessDayProtocol {
|
||||
|
||||
|
||||
class Broadcast(val date: LocalDate,
|
||||
override val progressTracker: ProgressTracker = Broadcast.tracker()) : ProtocolLogic<Unit>() {
|
||||
override val progressTracker: ProgressTracker = Broadcast.tracker()) : FlowLogic<Unit>() {
|
||||
|
||||
companion object {
|
||||
object NOTIFYING : ProgressTracker.Step("Notifying peers")
|
@ -5,11 +5,10 @@ import net.corda.core.crypto.Party
|
||||
import net.corda.core.node.CordaPluginRegistry
|
||||
import net.corda.irs.api.InterestRateSwapAPI
|
||||
import net.corda.irs.contract.InterestRateSwap
|
||||
import net.corda.irs.protocols.AutoOfferProtocol
|
||||
import net.corda.irs.protocols.ExitServerProtocol
|
||||
import net.corda.irs.protocols.FixingProtocol
|
||||
import net.corda.irs.protocols.UpdateBusinessDayProtocol
|
||||
import net.corda.protocols.TwoPartyDealProtocol
|
||||
import net.corda.irs.flows.AutoOfferFlow
|
||||
import net.corda.irs.flows.ExitServerFlow
|
||||
import net.corda.irs.flows.FixingFlow
|
||||
import net.corda.irs.flows.UpdateBusinessDayFlow
|
||||
import java.time.Duration
|
||||
|
||||
class IRSPlugin : CordaPluginRegistry() {
|
||||
@ -17,11 +16,11 @@ class IRSPlugin : CordaPluginRegistry() {
|
||||
override val staticServeDirs: Map<String, String> = mapOf(
|
||||
"irsdemo" to javaClass.classLoader.getResource("irsweb").toExternalForm()
|
||||
)
|
||||
override val servicePlugins: List<Class<*>> = listOf(FixingProtocol.Service::class.java)
|
||||
override val requiredProtocols: Map<String, Set<String>> = mapOf(
|
||||
Pair(AutoOfferProtocol.Requester::class.java.name, setOf(InterestRateSwap.State::class.java.name)),
|
||||
Pair(UpdateBusinessDayProtocol.Broadcast::class.java.name, setOf(java.time.LocalDate::class.java.name)),
|
||||
Pair(ExitServerProtocol.Broadcast::class.java.name, setOf(kotlin.Int::class.java.name)),
|
||||
Pair(FixingProtocol.FixingRoleDecider::class.java.name, setOf(StateRef::class.java.name, Duration::class.java.name)),
|
||||
Pair(FixingProtocol.Floater::class.java.name, setOf(Party::class.java.name, FixingProtocol.FixingSession::class.java.name)))
|
||||
override val servicePlugins: List<Class<*>> = listOf(FixingFlow.Service::class.java)
|
||||
override val requiredFlows: Map<String, Set<String>> = mapOf(
|
||||
Pair(AutoOfferFlow.Requester::class.java.name, setOf(InterestRateSwap.State::class.java.name)),
|
||||
Pair(UpdateBusinessDayFlow.Broadcast::class.java.name, setOf(java.time.LocalDate::class.java.name)),
|
||||
Pair(ExitServerFlow.Broadcast::class.java.name, setOf(kotlin.Int::class.java.name)),
|
||||
Pair(FixingFlow.FixingRoleDecider::class.java.name, setOf(StateRef::class.java.name, Duration::class.java.name)),
|
||||
Pair(FixingFlow.Floater::class.java.name, setOf(Party::class.java.name, FixingFlow.FixingSession::class.java.name)))
|
||||
}
|
||||
|
@ -9,17 +9,17 @@ import net.corda.core.RunOnCallerThread
|
||||
import net.corda.core.contracts.StateAndRef
|
||||
import net.corda.core.contracts.UniqueIdentifier
|
||||
import net.corda.core.flatMap
|
||||
import net.corda.core.flows.FlowStateMachine
|
||||
import net.corda.core.map
|
||||
import net.corda.core.node.services.linearHeadsOfType
|
||||
import net.corda.core.protocols.ProtocolStateMachine
|
||||
import net.corda.core.success
|
||||
import net.corda.core.transactions.SignedTransaction
|
||||
import net.corda.flows.TwoPartyDealFlow.Acceptor
|
||||
import net.corda.flows.TwoPartyDealFlow.AutoOffer
|
||||
import net.corda.flows.TwoPartyDealFlow.Instigator
|
||||
import net.corda.irs.contract.InterestRateSwap
|
||||
import net.corda.node.utilities.databaseTransaction
|
||||
import net.corda.protocols.TwoPartyDealProtocol.Acceptor
|
||||
import net.corda.protocols.TwoPartyDealProtocol.AutoOffer
|
||||
import net.corda.protocols.TwoPartyDealProtocol.Instigator
|
||||
import net.corda.testing.initiateSingleShotProtocol
|
||||
import net.corda.testing.initiateSingleShotFlow
|
||||
import net.corda.testing.node.InMemoryMessagingNetwork
|
||||
import net.corda.testing.node.MockIdentityService
|
||||
import java.time.LocalDate
|
||||
@ -118,15 +118,15 @@ class IRSSimulation(networkSendManuallyPumped: Boolean, runAsync: Boolean, laten
|
||||
irs.fixedLeg.fixedRatePayer = node1.info.legalIdentity
|
||||
irs.floatingLeg.floatingRatePayer = node2.info.legalIdentity
|
||||
|
||||
val acceptorTx = node2.initiateSingleShotProtocol(Instigator::class) { Acceptor(it) }.flatMap {
|
||||
(it.psm as ProtocolStateMachine<SignedTransaction>).resultFuture
|
||||
val acceptorTx = node2.initiateSingleShotFlow(Instigator::class) { Acceptor(it) }.flatMap {
|
||||
(it.fsm 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.keyPair!!)
|
||||
val instigatorTx: ListenableFuture<SignedTransaction> = node1.services.startProtocol(instigator).resultFuture
|
||||
val instigatorTx: ListenableFuture<SignedTransaction> = node1.services.startFlow(instigator).resultFuture
|
||||
|
||||
return Futures.allAsList(instigatorTx, acceptorTx).flatMap { instigatorTx }
|
||||
}
|
||||
|
@ -4,12 +4,12 @@ import com.google.common.util.concurrent.Futures
|
||||
import com.google.common.util.concurrent.ListenableFuture
|
||||
import net.corda.core.crypto.generateKeyPair
|
||||
import net.corda.core.flatMap
|
||||
import net.corda.core.flows.FlowLogic
|
||||
import net.corda.core.messaging.SingleMessageRecipient
|
||||
import net.corda.core.node.CityDatabase
|
||||
import net.corda.core.node.PhysicalLocation
|
||||
import net.corda.core.node.services.ServiceInfo
|
||||
import net.corda.core.node.services.containsType
|
||||
import net.corda.core.protocols.ProtocolLogic
|
||||
import net.corda.core.then
|
||||
import net.corda.core.utilities.ProgressTracker
|
||||
import net.corda.irs.api.NodeInterestRates
|
||||
@ -31,7 +31,7 @@ import java.util.*
|
||||
/**
|
||||
* Base class for network simulations that are based on the unit test / mock environment.
|
||||
*
|
||||
* Sets up some nodes that can run protocols between each other, and exposes their progress trackers. Provides banks
|
||||
* Sets up some nodes that can run flows between each other, and exposes their progress trackers. Provides banks
|
||||
* in a few cities around the world.
|
||||
*/
|
||||
abstract class Simulation(val networkSendManuallyPumped: Boolean,
|
||||
@ -202,9 +202,9 @@ abstract class Simulation(val networkSendManuallyPumped: Boolean,
|
||||
val clocks = (serviceProviders + regulators + banks).map { it.services.clock as TestClock }
|
||||
|
||||
// These are used from the network visualiser tool.
|
||||
private val _allProtocolSteps = PublishSubject.create<Pair<SimulatedNode, ProgressTracker.Change>>()
|
||||
private val _allFlowSteps = PublishSubject.create<Pair<SimulatedNode, ProgressTracker.Change>>()
|
||||
private val _doneSteps = PublishSubject.create<Collection<SimulatedNode>>()
|
||||
@Suppress("unused") val allProtocolSteps: Observable<Pair<SimulatedNode, ProgressTracker.Change>> = _allProtocolSteps
|
||||
@Suppress("unused") val allFlowSteps: Observable<Pair<SimulatedNode, ProgressTracker.Change>> = _allFlowSteps
|
||||
@Suppress("unused") val doneSteps: Observable<Collection<SimulatedNode>> = _doneSteps
|
||||
|
||||
private var pumpCursor = 0
|
||||
@ -268,16 +268,16 @@ abstract class Simulation(val networkSendManuallyPumped: Boolean,
|
||||
protected fun showProgressFor(nodes: List<SimulatedNode>) {
|
||||
nodes.forEach { node ->
|
||||
node.smm.changes.filter { it.addOrRemove == AddOrRemove.ADD }.subscribe {
|
||||
linkProtocolProgress(node, it.logic)
|
||||
linkFlowProgress(node, it.logic)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun linkProtocolProgress(node: SimulatedNode, protocol: ProtocolLogic<*>) {
|
||||
val pt = protocol.progressTracker ?: return
|
||||
private fun linkFlowProgress(node: SimulatedNode, flow: FlowLogic<*>) {
|
||||
val pt = flow.progressTracker ?: return
|
||||
pt.changes.subscribe { change: ProgressTracker.Change ->
|
||||
// Runs on node thread.
|
||||
_allProtocolSteps.onNext(Pair(node, change))
|
||||
_allFlowSteps.onNext(Pair(node, change))
|
||||
}
|
||||
}
|
||||
|
||||
@ -289,10 +289,10 @@ abstract class Simulation(val networkSendManuallyPumped: Boolean,
|
||||
}
|
||||
}
|
||||
|
||||
private fun linkConsensus(nodes: Collection<SimulatedNode>, protocol: ProtocolLogic<*>) {
|
||||
protocol.progressTracker?.changes?.subscribe { change: ProgressTracker.Change ->
|
||||
private fun linkConsensus(nodes: Collection<SimulatedNode>, flow: FlowLogic<*>) {
|
||||
flow.progressTracker?.changes?.subscribe { change: ProgressTracker.Change ->
|
||||
// Runs on node thread.
|
||||
if (protocol.progressTracker!!.currentStep == ProgressTracker.DONE) {
|
||||
if (flow.progressTracker!!.currentStep == ProgressTracker.DONE) {
|
||||
_doneSteps.onNext(nodes)
|
||||
}
|
||||
}
|
||||
|
@ -10,13 +10,13 @@ import net.corda.core.contracts.OwnableState
|
||||
import net.corda.core.contracts.`issued by`
|
||||
import net.corda.core.days
|
||||
import net.corda.core.flatMap
|
||||
import net.corda.core.flows.FlowStateMachine
|
||||
import net.corda.core.node.recordTransactions
|
||||
import net.corda.core.protocols.ProtocolStateMachine
|
||||
import net.corda.core.seconds
|
||||
import net.corda.core.transactions.SignedTransaction
|
||||
import net.corda.protocols.TwoPartyTradeProtocol.Buyer
|
||||
import net.corda.protocols.TwoPartyTradeProtocol.Seller
|
||||
import net.corda.testing.initiateSingleShotProtocol
|
||||
import net.corda.flows.TwoPartyTradeFlow.Buyer
|
||||
import net.corda.flows.TwoPartyTradeFlow.Seller
|
||||
import net.corda.testing.initiateSingleShotFlow
|
||||
import net.corda.testing.node.InMemoryMessagingNetwork
|
||||
import java.time.Instant
|
||||
|
||||
@ -50,12 +50,12 @@ class TradeSimulation(runAsync: Boolean, latencyInjector: InMemoryMessagingNetwo
|
||||
|
||||
val amount = 1000.DOLLARS
|
||||
|
||||
val buyerFuture = buyer.initiateSingleShotProtocol(Seller::class) {
|
||||
val buyerFuture = buyer.initiateSingleShotFlow(Seller::class) {
|
||||
Buyer(it, notary.info.notaryIdentity, amount, CommercialPaper.State::class.java)
|
||||
}.flatMap { (it.psm as ProtocolStateMachine<SignedTransaction>).resultFuture }
|
||||
}.flatMap { (it.fsm as FlowStateMachine<SignedTransaction>).resultFuture }
|
||||
|
||||
val sellerKey = seller.services.legalIdentityKey
|
||||
val sellerProtocol = Seller(
|
||||
val sellerFlow = Seller(
|
||||
buyer.info.legalIdentity,
|
||||
notary.info,
|
||||
issuance.tx.outRef<OwnableState>(0),
|
||||
@ -65,7 +65,7 @@ class TradeSimulation(runAsync: Boolean, latencyInjector: InMemoryMessagingNetwo
|
||||
showConsensusFor(listOf(buyer, seller, notary))
|
||||
showProgressFor(listOf(buyer, seller))
|
||||
|
||||
val sellerFuture = seller.services.startProtocol(sellerProtocol).resultFuture
|
||||
val sellerFuture = seller.services.startFlow(sellerFlow).resultFuture
|
||||
|
||||
return Futures.successfulAsList(buyerFuture, sellerFuture)
|
||||
}
|
||||
|
@ -1,6 +1,6 @@
|
||||
# Register a ServiceLoader service extending from net.corda.node.CordaPluginRegistry
|
||||
net.corda.irs.plugin.IRSPlugin
|
||||
net.corda.irs.api.NodeInterestRates$Plugin
|
||||
net.corda.irs.protocols.AutoOfferProtocol$Plugin
|
||||
net.corda.irs.protocols.ExitServerProtocol$Plugin
|
||||
net.corda.irs.protocols.UpdateBusinessDayProtocol$Plugin
|
||||
net.corda.irs.flows.AutoOfferFlow$Plugin
|
||||
net.corda.irs.flows.ExitServerFlow$Plugin
|
||||
net.corda.irs.flows.UpdateBusinessDayFlow$Plugin
|
||||
|
@ -15,16 +15,19 @@ import net.corda.core.transactions.FilteredTransaction
|
||||
import net.corda.core.utilities.DUMMY_NOTARY
|
||||
import net.corda.core.utilities.LogHelper
|
||||
import net.corda.irs.api.NodeInterestRates
|
||||
import net.corda.irs.protocols.RatesFixProtocol
|
||||
import net.corda.testing.node.MockNetwork
|
||||
import net.corda.irs.flows.RatesFixFlow
|
||||
import net.corda.node.utilities.configureDatabase
|
||||
import net.corda.node.utilities.databaseTransaction
|
||||
import net.corda.testing.ALICE_PUBKEY
|
||||
import net.corda.testing.MEGA_CORP
|
||||
import net.corda.testing.MEGA_CORP_KEY
|
||||
import net.corda.testing.node.MockNetwork
|
||||
import net.corda.testing.node.makeTestDataSourceProperties
|
||||
import org.jetbrains.exposed.sql.Database
|
||||
import org.junit.*
|
||||
import org.junit.After
|
||||
import org.junit.Assert
|
||||
import org.junit.Before
|
||||
import org.junit.Test
|
||||
import java.io.Closeable
|
||||
import java.time.Clock
|
||||
import kotlin.test.assertEquals
|
||||
@ -198,10 +201,10 @@ class NodeInterestRatesTest {
|
||||
val oracle = n2.info.serviceIdentities(NodeInterestRates.type).first()
|
||||
fun filterCommands(c: Command) = oracle.owningKey in c.signers && c.value is Fix
|
||||
val filterFuns = FilterFuns(filterCommands = ::filterCommands)
|
||||
val protocol = RatesFixProtocol(tx, filterFuns, oracle, fixOf, "0.675".bd, "0.1".bd)
|
||||
val flow = RatesFixFlow(tx, filterFuns, oracle, fixOf, "0.675".bd, "0.1".bd)
|
||||
LogHelper.setLevel("rates")
|
||||
net.runNetwork()
|
||||
val future = n1.services.startProtocol(protocol).resultFuture
|
||||
val future = n1.services.startFlow(flow).resultFuture
|
||||
net.runNetwork()
|
||||
future.get()
|
||||
// We should now have a valid signature over our tx from the oracle.
|
||||
|
@ -1,16 +1,5 @@
|
||||
package net.corda.netmap
|
||||
|
||||
import net.corda.netmap.VisualiserViewModel.Style
|
||||
import net.corda.core.messaging.SingleMessageRecipient
|
||||
import net.corda.core.serialization.deserialize
|
||||
import net.corda.core.then
|
||||
import net.corda.core.utilities.ProgressTracker
|
||||
import net.corda.node.services.network.NetworkMapService
|
||||
import net.corda.node.services.statemachine.StateMachineManager
|
||||
import net.corda.simulation.IRSSimulation
|
||||
import net.corda.simulation.Simulation
|
||||
import net.corda.testing.node.InMemoryMessagingNetwork
|
||||
import net.corda.testing.node.MockNetwork
|
||||
import javafx.animation.*
|
||||
import javafx.application.Application
|
||||
import javafx.application.Platform
|
||||
@ -22,6 +11,17 @@ import javafx.scene.input.KeyCodeCombination
|
||||
import javafx.scene.layout.VBox
|
||||
import javafx.stage.Stage
|
||||
import javafx.util.Duration
|
||||
import net.corda.core.messaging.SingleMessageRecipient
|
||||
import net.corda.core.serialization.deserialize
|
||||
import net.corda.core.then
|
||||
import net.corda.core.utilities.ProgressTracker
|
||||
import net.corda.netmap.VisualiserViewModel.Style
|
||||
import net.corda.node.services.network.NetworkMapService
|
||||
import net.corda.node.services.statemachine.StateMachineManager
|
||||
import net.corda.simulation.IRSSimulation
|
||||
import net.corda.simulation.Simulation
|
||||
import net.corda.testing.node.InMemoryMessagingNetwork
|
||||
import net.corda.testing.node.MockNetwork
|
||||
import rx.Scheduler
|
||||
import rx.schedulers.Schedulers
|
||||
import java.time.format.DateTimeFormatter
|
||||
@ -74,8 +74,8 @@ class NetworkMapVisualiser : Application() {
|
||||
viewModel.displayStyle = if ("--circle" in parameters.raw) { Style.CIRCLE } else { viewModel.displayStyle }
|
||||
|
||||
val simulation = viewModel.simulation
|
||||
// Update the white-backgrounded label indicating what protocol step it's up to.
|
||||
simulation.allProtocolSteps.observeOn(uiThread).subscribe { step: Pair<Simulation.SimulatedNode, ProgressTracker.Change> ->
|
||||
// Update the white-backgrounded label indicating what flow step it's up to.
|
||||
simulation.allFlowSteps.observeOn(uiThread).subscribe { step: Pair<Simulation.SimulatedNode, ProgressTracker.Change> ->
|
||||
val (node, change) = step
|
||||
val label = viewModel.nodesToWidgets[node]!!.statusLabel
|
||||
if (change is ProgressTracker.Change.Position) {
|
||||
@ -213,23 +213,23 @@ class NetworkMapVisualiser : Application() {
|
||||
}
|
||||
|
||||
private fun bindSidebar() {
|
||||
viewModel.simulation.allProtocolSteps.observeOn(uiThread).subscribe { step: Pair<Simulation.SimulatedNode, ProgressTracker.Change> ->
|
||||
viewModel.simulation.allFlowSteps.observeOn(uiThread).subscribe { step: Pair<Simulation.SimulatedNode, ProgressTracker.Change> ->
|
||||
val (node, change) = step
|
||||
|
||||
if (change is ProgressTracker.Change.Position) {
|
||||
val tracker = change.tracker.topLevelTracker
|
||||
if (change.newStep == ProgressTracker.DONE) {
|
||||
if (change.tracker == tracker) {
|
||||
// Protocol done; schedule it for removal in a few seconds. We batch them up to make nicer
|
||||
// Flow done; schedule it for removal in a few seconds. We batch them up to make nicer
|
||||
// animations.
|
||||
updateProgressTrackerWidget(change)
|
||||
println("Protocol done for ${node.info.legalIdentity.name}")
|
||||
println("Flow done for ${node.info.legalIdentity.name}")
|
||||
viewModel.doneTrackers += tracker
|
||||
} else {
|
||||
// Subprotocol is done; ignore it.
|
||||
// Subflow is done; ignore it.
|
||||
}
|
||||
} else if (!viewModel.trackerBoxes.containsKey(tracker)) {
|
||||
// New protocol started up; add.
|
||||
// New flow started up; add.
|
||||
val extraLabel = viewModel.simulation.extraNodeLabels[node]
|
||||
val label = if (extraLabel != null) "${node.info.legalIdentity.name}: $extraLabel" else node.info.legalIdentity.name
|
||||
val widget = view.buildProgressTrackerWidget(label, tracker.topLevelTracker)
|
||||
@ -343,7 +343,7 @@ class NetworkMapVisualiser : Application() {
|
||||
// Loopback messages are boring.
|
||||
if (transfer.sender.myAddress == transfer.recipients) return false
|
||||
// Network map push acknowledgements are boring.
|
||||
if (NetworkMapService.PUSH_ACK_PROTOCOL_TOPIC in transfer.message.topicSession.topic) return false
|
||||
if (NetworkMapService.PUSH_ACK_FLOW_TOPIC in transfer.message.topicSession.topic) return false
|
||||
val message = transfer.message.data.deserialize<Any>()
|
||||
val messageClassType = message.javaClass.name
|
||||
when (messageClassType) {
|
||||
|
@ -17,9 +17,7 @@ import com.opengamma.strata.pricer.rate.ImmutableRatesProvider
|
||||
import com.opengamma.strata.pricer.swap.DiscountingSwapProductPricer
|
||||
import com.opengamma.strata.product.swap.ResolvedSwapTrade
|
||||
import net.corda.core.utilities.loggerFor
|
||||
import net.corda.vega.protocols.toCordaCompatible
|
||||
import org.slf4j.Logger
|
||||
import org.slf4j.LoggerFactory
|
||||
import net.corda.vega.flows.toCordaCompatible
|
||||
import java.time.LocalDate
|
||||
|
||||
/**
|
||||
|
@ -9,12 +9,12 @@ import net.corda.core.node.services.dealsWith
|
||||
import net.corda.vega.analytics.InitialMarginTriple
|
||||
import net.corda.vega.contracts.IRSState
|
||||
import net.corda.vega.contracts.PortfolioState
|
||||
import net.corda.vega.flows.IRSTradeFlow
|
||||
import net.corda.vega.flows.SimmFlow
|
||||
import net.corda.vega.flows.SimmRevaluation
|
||||
import net.corda.vega.portfolio.Portfolio
|
||||
import net.corda.vega.portfolio.toPortfolio
|
||||
import net.corda.vega.portfolio.toStateAndRef
|
||||
import net.corda.vega.protocols.IRSTradeProtocol
|
||||
import net.corda.vega.protocols.SimmProtocol
|
||||
import net.corda.vega.protocols.SimmRevaluation
|
||||
import java.time.LocalDate
|
||||
import java.time.LocalDateTime
|
||||
import javax.ws.rs.*
|
||||
@ -153,7 +153,7 @@ class PortfolioApi(val services: ServiceHub) {
|
||||
return withParty(partyName) {
|
||||
val buyer = if (swap.buySell.isBuy) ownParty else it
|
||||
val seller = if (swap.buySell.isSell) ownParty else it
|
||||
services.invokeProtocolAsync(IRSTradeProtocol.Requester::class.java, swap.toData(buyer, seller), it).resultFuture.get()
|
||||
services.invokeFlowAsync(IRSTradeFlow.Requester::class.java, swap.toData(buyer, seller), it).resultFuture.get()
|
||||
Response.accepted().entity("{}").build()
|
||||
}
|
||||
}
|
||||
@ -268,9 +268,9 @@ class PortfolioApi(val services: ServiceHub) {
|
||||
return withParty(partyName) { otherParty ->
|
||||
val existingSwap = getPortfolioWith(otherParty)
|
||||
if (existingSwap == null) {
|
||||
services.invokeProtocolAsync(SimmProtocol.Requester::class.java, otherParty, params.valuationDate).resultFuture.get()
|
||||
services.invokeFlowAsync(SimmFlow.Requester::class.java, otherParty, params.valuationDate).resultFuture.get()
|
||||
} else {
|
||||
services.invokeProtocolAsync(SimmRevaluation.Initiator::class.java, getPortfolioStateAndRefWith(otherParty).ref, params.valuationDate).resultFuture.get()
|
||||
services.invokeFlowAsync(SimmRevaluation.Initiator::class.java, getPortfolioStateAndRefWith(otherParty).ref, params.valuationDate).resultFuture.get()
|
||||
}
|
||||
|
||||
withPortfolio(otherParty) { portfolioState ->
|
||||
|
@ -3,9 +3,9 @@ package net.corda.vega.contracts
|
||||
import net.corda.core.contracts.*
|
||||
import net.corda.core.crypto.CompositeKey
|
||||
import net.corda.core.crypto.Party
|
||||
import net.corda.core.protocols.ProtocolLogicRefFactory
|
||||
import net.corda.core.flows.FlowLogicRefFactory
|
||||
import net.corda.core.transactions.TransactionBuilder
|
||||
import net.corda.vega.protocols.SimmRevaluation
|
||||
import net.corda.vega.flows.SimmRevaluation
|
||||
import java.security.PublicKey
|
||||
import java.time.LocalDate
|
||||
import java.time.ZoneOffset
|
||||
@ -31,9 +31,9 @@ data class PortfolioState(val portfolio: List<StateRef>,
|
||||
override val participants: List<CompositeKey>
|
||||
get() = parties.map { it.owningKey }
|
||||
|
||||
override fun nextScheduledActivity(thisStateRef: StateRef, protocolLogicRefFactory: ProtocolLogicRefFactory): ScheduledActivity {
|
||||
val protocol = protocolLogicRefFactory.create(SimmRevaluation.Initiator::class.java, thisStateRef, LocalDate.now())
|
||||
return ScheduledActivity(protocol, LocalDate.now().plus(1, ChronoUnit.DAYS).atStartOfDay().toInstant(ZoneOffset.UTC))
|
||||
override fun nextScheduledActivity(thisStateRef: StateRef, flowLogicRefFactory: FlowLogicRefFactory): ScheduledActivity {
|
||||
val flow = flowLogicRefFactory.create(SimmRevaluation.Initiator::class.java, thisStateRef, LocalDate.now())
|
||||
return ScheduledActivity(flow, LocalDate.now().plus(1, ChronoUnit.DAYS).atStartOfDay().toInstant(ZoneOffset.UTC))
|
||||
}
|
||||
|
||||
override fun isRelevant(ourKeys: Set<PublicKey>): Boolean {
|
||||
|
@ -6,7 +6,7 @@ import net.corda.core.crypto.Party
|
||||
import net.corda.core.transactions.TransactionBuilder
|
||||
|
||||
/**
|
||||
* Deals implementing this interface will be usable with the StateRevisionProtocol that allows arbitrary updates
|
||||
* Deals implementing this interface will be usable with the StateRevisionFlow that allows arbitrary updates
|
||||
* to a state. This is not really an "amendable" state (recall the Corda model - all states are immutable in the
|
||||
* functional sense) however it can be amended and then re-written as another state into the ledger.
|
||||
*/
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user