diff --git a/core/src/main/kotlin/net/corda/core/crypto/Crypto.kt b/core/src/main/kotlin/net/corda/core/crypto/Crypto.kt index 9f1d7401f2..20bc768ec4 100644 --- a/core/src/main/kotlin/net/corda/core/crypto/Crypto.kt +++ b/core/src/main/kotlin/net/corda/core/crypto/Crypto.kt @@ -1000,5 +1000,17 @@ object Crypto { } else { txId } - } + } + + /** + * Method to force registering all [Crypto]-related cryptography [Provider]s. + * It is recommended that it is invoked first thing on `main` functions, so the [Provider]s are in place before any + * cryptographic operation is requested outside [Crypto] (i.e., SecureRandom, KeyStore, cert-path validation, + * CRL & CSR checks etc.). + */ + // TODO: perform all cryptographic operations via Crypto. + @JvmStatic + fun registerProviders() { + providerMap + } } diff --git a/core/src/main/kotlin/net/corda/core/crypto/internal/ProviderMap.kt b/core/src/main/kotlin/net/corda/core/crypto/internal/ProviderMap.kt index 0fad2bc466..4661b1ecd5 100644 --- a/core/src/main/kotlin/net/corda/core/crypto/internal/ProviderMap.kt +++ b/core/src/main/kotlin/net/corda/core/crypto/internal/ProviderMap.kt @@ -40,6 +40,8 @@ internal val cordaBouncyCastleProvider = BouncyCastleProvider().apply { } internal val bouncyCastlePQCProvider = BouncyCastlePQCProvider().apply { require(name == "BCPQC") // The constant it comes from is not final. +}.also { + Security.addProvider(it) } // This map is required to defend against users that forcibly call Security.addProvider / Security.removeProvider // that could cause unexpected and suspicious behaviour. diff --git a/node-api/src/main/kotlin/net/corda/nodeapi/ArtemisTcpTransport.kt b/node-api/src/main/kotlin/net/corda/nodeapi/ArtemisTcpTransport.kt index e63b1ae7f9..a5cdeb1207 100644 --- a/node-api/src/main/kotlin/net/corda/nodeapi/ArtemisTcpTransport.kt +++ b/node-api/src/main/kotlin/net/corda/nodeapi/ArtemisTcpTransport.kt @@ -75,7 +75,10 @@ class ArtemisTcpTransport { // TODO further investigate how to ensure we use a well defined wire level protocol for Node to Node communications. TransportConstants.PROTOCOLS_PROP_NAME to "CORE,AMQP", TransportConstants.USE_GLOBAL_WORKER_POOL_PROP_NAME to (nodeSerializationEnv != null), - TransportConstants.REMOTING_THREADS_PROPNAME to (if (nodeSerializationEnv != null) -1 else 1) + TransportConstants.REMOTING_THREADS_PROPNAME to (if (nodeSerializationEnv != null) -1 else 1), + // turn off direct delivery in Artemis - this is latency optimisation that can lead to + //hick-ups under high load (CORDA-1336) + TransportConstants.DIRECT_DELIVER to false ) if (config != null && enableSSL) { diff --git a/node-api/src/main/kotlin/net/corda/nodeapi/internal/config/ConfigUtilities.kt b/node-api/src/main/kotlin/net/corda/nodeapi/internal/config/ConfigUtilities.kt index dbc2c0375c..09dabfcda4 100644 --- a/node-api/src/main/kotlin/net/corda/nodeapi/internal/config/ConfigUtilities.kt +++ b/node-api/src/main/kotlin/net/corda/nodeapi/internal/config/ConfigUtilities.kt @@ -120,6 +120,7 @@ private fun Config.getSingleValue(path: String, type: KType, strict: Boolean = t NetworkHostAndPort::class -> NetworkHostAndPort.parse(getString(path)) Path::class -> Paths.get(getString(path)) URL::class -> URL(getString(path)) + UUID::class -> UUID.fromString(getString(path)) CordaX500Name::class -> { when (getValue(path).valueType()) { ConfigValueType.OBJECT -> getConfig(path).parseAs(strict) @@ -154,6 +155,7 @@ private fun Config.getCollectionValue(path: String, type: KType, strict: Boolean NetworkHostAndPort::class -> getStringList(path).map(NetworkHostAndPort.Companion::parse) Path::class -> getStringList(path).map { Paths.get(it) } URL::class -> getStringList(path).map(::URL) + UUID::class -> getStringList(path).map { UUID.fromString(it) } CordaX500Name::class -> getStringList(path).map(CordaX500Name.Companion::parse) Properties::class -> getConfigList(path).map(Config::toProperties) else -> if (elementClass.java.isEnum) { @@ -203,7 +205,7 @@ private fun Any.toConfigMap(): Map { val configValue = if (value is String || value is Boolean || value is Number) { // These types are supported by Config as use as is value - } else if (value is Temporal || value is NetworkHostAndPort || value is CordaX500Name || value is Path || value is URL) { + } else if (value is Temporal || value is NetworkHostAndPort || value is CordaX500Name || value is Path || value is URL || value is UUID) { // These types make sense to be represented as Strings and the exact inverse parsing function for use in parseAs value.toString() } else if (value is Enum<*>) { @@ -238,6 +240,7 @@ private fun Iterable<*>.toConfigIterable(field: Field): Iterable { NetworkHostAndPort::class.java -> map(Any?::toString) Path::class.java -> map(Any?::toString) URL::class.java -> map(Any?::toString) + UUID::class.java -> map(Any?::toString) CordaX500Name::class.java -> map(Any?::toString) Properties::class.java -> map { ConfigFactory.parseMap(uncheckedCast(it)).root() } else -> if (elementType.isEnum) { diff --git a/node-api/src/test/kotlin/net/corda/nodeapi/internal/config/ConfigParsingTest.kt b/node-api/src/test/kotlin/net/corda/nodeapi/internal/config/ConfigParsingTest.kt index 32e997e3a2..53833cb132 100644 --- a/node-api/src/test/kotlin/net/corda/nodeapi/internal/config/ConfigParsingTest.kt +++ b/node-api/src/test/kotlin/net/corda/nodeapi/internal/config/ConfigParsingTest.kt @@ -94,6 +94,11 @@ class ConfigParsingTest { testPropertyType(URL("http://localhost:1234"), URL("http://localhost:1235"), valuesToString = true) } + @Test + fun `UUID`() { + testPropertyType(UUID.randomUUID(), UUID.randomUUID(), valuesToString = true) + } + @Test fun CordaX500Name() { val name1 = CordaX500Name(organisation = "Mock Party", locality = "London", country = "GB") @@ -299,6 +304,8 @@ class ConfigParsingTest { data class PathListData(override val values: List) : ListData data class URLData(override val value: URL) : SingleData data class URLListData(override val values: List) : ListData + data class UUIDData(override val value: UUID) : SingleData + data class UUIDListData(override val values: List) : ListData data class CordaX500NameData(override val value: CordaX500Name) : SingleData data class CordaX500NameListData(override val values: List) : ListData data class PropertiesData(override val value: Properties) : SingleData diff --git a/node/src/main/kotlin/net/corda/node/Corda.kt b/node/src/main/kotlin/net/corda/node/Corda.kt index c710fd0197..58ff6eeaac 100644 --- a/node/src/main/kotlin/net/corda/node/Corda.kt +++ b/node/src/main/kotlin/net/corda/node/Corda.kt @@ -19,7 +19,9 @@ import kotlin.system.exitProcess import net.corda.node.internal.EnterpriseNode fun main(args: Array) { - Crypto.findProvider(CordaSecurityProvider.PROVIDER_NAME) // Install our SecureRandom before e.g. UUID asks for one. + // Register all cryptography [Provider]s first thing on boot. + // Required to install our [SecureRandom] before e.g., UUID asks for one. + Crypto.registerProviders() // Pass the arguments to the Node factory. In the Enterprise edition, this line is modified to point to a subclass. // It will exit the process in case of startup failure and is not intended to be used by embedders. If you want // to embed Node in your own container, instantiate it directly and set up the configuration objects yourself. diff --git a/node/src/main/kotlin/net/corda/node/internal/AbstractNode.kt b/node/src/main/kotlin/net/corda/node/internal/AbstractNode.kt index 3adf641c54..a3d29373b3 100644 --- a/node/src/main/kotlin/net/corda/node/internal/AbstractNode.kt +++ b/node/src/main/kotlin/net/corda/node/internal/AbstractNode.kt @@ -284,7 +284,8 @@ abstract class AbstractNode(val configuration: NodeConfiguration, NodeInfoWatcher(configuration.baseDirectory, getRxIoScheduler(), Duration.ofMillis(configuration.additionalNodeInfoPollingFrequencyMsec)), networkMapClient, networkParameters.serialize().hash, - configuration.baseDirectory) + configuration.baseDirectory, + configuration.extraNetworkMapKeys) runOnStop += networkMapUpdater::close networkMapUpdater.subscribeToNetworkMap() diff --git a/node/src/main/kotlin/net/corda/node/services/config/NodeConfiguration.kt b/node/src/main/kotlin/net/corda/node/services/config/NodeConfiguration.kt index fe1ab5947c..49ef42a6c4 100644 --- a/node/src/main/kotlin/net/corda/node/services/config/NodeConfiguration.kt +++ b/node/src/main/kotlin/net/corda/node/services/config/NodeConfiguration.kt @@ -68,6 +68,7 @@ interface NodeConfiguration : NodeSSLConfiguration { // do not change this value without syncing it with ScheduledFlowsDrainingModeTest val drainingModePollPeriod: Duration get() = Duration.ofSeconds(5) + val extraNetworkMapKeys: List fun validate(): List @@ -181,6 +182,7 @@ data class NodeConfigurationImpl( private val attachmentContentCacheSizeMegaBytes: Int? = null, override val attachmentCacheBound: Long = NodeConfiguration.defaultAttachmentCacheBound, override val graphiteOptions: GraphiteOptions? = null, + override val extraNetworkMapKeys: List = emptyList(), // do not use or remove (breaks DemoBench together with rejection of unknown configuration keys during parsing) private val h2port: Int = 0, // do not use or remove (used by Capsule) diff --git a/node/src/main/kotlin/net/corda/node/services/network/NetworkMapClient.kt b/node/src/main/kotlin/net/corda/node/services/network/NetworkMapClient.kt index 3b27784eda..4b176471d5 100644 --- a/node/src/main/kotlin/net/corda/node/services/network/NetworkMapClient.kt +++ b/node/src/main/kotlin/net/corda/node/services/network/NetworkMapClient.kt @@ -31,6 +31,7 @@ import java.io.BufferedReader import java.net.URL import java.security.cert.X509Certificate import java.time.Duration +import java.util.* class NetworkMapClient(compatibilityZoneURL: URL, val trustedRoot: X509Certificate) { companion object { @@ -53,13 +54,14 @@ class NetworkMapClient(compatibilityZoneURL: URL, val trustedRoot: X509Certifica logger.trace { "Sent network parameters approval to $ackURL successfully." } } - fun getNetworkMap(): NetworkMapResponse { - logger.trace { "Fetching network map update from $networkMapUrl." } - val connection = networkMapUrl.openHttpConnection() + fun getNetworkMap(networkMapKey: UUID? = null): NetworkMapResponse { + val url = networkMapKey?.let { URL("$networkMapUrl/$networkMapKey") } ?: networkMapUrl + logger.trace { "Fetching network map update from $url." } + val connection = url.openHttpConnection() val signedNetworkMap = connection.responseAs() val networkMap = signedNetworkMap.verifiedNetworkMapCert(trustedRoot) val timeout = connection.cacheControl.maxAgeSeconds().seconds - logger.trace { "Fetched network map update from $networkMapUrl successfully: $networkMap" } + logger.trace { "Fetched network map update from $url successfully: $networkMap" } return NetworkMapResponse(networkMap, timeout) } diff --git a/node/src/main/kotlin/net/corda/node/services/network/NetworkMapUpdater.kt b/node/src/main/kotlin/net/corda/node/services/network/NetworkMapUpdater.kt index 24f195f616..d965e1821a 100644 --- a/node/src/main/kotlin/net/corda/node/services/network/NetworkMapUpdater.kt +++ b/node/src/main/kotlin/net/corda/node/services/network/NetworkMapUpdater.kt @@ -28,9 +28,11 @@ import net.corda.nodeapi.exceptions.OutdatedNetworkParameterHashException import net.corda.nodeapi.internal.network.* import rx.Subscription import rx.subjects.PublishSubject +import java.net.URL import java.nio.file.Path import java.nio.file.StandardCopyOption import java.time.Duration +import java.util.* import java.util.concurrent.Executors import java.util.concurrent.TimeUnit import kotlin.system.exitProcess @@ -39,7 +41,8 @@ class NetworkMapUpdater(private val networkMapCache: NetworkMapCacheInternal, private val fileWatcher: NodeInfoWatcher, private val networkMapClient: NetworkMapClient?, private val currentParametersHash: SecureHash, - private val baseDirectory: Path + private val baseDirectory: Path, + private val extraNetworkMapKeys: List ) : AutoCloseable { companion object { private val logger = contextLogger() @@ -86,16 +89,25 @@ class NetworkMapUpdater(private val networkMapCache: NetworkMapCacheInternal, } private fun updateNetworkMapCache(networkMapClient: NetworkMapClient): Duration { - val (networkMap, cacheTimeout) = networkMapClient.getNetworkMap() - networkMap.parametersUpdate?.let { handleUpdateNetworkParameters(networkMapClient, it) } + val (globalNetworkMap, cacheTimeout) = networkMapClient.getNetworkMap() + globalNetworkMap.parametersUpdate?.let { handleUpdateNetworkParameters(networkMapClient, it) } + val additionalHashes = extraNetworkMapKeys.flatMap { + try { + networkMapClient.getNetworkMap(it).payload.nodeInfoHashes + } catch (e: Exception) { + // Failure to retrieve one network map using UUID shouldn't stop the whole update. + logger.warn("Error encountered when downloading network map with uuid '$it', skipping...", e) + emptyList() + } + } + val allHashesFromNetworkMap = (globalNetworkMap.nodeInfoHashes + additionalHashes).toSet() - if (currentParametersHash != networkMap.networkParameterHash) { - exitOnParametersMismatch(networkMap) + if (currentParametersHash != globalNetworkMap.networkParameterHash) { + exitOnParametersMismatch(globalNetworkMap) } val currentNodeHashes = networkMapCache.allNodeHashes - val hashesFromNetworkMap = networkMap.nodeInfoHashes - (hashesFromNetworkMap - currentNodeHashes).mapNotNull { + (allHashesFromNetworkMap - currentNodeHashes).mapNotNull { // Download new node info from network map try { networkMapClient.getNodeInfo(it) @@ -110,7 +122,7 @@ class NetworkMapUpdater(private val networkMapCache: NetworkMapCacheInternal, } // Remove node info from network map. - (currentNodeHashes - hashesFromNetworkMap - fileWatcher.processedNodeInfoHashes) + (currentNodeHashes - allHashesFromNetworkMap - fileWatcher.processedNodeInfoHashes) .mapNotNull(networkMapCache::getNodeByHash) .forEach(networkMapCache::removeNode) diff --git a/node/src/test/kotlin/net/corda/node/services/network/NetworkMapClientTest.kt b/node/src/test/kotlin/net/corda/node/services/network/NetworkMapClientTest.kt index 2315b369c7..c35cb1a44f 100644 --- a/node/src/test/kotlin/net/corda/node/services/network/NetworkMapClientTest.kt +++ b/node/src/test/kotlin/net/corda/node/services/network/NetworkMapClientTest.kt @@ -35,6 +35,7 @@ import java.io.IOException import java.net.URL import java.time.Instant import java.time.temporal.ChronoUnit +import java.util.* import kotlin.test.assertEquals class NetworkMapClientTest { @@ -94,11 +95,11 @@ class NetworkMapClientTest { } @Test - fun `download NetworkParameter correctly`() { + fun `download NetworkParameters correctly`() { // The test server returns same network parameter for any hash. val parametersHash = server.networkParameters.serialize().hash - val networkParameter = networkMapClient.getNetworkParameters(parametersHash).verified() - assertEquals(server.networkParameters, networkParameter) + val networkParameters = networkMapClient.getNetworkParameters(parametersHash).verified() + assertEquals(server.networkParameters, networkParameters) } @Test diff --git a/node/src/test/kotlin/net/corda/node/services/network/NetworkMapUpdaterTest.kt b/node/src/test/kotlin/net/corda/node/services/network/NetworkMapUpdaterTest.kt index a97fb19c5e..e4889b0c32 100644 --- a/node/src/test/kotlin/net/corda/node/services/network/NetworkMapUpdaterTest.kt +++ b/node/src/test/kotlin/net/corda/node/services/network/NetworkMapUpdaterTest.kt @@ -12,41 +12,37 @@ package net.corda.node.services.network import com.google.common.jimfs.Configuration.unix import com.google.common.jimfs.Jimfs -import com.nhaarman.mockito_kotlin.any -import com.nhaarman.mockito_kotlin.mock -import com.nhaarman.mockito_kotlin.times -import com.nhaarman.mockito_kotlin.verify +import com.nhaarman.mockito_kotlin.* import net.corda.cordform.CordformNode.NODE_INFO_DIRECTORY import net.corda.core.crypto.Crypto -import net.corda.core.crypto.SecureHash import net.corda.core.identity.CordaX500Name import net.corda.core.identity.Party import net.corda.core.internal.* import net.corda.core.messaging.ParametersUpdateInfo -import net.corda.core.node.NetworkParameters import net.corda.core.node.NodeInfo import net.corda.core.serialization.serialize import net.corda.core.utilities.millis import net.corda.node.services.api.NetworkMapCacheInternal import net.corda.nodeapi.internal.NodeInfoAndSigned -import net.corda.nodeapi.internal.SignedNodeInfo -import net.corda.nodeapi.internal.createDevNetworkMapCa -import net.corda.nodeapi.internal.crypto.CertificateAndKeyPair import net.corda.nodeapi.internal.network.* import net.corda.testing.common.internal.testNetworkParameters -import net.corda.testing.core.SerializationEnvironmentRule -import net.corda.testing.core.expect -import net.corda.testing.core.expectEvents -import net.corda.testing.core.sequence +import net.corda.testing.core.* +import net.corda.testing.driver.PortAllocation import net.corda.testing.internal.DEV_ROOT_CA import net.corda.testing.internal.createNodeInfoAndSigned +import net.corda.testing.node.internal.network.NetworkMapServer +import org.assertj.core.api.Assertions import org.assertj.core.api.Assertions.assertThat import org.junit.After +import org.junit.Before import org.junit.Rule import org.junit.Test import rx.schedulers.TestScheduler +import java.io.IOException +import java.net.URL import java.time.Instant import java.time.temporal.ChronoUnit +import java.util.* import java.util.concurrent.ConcurrentHashMap import java.util.concurrent.TimeUnit import kotlin.test.assertEquals @@ -56,24 +52,30 @@ class NetworkMapUpdaterTest { @JvmField val testSerialization = SerializationEnvironmentRule(true) + private val cacheExpiryMs = 1000 + private val privateNetUUID = UUID.randomUUID() private val fs = Jimfs.newFileSystem(unix()) private val baseDir = fs.getPath("/node") - private val networkMapCache = createMockNetworkMapCache() - private val nodeInfoMap = ConcurrentHashMap() - private val networkParamsMap = HashMap() - private val networkMapCertAndKeyPair: CertificateAndKeyPair = createDevNetworkMapCa() - private val cacheExpiryMs = 100 - private val networkMapClient = createMockNetworkMapClient() private val scheduler = TestScheduler() - private val networkParametersHash = SecureHash.randomSHA256() private val fileWatcher = NodeInfoWatcher(baseDir, scheduler) - private val updater = NetworkMapUpdater(networkMapCache, fileWatcher, networkMapClient, networkParametersHash, baseDir) - private var parametersUpdate: ParametersUpdate? = null + private val networkMapCache = createMockNetworkMapCache() + private lateinit var server: NetworkMapServer + private lateinit var networkMapClient: NetworkMapClient + private lateinit var updater: NetworkMapUpdater + + @Before + fun setUp() { + server = NetworkMapServer(cacheExpiryMs.millis, PortAllocation.Incremental(10000).nextHostAndPort()) + val hostAndPort = server.start() + networkMapClient = NetworkMapClient(URL("http://${hostAndPort.host}:${hostAndPort.port}"), DEV_ROOT_CA.certificate) + updater = NetworkMapUpdater(networkMapCache, fileWatcher, networkMapClient, server.networkParameters.serialize().hash, baseDir, listOf(privateNetUUID)) + } @After fun cleanUp() { updater.close() fs.close() + server.close() } @Test @@ -101,11 +103,9 @@ class NetworkMapUpdaterTest { NodeInfoWatcher.saveToFile(baseDir / NODE_INFO_DIRECTORY, fileNodeInfoAndSigned) networkMapClient.publish(signedNodeInfo3) networkMapClient.publish(signedNodeInfo4) - scheduler.advanceTimeBy(10, TimeUnit.SECONDS) // TODO: Remove sleep in unit test. Thread.sleep(2L * cacheExpiryMs) - // 4 node info from network map, and 1 from file. verify(networkMapCache, times(5)).addNode(any()) verify(networkMapCache, times(1)).addNode(nodeInfo3) @@ -134,12 +134,13 @@ class NetworkMapUpdaterTest { Thread.sleep(2L * cacheExpiryMs) // 4 node info from network map, and 1 from file. - assertThat(nodeInfoMap).hasSize(4) verify(networkMapCache, times(5)).addNode(any()) verify(networkMapCache, times(1)).addNode(fileNodeInfoAndSigned.nodeInfo) // Test remove node. - nodeInfoMap.clear() + listOf(nodeInfo1, nodeInfo2, nodeInfo3, nodeInfo4).forEach { + server.removeNodeInfo(it) + } // TODO: Remove sleep in unit test. Thread.sleep(2L * cacheExpiryMs) verify(networkMapCache, times(4)).removeNode(any()) @@ -178,7 +179,7 @@ class NetworkMapUpdaterTest { assertEquals(null, snapshot) val newParameters = testNetworkParameters(epoch = 2) val updateDeadline = Instant.now().plus(1, ChronoUnit.DAYS) - scheduleParametersUpdate(newParameters, "Test update", updateDeadline) + server.scheduleParametersUpdate(newParameters, "Test update", updateDeadline) updater.subscribeToNetworkMap() updates.expectEvents(isStrict = false) { sequence( @@ -195,49 +196,33 @@ class NetworkMapUpdaterTest { @Test fun `ack network parameters update`() { val newParameters = testNetworkParameters(epoch = 314) - scheduleParametersUpdate(newParameters, "Test update", Instant.MIN) + server.scheduleParametersUpdate(newParameters, "Test update", Instant.MIN) updater.subscribeToNetworkMap() // TODO: Remove sleep in unit test. Thread.sleep(2L * cacheExpiryMs) val newHash = newParameters.serialize().hash val keyPair = Crypto.generateKeyPair() updater.acceptNewNetworkParameters(newHash, { hash -> hash.serialize().sign(keyPair)}) - verify(networkMapClient).ackNetworkParametersUpdate(any()) val updateFile = baseDir / NETWORK_PARAMS_UPDATE_FILE_NAME val signedNetworkParams = updateFile.readObject() val paramsFromFile = signedNetworkParams.verifiedNetworkMapCert(DEV_ROOT_CA.certificate) assertEquals(newParameters, paramsFromFile) } - private fun scheduleParametersUpdate(nextParameters: NetworkParameters, description: String, updateDeadline: Instant) { - val nextParamsHash = nextParameters.serialize().hash - networkParamsMap[nextParamsHash] = nextParameters - parametersUpdate = ParametersUpdate(nextParamsHash, description, updateDeadline) - } - - private fun createMockNetworkMapClient(): NetworkMapClient { - return mock { - on { trustedRoot }.then { - DEV_ROOT_CA.certificate - } - on { publish(any()) }.then { - val signedNodeInfo: SignedNodeInfo = uncheckedCast(it.arguments[0]) - nodeInfoMap.put(signedNodeInfo.verified().serialize().hash, signedNodeInfo) - } - on { getNetworkMap() }.then { - NetworkMapResponse(NetworkMap(nodeInfoMap.keys.toList(), networkParametersHash, parametersUpdate), cacheExpiryMs.millis) - } - on { getNodeInfo(any()) }.then { - nodeInfoMap[it.arguments[0]]?.verified() - } - on { getNetworkParameters(any()) }.then { - val paramsHash: SecureHash = uncheckedCast(it.arguments[0]) - networkParamsMap[paramsHash]?.let { networkMapCertAndKeyPair.sign(it) } - } - on { ackNetworkParametersUpdate(any()) }.then { - Unit - } - } + @Test + fun `fetch nodes from private network`() { + server.addNodesToPrivateNetwork(privateNetUUID, listOf(ALICE_NAME)) + Assertions.assertThatThrownBy { networkMapClient.getNetworkMap(privateNetUUID).payload.nodeInfoHashes } + .isInstanceOf(IOException::class.java) + .hasMessageContaining("Response Code 404") + val (aliceInfo, signedAliceInfo) = createNodeInfoAndSigned(ALICE_NAME) // Goes to private network map + val aliceHash = aliceInfo.serialize().hash + val (bobInfo, signedBobInfo) = createNodeInfoAndSigned(BOB_NAME) // Goes to global network map + networkMapClient.publish(signedAliceInfo) + networkMapClient.publish(signedBobInfo) + assertThat(networkMapClient.getNetworkMap().payload.nodeInfoHashes).containsExactly(bobInfo.serialize().hash) + assertThat(networkMapClient.getNetworkMap(privateNetUUID).payload.nodeInfoHashes).containsExactly(aliceHash) + assertEquals(aliceInfo, networkMapClient.getNodeInfo(aliceHash)) } private fun createMockNetworkMapCache(): NetworkMapCacheInternal { diff --git a/testing/node-driver/src/main/kotlin/net/corda/testing/node/internal/InternalMockNetwork.kt b/testing/node-driver/src/main/kotlin/net/corda/testing/node/internal/InternalMockNetwork.kt index c44d4d30ff..d86b2a84ff 100644 --- a/testing/node-driver/src/main/kotlin/net/corda/testing/node/internal/InternalMockNetwork.kt +++ b/testing/node-driver/src/main/kotlin/net/corda/testing/node/internal/InternalMockNetwork.kt @@ -16,6 +16,7 @@ import com.nhaarman.mockito_kotlin.doReturn import com.nhaarman.mockito_kotlin.whenever import net.corda.core.DoNotImplement import net.corda.core.crypto.Crypto +import net.corda.core.crypto.SecureHash import net.corda.core.crypto.random63BitValue import net.corda.core.identity.CordaX500Name import net.corda.core.identity.Party @@ -377,6 +378,7 @@ open class InternalMockNetwork(private val cordappPackages: List, doReturn(parameters.legalName ?: CordaX500Name("Mock Company $id", "London", "GB")).whenever(it).myLegalName doReturn(makeTestDataSourceProperties("node_$id","net_$networkId")).whenever(it).dataSourceProperties doReturn(makeTestDatabaseProperties("node_$id")).whenever(it).database + doReturn(emptyList()).whenever(it).extraNetworkMapKeys parameters.configOverrides(it) } val node = nodeFactory(MockNodeArgs(config, this, id, parameters.entropyRoot, parameters.version)) diff --git a/testing/node-driver/src/main/kotlin/net/corda/testing/node/internal/network/NetworkMapServer.kt b/testing/node-driver/src/main/kotlin/net/corda/testing/node/internal/network/NetworkMapServer.kt index 0b093937cc..b65a105163 100644 --- a/testing/node-driver/src/main/kotlin/net/corda/testing/node/internal/network/NetworkMapServer.kt +++ b/testing/node-driver/src/main/kotlin/net/corda/testing/node/internal/network/NetworkMapServer.kt @@ -12,6 +12,7 @@ package net.corda.testing.node.internal.network import net.corda.core.crypto.SecureHash import net.corda.core.crypto.SignedData +import net.corda.core.identity.CordaX500Name import net.corda.core.internal.readObject import net.corda.core.node.NetworkParameters import net.corda.core.node.NodeInfo @@ -36,6 +37,7 @@ import java.security.PublicKey import java.security.SignatureException import java.time.Duration import java.time.Instant +import java.util.* import javax.ws.rs.* import javax.ws.rs.core.MediaType import javax.ws.rs.core.Response @@ -105,13 +107,20 @@ class NetworkMapServer(private val pollInterval: Duration, return service.latestAcceptedParametersMap[publicKey] } + fun addNodesToPrivateNetwork(networkUUID: UUID, nodesNames: List) { + service.addNodesToPrivateNetwork(networkUUID, nodesNames) + } + override fun close() { server.stop() } @Path("network-map") inner class InMemoryNetworkMapService { + private val nodeNamesUUID = mutableMapOf() private val nodeInfoMap = mutableMapOf() + // Mapping from the UUID of the network (null for global one) to hashes of the nodes in network + private val networkMaps = mutableMapOf>() val latestAcceptedParametersMap = mutableMapOf() private val signedNetParams by lazy { networkMapCertAndKeyPair.sign(networkParameters) } @@ -121,8 +130,11 @@ class NetworkMapServer(private val pollInterval: Duration, fun publishNodeInfo(input: InputStream): Response { return try { val signedNodeInfo = input.readObject() - signedNodeInfo.verified() - nodeInfoMap[signedNodeInfo.raw.hash] = signedNodeInfo + val hash = signedNodeInfo.raw.hash + val nodeInfo = signedNodeInfo.verified() + val privateNetwork = nodeNamesUUID[nodeInfo.legalIdentities[0].name] + networkMaps.computeIfAbsent(privateNetwork, { mutableSetOf() }).add(hash) + nodeInfoMap[hash] = signedNodeInfo ok() } catch (e: Exception) { when (e) { @@ -144,22 +156,50 @@ class NetworkMapServer(private val pollInterval: Duration, @GET @Produces(MediaType.APPLICATION_OCTET_STREAM) - fun getNetworkMap(): Response { - val networkMap = NetworkMap(nodeInfoMap.keys.toList(), signedNetParams.raw.hash, parametersUpdate) + fun getGlobalNetworkMap(): Response { + val nodeInfoHashes = networkMaps[null]?.toList() ?: emptyList() + return networkMapResponse(nodeInfoHashes) + } + + @GET + @Path("{var}") + @Produces(MediaType.APPLICATION_OCTET_STREAM) + fun getPrivateNetworkMap(@PathParam("var") extraUUID: String): Response { + val uuid = UUID.fromString(extraUUID) + val nodeInfoHashes = networkMaps[uuid] ?: return Response.status(Response.Status.NOT_FOUND).build() + return networkMapResponse(nodeInfoHashes.toList()) + } + + private fun networkMapResponse(nodeInfoHashes: List): Response { + val networkMap = NetworkMap(nodeInfoHashes, signedNetParams.raw.hash, parametersUpdate) val signedNetworkMap = networkMapCertAndKeyPair.sign(networkMap) return Response.ok(signedNetworkMap.serialize().bytes).header("Cache-Control", "max-age=${pollInterval.seconds}").build() } // Remove nodeInfo for testing. fun removeNodeInfo(nodeInfo: NodeInfo) { - nodeInfoMap.remove(nodeInfo.serialize().hash) + val hash = nodeInfo.serialize().hash + networkMaps.forEach { + it.value.remove(hash) + } + nodeInfoMap.remove(hash) + } + + fun addNodesToPrivateNetwork(networkUUID: UUID, nodeNames: List) { + for (name in nodeNames) { + check(name !in nodeInfoMap.values.flatMap { it.verified().legalIdentities.map { it.name } }) { + throw IllegalArgumentException("Node with name: $name was already published to global network map") + } + } + nodeNames.forEach { nodeNamesUUID[it] = networkUUID } } @GET @Path("node-info/{var}") @Produces(MediaType.APPLICATION_OCTET_STREAM) fun getNodeInfo(@PathParam("var") nodeInfoHash: String): Response { - val signedNodeInfo = nodeInfoMap[SecureHash.parse(nodeInfoHash)] + val hash = SecureHash.parse(nodeInfoHash) + val signedNodeInfo = nodeInfoMap[hash] return if (signedNodeInfo != null) { Response.ok(signedNodeInfo.serialize().bytes) } else {