[CORDA-446] Clean up other mentions of network map node and logic (#1974)

* [CORDA-446] Clean up other mentions of network map node and logic

* Rename AbstractNetworkMapService to NetworkMapService and remove the empty NetworkMapService

* fix build

* fix artemismessaging tests

* pr comments
This commit is contained in:
Alberto Arri
2017-11-01 14:25:48 +00:00
committed by GitHub
parent 86d273f2ef
commit 00e682a544
20 changed files with 53 additions and 536 deletions

View File

@ -4,8 +4,4 @@ trustStorePassword : "trustpass"
p2pAddress : "localhost:10005" p2pAddress : "localhost:10005"
rpcAddress : "localhost:10006" rpcAddress : "localhost:10006"
webAddress : "localhost:10007" webAddress : "localhost:10007"
networkMapService : {
address : "localhost:10000"
legalName : "O=Network Map Service,OU=corda,L=London,C=GB"
}
useHTTPS : false useHTTPS : false

View File

@ -3,7 +3,7 @@ Building a Corda Network on Azure Marketplace
To help you design, build and test applications on Corda, called CorDapps, a Corda network can be deployed on the `Microsoft Azure Marketplace <https://azure.microsoft.com/en-gb/overview/what-is-azure>`_ To help you design, build and test applications on Corda, called CorDapps, a Corda network can be deployed on the `Microsoft Azure Marketplace <https://azure.microsoft.com/en-gb/overview/what-is-azure>`_
This Corda network offering builds a pre-configured network of Corda nodes as Ubuntu virtual machines (VM). The network comprises of a Network Map Service node, a Notary node and up to nine Corda nodes using a version of Corda of your choosing. The following guide will also show you how to load a simple Yo! CorDapp which demonstrates the basic principles of Corda. When you are ready to go further with developing on Corda and start making contributions to the project head over to the `Corda.net <https://www.corda.net/>`_. This Corda network offering builds a pre-configured network of Corda nodes as Ubuntu virtual machines (VM). The network comprises of a Notary node and up to nine Corda nodes using a version of Corda of your choosing. The following guide will also show you how to load a simple Yo! CorDapp which demonstrates the basic principles of Corda. When you are ready to go further with developing on Corda and start making contributions to the project head over to the `Corda.net <https://www.corda.net/>`_.
Pre-requisites Pre-requisites
-------------- --------------

View File

@ -16,7 +16,7 @@ Running DemoBench
Configuring a Node Configuring a Node
Each node must have a unique name to identify it to the network map service. DemoBench will suggest node names, nearest cities and local port numbers to use. Each node must have a unique name to identify it to the network map service. DemoBench will suggest node names, nearest cities and local port numbers to use.
The first node will host the network map service, and we are forcing that node also to be a notary. Hence only notary services will be available to be selected in the ``Services`` list. For subsequent nodes you may also select any of Corda's other built-in services. The first node will be a notary. Hence only notary services will be available to be selected in the ``Services`` list. For subsequent nodes you may also select any of Corda's other built-in services.
Press the ``Start node`` button to launch the Corda node with your configuration. Press the ``Start node`` button to launch the Corda node with your configuration.

View File

@ -1,8 +1,4 @@
myLegalName : "O=Bank A,L=London,C=GB" myLegalName : "O=Bank A,L=London,C=GB"
p2pAddress : "my-corda-node:10002" p2pAddress : "my-corda-node:10002"
webAddress : "localhost:10003" webAddress : "localhost:10003"
networkMapService : {
address : "my-network-map:10000"
legalName : "O=Network Map Service,OU=corda,L=London,C=GB"
}
verifierType: "OutOfProcess" verifierType: "OutOfProcess"

View File

@ -65,11 +65,6 @@ for maintenance and other minor purposes.
corresponding bridge is used to forward the message to an advertising peer's p2p queue. Once a peer is picked the corresponding bridge is used to forward the message to an advertising peer's p2p queue. Once a peer is picked the
session continues on as normal. session continues on as normal.
:``internal.networkmap``:
This is another private queue just for the node which functions in a similar manner to the ``internal.peers.*`` queues
except this is used to form a connection to the network map node. The node running the network map service is treated
differently as it provides information about the rest of the network.
:``rpc.requests``: :``rpc.requests``:
RPC clients send their requests here, and it's only open for sending by clients authenticated as RPC users. RPC clients send their requests here, and it's only open for sending by clients authenticated as RPC users.

View File

@ -139,28 +139,17 @@ physical host and port information required for the physical
``ArtemisMQ`` messaging layer. ``ArtemisMQ`` messaging layer.
PersistentNetworkMapService and NetworkMapService PersistentNetworkMapService
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ ~~~~~~~~~~~~~~~~~~~~~~~~~~~
The ``NetworkMapService`` is a node internal component responsible for The ``PersistentNetworkMapService`` keeps track of ``NodeInfo`` and
managing and communicating the directory of authenticated registered persists it to the database. It and will include nodes that are not currently active.
nodes and advertised services in the Corda network. Only a single node The networking layer will persist any messages directed at such inactive
in the network (in future this will be a clustered service) should host
the NetworkMapService implementation. All other Corda nodes initiate
their remote connection to the ``NetworkMapService`` early in the
start-up sequence and wait to synchronise their local
``NetworkMapCache`` before activating any flows. For the
``PersistentNetworkMapService`` registered ``NodeInfo`` data is
persisted and will include nodes that are not currently active. The
networking layer will persist any messages directed at such inactive
nodes with the expectation that they will be delivered eventually, or nodes with the expectation that they will be delivered eventually, or
else that the source flow will be terminated by admin intervention. else that the source flow will be terminated by admin intervention.
An ``InMemoryNetworkMapService`` is also available for unit tests An ``InMemoryNetworkMapService`` is also available for unit tests
without a database. without a database.
The ``NetworkMapService`` should not be used by any flows, or
contracts. Instead they should access the NetworkMapCache service to
access this data.
Storage and persistence related services Storage and persistence related services
---------------------------------------- ----------------------------------------

View File

@ -3,8 +3,7 @@
Creating a Corda network Creating a Corda network
======================== ========================
A Corda network consists of a number of machines running nodes, including a single node operating as the network map A Corda network consists of a number of machines running nodes. These nodes communicate using persistent protocols in order to create and validate transactions.
service. These nodes communicate using persistent protocols in order to create and validate transactions.
There are four broader categories of functionality one such node may have. These pieces of functionality are provided as There are four broader categories of functionality one such node may have. These pieces of functionality are provided as
services, and one node may run several of them. services, and one node may run several of them.

View File

@ -42,10 +42,7 @@ import net.corda.node.services.events.ScheduledActivityObserver
import net.corda.node.services.identity.PersistentIdentityService import net.corda.node.services.identity.PersistentIdentityService
import net.corda.node.services.keys.PersistentKeyManagementService import net.corda.node.services.keys.PersistentKeyManagementService
import net.corda.node.services.messaging.MessagingService import net.corda.node.services.messaging.MessagingService
import net.corda.node.services.messaging.sendRequest
import net.corda.node.services.network.* import net.corda.node.services.network.*
import net.corda.node.services.network.NetworkMapService.RegistrationRequest
import net.corda.node.services.network.NetworkMapService.RegistrationResponse
import net.corda.node.services.persistence.DBCheckpointStorage import net.corda.node.services.persistence.DBCheckpointStorage
import net.corda.node.services.persistence.DBTransactionMappingStorage import net.corda.node.services.persistence.DBTransactionMappingStorage
import net.corda.node.services.persistence.DBTransactionStorage import net.corda.node.services.persistence.DBTransactionStorage
@ -58,7 +55,6 @@ import net.corda.node.services.upgrade.ContractUpgradeServiceImpl
import net.corda.node.services.vault.NodeVaultService import net.corda.node.services.vault.NodeVaultService
import net.corda.node.services.vault.VaultSoftLockManager import net.corda.node.services.vault.VaultSoftLockManager
import net.corda.node.utilities.* import net.corda.node.utilities.*
import net.corda.node.utilities.AddOrRemove.ADD
import org.apache.activemq.artemis.utils.ReusableLatch import org.apache.activemq.artemis.utils.ReusableLatch
import org.slf4j.Logger import org.slf4j.Logger
import rx.Observable import rx.Observable
@ -553,15 +549,6 @@ abstract class AbstractNode(config: NodeConfiguration,
} }
} }
private fun sendNetworkMapRegistration(networkMapAddress: SingleMessageRecipient): CordaFuture<RegistrationResponse> {
// Register this node against the network
val instant = platformClock.instant()
val expires = instant + NetworkMapService.DEFAULT_EXPIRATION_PERIOD
val reg = NodeRegistration(info, info.serial, ADD, expires)
val request = RegistrationRequest(reg.toWire(services.keyManagementService, info.legalIdentitiesAndCerts.first().owningKey), network.myAddress)
return network.sendRequest(NetworkMapService.REGISTER_TOPIC, request, networkMapAddress)
}
/** Return list of node's addresses. It's overridden in MockNetwork as we don't have real addresses for MockNodes. */ /** Return list of node's addresses. It's overridden in MockNetwork as we don't have real addresses for MockNodes. */
protected abstract fun myAddresses(): List<NetworkHostAndPort> protected abstract fun myAddresses(): List<NetworkHostAndPort>

