mirror of
https://github.com/corda/corda.git
synced 2025-06-17 06:38:21 +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:
@ -9,11 +9,12 @@ import net.corda.core.serialization.CordaSerializable
|
||||
* the flow to run at the scheduled time.
|
||||
*/
|
||||
interface FlowLogicRefFactory {
|
||||
fun create(type: Class<out FlowLogic<*>>, vararg args: Any?): FlowLogicRef
|
||||
fun create(flowClass: Class<out FlowLogic<*>>, vararg args: Any?): FlowLogicRef
|
||||
}
|
||||
|
||||
@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.
|
||||
|
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>>
|
||||
|
||||
/**
|
||||
* 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
|
||||
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
|
||||
* result of running the flow.
|
||||
* result of running the flow. [logicType] must be annotated with [net.corda.core.flows.StartableByRPC].
|
||||
*/
|
||||
@RPCReturnsObservables
|
||||
fun <T : Any> startTrackedFlowDynamic(logicType: Class<out FlowLogic<T>>, vararg args: Any?): FlowProgressHandle<T>
|
||||
|
@ -8,42 +8,38 @@ import java.util.function.Function
|
||||
* 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.
|
||||
*/
|
||||
abstract class CordaPluginRegistry(
|
||||
/**
|
||||
* 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.
|
||||
*/
|
||||
open val webApis: List<Function<CordaRPCOps, out Any>> = emptyList(),
|
||||
abstract class CordaPluginRegistry {
|
||||
/**
|
||||
* 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.
|
||||
*/
|
||||
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 "\*.
|
||||
* 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()
|
||||
*/
|
||||
open val staticServeDirs: Map<String, String> = emptyMap(),
|
||||
/**
|
||||
* 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
|
||||
* be specified with: javaClass.getResource("<folder-in-jar>").toExternalForm()
|
||||
*/
|
||||
open val staticServeDirs: Map<String, String> get() = emptyMap()
|
||||
|
||||
/**
|
||||
* A Map with an entry for each consumed Flow used by the webAPIs.
|
||||
* The key of each map entry should contain the FlowLogic<T> class name.
|
||||
* The associated map values are the union of all concrete class names passed to the Flow constructor.
|
||||
* Standard java.lang.* and kotlin.* types do not need to be included explicitly.
|
||||
* This is used to extend the white listed Flows that can be initiated from the ServiceHub invokeFlowAsync method.
|
||||
*/
|
||||
open val requiredFlows: Map<String, Set<String>> = emptyMap(),
|
||||
@Suppress("unused")
|
||||
@Deprecated("This is no longer needed. Instead annotate any flows that need to be invoked via RPC with " +
|
||||
"@StartableByRPC and any scheduled flows with @SchedulableFlow", level = DeprecationLevel.ERROR)
|
||||
open val requiredFlows: Map<String, Set<String>> get() = emptyMap()
|
||||
|
||||
/**
|
||||
* List of lambdas constructing additional long lived services to be hosted within the node.
|
||||
* They expect a single [PluginServiceHub] parameter as input.
|
||||
* 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.
|
||||
*/
|
||||
open val servicePlugins: List<Function<PluginServiceHub, out Any>> get() = emptyList()
|
||||
|
||||
/**
|
||||
* List of lambdas constructing additional long lived services to be hosted within the node.
|
||||
* They expect a single [PluginServiceHub] parameter as input.
|
||||
* 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.
|
||||
*/
|
||||
open val servicePlugins: List<Function<PluginServiceHub, out Any>> = emptyList()
|
||||
) {
|
||||
/**
|
||||
* 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
|
||||
* adding the @CordaSerializable annotation or via this method.
|
||||
* For example, if you add a new [net.corda.core.contracts.ContractState] it needs to be whitelisted. You can do that
|
||||
* 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.
|
||||
*/
|
||||
|
@ -2,6 +2,7 @@ package net.corda.flows
|
||||
|
||||
import net.corda.core.contracts.*
|
||||
import net.corda.core.flows.InitiatingFlow
|
||||
import net.corda.core.flows.StartableByRPC
|
||||
import net.corda.core.transactions.SignedTransaction
|
||||
import net.corda.core.transactions.TransactionBuilder
|
||||
import java.security.PublicKey
|
||||
@ -15,6 +16,7 @@ import java.security.PublicKey
|
||||
* use the new updated state for future transactions.
|
||||
*/
|
||||
@InitiatingFlow
|
||||
@StartableByRPC
|
||||
class ContractUpgradeFlow<OldState : ContractState, out NewState : ContractState>(
|
||||
originalState: StateAndRef<OldState>,
|
||||
newContractClass: Class<out UpgradedContract<OldState, NewState>>
|
||||
|
@ -1,5 +1,6 @@
|
||||
package net.corda.core.flows
|
||||
|
||||
import co.paralleluniverse.fibers.Suspendable
|
||||
import net.corda.contracts.asset.Cash
|
||||
import net.corda.core.contracts.*
|
||||
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.node.services.unconsumedStates
|
||||
import net.corda.core.serialization.OpaqueBytes
|
||||
import net.corda.core.transactions.SignedTransaction
|
||||
import net.corda.core.utilities.Emoji
|
||||
import net.corda.flows.CashIssueFlow
|
||||
import net.corda.flows.ContractUpgradeFlow
|
||||
@ -27,7 +29,6 @@ import org.junit.Before
|
||||
import org.junit.Test
|
||||
import java.security.PublicKey
|
||||
import java.util.*
|
||||
import java.util.concurrent.ExecutionException
|
||||
import kotlin.test.assertEquals
|
||||
import kotlin.test.assertFailsWith
|
||||
import kotlin.test.assertTrue
|
||||
@ -69,10 +70,10 @@ class ContractUpgradeFlowTest {
|
||||
requireNotNull(atx)
|
||||
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
|
||||
mockNet.runNetwork()
|
||||
assertFailsWith(ExecutionException::class) { rejectedFuture.get() }
|
||||
assertFailsWith(FlowSessionException::class) { rejectedFuture.getOrThrow() }
|
||||
|
||||
// Party B authorise the contract state upgrade.
|
||||
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
|
||||
mockNet.runNetwork()
|
||||
|
||||
val result = resultFuture.get()
|
||||
val result = resultFuture.getOrThrow()
|
||||
|
||||
fun check(node: MockNetwork.MockNode) {
|
||||
val nodeStx = node.database.transaction {
|
||||
@ -124,12 +125,12 @@ class ContractUpgradeFlowTest {
|
||||
.toSignedTransaction()
|
||||
|
||||
val user = rpcTestUser.copy(permissions = setOf(
|
||||
startFlowPermission<FinalityFlow>(),
|
||||
startFlowPermission<FinalityInvoker>(),
|
||||
startFlowPermission<ContractUpgradeFlow<*, *>>()
|
||||
))
|
||||
val rpcA = startProxy(a, 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()
|
||||
handle.returnValue.getOrThrow()
|
||||
|
||||
@ -143,7 +144,7 @@ class ContractUpgradeFlowTest {
|
||||
DummyContractV2::class.java).returnValue
|
||||
|
||||
mockNet.runNetwork()
|
||||
assertFailsWith(ExecutionException::class) { rejectedFuture.get() }
|
||||
assertFailsWith(FlowSessionException::class) { rejectedFuture.getOrThrow() }
|
||||
|
||||
// Party B authorise the contract state upgrade.
|
||||
rpcB.authoriseContractUpgrade(btx!!.tx.outRef<ContractState>(0), DummyContractV2::class.java)
|
||||
@ -154,7 +155,7 @@ class ContractUpgradeFlowTest {
|
||||
DummyContractV2::class.java).returnValue
|
||||
|
||||
mockNet.runNetwork()
|
||||
val result = resultFuture.get()
|
||||
val result = resultFuture.getOrThrow()
|
||||
// Check results.
|
||||
listOf(a, b).forEach {
|
||||
val signedTX = a.database.transaction { a.services.storageService.validatedTransactions.getTransaction(result.ref.txhash) }
|
||||
@ -210,4 +211,11 @@ class ContractUpgradeFlowTest {
|
||||
// Dummy Cash contract for testing.
|
||||
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))
|
||||
}
|
||||
}
|
||||
|
Reference in New Issue
Block a user