From 235497e0f4042eca20b4b965e50952f8acb2a637 Mon Sep 17 00:00:00 2001 From: Matthew Nesbit Date: Thu, 28 Jul 2016 11:58:35 +0100 Subject: [PATCH 1/5] Convert long lived services supporting protocol listeners to plugins --- .../r3corda/core/node/CordaPluginRegistry.kt | 8 + .../com/r3corda/node/internal/AbstractNode.kt | 49 +++-- .../node/internal/testing/IRSSimulation.kt | 6 - .../node/internal/testing/Simulation.kt | 7 +- .../FixingSessionInitiationHandler.kt | 22 --- .../node/services/NotaryChangeService.kt | 46 +++-- .../clientapi/FixingSessionInitiation.kt | 31 +++ .../services/clientapi/NodeInterestRates.kt | 30 +-- .../persistence/DataVendingService.kt | 176 +++++++++--------- .../com.r3corda.core.node.CordaPluginRegistry | 5 +- .../com/r3corda/node/services/MockServices.kt | 4 +- .../node/services/NodeInterestRatesTest.kt | 2 +- src/main/kotlin/com/r3corda/demos/IRSDemo.kt | 7 +- .../demos/protocols/AutoOfferProtocol.kt | 20 +- .../demos/protocols/ExitServerProtocol.kt | 15 +- .../protocols/UpdateBusinessDayProtocol.kt | 16 +- .../com.r3corda.core.node.CordaPluginRegistry | 5 +- 17 files changed, 257 insertions(+), 192 deletions(-) delete mode 100644 node/src/main/kotlin/com/r3corda/node/services/FixingSessionInitiationHandler.kt create mode 100644 node/src/main/kotlin/com/r3corda/node/services/clientapi/FixingSessionInitiation.kt diff --git a/core/src/main/kotlin/com/r3corda/core/node/CordaPluginRegistry.kt b/core/src/main/kotlin/com/r3corda/core/node/CordaPluginRegistry.kt index 179e8d48df..09cd1f3ad1 100644 --- a/core/src/main/kotlin/com/r3corda/core/node/CordaPluginRegistry.kt +++ b/core/src/main/kotlin/com/r3corda/core/node/CordaPluginRegistry.kt @@ -27,4 +27,12 @@ interface CordaPluginRegistry { * This is used to extend the white listed protocols that can be initiated from the ServiceHub invokeProtocolAsync method. */ val requiredProtocols: Map> + + /** + * List of additional long lived services to be hosted within the node. + * They are expected to have a single parameter constructor that takes a ServiceHubInternal as input. + * The ServiceHubInternal will be fully constructed before the plugin service is created and will + * allow access to the protocol factory and protocol initiation entry points there. + */ + val servicePlugins: List> } diff --git a/node/src/main/kotlin/com/r3corda/node/internal/AbstractNode.kt b/node/src/main/kotlin/com/r3corda/node/internal/AbstractNode.kt index a7ffa45446..f4bead4bcf 100644 --- a/node/src/main/kotlin/com/r3corda/node/internal/AbstractNode.kt +++ b/node/src/main/kotlin/com/r3corda/node/internal/AbstractNode.kt @@ -17,6 +17,7 @@ import com.r3corda.core.protocols.ProtocolLogic import com.r3corda.core.protocols.ProtocolLogicRefFactory import com.r3corda.core.random63BitValue import com.r3corda.core.seconds +import com.r3corda.core.serialization.SingletonSerializeAsToken import com.r3corda.core.serialization.deserialize import com.r3corda.core.serialization.serialize import com.r3corda.node.api.APIServer @@ -60,7 +61,7 @@ import java.util.* // In theory the NodeInfo for the node should be passed in, instead, however currently this is constructed by the // AbstractNode. It should be possible to generate the NodeInfo outside of AbstractNode, so it can be passed in. abstract class AbstractNode(val dir: Path, val configuration: NodeConfiguration, val networkMapService: NodeInfo?, - val advertisedServices: Set, val platformClock: Clock) { + val advertisedServices: Set, val platformClock: Clock): SingletonSerializeAsToken() { companion object { val PRIVATE_KEY_FILE_NAME = "identity-private-key" val PUBLIC_IDENTITY_FILE_NAME = "identity-public" @@ -124,6 +125,11 @@ abstract class AbstractNode(val dir: Path, val configuration: NodeConfiguration, lateinit var api: APIServer lateinit var scheduler: SchedulerService lateinit var protocolLogicFactory: ProtocolLogicRefFactory + var customServices: List = emptyList() + inline fun getCustomService() : T { + return customServices.single{ x-> x is T } as T + } + var isPreviousCheckpointsPresent = false private set @@ -151,7 +157,7 @@ abstract class AbstractNode(val dir: Path, val configuration: NodeConfiguration, net = makeMessagingService() netMapCache = InMemoryNetworkMapCache(net) wallet = NodeWalletService(services) - makeInterestRatesOracleService() + identity = makeIdentityService() // Place the long term identity key in the KMS. Eventually, this is likely going to be separated again because // the KMS is meant for derived temporary keys used in transactions, and we're not supposed to sign things with @@ -159,17 +165,18 @@ abstract class AbstractNode(val dir: Path, val configuration: NodeConfiguration, keyManagement = E2ETestKeyManagementService(setOf(storage.myLegalIdentityKey)) api = APIServerImpl(this) scheduler = NodeSchedulerService(services) - smm = StateMachineManager(services, - listOf(storage, net, wallet, keyManagement, identity, platformClock, scheduler, interestRatesService), - checkpointStorage, - serverThread) protocolLogicFactory = initialiseProtocolLogicFactory() - // This object doesn't need to be referenced from this class because it registers handlers on the network - // service and so that keeps it from being collected. - DataVendingService(net, services) - NotaryChangeService(net, smm, services.networkMapCache) + val tokenizableServices = mutableListOf(storage, net, wallet, keyManagement, identity, platformClock, scheduler) + + buildPluginServices(tokenizableServices) + + + smm = StateMachineManager(services, + listOf(tokenizableServices), + checkpointStorage, + serverThread) buildAdvertisedServices() @@ -199,6 +206,20 @@ abstract class AbstractNode(val dir: Path, val configuration: NodeConfiguration, return ProtocolLogicRefFactory(protocolWhitelist) } + private fun buildPluginServices(tokenizableServices: MutableList) { + val pluginServices = pluginRegistries.flatMap { x -> x.servicePlugins } + val serviceList = mutableListOf() + for (serviceClass in pluginServices) { + val service = serviceClass.getConstructor(ServiceHubInternal::class.java).newInstance(services) + serviceList.add(service) + tokenizableServices.add(service) + if(service is AcceptsFileUpload) { + _servicesThatAcceptUploads += service + } + } + customServices = serviceList + } + /** * Run any tasks that are needed to ensure the node is in a correct state before running start(). @@ -280,14 +301,6 @@ abstract class AbstractNode(val dir: Path, val configuration: NodeConfiguration, } } - lateinit var interestRatesService: NodeInterestRates.Service - - open protected fun makeInterestRatesOracleService() { - // TODO: Once the service has data, automatically register with the network map service (once built). - interestRatesService = NodeInterestRates.Service(this) - _servicesThatAcceptUploads += interestRatesService - } - protected open fun makeIdentityService(): IdentityService { val service = InMemoryIdentityService() if (networkMapService != null) diff --git a/node/src/main/kotlin/com/r3corda/node/internal/testing/IRSSimulation.kt b/node/src/main/kotlin/com/r3corda/node/internal/testing/IRSSimulation.kt index 674d663d4d..da1c261316 100644 --- a/node/src/main/kotlin/com/r3corda/node/internal/testing/IRSSimulation.kt +++ b/node/src/main/kotlin/com/r3corda/node/internal/testing/IRSSimulation.kt @@ -15,7 +15,6 @@ import com.r3corda.core.node.services.linearHeadsOfType import com.r3corda.core.node.services.testing.MockIdentityService import com.r3corda.core.random63BitValue import com.r3corda.core.success -import com.r3corda.node.services.FixingSessionInitiationHandler import com.r3corda.node.services.network.InMemoryMessagingNetwork import com.r3corda.node.utilities.JsonSupport import com.r3corda.protocols.TwoPartyDealProtocol @@ -40,11 +39,6 @@ class IRSSimulation(networkSendManuallyPumped: Boolean, runAsync: Boolean, laten private val executeOnNextIteration = Collections.synchronizedList(LinkedList<() -> Unit>()) override fun startMainSimulation(): ListenableFuture { - - // TODO: until we have general session initiation - FixingSessionInitiationHandler.register(banks[0]) - FixingSessionInitiationHandler.register(banks[1]) - val future = SettableFuture.create() nodeAKey = banks[0].keyManagement.freshKey() diff --git a/node/src/main/kotlin/com/r3corda/node/internal/testing/Simulation.kt b/node/src/main/kotlin/com/r3corda/node/internal/testing/Simulation.kt index 72ce1bb6fe..17041ab943 100644 --- a/node/src/main/kotlin/com/r3corda/node/internal/testing/Simulation.kt +++ b/node/src/main/kotlin/com/r3corda/node/internal/testing/Simulation.kt @@ -115,9 +115,10 @@ abstract class Simulation(val networkSendManuallyPumped: Boolean, } return object : SimulatedNode(dir, cfg, network, networkMapAddr, advertisedServices, id, keyPair) { - override fun makeInterestRatesOracleService() { - super.makeInterestRatesOracleService() - interestRatesService.upload(javaClass.getResourceAsStream("example.rates.txt")) + override fun start(): MockNetwork.MockNode { + super.start() + getCustomService().upload(javaClass.getResourceAsStream("example.rates.txt")) + return this } } } diff --git a/node/src/main/kotlin/com/r3corda/node/services/FixingSessionInitiationHandler.kt b/node/src/main/kotlin/com/r3corda/node/services/FixingSessionInitiationHandler.kt deleted file mode 100644 index 102ded3db8..0000000000 --- a/node/src/main/kotlin/com/r3corda/node/services/FixingSessionInitiationHandler.kt +++ /dev/null @@ -1,22 +0,0 @@ -package com.r3corda.node.services - -import com.r3corda.core.serialization.deserialize -import com.r3corda.node.internal.AbstractNode -import com.r3corda.protocols.TwoPartyDealProtocol - -/** - * This is a temporary handler required for establishing random sessionIDs for the [Fixer] and [Floater] as part of - * running scheduled fixings for the [InterestRateSwap] contract. - * - * TODO: This will be replaced with the automatic sessionID / session setup work. - */ -object FixingSessionInitiationHandler { - - fun register(node: AbstractNode) { - node.net.addMessageHandler("${TwoPartyDealProtocol.FIX_INITIATE_TOPIC}.0") { msg, registration -> - val initiation = msg.data.deserialize() - val protocol = TwoPartyDealProtocol.Fixer(initiation) - node.smm.add("fixings", protocol) - } - } -} diff --git a/node/src/main/kotlin/com/r3corda/node/services/NotaryChangeService.kt b/node/src/main/kotlin/com/r3corda/node/services/NotaryChangeService.kt index 57e97582d6..3e7e4da058 100644 --- a/node/src/main/kotlin/com/r3corda/node/services/NotaryChangeService.kt +++ b/node/src/main/kotlin/com/r3corda/node/services/NotaryChangeService.kt @@ -1,30 +1,38 @@ package com.r3corda.node.services import com.r3corda.core.messaging.Ack -import com.r3corda.core.messaging.MessagingService -import com.r3corda.core.node.services.NetworkMapCache +import com.r3corda.core.node.CordaPluginRegistry import com.r3corda.node.services.api.AbstractNodeService -import com.r3corda.node.services.statemachine.StateMachineManager +import com.r3corda.node.services.api.ServiceHubInternal import com.r3corda.protocols.AbstractStateReplacementProtocol import com.r3corda.protocols.NotaryChangeProtocol -/** - * A service that monitors the network for requests for changing the notary of a state, - * and immediately runs the [NotaryChangeProtocol] if the auto-accept criteria are met. - */ -class NotaryChangeService(net: MessagingService, val smm: StateMachineManager, networkMapCache: NetworkMapCache) : AbstractNodeService(net, networkMapCache) { - init { - addMessageHandler(NotaryChangeProtocol.TOPIC, - { req: AbstractStateReplacementProtocol.Handshake -> handleChangeNotaryRequest(req) } - ) + +object NotaryChange { + class Plugin : CordaPluginRegistry { + override val webApis: List> = emptyList() + override val requiredProtocols: Map> = emptyMap() + override val servicePlugins: List> = listOf(Service::class.java) } - private fun handleChangeNotaryRequest(req: AbstractStateReplacementProtocol.Handshake): Ack { - val protocol = NotaryChangeProtocol.Acceptor( - req.replyToParty, - req.sessionID, - req.sessionIdForSend) - smm.add(NotaryChangeProtocol.TOPIC, protocol) - return Ack + /** + * A service that monitors the network for requests for changing the notary of a state, + * and immediately runs the [NotaryChangeProtocol] if the auto-accept criteria are met. + */ + class Service(val services: ServiceHubInternal) : AbstractNodeService(services.networkService, services.networkMapCache) { + init { + addMessageHandler(NotaryChangeProtocol.TOPIC, + { req: AbstractStateReplacementProtocol.Handshake -> handleChangeNotaryRequest(req) } + ) + } + + private fun handleChangeNotaryRequest(req: AbstractStateReplacementProtocol.Handshake): Ack { + val protocol = NotaryChangeProtocol.Acceptor( + req.replyToParty, + req.sessionID, + req.sessionIdForSend) + services.startProtocol(NotaryChangeProtocol.TOPIC, protocol) + return Ack + } } } diff --git a/node/src/main/kotlin/com/r3corda/node/services/clientapi/FixingSessionInitiation.kt b/node/src/main/kotlin/com/r3corda/node/services/clientapi/FixingSessionInitiation.kt new file mode 100644 index 0000000000..5eb4115cd8 --- /dev/null +++ b/node/src/main/kotlin/com/r3corda/node/services/clientapi/FixingSessionInitiation.kt @@ -0,0 +1,31 @@ +package com.r3corda.node.services.clientapi + +import com.r3corda.core.node.CordaPluginRegistry +import com.r3corda.core.serialization.deserialize +import com.r3corda.node.internal.AbstractNode +import com.r3corda.node.services.api.ServiceHubInternal +import com.r3corda.protocols.TwoPartyDealProtocol + +/** + * This is a temporary handler required for establishing random sessionIDs for the [Fixer] and [Floater] as part of + * running scheduled fixings for the [InterestRateSwap] contract. + * + * TODO: This will be replaced with the automatic sessionID / session setup work. + */ +object FixingSessionInitiation { + class Plugin: CordaPluginRegistry { + override val webApis: List> = emptyList() + override val requiredProtocols: Map> = emptyMap() + override val servicePlugins: List> = listOf(Service::class.java) + } + + class Service(services: ServiceHubInternal) { + init { + services.networkService.addMessageHandler("${TwoPartyDealProtocol.FIX_INITIATE_TOPIC}.0") { msg, registration -> + val initiation = msg.data.deserialize() + val protocol = TwoPartyDealProtocol.Fixer(initiation) + services.startProtocol("fixings", protocol) + } + } + } +} diff --git a/node/src/main/kotlin/com/r3corda/node/services/clientapi/NodeInterestRates.kt b/node/src/main/kotlin/com/r3corda/node/services/clientapi/NodeInterestRates.kt index e7712eb57f..04c176d4db 100644 --- a/node/src/main/kotlin/com/r3corda/node/services/clientapi/NodeInterestRates.kt +++ b/node/src/main/kotlin/com/r3corda/node/services/clientapi/NodeInterestRates.kt @@ -13,9 +13,9 @@ import com.r3corda.core.node.CordaPluginRegistry import com.r3corda.core.node.services.ServiceType import com.r3corda.core.protocols.ProtocolLogic import com.r3corda.core.utilities.ProgressTracker -import com.r3corda.node.internal.AbstractNode import com.r3corda.node.services.api.AbstractNodeService import com.r3corda.node.services.api.AcceptsFileUpload +import com.r3corda.node.services.api.ServiceHubInternal import com.r3corda.node.utilities.FiberBox import com.r3corda.protocols.RatesFixProtocol import com.r3corda.protocols.ServiceRequestMessage @@ -42,12 +42,23 @@ import javax.annotation.concurrent.ThreadSafe */ object NodeInterestRates { object Type : ServiceType("corda.interest_rates") + + /** + * Register the protocol that is used with the Fixing integration tests. + */ + class Plugin : CordaPluginRegistry { + override val webApis: List> = emptyList() + override val requiredProtocols: Map> = mapOf(Pair(TwoPartyDealProtocol.FixingRoleDecider::class.java.name, setOf(Duration::class.java.name, StateRef::class.java.name))) + override val servicePlugins: List> = listOf(NodeInterestRates.Service::class.java) + override val staticServeDirs: Map = emptyMap() + } + /** * The Service that wraps [Oracle] and handles messages/network interaction/request scrubbing. */ - class Service(node: AbstractNode) : AcceptsFileUpload, AbstractNodeService(node.services.networkService, node.services.networkMapCache) { - val ss = node.services.storageService - val oracle = Oracle(ss.myLegalIdentity, ss.myLegalIdentityKey, node.services.clock) + class Service(services: ServiceHubInternal) : AcceptsFileUpload, AbstractNodeService(services.networkService, services.networkMapCache) { + val ss = services.storageService + val oracle = Oracle(ss.myLegalIdentity, ss.myLegalIdentityKey, services.clock) private val logger = LoggerFactory.getLogger(Service::class.java) @@ -65,7 +76,7 @@ object NodeInterestRates { * Interest rates become available when they are uploaded via the web as per [DataUploadServlet], * if they haven't already been uploaded that way. */ - node.smm.add("fixing", FixQueryHandler(this, req as RatesFixProtocol.QueryRequest)) + services.startProtocol("fixing", FixQueryHandler(this, req as RatesFixProtocol.QueryRequest)) Unit } }, @@ -96,15 +107,6 @@ object NodeInterestRates { } } - /** - * Register the protocol that is used with the Fixing integration tests. - */ - class FixingServicePlugin : CordaPluginRegistry { - override val webApis: List> = emptyList() - override val requiredProtocols: Map> = mapOf(Pair(TwoPartyDealProtocol.FixingRoleDecider::class.java.name, setOf(Duration::class.java.name, StateRef::class.java.name))) - override val staticServeDirs: Map = emptyMap() - } - // File upload support override val dataTypePrefix = "interest-rates" override val acceptableFileExtensions = listOf(".rates", ".txt") diff --git a/node/src/main/kotlin/com/r3corda/node/services/persistence/DataVendingService.kt b/node/src/main/kotlin/com/r3corda/node/services/persistence/DataVendingService.kt index a3ef3f630e..8b4400a8bf 100644 --- a/node/src/main/kotlin/com/r3corda/node/services/persistence/DataVendingService.kt +++ b/node/src/main/kotlin/com/r3corda/node/services/persistence/DataVendingService.kt @@ -4,104 +4,110 @@ import com.r3corda.core.contracts.SignedTransaction import com.r3corda.core.crypto.Party import com.r3corda.core.failure import com.r3corda.core.messaging.MessagingService +import com.r3corda.core.node.CordaPluginRegistry import com.r3corda.core.serialization.serialize import com.r3corda.core.success import com.r3corda.core.utilities.loggerFor import com.r3corda.node.services.api.AbstractNodeService import com.r3corda.node.services.api.ServiceHubInternal -import com.r3corda.protocols.FetchAttachmentsProtocol -import com.r3corda.protocols.FetchDataProtocol -import com.r3corda.protocols.FetchTransactionsProtocol -import com.r3corda.protocols.PartyRequestMessage -import com.r3corda.protocols.ResolveTransactionsProtocol +import com.r3corda.protocols.* import java.io.InputStream import javax.annotation.concurrent.ThreadSafe -/** - * This class sets up network message handlers for requests from peers for data keyed by hash. It is a piece of simple - * glue that sits between the network layer and the database layer. - * - * Note that in our data model, to be able to name a thing by hash automatically gives the power to request it. There - * are no access control lists. If you want to keep some data private, then you must be careful who you give its name - * to, and trust that they will not pass the name onwards. If someone suspects some data might exist but does not have - * its name, then the 256-bit search space they'd have to cover makes it physically impossible to enumerate, and as - * such the hash of a piece of data can be seen as a type of password allowing access to it. - * - * Additionally, because nodes do not store invalid transactions, requesting such a transaction will always yield null. - */ -@ThreadSafe -// TODO: I don't like that this needs ServiceHubInternal, but passing in a state machine breaks MockServices because +object DataVending { + + class Plugin : CordaPluginRegistry { + override val webApis: List> = emptyList() + override val requiredProtocols: Map> = emptyMap() + override val servicePlugins: List> = listOf(Service::class.java) + } + + /** + * This class sets up network message handlers for requests from peers for data keyed by hash. It is a piece of simple + * glue that sits between the network layer and the database layer. + * + * Note that in our data model, to be able to name a thing by hash automatically gives the power to request it. There + * are no access control lists. If you want to keep some data private, then you must be careful who you give its name + * to, and trust that they will not pass the name onwards. If someone suspects some data might exist but does not have + * its name, then the 256-bit search space they'd have to cover makes it physically impossible to enumerate, and as + * such the hash of a piece of data can be seen as a type of password allowing access to it. + * + * Additionally, because nodes do not store invalid transactions, requesting such a transaction will always yield null. + */ + @ThreadSafe + // TODO: I don't like that this needs ServiceHubInternal, but passing in a state machine breaks MockServices because // the state machine isn't set when this is constructed. [NodeSchedulerService] has the same problem, and both // should be fixed at the same time. -class DataVendingService(net: MessagingService, private val services: ServiceHubInternal) : AbstractNodeService(net, services.networkMapCache) { - companion object { - val logger = loggerFor() + class Service(net: MessagingService, private val services: ServiceHubInternal) : AbstractNodeService(net, services.networkMapCache) { + companion object { + val logger = loggerFor() - /** Topic for messages notifying a node of a new transaction */ - val NOTIFY_TX_PROTOCOL_TOPIC = "platform.wallet.notify_tx" - } - - val storage = services.storageService - - data class NotifyTxRequestMessage(val tx: SignedTransaction, override val replyToParty: Party, override val sessionID: Long) : PartyRequestMessage - data class NotifyTxResponseMessage(val accepted: Boolean) - - init { - addMessageHandler(FetchTransactionsProtocol.TOPIC, - { req: FetchDataProtocol.Request -> handleTXRequest(req) }, - { message, e -> logger.error("Failure processing data vending request.", e) } - ) - addMessageHandler(FetchAttachmentsProtocol.TOPIC, - { req: FetchDataProtocol.Request -> handleAttachmentRequest(req) }, - { message, e -> logger.error("Failure processing data vending request.", e) } - ) - addMessageHandler(NOTIFY_TX_PROTOCOL_TOPIC, - { req: NotifyTxRequestMessage -> handleTXNotification(req) }, - { message, e -> logger.error("Failure processing data vending request.", e) } - ) - } - - private fun handleTXNotification(req: NotifyTxRequestMessage): Unit { - // TODO: We should have a whitelist of contracts we're willing to accept at all, and reject if the transaction - // includes us in any outside that list. Potentially just if it includes any outside that list at all. - - // TODO: Do we want to be able to reject specific transactions on more complex rules, for example reject incoming - // cash without from unknown parties? - - services.startProtocol(NOTIFY_TX_PROTOCOL_TOPIC, ResolveTransactionsProtocol(req.tx, req.replyToParty)) - .success { - services.recordTransactions(req.tx) - val resp = NotifyTxResponseMessage(true) - val msg = net.createMessage(NOTIFY_TX_PROTOCOL_TOPIC + "." + req.sessionID, resp.serialize().bits) - net.send(msg, req.getReplyTo(services.networkMapCache)) - }.failure { - val resp = NotifyTxResponseMessage(false) - val msg = net.createMessage(NOTIFY_TX_PROTOCOL_TOPIC + "." + req.sessionID, resp.serialize().bits) - net.send(msg, req.getReplyTo(services.networkMapCache)) - } - } - - private fun handleTXRequest(req: FetchDataProtocol.Request): List { - require(req.hashes.isNotEmpty()) - return req.hashes.map { - val tx = storage.validatedTransactions.getTransaction(it) - if (tx == null) - logger.info("Got request for unknown tx $it") - tx + /** Topic for messages notifying a node of a new transaction */ + val NOTIFY_TX_PROTOCOL_TOPIC = "platform.wallet.notify_tx" } - } - private fun handleAttachmentRequest(req: FetchDataProtocol.Request): List { - // TODO: Use Artemis message streaming support here, called "large messages". This avoids the need to buffer. - require(req.hashes.isNotEmpty()) - return req.hashes.map { - val jar: InputStream? = storage.attachments.openAttachment(it)?.open() - if (jar == null) { - logger.info("Got request for unknown attachment $it") - null - } else { - jar.readBytes() + val storage = services.storageService + + data class NotifyTxRequestMessage(val tx: SignedTransaction, override val replyToParty: Party, override val sessionID: Long) : PartyRequestMessage + data class NotifyTxResponseMessage(val accepted: Boolean) + + init { + addMessageHandler(FetchTransactionsProtocol.TOPIC, + { req: FetchDataProtocol.Request -> handleTXRequest(req) }, + { message, e -> logger.error("Failure processing data vending request.", e) } + ) + addMessageHandler(FetchAttachmentsProtocol.TOPIC, + { req: FetchDataProtocol.Request -> handleAttachmentRequest(req) }, + { message, e -> logger.error("Failure processing data vending request.", e) } + ) + addMessageHandler(NOTIFY_TX_PROTOCOL_TOPIC, + { req: NotifyTxRequestMessage -> handleTXNotification(req) }, + { message, e -> logger.error("Failure processing data vending request.", e) } + ) + } + + private fun handleTXNotification(req: NotifyTxRequestMessage): Unit { + // TODO: We should have a whitelist of contracts we're willing to accept at all, and reject if the transaction + // includes us in any outside that list. Potentially just if it includes any outside that list at all. + + // TODO: Do we want to be able to reject specific transactions on more complex rules, for example reject incoming + // cash without from unknown parties? + + services.startProtocol(NOTIFY_TX_PROTOCOL_TOPIC, ResolveTransactionsProtocol(req.tx, req.replyToParty)) + .success { + services.recordTransactions(req.tx) + val resp = NotifyTxResponseMessage(true) + val msg = net.createMessage(NOTIFY_TX_PROTOCOL_TOPIC + "." + req.sessionID, resp.serialize().bits) + net.send(msg, req.getReplyTo(services.networkMapCache)) + }.failure { + val resp = NotifyTxResponseMessage(false) + val msg = net.createMessage(NOTIFY_TX_PROTOCOL_TOPIC + "." + req.sessionID, resp.serialize().bits) + net.send(msg, req.getReplyTo(services.networkMapCache)) + } + } + + private fun handleTXRequest(req: FetchDataProtocol.Request): List { + require(req.hashes.isNotEmpty()) + return req.hashes.map { + val tx = storage.validatedTransactions.getTransaction(it) + if (tx == null) + logger.info("Got request for unknown tx $it") + tx + } + } + + private fun handleAttachmentRequest(req: FetchDataProtocol.Request): List { + // TODO: Use Artemis message streaming support here, called "large messages". This avoids the need to buffer. + require(req.hashes.isNotEmpty()) + return req.hashes.map { + val jar: InputStream? = storage.attachments.openAttachment(it)?.open() + if (jar == null) { + logger.info("Got request for unknown attachment $it") + null + } else { + jar.readBytes() + } } } } -} +} \ No newline at end of file diff --git a/node/src/main/resources/META-INF/services/com.r3corda.core.node.CordaPluginRegistry b/node/src/main/resources/META-INF/services/com.r3corda.core.node.CordaPluginRegistry index 6c27482af8..6ddd29d16d 100644 --- a/node/src/main/resources/META-INF/services/com.r3corda.core.node.CordaPluginRegistry +++ b/node/src/main/resources/META-INF/services/com.r3corda.core.node.CordaPluginRegistry @@ -1,2 +1,5 @@ # Register a ServiceLoader service extending from com.r3corda.node.CordaPluginRegistry -com.r3corda.node.services.clientapi.NodeInterestRates$Service$FixingServicePlugin \ No newline at end of file +com.r3corda.node.services.clientapi.FixingSessionInitiation$Plugin +com.r3corda.node.services.clientapi.NodeInterestRates$Plugin +com.r3corda.node.services.NotaryChange$Plugin +com.r3corda.node.services.persistence.DataVending$Plugin \ No newline at end of file diff --git a/node/src/test/kotlin/com/r3corda/node/services/MockServices.kt b/node/src/test/kotlin/com/r3corda/node/services/MockServices.kt index 05b6f2d5fb..4bf957f5d4 100644 --- a/node/src/test/kotlin/com/r3corda/node/services/MockServices.kt +++ b/node/src/test/kotlin/com/r3corda/node/services/MockServices.kt @@ -14,7 +14,7 @@ import com.r3corda.node.services.api.MonitoringService import com.r3corda.node.services.api.ServiceHubInternal import com.r3corda.node.services.network.MockNetworkMapCache import com.r3corda.node.services.network.NetworkMapService -import com.r3corda.node.services.persistence.DataVendingService +import com.r3corda.node.services.persistence.DataVending import com.r3corda.node.services.statemachine.StateMachineManager import com.r3corda.node.services.wallet.NodeWalletService import java.time.Clock @@ -68,7 +68,7 @@ open class MockServices( if (net != null && storage != null) { // Creating this class is sufficient, we don't have to store it anywhere, because it registers a listener // on the networking service, so that will keep it from being collected. - DataVendingService(net, this) + DataVending.Service(this) } } } diff --git a/node/src/test/kotlin/com/r3corda/node/services/NodeInterestRatesTest.kt b/node/src/test/kotlin/com/r3corda/node/services/NodeInterestRatesTest.kt index 60a9b09066..c38f2540cd 100644 --- a/node/src/test/kotlin/com/r3corda/node/services/NodeInterestRatesTest.kt +++ b/node/src/test/kotlin/com/r3corda/node/services/NodeInterestRatesTest.kt @@ -105,7 +105,7 @@ class NodeInterestRatesTest { fun network() { val net = MockNetwork() val (n1, n2) = net.createTwoNodes() - n2.interestRatesService.oracle.knownFixes = TEST_DATA + n2.getCustomService().oracle.knownFixes = TEST_DATA val tx = TransactionType.General.Builder() val fixOf = NodeInterestRates.parseFixOf("LIBOR 2016-03-16 1M") diff --git a/src/main/kotlin/com/r3corda/demos/IRSDemo.kt b/src/main/kotlin/com/r3corda/demos/IRSDemo.kt index 88dfacedb5..81c8bc8add 100644 --- a/src/main/kotlin/com/r3corda/demos/IRSDemo.kt +++ b/src/main/kotlin/com/r3corda/demos/IRSDemo.kt @@ -17,7 +17,6 @@ import com.r3corda.demos.protocols.UpdateBusinessDayProtocol import com.r3corda.node.internal.AbstractNode import com.r3corda.node.internal.Node import com.r3corda.node.internal.testing.MockNetwork -import com.r3corda.node.services.FixingSessionInitiationHandler import com.r3corda.node.services.clientapi.NodeInterestRates import com.r3corda.node.services.config.NodeConfiguration import com.r3corda.node.services.config.NodeConfigurationFromConfig @@ -265,6 +264,7 @@ class IRSDemoPluginRegistry : CordaPluginRegistry { Pair(AutoOfferProtocol.Requester::class.java.name, setOf(InterestRateSwap.State::class.java.name)), Pair(UpdateBusinessDayProtocol.Broadcast::class.java.name, setOf(java.time.LocalDate::class.java.name)), Pair(ExitServerProtocol.Broadcast::class.java.name, setOf(kotlin.Int::class.java.name))) + override val servicePlugins: List> = emptyList() } private class NotSetupException: Throwable { @@ -332,11 +332,6 @@ private fun runNode(cliParams: CliParams.RunNode): Int { val networkMap = createRecipient(cliParams.mapAddress) val node = startNode(cliParams, networkMap) - // Register handlers for the demo - AutoOfferProtocol.Handler.register(node) - UpdateBusinessDayProtocol.Handler.register(node) - ExitServerProtocol.Handler.register(node) - FixingSessionInitiationHandler.register(node) if (cliParams.uploadRates) { runUploadRates(cliParams.apiAddress) diff --git a/src/main/kotlin/com/r3corda/demos/protocols/AutoOfferProtocol.kt b/src/main/kotlin/com/r3corda/demos/protocols/AutoOfferProtocol.kt index 50d96c23ce..3fa7362840 100644 --- a/src/main/kotlin/com/r3corda/demos/protocols/AutoOfferProtocol.kt +++ b/src/main/kotlin/com/r3corda/demos/protocols/AutoOfferProtocol.kt @@ -6,11 +6,12 @@ import com.google.common.util.concurrent.Futures import com.r3corda.core.contracts.DealState import com.r3corda.core.contracts.SignedTransaction import com.r3corda.core.crypto.Party +import com.r3corda.core.node.CordaPluginRegistry import com.r3corda.core.protocols.ProtocolLogic import com.r3corda.core.random63BitValue import com.r3corda.core.serialization.deserialize import com.r3corda.core.utilities.ProgressTracker -import com.r3corda.node.internal.Node +import com.r3corda.node.services.api.ServiceHubInternal import com.r3corda.protocols.TwoPartyDealProtocol /** @@ -27,7 +28,14 @@ object AutoOfferProtocol { val notary: Party, val otherSessionID: Long, val dealBeingOffered: DealState) - object Handler { + class Plugin: CordaPluginRegistry { + override val webApis: List> = emptyList() + override val requiredProtocols: Map> = emptyMap() + override val servicePlugins: List> = listOf(Service::class.java) + } + + + class Service(services: ServiceHubInternal) { object RECEIVED : ProgressTracker.Step("Received offer") object DEALING : ProgressTracker.Step("Starting the deal protocol") { @@ -46,16 +54,16 @@ object AutoOfferProtocol { } } - fun register(node: Node) { - node.net.addMessageHandler("$TOPIC.0") { msg, registration -> + init { + services.networkService.addMessageHandler("$TOPIC.0") { msg, registration -> val progressTracker = tracker() progressTracker.currentStep = RECEIVED val autoOfferMessage = msg.data.deserialize() // Put the deal onto the ledger progressTracker.currentStep = DEALING val seller = TwoPartyDealProtocol.Instigator(autoOfferMessage.otherSide, autoOfferMessage.notary, - autoOfferMessage.dealBeingOffered, node.services.keyManagementService.freshKey(), autoOfferMessage.otherSessionID, progressTracker.getChildProgressTracker(DEALING)!!) - val future = node.smm.add("${TwoPartyDealProtocol.DEAL_TOPIC}.seller", seller) + autoOfferMessage.dealBeingOffered, services.keyManagementService.freshKey(), autoOfferMessage.otherSessionID, progressTracker.getChildProgressTracker(DEALING)!!) + val future = services.startProtocol("${TwoPartyDealProtocol.DEAL_TOPIC}.seller", seller) // This is required because we are doing child progress outside of a subprotocol. In future, we should just wrap things like this in a protocol to avoid it Futures.addCallback(future, Callback() { seller.progressTracker.currentStep = ProgressTracker.DONE diff --git a/src/main/kotlin/com/r3corda/demos/protocols/ExitServerProtocol.kt b/src/main/kotlin/com/r3corda/demos/protocols/ExitServerProtocol.kt index da1f54bb49..56a2c27b5f 100644 --- a/src/main/kotlin/com/r3corda/demos/protocols/ExitServerProtocol.kt +++ b/src/main/kotlin/com/r3corda/demos/protocols/ExitServerProtocol.kt @@ -2,10 +2,11 @@ package com.r3corda.demos.protocols import co.paralleluniverse.fibers.Suspendable import co.paralleluniverse.strands.Strand +import com.r3corda.core.node.CordaPluginRegistry import com.r3corda.core.node.NodeInfo import com.r3corda.core.protocols.ProtocolLogic import com.r3corda.core.serialization.deserialize -import com.r3corda.node.internal.Node +import com.r3corda.node.services.api.ServiceHubInternal import com.r3corda.node.services.network.MockNetworkMapCache import java.util.concurrent.TimeUnit @@ -19,10 +20,16 @@ object ExitServerProtocol { data class ExitMessage(val exitCode: Int) - object Handler { + class Plugin: CordaPluginRegistry { + override val webApis: List> = emptyList() + override val requiredProtocols: Map> = emptyMap() + override val servicePlugins: List> = listOf(Service::class.java) + } - fun register(node: Node) { - node.net.addMessageHandler("$TOPIC.0") { msg, registration -> + class Service(services: ServiceHubInternal) { + + init { + services.networkService.addMessageHandler("$TOPIC.0") { msg, registration -> // Just to validate we got the message if (enabled) { val message = msg.data.deserialize() diff --git a/src/main/kotlin/com/r3corda/demos/protocols/UpdateBusinessDayProtocol.kt b/src/main/kotlin/com/r3corda/demos/protocols/UpdateBusinessDayProtocol.kt index c4bd14e67e..2b492ed2b0 100644 --- a/src/main/kotlin/com/r3corda/demos/protocols/UpdateBusinessDayProtocol.kt +++ b/src/main/kotlin/com/r3corda/demos/protocols/UpdateBusinessDayProtocol.kt @@ -1,12 +1,14 @@ package com.r3corda.demos.protocols import co.paralleluniverse.fibers.Suspendable +import com.r3corda.core.node.CordaPluginRegistry import com.r3corda.core.node.NodeInfo import com.r3corda.core.protocols.ProtocolLogic import com.r3corda.core.serialization.deserialize import com.r3corda.core.utilities.ProgressTracker import com.r3corda.demos.DemoClock import com.r3corda.node.internal.Node +import com.r3corda.node.services.api.ServiceHubInternal import com.r3corda.node.services.network.MockNetworkMapCache import java.time.LocalDate @@ -19,12 +21,18 @@ object UpdateBusinessDayProtocol { data class UpdateBusinessDayMessage(val date: LocalDate) - object Handler { + class Plugin: CordaPluginRegistry { + override val webApis: List> = emptyList() + override val requiredProtocols: Map> = emptyMap() + override val servicePlugins: List> = listOf(Service::class.java) + } - fun register(node: Node) { - node.net.addMessageHandler("${TOPIC}.0") { msg, registration -> + class Service(services: ServiceHubInternal) { + + init { + services.networkService.addMessageHandler("${TOPIC}.0") { msg, registration -> val updateBusinessDayMessage = msg.data.deserialize() - (node.services.clock as DemoClock).updateDate(updateBusinessDayMessage.date) + (services.clock as DemoClock).updateDate(updateBusinessDayMessage.date) } } } diff --git a/src/main/resources/META-INF/services/com.r3corda.core.node.CordaPluginRegistry b/src/main/resources/META-INF/services/com.r3corda.core.node.CordaPluginRegistry index 279300f9f9..33d9c399d8 100644 --- a/src/main/resources/META-INF/services/com.r3corda.core.node.CordaPluginRegistry +++ b/src/main/resources/META-INF/services/com.r3corda.core.node.CordaPluginRegistry @@ -1,2 +1,5 @@ # Register a ServiceLoader service extending from com.r3corda.node.CordaPluginRegistry -com.r3corda.demos.IRSDemoPluginRegistry \ No newline at end of file +com.r3corda.demos.IRSDemoPluginRegistry +com.r3corda.demos.protocols.AutoOfferProtocol$Plugin +com.r3corda.demos.protocols.ExitServerProtocol$Plugin +com.r3corda.demos.protocols.UpdateBusinessDayProtocol$Plugin \ No newline at end of file From 10f68b22f998e8fc25ddae155b596a06eab3b667 Mon Sep 17 00:00:00 2001 From: Matthew Nesbit Date: Thu, 28 Jul 2016 13:18:52 +0100 Subject: [PATCH 2/5] Minor fix after rebase --- .../services/persistence/DataVendingService.kt | 2 +- .../persistence/DataVendingServiceTests.kt | 14 +++++--------- 2 files changed, 6 insertions(+), 10 deletions(-) diff --git a/node/src/main/kotlin/com/r3corda/node/services/persistence/DataVendingService.kt b/node/src/main/kotlin/com/r3corda/node/services/persistence/DataVendingService.kt index 8b4400a8bf..427004d136 100644 --- a/node/src/main/kotlin/com/r3corda/node/services/persistence/DataVendingService.kt +++ b/node/src/main/kotlin/com/r3corda/node/services/persistence/DataVendingService.kt @@ -38,7 +38,7 @@ object DataVending { // TODO: I don't like that this needs ServiceHubInternal, but passing in a state machine breaks MockServices because // the state machine isn't set when this is constructed. [NodeSchedulerService] has the same problem, and both // should be fixed at the same time. - class Service(net: MessagingService, private val services: ServiceHubInternal) : AbstractNodeService(net, services.networkMapCache) { + class Service(val services: ServiceHubInternal) : AbstractNodeService(services.networkService, services.networkMapCache) { companion object { val logger = loggerFor() diff --git a/node/src/test/kotlin/com/r3corda/node/services/persistence/DataVendingServiceTests.kt b/node/src/test/kotlin/com/r3corda/node/services/persistence/DataVendingServiceTests.kt index 61c45fd9ab..4a2a24fb28 100644 --- a/node/src/test/kotlin/com/r3corda/node/services/persistence/DataVendingServiceTests.kt +++ b/node/src/test/kotlin/com/r3corda/node/services/persistence/DataVendingServiceTests.kt @@ -2,23 +2,19 @@ package com.r3corda.node.services.persistence import co.paralleluniverse.fibers.Suspendable import com.r3corda.contracts.asset.Cash -import com.r3corda.contracts.asset.DUMMY_CASH_ISSUER import com.r3corda.core.contracts.* import com.r3corda.core.node.NodeInfo import com.r3corda.core.protocols.ProtocolLogic import com.r3corda.core.random63BitValue import com.r3corda.core.testing.DUMMY_NOTARY import com.r3corda.core.testing.MEGA_CORP -import com.r3corda.core.testing.MEGA_CORP_KEY import com.r3corda.core.utilities.BriefLogFormatter import com.r3corda.node.internal.testing.MockNetwork import org.junit.Before import org.junit.Test import java.util.concurrent.TimeUnit -import javax.annotation.Signed import kotlin.test.assertEquals import kotlin.test.assertFalse -import kotlin.test.assertNotNull import kotlin.test.assertTrue /** @@ -38,12 +34,12 @@ class DataVendingServiceTests { class NotifyPSM(val server: NodeInfo, val tx: SignedTransaction) : ProtocolLogic() { - override val topic: String get() = DataVendingService.NOTIFY_TX_PROTOCOL_TOPIC + override val topic: String get() = DataVending.Service.NOTIFY_TX_PROTOCOL_TOPIC @Suspendable override fun call(): Boolean { val sessionID = random63BitValue() - val req = DataVendingService.NotifyTxRequestMessage(tx, serviceHub.storageService.myLegalIdentity, sessionID) - return sendAndReceive(server.identity, 0, sessionID, req).validate { it.accepted } + val req = DataVending.Service.NotifyTxRequestMessage(tx, serviceHub.storageService.myLegalIdentity, sessionID) + return sendAndReceive(server.identity, 0, sessionID, req).validate { it.accepted } } } @@ -62,7 +58,7 @@ class DataVendingServiceTests { ptx.signWith(registerNode.services.storageService.myLegalIdentityKey) val tx = ptx.toSignedTransaction() assertEquals(0, walletServiceNode.services.walletService.currentWallet.states.size) - val notifyPsm = registerNode.smm.add(DataVendingService.NOTIFY_TX_PROTOCOL_TOPIC, NotifyPSM(walletServiceNode.info, tx)) + val notifyPsm = registerNode.smm.add(DataVending.Service.NOTIFY_TX_PROTOCOL_TOPIC, NotifyPSM(walletServiceNode.info, tx)) // Check it was accepted network.runNetwork() @@ -93,7 +89,7 @@ class DataVendingServiceTests { ptx.signWith(registerNode.services.storageService.myLegalIdentityKey) val tx = ptx.toSignedTransaction(false) assertEquals(0, walletServiceNode.services.walletService.currentWallet.states.size) - val notifyPsm = registerNode.smm.add(DataVendingService.NOTIFY_TX_PROTOCOL_TOPIC, NotifyPSM(walletServiceNode.info, tx)) + val notifyPsm = registerNode.smm.add(DataVending.Service.NOTIFY_TX_PROTOCOL_TOPIC, NotifyPSM(walletServiceNode.info, tx)) // Check it was accepted network.runNetwork() From c57f265ccee0f211ec5d541c59b48cb76edf6505 Mon Sep 17 00:00:00 2001 From: Matthew Nesbit Date: Thu, 28 Jul 2016 13:55:06 +0100 Subject: [PATCH 3/5] Fixup plugins to include static content property after rebase inclusive of that work --- node/src/main/kotlin/com/r3corda/node/internal/AbstractNode.kt | 2 -- .../kotlin/com/r3corda/node/services/NotaryChangeService.kt | 1 + .../r3corda/node/services/clientapi/FixingSessionInitiation.kt | 1 + .../com/r3corda/node/services/clientapi/NodeInterestRates.kt | 2 +- .../com/r3corda/node/services/persistence/DataVendingService.kt | 1 + .../kotlin/com/r3corda/demos/protocols/AutoOfferProtocol.kt | 1 + .../kotlin/com/r3corda/demos/protocols/ExitServerProtocol.kt | 1 + .../com/r3corda/demos/protocols/UpdateBusinessDayProtocol.kt | 1 + 8 files changed, 7 insertions(+), 3 deletions(-) diff --git a/node/src/main/kotlin/com/r3corda/node/internal/AbstractNode.kt b/node/src/main/kotlin/com/r3corda/node/internal/AbstractNode.kt index f4bead4bcf..4f52da9950 100644 --- a/node/src/main/kotlin/com/r3corda/node/internal/AbstractNode.kt +++ b/node/src/main/kotlin/com/r3corda/node/internal/AbstractNode.kt @@ -21,9 +21,7 @@ import com.r3corda.core.serialization.SingletonSerializeAsToken import com.r3corda.core.serialization.deserialize import com.r3corda.core.serialization.serialize import com.r3corda.node.api.APIServer -import com.r3corda.node.services.NotaryChangeService import com.r3corda.node.services.api.* -import com.r3corda.node.services.clientapi.NodeInterestRates import com.r3corda.node.services.config.NodeConfiguration import com.r3corda.node.services.events.NodeSchedulerService import com.r3corda.node.services.events.ScheduledActivityObserver diff --git a/node/src/main/kotlin/com/r3corda/node/services/NotaryChangeService.kt b/node/src/main/kotlin/com/r3corda/node/services/NotaryChangeService.kt index 3e7e4da058..1a998b0c14 100644 --- a/node/src/main/kotlin/com/r3corda/node/services/NotaryChangeService.kt +++ b/node/src/main/kotlin/com/r3corda/node/services/NotaryChangeService.kt @@ -12,6 +12,7 @@ object NotaryChange { class Plugin : CordaPluginRegistry { override val webApis: List> = emptyList() override val requiredProtocols: Map> = emptyMap() + override val staticServeDirs: Map = emptyMap() override val servicePlugins: List> = listOf(Service::class.java) } diff --git a/node/src/main/kotlin/com/r3corda/node/services/clientapi/FixingSessionInitiation.kt b/node/src/main/kotlin/com/r3corda/node/services/clientapi/FixingSessionInitiation.kt index 5eb4115cd8..568ab4565f 100644 --- a/node/src/main/kotlin/com/r3corda/node/services/clientapi/FixingSessionInitiation.kt +++ b/node/src/main/kotlin/com/r3corda/node/services/clientapi/FixingSessionInitiation.kt @@ -15,6 +15,7 @@ import com.r3corda.protocols.TwoPartyDealProtocol object FixingSessionInitiation { class Plugin: CordaPluginRegistry { override val webApis: List> = emptyList() + override val staticServeDirs: Map = emptyMap() override val requiredProtocols: Map> = emptyMap() override val servicePlugins: List> = listOf(Service::class.java) } diff --git a/node/src/main/kotlin/com/r3corda/node/services/clientapi/NodeInterestRates.kt b/node/src/main/kotlin/com/r3corda/node/services/clientapi/NodeInterestRates.kt index 04c176d4db..649e1b8258 100644 --- a/node/src/main/kotlin/com/r3corda/node/services/clientapi/NodeInterestRates.kt +++ b/node/src/main/kotlin/com/r3corda/node/services/clientapi/NodeInterestRates.kt @@ -48,9 +48,9 @@ object NodeInterestRates { */ class Plugin : CordaPluginRegistry { override val webApis: List> = emptyList() + override val staticServeDirs: Map = emptyMap() override val requiredProtocols: Map> = mapOf(Pair(TwoPartyDealProtocol.FixingRoleDecider::class.java.name, setOf(Duration::class.java.name, StateRef::class.java.name))) override val servicePlugins: List> = listOf(NodeInterestRates.Service::class.java) - override val staticServeDirs: Map = emptyMap() } /** diff --git a/node/src/main/kotlin/com/r3corda/node/services/persistence/DataVendingService.kt b/node/src/main/kotlin/com/r3corda/node/services/persistence/DataVendingService.kt index 427004d136..7d680e788b 100644 --- a/node/src/main/kotlin/com/r3corda/node/services/persistence/DataVendingService.kt +++ b/node/src/main/kotlin/com/r3corda/node/services/persistence/DataVendingService.kt @@ -18,6 +18,7 @@ object DataVending { class Plugin : CordaPluginRegistry { override val webApis: List> = emptyList() + override val staticServeDirs: Map = emptyMap() override val requiredProtocols: Map> = emptyMap() override val servicePlugins: List> = listOf(Service::class.java) } diff --git a/src/main/kotlin/com/r3corda/demos/protocols/AutoOfferProtocol.kt b/src/main/kotlin/com/r3corda/demos/protocols/AutoOfferProtocol.kt index 3fa7362840..dbb004b1f3 100644 --- a/src/main/kotlin/com/r3corda/demos/protocols/AutoOfferProtocol.kt +++ b/src/main/kotlin/com/r3corda/demos/protocols/AutoOfferProtocol.kt @@ -30,6 +30,7 @@ object AutoOfferProtocol { class Plugin: CordaPluginRegistry { override val webApis: List> = emptyList() + override val staticServeDirs: Map = emptyMap() override val requiredProtocols: Map> = emptyMap() override val servicePlugins: List> = listOf(Service::class.java) } diff --git a/src/main/kotlin/com/r3corda/demos/protocols/ExitServerProtocol.kt b/src/main/kotlin/com/r3corda/demos/protocols/ExitServerProtocol.kt index 56a2c27b5f..4585f9c629 100644 --- a/src/main/kotlin/com/r3corda/demos/protocols/ExitServerProtocol.kt +++ b/src/main/kotlin/com/r3corda/demos/protocols/ExitServerProtocol.kt @@ -22,6 +22,7 @@ object ExitServerProtocol { class Plugin: CordaPluginRegistry { override val webApis: List> = emptyList() + override val staticServeDirs: Map = emptyMap() override val requiredProtocols: Map> = emptyMap() override val servicePlugins: List> = listOf(Service::class.java) } diff --git a/src/main/kotlin/com/r3corda/demos/protocols/UpdateBusinessDayProtocol.kt b/src/main/kotlin/com/r3corda/demos/protocols/UpdateBusinessDayProtocol.kt index 2b492ed2b0..df1f69e3db 100644 --- a/src/main/kotlin/com/r3corda/demos/protocols/UpdateBusinessDayProtocol.kt +++ b/src/main/kotlin/com/r3corda/demos/protocols/UpdateBusinessDayProtocol.kt @@ -23,6 +23,7 @@ object UpdateBusinessDayProtocol { class Plugin: CordaPluginRegistry { override val webApis: List> = emptyList() + override val staticServeDirs: Map = emptyMap() override val requiredProtocols: Map> = emptyMap() override val servicePlugins: List> = listOf(Service::class.java) } From 601b2faf5f09a9797b025b79be665ba2497f9732 Mon Sep 17 00:00:00 2001 From: Matthew Nesbit Date: Thu, 28 Jul 2016 16:22:35 +0100 Subject: [PATCH 4/5] Incorporate comments from PR --- .../com/r3corda/node/internal/AbstractNode.kt | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/node/src/main/kotlin/com/r3corda/node/internal/AbstractNode.kt b/node/src/main/kotlin/com/r3corda/node/internal/AbstractNode.kt index 4f52da9950..5bc82d82de 100644 --- a/node/src/main/kotlin/com/r3corda/node/internal/AbstractNode.kt +++ b/node/src/main/kotlin/com/r3corda/node/internal/AbstractNode.kt @@ -123,10 +123,10 @@ abstract class AbstractNode(val dir: Path, val configuration: NodeConfiguration, lateinit var api: APIServer lateinit var scheduler: SchedulerService lateinit var protocolLogicFactory: ProtocolLogicRefFactory - var customServices: List = emptyList() - inline fun getCustomService() : T { - return customServices.single{ x-> x is T } as T - } + val customServices: ArrayList = ArrayList() + + /** Locates and returns a service of the given type if loaded, or throws an exception if not found. */ + inline fun findService() = customServices.filterIsInstance().single() var isPreviousCheckpointsPresent = false private set @@ -168,8 +168,8 @@ abstract class AbstractNode(val dir: Path, val configuration: NodeConfiguration, val tokenizableServices = mutableListOf(storage, net, wallet, keyManagement, identity, platformClock, scheduler) - buildPluginServices(tokenizableServices) - + customServices.clear() + customServices.addAll(buildPluginServices(tokenizableServices)) smm = StateMachineManager(services, listOf(tokenizableServices), @@ -204,7 +204,7 @@ abstract class AbstractNode(val dir: Path, val configuration: NodeConfiguration, return ProtocolLogicRefFactory(protocolWhitelist) } - private fun buildPluginServices(tokenizableServices: MutableList) { + private fun buildPluginServices(tokenizableServices: MutableList): List { val pluginServices = pluginRegistries.flatMap { x -> x.servicePlugins } val serviceList = mutableListOf() for (serviceClass in pluginServices) { @@ -215,7 +215,7 @@ abstract class AbstractNode(val dir: Path, val configuration: NodeConfiguration, _servicesThatAcceptUploads += service } } - customServices = serviceList + return serviceList } From a462bb1d6ad4f16b329b24b99e55207b7f9f1bd0 Mon Sep 17 00:00:00 2001 From: Matthew Nesbit Date: Thu, 28 Jul 2016 16:30:53 +0100 Subject: [PATCH 5/5] Incorporate comments from PR --- .../com/r3corda/core/node/CordaPluginRegistry.kt | 14 +++++++------- .../r3corda/node/internal/testing/Simulation.kt | 2 +- .../r3corda/node/services/NotaryChangeService.kt | 5 +---- .../services/clientapi/FixingSessionInitiation.kt | 5 +---- .../node/services/clientapi/NodeInterestRates.kt | 4 +--- .../services/persistence/DataVendingService.kt | 5 +---- .../r3corda/node/services/NodeInterestRatesTest.kt | 2 +- src/main/kotlin/com/r3corda/demos/IRSDemo.kt | 3 +-- .../r3corda/demos/protocols/AutoOfferProtocol.kt | 5 +---- .../r3corda/demos/protocols/ExitServerProtocol.kt | 5 +---- .../demos/protocols/UpdateBusinessDayProtocol.kt | 5 +---- 11 files changed, 17 insertions(+), 38 deletions(-) diff --git a/core/src/main/kotlin/com/r3corda/core/node/CordaPluginRegistry.kt b/core/src/main/kotlin/com/r3corda/core/node/CordaPluginRegistry.kt index 09cd1f3ad1..de3f97f022 100644 --- a/core/src/main/kotlin/com/r3corda/core/node/CordaPluginRegistry.kt +++ b/core/src/main/kotlin/com/r3corda/core/node/CordaPluginRegistry.kt @@ -4,20 +4,20 @@ package com.r3corda.core.node * Implement this interface on a class advertised in a META-INF/services/com.r3corda.core.node.CordaPluginRegistry file * to extend a Corda node with additional application services. */ -interface CordaPluginRegistry { +abstract class CordaPluginRegistry { /** * List of JAX-RS classes inside the contract jar. They are expected to have a single parameter constructor that takes a ServiceHub as input. - * These are listed as Class<*>, because they will be instantiated inside an AttachmentClassLoader so that subsequent protocols, contracts, etc - * will be running in the appropriate isolated context. + * These are listed as Class<*>, because in the future they will be instantiated inside a ClassLoader so that + * Cordapp code can be loaded dynamically. */ - val webApis: List> + open val webApis: List> = 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("").toExternalForm() */ - val staticServeDirs: Map + open val staticServeDirs: Map = emptyMap() /** * A Map with an entry for each consumed protocol used by the webAPIs. @@ -26,7 +26,7 @@ interface CordaPluginRegistry { * Standard java.lang.* and kotlin.* types do not need to be included explicitly. * This is used to extend the white listed protocols that can be initiated from the ServiceHub invokeProtocolAsync method. */ - val requiredProtocols: Map> + open val requiredProtocols: Map> = emptyMap() /** * List of additional long lived services to be hosted within the node. @@ -34,5 +34,5 @@ interface CordaPluginRegistry { * The ServiceHubInternal will be fully constructed before the plugin service is created and will * allow access to the protocol factory and protocol initiation entry points there. */ - val servicePlugins: List> + open val servicePlugins: List> = emptyList() } diff --git a/node/src/main/kotlin/com/r3corda/node/internal/testing/Simulation.kt b/node/src/main/kotlin/com/r3corda/node/internal/testing/Simulation.kt index 17041ab943..e4f994baa1 100644 --- a/node/src/main/kotlin/com/r3corda/node/internal/testing/Simulation.kt +++ b/node/src/main/kotlin/com/r3corda/node/internal/testing/Simulation.kt @@ -117,7 +117,7 @@ abstract class Simulation(val networkSendManuallyPumped: Boolean, return object : SimulatedNode(dir, cfg, network, networkMapAddr, advertisedServices, id, keyPair) { override fun start(): MockNetwork.MockNode { super.start() - getCustomService().upload(javaClass.getResourceAsStream("example.rates.txt")) + findService().upload(javaClass.getResourceAsStream("example.rates.txt")) return this } } diff --git a/node/src/main/kotlin/com/r3corda/node/services/NotaryChangeService.kt b/node/src/main/kotlin/com/r3corda/node/services/NotaryChangeService.kt index 1a998b0c14..fb82820004 100644 --- a/node/src/main/kotlin/com/r3corda/node/services/NotaryChangeService.kt +++ b/node/src/main/kotlin/com/r3corda/node/services/NotaryChangeService.kt @@ -9,10 +9,7 @@ import com.r3corda.protocols.NotaryChangeProtocol object NotaryChange { - class Plugin : CordaPluginRegistry { - override val webApis: List> = emptyList() - override val requiredProtocols: Map> = emptyMap() - override val staticServeDirs: Map = emptyMap() + class Plugin : CordaPluginRegistry() { override val servicePlugins: List> = listOf(Service::class.java) } diff --git a/node/src/main/kotlin/com/r3corda/node/services/clientapi/FixingSessionInitiation.kt b/node/src/main/kotlin/com/r3corda/node/services/clientapi/FixingSessionInitiation.kt index 568ab4565f..fff47b7735 100644 --- a/node/src/main/kotlin/com/r3corda/node/services/clientapi/FixingSessionInitiation.kt +++ b/node/src/main/kotlin/com/r3corda/node/services/clientapi/FixingSessionInitiation.kt @@ -13,10 +13,7 @@ import com.r3corda.protocols.TwoPartyDealProtocol * TODO: This will be replaced with the automatic sessionID / session setup work. */ object FixingSessionInitiation { - class Plugin: CordaPluginRegistry { - override val webApis: List> = emptyList() - override val staticServeDirs: Map = emptyMap() - override val requiredProtocols: Map> = emptyMap() + class Plugin: CordaPluginRegistry() { override val servicePlugins: List> = listOf(Service::class.java) } diff --git a/node/src/main/kotlin/com/r3corda/node/services/clientapi/NodeInterestRates.kt b/node/src/main/kotlin/com/r3corda/node/services/clientapi/NodeInterestRates.kt index 649e1b8258..6eacbe3716 100644 --- a/node/src/main/kotlin/com/r3corda/node/services/clientapi/NodeInterestRates.kt +++ b/node/src/main/kotlin/com/r3corda/node/services/clientapi/NodeInterestRates.kt @@ -46,9 +46,7 @@ object NodeInterestRates { /** * Register the protocol that is used with the Fixing integration tests. */ - class Plugin : CordaPluginRegistry { - override val webApis: List> = emptyList() - override val staticServeDirs: Map = emptyMap() + class Plugin : CordaPluginRegistry() { override val requiredProtocols: Map> = mapOf(Pair(TwoPartyDealProtocol.FixingRoleDecider::class.java.name, setOf(Duration::class.java.name, StateRef::class.java.name))) override val servicePlugins: List> = listOf(NodeInterestRates.Service::class.java) } diff --git a/node/src/main/kotlin/com/r3corda/node/services/persistence/DataVendingService.kt b/node/src/main/kotlin/com/r3corda/node/services/persistence/DataVendingService.kt index 7d680e788b..515a7021fe 100644 --- a/node/src/main/kotlin/com/r3corda/node/services/persistence/DataVendingService.kt +++ b/node/src/main/kotlin/com/r3corda/node/services/persistence/DataVendingService.kt @@ -16,10 +16,7 @@ import javax.annotation.concurrent.ThreadSafe object DataVending { - class Plugin : CordaPluginRegistry { - override val webApis: List> = emptyList() - override val staticServeDirs: Map = emptyMap() - override val requiredProtocols: Map> = emptyMap() + class Plugin : CordaPluginRegistry() { override val servicePlugins: List> = listOf(Service::class.java) } diff --git a/node/src/test/kotlin/com/r3corda/node/services/NodeInterestRatesTest.kt b/node/src/test/kotlin/com/r3corda/node/services/NodeInterestRatesTest.kt index c38f2540cd..67550ebc10 100644 --- a/node/src/test/kotlin/com/r3corda/node/services/NodeInterestRatesTest.kt +++ b/node/src/test/kotlin/com/r3corda/node/services/NodeInterestRatesTest.kt @@ -105,7 +105,7 @@ class NodeInterestRatesTest { fun network() { val net = MockNetwork() val (n1, n2) = net.createTwoNodes() - n2.getCustomService().oracle.knownFixes = TEST_DATA + n2.findService().oracle.knownFixes = TEST_DATA val tx = TransactionType.General.Builder() val fixOf = NodeInterestRates.parseFixOf("LIBOR 2016-03-16 1M") diff --git a/src/main/kotlin/com/r3corda/demos/IRSDemo.kt b/src/main/kotlin/com/r3corda/demos/IRSDemo.kt index 81c8bc8add..1423f5cfd5 100644 --- a/src/main/kotlin/com/r3corda/demos/IRSDemo.kt +++ b/src/main/kotlin/com/r3corda/demos/IRSDemo.kt @@ -257,14 +257,13 @@ object CliParamsSpec { val help = parser.accepts("help", "Prints this help").forHelp() } -class IRSDemoPluginRegistry : CordaPluginRegistry { +class IRSDemoPluginRegistry : CordaPluginRegistry() { override val webApis: List> = listOf(InterestRateSwapAPI::class.java) override val staticServeDirs: Map = mapOf("irsdemo" to javaClass.getResource("irswebdemo").toExternalForm()) override val requiredProtocols: Map> = mapOf( Pair(AutoOfferProtocol.Requester::class.java.name, setOf(InterestRateSwap.State::class.java.name)), Pair(UpdateBusinessDayProtocol.Broadcast::class.java.name, setOf(java.time.LocalDate::class.java.name)), Pair(ExitServerProtocol.Broadcast::class.java.name, setOf(kotlin.Int::class.java.name))) - override val servicePlugins: List> = emptyList() } private class NotSetupException: Throwable { diff --git a/src/main/kotlin/com/r3corda/demos/protocols/AutoOfferProtocol.kt b/src/main/kotlin/com/r3corda/demos/protocols/AutoOfferProtocol.kt index dbb004b1f3..6b13d2b533 100644 --- a/src/main/kotlin/com/r3corda/demos/protocols/AutoOfferProtocol.kt +++ b/src/main/kotlin/com/r3corda/demos/protocols/AutoOfferProtocol.kt @@ -28,10 +28,7 @@ object AutoOfferProtocol { val notary: Party, val otherSessionID: Long, val dealBeingOffered: DealState) - class Plugin: CordaPluginRegistry { - override val webApis: List> = emptyList() - override val staticServeDirs: Map = emptyMap() - override val requiredProtocols: Map> = emptyMap() + class Plugin: CordaPluginRegistry() { override val servicePlugins: List> = listOf(Service::class.java) } diff --git a/src/main/kotlin/com/r3corda/demos/protocols/ExitServerProtocol.kt b/src/main/kotlin/com/r3corda/demos/protocols/ExitServerProtocol.kt index 4585f9c629..a1cdc9ea11 100644 --- a/src/main/kotlin/com/r3corda/demos/protocols/ExitServerProtocol.kt +++ b/src/main/kotlin/com/r3corda/demos/protocols/ExitServerProtocol.kt @@ -20,10 +20,7 @@ object ExitServerProtocol { data class ExitMessage(val exitCode: Int) - class Plugin: CordaPluginRegistry { - override val webApis: List> = emptyList() - override val staticServeDirs: Map = emptyMap() - override val requiredProtocols: Map> = emptyMap() + class Plugin: CordaPluginRegistry() { override val servicePlugins: List> = listOf(Service::class.java) } diff --git a/src/main/kotlin/com/r3corda/demos/protocols/UpdateBusinessDayProtocol.kt b/src/main/kotlin/com/r3corda/demos/protocols/UpdateBusinessDayProtocol.kt index df1f69e3db..6692b946c4 100644 --- a/src/main/kotlin/com/r3corda/demos/protocols/UpdateBusinessDayProtocol.kt +++ b/src/main/kotlin/com/r3corda/demos/protocols/UpdateBusinessDayProtocol.kt @@ -21,10 +21,7 @@ object UpdateBusinessDayProtocol { data class UpdateBusinessDayMessage(val date: LocalDate) - class Plugin: CordaPluginRegistry { - override val webApis: List> = emptyList() - override val staticServeDirs: Map = emptyMap() - override val requiredProtocols: Map> = emptyMap() + class Plugin: CordaPluginRegistry() { override val servicePlugins: List> = listOf(Service::class.java) }