Merge commit 'd8bf1019b6c7ddbe709cff7c730e66eb576f9ad5' into christians/merge-CORDA-1336

This commit is contained in:
Christian Sailer 2018-04-13 16:39:00 +01:00
commit a0bf0261bb
14 changed files with 158 additions and 84 deletions

View File

@ -1001,4 +1001,16 @@ object Crypto {
txId 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
}
} }

View File

@ -40,6 +40,8 @@ internal val cordaBouncyCastleProvider = BouncyCastleProvider().apply {
} }
internal val bouncyCastlePQCProvider = BouncyCastlePQCProvider().apply { internal val bouncyCastlePQCProvider = BouncyCastlePQCProvider().apply {
require(name == "BCPQC") // The constant it comes from is not final. 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 // This map is required to defend against users that forcibly call Security.addProvider / Security.removeProvider
// that could cause unexpected and suspicious behaviour. // that could cause unexpected and suspicious behaviour.

View File

@ -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. // 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.PROTOCOLS_PROP_NAME to "CORE,AMQP",
TransportConstants.USE_GLOBAL_WORKER_POOL_PROP_NAME to (nodeSerializationEnv != null), 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) { if (config != null && enableSSL) {

View File

@ -120,6 +120,7 @@ private fun Config.getSingleValue(path: String, type: KType, strict: Boolean = t
NetworkHostAndPort::class -> NetworkHostAndPort.parse(getString(path)) NetworkHostAndPort::class -> NetworkHostAndPort.parse(getString(path))
Path::class -> Paths.get(getString(path)) Path::class -> Paths.get(getString(path))
URL::class -> URL(getString(path)) URL::class -> URL(getString(path))
UUID::class -> UUID.fromString(getString(path))
CordaX500Name::class -> { CordaX500Name::class -> {
when (getValue(path).valueType()) { when (getValue(path).valueType()) {
ConfigValueType.OBJECT -> getConfig(path).parseAs(strict) 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) NetworkHostAndPort::class -> getStringList(path).map(NetworkHostAndPort.Companion::parse)
Path::class -> getStringList(path).map { Paths.get(it) } Path::class -> getStringList(path).map { Paths.get(it) }
URL::class -> getStringList(path).map(::URL) URL::class -> getStringList(path).map(::URL)
UUID::class -> getStringList(path).map { UUID.fromString(it) }
CordaX500Name::class -> getStringList(path).map(CordaX500Name.Companion::parse) CordaX500Name::class -> getStringList(path).map(CordaX500Name.Companion::parse)
Properties::class -> getConfigList(path).map(Config::toProperties) Properties::class -> getConfigList(path).map(Config::toProperties)
else -> if (elementClass.java.isEnum) { else -> if (elementClass.java.isEnum) {
@ -203,7 +205,7 @@ private fun Any.toConfigMap(): Map<String, Any> {
val configValue = if (value is String || value is Boolean || value is Number) { val configValue = if (value is String || value is Boolean || value is Number) {
// These types are supported by Config as use as is // These types are supported by Config as use as is
value 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 // These types make sense to be represented as Strings and the exact inverse parsing function for use in parseAs
value.toString() value.toString()
} else if (value is Enum<*>) { } else if (value is Enum<*>) {
@ -238,6 +240,7 @@ private fun Iterable<*>.toConfigIterable(field: Field): Iterable<Any?> {
NetworkHostAndPort::class.java -> map(Any?::toString) NetworkHostAndPort::class.java -> map(Any?::toString)
Path::class.java -> map(Any?::toString) Path::class.java -> map(Any?::toString)
URL::class.java -> map(Any?::toString) URL::class.java -> map(Any?::toString)
UUID::class.java -> map(Any?::toString)
CordaX500Name::class.java -> map(Any?::toString) CordaX500Name::class.java -> map(Any?::toString)
Properties::class.java -> map { ConfigFactory.parseMap(uncheckedCast(it)).root() } Properties::class.java -> map { ConfigFactory.parseMap(uncheckedCast(it)).root() }
else -> if (elementType.isEnum) { else -> if (elementType.isEnum) {

View File

@ -94,6 +94,11 @@ class ConfigParsingTest {
testPropertyType<URLData, URLListData, URL>(URL("http://localhost:1234"), URL("http://localhost:1235"), valuesToString = true) testPropertyType<URLData, URLListData, URL>(URL("http://localhost:1234"), URL("http://localhost:1235"), valuesToString = true)
} }
@Test
fun `UUID`() {
testPropertyType<UUIDData, UUIDListData, UUID>(UUID.randomUUID(), UUID.randomUUID(), valuesToString = true)
}
@Test @Test
fun CordaX500Name() { fun CordaX500Name() {
val name1 = CordaX500Name(organisation = "Mock Party", locality = "London", country = "GB") val name1 = CordaX500Name(organisation = "Mock Party", locality = "London", country = "GB")
@ -299,6 +304,8 @@ class ConfigParsingTest {
data class PathListData(override val values: List<Path>) : ListData<Path> data class PathListData(override val values: List<Path>) : ListData<Path>
data class URLData(override val value: URL) : SingleData<URL> data class URLData(override val value: URL) : SingleData<URL>
data class URLListData(override val values: List<URL>) : ListData<URL> data class URLListData(override val values: List<URL>) : ListData<URL>
data class UUIDData(override val value: UUID) : SingleData<UUID>
data class UUIDListData(override val values: List<UUID>) : ListData<UUID>
data class CordaX500NameData(override val value: CordaX500Name) : SingleData<CordaX500Name> data class CordaX500NameData(override val value: CordaX500Name) : SingleData<CordaX500Name>
data class CordaX500NameListData(override val values: List<CordaX500Name>) : ListData<CordaX500Name> data class CordaX500NameListData(override val values: List<CordaX500Name>) : ListData<CordaX500Name>
data class PropertiesData(override val value: Properties) : SingleData<Properties> data class PropertiesData(override val value: Properties) : SingleData<Properties>

View File

@ -19,7 +19,9 @@ import kotlin.system.exitProcess
import net.corda.node.internal.EnterpriseNode import net.corda.node.internal.EnterpriseNode
fun main(args: Array<String>) { fun main(args: Array<String>) {
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. // 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 // 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. // to embed Node in your own container, instantiate it directly and set up the configuration objects yourself.

View File

@ -284,7 +284,8 @@ abstract class AbstractNode(val configuration: NodeConfiguration,
NodeInfoWatcher(configuration.baseDirectory, getRxIoScheduler(), Duration.ofMillis(configuration.additionalNodeInfoPollingFrequencyMsec)), NodeInfoWatcher(configuration.baseDirectory, getRxIoScheduler(), Duration.ofMillis(configuration.additionalNodeInfoPollingFrequencyMsec)),
networkMapClient, networkMapClient,
networkParameters.serialize().hash, networkParameters.serialize().hash,
configuration.baseDirectory) configuration.baseDirectory,
configuration.extraNetworkMapKeys)
runOnStop += networkMapUpdater::close runOnStop += networkMapUpdater::close
networkMapUpdater.subscribeToNetworkMap() networkMapUpdater.subscribeToNetworkMap()

View File

@ -68,6 +68,7 @@ interface NodeConfiguration : NodeSSLConfiguration {
// do not change this value without syncing it with ScheduledFlowsDrainingModeTest // do not change this value without syncing it with ScheduledFlowsDrainingModeTest
val drainingModePollPeriod: Duration get() = Duration.ofSeconds(5) val drainingModePollPeriod: Duration get() = Duration.ofSeconds(5)
val extraNetworkMapKeys: List<UUID>
fun validate(): List<String> fun validate(): List<String>
@ -181,6 +182,7 @@ data class NodeConfigurationImpl(
private val attachmentContentCacheSizeMegaBytes: Int? = null, private val attachmentContentCacheSizeMegaBytes: Int? = null,
override val attachmentCacheBound: Long = NodeConfiguration.defaultAttachmentCacheBound, override val attachmentCacheBound: Long = NodeConfiguration.defaultAttachmentCacheBound,
override val graphiteOptions: GraphiteOptions? = null, override val graphiteOptions: GraphiteOptions? = null,
override val extraNetworkMapKeys: List<UUID> = emptyList(),
// do not use or remove (breaks DemoBench together with rejection of unknown configuration keys during parsing) // do not use or remove (breaks DemoBench together with rejection of unknown configuration keys during parsing)
private val h2port: Int = 0, private val h2port: Int = 0,
// do not use or remove (used by Capsule) // do not use or remove (used by Capsule)

View File

@ -31,6 +31,7 @@ import java.io.BufferedReader
import java.net.URL import java.net.URL
import java.security.cert.X509Certificate import java.security.cert.X509Certificate
import java.time.Duration import java.time.Duration
import java.util.*
class NetworkMapClient(compatibilityZoneURL: URL, val trustedRoot: X509Certificate) { class NetworkMapClient(compatibilityZoneURL: URL, val trustedRoot: X509Certificate) {
companion object { companion object {
@ -53,13 +54,14 @@ class NetworkMapClient(compatibilityZoneURL: URL, val trustedRoot: X509Certifica
logger.trace { "Sent network parameters approval to $ackURL successfully." } logger.trace { "Sent network parameters approval to $ackURL successfully." }
} }
fun getNetworkMap(): NetworkMapResponse { fun getNetworkMap(networkMapKey: UUID? = null): NetworkMapResponse {
logger.trace { "Fetching network map update from $networkMapUrl." } val url = networkMapKey?.let { URL("$networkMapUrl/$networkMapKey") } ?: networkMapUrl
val connection = networkMapUrl.openHttpConnection() logger.trace { "Fetching network map update from $url." }
val connection = url.openHttpConnection()
val signedNetworkMap = connection.responseAs<SignedNetworkMap>() val signedNetworkMap = connection.responseAs<SignedNetworkMap>()
val networkMap = signedNetworkMap.verifiedNetworkMapCert(trustedRoot) val networkMap = signedNetworkMap.verifiedNetworkMapCert(trustedRoot)
val timeout = connection.cacheControl.maxAgeSeconds().seconds 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) return NetworkMapResponse(networkMap, timeout)
} }

View File

@ -28,9 +28,11 @@ import net.corda.nodeapi.exceptions.OutdatedNetworkParameterHashException
import net.corda.nodeapi.internal.network.* import net.corda.nodeapi.internal.network.*
import rx.Subscription import rx.Subscription
import rx.subjects.PublishSubject import rx.subjects.PublishSubject
import java.net.URL
import java.nio.file.Path import java.nio.file.Path
import java.nio.file.StandardCopyOption import java.nio.file.StandardCopyOption
import java.time.Duration import java.time.Duration
import java.util.*
import java.util.concurrent.Executors import java.util.concurrent.Executors
import java.util.concurrent.TimeUnit import java.util.concurrent.TimeUnit
import kotlin.system.exitProcess import kotlin.system.exitProcess
@ -39,7 +41,8 @@ class NetworkMapUpdater(private val networkMapCache: NetworkMapCacheInternal,
private val fileWatcher: NodeInfoWatcher, private val fileWatcher: NodeInfoWatcher,
private val networkMapClient: NetworkMapClient?, private val networkMapClient: NetworkMapClient?,
private val currentParametersHash: SecureHash, private val currentParametersHash: SecureHash,
private val baseDirectory: Path private val baseDirectory: Path,
private val extraNetworkMapKeys: List<UUID>
) : AutoCloseable { ) : AutoCloseable {
companion object { companion object {
private val logger = contextLogger() private val logger = contextLogger()
@ -86,16 +89,25 @@ class NetworkMapUpdater(private val networkMapCache: NetworkMapCacheInternal,
} }
private fun updateNetworkMapCache(networkMapClient: NetworkMapClient): Duration { private fun updateNetworkMapCache(networkMapClient: NetworkMapClient): Duration {
val (networkMap, cacheTimeout) = networkMapClient.getNetworkMap() val (globalNetworkMap, cacheTimeout) = networkMapClient.getNetworkMap()
networkMap.parametersUpdate?.let { handleUpdateNetworkParameters(networkMapClient, it) } 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<SecureHash>()
}
}
val allHashesFromNetworkMap = (globalNetworkMap.nodeInfoHashes + additionalHashes).toSet()
if (currentParametersHash != networkMap.networkParameterHash) { if (currentParametersHash != globalNetworkMap.networkParameterHash) {
exitOnParametersMismatch(networkMap) exitOnParametersMismatch(globalNetworkMap)
} }
val currentNodeHashes = networkMapCache.allNodeHashes val currentNodeHashes = networkMapCache.allNodeHashes
val hashesFromNetworkMap = networkMap.nodeInfoHashes (allHashesFromNetworkMap - currentNodeHashes).mapNotNull {
(hashesFromNetworkMap - currentNodeHashes).mapNotNull {
// Download new node info from network map // Download new node info from network map
try { try {
networkMapClient.getNodeInfo(it) networkMapClient.getNodeInfo(it)
@ -110,7 +122,7 @@ class NetworkMapUpdater(private val networkMapCache: NetworkMapCacheInternal,
} }
// Remove node info from network map. // Remove node info from network map.
(currentNodeHashes - hashesFromNetworkMap - fileWatcher.processedNodeInfoHashes) (currentNodeHashes - allHashesFromNetworkMap - fileWatcher.processedNodeInfoHashes)
.mapNotNull(networkMapCache::getNodeByHash) .mapNotNull(networkMapCache::getNodeByHash)
.forEach(networkMapCache::removeNode) .forEach(networkMapCache::removeNode)

View File

@ -35,6 +35,7 @@ import java.io.IOException
import java.net.URL import java.net.URL
import java.time.Instant import java.time.Instant
import java.time.temporal.ChronoUnit import java.time.temporal.ChronoUnit
import java.util.*
import kotlin.test.assertEquals import kotlin.test.assertEquals
class NetworkMapClientTest { class NetworkMapClientTest {
@ -94,11 +95,11 @@ class NetworkMapClientTest {
} }
@Test @Test
fun `download NetworkParameter correctly`() { fun `download NetworkParameters correctly`() {
// The test server returns same network parameter for any hash. // The test server returns same network parameter for any hash.
val parametersHash = server.networkParameters.serialize().hash val parametersHash = server.networkParameters.serialize().hash
val networkParameter = networkMapClient.getNetworkParameters(parametersHash).verified() val networkParameters = networkMapClient.getNetworkParameters(parametersHash).verified()
assertEquals(server.networkParameters, networkParameter) assertEquals(server.networkParameters, networkParameters)
} }
@Test @Test

View File

@ -12,41 +12,37 @@ package net.corda.node.services.network
import com.google.common.jimfs.Configuration.unix import com.google.common.jimfs.Configuration.unix
import com.google.common.jimfs.Jimfs import com.google.common.jimfs.Jimfs
import com.nhaarman.mockito_kotlin.any import com.nhaarman.mockito_kotlin.*
import com.nhaarman.mockito_kotlin.mock
import com.nhaarman.mockito_kotlin.times
import com.nhaarman.mockito_kotlin.verify
import net.corda.cordform.CordformNode.NODE_INFO_DIRECTORY import net.corda.cordform.CordformNode.NODE_INFO_DIRECTORY
import net.corda.core.crypto.Crypto import net.corda.core.crypto.Crypto
import net.corda.core.crypto.SecureHash
import net.corda.core.identity.CordaX500Name import net.corda.core.identity.CordaX500Name
import net.corda.core.identity.Party import net.corda.core.identity.Party
import net.corda.core.internal.* import net.corda.core.internal.*
import net.corda.core.messaging.ParametersUpdateInfo import net.corda.core.messaging.ParametersUpdateInfo
import net.corda.core.node.NetworkParameters
import net.corda.core.node.NodeInfo import net.corda.core.node.NodeInfo
import net.corda.core.serialization.serialize import net.corda.core.serialization.serialize
import net.corda.core.utilities.millis import net.corda.core.utilities.millis
import net.corda.node.services.api.NetworkMapCacheInternal import net.corda.node.services.api.NetworkMapCacheInternal
import net.corda.nodeapi.internal.NodeInfoAndSigned 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.nodeapi.internal.network.*
import net.corda.testing.common.internal.testNetworkParameters import net.corda.testing.common.internal.testNetworkParameters
import net.corda.testing.core.SerializationEnvironmentRule import net.corda.testing.core.*
import net.corda.testing.core.expect import net.corda.testing.driver.PortAllocation
import net.corda.testing.core.expectEvents
import net.corda.testing.core.sequence
import net.corda.testing.internal.DEV_ROOT_CA import net.corda.testing.internal.DEV_ROOT_CA
import net.corda.testing.internal.createNodeInfoAndSigned 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.assertj.core.api.Assertions.assertThat
import org.junit.After import org.junit.After
import org.junit.Before
import org.junit.Rule import org.junit.Rule
import org.junit.Test import org.junit.Test
import rx.schedulers.TestScheduler import rx.schedulers.TestScheduler
import java.io.IOException
import java.net.URL
import java.time.Instant import java.time.Instant
import java.time.temporal.ChronoUnit import java.time.temporal.ChronoUnit
import java.util.*
import java.util.concurrent.ConcurrentHashMap import java.util.concurrent.ConcurrentHashMap
import java.util.concurrent.TimeUnit import java.util.concurrent.TimeUnit
import kotlin.test.assertEquals import kotlin.test.assertEquals
@ -56,24 +52,30 @@ class NetworkMapUpdaterTest {
@JvmField @JvmField
val testSerialization = SerializationEnvironmentRule(true) val testSerialization = SerializationEnvironmentRule(true)
private val cacheExpiryMs = 1000
private val privateNetUUID = UUID.randomUUID()
private val fs = Jimfs.newFileSystem(unix()) private val fs = Jimfs.newFileSystem(unix())
private val baseDir = fs.getPath("/node") private val baseDir = fs.getPath("/node")
private val networkMapCache = createMockNetworkMapCache()
private val nodeInfoMap = ConcurrentHashMap<SecureHash, SignedNodeInfo>()
private val networkParamsMap = HashMap<SecureHash, NetworkParameters>()
private val networkMapCertAndKeyPair: CertificateAndKeyPair = createDevNetworkMapCa()
private val cacheExpiryMs = 100
private val networkMapClient = createMockNetworkMapClient()
private val scheduler = TestScheduler() private val scheduler = TestScheduler()
private val networkParametersHash = SecureHash.randomSHA256()
private val fileWatcher = NodeInfoWatcher(baseDir, scheduler) private val fileWatcher = NodeInfoWatcher(baseDir, scheduler)
private val updater = NetworkMapUpdater(networkMapCache, fileWatcher, networkMapClient, networkParametersHash, baseDir) private val networkMapCache = createMockNetworkMapCache()
private var parametersUpdate: ParametersUpdate? = null 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 @After
fun cleanUp() { fun cleanUp() {
updater.close() updater.close()
fs.close() fs.close()
server.close()
} }
@Test @Test
@ -101,11 +103,9 @@ class NetworkMapUpdaterTest {
NodeInfoWatcher.saveToFile(baseDir / NODE_INFO_DIRECTORY, fileNodeInfoAndSigned) NodeInfoWatcher.saveToFile(baseDir / NODE_INFO_DIRECTORY, fileNodeInfoAndSigned)
networkMapClient.publish(signedNodeInfo3) networkMapClient.publish(signedNodeInfo3)
networkMapClient.publish(signedNodeInfo4) networkMapClient.publish(signedNodeInfo4)
scheduler.advanceTimeBy(10, TimeUnit.SECONDS) scheduler.advanceTimeBy(10, TimeUnit.SECONDS)
// TODO: Remove sleep in unit test. // TODO: Remove sleep in unit test.
Thread.sleep(2L * cacheExpiryMs) Thread.sleep(2L * cacheExpiryMs)
// 4 node info from network map, and 1 from file. // 4 node info from network map, and 1 from file.
verify(networkMapCache, times(5)).addNode(any()) verify(networkMapCache, times(5)).addNode(any())
verify(networkMapCache, times(1)).addNode(nodeInfo3) verify(networkMapCache, times(1)).addNode(nodeInfo3)
@ -134,12 +134,13 @@ class NetworkMapUpdaterTest {
Thread.sleep(2L * cacheExpiryMs) Thread.sleep(2L * cacheExpiryMs)
// 4 node info from network map, and 1 from file. // 4 node info from network map, and 1 from file.
assertThat(nodeInfoMap).hasSize(4)
verify(networkMapCache, times(5)).addNode(any()) verify(networkMapCache, times(5)).addNode(any())
verify(networkMapCache, times(1)).addNode(fileNodeInfoAndSigned.nodeInfo) verify(networkMapCache, times(1)).addNode(fileNodeInfoAndSigned.nodeInfo)
// Test remove node. // Test remove node.
nodeInfoMap.clear() listOf(nodeInfo1, nodeInfo2, nodeInfo3, nodeInfo4).forEach {
server.removeNodeInfo(it)
}
// TODO: Remove sleep in unit test. // TODO: Remove sleep in unit test.
Thread.sleep(2L * cacheExpiryMs) Thread.sleep(2L * cacheExpiryMs)
verify(networkMapCache, times(4)).removeNode(any()) verify(networkMapCache, times(4)).removeNode(any())
@ -178,7 +179,7 @@ class NetworkMapUpdaterTest {
assertEquals(null, snapshot) assertEquals(null, snapshot)
val newParameters = testNetworkParameters(epoch = 2) val newParameters = testNetworkParameters(epoch = 2)
val updateDeadline = Instant.now().plus(1, ChronoUnit.DAYS) val updateDeadline = Instant.now().plus(1, ChronoUnit.DAYS)
scheduleParametersUpdate(newParameters, "Test update", updateDeadline) server.scheduleParametersUpdate(newParameters, "Test update", updateDeadline)
updater.subscribeToNetworkMap() updater.subscribeToNetworkMap()
updates.expectEvents(isStrict = false) { updates.expectEvents(isStrict = false) {
sequence( sequence(
@ -195,49 +196,33 @@ class NetworkMapUpdaterTest {
@Test @Test
fun `ack network parameters update`() { fun `ack network parameters update`() {
val newParameters = testNetworkParameters(epoch = 314) val newParameters = testNetworkParameters(epoch = 314)
scheduleParametersUpdate(newParameters, "Test update", Instant.MIN) server.scheduleParametersUpdate(newParameters, "Test update", Instant.MIN)
updater.subscribeToNetworkMap() updater.subscribeToNetworkMap()
// TODO: Remove sleep in unit test. // TODO: Remove sleep in unit test.
Thread.sleep(2L * cacheExpiryMs) Thread.sleep(2L * cacheExpiryMs)
val newHash = newParameters.serialize().hash val newHash = newParameters.serialize().hash
val keyPair = Crypto.generateKeyPair() val keyPair = Crypto.generateKeyPair()
updater.acceptNewNetworkParameters(newHash, { hash -> hash.serialize().sign(keyPair)}) updater.acceptNewNetworkParameters(newHash, { hash -> hash.serialize().sign(keyPair)})
verify(networkMapClient).ackNetworkParametersUpdate(any())
val updateFile = baseDir / NETWORK_PARAMS_UPDATE_FILE_NAME val updateFile = baseDir / NETWORK_PARAMS_UPDATE_FILE_NAME
val signedNetworkParams = updateFile.readObject<SignedNetworkParameters>() val signedNetworkParams = updateFile.readObject<SignedNetworkParameters>()
val paramsFromFile = signedNetworkParams.verifiedNetworkMapCert(DEV_ROOT_CA.certificate) val paramsFromFile = signedNetworkParams.verifiedNetworkMapCert(DEV_ROOT_CA.certificate)
assertEquals(newParameters, paramsFromFile) assertEquals(newParameters, paramsFromFile)
} }
private fun scheduleParametersUpdate(nextParameters: NetworkParameters, description: String, updateDeadline: Instant) { @Test
val nextParamsHash = nextParameters.serialize().hash fun `fetch nodes from private network`() {
networkParamsMap[nextParamsHash] = nextParameters server.addNodesToPrivateNetwork(privateNetUUID, listOf(ALICE_NAME))
parametersUpdate = ParametersUpdate(nextParamsHash, description, updateDeadline) Assertions.assertThatThrownBy { networkMapClient.getNetworkMap(privateNetUUID).payload.nodeInfoHashes }
} .isInstanceOf(IOException::class.java)
.hasMessageContaining("Response Code 404")
private fun createMockNetworkMapClient(): NetworkMapClient { val (aliceInfo, signedAliceInfo) = createNodeInfoAndSigned(ALICE_NAME) // Goes to private network map
return mock { val aliceHash = aliceInfo.serialize().hash
on { trustedRoot }.then { val (bobInfo, signedBobInfo) = createNodeInfoAndSigned(BOB_NAME) // Goes to global network map
DEV_ROOT_CA.certificate networkMapClient.publish(signedAliceInfo)
} networkMapClient.publish(signedBobInfo)
on { publish(any()) }.then { assertThat(networkMapClient.getNetworkMap().payload.nodeInfoHashes).containsExactly(bobInfo.serialize().hash)
val signedNodeInfo: SignedNodeInfo = uncheckedCast(it.arguments[0]) assertThat(networkMapClient.getNetworkMap(privateNetUUID).payload.nodeInfoHashes).containsExactly(aliceHash)
nodeInfoMap.put(signedNodeInfo.verified().serialize().hash, signedNodeInfo) assertEquals(aliceInfo, networkMapClient.getNodeInfo(aliceHash))
}
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
}
}
} }
private fun createMockNetworkMapCache(): NetworkMapCacheInternal { private fun createMockNetworkMapCache(): NetworkMapCacheInternal {

View File

@ -16,6 +16,7 @@ import com.nhaarman.mockito_kotlin.doReturn
import com.nhaarman.mockito_kotlin.whenever import com.nhaarman.mockito_kotlin.whenever
import net.corda.core.DoNotImplement import net.corda.core.DoNotImplement
import net.corda.core.crypto.Crypto import net.corda.core.crypto.Crypto
import net.corda.core.crypto.SecureHash
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.identity.Party import net.corda.core.identity.Party
@ -377,6 +378,7 @@ open class InternalMockNetwork(private val cordappPackages: List<String>,
doReturn(parameters.legalName ?: CordaX500Name("Mock Company $id", "London", "GB")).whenever(it).myLegalName doReturn(parameters.legalName ?: CordaX500Name("Mock Company $id", "London", "GB")).whenever(it).myLegalName
doReturn(makeTestDataSourceProperties("node_$id","net_$networkId")).whenever(it).dataSourceProperties doReturn(makeTestDataSourceProperties("node_$id","net_$networkId")).whenever(it).dataSourceProperties
doReturn(makeTestDatabaseProperties("node_$id")).whenever(it).database doReturn(makeTestDatabaseProperties("node_$id")).whenever(it).database
doReturn(emptyList<SecureHash>()).whenever(it).extraNetworkMapKeys
parameters.configOverrides(it) parameters.configOverrides(it)
} }
val node = nodeFactory(MockNodeArgs(config, this, id, parameters.entropyRoot, parameters.version)) val node = nodeFactory(MockNodeArgs(config, this, id, parameters.entropyRoot, parameters.version))

View File

@ -12,6 +12,7 @@ package net.corda.testing.node.internal.network
import net.corda.core.crypto.SecureHash import net.corda.core.crypto.SecureHash
import net.corda.core.crypto.SignedData import net.corda.core.crypto.SignedData
import net.corda.core.identity.CordaX500Name
import net.corda.core.internal.readObject import net.corda.core.internal.readObject
import net.corda.core.node.NetworkParameters import net.corda.core.node.NetworkParameters
import net.corda.core.node.NodeInfo import net.corda.core.node.NodeInfo
@ -36,6 +37,7 @@ import java.security.PublicKey
import java.security.SignatureException import java.security.SignatureException
import java.time.Duration import java.time.Duration
import java.time.Instant import java.time.Instant
import java.util.*
import javax.ws.rs.* import javax.ws.rs.*
import javax.ws.rs.core.MediaType import javax.ws.rs.core.MediaType
import javax.ws.rs.core.Response import javax.ws.rs.core.Response
@ -105,13 +107,20 @@ class NetworkMapServer(private val pollInterval: Duration,
return service.latestAcceptedParametersMap[publicKey] return service.latestAcceptedParametersMap[publicKey]
} }
fun addNodesToPrivateNetwork(networkUUID: UUID, nodesNames: List<CordaX500Name>) {
service.addNodesToPrivateNetwork(networkUUID, nodesNames)
}
override fun close() { override fun close() {
server.stop() server.stop()
} }
@Path("network-map") @Path("network-map")
inner class InMemoryNetworkMapService { inner class InMemoryNetworkMapService {
private val nodeNamesUUID = mutableMapOf<CordaX500Name, UUID>()
private val nodeInfoMap = mutableMapOf<SecureHash, SignedNodeInfo>() private val nodeInfoMap = mutableMapOf<SecureHash, SignedNodeInfo>()
// Mapping from the UUID of the network (null for global one) to hashes of the nodes in network
private val networkMaps = mutableMapOf<UUID?, MutableSet<SecureHash>>()
val latestAcceptedParametersMap = mutableMapOf<PublicKey, SecureHash>() val latestAcceptedParametersMap = mutableMapOf<PublicKey, SecureHash>()
private val signedNetParams by lazy { networkMapCertAndKeyPair.sign(networkParameters) } private val signedNetParams by lazy { networkMapCertAndKeyPair.sign(networkParameters) }
@ -121,8 +130,11 @@ class NetworkMapServer(private val pollInterval: Duration,
fun publishNodeInfo(input: InputStream): Response { fun publishNodeInfo(input: InputStream): Response {
return try { return try {
val signedNodeInfo = input.readObject<SignedNodeInfo>() val signedNodeInfo = input.readObject<SignedNodeInfo>()
signedNodeInfo.verified() val hash = signedNodeInfo.raw.hash
nodeInfoMap[signedNodeInfo.raw.hash] = signedNodeInfo val nodeInfo = signedNodeInfo.verified()
val privateNetwork = nodeNamesUUID[nodeInfo.legalIdentities[0].name]
networkMaps.computeIfAbsent(privateNetwork, { mutableSetOf() }).add(hash)
nodeInfoMap[hash] = signedNodeInfo
ok() ok()
} catch (e: Exception) { } catch (e: Exception) {
when (e) { when (e) {
@ -144,22 +156,50 @@ class NetworkMapServer(private val pollInterval: Duration,
@GET @GET
@Produces(MediaType.APPLICATION_OCTET_STREAM) @Produces(MediaType.APPLICATION_OCTET_STREAM)
fun getNetworkMap(): Response { fun getGlobalNetworkMap(): Response {
val networkMap = NetworkMap(nodeInfoMap.keys.toList(), signedNetParams.raw.hash, parametersUpdate) 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<SecureHash>): Response {
val networkMap = NetworkMap(nodeInfoHashes, signedNetParams.raw.hash, parametersUpdate)
val signedNetworkMap = networkMapCertAndKeyPair.sign(networkMap) val signedNetworkMap = networkMapCertAndKeyPair.sign(networkMap)
return Response.ok(signedNetworkMap.serialize().bytes).header("Cache-Control", "max-age=${pollInterval.seconds}").build() return Response.ok(signedNetworkMap.serialize().bytes).header("Cache-Control", "max-age=${pollInterval.seconds}").build()
} }
// Remove nodeInfo for testing. // Remove nodeInfo for testing.
fun removeNodeInfo(nodeInfo: NodeInfo) { 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<CordaX500Name>) {
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 @GET
@Path("node-info/{var}") @Path("node-info/{var}")
@Produces(MediaType.APPLICATION_OCTET_STREAM) @Produces(MediaType.APPLICATION_OCTET_STREAM)
fun getNodeInfo(@PathParam("var") nodeInfoHash: String): Response { 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) { return if (signedNodeInfo != null) {
Response.ok(signedNodeInfo.serialize().bytes) Response.ok(signedNodeInfo.serialize().bytes)
} else { } else {