mirror of
https://github.com/corda/corda.git
synced 2025-05-02 16:53:22 +00:00
Introducing StartableByRPC and SchedulableFlow annotations, needed by flows started via RPC and schedulable flows respectively.
CordaPluginRegistry.requiredFlows is no longer needed as a result.
This commit is contained in:
parent
b155764023
commit
48f58b6dbc
@ -9,11 +9,12 @@ import net.corda.core.serialization.CordaSerializable
|
|||||||
* the flow to run at the scheduled time.
|
* the flow to run at the scheduled time.
|
||||||
*/
|
*/
|
||||||
interface FlowLogicRefFactory {
|
interface FlowLogicRefFactory {
|
||||||
fun create(type: Class<out FlowLogic<*>>, vararg args: Any?): FlowLogicRef
|
fun create(flowClass: Class<out FlowLogic<*>>, vararg args: Any?): FlowLogicRef
|
||||||
}
|
}
|
||||||
|
|
||||||
@CordaSerializable
|
@CordaSerializable
|
||||||
class IllegalFlowLogicException(type: Class<*>, msg: String) : IllegalArgumentException("${FlowLogicRef::class.java.simpleName} cannot be constructed for ${FlowLogic::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 handle interface representing a [FlowLogic] instance which would be possible to safely pass out of the contract sandbox.
|
* A handle interface representing a [FlowLogic] instance which would be possible to safely pass out of the contract sandbox.
|
||||||
|
14
core/src/main/kotlin/net/corda/core/flows/SchedulableFlow.kt
Normal file
14
core/src/main/kotlin/net/corda/core/flows/SchedulableFlow.kt
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
package net.corda.core.flows
|
||||||
|
|
||||||
|
import java.lang.annotation.Inherited
|
||||||
|
import kotlin.annotation.AnnotationTarget.CLASS
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Any [FlowLogic] which is schedulable and is designed to be invoked by a [net.corda.core.contracts.SchedulableState]
|
||||||
|
* must have this annotation. If it's missing [FlowLogicRefFactory.create] will throw an exception when it comes time
|
||||||
|
* to schedule the next activity in [net.corda.core.contracts.SchedulableState.nextScheduledActivity].
|
||||||
|
*/
|
||||||
|
@Target(CLASS)
|
||||||
|
@Inherited
|
||||||
|
@MustBeDocumented
|
||||||
|
annotation class SchedulableFlow
|
15
core/src/main/kotlin/net/corda/core/flows/StartableByRPC.kt
Normal file
15
core/src/main/kotlin/net/corda/core/flows/StartableByRPC.kt
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
package net.corda.core.flows
|
||||||
|
|
||||||
|
import java.lang.annotation.Inherited
|
||||||
|
import kotlin.annotation.AnnotationTarget.CLASS
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Any [FlowLogic] which is to be started by the RPC interface ([net.corda.core.messaging.CordaRPCOps.startFlowDynamic]
|
||||||
|
* and [net.corda.core.messaging.CordaRPCOps.startTrackedFlowDynamic]) must have this annotation. If it's missing the
|
||||||
|
* flow will not be allowed to start and an exception will be thrown.
|
||||||
|
*/
|
||||||
|
@Target(CLASS)
|
||||||
|
@Inherited
|
||||||
|
@MustBeDocumented
|
||||||
|
// TODO Consider a different name, something along the lines of SchedulableFlow
|
||||||
|
annotation class StartableByRPC
|
@ -148,14 +148,14 @@ interface CordaRPCOps : RPCOps {
|
|||||||
fun networkMapUpdates(): Pair<List<NodeInfo>, Observable<NetworkMapCache.MapChange>>
|
fun networkMapUpdates(): Pair<List<NodeInfo>, Observable<NetworkMapCache.MapChange>>
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Start the given flow with the given arguments.
|
* Start the given flow with the given arguments. [logicType] must be annotated with [net.corda.core.flows.StartableByRPC].
|
||||||
*/
|
*/
|
||||||
@RPCReturnsObservables
|
@RPCReturnsObservables
|
||||||
fun <T : Any> startFlowDynamic(logicType: Class<out FlowLogic<T>>, vararg args: Any?): FlowHandle<T>
|
fun <T : Any> startFlowDynamic(logicType: Class<out FlowLogic<T>>, vararg args: Any?): FlowHandle<T>
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Start the given flow with the given arguments, returning an [Observable] with a single observation of the
|
* Start the given flow with the given arguments, returning an [Observable] with a single observation of the
|
||||||
* result of running the flow.
|
* result of running the flow. [logicType] must be annotated with [net.corda.core.flows.StartableByRPC].
|
||||||
*/
|
*/
|
||||||
@RPCReturnsObservables
|
@RPCReturnsObservables
|
||||||
fun <T : Any> startTrackedFlowDynamic(logicType: Class<out FlowLogic<T>>, vararg args: Any?): FlowProgressHandle<T>
|
fun <T : Any> startTrackedFlowDynamic(logicType: Class<out FlowLogic<T>>, vararg args: Any?): FlowProgressHandle<T>
|
||||||
|
@ -8,28 +8,24 @@ import java.util.function.Function
|
|||||||
* Implement this interface on a class advertised in a META-INF/services/net.corda.core.node.CordaPluginRegistry file
|
* Implement this interface on a class advertised in a META-INF/services/net.corda.core.node.CordaPluginRegistry file
|
||||||
* to extend a Corda node with additional application services.
|
* to extend a Corda node with additional application services.
|
||||||
*/
|
*/
|
||||||
abstract class CordaPluginRegistry(
|
abstract class CordaPluginRegistry {
|
||||||
/**
|
/**
|
||||||
* List of lambdas returning JAX-RS objects. They may only depend on the RPC interface, as the webserver should
|
* List of lambdas returning JAX-RS objects. They may only depend on the RPC interface, as the webserver should
|
||||||
* potentially be able to live in a process separate from the node itself.
|
* potentially be able to live in a process separate from the node itself.
|
||||||
*/
|
*/
|
||||||
open val webApis: List<Function<CordaRPCOps, out Any>> = emptyList(),
|
open val webApis: List<Function<CordaRPCOps, out Any>> get() = emptyList()
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Map of static serving endpoints to the matching resource directory. All endpoints will be prefixed with "/web" and postfixed with "\*.
|
* Map of static serving endpoints to the matching resource directory. All endpoints will be prefixed with "/web" and postfixed with "\*.
|
||||||
* Resource directories can be either on disk directories (especially when debugging) in the form "a/b/c". Serving from a JAR can
|
* Resource directories can be either on disk directories (especially when debugging) in the form "a/b/c". Serving from a JAR can
|
||||||
* be specified with: javaClass.getResource("<folder-in-jar>").toExternalForm()
|
* be specified with: javaClass.getResource("<folder-in-jar>").toExternalForm()
|
||||||
*/
|
*/
|
||||||
open val staticServeDirs: Map<String, String> = emptyMap(),
|
open val staticServeDirs: Map<String, String> get() = emptyMap()
|
||||||
|
|
||||||
/**
|
@Suppress("unused")
|
||||||
* A Map with an entry for each consumed Flow used by the webAPIs.
|
@Deprecated("This is no longer needed. Instead annotate any flows that need to be invoked via RPC with " +
|
||||||
* The key of each map entry should contain the FlowLogic<T> class name.
|
"@StartableByRPC and any scheduled flows with @SchedulableFlow", level = DeprecationLevel.ERROR)
|
||||||
* The associated map values are the union of all concrete class names passed to the Flow constructor.
|
open val requiredFlows: Map<String, Set<String>> get() = emptyMap()
|
||||||
* Standard java.lang.* and kotlin.* types do not need to be included explicitly.
|
|
||||||
* This is used to extend the white listed Flows that can be initiated from the ServiceHub invokeFlowAsync method.
|
|
||||||
*/
|
|
||||||
open val requiredFlows: Map<String, Set<String>> = emptyMap(),
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* List of lambdas constructing additional long lived services to be hosted within the node.
|
* List of lambdas constructing additional long lived services to be hosted within the node.
|
||||||
@ -37,13 +33,13 @@ abstract class CordaPluginRegistry(
|
|||||||
* The [PluginServiceHub] will be fully constructed before the plugin service is created and will
|
* The [PluginServiceHub] will be fully constructed before the plugin service is created and will
|
||||||
* allow access to the Flow factory and Flow initiation entry points there.
|
* allow access to the Flow factory and Flow initiation entry points there.
|
||||||
*/
|
*/
|
||||||
open val servicePlugins: List<Function<PluginServiceHub, out Any>> = emptyList()
|
open val servicePlugins: List<Function<PluginServiceHub, out Any>> get() = emptyList()
|
||||||
) {
|
|
||||||
/**
|
/**
|
||||||
* Optionally whitelist types for use in object serialization, as we lock down the types that can be serialized.
|
* Optionally whitelist types for use in object serialization, as we lock down the types that can be serialized.
|
||||||
*
|
*
|
||||||
* For example, if you add a new [ContractState] it needs to be whitelisted. You can do that either by
|
* For example, if you add a new [net.corda.core.contracts.ContractState] it needs to be whitelisted. You can do that
|
||||||
* adding the @CordaSerializable annotation or via this method.
|
* either by adding the [net.corda.core.serialization.CordaSerializable] annotation or via this method.
|
||||||
**
|
**
|
||||||
* @return true if you register types, otherwise you will be filtered out of the list of plugins considered in future.
|
* @return true if you register types, otherwise you will be filtered out of the list of plugins considered in future.
|
||||||
*/
|
*/
|
||||||
|
@ -2,6 +2,7 @@ package net.corda.flows
|
|||||||
|
|
||||||
import net.corda.core.contracts.*
|
import net.corda.core.contracts.*
|
||||||
import net.corda.core.flows.InitiatingFlow
|
import net.corda.core.flows.InitiatingFlow
|
||||||
|
import net.corda.core.flows.StartableByRPC
|
||||||
import net.corda.core.transactions.SignedTransaction
|
import net.corda.core.transactions.SignedTransaction
|
||||||
import net.corda.core.transactions.TransactionBuilder
|
import net.corda.core.transactions.TransactionBuilder
|
||||||
import java.security.PublicKey
|
import java.security.PublicKey
|
||||||
@ -15,6 +16,7 @@ import java.security.PublicKey
|
|||||||
* use the new updated state for future transactions.
|
* use the new updated state for future transactions.
|
||||||
*/
|
*/
|
||||||
@InitiatingFlow
|
@InitiatingFlow
|
||||||
|
@StartableByRPC
|
||||||
class ContractUpgradeFlow<OldState : ContractState, out NewState : ContractState>(
|
class ContractUpgradeFlow<OldState : ContractState, out NewState : ContractState>(
|
||||||
originalState: StateAndRef<OldState>,
|
originalState: StateAndRef<OldState>,
|
||||||
newContractClass: Class<out UpgradedContract<OldState, NewState>>
|
newContractClass: Class<out UpgradedContract<OldState, NewState>>
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
package net.corda.core.flows
|
package net.corda.core.flows
|
||||||
|
|
||||||
|
import co.paralleluniverse.fibers.Suspendable
|
||||||
import net.corda.contracts.asset.Cash
|
import net.corda.contracts.asset.Cash
|
||||||
import net.corda.core.contracts.*
|
import net.corda.core.contracts.*
|
||||||
import net.corda.core.crypto.SecureHash
|
import net.corda.core.crypto.SecureHash
|
||||||
@ -9,6 +10,7 @@ import net.corda.core.messaging.CordaRPCOps
|
|||||||
import net.corda.core.messaging.startFlow
|
import net.corda.core.messaging.startFlow
|
||||||
import net.corda.core.node.services.unconsumedStates
|
import net.corda.core.node.services.unconsumedStates
|
||||||
import net.corda.core.serialization.OpaqueBytes
|
import net.corda.core.serialization.OpaqueBytes
|
||||||
|
import net.corda.core.transactions.SignedTransaction
|
||||||
import net.corda.core.utilities.Emoji
|
import net.corda.core.utilities.Emoji
|
||||||
import net.corda.flows.CashIssueFlow
|
import net.corda.flows.CashIssueFlow
|
||||||
import net.corda.flows.ContractUpgradeFlow
|
import net.corda.flows.ContractUpgradeFlow
|
||||||
@ -27,7 +29,6 @@ import org.junit.Before
|
|||||||
import org.junit.Test
|
import org.junit.Test
|
||||||
import java.security.PublicKey
|
import java.security.PublicKey
|
||||||
import java.util.*
|
import java.util.*
|
||||||
import java.util.concurrent.ExecutionException
|
|
||||||
import kotlin.test.assertEquals
|
import kotlin.test.assertEquals
|
||||||
import kotlin.test.assertFailsWith
|
import kotlin.test.assertFailsWith
|
||||||
import kotlin.test.assertTrue
|
import kotlin.test.assertTrue
|
||||||
@ -69,10 +70,10 @@ class ContractUpgradeFlowTest {
|
|||||||
requireNotNull(atx)
|
requireNotNull(atx)
|
||||||
requireNotNull(btx)
|
requireNotNull(btx)
|
||||||
|
|
||||||
// The request is expected to be rejected because party B haven't authorise the upgrade yet.
|
// The request is expected to be rejected because party B hasn't authorised the upgrade yet.
|
||||||
val rejectedFuture = a.services.startFlow(ContractUpgradeFlow(atx!!.tx.outRef(0), DummyContractV2::class.java)).resultFuture
|
val rejectedFuture = a.services.startFlow(ContractUpgradeFlow(atx!!.tx.outRef(0), DummyContractV2::class.java)).resultFuture
|
||||||
mockNet.runNetwork()
|
mockNet.runNetwork()
|
||||||
assertFailsWith(ExecutionException::class) { rejectedFuture.get() }
|
assertFailsWith(FlowSessionException::class) { rejectedFuture.getOrThrow() }
|
||||||
|
|
||||||
// Party B authorise the contract state upgrade.
|
// Party B authorise the contract state upgrade.
|
||||||
b.services.vaultService.authoriseContractUpgrade(btx!!.tx.outRef<ContractState>(0), DummyContractV2::class.java)
|
b.services.vaultService.authoriseContractUpgrade(btx!!.tx.outRef<ContractState>(0), DummyContractV2::class.java)
|
||||||
@ -81,7 +82,7 @@ class ContractUpgradeFlowTest {
|
|||||||
val resultFuture = a.services.startFlow(ContractUpgradeFlow(atx.tx.outRef(0), DummyContractV2::class.java)).resultFuture
|
val resultFuture = a.services.startFlow(ContractUpgradeFlow(atx.tx.outRef(0), DummyContractV2::class.java)).resultFuture
|
||||||
mockNet.runNetwork()
|
mockNet.runNetwork()
|
||||||
|
|
||||||
val result = resultFuture.get()
|
val result = resultFuture.getOrThrow()
|
||||||
|
|
||||||
fun check(node: MockNetwork.MockNode) {
|
fun check(node: MockNetwork.MockNode) {
|
||||||
val nodeStx = node.database.transaction {
|
val nodeStx = node.database.transaction {
|
||||||
@ -124,12 +125,12 @@ class ContractUpgradeFlowTest {
|
|||||||
.toSignedTransaction()
|
.toSignedTransaction()
|
||||||
|
|
||||||
val user = rpcTestUser.copy(permissions = setOf(
|
val user = rpcTestUser.copy(permissions = setOf(
|
||||||
startFlowPermission<FinalityFlow>(),
|
startFlowPermission<FinalityInvoker>(),
|
||||||
startFlowPermission<ContractUpgradeFlow<*, *>>()
|
startFlowPermission<ContractUpgradeFlow<*, *>>()
|
||||||
))
|
))
|
||||||
val rpcA = startProxy(a, user)
|
val rpcA = startProxy(a, user)
|
||||||
val rpcB = startProxy(b, user)
|
val rpcB = startProxy(b, user)
|
||||||
val handle = rpcA.startFlow(::FinalityFlow, stx, setOf(a.info.legalIdentity, b.info.legalIdentity))
|
val handle = rpcA.startFlow(::FinalityInvoker, stx, setOf(a.info.legalIdentity, b.info.legalIdentity))
|
||||||
mockNet.runNetwork()
|
mockNet.runNetwork()
|
||||||
handle.returnValue.getOrThrow()
|
handle.returnValue.getOrThrow()
|
||||||
|
|
||||||
@ -143,7 +144,7 @@ class ContractUpgradeFlowTest {
|
|||||||
DummyContractV2::class.java).returnValue
|
DummyContractV2::class.java).returnValue
|
||||||
|
|
||||||
mockNet.runNetwork()
|
mockNet.runNetwork()
|
||||||
assertFailsWith(ExecutionException::class) { rejectedFuture.get() }
|
assertFailsWith(FlowSessionException::class) { rejectedFuture.getOrThrow() }
|
||||||
|
|
||||||
// Party B authorise the contract state upgrade.
|
// Party B authorise the contract state upgrade.
|
||||||
rpcB.authoriseContractUpgrade(btx!!.tx.outRef<ContractState>(0), DummyContractV2::class.java)
|
rpcB.authoriseContractUpgrade(btx!!.tx.outRef<ContractState>(0), DummyContractV2::class.java)
|
||||||
@ -154,7 +155,7 @@ class ContractUpgradeFlowTest {
|
|||||||
DummyContractV2::class.java).returnValue
|
DummyContractV2::class.java).returnValue
|
||||||
|
|
||||||
mockNet.runNetwork()
|
mockNet.runNetwork()
|
||||||
val result = resultFuture.get()
|
val result = resultFuture.getOrThrow()
|
||||||
// Check results.
|
// Check results.
|
||||||
listOf(a, b).forEach {
|
listOf(a, b).forEach {
|
||||||
val signedTX = a.database.transaction { a.services.storageService.validatedTransactions.getTransaction(result.ref.txhash) }
|
val signedTX = a.database.transaction { a.services.storageService.validatedTransactions.getTransaction(result.ref.txhash) }
|
||||||
@ -210,4 +211,11 @@ class ContractUpgradeFlowTest {
|
|||||||
// Dummy Cash contract for testing.
|
// Dummy Cash contract for testing.
|
||||||
override val legalContractReference = SecureHash.sha256("")
|
override val legalContractReference = SecureHash.sha256("")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@StartableByRPC
|
||||||
|
class FinalityInvoker(val transaction: SignedTransaction,
|
||||||
|
val extraRecipients: Set<Party>) : FlowLogic<List<SignedTransaction>>() {
|
||||||
|
@Suspendable
|
||||||
|
override fun call(): List<SignedTransaction> = subFlow(FinalityFlow(transaction, extraRecipients))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -8,8 +8,11 @@ UNRELEASED
|
|||||||
----------
|
----------
|
||||||
|
|
||||||
* API changes:
|
* API changes:
|
||||||
* Initiating flows (i.e. those which initiate flows in a counterparty) are now required to be annotated with
|
* ``CordaPluginRegistry.requiredFlows`` is no longer needed. Instead annotate any flows you wish to start via RPC with
|
||||||
``InitiatingFlow``.
|
``@StartableByRPC`` and any scheduled flows with ``@SchedulableFlow``.
|
||||||
|
|
||||||
|
* Flows which initiate flows in their counterparties (an example of which is the ``NotaryFlow.Client``) are now
|
||||||
|
required to be annotated with ``@InitiatingFlow``.
|
||||||
|
|
||||||
* ``PluginServiceHub.registerFlowInitiator`` has been deprecated and replaced by ``registerServiceFlow`` with the
|
* ``PluginServiceHub.registerFlowInitiator`` has been deprecated and replaced by ``registerServiceFlow`` with the
|
||||||
marker Class restricted to ``FlowLogic``. In line with the introduction of ``InitiatingFlow``, it throws an
|
marker Class restricted to ``FlowLogic``. In line with the introduction of ``InitiatingFlow``, it throws an
|
||||||
|
@ -45,19 +45,7 @@ extensions to be created, or registered at startup. In particular:
|
|||||||
jars. These static serving directories will not be available if the
|
jars. These static serving directories will not be available if the
|
||||||
bundled web server is not started.
|
bundled web server is not started.
|
||||||
|
|
||||||
c. The ``requiredFlows`` property is used to declare new protocols in
|
c. The ``servicePlugins`` property returns a list of classes which will
|
||||||
the plugin jar. Specifically the property must return a map with a key
|
|
||||||
naming each exposed top level flow class and a value which is a set
|
|
||||||
naming every parameter class that will be passed to the flow's
|
|
||||||
constructor. Standard ``java.lang.*`` and ``kotlin.*`` types do not need
|
|
||||||
to be included, but all other parameter types, or concrete interface
|
|
||||||
implementations need declaring. Declaring a specific flow in this map
|
|
||||||
white lists it for activation by the ``FlowLogicRefFactory``. White
|
|
||||||
listing is not strictly required for ``subFlows`` used internally, but
|
|
||||||
is required for any top level flow, or a flow which is invoked through
|
|
||||||
the scheduler.
|
|
||||||
|
|
||||||
d. The ``servicePlugins`` property returns a list of classes which will
|
|
||||||
be instantiated once during the ``AbstractNode.start`` call. These
|
be instantiated once during the ``AbstractNode.start`` call. These
|
||||||
classes must provide a single argument constructor which will receive a
|
classes must provide a single argument constructor which will receive a
|
||||||
``PluginServiceHub`` reference. They must also extend the abstract class
|
``PluginServiceHub`` reference. They must also extend the abstract class
|
||||||
@ -90,7 +78,7 @@ extensions to be created, or registered at startup. In particular:
|
|||||||
functions inside the node, for instance to initiate workflows when
|
functions inside the node, for instance to initiate workflows when
|
||||||
certain conditions are met.
|
certain conditions are met.
|
||||||
|
|
||||||
e. The ``customizeSerialization`` function allows classes to be whitelisted
|
d. The ``customizeSerialization`` function allows classes to be whitelisted
|
||||||
for object serialisation, over and above those tagged with the ``@CordaSerializable``
|
for object serialisation, over and above those tagged with the ``@CordaSerializable``
|
||||||
annotation. In general the annotation should be preferred. For
|
annotation. In general the annotation should be preferred. For
|
||||||
instance new state types will need to be explicitly registered. This will be called at
|
instance new state types will need to be explicitly registered. This will be called at
|
||||||
|
@ -12,11 +12,10 @@ App plugins
|
|||||||
To create an app plugin you must extend from `CordaPluginRegistry`_. The JavaDoc contains
|
To create an app plugin you must extend from `CordaPluginRegistry`_. The JavaDoc contains
|
||||||
specific details of the implementation, but you can extend the server in the following ways:
|
specific details of the implementation, but you can extend the server in the following ways:
|
||||||
|
|
||||||
1. Required flows: Specify which flows will be whitelisted for use in your RPC calls.
|
1. Service plugins: Register your services (see below).
|
||||||
2. Service plugins: Register your services (see below).
|
2. Web APIs: You may register your own endpoints under /api/ of the bundled web server.
|
||||||
3. Web APIs: You may register your own endpoints under /api/ of the bundled web server.
|
3. Static web endpoints: You may register your own static serving directories for serving web content from the web server.
|
||||||
4. Static web endpoints: You may register your own static serving directories for serving web content from the web server.
|
4. Whitelisting your additional contract, state and other classes for object serialization. Any class that forms part
|
||||||
5. Whitelisting your additional contract, state and other classes for object serialization. Any class that forms part
|
|
||||||
of a persisted state, that is used in messaging between flows or in RPC needs to be whitelisted.
|
of a persisted state, that is used in messaging between flows or in RPC needs to be whitelisted.
|
||||||
|
|
||||||
Services
|
Services
|
||||||
|
@ -42,7 +42,8 @@ There are two main steps to implementing scheduled events:
|
|||||||
``nextScheduledActivity`` to be implemented which returns an optional ``ScheduledActivity`` instance.
|
``nextScheduledActivity`` to be implemented which returns an optional ``ScheduledActivity`` instance.
|
||||||
``ScheduledActivity`` captures what ``FlowLogic`` instance each node will run, to perform the activity, and when it
|
``ScheduledActivity`` captures what ``FlowLogic`` instance each node will run, to perform the activity, and when it
|
||||||
will run is described by a ``java.time.Instant``. Once your state implements this interface and is tracked by the
|
will run is described by a ``java.time.Instant``. Once your state implements this interface and is tracked by the
|
||||||
wallet, it can expect to be queried for the next activity when committed to the wallet.
|
wallet, it can expect to be queried for the next activity when committed to the wallet. The ``FlowLogic`` must be
|
||||||
|
annotated with ``@SchedulableFlow``.
|
||||||
* If nothing suitable exists, implement a ``FlowLogic`` to be executed by each node as the activity itself.
|
* If nothing suitable exists, implement a ``FlowLogic`` to be executed by each node as the activity itself.
|
||||||
The important thing to remember is that in the current implementation, each node that is party to the transaction
|
The important thing to remember is that in the current implementation, each node that is party to the transaction
|
||||||
will execute the same ``FlowLogic``, so it needs to establish roles in the business process based on the contract
|
will execute the same ``FlowLogic``, so it needs to establish roles in the business process based on the contract
|
||||||
@ -90,10 +91,7 @@ business process and to take on those roles. That ``FlowLogic`` will be handed
|
|||||||
rate swap ``State`` in question, as well as a tolerance ``Duration`` of how long to wait after the activity is triggered
|
rate swap ``State`` in question, as well as a tolerance ``Duration`` of how long to wait after the activity is triggered
|
||||||
for the interest rate before indicating an error.
|
for the interest rate before indicating an error.
|
||||||
|
|
||||||
.. note:: This is a way to create a reference to the FlowLogic class and its constructor parameters to
|
.. note:: This is a way to create a reference to the FlowLogic class and its constructor parameters to instantiate.
|
||||||
instantiate. The reference can be checked against a per-node whitelist of approved and allowable types as
|
|
||||||
part of our overall security sandboxing.
|
|
||||||
|
|
||||||
|
|
||||||
As previously mentioned, we currently need a small network handler to assist with session setup until the work to
|
As previously mentioned, we currently need a small network handler to assist with session setup until the work to
|
||||||
automate that is complete. See the interest rate swap specific implementation ``FixingSessionInitiationHandler`` which
|
automate that is complete. See the interest rate swap specific implementation ``FixingSessionInitiationHandler`` which
|
||||||
|
@ -206,9 +206,10 @@ how to register handlers with the messaging system (see ":doc:`messaging`") and
|
|||||||
when messages arrive. It provides the send/receive/sendAndReceive calls that let the code request network
|
when messages arrive. It provides the send/receive/sendAndReceive calls that let the code request network
|
||||||
interaction and it will save/restore serialised versions of the fiber at the right times.
|
interaction and it will save/restore serialised versions of the fiber at the right times.
|
||||||
|
|
||||||
Flows can be invoked in several ways. For instance, they can be triggered by scheduled events,
|
Flows can be invoked in several ways. For instance, they can be triggered by scheduled events (in which case they need to
|
||||||
see ":doc:`event-scheduling`" to learn more about this. Or they can be triggered directly via the Java-level node RPC
|
be annotated with ``@SchedulableFlow``), see ":doc:`event-scheduling`" to learn more about this. They can also be triggered
|
||||||
APIs from your app code.
|
directly via the node's RPC API from your app code (in which case they need to be annotated with `StartableByRPC`). It's
|
||||||
|
possible for a flow to be of both types.
|
||||||
|
|
||||||
You request a flow to be invoked by using the ``CordaRPCOps.startFlowDynamic`` method. This takes a
|
You request a flow to be invoked by using the ``CordaRPCOps.startFlowDynamic`` method. This takes a
|
||||||
Java reflection ``Class`` object that describes the flow class to use (in this case, either ``Buyer`` or ``Seller``).
|
Java reflection ``Class`` object that describes the flow class to use (in this case, either ``Buyer`` or ``Seller``).
|
||||||
@ -399,15 +400,35 @@ This code is longer but no more complicated. Here are some things to pay attenti
|
|||||||
As you can see, the flow logic is straightforward and does not contain any callbacks or network glue code, despite
|
As you can see, the flow logic is straightforward and does not contain any callbacks or network glue code, despite
|
||||||
the fact that it takes minimal resources and can survive node restarts.
|
the fact that it takes minimal resources and can survive node restarts.
|
||||||
|
|
||||||
Initiating communication
|
Flow sessions
|
||||||
------------------------
|
-------------
|
||||||
|
|
||||||
Now that we have both sides of the deal negotation implemented as flows we need a way to start things off. We do this by
|
Before going any further it will be useful to describe how flows communicate with each other. A node may have many flows
|
||||||
having one side initiate communication and the other respond to it and start their flow. Initiation is typically done using
|
running at the same time, and perhaps communicating with the same counterparty node but for different purposes. Therefore
|
||||||
RPC with the ``startFlowDynamic`` method. The initiating flow has be to annotated with ``InitiatingFlow``. In our example
|
flows need a way to segregate communication channels so that concurrent conversations between flows on the same set of nodes
|
||||||
it doesn't matter which flow is the initiator and which is the initiated, which is why neither ``Buyer`` nor ``Seller``
|
do not interfere with each other.
|
||||||
are annotated with it. For example, if we choose the seller side as the initiator then we need a seller starter flow that
|
|
||||||
might look something like this:
|
To achieve this the flow framework initiates a new flow session each time a flow starts communicating with a ``Party``
|
||||||
|
for the first time. A session is simply a pair of IDs, one for each side, to allow the node to route received messages to
|
||||||
|
the correct flow. If the other side accepts the session request then subsequent sends and receives to that same ``Party``
|
||||||
|
will use the same session. A session ends when either flow ends, whether as expected or pre-maturely. If a flow ends
|
||||||
|
pre-maturely then the other side will be notified of that and they will also end, as the whole point of flows is a known
|
||||||
|
sequence of message transfers. Flows end pre-maturely due to exceptions, and as described above, if that exception is
|
||||||
|
``FlowException`` or a sub-type then it will propagate to the other side. Any other exception will not propagate.
|
||||||
|
|
||||||
|
Taking a step back, we mentioned that the other side has to accept the session request for there to be a communication
|
||||||
|
channel. A node accepts a session request if it has registered the flow type (the fully-qualified class name) that is
|
||||||
|
making the request - each session initiation includes the initiating flow type. The registration is done by a CorDapp
|
||||||
|
which has made available the particular flow communication, using ``PluginServiceHub.registerServiceFlow``. This method
|
||||||
|
specifies a flow factory for generating the counter-flow to any given initiating flow. If this registration doesn't exist
|
||||||
|
then no further communication takes place and the initiating flow ends with an exception. The initiating flow has to be
|
||||||
|
annotated with ``InitiatingFlow``.
|
||||||
|
|
||||||
|
Going back to our buyer and seller flows, we need a way to initiate communication between the two. This is typically done
|
||||||
|
with one side started manually using the ``startFlowDynamic`` RPC and this initiates the counter-flow on the other side.
|
||||||
|
In this case it doesn't matter which flow is the initiator and which is the initiated, which is why neither ``Buyer`` nor
|
||||||
|
``Seller`` are annotated with ``InitiatingFlow``. For example, if we choose the seller side as the initiator then we need
|
||||||
|
to create a simple seller starter flow that has the annotation we need:
|
||||||
|
|
||||||
.. container:: codeset
|
.. container:: codeset
|
||||||
|
|
||||||
|
@ -3,13 +3,13 @@
|
|||||||
Client RPC API tutorial
|
Client RPC API tutorial
|
||||||
=======================
|
=======================
|
||||||
|
|
||||||
In this tutorial we will build a simple command line utility that
|
In this tutorial we will build a simple command line utility that connects to a node, creates some Cash transactions and
|
||||||
connects to a node, creates some Cash transactions and meanwhile dumps
|
meanwhile dumps the transaction graph to the standard output. We will then put some simple visualisation on top. For an
|
||||||
the transaction graph to the standard output. We will then put some
|
explanation on how the RPC works see :doc:`clientrpc`.
|
||||||
simple visualisation on top. For an explanation on how the RPC works
|
|
||||||
see :doc:`clientrpc`.
|
|
||||||
|
|
||||||
We start off by connecting to the node itself. For the purposes of the tutorial we will use the Driver to start up a notary and a node that issues/exits and moves Cash around for herself. To authenticate we will use the certificates of the nodes directly.
|
We start off by connecting to the node itself. For the purposes of the tutorial we will use the Driver to start up a notary
|
||||||
|
and a node that issues/exits and moves Cash around for herself. To authenticate we will use the certificates of the nodes
|
||||||
|
directly.
|
||||||
|
|
||||||
Note how we configure the node to create a user that has permission to start the CashFlow.
|
Note how we configure the node to create a user that has permission to start the CashFlow.
|
||||||
|
|
||||||
@ -25,14 +25,16 @@ Now we can connect to the node itself using a valid RPC login. We login using th
|
|||||||
:start-after: START 2
|
:start-after: START 2
|
||||||
:end-before: END 2
|
:end-before: END 2
|
||||||
|
|
||||||
We start generating transactions in a different thread (``generateTransactions`` to be defined later) using ``proxy``, which exposes the full RPC interface of the node:
|
We start generating transactions in a different thread (``generateTransactions`` to be defined later) using ``proxy``,
|
||||||
|
which exposes the full RPC interface of the node:
|
||||||
|
|
||||||
.. literalinclude:: ../../core/src/main/kotlin/net/corda/core/messaging/CordaRPCOps.kt
|
.. literalinclude:: ../../core/src/main/kotlin/net/corda/core/messaging/CordaRPCOps.kt
|
||||||
:language: kotlin
|
:language: kotlin
|
||||||
:start-after: interface CordaRPCOps
|
:start-after: interface CordaRPCOps
|
||||||
:end-before: }
|
:end-before: }
|
||||||
|
|
||||||
.. warning:: This API is evolving and will continue to grow as new functionality and features added to Corda are made available to RPC clients.
|
.. warning:: This API is evolving and will continue to grow as new functionality and features added to Corda are made
|
||||||
|
available to RPC clients.
|
||||||
|
|
||||||
The one we need in order to dump the transaction graph is ``verifiedTransactions``. The type signature tells us that the
|
The one we need in order to dump the transaction graph is ``verifiedTransactions``. The type signature tells us that the
|
||||||
RPC will return a list of transactions and an Observable stream. This is a general pattern, we query some data and the
|
RPC will return a list of transactions and an Observable stream. This is a general pattern, we query some data and the
|
||||||
@ -61,13 +63,19 @@ Now we just need to create the transactions themselves!
|
|||||||
:start-after: START 6
|
:start-after: START 6
|
||||||
:end-before: END 6
|
:end-before: END 6
|
||||||
|
|
||||||
We utilise several RPC functions here to query things like the notaries in the node cluster or our own vault. These RPC functions also return ``Observable`` objects so that the node can send us updated values. However, we don't need updates here and so we mark these observables as ``notUsed``. (As a rule, you should always either subscribe to an ``Observable`` or mark it as not used. Failing to do this will leak resources in the node.)
|
We utilise several RPC functions here to query things like the notaries in the node cluster or our own vault. These RPC
|
||||||
|
functions also return ``Observable`` objects so that the node can send us updated values. However, we don't need updates
|
||||||
|
here and so we mark these observables as ``notUsed``. (As a rule, you should always either subscribe to an ``Observable``
|
||||||
|
or mark it as not used. Failing to do this will leak resources in the node.)
|
||||||
|
|
||||||
Then in a loop we generate randomly either an Issue, a Pay or an Exit transaction.
|
Then in a loop we generate randomly either an Issue, a Pay or an Exit transaction.
|
||||||
|
|
||||||
The RPC we need to initiate a Cash transaction is ``startFlowDynamic`` which may start an arbitrary flow, given sufficient permissions to do so. We won't use this function directly, but rather a type-safe wrapper around it ``startFlow`` that type-checks the arguments for us.
|
The RPC we need to initiate a Cash transaction is ``startFlowDynamic`` which may start an arbitrary flow, given sufficient
|
||||||
|
permissions to do so. We won't use this function directly, but rather a type-safe wrapper around it ``startFlow`` that
|
||||||
|
type-checks the arguments for us.
|
||||||
|
|
||||||
Finally we have everything in place: we start a couple of nodes, connect to them, and start creating transactions while listening on successfully created ones, which are dumped to the console. We just need to run it!:
|
Finally we have everything in place: we start a couple of nodes, connect to them, and start creating transactions while
|
||||||
|
listening on successfully created ones, which are dumped to the console. We just need to run it!:
|
||||||
|
|
||||||
.. code-block:: text
|
.. code-block:: text
|
||||||
|
|
||||||
@ -106,9 +114,10 @@ RPC credentials associated with a Client must match the permission set configure
|
|||||||
This refers to both authentication (username and password) and role-based authorisation (a permissioned set of RPC operations an
|
This refers to both authentication (username and password) and role-based authorisation (a permissioned set of RPC operations an
|
||||||
authenticated user is entitled to run).
|
authenticated user is entitled to run).
|
||||||
|
|
||||||
.. note:: Permissions are represented as *String's* to allow RPC implementations to add their own permissioning.
|
.. note:: Permissions are represented as *String's* to allow RPC implementations to add their own permissioning. Currently
|
||||||
Currently the only permission type defined is *StartFlow*, which defines a list of whitelisted flows an authenticated use may execute.
|
the only permission type defined is *StartFlow*, which defines a list of whitelisted flows an authenticated use may
|
||||||
An administrator user (or a developer) may also be assigned the ``ALL`` permission, which grants access to any flow.
|
execute. An administrator user (or a developer) may also be assigned the ``ALL`` permission, which grants access to
|
||||||
|
any flow.
|
||||||
|
|
||||||
In the instructions above the server node permissions are configured programmatically in the driver code:
|
In the instructions above the server node permissions are configured programmatically in the driver code:
|
||||||
|
|
||||||
@ -126,7 +135,8 @@ When starting a standalone node using a configuration file we must supply the RP
|
|||||||
{ username=user, password=password, permissions=[ StartFlow.net.corda.flows.CashFlow ] }
|
{ username=user, password=password, permissions=[ StartFlow.net.corda.flows.CashFlow ] }
|
||||||
]
|
]
|
||||||
|
|
||||||
When using the gradle Cordformation plugin to configure and deploy a node you must supply the RPC credentials in a similar manner:
|
When using the gradle Cordformation plugin to configure and deploy a node you must supply the RPC credentials in a similar
|
||||||
|
manner:
|
||||||
|
|
||||||
.. code-block:: text
|
.. code-block:: text
|
||||||
|
|
||||||
@ -148,5 +158,8 @@ You can then deploy and launch the nodes (Notary and Alice) as follows:
|
|||||||
./docs/source/example-code/build/install/docs/source/example-code/bin/client-rpc-tutorial Print
|
./docs/source/example-code/build/install/docs/source/example-code/bin/client-rpc-tutorial Print
|
||||||
./docs/source/example-code/build/install/docs/source/example-code/bin/client-rpc-tutorial Visualise
|
./docs/source/example-code/build/install/docs/source/example-code/bin/client-rpc-tutorial Visualise
|
||||||
|
|
||||||
|
With regards to the start flow RPCs, there is an extra layer of security whereby the flow to be executed has to be
|
||||||
|
annotated with ``@StartableByRPC``. Flows without this annotation cannot execute using RPC.
|
||||||
|
|
||||||
See more on security in :doc:`secure-coding-guidelines`, node configuration in :doc:`corda-configuration-file` and
|
See more on security in :doc:`secure-coding-guidelines`, node configuration in :doc:`corda-configuration-file` and
|
||||||
Cordformation in :doc:`creating-a-cordapp`
|
Cordformation in :doc:`creating-a-cordapp`
|
||||||
|
@ -6,6 +6,7 @@ import net.corda.core.contracts.Amount
|
|||||||
import net.corda.core.contracts.TransactionType
|
import net.corda.core.contracts.TransactionType
|
||||||
import net.corda.core.contracts.issuedBy
|
import net.corda.core.contracts.issuedBy
|
||||||
import net.corda.core.identity.Party
|
import net.corda.core.identity.Party
|
||||||
|
import net.corda.core.flows.StartableByRPC
|
||||||
import net.corda.core.serialization.OpaqueBytes
|
import net.corda.core.serialization.OpaqueBytes
|
||||||
import net.corda.core.transactions.SignedTransaction
|
import net.corda.core.transactions.SignedTransaction
|
||||||
import net.corda.core.transactions.TransactionBuilder
|
import net.corda.core.transactions.TransactionBuilder
|
||||||
@ -20,6 +21,7 @@ import java.util.*
|
|||||||
* @param recipient the party who should own the currency after it is issued.
|
* @param recipient the party who should own the currency after it is issued.
|
||||||
* @param notary the notary to set on the output states.
|
* @param notary the notary to set on the output states.
|
||||||
*/
|
*/
|
||||||
|
@StartableByRPC
|
||||||
class CashIssueFlow(val amount: Amount<Currency>,
|
class CashIssueFlow(val amount: Amount<Currency>,
|
||||||
val issueRef: OpaqueBytes,
|
val issueRef: OpaqueBytes,
|
||||||
val recipient: Party,
|
val recipient: Party,
|
||||||
|
@ -6,6 +6,7 @@ import net.corda.core.contracts.InsufficientBalanceException
|
|||||||
import net.corda.core.contracts.TransactionType
|
import net.corda.core.contracts.TransactionType
|
||||||
import net.corda.core.crypto.expandedCompositeKeys
|
import net.corda.core.crypto.expandedCompositeKeys
|
||||||
import net.corda.core.crypto.toStringShort
|
import net.corda.core.crypto.toStringShort
|
||||||
|
import net.corda.core.flows.StartableByRPC
|
||||||
import net.corda.core.identity.Party
|
import net.corda.core.identity.Party
|
||||||
import net.corda.core.transactions.SignedTransaction
|
import net.corda.core.transactions.SignedTransaction
|
||||||
import net.corda.core.transactions.TransactionBuilder
|
import net.corda.core.transactions.TransactionBuilder
|
||||||
@ -20,6 +21,7 @@ import java.util.*
|
|||||||
* @param recipient the party to pay the currency to.
|
* @param recipient the party to pay the currency to.
|
||||||
* @param issuerConstraint if specified, the payment will be made using only cash issued by the given parties.
|
* @param issuerConstraint if specified, the payment will be made using only cash issued by the given parties.
|
||||||
*/
|
*/
|
||||||
|
@StartableByRPC
|
||||||
open class CashPaymentFlow(
|
open class CashPaymentFlow(
|
||||||
val amount: Amount<Currency>,
|
val amount: Amount<Currency>,
|
||||||
val recipient: Party,
|
val recipient: Party,
|
||||||
|
@ -6,6 +6,7 @@ import net.corda.core.flows.FlowException
|
|||||||
import net.corda.core.flows.FlowLogic
|
import net.corda.core.flows.FlowLogic
|
||||||
import net.corda.core.flows.InitiatingFlow
|
import net.corda.core.flows.InitiatingFlow
|
||||||
import net.corda.core.identity.Party
|
import net.corda.core.identity.Party
|
||||||
|
import net.corda.core.flows.StartableByRPC
|
||||||
import net.corda.core.node.PluginServiceHub
|
import net.corda.core.node.PluginServiceHub
|
||||||
import net.corda.core.serialization.CordaSerializable
|
import net.corda.core.serialization.CordaSerializable
|
||||||
import net.corda.core.serialization.OpaqueBytes
|
import net.corda.core.serialization.OpaqueBytes
|
||||||
@ -30,6 +31,7 @@ object IssuerFlow {
|
|||||||
* Returns the transaction created by the Issuer to move the cash to the Requester.
|
* Returns the transaction created by the Issuer to move the cash to the Requester.
|
||||||
*/
|
*/
|
||||||
@InitiatingFlow
|
@InitiatingFlow
|
||||||
|
@StartableByRPC
|
||||||
class IssuanceRequester(val amount: Amount<Currency>, val issueToParty: Party, val issueToPartyRef: OpaqueBytes,
|
class IssuanceRequester(val amount: Amount<Currency>, val issueToParty: Party, val issueToPartyRef: OpaqueBytes,
|
||||||
val issuerBankParty: Party) : FlowLogic<SignedTransaction>() {
|
val issuerBankParty: Party) : FlowLogic<SignedTransaction>() {
|
||||||
@Suspendable
|
@Suspendable
|
||||||
|
@ -150,6 +150,9 @@ dependencies {
|
|||||||
// Requery: object mapper for Kotlin
|
// Requery: object mapper for Kotlin
|
||||||
compile "io.requery:requery-kotlin:$requery_version"
|
compile "io.requery:requery-kotlin:$requery_version"
|
||||||
|
|
||||||
|
// FastClasspathScanner: classpath scanning
|
||||||
|
compile 'io.github.lukehutch:fast-classpath-scanner:2.0.19'
|
||||||
|
|
||||||
// Integration test helpers
|
// Integration test helpers
|
||||||
integrationTestCompile "junit:junit:$junit_version"
|
integrationTestCompile "junit:junit:$junit_version"
|
||||||
}
|
}
|
||||||
|
@ -1,10 +1,11 @@
|
|||||||
package net.corda.node
|
package net.corda.node
|
||||||
|
|
||||||
|
import co.paralleluniverse.fibers.Suspendable
|
||||||
import net.corda.core.div
|
import net.corda.core.div
|
||||||
import net.corda.core.flows.FlowLogic
|
import net.corda.core.flows.FlowLogic
|
||||||
|
import net.corda.core.flows.StartableByRPC
|
||||||
import net.corda.core.getOrThrow
|
import net.corda.core.getOrThrow
|
||||||
import net.corda.core.messaging.startFlow
|
import net.corda.core.messaging.startFlow
|
||||||
import net.corda.core.node.CordaPluginRegistry
|
|
||||||
import net.corda.core.utilities.ALICE
|
import net.corda.core.utilities.ALICE
|
||||||
import net.corda.node.driver.driver
|
import net.corda.node.driver.driver
|
||||||
import net.corda.node.services.startFlowPermission
|
import net.corda.node.services.startFlowPermission
|
||||||
@ -48,18 +49,12 @@ class BootTests {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@StartableByRPC
|
||||||
class ObjectInputStreamFlow : FlowLogic<Unit>() {
|
class ObjectInputStreamFlow : FlowLogic<Unit>() {
|
||||||
|
@Suspendable
|
||||||
override fun call() {
|
override fun call() {
|
||||||
System.clearProperty("jdk.serialFilter") // This checks that the node has already consumed the property.
|
System.clearProperty("jdk.serialFilter") // This checks that the node has already consumed the property.
|
||||||
val data = ByteArrayOutputStream().apply { ObjectOutputStream(this).use { it.writeObject(object : Serializable {}) } }.toByteArray()
|
val data = ByteArrayOutputStream().apply { ObjectOutputStream(this).use { it.writeObject(object : Serializable {}) } }.toByteArray()
|
||||||
ObjectInputStream(data.inputStream()).use { it.readObject() }
|
ObjectInputStream(data.inputStream()).use { it.readObject() }
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
class BootTestsPlugin : CordaPluginRegistry() {
|
|
||||||
|
|
||||||
override val requiredFlows: Map<String, Set<String>> = mapOf(ObjectInputStreamFlow::class.java.name to setOf())
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -1 +0,0 @@
|
|||||||
net.corda.node.BootTestsPlugin
|
|
@ -5,15 +5,16 @@ import com.google.common.annotations.VisibleForTesting
|
|||||||
import com.google.common.util.concurrent.ListenableFuture
|
import com.google.common.util.concurrent.ListenableFuture
|
||||||
import com.google.common.util.concurrent.MoreExecutors
|
import com.google.common.util.concurrent.MoreExecutors
|
||||||
import com.google.common.util.concurrent.SettableFuture
|
import com.google.common.util.concurrent.SettableFuture
|
||||||
|
import io.github.lukehutch.fastclasspathscanner.FastClasspathScanner
|
||||||
|
import io.github.lukehutch.fastclasspathscanner.scanner.ClassInfo
|
||||||
import net.corda.core.*
|
import net.corda.core.*
|
||||||
import net.corda.core.contracts.Amount
|
|
||||||
import net.corda.core.contracts.PartyAndReference
|
|
||||||
import net.corda.core.crypto.KeyStoreUtilities
|
import net.corda.core.crypto.KeyStoreUtilities
|
||||||
import net.corda.core.crypto.X509Utilities
|
import net.corda.core.crypto.X509Utilities
|
||||||
import net.corda.core.crypto.replaceCommonName
|
import net.corda.core.crypto.replaceCommonName
|
||||||
import net.corda.core.flows.FlowInitiator
|
import net.corda.core.flows.FlowInitiator
|
||||||
import net.corda.core.flows.FlowLogic
|
import net.corda.core.flows.FlowLogic
|
||||||
import net.corda.core.flows.InitiatingFlow
|
import net.corda.core.flows.InitiatingFlow
|
||||||
|
import net.corda.core.flows.StartableByRPC
|
||||||
import net.corda.core.identity.Party
|
import net.corda.core.identity.Party
|
||||||
import net.corda.core.messaging.CordaRPCOps
|
import net.corda.core.messaging.CordaRPCOps
|
||||||
import net.corda.core.messaging.RPCOps
|
import net.corda.core.messaging.RPCOps
|
||||||
@ -21,7 +22,6 @@ import net.corda.core.messaging.SingleMessageRecipient
|
|||||||
import net.corda.core.node.*
|
import net.corda.core.node.*
|
||||||
import net.corda.core.node.services.*
|
import net.corda.core.node.services.*
|
||||||
import net.corda.core.node.services.NetworkMapCache.MapChange
|
import net.corda.core.node.services.NetworkMapCache.MapChange
|
||||||
import net.corda.core.serialization.OpaqueBytes
|
|
||||||
import net.corda.core.serialization.SingletonSerializeAsToken
|
import net.corda.core.serialization.SingletonSerializeAsToken
|
||||||
import net.corda.core.serialization.deserialize
|
import net.corda.core.serialization.deserialize
|
||||||
import net.corda.core.serialization.serialize
|
import net.corda.core.serialization.serialize
|
||||||
@ -47,7 +47,6 @@ import net.corda.node.services.network.PersistentNetworkMapService
|
|||||||
import net.corda.node.services.persistence.*
|
import net.corda.node.services.persistence.*
|
||||||
import net.corda.node.services.schema.HibernateObserver
|
import net.corda.node.services.schema.HibernateObserver
|
||||||
import net.corda.node.services.schema.NodeSchemaService
|
import net.corda.node.services.schema.NodeSchemaService
|
||||||
import net.corda.node.services.statemachine.FlowLogicRefFactoryImpl
|
|
||||||
import net.corda.node.services.statemachine.FlowStateMachineImpl
|
import net.corda.node.services.statemachine.FlowStateMachineImpl
|
||||||
import net.corda.node.services.statemachine.StateMachineManager
|
import net.corda.node.services.statemachine.StateMachineManager
|
||||||
import net.corda.node.services.statemachine.flowVersion
|
import net.corda.node.services.statemachine.flowVersion
|
||||||
@ -64,8 +63,11 @@ import org.bouncycastle.asn1.x500.X500Name
|
|||||||
import org.jetbrains.exposed.sql.Database
|
import org.jetbrains.exposed.sql.Database
|
||||||
import org.slf4j.Logger
|
import org.slf4j.Logger
|
||||||
import java.io.IOException
|
import java.io.IOException
|
||||||
|
import java.lang.reflect.Modifier.*
|
||||||
|
import java.net.URL
|
||||||
import java.nio.file.FileAlreadyExistsException
|
import java.nio.file.FileAlreadyExistsException
|
||||||
import java.nio.file.Path
|
import java.nio.file.Path
|
||||||
|
import java.nio.file.Paths
|
||||||
import java.security.KeyPair
|
import java.security.KeyPair
|
||||||
import java.security.KeyStoreException
|
import java.security.KeyStoreException
|
||||||
import java.time.Clock
|
import java.time.Clock
|
||||||
@ -73,6 +75,7 @@ import java.util.*
|
|||||||
import java.util.concurrent.ConcurrentHashMap
|
import java.util.concurrent.ConcurrentHashMap
|
||||||
import java.util.concurrent.ExecutorService
|
import java.util.concurrent.ExecutorService
|
||||||
import java.util.concurrent.TimeUnit.SECONDS
|
import java.util.concurrent.TimeUnit.SECONDS
|
||||||
|
import kotlin.collections.ArrayList
|
||||||
import kotlin.reflect.KClass
|
import kotlin.reflect.KClass
|
||||||
import net.corda.core.crypto.generateKeyPair as cryptoGenerateKeyPair
|
import net.corda.core.crypto.generateKeyPair as cryptoGenerateKeyPair
|
||||||
|
|
||||||
@ -93,14 +96,6 @@ abstract class AbstractNode(open val configuration: NodeConfiguration,
|
|||||||
companion object {
|
companion object {
|
||||||
val PRIVATE_KEY_FILE_NAME = "identity-private-key"
|
val PRIVATE_KEY_FILE_NAME = "identity-private-key"
|
||||||
val PUBLIC_IDENTITY_FILE_NAME = "identity-public"
|
val PUBLIC_IDENTITY_FILE_NAME = "identity-public"
|
||||||
|
|
||||||
val defaultFlowWhiteList: Map<Class<out FlowLogic<*>>, Set<Class<*>>> = mapOf(
|
|
||||||
CashExitFlow::class.java to setOf(Amount::class.java, PartyAndReference::class.java),
|
|
||||||
CashIssueFlow::class.java to setOf(Amount::class.java, OpaqueBytes::class.java, Party::class.java),
|
|
||||||
CashPaymentFlow::class.java to setOf(Amount::class.java, Party::class.java),
|
|
||||||
FinalityFlow::class.java to setOf(LinkedHashSet::class.java),
|
|
||||||
ContractUpgradeFlow::class.java to emptySet()
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: Persist this, as well as whether the node is registered.
|
// TODO: Persist this, as well as whether the node is registered.
|
||||||
@ -133,10 +128,10 @@ abstract class AbstractNode(open val configuration: NodeConfiguration,
|
|||||||
override val schemaService: SchemaService get() = schemas
|
override val schemaService: SchemaService get() = schemas
|
||||||
override val transactionVerifierService: TransactionVerifierService get() = txVerifierService
|
override val transactionVerifierService: TransactionVerifierService get() = txVerifierService
|
||||||
override val auditService: AuditService get() = auditService
|
override val auditService: AuditService get() = auditService
|
||||||
|
override val rpcFlows: List<Class<out FlowLogic<*>>> get() = this@AbstractNode.rpcFlows
|
||||||
|
|
||||||
// Internal only
|
// Internal only
|
||||||
override val monitoringService: MonitoringService = MonitoringService(MetricRegistry())
|
override val monitoringService: MonitoringService = MonitoringService(MetricRegistry())
|
||||||
override val flowLogicRefFactory: FlowLogicRefFactoryInternal get() = flowLogicFactory
|
|
||||||
|
|
||||||
override fun <T> startFlow(logic: FlowLogic<T>, flowInitiator: FlowInitiator): FlowStateMachineImpl<T> {
|
override fun <T> startFlow(logic: FlowLogic<T>, flowInitiator: FlowInitiator): FlowStateMachineImpl<T> {
|
||||||
return serverThread.fetchFrom { smm.add(logic, flowInitiator) }
|
return serverThread.fetchFrom { smm.add(logic, flowInitiator) }
|
||||||
@ -176,13 +171,13 @@ abstract class AbstractNode(open val configuration: NodeConfiguration,
|
|||||||
lateinit var net: MessagingService
|
lateinit var net: MessagingService
|
||||||
lateinit var netMapCache: NetworkMapCacheInternal
|
lateinit var netMapCache: NetworkMapCacheInternal
|
||||||
lateinit var scheduler: NodeSchedulerService
|
lateinit var scheduler: NodeSchedulerService
|
||||||
lateinit var flowLogicFactory: FlowLogicRefFactoryInternal
|
|
||||||
lateinit var schemas: SchemaService
|
lateinit var schemas: SchemaService
|
||||||
lateinit var auditService: AuditService
|
lateinit var auditService: AuditService
|
||||||
val customServices: ArrayList<Any> = ArrayList()
|
val customServices: ArrayList<Any> = ArrayList()
|
||||||
protected val runOnStop: ArrayList<Runnable> = ArrayList()
|
protected val runOnStop: ArrayList<Runnable> = ArrayList()
|
||||||
lateinit var database: Database
|
lateinit var database: Database
|
||||||
protected var dbCloser: Runnable? = null
|
protected var dbCloser: Runnable? = null
|
||||||
|
private lateinit var rpcFlows: List<Class<out FlowLogic<*>>>
|
||||||
|
|
||||||
/** Locates and returns a service of the given type if loaded, or throws an exception if not found. */
|
/** Locates and returns a service of the given type if loaded, or throws an exception if not found. */
|
||||||
inline fun <reified T : Any> findService() = customServices.filterIsInstance<T>().single()
|
inline fun <reified T : Any> findService() = customServices.filterIsInstance<T>().single()
|
||||||
@ -250,6 +245,16 @@ abstract class AbstractNode(open val configuration: NodeConfiguration,
|
|||||||
}
|
}
|
||||||
startMessagingService(rpcOps)
|
startMessagingService(rpcOps)
|
||||||
installCoreFlows()
|
installCoreFlows()
|
||||||
|
|
||||||
|
fun Class<out FlowLogic<*>>.isUserInvokable(): Boolean {
|
||||||
|
return isPublic(modifiers) && !isLocalClass && !isAnonymousClass && (!isMemberClass || isStatic(modifiers))
|
||||||
|
}
|
||||||
|
|
||||||
|
val flows = scanForFlows()
|
||||||
|
rpcFlows = flows.filter { it.isUserInvokable() && it.isAnnotationPresent(StartableByRPC::class.java) } +
|
||||||
|
// Add any core flows here
|
||||||
|
listOf(ContractUpgradeFlow::class.java)
|
||||||
|
|
||||||
runOnStop += Runnable { net.stop() }
|
runOnStop += Runnable { net.stop() }
|
||||||
_networkMapRegistrationFuture.setFuture(registerWithNetworkMapIfConfigured())
|
_networkMapRegistrationFuture.setFuture(registerWithNetworkMapIfConfigured())
|
||||||
smm.start()
|
smm.start()
|
||||||
@ -305,8 +310,7 @@ abstract class AbstractNode(open val configuration: NodeConfiguration,
|
|||||||
// the KMS is meant for derived temporary keys used in transactions, and we're not supposed to sign things with
|
// the KMS is meant for derived temporary keys used in transactions, and we're not supposed to sign things with
|
||||||
// the identity key. But the infrastructure to make that easy isn't here yet.
|
// the identity key. But the infrastructure to make that easy isn't here yet.
|
||||||
keyManagement = makeKeyManagementService()
|
keyManagement = makeKeyManagementService()
|
||||||
flowLogicFactory = initialiseFlowLogicFactory()
|
scheduler = NodeSchedulerService(services, database, unfinishedSchedules = busyNodeLatch)
|
||||||
scheduler = NodeSchedulerService(services, database, flowLogicFactory, unfinishedSchedules = busyNodeLatch)
|
|
||||||
|
|
||||||
val tokenizableServices = mutableListOf(storage, net, vault, keyManagement, identity, platformClock, scheduler)
|
val tokenizableServices = mutableListOf(storage, net, vault, keyManagement, identity, platformClock, scheduler)
|
||||||
makeAdvertisedServices(tokenizableServices)
|
makeAdvertisedServices(tokenizableServices)
|
||||||
@ -318,6 +322,53 @@ abstract class AbstractNode(open val configuration: NodeConfiguration,
|
|||||||
return tokenizableServices
|
return tokenizableServices
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun scanForFlows(): List<Class<out FlowLogic<*>>> {
|
||||||
|
val pluginsDir = configuration.baseDirectory / "plugins"
|
||||||
|
log.info("Scanning plugins in $pluginsDir ...")
|
||||||
|
if (!pluginsDir.exists()) return emptyList()
|
||||||
|
|
||||||
|
val pluginJars = pluginsDir.list {
|
||||||
|
it.filter { it.isRegularFile() && it.toString().endsWith(".jar") }.toArray()
|
||||||
|
}
|
||||||
|
|
||||||
|
val scanResult = FastClasspathScanner().overrideClasspath(*pluginJars).scan() // This will only scan the plugin jars and nothing else
|
||||||
|
|
||||||
|
fun loadFlowClass(className: String): Class<out FlowLogic<*>>? {
|
||||||
|
return try {
|
||||||
|
// TODO Make sure this is loaded by the correct class loader
|
||||||
|
@Suppress("UNCHECKED_CAST")
|
||||||
|
Class.forName(className, false, javaClass.classLoader) as Class<out FlowLogic<*>>
|
||||||
|
} catch (e: Exception) {
|
||||||
|
log.warn("Unable to load flow class $className", e)
|
||||||
|
null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
val flowClasses = scanResult.getNamesOfSubclassesOf(FlowLogic::class.java)
|
||||||
|
.mapNotNull { loadFlowClass(it) }
|
||||||
|
.filterNot { isAbstract(it.modifiers) }
|
||||||
|
|
||||||
|
fun URL.pluginName(): String {
|
||||||
|
return try {
|
||||||
|
Paths.get(toURI()).fileName.toString()
|
||||||
|
} catch (e: Exception) {
|
||||||
|
toString()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
val classpathURLsField = ClassInfo::class.java.getDeclaredField("classpathElementURLs").apply { isAccessible = true }
|
||||||
|
|
||||||
|
flowClasses.groupBy {
|
||||||
|
val classInfo = scanResult.classNameToClassInfo[it.name]
|
||||||
|
@Suppress("UNCHECKED_CAST")
|
||||||
|
(classpathURLsField.get(classInfo) as Set<URL>).first()
|
||||||
|
}.forEach { url, classes ->
|
||||||
|
log.info("Found flows in plugin ${url.pluginName()}: ${classes.joinToString { it.name }}")
|
||||||
|
}
|
||||||
|
|
||||||
|
return flowClasses
|
||||||
|
}
|
||||||
|
|
||||||
private fun initUploaders(storageServices: Pair<TxWritableStorageService, CheckpointStorage>) {
|
private fun initUploaders(storageServices: Pair<TxWritableStorageService, CheckpointStorage>) {
|
||||||
val uploaders: List<FileUploader> = listOf(storageServices.first.attachments as NodeAttachmentService) +
|
val uploaders: List<FileUploader> = listOf(storageServices.first.attachments as NodeAttachmentService) +
|
||||||
customServices.filterIsInstance(AcceptsFileUpload::class.java)
|
customServices.filterIsInstance(AcceptsFileUpload::class.java)
|
||||||
@ -386,26 +437,6 @@ abstract class AbstractNode(open val configuration: NodeConfiguration,
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun initialiseFlowLogicFactory(): FlowLogicRefFactoryInternal {
|
|
||||||
val flowWhitelist = HashMap<String, Set<String>>()
|
|
||||||
|
|
||||||
for ((flowClass, extraArgumentTypes) in defaultFlowWhiteList) {
|
|
||||||
val argumentWhitelistClassNames = HashSet(extraArgumentTypes.map { it.name })
|
|
||||||
flowClass.constructors.forEach {
|
|
||||||
it.parameters.mapTo(argumentWhitelistClassNames) { it.type.name }
|
|
||||||
}
|
|
||||||
flowWhitelist.merge(flowClass.name, argumentWhitelistClassNames, { x, y -> x + y })
|
|
||||||
}
|
|
||||||
|
|
||||||
for (plugin in pluginRegistries) {
|
|
||||||
for ((className, classWhitelist) in plugin.requiredFlows) {
|
|
||||||
flowWhitelist.merge(className, classWhitelist, { x, y -> x + y })
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return FlowLogicRefFactoryImpl(flowWhitelist)
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun makePluginServices(tokenizableServices: MutableList<Any>): List<Any> {
|
private fun makePluginServices(tokenizableServices: MutableList<Any>): List<Any> {
|
||||||
val pluginServices = pluginRegistries.flatMap { it.servicePlugins }.map { it.apply(services) }
|
val pluginServices = pluginRegistries.flatMap { it.servicePlugins }.map { it.apply(services) }
|
||||||
tokenizableServices.addAll(pluginServices)
|
tokenizableServices.addAll(pluginServices)
|
||||||
|
@ -7,6 +7,7 @@ import net.corda.core.contracts.UpgradedContract
|
|||||||
import net.corda.core.crypto.SecureHash
|
import net.corda.core.crypto.SecureHash
|
||||||
import net.corda.core.flows.FlowInitiator
|
import net.corda.core.flows.FlowInitiator
|
||||||
import net.corda.core.flows.FlowLogic
|
import net.corda.core.flows.FlowLogic
|
||||||
|
import net.corda.core.flows.StartableByRPC
|
||||||
import net.corda.core.messaging.*
|
import net.corda.core.messaging.*
|
||||||
import net.corda.core.node.NodeInfo
|
import net.corda.core.node.NodeInfo
|
||||||
import net.corda.core.node.services.NetworkMapCache
|
import net.corda.core.node.services.NetworkMapCache
|
||||||
@ -20,6 +21,7 @@ import net.corda.node.services.api.ServiceHubInternal
|
|||||||
import net.corda.node.services.messaging.getRpcContext
|
import net.corda.node.services.messaging.getRpcContext
|
||||||
import net.corda.node.services.messaging.requirePermission
|
import net.corda.node.services.messaging.requirePermission
|
||||||
import net.corda.node.services.startFlowPermission
|
import net.corda.node.services.startFlowPermission
|
||||||
|
import net.corda.node.services.statemachine.FlowStateMachineImpl
|
||||||
import net.corda.node.services.statemachine.StateMachineManager
|
import net.corda.node.services.statemachine.StateMachineManager
|
||||||
import net.corda.node.utilities.transaction
|
import net.corda.node.utilities.transaction
|
||||||
import org.bouncycastle.asn1.x500.X500Name
|
import org.bouncycastle.asn1.x500.X500Name
|
||||||
@ -39,8 +41,6 @@ class CordaRPCOpsImpl(
|
|||||||
private val smm: StateMachineManager,
|
private val smm: StateMachineManager,
|
||||||
private val database: Database
|
private val database: Database
|
||||||
) : CordaRPCOps {
|
) : CordaRPCOps {
|
||||||
override val protocolVersion: Int = 0
|
|
||||||
|
|
||||||
override fun networkMapUpdates(): Pair<List<NodeInfo>, Observable<NetworkMapCache.MapChange>> {
|
override fun networkMapUpdates(): Pair<List<NodeInfo>, Observable<NetworkMapCache.MapChange>> {
|
||||||
return database.transaction {
|
return database.transaction {
|
||||||
services.networkMapCache.track()
|
services.networkMapCache.track()
|
||||||
@ -115,12 +115,8 @@ class CordaRPCOpsImpl(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: Check that this flow is annotated as being intended for RPC invocation
|
|
||||||
override fun <T : Any> startTrackedFlowDynamic(logicType: Class<out FlowLogic<T>>, vararg args: Any?): FlowProgressHandle<T> {
|
override fun <T : Any> startTrackedFlowDynamic(logicType: Class<out FlowLogic<T>>, vararg args: Any?): FlowProgressHandle<T> {
|
||||||
val rpcContext = getRpcContext()
|
val stateMachine = startFlow(logicType, args)
|
||||||
rpcContext.requirePermission(startFlowPermission(logicType))
|
|
||||||
val currentUser = FlowInitiator.RPC(rpcContext.currentUser.username)
|
|
||||||
val stateMachine = services.invokeFlowAsync(logicType, currentUser, *args)
|
|
||||||
return FlowProgressHandleImpl(
|
return FlowProgressHandleImpl(
|
||||||
id = stateMachine.id,
|
id = stateMachine.id,
|
||||||
returnValue = stateMachine.resultFuture,
|
returnValue = stateMachine.resultFuture,
|
||||||
@ -128,13 +124,17 @@ class CordaRPCOpsImpl(
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
// 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> {
|
override fun <T : Any> startFlowDynamic(logicType: Class<out FlowLogic<T>>, vararg args: Any?): FlowHandle<T> {
|
||||||
|
val stateMachine = startFlow(logicType, args)
|
||||||
|
return FlowHandleImpl(id = stateMachine.id, returnValue = stateMachine.resultFuture)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun <T : Any> startFlow(logicType: Class<out FlowLogic<T>>, args: Array<out Any?>): FlowStateMachineImpl<T> {
|
||||||
|
require(logicType.isAnnotationPresent(StartableByRPC::class.java)) { "${logicType.name} was not designed for RPC" }
|
||||||
val rpcContext = getRpcContext()
|
val rpcContext = getRpcContext()
|
||||||
rpcContext.requirePermission(startFlowPermission(logicType))
|
rpcContext.requirePermission(startFlowPermission(logicType))
|
||||||
val currentUser = FlowInitiator.RPC(rpcContext.currentUser.username)
|
val currentUser = FlowInitiator.RPC(rpcContext.currentUser.username)
|
||||||
val stateMachine = services.invokeFlowAsync(logicType, currentUser, *args)
|
return services.invokeFlowAsync(logicType, currentUser, *args)
|
||||||
return FlowHandleImpl(id = stateMachine.id, returnValue = stateMachine.resultFuture)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun attachmentExists(id: SecureHash): Boolean {
|
override fun attachmentExists(id: SecureHash): Boolean {
|
||||||
@ -171,11 +171,12 @@ class CordaRPCOpsImpl(
|
|||||||
|
|
||||||
override fun waitUntilRegisteredWithNetworkMap() = services.networkMapCache.mapServiceRegistered
|
override fun waitUntilRegisteredWithNetworkMap() = services.networkMapCache.mapServiceRegistered
|
||||||
override fun partyFromKey(key: PublicKey) = services.identityService.partyFromKey(key)
|
override fun partyFromKey(key: PublicKey) = services.identityService.partyFromKey(key)
|
||||||
|
@Suppress("DEPRECATION")
|
||||||
@Deprecated("Use partyFromX500Name instead")
|
@Deprecated("Use partyFromX500Name instead")
|
||||||
override fun partyFromName(name: String) = services.identityService.partyFromName(name)
|
override fun partyFromName(name: String) = services.identityService.partyFromName(name)
|
||||||
override fun partyFromX500Name(x500Name: X500Name)= services.identityService.partyFromX500Name(x500Name)
|
override fun partyFromX500Name(x500Name: X500Name)= services.identityService.partyFromX500Name(x500Name)
|
||||||
|
|
||||||
override fun registeredFlows(): List<String> = services.flowLogicRefFactory.flowWhitelist.keys.sorted()
|
override fun registeredFlows(): List<String> = services.rpcFlows.map { it.name }.sorted()
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
private fun stateMachineInfoFromFlowLogic(flowLogic: FlowLogic<*>): StateMachineInfo {
|
private fun stateMachineInfoFromFlowLogic(flowLogic: FlowLogic<*>): StateMachineInfo {
|
||||||
|
@ -2,7 +2,9 @@ package net.corda.node.services.api
|
|||||||
|
|
||||||
import com.google.common.annotations.VisibleForTesting
|
import com.google.common.annotations.VisibleForTesting
|
||||||
import com.google.common.util.concurrent.ListenableFuture
|
import com.google.common.util.concurrent.ListenableFuture
|
||||||
import net.corda.core.flows.*
|
import net.corda.core.flows.FlowInitiator
|
||||||
|
import net.corda.core.flows.FlowLogic
|
||||||
|
import net.corda.core.flows.FlowStateMachine
|
||||||
import net.corda.core.messaging.SingleMessageRecipient
|
import net.corda.core.messaging.SingleMessageRecipient
|
||||||
import net.corda.core.node.NodeInfo
|
import net.corda.core.node.NodeInfo
|
||||||
import net.corda.core.node.PluginServiceHub
|
import net.corda.core.node.PluginServiceHub
|
||||||
@ -13,6 +15,7 @@ import net.corda.core.transactions.SignedTransaction
|
|||||||
import net.corda.core.utilities.loggerFor
|
import net.corda.core.utilities.loggerFor
|
||||||
import net.corda.node.internal.ServiceFlowInfo
|
import net.corda.node.internal.ServiceFlowInfo
|
||||||
import net.corda.node.services.messaging.MessagingService
|
import net.corda.node.services.messaging.MessagingService
|
||||||
|
import net.corda.node.services.statemachine.FlowLogicRefFactoryImpl
|
||||||
import net.corda.node.services.statemachine.FlowStateMachineImpl
|
import net.corda.node.services.statemachine.FlowStateMachineImpl
|
||||||
|
|
||||||
interface NetworkMapCacheInternal : NetworkMapCache {
|
interface NetworkMapCacheInternal : NetworkMapCache {
|
||||||
@ -47,11 +50,6 @@ interface NetworkMapCacheInternal : NetworkMapCache {
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
interface FlowLogicRefFactoryInternal : FlowLogicRefFactory {
|
|
||||||
val flowWhitelist: Map<String, Set<String>>
|
|
||||||
fun toFlowLogic(ref: FlowLogicRef): FlowLogic<*>
|
|
||||||
}
|
|
||||||
|
|
||||||
@CordaSerializable
|
@CordaSerializable
|
||||||
sealed class NetworkCacheError : Exception() {
|
sealed class NetworkCacheError : Exception() {
|
||||||
/** Indicates a failure to deregister, because of a rejected request from the remote node */
|
/** Indicates a failure to deregister, because of a rejected request from the remote node */
|
||||||
@ -64,12 +62,11 @@ abstract class ServiceHubInternal : PluginServiceHub {
|
|||||||
}
|
}
|
||||||
|
|
||||||
abstract val monitoringService: MonitoringService
|
abstract val monitoringService: MonitoringService
|
||||||
abstract val flowLogicRefFactory: FlowLogicRefFactoryInternal
|
|
||||||
abstract val schemaService: SchemaService
|
abstract val schemaService: SchemaService
|
||||||
abstract override val networkMapCache: NetworkMapCacheInternal
|
abstract override val networkMapCache: NetworkMapCacheInternal
|
||||||
abstract val schedulerService: SchedulerService
|
abstract val schedulerService: SchedulerService
|
||||||
abstract val auditService: AuditService
|
abstract val auditService: AuditService
|
||||||
|
abstract val rpcFlows: List<Class<out FlowLogic<*>>>
|
||||||
abstract val networkService: MessagingService
|
abstract val networkService: MessagingService
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -119,9 +116,9 @@ abstract class ServiceHubInternal : PluginServiceHub {
|
|||||||
logicType: Class<out FlowLogic<T>>,
|
logicType: Class<out FlowLogic<T>>,
|
||||||
flowInitiator: FlowInitiator,
|
flowInitiator: FlowInitiator,
|
||||||
vararg args: Any?): FlowStateMachineImpl<T> {
|
vararg args: Any?): FlowStateMachineImpl<T> {
|
||||||
val logicRef = flowLogicRefFactory.create(logicType, *args)
|
val logicRef = FlowLogicRefFactoryImpl.createForRPC(logicType, *args)
|
||||||
@Suppress("UNCHECKED_CAST")
|
@Suppress("UNCHECKED_CAST")
|
||||||
val logic = flowLogicRefFactory.toFlowLogic(logicRef) as FlowLogic<T>
|
val logic = FlowLogicRefFactoryImpl.toFlowLogic(logicRef) as FlowLogic<T>
|
||||||
return startFlow(logic, flowInitiator)
|
return startFlow(logic, flowInitiator)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -12,9 +12,9 @@ import net.corda.core.serialization.SingletonSerializeAsToken
|
|||||||
import net.corda.core.then
|
import net.corda.core.then
|
||||||
import net.corda.core.utilities.loggerFor
|
import net.corda.core.utilities.loggerFor
|
||||||
import net.corda.core.utilities.trace
|
import net.corda.core.utilities.trace
|
||||||
import net.corda.node.services.api.FlowLogicRefFactoryInternal
|
|
||||||
import net.corda.node.services.api.SchedulerService
|
import net.corda.node.services.api.SchedulerService
|
||||||
import net.corda.node.services.api.ServiceHubInternal
|
import net.corda.node.services.api.ServiceHubInternal
|
||||||
|
import net.corda.node.services.statemachine.FlowLogicRefFactoryImpl
|
||||||
import net.corda.node.utilities.*
|
import net.corda.node.utilities.*
|
||||||
import org.apache.activemq.artemis.utils.ReusableLatch
|
import org.apache.activemq.artemis.utils.ReusableLatch
|
||||||
import org.jetbrains.exposed.sql.Database
|
import org.jetbrains.exposed.sql.Database
|
||||||
@ -38,14 +38,12 @@ import javax.annotation.concurrent.ThreadSafe
|
|||||||
* but that starts to sound a lot like off-ledger state.
|
* but that starts to sound a lot like off-ledger state.
|
||||||
*
|
*
|
||||||
* @param services Core node services.
|
* @param services Core node services.
|
||||||
* @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
|
* @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 [FlowLogic] is launched on.
|
* activity. Only replace this for unit testing purposes. This is not the executor the [FlowLogic] is launched on.
|
||||||
*/
|
*/
|
||||||
@ThreadSafe
|
@ThreadSafe
|
||||||
class NodeSchedulerService(private val services: ServiceHubInternal,
|
class NodeSchedulerService(private val services: ServiceHubInternal,
|
||||||
private val database: Database,
|
private val database: Database,
|
||||||
private val flowLogicRefFactory: FlowLogicRefFactoryInternal,
|
|
||||||
private val schedulerTimerExecutor: Executor = Executors.newSingleThreadExecutor(),
|
private val schedulerTimerExecutor: Executor = Executors.newSingleThreadExecutor(),
|
||||||
private val unfinishedSchedules: ReusableLatch = ReusableLatch())
|
private val unfinishedSchedules: ReusableLatch = ReusableLatch())
|
||||||
: SchedulerService, SingletonSerializeAsToken() {
|
: SchedulerService, SingletonSerializeAsToken() {
|
||||||
@ -192,7 +190,7 @@ class NodeSchedulerService(private val services: ServiceHubInternal,
|
|||||||
ScheduledStateRef(scheduledState.ref, scheduledActivity.scheduledAt)
|
ScheduledStateRef(scheduledState.ref, scheduledActivity.scheduledAt)
|
||||||
} else {
|
} else {
|
||||||
// TODO: FlowLogicRefFactory needs to sort out the class loader etc
|
// TODO: FlowLogicRefFactory needs to sort out the class loader etc
|
||||||
val flowLogic = flowLogicRefFactory.toFlowLogic(scheduledActivity.logicRef)
|
val flowLogic = FlowLogicRefFactoryImpl.toFlowLogic(scheduledActivity.logicRef)
|
||||||
log.trace { "Scheduler starting FlowLogic $flowLogic" }
|
log.trace { "Scheduler starting FlowLogic $flowLogic" }
|
||||||
scheduledFlow = flowLogic
|
scheduledFlow = flowLogic
|
||||||
null
|
null
|
||||||
@ -213,7 +211,7 @@ class NodeSchedulerService(private val services: ServiceHubInternal,
|
|||||||
val state = txState.data as SchedulableState
|
val state = txState.data as SchedulableState
|
||||||
return try {
|
return try {
|
||||||
// This can throw as running contract code.
|
// This can throw as running contract code.
|
||||||
state.nextScheduledActivity(scheduledState.ref, flowLogicRefFactory)
|
state.nextScheduledActivity(scheduledState.ref, FlowLogicRefFactoryImpl)
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
log.error("Attempt to run scheduled state $scheduledState resulted in error.", e)
|
log.error("Attempt to run scheduled state $scheduledState resulted in error.", e)
|
||||||
null
|
null
|
||||||
|
@ -4,8 +4,8 @@ import net.corda.core.contracts.ContractState
|
|||||||
import net.corda.core.contracts.SchedulableState
|
import net.corda.core.contracts.SchedulableState
|
||||||
import net.corda.core.contracts.ScheduledStateRef
|
import net.corda.core.contracts.ScheduledStateRef
|
||||||
import net.corda.core.contracts.StateAndRef
|
import net.corda.core.contracts.StateAndRef
|
||||||
import net.corda.core.flows.FlowLogicRefFactory
|
|
||||||
import net.corda.node.services.api.ServiceHubInternal
|
import net.corda.node.services.api.ServiceHubInternal
|
||||||
|
import net.corda.node.services.statemachine.FlowLogicRefFactoryImpl
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This observes the vault and schedules and unschedules activities appropriately based on state production and
|
* This observes the vault and schedules and unschedules activities appropriately based on state production and
|
||||||
@ -13,16 +13,16 @@ import net.corda.node.services.api.ServiceHubInternal
|
|||||||
*/
|
*/
|
||||||
class ScheduledActivityObserver(val services: ServiceHubInternal) {
|
class ScheduledActivityObserver(val services: ServiceHubInternal) {
|
||||||
init {
|
init {
|
||||||
services.vaultService.rawUpdates.subscribe { update ->
|
services.vaultService.rawUpdates.subscribe { (consumed, produced) ->
|
||||||
update.consumed.forEach { services.schedulerService.unscheduleStateActivity(it.ref) }
|
consumed.forEach { services.schedulerService.unscheduleStateActivity(it.ref) }
|
||||||
update.produced.forEach { scheduleStateActivity(it, services.flowLogicRefFactory) }
|
produced.forEach { scheduleStateActivity(it) }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun scheduleStateActivity(produced: StateAndRef<ContractState>, flowLogicRefFactory: FlowLogicRefFactory) {
|
private fun scheduleStateActivity(produced: StateAndRef<ContractState>) {
|
||||||
val producedState = produced.state.data
|
val producedState = produced.state.data
|
||||||
if (producedState is SchedulableState) {
|
if (producedState is SchedulableState) {
|
||||||
val scheduledAt = sandbox { producedState.nextScheduledActivity(produced.ref, flowLogicRefFactory)?.scheduledAt } ?: return
|
val scheduledAt = sandbox { producedState.nextScheduledActivity(produced.ref, FlowLogicRefFactoryImpl)?.scheduledAt } ?: return
|
||||||
services.schedulerService.scheduleStateActivity(ScheduledStateRef(produced.ref, scheduledAt))
|
services.schedulerService.scheduleStateActivity(ScheduledStateRef(produced.ref, scheduledAt))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,14 +1,10 @@
|
|||||||
package net.corda.node.services.statemachine
|
package net.corda.node.services.statemachine
|
||||||
|
|
||||||
|
import com.google.common.annotations.VisibleForTesting
|
||||||
import com.google.common.primitives.Primitives
|
import com.google.common.primitives.Primitives
|
||||||
import net.corda.core.crypto.SecureHash
|
import net.corda.core.flows.*
|
||||||
import net.corda.core.flows.AppContext
|
|
||||||
import net.corda.core.flows.FlowLogic
|
|
||||||
import net.corda.core.flows.FlowLogicRef
|
|
||||||
import net.corda.core.flows.IllegalFlowLogicException
|
|
||||||
import net.corda.core.serialization.CordaSerializable
|
import net.corda.core.serialization.CordaSerializable
|
||||||
import net.corda.core.serialization.SingletonSerializeAsToken
|
import net.corda.core.serialization.SingletonSerializeAsToken
|
||||||
import net.corda.node.services.api.FlowLogicRefFactoryInternal
|
|
||||||
import java.lang.reflect.ParameterizedType
|
import java.lang.reflect.ParameterizedType
|
||||||
import java.lang.reflect.Type
|
import java.lang.reflect.Type
|
||||||
import java.util.*
|
import java.util.*
|
||||||
@ -29,59 +25,26 @@ data class FlowLogicRefImpl internal constructor(val flowLogicClassName: String,
|
|||||||
* Validation of types is performed on the way in and way out in case this object is passed between JVMs which might have differing
|
* 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.
|
* whitelists.
|
||||||
*
|
*
|
||||||
* 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 FlowLogic references (FlowRef)
|
* TODO: Align with API related logic for passing in FlowLogic references (FlowRef)
|
||||||
* TODO: Actual support for AppContext / AttachmentsClassLoader
|
* TODO: Actual support for AppContext / AttachmentsClassLoader
|
||||||
|
* TODO: at some point check whether there is permission, beyond the annotations, to start flows. For example, as a security
|
||||||
|
* measure we might want the ability for the node admin to blacklist a flow such that it moves immediately to the "Flow Hospital"
|
||||||
|
* in response to a potential malicious use or buggy update to an app etc.
|
||||||
*/
|
*/
|
||||||
class FlowLogicRefFactoryImpl(override val flowWhitelist: Map<String, Set<String>>) : SingletonSerializeAsToken(), FlowLogicRefFactoryInternal {
|
object FlowLogicRefFactoryImpl : SingletonSerializeAsToken(), FlowLogicRefFactory {
|
||||||
constructor() : this(mapOf())
|
override fun create(flowClass: Class<out FlowLogic<*>>, vararg args: Any?): FlowLogicRef {
|
||||||
|
if (!flowClass.isAnnotationPresent(SchedulableFlow::class.java)) {
|
||||||
// Pending real dependence on AppContext for class loading etc
|
throw IllegalFlowLogicException(flowClass, "because it's not a schedulable flow")
|
||||||
@Suppress("UNUSED_PARAMETER")
|
}
|
||||||
private fun validateFlowClassName(className: String, appContext: AppContext) {
|
return createForRPC(flowClass, *args)
|
||||||
// TODO: make this specific to the attachments in the [AppContext] by including [SecureHash] in whitelist check
|
|
||||||
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
|
fun createForRPC(flowClass: Class<out FlowLogic<*>>, vararg args: Any?): FlowLogicRef {
|
||||||
@Suppress("UNUSED_PARAMETER")
|
|
||||||
private fun validateArgClassName(className: String, argClassName: String, appContext: AppContext) {
|
|
||||||
// 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 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(flowWhitelist[className]!!.contains(argClassName)) { "Args to $className must have types on the args whitelist: $argClassName, but it has ${flowWhitelist[className]}" }
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Create a [FlowLogicRef] for the Kotlin primary constructor of a named [FlowLogic]
|
|
||||||
*/
|
|
||||||
fun createKotlin(flowLogicClassName: String, args: Map<String, Any?>, attachments: List<SecureHash> = emptyList()): FlowLogicRef {
|
|
||||||
val context = AppContext(attachments)
|
|
||||||
validateFlowClassName(flowLogicClassName, context)
|
|
||||||
for (arg in args.values.filterNotNull()) {
|
|
||||||
validateArgClassName(flowLogicClassName, arg.javaClass.name, context)
|
|
||||||
}
|
|
||||||
val clazz = Class.forName(flowLogicClassName)
|
|
||||||
require(FlowLogic::class.java.isAssignableFrom(clazz)) { "$flowLogicClassName is not a FlowLogic" }
|
|
||||||
@Suppress("UNCHECKED_CAST")
|
|
||||||
val logic = clazz as Class<FlowLogic<FlowLogic<*>>>
|
|
||||||
return createKotlin(logic, args)
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Create a [FlowLogicRef] by assuming a single constructor and the given args.
|
|
||||||
*/
|
|
||||||
override fun create(type: Class<out FlowLogic<*>>, vararg args: Any?): FlowLogicRef {
|
|
||||||
// TODO: This is used via RPC but it's probably better if we pass in argument names and values explicitly
|
// TODO: This is used via RPC but it's probably better if we pass in argument names and values explicitly
|
||||||
// to avoid requiring only a single constructor.
|
// to avoid requiring only a single constructor.
|
||||||
val argTypes = args.map { it?.javaClass }
|
val argTypes = args.map { it?.javaClass }
|
||||||
val constructor = try {
|
val constructor = try {
|
||||||
type.kotlin.constructors.single { ctor ->
|
flowClass.kotlin.constructors.single { ctor ->
|
||||||
// Get the types of the arguments, always boxed (as that's what we get in the invocation).
|
// Get the types of the arguments, always boxed (as that's what we get in the invocation).
|
||||||
val ctorTypes = ctor.javaConstructor!!.parameterTypes.map { Primitives.wrap(it) }
|
val ctorTypes = ctor.javaConstructor!!.parameterTypes.map { Primitives.wrap(it) }
|
||||||
if (argTypes.size != ctorTypes.size)
|
if (argTypes.size != ctorTypes.size)
|
||||||
@ -93,14 +56,14 @@ class FlowLogicRefFactoryImpl(override val flowWhitelist: Map<String, Set<String
|
|||||||
true
|
true
|
||||||
}
|
}
|
||||||
} catch (e: IllegalArgumentException) {
|
} catch (e: IllegalArgumentException) {
|
||||||
throw IllegalFlowLogicException(type, "due to ambiguous match against the constructors: $argTypes")
|
throw IllegalFlowLogicException(flowClass, "due to ambiguous match against the constructors: $argTypes")
|
||||||
} catch (e: NoSuchElementException) {
|
} catch (e: NoSuchElementException) {
|
||||||
throw IllegalFlowLogicException(type, "due to missing constructor for arguments: $argTypes")
|
throw IllegalFlowLogicException(flowClass, "due to missing constructor for arguments: $argTypes")
|
||||||
}
|
}
|
||||||
|
|
||||||
// Build map of args from array
|
// Build map of args from array
|
||||||
val argsMap = args.zip(constructor.parameters).map { Pair(it.second.name!!, it.first) }.toMap()
|
val argsMap = args.zip(constructor.parameters).map { Pair(it.second.name!!, it.first) }.toMap()
|
||||||
return createKotlin(type, argsMap)
|
return createKotlin(flowClass, argsMap)
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -108,44 +71,32 @@ class FlowLogicRefFactoryImpl(override val flowWhitelist: Map<String, Set<String
|
|||||||
*
|
*
|
||||||
* TODO: Rethink language specific naming.
|
* TODO: Rethink language specific naming.
|
||||||
*/
|
*/
|
||||||
fun createKotlin(type: Class<out FlowLogic<*>>, args: Map<String, Any?>): FlowLogicRef {
|
@VisibleForTesting
|
||||||
|
internal 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,
|
// 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.
|
// perhaps as some sort of ThreadLocal style object. For now, just create an empty one.
|
||||||
val appContext = AppContext(emptyList())
|
val appContext = AppContext(emptyList())
|
||||||
validateFlowClassName(type.name, appContext)
|
|
||||||
// Check we can find a constructor and populate the args to it, but don't call it
|
// Check we can find a constructor and populate the args to it, but don't call it
|
||||||
createConstructor(appContext, type, args)
|
createConstructor(type, args)
|
||||||
return FlowLogicRefImpl(type.name, appContext, args)
|
return FlowLogicRefImpl(type.name, appContext, args)
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
fun toFlowLogic(ref: FlowLogicRef): FlowLogic<*> {
|
||||||
* Create a [FlowLogicRef] by trying to find a Java constructor that matches the given args.
|
|
||||||
*/
|
|
||||||
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
|
|
||||||
args.forEach { argsMap["arg${index++}"] = it }
|
|
||||||
return createKotlin(type, argsMap)
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun toFlowLogic(ref: FlowLogicRef): FlowLogic<*> {
|
|
||||||
if (ref !is FlowLogicRefImpl) throw IllegalFlowLogicException(ref.javaClass, "FlowLogicRef was not created via correct FlowLogicRefFactory interface")
|
if (ref !is FlowLogicRefImpl) throw IllegalFlowLogicException(ref.javaClass, "FlowLogicRef was not created via correct FlowLogicRefFactory interface")
|
||||||
validateFlowClassName(ref.flowLogicClassName, ref.appContext)
|
|
||||||
val klass = Class.forName(ref.flowLogicClassName, true, ref.appContext.classLoader).asSubclass(FlowLogic::class.java)
|
val klass = Class.forName(ref.flowLogicClassName, true, ref.appContext.classLoader).asSubclass(FlowLogic::class.java)
|
||||||
return createConstructor(ref.appContext, klass, ref.args)()
|
return createConstructor(klass, ref.args)()
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun createConstructor(appContext: AppContext, clazz: Class<out FlowLogic<*>>, args: Map<String, Any?>): () -> FlowLogic<*> {
|
private fun createConstructor(clazz: Class<out FlowLogic<*>>, args: Map<String, Any?>): () -> FlowLogic<*> {
|
||||||
for (constructor in clazz.kotlin.constructors) {
|
for (constructor in clazz.kotlin.constructors) {
|
||||||
val params = buildParams(appContext, clazz, constructor, args) ?: continue
|
val params = buildParams(constructor, args) ?: continue
|
||||||
// If we get here then we matched every parameter
|
// If we get here then we matched every parameter
|
||||||
return { constructor.callBy(params) }
|
return { constructor.callBy(params) }
|
||||||
}
|
}
|
||||||
throw IllegalFlowLogicException(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 FlowLogic<*>>, constructor: KFunction<FlowLogic<*>>, args: Map<String, Any?>): HashMap<KParameter, Any?>? {
|
private fun buildParams(constructor: KFunction<FlowLogic<*>>, args: Map<String, Any?>): HashMap<KParameter, Any?>? {
|
||||||
val params = hashMapOf<KParameter, Any?>()
|
val params = hashMapOf<KParameter, Any?>()
|
||||||
val usedKeys = hashSetOf<String>()
|
val usedKeys = hashSetOf<String>()
|
||||||
for (parameter in constructor.parameters) {
|
for (parameter in constructor.parameters) {
|
||||||
@ -159,7 +110,6 @@ class FlowLogicRefFactoryImpl(override val flowWhitelist: Map<String, Set<String
|
|||||||
// Not all args were used
|
// Not all args were used
|
||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
params.values.forEach { if (it is Any) validateArgClassName(clazz.name, it.javaClass.name, appContext) }
|
|
||||||
return params
|
return params
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -210,7 +210,7 @@ object InteractiveShell {
|
|||||||
*/
|
*/
|
||||||
@JvmStatic
|
@JvmStatic
|
||||||
fun runFlowByNameFragment(nameFragment: String, inputData: String, output: RenderPrintWriter) {
|
fun runFlowByNameFragment(nameFragment: String, inputData: String, output: RenderPrintWriter) {
|
||||||
val matches = node.flowLogicFactory.flowWhitelist.keys.filter { nameFragment in it }
|
val matches = node.services.rpcFlows.filter { nameFragment in it.name }
|
||||||
if (matches.isEmpty()) {
|
if (matches.isEmpty()) {
|
||||||
output.println("No matching flow found, run 'flow list' to see your options.", Color.red)
|
output.println("No matching flow found, run 'flow list' to see your options.", Color.red)
|
||||||
return
|
return
|
||||||
@ -219,14 +219,12 @@ object InteractiveShell {
|
|||||||
matches.forEachIndexed { i, s -> output.println("${i + 1}. $s", Color.yellow) }
|
matches.forEachIndexed { i, s -> output.println("${i + 1}. $s", Color.yellow) }
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
val match = matches.single()
|
|
||||||
val clazz = Class.forName(match)
|
@Suppress("UNCHECKED_CAST")
|
||||||
if (!FlowLogic::class.java.isAssignableFrom(clazz))
|
val clazz = matches.single() as Class<FlowLogic<*>>
|
||||||
throw IllegalStateException("Found a non-FlowLogic class in the whitelist? $clazz")
|
|
||||||
try {
|
try {
|
||||||
// TODO Flow invocation should use startFlowDynamic.
|
// TODO Flow invocation should use startFlowDynamic.
|
||||||
@Suppress("UNCHECKED_CAST")
|
val fsm = runFlowFromString({ node.services.startFlow(it, FlowInitiator.Shell) }, inputData, clazz)
|
||||||
val fsm = runFlowFromString({ node.services.startFlow(it, FlowInitiator.Shell) }, inputData, clazz as Class<FlowLogic<*>>)
|
|
||||||
// Show the progress tracker on the console until the flow completes or is interrupted with a
|
// Show the progress tracker on the console until the flow completes or is interrupted with a
|
||||||
// Ctrl-C keypress.
|
// Ctrl-C keypress.
|
||||||
val latch = CountDownLatch(1)
|
val latch = CountDownLatch(1)
|
||||||
@ -275,10 +273,7 @@ object InteractiveShell {
|
|||||||
var paramNamesFromConstructor: List<String>? = null
|
var paramNamesFromConstructor: List<String>? = null
|
||||||
fun getPrototype(ctor: Constructor<*>): List<String> {
|
fun getPrototype(ctor: Constructor<*>): List<String> {
|
||||||
val argTypes = ctor.parameterTypes.map { it.simpleName }
|
val argTypes = ctor.parameterTypes.map { it.simpleName }
|
||||||
val prototype = paramNamesFromConstructor!!.zip(argTypes).map { pair ->
|
val prototype = paramNamesFromConstructor!!.zip(argTypes).map { (name, type) -> "$name: $type" }
|
||||||
val (name, type) = pair
|
|
||||||
"$name: $type"
|
|
||||||
}
|
|
||||||
return prototype
|
return prototype
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
|
@ -1,15 +1,9 @@
|
|||||||
package net.corda.node.services.events;
|
package net.corda.node.services.events;
|
||||||
|
|
||||||
import net.corda.core.flows.FlowLogic;
|
import net.corda.core.flows.FlowLogic;
|
||||||
import net.corda.core.flows.FlowLogicRefFactory;
|
|
||||||
import net.corda.node.services.statemachine.FlowLogicRefFactoryImpl;
|
import net.corda.node.services.statemachine.FlowLogicRefFactoryImpl;
|
||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
|
|
||||||
import java.util.HashMap;
|
|
||||||
import java.util.HashSet;
|
|
||||||
import java.util.Map;
|
|
||||||
import java.util.Set;
|
|
||||||
|
|
||||||
public class FlowLogicRefFromJavaTest {
|
public class FlowLogicRefFromJavaTest {
|
||||||
|
|
||||||
@SuppressWarnings("unused")
|
@SuppressWarnings("unused")
|
||||||
@ -56,20 +50,11 @@ public class FlowLogicRefFromJavaTest {
|
|||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void test() {
|
public void test() {
|
||||||
Map<String, Set<String>> whiteList = new HashMap<>();
|
FlowLogicRefFactoryImpl.INSTANCE.createForRPC(JavaFlowLogic.class, new ParamType1(1), new ParamType2("Hello Jack"));
|
||||||
Set<String> argsList = new HashSet<>();
|
|
||||||
argsList.add(ParamType1.class.getName());
|
|
||||||
argsList.add(ParamType2.class.getName());
|
|
||||||
whiteList.put(JavaFlowLogic.class.getName(), argsList);
|
|
||||||
FlowLogicRefFactory factory = new FlowLogicRefFactoryImpl(whiteList);
|
|
||||||
factory.create(JavaFlowLogic.class, new ParamType1(1), new ParamType2("Hello Jack"));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testNoArg() {
|
public void testNoArg() {
|
||||||
Map<String, Set<String>> whiteList = new HashMap<>();
|
FlowLogicRefFactoryImpl.INSTANCE.createForRPC(JavaNoArgFlowLogic.class);
|
||||||
whiteList.put(JavaNoArgFlowLogic.class.getName(), new HashSet<>());
|
|
||||||
FlowLogicRefFactory factory = new FlowLogicRefFactoryImpl(whiteList);
|
|
||||||
factory.create(JavaNoArgFlowLogic.class);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,9 +1,11 @@
|
|||||||
package net.corda.node
|
package net.corda.node
|
||||||
|
|
||||||
|
import co.paralleluniverse.fibers.Suspendable
|
||||||
import net.corda.contracts.asset.Cash
|
import net.corda.contracts.asset.Cash
|
||||||
import net.corda.core.contracts.*
|
import net.corda.core.contracts.*
|
||||||
import net.corda.core.crypto.isFulfilledBy
|
import net.corda.core.crypto.isFulfilledBy
|
||||||
import net.corda.core.crypto.keys
|
import net.corda.core.crypto.keys
|
||||||
|
import net.corda.core.flows.FlowLogic
|
||||||
import net.corda.core.flows.StateMachineRunId
|
import net.corda.core.flows.StateMachineRunId
|
||||||
import net.corda.core.messaging.StateMachineUpdate
|
import net.corda.core.messaging.StateMachineUpdate
|
||||||
import net.corda.core.messaging.startFlow
|
import net.corda.core.messaging.startFlow
|
||||||
@ -35,7 +37,9 @@ import org.junit.Before
|
|||||||
import org.junit.Test
|
import org.junit.Test
|
||||||
import rx.Observable
|
import rx.Observable
|
||||||
import java.io.ByteArrayOutputStream
|
import java.io.ByteArrayOutputStream
|
||||||
import kotlin.test.*
|
import kotlin.test.assertEquals
|
||||||
|
import kotlin.test.assertFalse
|
||||||
|
import kotlin.test.assertTrue
|
||||||
|
|
||||||
class CordaRPCOpsImplTest {
|
class CordaRPCOpsImplTest {
|
||||||
|
|
||||||
@ -118,7 +122,6 @@ class CordaRPCOpsImplTest {
|
|||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `issue and move`() {
|
fun `issue and move`() {
|
||||||
|
|
||||||
rpc.startFlow(::CashIssueFlow,
|
rpc.startFlow(::CashIssueFlow,
|
||||||
Amount(100, USD),
|
Amount(100, USD),
|
||||||
OpaqueBytes(ByteArray(1, { 1 })),
|
OpaqueBytes(ByteArray(1, { 1 })),
|
||||||
@ -225,4 +228,19 @@ class CordaRPCOpsImplTest {
|
|||||||
|
|
||||||
assertArrayEquals(bufferFile.toByteArray(), bufferRpc.toByteArray())
|
assertArrayEquals(bufferFile.toByteArray(), bufferRpc.toByteArray())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `attempt to start non-RPC flow`() {
|
||||||
|
CURRENT_RPC_CONTEXT.set(RpcContext(User("user", "pwd", permissions = setOf(
|
||||||
|
startFlowPermission<NonRPCFlow>()
|
||||||
|
))))
|
||||||
|
assertThatExceptionOfType(IllegalArgumentException::class.java).isThrownBy {
|
||||||
|
rpc.startFlow(::NonRPCFlow)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class NonRPCFlow : FlowLogic<Unit>() {
|
||||||
|
@Suspendable
|
||||||
|
override fun call() = Unit
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -12,7 +12,6 @@ import net.corda.node.serialization.NodeClock
|
|||||||
import net.corda.node.services.api.*
|
import net.corda.node.services.api.*
|
||||||
import net.corda.node.services.messaging.MessagingService
|
import net.corda.node.services.messaging.MessagingService
|
||||||
import net.corda.node.services.schema.NodeSchemaService
|
import net.corda.node.services.schema.NodeSchemaService
|
||||||
import net.corda.node.services.statemachine.FlowLogicRefFactoryImpl
|
|
||||||
import net.corda.node.services.statemachine.FlowStateMachineImpl
|
import net.corda.node.services.statemachine.FlowStateMachineImpl
|
||||||
import net.corda.node.services.statemachine.StateMachineManager
|
import net.corda.node.services.statemachine.StateMachineManager
|
||||||
import net.corda.node.services.transactions.InMemoryTransactionVerifierService
|
import net.corda.node.services.transactions.InMemoryTransactionVerifierService
|
||||||
@ -30,7 +29,6 @@ open class MockServiceHubInternal(
|
|||||||
val mapCache: NetworkMapCacheInternal? = MockNetworkMapCache(),
|
val mapCache: NetworkMapCacheInternal? = MockNetworkMapCache(),
|
||||||
val scheduler: SchedulerService? = null,
|
val scheduler: SchedulerService? = null,
|
||||||
val overrideClock: Clock? = NodeClock(),
|
val overrideClock: Clock? = NodeClock(),
|
||||||
val flowFactory: FlowLogicRefFactoryInternal? = FlowLogicRefFactoryImpl(),
|
|
||||||
val schemas: SchemaService? = NodeSchemaService(),
|
val schemas: SchemaService? = NodeSchemaService(),
|
||||||
val customTransactionVerifierService: TransactionVerifierService? = InMemoryTransactionVerifierService(2)
|
val customTransactionVerifierService: TransactionVerifierService? = InMemoryTransactionVerifierService(2)
|
||||||
) : ServiceHubInternal() {
|
) : ServiceHubInternal() {
|
||||||
@ -56,8 +54,8 @@ open class MockServiceHubInternal(
|
|||||||
get() = throw UnsupportedOperationException()
|
get() = throw UnsupportedOperationException()
|
||||||
|
|
||||||
override val monitoringService: MonitoringService = MonitoringService(MetricRegistry())
|
override val monitoringService: MonitoringService = MonitoringService(MetricRegistry())
|
||||||
override val flowLogicRefFactory: FlowLogicRefFactoryInternal
|
override val rpcFlows: List<Class<out FlowLogic<*>>>
|
||||||
get() = flowFactory ?: throw UnsupportedOperationException()
|
get() = throw UnsupportedOperationException()
|
||||||
override val schemaService: SchemaService
|
override val schemaService: SchemaService
|
||||||
get() = schemas ?: throw UnsupportedOperationException()
|
get() = schemas ?: throw UnsupportedOperationException()
|
||||||
override val auditService: AuditService = DummyAuditService()
|
override val auditService: AuditService = DummyAuditService()
|
||||||
|
@ -1,9 +1,8 @@
|
|||||||
package net.corda.node.services.events
|
package net.corda.node.services.events
|
||||||
|
|
||||||
import net.corda.core.days
|
|
||||||
import net.corda.core.flows.FlowLogic
|
import net.corda.core.flows.FlowLogic
|
||||||
|
import net.corda.core.flows.IllegalFlowLogicException
|
||||||
import net.corda.node.services.statemachine.FlowLogicRefFactoryImpl
|
import net.corda.node.services.statemachine.FlowLogicRefFactoryImpl
|
||||||
import org.junit.Before
|
|
||||||
import org.junit.Test
|
import org.junit.Test
|
||||||
import java.time.Duration
|
import java.time.Duration
|
||||||
|
|
||||||
@ -31,67 +30,51 @@ class FlowLogicRefTest {
|
|||||||
override fun call() = Unit
|
override fun call() = Unit
|
||||||
}
|
}
|
||||||
|
|
||||||
@Suppress("UNUSED_PARAMETER") // We will never use A or b
|
class NonSchedulableFlow : FlowLogic<Unit>() {
|
||||||
class NotWhiteListedKotlinFlowLogic(A: Int, b: String) : FlowLogic<Unit>() {
|
|
||||||
override fun call() = Unit
|
override fun call() = Unit
|
||||||
}
|
}
|
||||||
|
|
||||||
lateinit var factory: FlowLogicRefFactoryImpl
|
@Test
|
||||||
|
fun `create kotlin no arg`() {
|
||||||
@Before
|
FlowLogicRefFactoryImpl.createForRPC(KotlinNoArgFlowLogic::class.java)
|
||||||
fun setup() {
|
|
||||||
// We have to allow Java boxed primitives but Kotlin warns we shouldn't be using them
|
|
||||||
factory = FlowLogicRefFactoryImpl(mapOf(Pair(KotlinFlowLogic::class.java.name, setOf(ParamType1::class.java.name, ParamType2::class.java.name)),
|
|
||||||
Pair(KotlinNoArgFlowLogic::class.java.name, setOf())))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun testCreateKotlinNoArg() {
|
fun `create kotlin`() {
|
||||||
factory.create(KotlinNoArgFlowLogic::class.java)
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
fun testCreateKotlin() {
|
|
||||||
val args = mapOf(Pair("A", ParamType1(1)), Pair("b", ParamType2("Hello Jack")))
|
val args = mapOf(Pair("A", ParamType1(1)), Pair("b", ParamType2("Hello Jack")))
|
||||||
factory.createKotlin(KotlinFlowLogic::class.java, args)
|
FlowLogicRefFactoryImpl.createKotlin(KotlinFlowLogic::class.java, args)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun testCreatePrimary() {
|
fun `create primary`() {
|
||||||
factory.create(KotlinFlowLogic::class.java, ParamType1(1), ParamType2("Hello Jack"))
|
FlowLogicRefFactoryImpl.createForRPC(KotlinFlowLogic::class.java, ParamType1(1), ParamType2("Hello Jack"))
|
||||||
}
|
|
||||||
|
|
||||||
@Test(expected = IllegalArgumentException::class)
|
|
||||||
fun testCreateNotWhiteListed() {
|
|
||||||
factory.create(NotWhiteListedKotlinFlowLogic::class.java, ParamType1(1), ParamType2("Hello Jack"))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun testCreateKotlinVoid() {
|
fun `create kotlin void`() {
|
||||||
factory.createKotlin(KotlinFlowLogic::class.java, emptyMap())
|
FlowLogicRefFactoryImpl.createKotlin(KotlinFlowLogic::class.java, emptyMap())
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun testCreateKotlinNonPrimary() {
|
fun `create kotlin non primary`() {
|
||||||
val args = mapOf(Pair("C", ParamType2("Hello Jack")))
|
val args = mapOf(Pair("C", ParamType2("Hello Jack")))
|
||||||
factory.createKotlin(KotlinFlowLogic::class.java, args)
|
FlowLogicRefFactoryImpl.createKotlin(KotlinFlowLogic::class.java, args)
|
||||||
}
|
|
||||||
|
|
||||||
@Test(expected = IllegalArgumentException::class)
|
|
||||||
fun testCreateArgNotWhiteListed() {
|
|
||||||
val args = mapOf(Pair("illegal", 1.days))
|
|
||||||
factory.createKotlin(KotlinFlowLogic::class.java, args)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun testCreateJavaPrimitiveNoRegistrationRequired() {
|
fun `create java primitive no registration required`() {
|
||||||
val args = mapOf(Pair("primitive", "A string"))
|
val args = mapOf(Pair("primitive", "A string"))
|
||||||
factory.createKotlin(KotlinFlowLogic::class.java, args)
|
FlowLogicRefFactoryImpl.createKotlin(KotlinFlowLogic::class.java, args)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun testCreateKotlinPrimitiveNoRegistrationRequired() {
|
fun `create kotlin primitive no registration required`() {
|
||||||
val args = mapOf(Pair("kotlinType", 3))
|
val args = mapOf(Pair("kotlinType", 3))
|
||||||
factory.createKotlin(KotlinFlowLogic::class.java, args)
|
FlowLogicRefFactoryImpl.createKotlin(KotlinFlowLogic::class.java, args)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test(expected = IllegalFlowLogicException::class)
|
||||||
|
fun `create for non-schedulable flow logic`() {
|
||||||
|
FlowLogicRefFactoryImpl.create(NonSchedulableFlow::class.java)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -44,10 +44,6 @@ class NodeSchedulerServiceTest : SingletonSerializeAsToken() {
|
|||||||
|
|
||||||
val schedulerGatedExecutor = AffinityExecutor.Gate(true)
|
val schedulerGatedExecutor = AffinityExecutor.Gate(true)
|
||||||
|
|
||||||
// We have to allow Java boxed primitives but Kotlin warns we shouldn't be using them
|
|
||||||
@Suppress("PLATFORM_CLASS_MAPPED_TO_KOTLIN")
|
|
||||||
val factory = FlowLogicRefFactoryImpl(mapOf(Pair(TestFlowLogic::class.java.name, setOf(NodeSchedulerServiceTest::class.java.name, Integer::class.java.name))))
|
|
||||||
|
|
||||||
lateinit var services: MockServiceHubInternal
|
lateinit var services: MockServiceHubInternal
|
||||||
|
|
||||||
lateinit var scheduler: NodeSchedulerService
|
lateinit var scheduler: NodeSchedulerService
|
||||||
@ -82,12 +78,16 @@ class NodeSchedulerServiceTest : SingletonSerializeAsToken() {
|
|||||||
database.transaction {
|
database.transaction {
|
||||||
val kms = MockKeyManagementService(ALICE_KEY)
|
val kms = MockKeyManagementService(ALICE_KEY)
|
||||||
val nullIdentity = X500Name("cn=None")
|
val nullIdentity = X500Name("cn=None")
|
||||||
val mockMessagingService = InMemoryMessagingNetwork(false).InMemoryMessaging(false, InMemoryMessagingNetwork.PeerHandle(0, nullIdentity), AffinityExecutor.ServiceAffinityExecutor("test", 1), database)
|
val mockMessagingService = InMemoryMessagingNetwork(false).InMemoryMessaging(
|
||||||
|
false,
|
||||||
|
InMemoryMessagingNetwork.PeerHandle(0, nullIdentity),
|
||||||
|
AffinityExecutor.ServiceAffinityExecutor("test", 1),
|
||||||
|
database)
|
||||||
services = object : MockServiceHubInternal(overrideClock = testClock, keyManagement = kms, net = mockMessagingService), TestReference {
|
services = object : MockServiceHubInternal(overrideClock = testClock, keyManagement = kms, net = mockMessagingService), TestReference {
|
||||||
override val vaultService: VaultService = NodeVaultService(this, dataSourceProps)
|
override val vaultService: VaultService = NodeVaultService(this, dataSourceProps)
|
||||||
override val testReference = this@NodeSchedulerServiceTest
|
override val testReference = this@NodeSchedulerServiceTest
|
||||||
}
|
}
|
||||||
scheduler = NodeSchedulerService(services, database, factory, schedulerGatedExecutor)
|
scheduler = NodeSchedulerService(services, database, schedulerGatedExecutor)
|
||||||
smmExecutor = AffinityExecutor.ServiceAffinityExecutor("test", 1)
|
smmExecutor = AffinityExecutor.ServiceAffinityExecutor("test", 1)
|
||||||
val mockSMM = StateMachineManager(services, listOf(services, scheduler), DBCheckpointStorage(), smmExecutor, database)
|
val mockSMM = StateMachineManager(services, listOf(services, scheduler), DBCheckpointStorage(), smmExecutor, database)
|
||||||
mockSMM.changes.subscribe { change ->
|
mockSMM.changes.subscribe { change ->
|
||||||
@ -269,7 +269,7 @@ class NodeSchedulerServiceTest : SingletonSerializeAsToken() {
|
|||||||
database.transaction {
|
database.transaction {
|
||||||
apply {
|
apply {
|
||||||
val freshKey = services.keyManagementService.freshKey()
|
val freshKey = services.keyManagementService.freshKey()
|
||||||
val state = TestState(factory.create(TestFlowLogic::class.java, increment), instant)
|
val state = TestState(FlowLogicRefFactoryImpl.createForRPC(TestFlowLogic::class.java, increment), instant)
|
||||||
val usefulTX = TransactionType.General.Builder(null).apply {
|
val usefulTX = TransactionType.General.Builder(null).apply {
|
||||||
addOutputState(state, DUMMY_NOTARY)
|
addOutputState(state, DUMMY_NOTARY)
|
||||||
addCommand(Command(), freshKey.public)
|
addCommand(Command(), freshKey.public)
|
||||||
|
@ -7,7 +7,7 @@ import net.corda.core.crypto.containsAny
|
|||||||
import net.corda.core.flows.FlowInitiator
|
import net.corda.core.flows.FlowInitiator
|
||||||
import net.corda.core.flows.FlowLogic
|
import net.corda.core.flows.FlowLogic
|
||||||
import net.corda.core.flows.FlowLogicRefFactory
|
import net.corda.core.flows.FlowLogicRefFactory
|
||||||
import net.corda.core.node.CordaPluginRegistry
|
import net.corda.core.flows.SchedulableFlow
|
||||||
import net.corda.core.node.services.ServiceInfo
|
import net.corda.core.node.services.ServiceInfo
|
||||||
import net.corda.core.node.services.linearHeadsOfType
|
import net.corda.core.node.services.linearHeadsOfType
|
||||||
import net.corda.core.utilities.DUMMY_NOTARY
|
import net.corda.core.utilities.DUMMY_NOTARY
|
||||||
@ -15,7 +15,6 @@ import net.corda.flows.FinalityFlow
|
|||||||
import net.corda.node.services.network.NetworkMapService
|
import net.corda.node.services.network.NetworkMapService
|
||||||
import net.corda.node.services.statemachine.StateMachineManager
|
import net.corda.node.services.statemachine.StateMachineManager
|
||||||
import net.corda.node.services.transactions.ValidatingNotaryService
|
import net.corda.node.services.transactions.ValidatingNotaryService
|
||||||
import net.corda.node.utilities.AddOrRemove
|
|
||||||
import net.corda.node.utilities.transaction
|
import net.corda.node.utilities.transaction
|
||||||
import net.corda.testing.node.MockNetwork
|
import net.corda.testing.node.MockNetwork
|
||||||
import org.junit.After
|
import org.junit.After
|
||||||
@ -68,6 +67,7 @@ class ScheduledFlowTests {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@SchedulableFlow
|
||||||
class ScheduledFlow(val stateRef: StateRef) : FlowLogic<Unit>() {
|
class ScheduledFlow(val stateRef: StateRef) : FlowLogic<Unit>() {
|
||||||
@Suspendable
|
@Suspendable
|
||||||
override fun call() {
|
override fun call() {
|
||||||
@ -87,14 +87,6 @@ class ScheduledFlowTests {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
object ScheduledFlowTestPlugin : CordaPluginRegistry() {
|
|
||||||
override val requiredFlows: Map<String, Set<String>> = mapOf(
|
|
||||||
InsertInitialStateFlow::class.java.name to setOf(Party::class.java.name),
|
|
||||||
ScheduledFlow::class.java.name to setOf(StateRef::class.java.name)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
@Before
|
@Before
|
||||||
fun setup() {
|
fun setup() {
|
||||||
net = MockNetwork(threadPerNode = true)
|
net = MockNetwork(threadPerNode = true)
|
||||||
@ -103,8 +95,6 @@ class ScheduledFlowTests {
|
|||||||
advertisedServices = *arrayOf(ServiceInfo(NetworkMapService.type), ServiceInfo(ValidatingNotaryService.type)))
|
advertisedServices = *arrayOf(ServiceInfo(NetworkMapService.type), ServiceInfo(ValidatingNotaryService.type)))
|
||||||
nodeA = net.createNode(notaryNode.info.address, start = false)
|
nodeA = net.createNode(notaryNode.info.address, start = false)
|
||||||
nodeB = net.createNode(notaryNode.info.address, start = false)
|
nodeB = net.createNode(notaryNode.info.address, start = false)
|
||||||
nodeA.testPluginRegistries.add(ScheduledFlowTestPlugin)
|
|
||||||
nodeB.testPluginRegistries.add(ScheduledFlowTestPlugin)
|
|
||||||
net.startNodes()
|
net.startNodes()
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -138,7 +128,7 @@ class ScheduledFlowTests {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `Run a whole batch of scheduled flows`() {
|
fun `run a whole batch of scheduled flows`() {
|
||||||
val N = 100
|
val N = 100
|
||||||
for (i in 0..N - 1) {
|
for (i in 0..N - 1) {
|
||||||
nodeA.services.startFlow(InsertInitialStateFlow(nodeB.info.legalIdentity))
|
nodeA.services.startFlow(InsertInitialStateFlow(nodeB.info.legalIdentity))
|
||||||
|
@ -7,6 +7,7 @@ import net.corda.core.utilities.DUMMY_BANK_A
|
|||||||
import net.corda.core.utilities.DUMMY_BANK_B
|
import net.corda.core.utilities.DUMMY_BANK_B
|
||||||
import net.corda.core.utilities.DUMMY_NOTARY
|
import net.corda.core.utilities.DUMMY_NOTARY
|
||||||
import net.corda.node.driver.driver
|
import net.corda.node.driver.driver
|
||||||
|
import net.corda.node.services.startFlowPermission
|
||||||
import net.corda.node.services.transactions.SimpleNotaryService
|
import net.corda.node.services.transactions.SimpleNotaryService
|
||||||
import net.corda.nodeapi.User
|
import net.corda.nodeapi.User
|
||||||
import org.junit.Test
|
import org.junit.Test
|
||||||
@ -17,11 +18,11 @@ class AttachmentDemoTest {
|
|||||||
@Test fun `attachment demo using a 10MB zip file`() {
|
@Test fun `attachment demo using a 10MB zip file`() {
|
||||||
val numOfExpectedBytes = 10_000_000
|
val numOfExpectedBytes = 10_000_000
|
||||||
driver(dsl = {
|
driver(dsl = {
|
||||||
val demoUser = listOf(User("demo", "demo", setOf("StartFlow.net.corda.flows.FinalityFlow")))
|
val demoUser = listOf(User("demo", "demo", setOf(startFlowPermission<AttachmentDemoFlow>())))
|
||||||
val (nodeA, nodeB) = Futures.allAsList(
|
val (nodeA, nodeB) = Futures.allAsList(
|
||||||
startNode(DUMMY_BANK_A.name, rpcUsers = demoUser),
|
startNode(DUMMY_BANK_A.name, rpcUsers = demoUser),
|
||||||
startNode(DUMMY_BANK_B.name, rpcUsers = demoUser),
|
startNode(DUMMY_BANK_B.name, rpcUsers = demoUser),
|
||||||
startNode(DUMMY_NOTARY.name, setOf(ServiceInfo(SimpleNotaryService.Companion.type)))
|
startNode(DUMMY_NOTARY.name, setOf(ServiceInfo(SimpleNotaryService.type)))
|
||||||
).getOrThrow()
|
).getOrThrow()
|
||||||
|
|
||||||
val senderThread = CompletableFuture.supplyAsync {
|
val senderThread = CompletableFuture.supplyAsync {
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
package net.corda.attachmentdemo
|
package net.corda.attachmentdemo
|
||||||
|
|
||||||
|
import co.paralleluniverse.fibers.Suspendable
|
||||||
import com.google.common.net.HostAndPort
|
import com.google.common.net.HostAndPort
|
||||||
import joptsimple.OptionParser
|
import joptsimple.OptionParser
|
||||||
import net.corda.client.rpc.CordaRPCClient
|
import net.corda.client.rpc.CordaRPCClient
|
||||||
@ -9,14 +10,14 @@ import net.corda.core.contracts.TransactionForContract
|
|||||||
import net.corda.core.contracts.TransactionType
|
import net.corda.core.contracts.TransactionType
|
||||||
import net.corda.core.identity.Party
|
import net.corda.core.identity.Party
|
||||||
import net.corda.core.crypto.SecureHash
|
import net.corda.core.crypto.SecureHash
|
||||||
|
import net.corda.core.flows.FlowLogic
|
||||||
|
import net.corda.core.flows.StartableByRPC
|
||||||
import net.corda.core.getOrThrow
|
import net.corda.core.getOrThrow
|
||||||
import net.corda.core.messaging.CordaRPCOps
|
import net.corda.core.messaging.CordaRPCOps
|
||||||
import net.corda.core.messaging.startTrackedFlow
|
import net.corda.core.messaging.startTrackedFlow
|
||||||
import net.corda.core.sizedInputStreamAndHash
|
import net.corda.core.sizedInputStreamAndHash
|
||||||
import net.corda.core.utilities.DUMMY_BANK_B
|
import net.corda.core.transactions.SignedTransaction
|
||||||
import net.corda.core.utilities.DUMMY_NOTARY
|
import net.corda.core.utilities.*
|
||||||
import net.corda.core.utilities.DUMMY_NOTARY_KEY
|
|
||||||
import net.corda.core.utilities.Emoji
|
|
||||||
import net.corda.flows.FinalityFlow
|
import net.corda.flows.FinalityFlow
|
||||||
import net.corda.node.driver.poll
|
import net.corda.node.driver.poll
|
||||||
import java.io.InputStream
|
import java.io.InputStream
|
||||||
@ -83,24 +84,38 @@ fun sender(rpc: CordaRPCOps, inputStream: InputStream, hash: SecureHash.SHA256)
|
|||||||
val id = rpc.uploadAttachment(it)
|
val id = rpc.uploadAttachment(it)
|
||||||
require(hash == id) { "Id was '$id' instead of '$hash'" }
|
require(hash == id) { "Id was '$id' instead of '$hash'" }
|
||||||
}
|
}
|
||||||
|
require(rpc.attachmentExists(hash))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
val flowHandle = rpc.startTrackedFlow(::AttachmentDemoFlow, otherSide, hash)
|
||||||
|
flowHandle.progress.subscribe(::println)
|
||||||
|
val stx = flowHandle.returnValue.getOrThrow()
|
||||||
|
println("Sent ${stx.id}")
|
||||||
|
}
|
||||||
|
|
||||||
|
@StartableByRPC
|
||||||
|
class AttachmentDemoFlow(val otherSide: Party, val hash: SecureHash.SHA256) : FlowLogic<SignedTransaction>() {
|
||||||
|
|
||||||
|
object SIGNING : ProgressTracker.Step("Signing transaction")
|
||||||
|
|
||||||
|
override val progressTracker: ProgressTracker = ProgressTracker(SIGNING)
|
||||||
|
|
||||||
|
@Suspendable
|
||||||
|
override fun call(): SignedTransaction {
|
||||||
// Create a trivial transaction with an output that describes the attachment, and the attachment itself
|
// Create a trivial transaction with an output that describes the attachment, and the attachment itself
|
||||||
val ptx = TransactionType.General.Builder(notary = DUMMY_NOTARY)
|
val ptx = TransactionType.General.Builder(notary = DUMMY_NOTARY)
|
||||||
require(rpc.attachmentExists(hash))
|
|
||||||
ptx.addOutputState(AttachmentContract.State(hash))
|
ptx.addOutputState(AttachmentContract.State(hash))
|
||||||
ptx.addAttachment(hash)
|
ptx.addAttachment(hash)
|
||||||
|
|
||||||
|
progressTracker.currentStep = SIGNING
|
||||||
// Sign with the notary key
|
// Sign with the notary key
|
||||||
ptx.signWith(DUMMY_NOTARY_KEY)
|
ptx.signWith(DUMMY_NOTARY_KEY)
|
||||||
|
|
||||||
// Send the transaction to the other recipient
|
// Send the transaction to the other recipient
|
||||||
val stx = ptx.toSignedTransaction()
|
val stx = ptx.toSignedTransaction()
|
||||||
println("Sending ${stx.id}")
|
|
||||||
val flowHandle = rpc.startTrackedFlow(::FinalityFlow, stx, setOf(otherSide))
|
return subFlow(FinalityFlow(stx, setOf(otherSide))).single()
|
||||||
flowHandle.progress.subscribe(::println)
|
}
|
||||||
flowHandle.returnValue.getOrThrow()
|
|
||||||
println("Sent ${stx.id}")
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fun recipient(rpc: CordaRPCOps) {
|
fun recipient(rpc: CordaRPCOps) {
|
||||||
|
@ -1,12 +0,0 @@
|
|||||||
package net.corda.attachmentdemo.plugin
|
|
||||||
|
|
||||||
import net.corda.core.node.CordaPluginRegistry
|
|
||||||
import net.corda.core.transactions.SignedTransaction
|
|
||||||
import net.corda.flows.FinalityFlow
|
|
||||||
|
|
||||||
class AttachmentDemoPlugin : CordaPluginRegistry() {
|
|
||||||
// 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)
|
|
||||||
)
|
|
||||||
}
|
|
@ -1,2 +0,0 @@
|
|||||||
# Register a ServiceLoader service extending from net.corda.core.node.CordaPluginRegistry
|
|
||||||
net.corda.attachmentdemo.plugin.AttachmentDemoPlugin
|
|
@ -1,17 +1,13 @@
|
|||||||
package net.corda.bank.plugin
|
package net.corda.bank.plugin
|
||||||
|
|
||||||
import net.corda.bank.api.BankOfCordaWebApi
|
import net.corda.bank.api.BankOfCordaWebApi
|
||||||
import net.corda.core.contracts.Amount
|
|
||||||
import net.corda.core.identity.Party
|
import net.corda.core.identity.Party
|
||||||
import net.corda.core.node.CordaPluginRegistry
|
import net.corda.core.node.CordaPluginRegistry
|
||||||
import net.corda.core.serialization.OpaqueBytes
|
|
||||||
import net.corda.flows.IssuerFlow
|
import net.corda.flows.IssuerFlow
|
||||||
import java.util.function.Function
|
import java.util.function.Function
|
||||||
|
|
||||||
class BankOfCordaPlugin : CordaPluginRegistry() {
|
class BankOfCordaPlugin : CordaPluginRegistry() {
|
||||||
// A list of classes that expose web APIs.
|
// A list of classes that expose web APIs.
|
||||||
override val webApis = listOf(Function(::BankOfCordaWebApi))
|
override val webApis = listOf(Function(::BankOfCordaWebApi))
|
||||||
// A list of flow that are required for this cordapp
|
|
||||||
override val requiredFlows: Map<String, Set<String>> = mapOf(IssuerFlow.IssuanceRequester::class.java.name to setOf(Amount::class.java.name, Party::class.java.name, OpaqueBytes::class.java.name, Party::class.java.name))
|
|
||||||
override val servicePlugins = listOf(Function(IssuerFlow.Issuer::Service))
|
override val servicePlugins = listOf(Function(IssuerFlow.Issuer::Service))
|
||||||
}
|
}
|
||||||
|
@ -19,7 +19,6 @@ import net.corda.core.serialization.SingletonSerializeAsToken
|
|||||||
import net.corda.core.transactions.FilteredTransaction
|
import net.corda.core.transactions.FilteredTransaction
|
||||||
import net.corda.core.utilities.ProgressTracker
|
import net.corda.core.utilities.ProgressTracker
|
||||||
import net.corda.core.utilities.unwrap
|
import net.corda.core.utilities.unwrap
|
||||||
import net.corda.irs.flows.FixingFlow
|
|
||||||
import net.corda.irs.flows.RatesFixFlow
|
import net.corda.irs.flows.RatesFixFlow
|
||||||
import net.corda.node.services.api.AcceptsFileUpload
|
import net.corda.node.services.api.AcceptsFileUpload
|
||||||
import net.corda.node.utilities.AbstractJDBCHashSet
|
import net.corda.node.utilities.AbstractJDBCHashSet
|
||||||
@ -32,12 +31,14 @@ import java.io.InputStream
|
|||||||
import java.math.BigDecimal
|
import java.math.BigDecimal
|
||||||
import java.security.KeyPair
|
import java.security.KeyPair
|
||||||
import java.time.Clock
|
import java.time.Clock
|
||||||
import java.time.Duration
|
|
||||||
import java.time.Instant
|
import java.time.Instant
|
||||||
import java.time.LocalDate
|
import java.time.LocalDate
|
||||||
import java.util.*
|
import java.util.*
|
||||||
import java.util.function.Function
|
import java.util.function.Function
|
||||||
import javax.annotation.concurrent.ThreadSafe
|
import javax.annotation.concurrent.ThreadSafe
|
||||||
|
import kotlin.collections.component1
|
||||||
|
import kotlin.collections.component2
|
||||||
|
import kotlin.collections.set
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* An interest rates service is an oracle that signs transactions which contain embedded assertions about an interest
|
* An interest rates service is an oracle that signs transactions which contain embedded assertions about an interest
|
||||||
@ -55,7 +56,6 @@ object NodeInterestRates {
|
|||||||
* Register the flow that is used with the Fixing integration tests.
|
* Register the flow that is used with the Fixing integration tests.
|
||||||
*/
|
*/
|
||||||
class Plugin : CordaPluginRegistry() {
|
class Plugin : CordaPluginRegistry() {
|
||||||
override val requiredFlows = mapOf(Pair(FixingFlow.FixingRoleDecider::class.java.name, setOf(Duration::class.java.name, StateRef::class.java.name)))
|
|
||||||
override val servicePlugins = listOf(Function(::Service))
|
override val servicePlugins = listOf(Function(::Service))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -5,6 +5,7 @@ import net.corda.core.contracts.DealState
|
|||||||
import net.corda.core.identity.AbstractParty
|
import net.corda.core.identity.AbstractParty
|
||||||
import net.corda.core.flows.FlowLogic
|
import net.corda.core.flows.FlowLogic
|
||||||
import net.corda.core.flows.InitiatingFlow
|
import net.corda.core.flows.InitiatingFlow
|
||||||
|
import net.corda.core.flows.StartableByRPC
|
||||||
import net.corda.core.node.CordaPluginRegistry
|
import net.corda.core.node.CordaPluginRegistry
|
||||||
import net.corda.core.node.PluginServiceHub
|
import net.corda.core.node.PluginServiceHub
|
||||||
import net.corda.core.serialization.SingletonSerializeAsToken
|
import net.corda.core.serialization.SingletonSerializeAsToken
|
||||||
@ -37,6 +38,7 @@ object AutoOfferFlow {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@InitiatingFlow
|
@InitiatingFlow
|
||||||
|
@StartableByRPC
|
||||||
class Requester(val dealToBeOffered: DealState) : FlowLogic<SignedTransaction>() {
|
class Requester(val dealToBeOffered: DealState) : FlowLogic<SignedTransaction>() {
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
|
@ -8,6 +8,7 @@ import net.corda.core.crypto.keys
|
|||||||
import net.corda.core.crypto.toBase58String
|
import net.corda.core.crypto.toBase58String
|
||||||
import net.corda.core.flows.FlowLogic
|
import net.corda.core.flows.FlowLogic
|
||||||
import net.corda.core.flows.InitiatingFlow
|
import net.corda.core.flows.InitiatingFlow
|
||||||
|
import net.corda.core.flows.SchedulableFlow
|
||||||
import net.corda.core.node.NodeInfo
|
import net.corda.core.node.NodeInfo
|
||||||
import net.corda.core.node.PluginServiceHub
|
import net.corda.core.node.PluginServiceHub
|
||||||
import net.corda.core.node.services.ServiceType
|
import net.corda.core.node.services.ServiceType
|
||||||
@ -136,6 +137,7 @@ object FixingFlow {
|
|||||||
* Fixer role is chosen, then that will be initiated by the [FixingSession] message sent from the other party.
|
* Fixer role is chosen, then that will be initiated by the [FixingSession] message sent from the other party.
|
||||||
*/
|
*/
|
||||||
@InitiatingFlow
|
@InitiatingFlow
|
||||||
|
@SchedulableFlow
|
||||||
class FixingRoleDecider(val ref: StateRef, override val progressTracker: ProgressTracker) : FlowLogic<Unit>() {
|
class FixingRoleDecider(val ref: StateRef, override val progressTracker: ProgressTracker) : FlowLogic<Unit>() {
|
||||||
@Suppress("unused") // Used via reflection.
|
@Suppress("unused") // Used via reflection.
|
||||||
constructor(ref: StateRef) : this(ref, tracker())
|
constructor(ref: StateRef) : this(ref, tracker())
|
||||||
|
@ -4,6 +4,7 @@ import co.paralleluniverse.fibers.Suspendable
|
|||||||
import net.corda.core.identity.Party
|
import net.corda.core.identity.Party
|
||||||
import net.corda.core.flows.FlowLogic
|
import net.corda.core.flows.FlowLogic
|
||||||
import net.corda.core.flows.InitiatingFlow
|
import net.corda.core.flows.InitiatingFlow
|
||||||
|
import net.corda.core.flows.StartableByRPC
|
||||||
import net.corda.core.node.CordaPluginRegistry
|
import net.corda.core.node.CordaPluginRegistry
|
||||||
import net.corda.core.node.NodeInfo
|
import net.corda.core.node.NodeInfo
|
||||||
import net.corda.core.node.PluginServiceHub
|
import net.corda.core.node.PluginServiceHub
|
||||||
@ -44,6 +45,7 @@ object UpdateBusinessDayFlow {
|
|||||||
|
|
||||||
|
|
||||||
@InitiatingFlow
|
@InitiatingFlow
|
||||||
|
@StartableByRPC
|
||||||
class Broadcast(val date: LocalDate, override val progressTracker: ProgressTracker) : FlowLogic<Unit>() {
|
class Broadcast(val date: LocalDate, override val progressTracker: ProgressTracker) : FlowLogic<Unit>() {
|
||||||
constructor(date: LocalDate) : this(date, tracker())
|
constructor(date: LocalDate) : this(date, tracker())
|
||||||
|
|
||||||
|
@ -1,15 +1,9 @@
|
|||||||
package net.corda.irs.plugin
|
package net.corda.irs.plugin
|
||||||
|
|
||||||
import net.corda.core.contracts.StateRef
|
|
||||||
import net.corda.core.identity.Party
|
import net.corda.core.identity.Party
|
||||||
import net.corda.core.node.CordaPluginRegistry
|
import net.corda.core.node.CordaPluginRegistry
|
||||||
import net.corda.irs.api.InterestRateSwapAPI
|
import net.corda.irs.api.InterestRateSwapAPI
|
||||||
import net.corda.irs.contract.InterestRateSwap
|
|
||||||
import net.corda.irs.flows.AutoOfferFlow
|
|
||||||
import net.corda.irs.flows.FixingFlow
|
import net.corda.irs.flows.FixingFlow
|
||||||
import net.corda.irs.flows.UpdateBusinessDayFlow
|
|
||||||
import java.time.Duration
|
|
||||||
import java.time.LocalDate
|
|
||||||
import java.util.function.Function
|
import java.util.function.Function
|
||||||
|
|
||||||
class IRSPlugin : CordaPluginRegistry() {
|
class IRSPlugin : CordaPluginRegistry() {
|
||||||
@ -18,9 +12,4 @@ class IRSPlugin : CordaPluginRegistry() {
|
|||||||
"irsdemo" to javaClass.classLoader.getResource("irsweb").toExternalForm()
|
"irsdemo" to javaClass.classLoader.getResource("irsweb").toExternalForm()
|
||||||
)
|
)
|
||||||
override val servicePlugins = listOf(Function(FixingFlow::Service))
|
override val servicePlugins = listOf(Function(FixingFlow::Service))
|
||||||
override val requiredFlows: Map<String, Set<String>> = mapOf(
|
|
||||||
AutoOfferFlow.Requester::class.java.name to setOf(InterestRateSwap.State::class.java.name),
|
|
||||||
UpdateBusinessDayFlow.Broadcast::class.java.name to setOf(LocalDate::class.java.name),
|
|
||||||
FixingFlow.FixingRoleDecider::class.java.name to setOf(StateRef::class.java.name, Duration::class.java.name),
|
|
||||||
FixingFlow.Floater::class.java.name to setOf(Party::class.java.name, FixingFlow.FixingSession::class.java.name))
|
|
||||||
}
|
}
|
||||||
|
@ -1,15 +0,0 @@
|
|||||||
package net.corda.notarydemo.plugin
|
|
||||||
|
|
||||||
import net.corda.core.identity.Party
|
|
||||||
import net.corda.core.node.CordaPluginRegistry
|
|
||||||
import net.corda.core.transactions.SignedTransaction
|
|
||||||
import net.corda.flows.NotaryFlow
|
|
||||||
import net.corda.notarydemo.flows.DummyIssueAndMove
|
|
||||||
|
|
||||||
class NotaryDemoPlugin : CordaPluginRegistry() {
|
|
||||||
// A list of protocols that are required for this cordapp
|
|
||||||
override val requiredFlows = mapOf(
|
|
||||||
NotaryFlow.Client::class.java.name to setOf(SignedTransaction::class.java.name, setOf(Unit).javaClass.name),
|
|
||||||
DummyIssueAndMove::class.java.name to setOf(Party::class.java.name)
|
|
||||||
)
|
|
||||||
}
|
|
@ -1,2 +0,0 @@
|
|||||||
# Register a ServiceLoader service extending from net.corda.node.CordaPluginRegistry
|
|
||||||
net.corda.notarydemo.plugin.NotaryDemoPlugin
|
|
@ -4,6 +4,7 @@ import co.paralleluniverse.fibers.Suspendable
|
|||||||
import net.corda.core.identity.Party
|
import net.corda.core.identity.Party
|
||||||
import net.corda.core.flows.FlowLogic
|
import net.corda.core.flows.FlowLogic
|
||||||
import net.corda.core.flows.InitiatingFlow
|
import net.corda.core.flows.InitiatingFlow
|
||||||
|
import net.corda.core.flows.StartableByRPC
|
||||||
import net.corda.core.node.PluginServiceHub
|
import net.corda.core.node.PluginServiceHub
|
||||||
import net.corda.core.serialization.CordaSerializable
|
import net.corda.core.serialization.CordaSerializable
|
||||||
import net.corda.core.transactions.SignedTransaction
|
import net.corda.core.transactions.SignedTransaction
|
||||||
@ -24,6 +25,7 @@ object IRSTradeFlow {
|
|||||||
data class OfferMessage(val notary: Party, val dealBeingOffered: IRSState)
|
data class OfferMessage(val notary: Party, val dealBeingOffered: IRSState)
|
||||||
|
|
||||||
@InitiatingFlow
|
@InitiatingFlow
|
||||||
|
@StartableByRPC
|
||||||
class Requester(val swap: SwapData, val otherParty: Party) : FlowLogic<SignedTransaction>() {
|
class Requester(val swap: SwapData, val otherParty: Party) : FlowLogic<SignedTransaction>() {
|
||||||
@Suspendable
|
@Suspendable
|
||||||
override fun call(): SignedTransaction {
|
override fun call(): SignedTransaction {
|
||||||
|
@ -14,6 +14,7 @@ import net.corda.core.identity.AnonymousParty
|
|||||||
import net.corda.core.identity.Party
|
import net.corda.core.identity.Party
|
||||||
import net.corda.core.flows.FlowLogic
|
import net.corda.core.flows.FlowLogic
|
||||||
import net.corda.core.flows.InitiatingFlow
|
import net.corda.core.flows.InitiatingFlow
|
||||||
|
import net.corda.core.flows.StartableByRPC
|
||||||
import net.corda.core.node.PluginServiceHub
|
import net.corda.core.node.PluginServiceHub
|
||||||
import net.corda.core.node.services.dealsWith
|
import net.corda.core.node.services.dealsWith
|
||||||
import net.corda.core.serialization.CordaSerializable
|
import net.corda.core.serialization.CordaSerializable
|
||||||
@ -51,6 +52,7 @@ object SimmFlow {
|
|||||||
* margin using SIMM. If there is an existing state it will update and revalue the portfolio agreement.
|
* margin using SIMM. If there is an existing state it will update and revalue the portfolio agreement.
|
||||||
*/
|
*/
|
||||||
@InitiatingFlow
|
@InitiatingFlow
|
||||||
|
@StartableByRPC
|
||||||
class Requester(val otherParty: Party,
|
class Requester(val otherParty: Party,
|
||||||
val valuationDate: LocalDate,
|
val valuationDate: LocalDate,
|
||||||
val existing: StateAndRef<PortfolioState>?)
|
val existing: StateAndRef<PortfolioState>?)
|
||||||
|
@ -3,6 +3,8 @@ package net.corda.vega.flows
|
|||||||
import co.paralleluniverse.fibers.Suspendable
|
import co.paralleluniverse.fibers.Suspendable
|
||||||
import net.corda.core.contracts.StateRef
|
import net.corda.core.contracts.StateRef
|
||||||
import net.corda.core.flows.FlowLogic
|
import net.corda.core.flows.FlowLogic
|
||||||
|
import net.corda.core.flows.SchedulableFlow
|
||||||
|
import net.corda.core.flows.StartableByRPC
|
||||||
import net.corda.core.node.services.linearHeadsOfType
|
import net.corda.core.node.services.linearHeadsOfType
|
||||||
import net.corda.vega.contracts.PortfolioState
|
import net.corda.vega.contracts.PortfolioState
|
||||||
import java.time.LocalDate
|
import java.time.LocalDate
|
||||||
@ -12,6 +14,8 @@ import java.time.LocalDate
|
|||||||
* requirements
|
* requirements
|
||||||
*/
|
*/
|
||||||
object SimmRevaluation {
|
object SimmRevaluation {
|
||||||
|
@StartableByRPC
|
||||||
|
@SchedulableFlow
|
||||||
class Initiator(val curStateRef: StateRef, val valuationDate: LocalDate) : FlowLogic<Unit>() {
|
class Initiator(val curStateRef: StateRef, val valuationDate: LocalDate) : FlowLogic<Unit>() {
|
||||||
@Suspendable
|
@Suspendable
|
||||||
override fun call(): Unit {
|
override fun call(): Unit {
|
||||||
|
@ -10,18 +10,14 @@ import com.opengamma.strata.market.curve.CurveName
|
|||||||
import com.opengamma.strata.market.param.CurrencyParameterSensitivities
|
import com.opengamma.strata.market.param.CurrencyParameterSensitivities
|
||||||
import com.opengamma.strata.market.param.CurrencyParameterSensitivity
|
import com.opengamma.strata.market.param.CurrencyParameterSensitivity
|
||||||
import com.opengamma.strata.market.param.TenorDateParameterMetadata
|
import com.opengamma.strata.market.param.TenorDateParameterMetadata
|
||||||
import net.corda.core.contracts.StateRef
|
|
||||||
import net.corda.core.identity.Party
|
import net.corda.core.identity.Party
|
||||||
import net.corda.core.node.CordaPluginRegistry
|
import net.corda.core.node.CordaPluginRegistry
|
||||||
import net.corda.core.serialization.SerializationCustomization
|
import net.corda.core.serialization.SerializationCustomization
|
||||||
import net.corda.vega.analytics.CordaMarketData
|
import net.corda.vega.analytics.CordaMarketData
|
||||||
import net.corda.vega.analytics.InitialMarginTriple
|
import net.corda.vega.analytics.InitialMarginTriple
|
||||||
import net.corda.vega.api.PortfolioApi
|
import net.corda.vega.api.PortfolioApi
|
||||||
import net.corda.vega.contracts.SwapData
|
|
||||||
import net.corda.vega.flows.IRSTradeFlow
|
import net.corda.vega.flows.IRSTradeFlow
|
||||||
import net.corda.vega.flows.SimmFlow
|
import net.corda.vega.flows.SimmFlow
|
||||||
import net.corda.vega.flows.SimmRevaluation
|
|
||||||
import java.time.LocalDate
|
|
||||||
import java.util.function.Function
|
import java.util.function.Function
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -32,10 +28,6 @@ import java.util.function.Function
|
|||||||
object SimmService {
|
object SimmService {
|
||||||
class Plugin : CordaPluginRegistry() {
|
class Plugin : CordaPluginRegistry() {
|
||||||
override val webApis = listOf(Function(::PortfolioApi))
|
override val webApis = listOf(Function(::PortfolioApi))
|
||||||
override val requiredFlows: Map<String, Set<String>> = mapOf(
|
|
||||||
SimmFlow.Requester::class.java.name to setOf(Party::class.java.name, LocalDate::class.java.name),
|
|
||||||
SimmRevaluation.Initiator::class.java.name to setOf(StateRef::class.java.name, LocalDate::class.java.name),
|
|
||||||
IRSTradeFlow.Requester::class.java.name to setOf(SwapData::class.java.name, Party::class.java.name))
|
|
||||||
override val staticServeDirs: Map<String, String> = mapOf("simmvaluationdemo" to javaClass.classLoader.getResource("simmvaluationweb").toExternalForm())
|
override val staticServeDirs: Map<String, String> = mapOf("simmvaluationdemo" to javaClass.classLoader.getResource("simmvaluationweb").toExternalForm())
|
||||||
override val servicePlugins = listOf(Function(SimmFlow::Service), Function(IRSTradeFlow::Service))
|
override val servicePlugins = listOf(Function(SimmFlow::Service), Function(IRSTradeFlow::Service))
|
||||||
override fun customizeSerialization(custom: SerializationCustomization): Boolean {
|
override fun customizeSerialization(custom: SerializationCustomization): Boolean {
|
||||||
|
@ -10,6 +10,7 @@ import net.corda.core.crypto.generateKeyPair
|
|||||||
import net.corda.core.days
|
import net.corda.core.days
|
||||||
import net.corda.core.flows.FlowLogic
|
import net.corda.core.flows.FlowLogic
|
||||||
import net.corda.core.flows.InitiatingFlow
|
import net.corda.core.flows.InitiatingFlow
|
||||||
|
import net.corda.core.flows.StartableByRPC
|
||||||
import net.corda.core.node.NodeInfo
|
import net.corda.core.node.NodeInfo
|
||||||
import net.corda.core.seconds
|
import net.corda.core.seconds
|
||||||
import net.corda.core.transactions.SignedTransaction
|
import net.corda.core.transactions.SignedTransaction
|
||||||
@ -22,6 +23,7 @@ import java.time.Instant
|
|||||||
import java.util.*
|
import java.util.*
|
||||||
|
|
||||||
@InitiatingFlow
|
@InitiatingFlow
|
||||||
|
@StartableByRPC
|
||||||
class SellerFlow(val otherParty: Party,
|
class SellerFlow(val otherParty: Party,
|
||||||
val amount: Amount<Currency>,
|
val amount: Amount<Currency>,
|
||||||
override val progressTracker: ProgressTracker) : FlowLogic<SignedTransaction>() {
|
override val progressTracker: ProgressTracker) : FlowLogic<SignedTransaction>() {
|
||||||
|
@ -1,16 +1,10 @@
|
|||||||
package net.corda.traderdemo.plugin
|
package net.corda.traderdemo.plugin
|
||||||
|
|
||||||
import net.corda.core.contracts.Amount
|
|
||||||
import net.corda.core.identity.Party
|
import net.corda.core.identity.Party
|
||||||
import net.corda.core.node.CordaPluginRegistry
|
import net.corda.core.node.CordaPluginRegistry
|
||||||
import net.corda.traderdemo.flow.BuyerFlow
|
import net.corda.traderdemo.flow.BuyerFlow
|
||||||
import net.corda.traderdemo.flow.SellerFlow
|
|
||||||
import java.util.function.Function
|
import java.util.function.Function
|
||||||
|
|
||||||
class TraderDemoPlugin : CordaPluginRegistry() {
|
class TraderDemoPlugin : CordaPluginRegistry() {
|
||||||
// A list of Flows that are required for this cordapp
|
|
||||||
override val requiredFlows: Map<String, Set<String>> = mapOf(
|
|
||||||
SellerFlow::class.java.name to setOf(Party::class.java.name, Amount::class.java.name)
|
|
||||||
)
|
|
||||||
override val servicePlugins = listOf(Function(BuyerFlow::Service))
|
override val servicePlugins = listOf(Function(BuyerFlow::Service))
|
||||||
}
|
}
|
||||||
|
@ -1,14 +1,10 @@
|
|||||||
package net.corda.explorer.plugin
|
package net.corda.explorer.plugin
|
||||||
|
|
||||||
import net.corda.core.contracts.Amount
|
|
||||||
import net.corda.core.identity.Party
|
import net.corda.core.identity.Party
|
||||||
import net.corda.core.node.CordaPluginRegistry
|
import net.corda.core.node.CordaPluginRegistry
|
||||||
import net.corda.core.serialization.OpaqueBytes
|
|
||||||
import net.corda.flows.IssuerFlow
|
import net.corda.flows.IssuerFlow
|
||||||
import java.util.function.Function
|
import java.util.function.Function
|
||||||
|
|
||||||
class ExplorerPlugin : CordaPluginRegistry() {
|
class ExplorerPlugin : CordaPluginRegistry() {
|
||||||
// A list of flow that are required for this cordapp
|
|
||||||
override val requiredFlows: Map<String, Set<String>> = mapOf(IssuerFlow.IssuanceRequester::class.java.name to setOf(Amount::class.java.name, Party::class.java.name, OpaqueBytes::class.java.name, Party::class.java.name))
|
|
||||||
override val servicePlugins = listOf(Function(IssuerFlow.Issuer::Service))
|
override val servicePlugins = listOf(Function(IssuerFlow.Issuer::Service))
|
||||||
}
|
}
|
||||||
|
@ -30,14 +30,6 @@ class CorDappInfoServlet(val plugins: List<CordaPluginRegistry>, val rpc: CordaR
|
|||||||
} else {
|
} else {
|
||||||
plugins.forEach { plugin ->
|
plugins.forEach { plugin ->
|
||||||
h3 { +plugin::class.java.name }
|
h3 { +plugin::class.java.name }
|
||||||
if (plugin.requiredFlows.isNotEmpty()) {
|
|
||||||
div {
|
|
||||||
p { +"Whitelisted flows:" }
|
|
||||||
ul {
|
|
||||||
plugin.requiredFlows.map { it.key }.forEach { li { +it } }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (plugin.webApis.isNotEmpty()) {
|
if (plugin.webApis.isNotEmpty()) {
|
||||||
div {
|
div {
|
||||||
plugin.webApis.forEach { api ->
|
plugin.webApis.forEach { api ->
|
||||||
@ -56,7 +48,7 @@ class CorDappInfoServlet(val plugins: List<CordaPluginRegistry>, val rpc: CordaR
|
|||||||
div {
|
div {
|
||||||
p { +"Static web content:" }
|
p { +"Static web content:" }
|
||||||
ul {
|
ul {
|
||||||
plugin.staticServeDirs.map { it.key }.forEach {
|
plugin.staticServeDirs.keys.forEach {
|
||||||
li { a("web/$it") { +it } }
|
li { a("web/$it") { +it } }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user