View File

@ -11,7 +11,8 @@ import net.corda.core.internal.uncheckedCast
import net.corda.core.messaging.RPCOps import net.corda.core.messaging.RPCOps
import net.corda.core.node.ServiceHub import net.corda.core.node.ServiceHub
import net.corda.core.serialization.SerializationDefaults import net.corda.core.serialization.SerializationDefaults
import net.corda.core.utilities.* import net.corda.core.utilities.NetworkHostAndPort
import net.corda.core.utilities.loggerFor
import net.corda.node.VersionInfo import net.corda.node.VersionInfo
import net.corda.node.internal.cordapp.CordappLoader import net.corda.node.internal.cordapp.CordappLoader
import net.corda.node.serialization.KryoServerSerializationScheme import net.corda.node.serialization.KryoServerSerializationScheme
@ -22,8 +23,6 @@ import net.corda.node.services.api.NetworkMapCacheInternal
import net.corda.node.services.api.SchemaService import net.corda.node.services.api.SchemaService
import net.corda.node.services.config.NodeConfiguration import net.corda.node.services.config.NodeConfiguration
import net.corda.node.services.messaging.ArtemisMessagingServer import net.corda.node.services.messaging.ArtemisMessagingServer
import net.corda.node.services.messaging.ArtemisMessagingServer.Companion.ipDetectRequestProperty
import net.corda.node.services.messaging.ArtemisMessagingServer.Companion.ipDetectResponseProperty
import net.corda.node.services.messaging.MessagingService import net.corda.node.services.messaging.MessagingService
import net.corda.node.services.messaging.NodeMessagingClient import net.corda.node.services.messaging.NodeMessagingClient
import net.corda.node.services.network.NetworkMapService import net.corda.node.services.network.NetworkMapService
@ -32,10 +31,6 @@ import net.corda.node.utilities.AddressUtils
import net.corda.node.utilities.AffinityExecutor import net.corda.node.utilities.AffinityExecutor
import net.corda.node.utilities.TestClock import net.corda.node.utilities.TestClock
import net.corda.nodeapi.ArtemisMessagingComponent import net.corda.nodeapi.ArtemisMessagingComponent
import net.corda.nodeapi.ArtemisMessagingComponent.Companion.IP_REQUEST_PREFIX
import net.corda.nodeapi.ArtemisMessagingComponent.Companion.PEER_USER
import net.corda.nodeapi.ArtemisTcpTransport
import net.corda.nodeapi.ConnectionDirection
import net.corda.nodeapi.internal.ShutdownHook import net.corda.nodeapi.internal.ShutdownHook
import net.corda.nodeapi.internal.addShutdownHook import net.corda.nodeapi.internal.addShutdownHook
import net.corda.nodeapi.internal.serialization.* import net.corda.nodeapi.internal.serialization.*
@ -46,9 +41,7 @@ import org.apache.activemq.artemis.api.core.client.ActiveMQClient
import org.apache.activemq.artemis.api.core.client.ClientMessage import org.apache.activemq.artemis.api.core.client.ClientMessage
import org.slf4j.Logger import org.slf4j.Logger
import org.slf4j.LoggerFactory import org.slf4j.LoggerFactory
import java.io.IOException
import java.time.Clock import java.time.Clock
import java.util.*
import java.util.concurrent.atomic.AtomicInteger import java.util.concurrent.atomic.AtomicInteger
import javax.management.ObjectName import javax.management.ObjectName
import kotlin.system.exitProcess import kotlin.system.exitProcess
@ -160,7 +153,6 @@ open class Node(override val configuration: NodeConfiguration,
legalIdentity.owningKey, legalIdentity.owningKey,
serverThread, serverThread,
database, database,
nodeReadyFuture,
services.monitoringService, services.monitoringService,
advertisedAddress) advertisedAddress)
} }
@ -200,53 +192,6 @@ open class Node(override val configuration: NodeConfiguration,
return null return null
} }
/**
* Asks the network map service to provide this node's public IP address:
* - Connects to the network map service's message broker and creates a special IP request queue with a custom
* request id. Marks the established session with the same request id.
* - On the server side a special post-queue-creation callback is fired. It finds the session matching the request id
* encoded in the queue name. It then extracts the remote IP from the session details and posts a message containing
* it back to the queue.
* - Once the message is received the session is closed and the queue deleted.
*/
private fun discoverPublicHost(serverAddress: NetworkHostAndPort): String? {
log.trace { "Trying to detect public hostname through the Network Map Service at $serverAddress" }
val tcpTransport = ArtemisTcpTransport.tcpTransport(ConnectionDirection.Outbound(), serverAddress, configuration)
val locator = ActiveMQClient.createServerLocatorWithoutHA(tcpTransport).apply {
initialConnectAttempts = 2 // TODO Public host discovery needs rewriting, as we may start nodes without network map, and we don't want to wait that long on startup.
retryInterval = 2.seconds.toMillis()
retryIntervalMultiplier = 1.5
maxRetryInterval = 3.minutes.toMillis()
}
val clientFactory = try {
locator.createSessionFactory()
} catch (e: ActiveMQNotConnectedException) {
log.warn("Unable to connect to the Network Map Service at $serverAddress for IP address discovery. " +
"Using the provided \"${configuration.p2pAddress.host}\" as the advertised address.")
return null
}
val session = clientFactory.createSession(PEER_USER, PEER_USER, false, true, true, locator.isPreAcknowledge, ActiveMQClient.DEFAULT_ACK_BATCH_SIZE)
val requestId = UUID.randomUUID().toString()
session.addMetaData(ipDetectRequestProperty, requestId)
session.start()
val queueName = "$IP_REQUEST_PREFIX$requestId"
session.createQueue(queueName, RoutingType.MULTICAST, queueName, false)
val consumer = session.createConsumer(queueName)
val artemisMessage: ClientMessage = consumer.receive(10.seconds.toMillis()) ?:
throw IOException("Did not receive a response from the Network Map Service at $serverAddress")
val publicHostAndPort = artemisMessage.getStringProperty(ipDetectResponseProperty)
log.info("Detected public address: $publicHostAndPort")
consumer.close()
session.deleteQueue(queueName)
clientFactory.close()
return NetworkHostAndPort.parse(publicHostAndPort.removePrefix("/")).host
}
override fun startMessagingService(rpcOps: RPCOps) { override fun startMessagingService(rpcOps: RPCOps) {
// Start up the embedded MQ server // Start up the embedded MQ server
messageBroker?.apply { messageBroker?.apply {
@ -264,7 +209,7 @@ open class Node(override val configuration: NodeConfiguration,
} }
override fun makeNetworkMapService(network: MessagingService, networkMapCache: NetworkMapCacheInternal): NetworkMapService { override fun makeNetworkMapService(network: MessagingService, networkMapCache: NetworkMapCacheInternal): NetworkMapService {
return PersistentNetworkMapService(network, networkMapCache, configuration.minimumPlatformVersion) return PersistentNetworkMapService()
} }
/** /**

View File

@ -1,6 +1,5 @@
package net.corda.node.services.api package net.corda.node.services.api
import net.corda.core.CordaException
import net.corda.core.concurrent.CordaFuture import net.corda.core.concurrent.CordaFuture
import net.corda.core.crypto.SecureHash import net.corda.core.crypto.SecureHash
import net.corda.core.flows.FlowInitiator import net.corda.core.flows.FlowInitiator
@ -18,7 +17,6 @@ import net.corda.core.node.StatesToRecord
import net.corda.core.node.services.NetworkMapCache import net.corda.core.node.services.NetworkMapCache
import net.corda.core.node.services.NetworkMapCacheBase import net.corda.core.node.services.NetworkMapCacheBase
import net.corda.core.node.services.TransactionStorage import net.corda.core.node.services.TransactionStorage
import net.corda.core.serialization.CordaSerializable
import net.corda.core.transactions.SignedTransaction import net.corda.core.transactions.SignedTransaction
import net.corda.core.utilities.getOrThrow import net.corda.core.utilities.getOrThrow
import net.corda.core.utilities.loggerFor import net.corda.core.utilities.loggerFor
@ -42,12 +40,6 @@ interface NetworkMapCacheBaseInternal : NetworkMapCacheBase {
val loadDBSuccess: Boolean val loadDBSuccess: Boolean
} }
@CordaSerializable
sealed class NetworkCacheException : CordaException("Network Cache Error") {
/** Indicates a failure to deregister, because of a rejected request from the remote node */
class DeregistrationFailed : NetworkCacheException()
}
interface ServiceHubInternal : ServiceHub { interface ServiceHubInternal : ServiceHub {
companion object { companion object {
private val log = loggerFor<ServiceHubInternal>() private val log = loggerFor<ServiceHubInternal>()

View File

@ -116,7 +116,7 @@ class ArtemisMessagingServer(override val config: NodeConfiguration,
/** /**
* The server will make sure the bridge exists on network map changes, see method [updateBridgesOnNetworkChange] * The server will make sure the bridge exists on network map changes, see method [updateBridgesOnNetworkChange]
* We assume network map will be updated accordingly when the client node register with the network map server. * We assume network map will be updated accordingly when the client node register with the network map.
*/ */
@Throws(IOException::class, KeyStoreException::class) @Throws(IOException::class, KeyStoreException::class)
fun start() = mutex.locked { fun start() = mutex.locked {

View File

@ -1,11 +1,8 @@
package net.corda.node.services.messaging package net.corda.node.services.messaging
import net.corda.core.concurrent.CordaFuture
import net.corda.core.crypto.random63BitValue import net.corda.core.crypto.random63BitValue
import net.corda.core.identity.CordaX500Name import net.corda.core.identity.CordaX500Name
import net.corda.core.internal.ThreadBox import net.corda.core.internal.ThreadBox
import net.corda.core.internal.concurrent.andForget
import net.corda.core.internal.concurrent.thenMatch
import net.corda.core.messaging.CordaRPCOps import net.corda.core.messaging.CordaRPCOps
import net.corda.core.messaging.MessageRecipients import net.corda.core.messaging.MessageRecipients
import net.corda.core.messaging.RPCOps import net.corda.core.messaging.RPCOps
@ -16,7 +13,10 @@ import net.corda.core.serialization.SerializationDefaults
import net.corda.core.serialization.deserialize import net.corda.core.serialization.deserialize
import net.corda.core.serialization.serialize import net.corda.core.serialization.serialize
import net.corda.core.transactions.LedgerTransaction import net.corda.core.transactions.LedgerTransaction
import net.corda.core.utilities.* import net.corda.core.utilities.NetworkHostAndPort
import net.corda.core.utilities.loggerFor
import net.corda.core.utilities.sequence
import net.corda.core.utilities.trace
import net.corda.node.VersionInfo import net.corda.node.VersionInfo
import net.corda.node.services.RPCUserService import net.corda.node.services.RPCUserService
import net.corda.node.services.api.MonitoringService import net.corda.node.services.api.MonitoringService
@ -65,8 +65,7 @@ import javax.persistence.Lob
* CordaRPCClient class. * CordaRPCClient class.
* *
* @param serverAddress The address of the broker instance to connect to (might be running in the same process). * @param serverAddress The address of the broker instance to connect to (might be running in the same process).
* @param myIdentity Either the public key to be used as the ArtemisMQ address and queue name for the node globally, or null to indicate * @param myIdentity The public key to be used as the ArtemisMQ address and queue name for the node.
* that this is a NetworkMapService node which will be bound globally to the name "networkmap".
* @param nodeExecutor An executor to run received message tasks upon. * @param nodeExecutor An executor to run received message tasks upon.
* @param advertisedAddress The node address for inbound connections, advertised to the network map service and peers. * @param advertisedAddress The node address for inbound connections, advertised to the network map service and peers.
* If not provided, will default to [serverAddress]. * If not provided, will default to [serverAddress].
@ -78,7 +77,6 @@ class NodeMessagingClient(override val config: NodeConfiguration,
private val myIdentity: PublicKey, private val myIdentity: PublicKey,
private val nodeExecutor: AffinityExecutor.ServiceAffinityExecutor, private val nodeExecutor: AffinityExecutor.ServiceAffinityExecutor,
val database: CordaPersistence, val database: CordaPersistence,
private val networkMapRegistrationFuture: CordaFuture<Unit>,
val monitoringService: MonitoringService, val monitoringService: MonitoringService,
advertisedAddress: NetworkHostAndPort = serverAddress advertisedAddress: NetworkHostAndPort = serverAddress
) : ArtemisMessagingComponent(), MessagingService { ) : ArtemisMessagingComponent(), MessagingService {
@ -234,18 +232,7 @@ class NodeMessagingClient(override val config: NodeConfiguration,
this.producer = producer this.producer = producer
// Create a queue, consumer and producer for handling P2P network messages. // Create a queue, consumer and producer for handling P2P network messages.
p2pConsumer = makeP2PConsumer(session, true) p2pConsumer = session.createConsumer(P2P_QUEUE)
networkMapRegistrationFuture.thenMatch({
state.locked {
log.info("Network map is complete, so removing filter from P2P consumer.")
try {
p2pConsumer!!.close()
} catch (e: ActiveMQObjectClosedException) {
// Ignore it: this can happen if the server has gone away before we do.
}
p2pConsumer = makeP2PConsumer(session, false)
}
}, {})
val myCert = loadKeyStore(config.sslKeystore, config.keyStorePassword).getX509Certificate(X509Utilities.CORDA_CLIENT_TLS) val myCert = loadKeyStore(config.sslKeystore, config.keyStorePassword).getX509Certificate(X509Utilities.CORDA_CLIENT_TLS)
rpcServer = RPCServer(rpcOps, NODE_USER, NODE_USER, locator, userService, CordaX500Name.build(myCert.subjectX500Principal)) rpcServer = RPCServer(rpcOps, NODE_USER, NODE_USER, locator, userService, CordaX500Name.build(myCert.subjectX500Principal))
@ -267,20 +254,6 @@ class NodeMessagingClient(override val config: NodeConfiguration,
resumeMessageRedelivery() resumeMessageRedelivery()
} }
/**
* We make the consumer twice, once to filter for just network map messages, and then once that is complete, we close
* the original and make another without a filter. We do this so that there is a network map in place for all other
* message handlers.
*/
private fun makeP2PConsumer(session: ClientSession, networkMapOnly: Boolean): ClientConsumer {
return if (networkMapOnly) {
// Filter for just the network map messages.
val messageFilter = "hyphenated_props:$topicProperty like 'platform.network_map.%'"
session.createConsumer(P2P_QUEUE, messageFilter)
} else
session.createConsumer(P2P_QUEUE)
}
private fun resumeMessageRedelivery() { private fun resumeMessageRedelivery() {
messagesToRedeliver.forEach { retryId, (message, target) -> messagesToRedeliver.forEach { retryId, (message, target) ->
send(message, target, retryId) send(message, target, retryId)
@ -325,46 +298,22 @@ class NodeMessagingClient(override val config: NodeConfiguration,
return true return true
} }
private fun runPreNetworkMap(serverControl: ActiveMQServerControl) { /**
* Starts the p2p event loop: this method only returns once [stop] has been called.
*/
fun run(serverControl: ActiveMQServerControl) {
try {
val consumer = state.locked { val consumer = state.locked {
check(started) { "start must be called first" } check(started) { "start must be called first" }
check(!running) { "run can't be called twice" } check(!running) { "run can't be called twice" }
running = true running = true
rpcServer!!.start(serverControl) rpcServer!!.start(serverControl)
(verifierService as? OutOfProcessTransactionVerifierService)?.start(verificationResponseConsumer!!) (verifierService as? OutOfProcessTransactionVerifierService)?.start(verificationResponseConsumer!!)
p2pConsumer!!
}
while (!networkMapRegistrationFuture.isDone && processMessage(consumer)) {
}
with(networkMapRegistrationFuture) {
if (isDone) getOrThrow() else andForget(log) // Trigger node shutdown here to avoid deadlock in shutdown hooks.
}
}
private fun runPostNetworkMap() {
val consumer = state.locked {
// If it's null, it means we already called stop, so return immediately. // If it's null, it means we already called stop, so return immediately.
p2pConsumer ?: return p2pConsumer ?: return
} }
while (processMessage(consumer)) { while (processMessage(consumer)) { }
}
}
/**
* Starts the p2p event loop: this method only returns once [stop] has been called.
*
* This actually runs as two sequential loops. The first subscribes for and receives only network map messages until
* we get our network map fetch response. At that point the filtering consumer is closed and we proceed to the second loop and
* consume all messages via a new consumer without a filter applied.
*/
fun run(serverControl: ActiveMQServerControl) {
try {
// Build the network map.
runPreNetworkMap(serverControl)
// Process everything else once we have the network map.
runPostNetworkMap()
} finally { } finally {
shutdownLatch.countDown() shutdownLatch.countDown()
} }

View File

@ -1,48 +1,14 @@
package net.corda.node.services.network package net.corda.node.services.network
import net.corda.core.CordaException
import net.corda.core.crypto.DigitalSignature
import net.corda.core.crypto.SignedData
import net.corda.core.crypto.isFulfilledBy
import net.corda.core.crypto.random63BitValue
import net.corda.core.identity.PartyAndCertificate import net.corda.core.identity.PartyAndCertificate
import net.corda.core.internal.ThreadBox import net.corda.core.internal.ThreadBox
import net.corda.core.internal.VisibleForTesting
import net.corda.core.messaging.MessageRecipients
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.services.KeyManagementService
import net.corda.core.node.services.NetworkMapCache import net.corda.core.node.services.NetworkMapCache
import net.corda.core.serialization.CordaSerializable import net.corda.core.serialization.CordaSerializable
import net.corda.core.serialization.SerializedBytes
import net.corda.core.serialization.deserialize
import net.corda.core.serialization.serialize
import net.corda.core.utilities.debug
import net.corda.core.utilities.loggerFor
import net.corda.node.services.api.AbstractNodeService
import net.corda.node.services.api.NetworkMapCacheInternal
import net.corda.node.services.messaging.MessageHandlerRegistration
import net.corda.node.services.messaging.MessagingService
import net.corda.node.services.messaging.ServiceRequestMessage
import net.corda.node.services.messaging.createMessage
import net.corda.node.services.network.NetworkMapService.*
import net.corda.node.services.network.NetworkMapService.Companion.FETCH_TOPIC
import net.corda.node.services.network.NetworkMapService.Companion.PUSH_ACK_TOPIC
import net.corda.node.services.network.NetworkMapService.Companion.PUSH_TOPIC
import net.corda.node.services.network.NetworkMapService.Companion.QUERY_TOPIC
import net.corda.node.services.network.NetworkMapService.Companion.REGISTER_TOPIC
import net.corda.node.services.network.NetworkMapService.Companion.SUBSCRIPTION_TOPIC
import net.corda.node.utilities.AddOrRemove import net.corda.node.utilities.AddOrRemove
import net.corda.node.utilities.AddOrRemove.ADD
import net.corda.node.utilities.AddOrRemove.REMOVE
import java.io.IOException
import java.security.PublicKey
import java.security.SignatureException
import java.time.Instant import java.time.Instant
import java.time.Period
import java.util.*
import java.util.concurrent.ConcurrentHashMap import java.util.concurrent.ConcurrentHashMap
import java.util.concurrent.atomic.AtomicInteger
import javax.annotation.concurrent.ThreadSafe import javax.annotation.concurrent.ThreadSafe
/** /**
@ -59,256 +25,23 @@ import javax.annotation.concurrent.ThreadSafe
// It may also be that this is replaced or merged with the identity management service; for example if the network has // It may also be that this is replaced or merged with the identity management service; for example if the network has
// a concept of identity changes over time, should that include the node for an identity? If so, that is likely to // a concept of identity changes over time, should that include the node for an identity? If so, that is likely to
// replace this service. // replace this service.
@ThreadSafe
interface NetworkMapService { interface NetworkMapService {
companion object { val nodeRegistrations: Map<PartyAndCertificate, NodeRegistrationInfo>
val DEFAULT_EXPIRATION_PERIOD: Period = Period.ofWeeks(4)
const val FETCH_TOPIC = "platform.network_map.fetch"
const val QUERY_TOPIC = "platform.network_map.query"
const val REGISTER_TOPIC = "platform.network_map.register"
const val SUBSCRIPTION_TOPIC = "platform.network_map.subscribe"
// Base topic used when pushing out updates to the network map. Consumed, for example, by the map cache.
// When subscribing to these updates, remember they must be acknowledged
const val PUSH_TOPIC = "platform.network_map.push"
// Base topic for messages acknowledging pushed updates
const val PUSH_ACK_TOPIC = "platform.network_map.push_ack"
}
data class FetchMapRequest(val subscribe: Boolean, // Map from subscriber address, to most recently acknowledged update map version.
val ifChangedSinceVersion: Int?, val subscribers: ThreadBox<MutableMap<SingleMessageRecipient, LastAcknowledgeInfo>>
override val replyTo: SingleMessageRecipient,
override val sessionID: Long = random63BitValue()) : ServiceRequestMessage
@CordaSerializable
data class FetchMapResponse(val nodes: List<NodeRegistration>?, val version: Int)
data class QueryIdentityRequest(val identity: PartyAndCertificate,
override val replyTo: SingleMessageRecipient,
override val sessionID: Long = random63BitValue()) : ServiceRequestMessage
@CordaSerializable
data class QueryIdentityResponse(val node: NodeInfo?)
// TODO Rename this RegistractionChangeRequest or similar (and related classes)
data class RegistrationRequest(val wireReg: WireNodeRegistration,
override val replyTo: SingleMessageRecipient,
override val sessionID: Long = random63BitValue()) : ServiceRequestMessage
/** If [error] is null then the registration was successful. If not null then it wasn't and it explains why */
@CordaSerializable
data class RegistrationResponse(val error: String?)
data class SubscribeRequest(val subscribe: Boolean,
override val replyTo: SingleMessageRecipient,
override val sessionID: Long = random63BitValue()) : ServiceRequestMessage
@CordaSerializable
data class SubscribeResponse(val confirmed: Boolean)
@CordaSerializable
data class Update(val wireReg: WireNodeRegistration, val mapVersion: Int, val replyTo: MessageRecipients)
@CordaSerializable
data class UpdateAcknowledge(val mapVersion: Int, val replyTo: MessageRecipients)
} }
object NullNetworkMapService : NetworkMapService
@ThreadSafe @ThreadSafe
class InMemoryNetworkMapService(network: MessagingService, networkMapCache: NetworkMapCacheInternal, minimumPlatformVersion: Int) class InMemoryNetworkMapService: NetworkMapService {
: AbstractNetworkMapService(network, networkMapCache, minimumPlatformVersion) {
override val nodeRegistrations: MutableMap<PartyAndCertificate, NodeRegistrationInfo> = ConcurrentHashMap() override val nodeRegistrations: MutableMap<PartyAndCertificate, NodeRegistrationInfo> = ConcurrentHashMap()
override val subscribers = ThreadBox(mutableMapOf<SingleMessageRecipient, LastAcknowledgeInfo>()) override val subscribers = ThreadBox(mutableMapOf<SingleMessageRecipient, LastAcknowledgeInfo>())
init {
setup()
}
} }
/**
* Abstracted out core functionality as the basis for a persistent implementation, as well as existing in-memory implementation.
*
* Design is slightly refactored to track time and map version of last acknowledge per subscriber to facilitate
* subscriber clean up and is simpler to persist than the previous implementation based on a set of missing messages acks.
*/
@ThreadSafe
abstract class AbstractNetworkMapService(network: MessagingService,
private val networkMapCache: NetworkMapCacheInternal,
private val minimumPlatformVersion: Int) : NetworkMapService, AbstractNodeService(network) {
companion object {
/**
* Maximum credible size for a registration request. Generally requests are around 2000-6000 bytes, so this gives a
* 10 times overhead.
*/
private const val MAX_SIZE_REGISTRATION_REQUEST_BYTES = 40000
private val logger = loggerFor<AbstractNetworkMapService>()
}
protected abstract val nodeRegistrations: MutableMap<PartyAndCertificate, NodeRegistrationInfo>
// Map from subscriber address, to most recently acknowledged update map version.
protected abstract val subscribers: ThreadBox<MutableMap<SingleMessageRecipient, LastAcknowledgeInfo>>
protected val _mapVersion = AtomicInteger(0)
@VisibleForTesting
val mapVersion: Int
get() = _mapVersion.get()
/** Maximum number of unacknowledged updates to send to a node before automatically unregistering them for updates */
val maxUnacknowledgedUpdates = 10
private val handlers = ArrayList<MessageHandlerRegistration>()
protected fun setup() {
// Register message handlers
handlers += addMessageHandler(FETCH_TOPIC) { req: FetchMapRequest -> processFetchAllRequest(req) }
handlers += addMessageHandler(QUERY_TOPIC) { req: QueryIdentityRequest -> processQueryRequest(req) }
handlers += addMessageHandler(REGISTER_TOPIC) { req: RegistrationRequest -> processRegistrationRequest(req) }
handlers += addMessageHandler(SUBSCRIPTION_TOPIC) { req: SubscribeRequest -> processSubscriptionRequest(req) }
handlers += network.addMessageHandler(PUSH_ACK_TOPIC) { message, _ ->
val req = message.data.deserialize<UpdateAcknowledge>()
processAcknowledge(req)
}
}
@VisibleForTesting
fun unregisterNetworkHandlers() {
for (handler in handlers) {
network.removeMessageHandler(handler)
}
handlers.clear()
}
private fun addSubscriber(subscriber: MessageRecipients) {
if (subscriber !is SingleMessageRecipient) throw NodeMapException.InvalidSubscriber()
subscribers.locked {
if (!containsKey(subscriber)) {
put(subscriber, LastAcknowledgeInfo(mapVersion))
}
}
}
private fun removeSubscriber(subscriber: MessageRecipients) {
if (subscriber !is SingleMessageRecipient) throw NodeMapException.InvalidSubscriber()
subscribers.locked { remove(subscriber) }
}
private fun processAcknowledge(request: UpdateAcknowledge) {
if (request.replyTo !is SingleMessageRecipient) throw NodeMapException.InvalidSubscriber()
subscribers.locked {
val lastVersionAcked = this[request.replyTo]?.mapVersion
if ((lastVersionAcked ?: 0) < request.mapVersion) {
this[request.replyTo] = LastAcknowledgeInfo(request.mapVersion)
}
}
}
private fun processFetchAllRequest(request: FetchMapRequest): FetchMapResponse {
if (request.subscribe) {
addSubscriber(request.replyTo)
}
val currentVersion = mapVersion
val nodeRegistrations = if (request.ifChangedSinceVersion == null || request.ifChangedSinceVersion < currentVersion) {
// We return back the current state of the entire map including nodes that have been removed
ArrayList(nodeRegistrations.values.map { it.reg }) // Snapshot to avoid attempting to serialise Map internals
} else {
null
}
return FetchMapResponse(nodeRegistrations, currentVersion)
}
private fun processQueryRequest(request: QueryIdentityRequest): QueryIdentityResponse {
val candidate = nodeRegistrations[request.identity]?.reg
// If the most recent record we have is of the node being removed from the map, then it's considered
// as no match.
val node = if (candidate == null || candidate.type == REMOVE) null else candidate.node
return QueryIdentityResponse(node)
}
private fun processRegistrationRequest(request: RegistrationRequest): RegistrationResponse {
val requestSize = request.wireReg.raw.size
logger.debug { "Received registration request of size: $requestSize" }
if (requestSize > MAX_SIZE_REGISTRATION_REQUEST_BYTES) {
return RegistrationResponse("Request is too big")
}
val change = try {
request.wireReg.verified()
} catch (e: SignatureException) {
return RegistrationResponse("Invalid signature on request")
} catch (e: IOException) {
val msg = "Unexpected IO exception: ${e.message}"
logger.error(msg, e)
return RegistrationResponse(msg)
}
val node = change.node
// Get identity from signature on node's registration and use it as an index.
val identity = node.legalIdentitiesAndCerts.singleOrNull { request.wireReg.sig.by == it.owningKey }
identity ?: return RegistrationResponse("Key from signature on the node registration wasn't found in NodeInfo")
if (node.platformVersion < minimumPlatformVersion) {
return RegistrationResponse("Minimum platform version requirement not met: $minimumPlatformVersion")
}
// Update the current value atomically, so that if multiple updates come
// in on different threads, there is no risk of a race condition while checking
// sequence numbers.
val registrationInfo = try {
nodeRegistrations.compute(identity) { _, existing: NodeRegistrationInfo? ->
require(!((existing == null || existing.reg.type == REMOVE) && change.type == REMOVE)) {
"Attempting to de-register unknown node"
}
require(existing == null || existing.reg.serial < change.serial) { "Serial value is too small" }
NodeRegistrationInfo(change, _mapVersion.incrementAndGet())
}
} catch (e: IllegalArgumentException) {
return RegistrationResponse(e.message)
}
notifySubscribers(request.wireReg, registrationInfo!!.mapVersion)
// Update the local cache
// TODO: Once local messaging is fixed, this should go over the network layer as it does to other
// subscribers
when (change.type) {
ADD -> {
logger.info("Added node ${node.addresses} to network map")
networkMapCache.addNode(change.node)
}
REMOVE -> {
logger.info("Removed node ${node.addresses} from network map")
networkMapCache.removeNode(change.node)
}
}
return RegistrationResponse(null)
}
private fun notifySubscribers(wireReg: WireNodeRegistration, newMapVersion: Int) {
// TODO: Once we have a better established messaging system, we can probably send
// to a MessageRecipientGroup that nodes join/leave, rather than the network map
// service itself managing the group
val update = NetworkMapService.Update(wireReg, newMapVersion, network.myAddress).serialize().bytes
val message = network.createMessage(PUSH_TOPIC, data = update)
subscribers.locked {
// Remove any stale subscribers
values.removeIf { (mapVersion) -> newMapVersion - mapVersion > maxUnacknowledgedUpdates }
// TODO: introduce some concept of time in the condition to avoid unsubscribes when there's a message burst.
keys.forEach { recipient -> network.send(message, recipient) }
}
}
private fun processSubscriptionRequest(request: SubscribeRequest): SubscribeResponse {
if (request.subscribe) {
addSubscriber(request.replyTo)
} else {
removeSubscriber(request.replyTo)
}
return SubscribeResponse(true)
}
}
/** /**
* A node registration state in the network map. * A node registration state in the network map.
@ -325,42 +58,9 @@ abstract class AbstractNetworkMapService(network: MessagingService,
// involves providing both node and paerty, and deregistering a node involves a request with party but no node. // involves providing both node and paerty, and deregistering a node involves a request with party but no node.
@CordaSerializable @CordaSerializable
data class NodeRegistration(val node: NodeInfo, val serial: Long, val type: AddOrRemove, var expires: Instant) { data class NodeRegistration(val node: NodeInfo, val serial: Long, val type: AddOrRemove, var expires: Instant) {
/**
* Build a node registration in wire format.
*/
fun toWire(keyManager: KeyManagementService, publicKey: PublicKey): WireNodeRegistration {
val regSerialized = this.serialize()
val regSig = keyManager.sign(regSerialized.bytes, publicKey)
return WireNodeRegistration(regSerialized, regSig)
}
override fun toString(): String = "$node #$serial ($type)" override fun toString(): String = "$node #$serial ($type)"
} }
/**
* A node registration and its signature as a pair.
*/
@CordaSerializable
class WireNodeRegistration(raw: SerializedBytes<NodeRegistration>, sig: DigitalSignature.WithKey) : SignedData<NodeRegistration>(raw, sig) {
@Throws(IllegalArgumentException::class)
override fun verifyData(data: NodeRegistration) {
// Check that the registration is fulfilled by any of node's identities.
// TODO It may cause some problems with distributed services? We loose node's main identity. Should be all signatures instead of isFulfilledBy?
require(data.node.legalIdentitiesAndCerts.any { it.owningKey.isFulfilledBy(sig.by) })
}
}
@CordaSerializable
sealed class NodeMapException : CordaException("Network Map Protocol Error") {
/** Thrown if the signature on the node info does not match the public key for the identity */
class InvalidSignature : NodeMapException()
/** Thrown if the replyTo of a subscription change message is not a single message recipient */
class InvalidSubscriber : NodeMapException()
}
@CordaSerializable @CordaSerializable
data class LastAcknowledgeInfo(val mapVersion: Int) data class LastAcknowledgeInfo(val mapVersion: Int)

View File

@ -8,26 +8,23 @@ import net.corda.core.serialization.SerializationDefaults
import net.corda.core.serialization.deserialize import net.corda.core.serialization.deserialize
import net.corda.core.serialization.serialize import net.corda.core.serialization.serialize
import net.corda.core.utilities.MAX_HASH_HEX_SIZE import net.corda.core.utilities.MAX_HASH_HEX_SIZE
import net.corda.node.services.api.NetworkMapCacheInternal
import net.corda.node.services.messaging.MessagingService
import net.corda.node.utilities.NODE_DATABASE_PREFIX import net.corda.node.utilities.NODE_DATABASE_PREFIX
import net.corda.node.utilities.PersistentMap import net.corda.node.utilities.PersistentMap
import net.corda.nodeapi.ArtemisMessagingComponent import net.corda.nodeapi.ArtemisMessagingComponent
import java.io.ByteArrayInputStream import java.io.ByteArrayInputStream
import java.security.cert.CertificateFactory import java.security.cert.CertificateFactory
import javax.persistence.*
import java.util.* import java.util.*
import javax.persistence.*
/** /**
* A network map service backed by a database to survive restarts of the node hosting it. * A network map service backed by a database to survive restarts of the node hosting it.
* *
* Majority of the logic is inherited from [AbstractNetworkMapService]. * Majority of the logic is inherited from [NetworkMapService].
* *
* This class needs database transactions to be in-flight during method calls and init, otherwise it will throw * This class needs database transactions to be in-flight during method calls and init, otherwise it will throw
* exceptions. * exceptions.
*/ */
class PersistentNetworkMapService(network: MessagingService, networkMapCache: NetworkMapCacheInternal, minimumPlatformVersion: Int) class PersistentNetworkMapService: NetworkMapService {
: AbstractNetworkMapService(network, networkMapCache, minimumPlatformVersion) {
// Only the node_party_path column is needed to reconstruct a PartyAndCertificate but we have the others for human readability // Only the node_party_path column is needed to reconstruct a PartyAndCertificate but we have the others for human readability
@Entity @Entity
@ -124,10 +121,4 @@ class PersistentNetworkMapService(network: MessagingService, networkMapCache: Ne
) )
override val subscribers = ThreadBox(createNetworkSubscribersMap()) override val subscribers = ThreadBox(createNetworkSubscribersMap())
init {
// Initialise the network map version with the current highest persisted version, or zero if there are no entries.
_mapVersion.set(nodeRegistrations.values.map { it.mapVersion }.max() ?: 0)
setup()
}
} }

View File

@ -14,7 +14,6 @@ import net.corda.node.services.config.NodeConfiguration
import net.corda.node.services.config.configureWithDevSSLCertificate import net.corda.node.services.config.configureWithDevSSLCertificate
import net.corda.node.services.network.NetworkMapCacheImpl import net.corda.node.services.network.NetworkMapCacheImpl
import net.corda.node.services.network.PersistentNetworkMapCache import net.corda.node.services.network.PersistentNetworkMapCache
import net.corda.node.services.network.NetworkMapService
import net.corda.node.services.transactions.PersistentUniquenessProvider import net.corda.node.services.transactions.PersistentUniquenessProvider
import net.corda.node.utilities.AffinityExecutor.ServiceAffinityExecutor import net.corda.node.utilities.AffinityExecutor.ServiceAffinityExecutor
import net.corda.node.utilities.CordaPersistence import net.corda.node.utilities.CordaPersistence
@ -40,16 +39,19 @@ import kotlin.test.assertNull
//TODO This needs to be merged into P2PMessagingTest as that creates a more realistic environment //TODO This needs to be merged into P2PMessagingTest as that creates a more realistic environment
class ArtemisMessagingTests { class ArtemisMessagingTests {
companion object {
const val TOPIC = "platform.self"
}
@Rule @Rule
@JvmField @JvmField
val testSerialization = SerializationEnvironmentRule() val testSerialization = SerializationEnvironmentRule()
@Rule @Rule
@JvmField @JvmField
val temporaryFolder = TemporaryFolder() val temporaryFolder = TemporaryFolder()
val serverPort = freePort() val serverPort = freePort()
val rpcPort = freePort() val rpcPort = freePort()
val topic = "platform.self"
val identity = generateKeyPair() val identity = generateKeyPair()
lateinit var config: NodeConfiguration lateinit var config: NodeConfiguration
@ -130,7 +132,7 @@ class ArtemisMessagingTests {
val receivedMessages = LinkedBlockingQueue<Message>() val receivedMessages = LinkedBlockingQueue<Message>()
val messagingClient = createAndStartClientAndServer(receivedMessages) val messagingClient = createAndStartClientAndServer(receivedMessages)
val message = messagingClient.createMessage(topic, data = "first msg".toByteArray()) val message = messagingClient.createMessage(TOPIC, data = "first msg".toByteArray())
messagingClient.send(message, messagingClient.myAddress) messagingClient.send(message, messagingClient.myAddress)
val actual: Message = receivedMessages.take() val actual: Message = receivedMessages.take()
@ -146,15 +148,9 @@ class ArtemisMessagingTests {
val receivedMessages = LinkedBlockingQueue<Message>() val receivedMessages = LinkedBlockingQueue<Message>()
val messagingClient = createAndStartClientAndServer(receivedMessages) val messagingClient = createAndStartClientAndServer(receivedMessages)
val message = messagingClient.createMessage(topic, data = "first msg".toByteArray()) val message = messagingClient.createMessage(TOPIC, data = "first msg".toByteArray())
messagingClient.send(message, messagingClient.myAddress) messagingClient.send(message, messagingClient.myAddress)
val networkMapMessage = messagingClient.createMessage(NetworkMapService.FETCH_TOPIC, data = "second msg".toByteArray())
messagingClient.send(networkMapMessage, messagingClient.myAddress)
val actual: Message = receivedMessages.take()
assertEquals("second msg", String(actual.data))
assertNull(receivedMessages.poll(200, MILLISECONDS))
settableFuture.set(Unit) settableFuture.set(Unit)
val firstActual: Message = receivedMessages.take() val firstActual: Message = receivedMessages.take()
assertEquals("first msg", String(firstActual.data)) assertEquals("first msg", String(firstActual.data))
@ -171,17 +167,10 @@ class ArtemisMessagingTests {
val messagingClient = createAndStartClientAndServer(receivedMessages) val messagingClient = createAndStartClientAndServer(receivedMessages)
for (iter in 1..iterations) { for (iter in 1..iterations) {
val message = messagingClient.createMessage(topic, data = "first msg $iter".toByteArray()) val message = messagingClient.createMessage(TOPIC, data = "first msg $iter".toByteArray())
messagingClient.send(message, messagingClient.myAddress) messagingClient.send(message, messagingClient.myAddress)
} }
val networkMapMessage = messagingClient.createMessage(NetworkMapService.FETCH_TOPIC, data = "second msg".toByteArray())
messagingClient.send(networkMapMessage, messagingClient.myAddress)
val actual: Message = receivedMessages.take()
assertEquals("second msg", String(actual.data))
assertNull(receivedMessages.poll(200, MILLISECONDS))
// Stop client and server and create afresh. // Stop client and server and create afresh.
messagingClient.stop() messagingClient.stop()
messagingServer?.stop() messagingServer?.stop()
@ -205,10 +194,7 @@ class ArtemisMessagingTests {
val messagingClient = createMessagingClient() val messagingClient = createMessagingClient()
startNodeMessagingClient() startNodeMessagingClient()
messagingClient.addMessageHandler(topic) { message, _ -> messagingClient.addMessageHandler(TOPIC) { message, _ ->
receivedMessages.add(message)
}
messagingClient.addMessageHandler(NetworkMapService.FETCH_TOPIC) { message, _ ->
receivedMessages.add(message) receivedMessages.add(message)
} }
// Run after the handlers are added, otherwise (some of) the messages get delivered and discarded / dead-lettered. // Run after the handlers are added, otherwise (some of) the messages get delivered and discarded / dead-lettered.
@ -225,7 +211,6 @@ class ArtemisMessagingTests {
identity.public, identity.public,
ServiceAffinityExecutor("ArtemisMessagingTests", 1), ServiceAffinityExecutor("ArtemisMessagingTests", 1),
database, database,
networkMapRegistrationFuture,
MonitoringService(MetricRegistry())).apply { MonitoringService(MetricRegistry())).apply {
config.configureWithDevSSLCertificate() config.configureWithDevSSLCertificate()
messagingClient = this messagingClient = this

View File

@ -343,8 +343,6 @@ class NetworkMapVisualiser : Application() {
private fun transferIsInteresting(transfer: InMemoryMessagingNetwork.MessageTransfer): Boolean { private fun transferIsInteresting(transfer: InMemoryMessagingNetwork.MessageTransfer): Boolean {
// Loopback messages are boring. // Loopback messages are boring.
if (transfer.sender == transfer.recipients) return false if (transfer.sender == transfer.recipients) return false
// Network map push acknowledgements are boring.
if (NetworkMapService.PUSH_ACK_TOPIC in transfer.message.topicSession.topic) return false
val message = transfer.message.data.deserialize<Any>() val message = transfer.message.data.deserialize<Any>()
return when (message) { return when (message) {
is SessionEnd -> false is SessionEnd -> false

View File

@ -1,7 +1,7 @@
Trader demo Trader demo
----------- -----------
This demo brings up four nodes: Bank A, Bank B, Bank Of Corda, and a notary/network map node that they all use. Bank A This demo brings up four nodes: Bank A, Bank B, Bank Of Corda, and a notary node that they all use. Bank A
will be the buyer, and requests some cash from the Bank of Corda in order to acquire commercial paper from Bank B, the will be the buyer, and requests some cash from the Bank of Corda in order to acquire commercial paper from Bank B, the
seller. seller.

View File

@ -33,7 +33,10 @@ import net.corda.nodeapi.NodeInfoFilesCopier
import net.corda.nodeapi.User import net.corda.nodeapi.User
import net.corda.nodeapi.config.toConfig import net.corda.nodeapi.config.toConfig
import net.corda.nodeapi.internal.addShutdownHook import net.corda.nodeapi.internal.addShutdownHook
import net.corda.testing.* import net.corda.testing.ALICE
import net.corda.testing.BOB
import net.corda.testing.DUMMY_BANK_A
import net.corda.testing.initialiseTestSerialization
import net.corda.testing.node.MockServices.Companion.MOCK_VERSION_INFO import net.corda.testing.node.MockServices.Companion.MOCK_VERSION_INFO
import okhttp3.OkHttpClient import okhttp3.OkHttpClient
import okhttp3.Request import okhttp3.Request
@ -62,8 +65,7 @@ import kotlin.concurrent.thread
/** /**
* This file defines a small "Driver" DSL for starting up nodes that is only intended for development, demos and tests. * This file defines a small "Driver" DSL for starting up nodes that is only intended for development, demos and tests.
* *
* The process the driver is run in behaves as an Artemis client and starts up other processes. Namely it first * The process the driver is run in behaves as an Artemis client and starts up other processes.
* bootstraps a network map service to allow the specified nodes to connect to, then starts up the actual nodes.
* *
* TODO this file is getting way too big, it should be split into several files. * TODO this file is getting way too big, it should be split into several files.
*/ */
@ -302,8 +304,6 @@ data class NodeParameters(
* of the [NodeInfo] that may be waited on, which completes when the new node registered with the network map service or * of the [NodeInfo] that may be waited on, which completes when the new node registered with the network map service or
* loaded node data from database. * loaded node data from database.
* *
* The driver implicitly bootstraps a [NetworkMapService].
*
* @param defaultParameters The default parameters for the driver. Allows the driver to be configured in builder style * @param defaultParameters The default parameters for the driver. Allows the driver to be configured in builder style
* when called from Java code. * when called from Java code.
* @param isDebug Indicates whether the spawned nodes should start in jdwt debug mode and have debug level logging. * @param isDebug Indicates whether the spawned nodes should start in jdwt debug mode and have debug level logging.

View File

@ -29,7 +29,7 @@ import net.corda.node.services.config.BFTSMaRtConfiguration
import net.corda.node.services.config.NodeConfiguration import net.corda.node.services.config.NodeConfiguration
import net.corda.node.services.config.NotaryConfig import net.corda.node.services.config.NotaryConfig
import net.corda.node.services.keys.E2ETestKeyManagementService import net.corda.node.services.keys.E2ETestKeyManagementService
import net.corda.node.services.messaging.* import net.corda.node.services.messaging.MessagingService
import net.corda.node.services.network.InMemoryNetworkMapService import net.corda.node.services.network.InMemoryNetworkMapService
import net.corda.node.services.network.NetworkMapService import net.corda.node.services.network.NetworkMapService
import net.corda.node.services.transactions.BFTNonValidatingNotaryService import net.corda.node.services.transactions.BFTNonValidatingNotaryService
@ -214,7 +214,7 @@ class MockNetwork(defaultParameters: MockNetworkParameters = MockNetworkParamete
} }
override fun makeNetworkMapService(network: MessagingService, networkMapCache: NetworkMapCacheInternal): NetworkMapService { override fun makeNetworkMapService(network: MessagingService, networkMapCache: NetworkMapCacheInternal): NetworkMapService {
return InMemoryNetworkMapService(network, networkMapCache, 1) return InMemoryNetworkMapService()
} }
// This is not thread safe, but node construction is done on a single thread, so that should always be fine // This is not thread safe, but node construction is done on a single thread, so that should always be fine

View File

@ -18,7 +18,6 @@ import org.bouncycastle.cert.X509CertificateHolder
import java.math.BigInteger import java.math.BigInteger
import java.security.KeyPair import java.security.KeyPair
import java.security.PublicKey import java.security.PublicKey
import java.security.cert.Certificate
import java.time.Instant import java.time.Instant
// A dummy time at which we will be pretending test transactions are created. // A dummy time at which we will be pretending test transactions are created.
@ -33,10 +32,6 @@ val DUMMY_NOTARY_IDENTITY: PartyAndCertificate get() = getTestPartyAndCertificat
val DUMMY_NOTARY: Party get() = Party(CordaX500Name(organisation = "Notary Service", locality = "Zurich", country = "CH"), DUMMY_NOTARY_KEY.public) val DUMMY_NOTARY: Party get() = Party(CordaX500Name(organisation = "Notary Service", locality = "Zurich", country = "CH"), DUMMY_NOTARY_KEY.public)
val DUMMY_NOTARY_SERVICE_NAME: CordaX500Name = DUMMY_NOTARY.name.copy(commonName = "corda.notary.validating") val DUMMY_NOTARY_SERVICE_NAME: CordaX500Name = DUMMY_NOTARY.name.copy(commonName = "corda.notary.validating")
val DUMMY_MAP_KEY: KeyPair by lazy { entropyToKeyPair(BigInteger.valueOf(30)) }
/** Dummy network map service identity for tests and simulations */
val DUMMY_MAP: Party get() = Party(CordaX500Name(organisation = "Network Map Service", locality = "Amsterdam", country = "NL"), DUMMY_MAP_KEY.public)
val DUMMY_BANK_A_KEY: KeyPair by lazy { entropyToKeyPair(BigInteger.valueOf(40)) } val DUMMY_BANK_A_KEY: KeyPair by lazy { entropyToKeyPair(BigInteger.valueOf(40)) }
/** Dummy bank identity for tests and simulations */ /** Dummy bank identity for tests and simulations */
val DUMMY_BANK_A: Party get() = Party(CordaX500Name(organisation = "Bank A", locality = "London", country = "GB"), DUMMY_BANK_A_KEY.public) val DUMMY_BANK_A: Party get() = Party(CordaX500Name(organisation = "Bank A", locality = "London", country = "GB"), DUMMY_BANK_A_KEY.public)