mirror of
https://github.com/corda/corda.git
synced 2025-06-16 14:18:20 +00:00
ENT-1584: Subscribe to private network maps using UUIDs (#2922)
Client private network map implementation Add private network maps UUIDs to config as extraNetworkMapKeys. Adjust NetworkMapServer implementation accordingly. Change NetworkMapUpdaterTest to use NetworkMapServer instead of mock
This commit is contained in:
committed by
GitHub
parent
02913b284e
commit
91c52af5ac
@ -108,6 +108,7 @@ private fun Config.getSingleValue(path: String, type: KType): Any? {
|
|||||||
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()
|
ConfigValueType.OBJECT -> getConfig(path).parseAs()
|
||||||
@ -142,6 +143,7 @@ private fun Config.getCollectionValue(path: String, type: KType): Collection<Any
|
|||||||
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) {
|
||||||
@ -191,7 +193,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<*>) {
|
||||||
@ -226,6 +228,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) {
|
||||||
|
@ -84,6 +84,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")
|
||||||
@ -289,6 +294,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>
|
||||||
|
@ -278,7 +278,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()
|
||||||
|
@ -53,6 +53,7 @@ interface NodeConfiguration : NodeSSLConfiguration {
|
|||||||
val attachmentCacheBound: Long get() = defaultAttachmentCacheBound
|
val attachmentCacheBound: Long get() = defaultAttachmentCacheBound
|
||||||
// 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>
|
||||||
|
|
||||||
@ -144,6 +145,7 @@ data class NodeConfigurationImpl(
|
|||||||
private val transactionCacheSizeMegaBytes: Int? = null,
|
private val transactionCacheSizeMegaBytes: Int? = null,
|
||||||
private val attachmentContentCacheSizeMegaBytes: Int? = null,
|
private val attachmentContentCacheSizeMegaBytes: Int? = null,
|
||||||
override val attachmentCacheBound: Long = NodeConfiguration.defaultAttachmentCacheBound,
|
override val attachmentCacheBound: Long = NodeConfiguration.defaultAttachmentCacheBound,
|
||||||
|
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)
|
||||||
|
@ -21,6 +21,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 {
|
||||||
@ -43,13 +44,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)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -18,9 +18,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
|
||||||
@ -29,7 +31,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()
|
||||||
@ -76,16 +79,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)
|
||||||
@ -100,7 +112,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)
|
||||||
|
|
||||||
|
@ -25,6 +25,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 {
|
||||||
@ -84,11 +85,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
|
||||||
|
@ -2,41 +2,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
|
||||||
@ -46,24 +42,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
|
||||||
@ -91,11 +93,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)
|
||||||
@ -124,12 +124,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())
|
||||||
@ -168,7 +169,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(
|
||||||
@ -185,49 +186,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 {
|
||||||
|
@ -6,6 +6,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
|
||||||
@ -366,6 +367,7 @@ open class InternalMockNetwork(private val cordappPackages: List<String>,
|
|||||||
doReturn(baseDirectory(id).createDirectories()).whenever(it).baseDirectory
|
doReturn(baseDirectory(id).createDirectories()).whenever(it).baseDirectory
|
||||||
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(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))
|
||||||
|
@ -2,6 +2,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
|
||||||
@ -26,6 +27,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
|
||||||
@ -95,13 +97,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) }
|
||||||
|
|
||||||
@ -111,8 +120,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) {
|
||||||
@ -134,22 +146,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 {
|
||||||
|
Reference in New Issue
Block a user