mirror of
https://github.com/corda/corda.git
synced 2024-12-28 16:58:55 +00:00
Merge commit 'd8bf1019b6c7ddbe709cff7c730e66eb576f9ad5' into christians/merge-CORDA-1336
This commit is contained in:
commit
a0bf0261bb
@ -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
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -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.
|
||||||
|
@ -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) {
|
||||||
|
@ -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) {
|
||||||
|
@ -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>
|
||||||
|
@ -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.
|
||||||
|
@ -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()
|
||||||
|
@ -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)
|
||||||
|
@ -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)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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)
|
||||||
|
|
||||||
|
@ -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
|
||||||
|
@ -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 {
|
||||||
|
@ -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))
|
||||||
|
@ -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 {
|
||||||
|
Loading…
Reference in New Issue
Block a user