From 2dc4251cbe13176c8941c4e50aab24a67f2f6d5c Mon Sep 17 00:00:00 2001 From: Patrick Kuo Date: Mon, 20 Nov 2017 09:57:03 +0000 Subject: [PATCH 01/23] change version to 3.0-NETWORKMAP-SNAPSHOT for the feature branch (#2076) --- build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.gradle b/build.gradle index 878df9405e..3903fc1d6b 100644 --- a/build.gradle +++ b/build.gradle @@ -4,7 +4,7 @@ buildscript { file("$projectDir/constants.properties").withInputStream { constants.load(it) } // Our version: bump this on release. - ext.corda_release_version = "3.0-SNAPSHOT" + ext.corda_release_version = "3.0-NETWORKMAP-SNAPSHOT" // Increment this on any release that changes public APIs anywhere in the Corda platform // TODO This is going to be difficult until we have a clear separation throughout the code of what is public and what is internal ext.corda_platform_version = 2 From a13dbcaa5dfa36f254a976fdd8b64dd779ea3f73 Mon Sep 17 00:00:00 2001 From: Alberto Arri <30873160+al-r3@users.noreply.github.com> Date: Tue, 21 Nov 2017 09:59:56 +0000 Subject: [PATCH 02/23] ENT-1069 Allow for nodes to be started in registration mode (#2065) * ENT-1069 Allow for nodes to be started in registration mode --- .../net/corda/testing/driver/DriverTests.kt | 41 +++++++ .../kotlin/net/corda/testing/driver/Driver.kt | 111 +++++++++++++----- .../net/corda/testing/internal/RPCDriver.kt | 5 +- .../net/corda/verifier/VerifierDriver.kt | 5 +- 4 files changed, 133 insertions(+), 29 deletions(-) diff --git a/testing/node-driver/src/integration-test/kotlin/net/corda/testing/driver/DriverTests.kt b/testing/node-driver/src/integration-test/kotlin/net/corda/testing/driver/DriverTests.kt index 39500c7834..f99fb75000 100644 --- a/testing/node-driver/src/integration-test/kotlin/net/corda/testing/driver/DriverTests.kt +++ b/testing/node-driver/src/integration-test/kotlin/net/corda/testing/driver/DriverTests.kt @@ -1,5 +1,8 @@ package net.corda.testing.driver +import com.sun.net.httpserver.HttpExchange +import com.sun.net.httpserver.HttpHandler +import com.sun.net.httpserver.HttpServer import net.corda.core.concurrent.CordaFuture import net.corda.core.internal.div import net.corda.core.internal.list @@ -13,6 +16,8 @@ import net.corda.testing.ProjectStructure.projectRootDir import net.corda.testing.node.NotarySpec import org.assertj.core.api.Assertions.assertThat import org.junit.Test +import java.net.InetSocketAddress +import java.net.URL import java.util.concurrent.Executors import java.util.concurrent.ScheduledExecutorService @@ -51,6 +56,42 @@ class DriverTests { nodeMustBeDown(nodeHandle) } + @Test + fun `node registration`() { + // Very simple Http handler which counts the requests it has received and always returns the same payload. + val handler = object : HttpHandler { + private val _requests = mutableListOf() + val requests: List + get() = _requests.toList() + + override fun handle(exchange: HttpExchange) { + val response = "reply" + _requests.add(exchange.requestURI.toString()) + exchange.responseHeaders.set("Content-Type", "text/html; charset=" + Charsets.UTF_8) + exchange.sendResponseHeaders(200, response.length.toLong()) + exchange.responseBody.use { it.write(response.toByteArray()) } + } + } + + val inetSocketAddress = InetSocketAddress(0) + val server = HttpServer.create(inetSocketAddress, 0) + val port = server.address.port + server.createContext("/", handler) + server.executor = null // creates a default executor + server.start() + + driver(compatibilityZoneURL = URL("http://localhost:$port")) { + // Wait for the notary to have started. + notaryHandles.first().nodeHandles.get() + } + + // We're getting: + // a request to sign the certificate then + // at least one poll request to see if the request has been approved. + // all the network map registration and download. + assertThat(handler.requests).startsWith("/certificate", "/certificate/reply") + } + @Test fun `debug mode enables debug logging level`() { // Make sure we're using the log4j2 config which writes to the log file diff --git a/testing/node-driver/src/main/kotlin/net/corda/testing/driver/Driver.kt b/testing/node-driver/src/main/kotlin/net/corda/testing/driver/Driver.kt index 1fd398e6ac..7cdb4aa993 100644 --- a/testing/node-driver/src/main/kotlin/net/corda/testing/driver/Driver.kt +++ b/testing/node-driver/src/main/kotlin/net/corda/testing/driver/Driver.kt @@ -30,6 +30,8 @@ import net.corda.node.internal.cordapp.CordappLoader import net.corda.node.services.Permissions.Companion.invokeRpc import net.corda.node.services.config.* import net.corda.node.utilities.ServiceIdentityGenerator +import net.corda.node.utilities.registration.HTTPNetworkRegistrationService +import net.corda.node.utilities.registration.NetworkRegistrationHelper import net.corda.nodeapi.NodeInfoFilesCopier import net.corda.nodeapi.User import net.corda.nodeapi.config.toConfig @@ -319,7 +321,9 @@ data class NodeParameters( * @param startNodesInProcess Provides the default behaviour of whether new nodes should start inside this process or * not. Note that this may be overridden in [DriverDSLExposedInterface.startNode]. * @param notarySpecs The notaries advertised in the [NetworkParameters] for this network. These nodes will be started - * automatically and will be available from [DriverDSLExposedInterface.notaryHandles]. Defaults to a simple validating notary. + * automatically and will be available from [DriverDSLExposedInterface.notaryHandles]. Defaults to a simple validating notary. + * @param compatibilityZoneURL if not null each node is started once in registration mode (which makes the node register and quit), + * and then re-starts the node with the given parameters. * @param dsl The dsl itself. * @return The value returned in the [dsl] closure. */ @@ -336,6 +340,7 @@ fun driver( waitForAllNodesToFinish: Boolean = defaultParameters.waitForNodesToFinish, notarySpecs: List = defaultParameters.notarySpecs, extraCordappPackagesToScan: List = defaultParameters.extraCordappPackagesToScan, + compatibilityZoneURL: URL? = defaultParameters.compatibilityZoneURL, dsl: DriverDSLExposedInterface.() -> A ): A { return genericDriver( @@ -349,7 +354,8 @@ fun driver( startNodesInProcess = startNodesInProcess, waitForNodesToFinish = waitForAllNodesToFinish, notarySpecs = notarySpecs, - extraCordappPackagesToScan = extraCordappPackagesToScan + extraCordappPackagesToScan = extraCordappPackagesToScan, + compatibilityZoneURL = compatibilityZoneURL ), coerce = { it }, dsl = dsl, @@ -384,7 +390,8 @@ data class DriverParameters( val startNodesInProcess: Boolean = false, val waitForNodesToFinish: Boolean = false, val notarySpecs: List = listOf(NotarySpec(DUMMY_NOTARY.name)), - val extraCordappPackagesToScan: List = emptyList() + val extraCordappPackagesToScan: List = emptyList(), + val compatibilityZoneURL: URL? = null ) { fun setIsDebug(isDebug: Boolean) = copy(isDebug = isDebug) fun setDriverDirectory(driverDirectory: Path) = copy(driverDirectory = driverDirectory) @@ -449,6 +456,7 @@ fun genericD startNodesInProcess: Boolean = defaultParameters.startNodesInProcess, notarySpecs: List, extraCordappPackagesToScan: List = defaultParameters.extraCordappPackagesToScan, + compatibilityZoneURL: URL? = defaultParameters.compatibilityZoneURL, driverDslWrapper: (DriverDSL) -> D, coerce: (D) -> DI, dsl: DI.() -> A ): A { @@ -464,7 +472,8 @@ fun genericD startNodesInProcess = startNodesInProcess, waitForNodesToFinish = waitForNodesToFinish, extraCordappPackagesToScan = extraCordappPackagesToScan, - notarySpecs = notarySpecs + notarySpecs = notarySpecs, + compatibilityZoneURL = compatibilityZoneURL ) ) val shutdownHook = addShutdownHook(driverDsl::shutdown) @@ -570,7 +579,8 @@ class DriverDSL( val startNodesInProcess: Boolean, val waitForNodesToFinish: Boolean, extraCordappPackagesToScan: List, - val notarySpecs: List + val notarySpecs: List, + val compatibilityZoneURL: URL? ) : DriverDSLInternalInterface { private var _executorService: ScheduledExecutorService? = null val executorService get() = _executorService!! @@ -644,25 +654,50 @@ class DriverDSL( maximumHeapSize: String ): CordaFuture { val p2pAddress = portAllocation.nextHostAndPort() - val rpcAddress = portAllocation.nextHostAndPort() - val webAddress = portAllocation.nextHostAndPort() // TODO: Derive name from the full picked name, don't just wrap the common name val name = providedName ?: CordaX500Name(organisation = "${oneOf(names).organisation}-${p2pAddress.port}", locality = "London", country = "GB") - val users = rpcUsers.map { it.copy(permissions = it.permissions + DRIVER_REQUIRED_PERMISSIONS) } + val registrationFuture = compatibilityZoneURL?.let { registerNode(name, it) } ?: doneFuture(Unit) + return registrationFuture.flatMap { + val rpcAddress = portAllocation.nextHostAndPort() + val webAddress = portAllocation.nextHostAndPort() + val users = rpcUsers.map { it.copy(permissions = it.permissions + DRIVER_REQUIRED_PERMISSIONS) } + val config = ConfigHelper.loadConfig( + baseDirectory = baseDirectory(name), + allowMissingConfig = true, + configOverrides = configOf( + "myLegalName" to name.toString(), + "p2pAddress" to p2pAddress.toString(), + "rpcAddress" to rpcAddress.toString(), + "webAddress" to webAddress.toString(), + "useTestClock" to useTestClock, + "rpcUsers" to if (users.isEmpty()) defaultRpcUserList else users.map { it.toConfig().root().unwrapped() }, + "verifierType" to verifierType.name + ) + customOverrides + ) + + startNodeInternal(config, webAddress, startInSameProcess, maximumHeapSize) + } + } + + private fun registerNode(providedName: CordaX500Name, compatibilityZoneURL: URL): CordaFuture { val config = ConfigHelper.loadConfig( - baseDirectory = baseDirectory(name), + baseDirectory = baseDirectory(providedName), allowMissingConfig = true, configOverrides = configOf( - "myLegalName" to name.toString(), - "p2pAddress" to p2pAddress.toString(), - "rpcAddress" to rpcAddress.toString(), - "webAddress" to webAddress.toString(), - "useTestClock" to useTestClock, - "rpcUsers" to if (users.isEmpty()) defaultRpcUserList else users.map { it.toConfig().root().unwrapped() }, - "verifierType" to verifierType.name - ) + customOverrides + "p2pAddress" to "localhost:1222", // required argument, not really used + "compatibilityZoneURL" to compatibilityZoneURL.toString(), + "myLegalName" to providedName.toString()) ) - return startNodeInternal(config, webAddress, startInSameProcess, maximumHeapSize) + if (startNodesInProcess) { + // This is a bit cheating, we're not starting a full node, we're just calling the code nodes call + // when registering. + val configuration = config.parseAsNodeConfiguration() + NetworkRegistrationHelper(configuration, HTTPNetworkRegistrationService(compatibilityZoneURL)) + .buildKeystore() + return doneFuture(Unit) + } else { + return startNodeForRegistration(config) + } } override fun startNodes(nodes: List, startInSameProcess: Boolean?, maximumHeapSize: String): List> { @@ -679,7 +714,8 @@ class DriverDSL( "rpcUsers" to if (rpcUsers.isEmpty()) defaultRpcUserList else rpcUsers ) ) - startNodeInternal(config, webAddress, startInSameProcess, maximumHeapSize) + val registrationFuture = compatibilityZoneURL?.let { registerNode(name, it) } ?: doneFuture(Unit) + registrationFuture.flatMap { startNodeInternal(config, webAddress, startInSameProcess, maximumHeapSize) } } } @@ -856,6 +892,20 @@ class DriverDSL( return future } + private fun startNodeForRegistration(config: Config): CordaFuture { + val maximumHeapSize = "200m" + val configuration = config.parseAsNodeConfiguration() + configuration.baseDirectory.createDirectories() + + val debugPort = if (isDebug) debugPortAllocation.nextPort() else null + val process = startOutOfProcessNode(configuration, config, quasarJarPath, debugPort, + systemProperties, cordappPackages, maximumHeapSize, initialRegistration = true) + + return poll(executorService, "process exit") { + if (process.isAlive) null else Unit + } + } + private fun startNodeInternal(config: Config, webAddress: NetworkHostAndPort, startInProcess: Boolean?, @@ -887,7 +937,8 @@ class DriverDSL( } } else { val debugPort = if (isDebug) debugPortAllocation.nextPort() else null - val process = startOutOfProcessNode(configuration, config, quasarJarPath, debugPort, systemProperties, cordappPackages, maximumHeapSize) + val process = startOutOfProcessNode(configuration, config, quasarJarPath, debugPort, + systemProperties, cordappPackages, maximumHeapSize, initialRegistration = false) if (waitForNodesToFinish) { state.locked { processes += process @@ -967,7 +1018,8 @@ class DriverDSL( debugPort: Int?, overriddenSystemProperties: Map, cordappPackages: List, - maximumHeapSize: String + maximumHeapSize: String, + initialRegistration: Boolean ): Process { log.info("Starting out-of-process Node ${nodeConf.myLegalName.organisation}, debug port is " + (debugPort ?: "not enabled")) // Write node.conf @@ -991,13 +1043,18 @@ class DriverDSL( "-javaagent:$quasarJarPath=$excludePattern" val loggingLevel = if (debugPort == null) "INFO" else "DEBUG" - return ProcessUtilities.startCordaProcess( + val arguments = mutableListOf( + "--base-directory=${nodeConf.baseDirectory}", + "--logging-level=$loggingLevel", + "--no-local-shell").also { + if (initialRegistration) { + it += "--initial-registration" + } + }.toList() + + return ProcessUtilities.startCordaProcess( className = "net.corda.node.Corda", // cannot directly get class for this, so just use string - arguments = listOf( - "--base-directory=${nodeConf.baseDirectory}", - "--logging-level=$loggingLevel", - "--no-local-shell" - ), + arguments = arguments, jdwpPort = debugPort, extraJvmArguments = extraJvmArguments, errorLogPath = nodeConf.baseDirectory / NodeStartup.LOGS_DIRECTORY_NAME / "error.log", diff --git a/testing/node-driver/src/main/kotlin/net/corda/testing/internal/RPCDriver.kt b/testing/node-driver/src/main/kotlin/net/corda/testing/internal/RPCDriver.kt index 3146a2c821..d8eb0dcaa4 100644 --- a/testing/node-driver/src/main/kotlin/net/corda/testing/internal/RPCDriver.kt +++ b/testing/node-driver/src/main/kotlin/net/corda/testing/internal/RPCDriver.kt @@ -48,6 +48,7 @@ import org.apache.activemq.artemis.core.settings.impl.AddressSettings import org.apache.activemq.artemis.spi.core.protocol.RemotingConnection import org.apache.activemq.artemis.spi.core.security.ActiveMQSecurityManager3 import java.lang.reflect.Method +import java.net.URL import java.nio.file.Path import java.nio.file.Paths import java.util.* @@ -236,6 +237,7 @@ fun rpcDriver( extraCordappPackagesToScan: List = emptyList(), notarySpecs: List = emptyList(), externalTrace: Trace? = null, + compatibilityZoneURL: URL? = null, dsl: RPCDriverExposedDSLInterface.() -> A ) = genericDriver( driverDsl = RPCDriverDSL( @@ -249,7 +251,8 @@ fun rpcDriver( startNodesInProcess = startNodesInProcess, waitForNodesToFinish = waitForNodesToFinish, extraCordappPackagesToScan = extraCordappPackagesToScan, - notarySpecs = notarySpecs + notarySpecs = notarySpecs, + compatibilityZoneURL = compatibilityZoneURL ), externalTrace ), coerce = { it }, diff --git a/verifier/src/integration-test/kotlin/net/corda/verifier/VerifierDriver.kt b/verifier/src/integration-test/kotlin/net/corda/verifier/VerifierDriver.kt index ecacae7cdb..12346168cc 100644 --- a/verifier/src/integration-test/kotlin/net/corda/verifier/VerifierDriver.kt +++ b/verifier/src/integration-test/kotlin/net/corda/verifier/VerifierDriver.kt @@ -37,6 +37,7 @@ import org.apache.activemq.artemis.core.security.CheckType import org.apache.activemq.artemis.core.security.Role import org.apache.activemq.artemis.core.server.impl.ActiveMQServerImpl import org.apache.activemq.artemis.spi.core.security.ActiveMQSecurityManager +import java.net.URL import java.nio.file.Path import java.nio.file.Paths import java.util.concurrent.ConcurrentHashMap @@ -85,6 +86,7 @@ fun verifierDriver( waitForNodesToFinish: Boolean = false, extraCordappPackagesToScan: List = emptyList(), notarySpecs: List = emptyList(), + compatibilityZoneURL: URL? = null, dsl: VerifierExposedDSLInterface.() -> A ) = genericDriver( driverDsl = VerifierDriverDSL( @@ -98,7 +100,8 @@ fun verifierDriver( startNodesInProcess = startNodesInProcess, waitForNodesToFinish = waitForNodesToFinish, extraCordappPackagesToScan = extraCordappPackagesToScan, - notarySpecs = notarySpecs + notarySpecs = notarySpecs, + compatibilityZoneURL = compatibilityZoneURL ) ), coerce = { it }, From 9097107d2edb92fccdaf0536528b7161ab427160 Mon Sep 17 00:00:00 2001 From: Shams Asari Date: Tue, 21 Nov 2017 15:05:24 +0000 Subject: [PATCH 03/23] Revert "Revert "Introducing network parameters."" This reverts commit 953a4a3790940d65217450d544043efdafc45dfe. --- .ci/api-current.txt | 32 ++++++++++ .idea/compiler.xml | 2 - .../net/corda/core/node/NetworkParameters.kt | 40 +++++++++++++ .../core/node/services/NetworkMapCache.kt | 3 +- .../node/services/BFTNotaryServiceTests.kt | 24 +++++--- .../node/services/DistributedServiceTests.kt | 3 +- .../net/corda/node/internal/AbstractNode.kt | 60 ++++++++++++------- .../node/services/config/NodeConfiguration.kt | 1 - .../network/PersistentNetworkMapCache.kt | 33 ++++------ .../utilities/ServiceIdentityGenerator.kt | 4 +- .../corda/node/services/NotaryChangeTests.kt | 10 +--- .../messaging/ArtemisMessagingTests.kt | 2 +- .../net/corda/notarydemo/BFTNotaryCordform.kt | 7 ++- .../corda/notarydemo/RaftNotaryCordform.kt | 6 +- .../kotlin/net/corda/testing/driver/Driver.kt | 24 +++++--- .../corda/testing/internal/NodeBasedTest.kt | 10 ++++ .../testing/internal/demorun/DemoRunner.kt | 1 + .../corda/testing/node/MockNetworkMapCache.kt | 3 +- .../kotlin/net/corda/testing/node/MockNode.kt | 23 ++++++- .../net/corda/testing/node/NotarySpec.kt | 11 +--- .../net/corda/smoketesting/NodeProcess.kt | 13 ++++ .../internal/NetworkParametersCopier.kt | 32 ++++++++++ .../common/internal/ParametersUtilities.kt | 18 ++++++ .../net/corda/loadtest/tests/NotaryTest.kt | 2 +- 24 files changed, 275 insertions(+), 89 deletions(-) create mode 100644 core/src/main/kotlin/net/corda/core/node/NetworkParameters.kt create mode 100644 testing/test-common/src/main/kotlin/net/corda/testing/common/internal/NetworkParametersCopier.kt create mode 100644 testing/test-common/src/main/kotlin/net/corda/testing/common/internal/ParametersUtilities.kt diff --git a/.ci/api-current.txt b/.ci/api-current.txt index 84b4d41817..a5893b7cb8 100644 --- a/.ci/api-current.txt +++ b/.ci/api-current.txt @@ -1678,6 +1678,27 @@ public @interface net.corda.core.messaging.RPCReturnsObservables @org.jetbrains.annotations.NotNull public abstract net.corda.core.messaging.FlowHandle startFlow(net.corda.core.flows.FlowLogic) @org.jetbrains.annotations.NotNull public abstract net.corda.core.messaging.FlowProgressHandle startTrackedFlow(net.corda.core.flows.FlowLogic) ## +@net.corda.core.serialization.CordaSerializable public final class net.corda.core.node.NetworkParameters extends java.lang.Object + public (int, List, java.time.Duration, int, int, java.time.Instant, int) + public final int component1() + @org.jetbrains.annotations.NotNull public final List component2() + @org.jetbrains.annotations.NotNull public final java.time.Duration component3() + public final int component4() + public final int component5() + @org.jetbrains.annotations.NotNull public final java.time.Instant component6() + public final int component7() + @org.jetbrains.annotations.NotNull public final net.corda.core.node.NetworkParameters copy(int, List, java.time.Duration, int, int, java.time.Instant, int) + public boolean equals(Object) + public final int getEpoch() + @org.jetbrains.annotations.NotNull public final java.time.Duration getEventHorizon() + public final int getMaxMessageSize() + public final int getMaxTransactionSize() + public final int getMinimumPlatformVersion() + @org.jetbrains.annotations.NotNull public final java.time.Instant getModifiedTime() + @org.jetbrains.annotations.NotNull public final List getNotaries() + public int hashCode() + public String toString() +## @net.corda.core.serialization.CordaSerializable public final class net.corda.core.node.NodeInfo extends java.lang.Object public (List, List, int, long) @org.jetbrains.annotations.NotNull public final List component1() @@ -1696,6 +1717,17 @@ public @interface net.corda.core.messaging.RPCReturnsObservables public final boolean isLegalIdentity(net.corda.core.identity.Party) public String toString() ## +@net.corda.core.serialization.CordaSerializable public final class net.corda.core.node.NotaryInfo extends java.lang.Object + public (net.corda.core.identity.Party, boolean) + @org.jetbrains.annotations.NotNull public final net.corda.core.identity.Party component1() + public final boolean component2() + @org.jetbrains.annotations.NotNull public final net.corda.core.node.NotaryInfo copy(net.corda.core.identity.Party, boolean) + public boolean equals(Object) + @org.jetbrains.annotations.NotNull public final net.corda.core.identity.Party getIdentity() + public final boolean getValidating() + public int hashCode() + public String toString() +## @net.corda.core.DoNotImplement public interface net.corda.core.node.ServiceHub extends net.corda.core.node.ServicesForResolution @org.jetbrains.annotations.NotNull public abstract net.corda.core.transactions.SignedTransaction addSignature(net.corda.core.transactions.SignedTransaction) @org.jetbrains.annotations.NotNull public abstract net.corda.core.transactions.SignedTransaction addSignature(net.corda.core.transactions.SignedTransaction, java.security.PublicKey) diff --git a/.idea/compiler.xml b/.idea/compiler.xml index e754504179..67675cf9a5 100644 --- a/.idea/compiler.xml +++ b/.idea/compiler.xml @@ -43,8 +43,6 @@ - - diff --git a/core/src/main/kotlin/net/corda/core/node/NetworkParameters.kt b/core/src/main/kotlin/net/corda/core/node/NetworkParameters.kt new file mode 100644 index 0000000000..d5a035175a --- /dev/null +++ b/core/src/main/kotlin/net/corda/core/node/NetworkParameters.kt @@ -0,0 +1,40 @@ +package net.corda.core.node + +import net.corda.core.identity.Party +import net.corda.core.serialization.CordaSerializable +import java.time.Duration +import java.time.Instant + +/** + * @property minimumPlatformVersion + * @property notaries + * @property eventHorizon + * @property maxMessageSize Maximum P2P message sent over the wire in bytes. + * @property maxTransactionSize Maximum permitted transaction size in bytes. + * @property modifiedTime + * @property epoch Version number of the network parameters. Starting from 1, this will always increment on each new set + * of parameters. + */ +// TODO Wire up the parameters +@CordaSerializable +data class NetworkParameters( + val minimumPlatformVersion: Int, + val notaries: List, + val eventHorizon: Duration, + val maxMessageSize: Int, + val maxTransactionSize: Int, + val modifiedTime: Instant, + val epoch: Int +) { + init { + require(minimumPlatformVersion > 0) { "minimumPlatformVersion must be at least 1" } + require(notaries.distinctBy { it.identity } == notaries) { "Duplicate notary identities" } + require(epoch > 0) { "epoch must be at least 1" } + } +} + +/** + * + */ +@CordaSerializable +data class NotaryInfo(val identity: Party, val validating: Boolean) diff --git a/core/src/main/kotlin/net/corda/core/node/services/NetworkMapCache.kt b/core/src/main/kotlin/net/corda/core/node/services/NetworkMapCache.kt index 5b5226087a..6cfe29dbec 100644 --- a/core/src/main/kotlin/net/corda/core/node/services/NetworkMapCache.kt +++ b/core/src/main/kotlin/net/corda/core/node/services/NetworkMapCache.kt @@ -53,7 +53,6 @@ interface NetworkMapCacheBase { * * Note that the identities are sorted based on legal name, and the ordering might change once new notaries are introduced. */ - // TODO this list will be taken from NetworkParameters distributed by NetworkMap. val notaryIdentities: List // DOCEND 1 @@ -117,7 +116,7 @@ interface NetworkMapCacheBase { fun getNotary(name: CordaX500Name): Party? = notaryIdentities.firstOrNull { it.name == name } // DOCEND 2 - /** Checks whether a given party is an advertised notary identity. */ + /** Returns true if and only if the given [Party] is a notary, which is defined by the network parameters. */ fun isNotary(party: Party): Boolean = party in notaryIdentities /** diff --git a/node/src/integration-test/kotlin/net/corda/node/services/BFTNotaryServiceTests.kt b/node/src/integration-test/kotlin/net/corda/node/services/BFTNotaryServiceTests.kt index 2b02e383ac..e7d05ff80e 100644 --- a/node/src/integration-test/kotlin/net/corda/node/services/BFTNotaryServiceTests.kt +++ b/node/src/integration-test/kotlin/net/corda/node/services/BFTNotaryServiceTests.kt @@ -13,6 +13,8 @@ import net.corda.core.identity.CordaX500Name import net.corda.core.identity.Party import net.corda.core.internal.deleteIfExists import net.corda.core.internal.div +import net.corda.core.node.NotaryInfo +import net.corda.core.node.services.NotaryService import net.corda.core.transactions.SignedTransaction import net.corda.core.transactions.TransactionBuilder import net.corda.core.utilities.NetworkHostAndPort @@ -21,11 +23,12 @@ import net.corda.core.utilities.getOrThrow import net.corda.node.internal.StartedNode import net.corda.node.services.config.BFTSMaRtConfiguration import net.corda.node.services.config.NotaryConfig -import net.corda.node.services.transactions.BFTNonValidatingNotaryService import net.corda.node.services.transactions.minClusterSize import net.corda.node.services.transactions.minCorrectReplicas import net.corda.node.utilities.ServiceIdentityGenerator import net.corda.testing.chooseIdentity +import net.corda.testing.common.internal.NetworkParametersCopier +import net.corda.testing.common.internal.testNetworkParameters import net.corda.testing.contracts.DummyContract import net.corda.testing.dummyCommand import net.corda.testing.node.MockNetwork @@ -54,19 +57,26 @@ class BFTNotaryServiceTests { notary = ServiceIdentityGenerator.generateToDisk( replicaIds.map { mockNet.baseDirectory(mockNet.nextNodeId + it) }, - CordaX500Name(BFTNonValidatingNotaryService.id, "BFT", "Zurich", "CH") - ) + CordaX500Name("BFT", "Zurich", "CH"), + NotaryService.constructId(validating = false, bft = true)) + + val networkParameters = NetworkParametersCopier(testNetworkParameters(listOf(NotaryInfo(notary, false)))) val clusterAddresses = replicaIds.map { NetworkHostAndPort("localhost", 11000 + it * 10) } - replicaIds.forEach { replicaId -> - mockNet.createNode(MockNodeParameters(configOverrides = { + val nodes = replicaIds.map { replicaId -> + mockNet.createUnstartedNode(MockNodeParameters(configOverrides = { val notary = NotaryConfig(validating = false, bftSMaRt = BFTSMaRtConfiguration(replicaId, clusterAddresses, exposeRaces = exposeRaces)) doReturn(notary).whenever(it).notary })) - } + } + mockNet.createUnstartedNode() - node = mockNet.createNode() + // MockNetwork doesn't support BFT clusters, so we create all the nodes we need unstarted, and then install the + // network-parameters in their directories before they're started. + node = nodes.map { node -> + networkParameters.install(mockNet.baseDirectory(node.id)) + node.start() + }.last() } /** Failure mode is the redundant replica gets stuck in startup, so we can't dispose it cleanly at the end. */ diff --git a/node/src/integration-test/kotlin/net/corda/node/services/DistributedServiceTests.kt b/node/src/integration-test/kotlin/net/corda/node/services/DistributedServiceTests.kt index ce5f107d93..15a8914e35 100644 --- a/node/src/integration-test/kotlin/net/corda/node/services/DistributedServiceTests.kt +++ b/node/src/integration-test/kotlin/net/corda/node/services/DistributedServiceTests.kt @@ -13,7 +13,6 @@ import net.corda.finance.flows.CashIssueFlow import net.corda.finance.flows.CashPaymentFlow import net.corda.node.services.Permissions.Companion.invokeRpc import net.corda.node.services.Permissions.Companion.startFlow -import net.corda.node.services.transactions.RaftValidatingNotaryService import net.corda.nodeapi.User import net.corda.testing.* import net.corda.testing.driver.NodeHandle @@ -42,7 +41,7 @@ class DistributedServiceTests { driver( extraCordappPackagesToScan = listOf("net.corda.finance.contracts"), - notarySpecs = listOf(NotarySpec(DUMMY_NOTARY.name.copy(commonName = RaftValidatingNotaryService.id), rpcUsers = listOf(testUser), cluster = ClusterSpec.Raft(clusterSize = 3)))) + notarySpecs = listOf(NotarySpec(DUMMY_NOTARY.name, rpcUsers = listOf(testUser), cluster = ClusterSpec.Raft(clusterSize = 3)))) { alice = startNode(providedName = ALICE.name, rpcUsers = listOf(testUser)).getOrThrow() raftNotaryIdentity = defaultNotaryIdentity diff --git a/node/src/main/kotlin/net/corda/node/internal/AbstractNode.kt b/node/src/main/kotlin/net/corda/node/internal/AbstractNode.kt index 89af824be0..28bebb688e 100644 --- a/node/src/main/kotlin/net/corda/node/internal/AbstractNode.kt +++ b/node/src/main/kotlin/net/corda/node/internal/AbstractNode.kt @@ -20,10 +20,7 @@ import net.corda.core.internal.concurrent.openFuture import net.corda.core.messaging.* import net.corda.core.node.* import net.corda.core.node.services.* -import net.corda.core.serialization.SerializationWhitelist -import net.corda.core.serialization.SerializeAsToken -import net.corda.core.serialization.SingletonSerializeAsToken -import net.corda.core.serialization.serialize +import net.corda.core.serialization.* import net.corda.core.transactions.SignedTransaction import net.corda.core.utilities.NetworkHostAndPort import net.corda.core.utilities.debug @@ -47,8 +44,15 @@ import net.corda.node.services.events.ScheduledActivityObserver import net.corda.node.services.identity.PersistentIdentityService import net.corda.node.services.keys.PersistentKeyManagementService import net.corda.node.services.messaging.MessagingService -import net.corda.node.services.network.* +import net.corda.node.services.network.NetworkMapCacheImpl +import net.corda.node.services.network.NodeInfoWatcher +import net.corda.node.services.network.PersistentNetworkMapCache import net.corda.node.services.persistence.* +import net.corda.node.services.network.* +import net.corda.node.services.persistence.DBCheckpointStorage +import net.corda.node.services.persistence.DBTransactionMappingStorage +import net.corda.node.services.persistence.DBTransactionStorage +import net.corda.node.services.persistence.NodeAttachmentService import net.corda.node.services.schema.HibernateObserver import net.corda.node.services.schema.NodeSchemaService import net.corda.node.services.statemachine.* @@ -86,6 +90,8 @@ import net.corda.core.crypto.generateKeyPair as cryptoGenerateKeyPair * Marked as SingletonSerializeAsToken to prevent the invisible reference to AbstractNode in the ServiceHub accidentally * sweeping up the Node into the Kryo checkpoint serialization via any flows holding a reference to ServiceHub. */ +// TODO Log warning if this node is a notary but not one of the ones specified in the network parameters, both for core and custom + // In theory the NodeInfo for the node should be passed in, instead, however currently this is constructed by the // AbstractNode. It should be possible to generate the NodeInfo outside of AbstractNode, so it can be passed in. abstract class AbstractNode(val configuration: NodeConfiguration, @@ -115,6 +121,7 @@ abstract class AbstractNode(val configuration: NodeConfiguration, // low-performance prototyping period. protected abstract val serverThread: AffinityExecutor + protected lateinit var networkParameters: NetworkParameters private val cordappServices = MutableClassToInstanceMap.create() private val flowFactories = ConcurrentHashMap>, InitiatedFlowFactory<*>>() @@ -180,6 +187,7 @@ abstract class AbstractNode(val configuration: NodeConfiguration, log.info("Node starting up ...") initCertificate() val (keyPairs, info) = initNodeInfo() + readNetworkParameters() val schemaService = NodeSchemaService(cordappLoader) // Do all of this in a database transaction so anything that might need a connection has one. val (startedImpl, schedulerService) = initialiseDatabasePersistence(schemaService) { database -> @@ -253,13 +261,18 @@ abstract class AbstractNode(val configuration: NodeConfiguration, val keyPairs = mutableSetOf(identityKeyPair) myNotaryIdentity = configuration.notary?.let { - val (notaryIdentity, notaryIdentityKeyPair) = obtainIdentity(it) - keyPairs += notaryIdentityKeyPair - notaryIdentity + if (it.isClusterConfig) { + val (notaryIdentity, notaryIdentityKeyPair) = obtainIdentity(it) + keyPairs += notaryIdentityKeyPair + notaryIdentity + } else { + // In case of a single notary service myNotaryIdentity will be the node's single identity. + identity + } } val info = NodeInfo( myAddresses(), - listOf(identity, myNotaryIdentity).filterNotNull(), + setOf(identity, myNotaryIdentity).filterNotNull(), versionInfo.platformVersion, platformClock.instant().toEpochMilli() ) @@ -588,6 +601,13 @@ abstract class AbstractNode(val configuration: NodeConfiguration, return PersistentKeyManagementService(identityService, keyPairs) } + private fun readNetworkParameters() { + val file = configuration.baseDirectory / "network-parameters" + networkParameters = file.readAll().deserialize>().verified() + log.info(networkParameters.toString()) + check(networkParameters.minimumPlatformVersion <= versionInfo.platformVersion) { "Node is too old for the network" } + } + private fun makeCoreNotaryService(notaryConfig: NotaryConfig, database: CordaPersistence): NotaryService { val notaryKey = myNotaryIdentity?.owningKey ?: throw IllegalArgumentException("No notary identity initialized when creating a notary service") return notaryConfig.run { @@ -643,22 +663,16 @@ abstract class AbstractNode(val configuration: NodeConfiguration, private fun obtainIdentity(notaryConfig: NotaryConfig?): Pair { val keyStore = KeyStoreWrapper(configuration.nodeKeystore, configuration.keyStorePassword) - val (id, singleName) = if (notaryConfig == null) { - // Node's main identity + val (id, singleName) = if (notaryConfig == null || !notaryConfig.isClusterConfig) { + // Node's main identity or if it's a single node notary Pair("identity", myLegalName) } else { val notaryId = notaryConfig.run { NotaryService.constructId(validating, raft != null, bftSMaRt != null, custom) } - if (!notaryConfig.isClusterConfig) { - // Node's notary identity - Pair(notaryId, myLegalName.copy(commonName = notaryId)) - } else { - // The node is part of a distributed notary whose identity must already be generated beforehand - Pair(notaryId, null) - } + // The node is part of a distributed notary whose identity must already be generated beforehand. + Pair(notaryId, null) } - // TODO: Integrate with Key management service? val privateKeyAlias = "$id-private-key" @@ -723,7 +737,13 @@ abstract class AbstractNode(val configuration: NodeConfiguration, override val stateMachineRecordedTransactionMapping = DBTransactionMappingStorage() override val auditService = DummyAuditService() override val transactionVerifierService by lazy { makeTransactionVerifierService() } - override val networkMapCache by lazy { NetworkMapCacheImpl(PersistentNetworkMapCache(database), identityService) } + override val networkMapCache by lazy { + NetworkMapCacheImpl( + PersistentNetworkMapCache( + database, + networkParameters.notaries), + identityService) + } override val vaultService by lazy { makeVaultService(keyManagementService, stateLoader, database.hibernateConfig) } override val contractUpgradeService by lazy { ContractUpgradeServiceImpl() } override val attachments: AttachmentStorage get() = this@AbstractNode.attachments diff --git a/node/src/main/kotlin/net/corda/node/services/config/NodeConfiguration.kt b/node/src/main/kotlin/net/corda/node/services/config/NodeConfiguration.kt index 607f3927b8..5d3b7cb3e3 100644 --- a/node/src/main/kotlin/net/corda/node/services/config/NodeConfiguration.kt +++ b/node/src/main/kotlin/net/corda/node/services/config/NodeConfiguration.kt @@ -120,7 +120,6 @@ data class NodeConfigurationImpl( // This is a sanity feature do not remove. require(!useTestClock || devMode) { "Cannot use test clock outside of dev mode" } require(devModeOptions == null || devMode) { "Cannot use devModeOptions outside of dev mode" } - require(myLegalName.commonName == null) { "Common name must be null: $myLegalName" } } } diff --git a/node/src/main/kotlin/net/corda/node/services/network/PersistentNetworkMapCache.kt b/node/src/main/kotlin/net/corda/node/services/network/PersistentNetworkMapCache.kt index 982f4da070..e454e52ac1 100644 --- a/node/src/main/kotlin/net/corda/node/services/network/PersistentNetworkMapCache.kt +++ b/node/src/main/kotlin/net/corda/node/services/network/PersistentNetworkMapCache.kt @@ -12,9 +12,9 @@ import net.corda.core.internal.concurrent.openFuture import net.corda.core.internal.schemas.NodeInfoSchemaV1 import net.corda.core.messaging.DataFeed import net.corda.core.node.NodeInfo +import net.corda.core.node.NotaryInfo import net.corda.core.node.services.IdentityService import net.corda.core.node.services.NetworkMapCache.MapChange -import net.corda.core.node.services.NotaryService import net.corda.core.node.services.PartyInfo import net.corda.core.serialization.SingletonSerializeAsToken import net.corda.core.serialization.serialize @@ -32,6 +32,7 @@ import java.security.PublicKey import java.util.* import javax.annotation.concurrent.ThreadSafe import kotlin.collections.HashMap +import kotlin.collections.HashSet class NetworkMapCacheImpl( networkMapCacheBase: NetworkMapCacheBaseInternal, @@ -61,13 +62,15 @@ class NetworkMapCacheImpl( * Extremely simple in-memory cache of the network map. */ @ThreadSafe -open class PersistentNetworkMapCache(private val database: CordaPersistence) : SingletonSerializeAsToken(), NetworkMapCacheBaseInternal { +open class PersistentNetworkMapCache( + private val database: CordaPersistence, + notaries: List +) : SingletonSerializeAsToken(), NetworkMapCacheBaseInternal { companion object { private val logger = contextLogger() } - // TODO Small explanation, partyNodes and registeredNodes is left in memory as it was before, because it will be removed in - // next PR that gets rid of services. These maps are used only for queries by service. + // TODO Cleanup registered and party nodes protected val registeredNodes: MutableMap = Collections.synchronizedMap(HashMap()) protected val partyNodes: MutableList get() = registeredNodes.map { it.value }.toMutableList() private val _changed = PublishSubject.create() @@ -81,22 +84,9 @@ open class PersistentNetworkMapCache(private val database: CordaPersistence) : S override val nodeReady: CordaFuture get() = _registrationFuture private var _loadDBSuccess: Boolean = false override val loadDBSuccess get() = _loadDBSuccess - // TODO From the NetworkMapService redesign doc: Remove the concept of network services. - // As a temporary hack, just assume for now that every network has a notary service named "Notary Service" that can be looked up in the map. - // This should eliminate the only required usage of services. - // It is ensured on node startup when constructing a notary that the name contains "notary". - override val notaryIdentities: List - get() { - return partyNodes - .flatMap { - // TODO: validate notary identity certificates before loading into network map cache. - // Notary certificates have to be signed by the doorman directly - it.legalIdentities - } - .filter { it.name.commonName?.startsWith(NotaryService.ID_PREFIX) ?: false } - .toSet() // Distinct, because of distributed service nodes - .sortedBy { it.name.toString() } - } + + override val notaryIdentities: List = notaries.map { it.identity } + private val validatingNotaries = notaries.mapNotNullTo(HashSet()) { if (it.validating) it.identity else null } init { database.transaction { loadFromDB(session) } @@ -127,7 +117,7 @@ open class PersistentNetworkMapCache(private val database: CordaPersistence) : S } } - override fun isValidatingNotary(party: Party): Boolean = isNotary(party) && "validating" in party.name.commonName!! + override fun isValidatingNotary(party: Party): Boolean = party in validatingNotaries override fun getPartyInfo(party: Party): PartyInfo? { val nodes = database.transaction { queryByIdentityKey(session, party.owningKey) } @@ -310,7 +300,6 @@ open class PersistentNetworkMapCache(private val database: CordaPersistence) : S id = 0, hash = nodeInfo.serialize().hash.toString(), addresses = nodeInfo.addresses.map { NodeInfoSchemaV1.DBHostAndPort.fromHostAndPort(it) }, - // TODO Another ugly hack with special first identity... legalIdentitiesAndCerts = nodeInfo.legalIdentitiesAndCerts.mapIndexed { idx, elem -> NodeInfoSchemaV1.DBPartyAndCertificate(elem, isMain = idx == 0) }, diff --git a/node/src/main/kotlin/net/corda/node/utilities/ServiceIdentityGenerator.kt b/node/src/main/kotlin/net/corda/node/utilities/ServiceIdentityGenerator.kt index 051f68c63c..9f5c6dbae8 100644 --- a/node/src/main/kotlin/net/corda/node/utilities/ServiceIdentityGenerator.kt +++ b/node/src/main/kotlin/net/corda/node/utilities/ServiceIdentityGenerator.kt @@ -19,11 +19,12 @@ object ServiceIdentityGenerator { * This method should be called *before* any of the nodes are started. * * @param dirs List of node directories to place the generated identity and key pairs in. - * @param serviceName The legal name of the distributed service, with service id as CN. + * @param serviceName The legal name of the distributed service. * @param threshold The threshold for the generated group [CompositeKey]. */ fun generateToDisk(dirs: List, serviceName: CordaX500Name, + serviceId: String, threshold: Int = 1): Party { log.trace { "Generating a group identity \"serviceName\" for nodes: ${dirs.joinToString()}" } val keyPairs = (1..dirs.size).map { generateKeyPair() } @@ -38,7 +39,6 @@ object ServiceIdentityGenerator { val compositeKeyCert = X509Utilities.createCertificate(CertificateType.CLIENT_CA, issuer.certificate, issuer.keyPair, serviceName, notaryKey) val certPath = (dir / "certificates").createDirectories() / "distributedService.jks" val keystore = loadOrCreateKeyStore(certPath, "cordacadevpass") - val serviceId = serviceName.commonName keystore.setCertificateEntry("$serviceId-composite-key", compositeKeyCert.cert) keystore.setKeyEntry("$serviceId-private-key", keyPair.private, "cordacadevkeypass".toCharArray(), arrayOf(serviceKeyCert.cert, issuer.certificate.cert, rootCert)) keystore.save(certPath, "cordacadevpass") diff --git a/node/src/test/kotlin/net/corda/node/services/NotaryChangeTests.kt b/node/src/test/kotlin/net/corda/node/services/NotaryChangeTests.kt index ee3e96d280..78539f9c39 100644 --- a/node/src/test/kotlin/net/corda/node/services/NotaryChangeTests.kt +++ b/node/src/test/kotlin/net/corda/node/services/NotaryChangeTests.kt @@ -28,10 +28,6 @@ import kotlin.test.assertEquals import kotlin.test.assertTrue class NotaryChangeTests { - companion object { - private val DUMMY_NOTARY_SERVICE_NAME: CordaX500Name = DUMMY_NOTARY.name.copy(commonName = "corda.notary.validating") - } - private lateinit var mockNet: MockNetwork private lateinit var oldNotaryNode: StartedNode private lateinit var clientNodeA: StartedNode @@ -42,7 +38,7 @@ class NotaryChangeTests { @Before fun setUp() { - val oldNotaryName = DUMMY_NOTARY.name.copy(organisation = "Old Dummy Notary") + val oldNotaryName = DUMMY_REGULATOR.name mockNet = MockNetwork( notarySpecs = listOf(NotarySpec(DUMMY_NOTARY.name), NotarySpec(oldNotaryName)), cordappPackages = listOf("net.corda.testing.contracts") @@ -51,8 +47,8 @@ class NotaryChangeTests { clientNodeB = mockNet.createNode(MockNodeParameters(legalName = BOB_NAME)) clientA = clientNodeA.info.singleIdentity() oldNotaryNode = mockNet.notaryNodes[1] - newNotaryParty = clientNodeA.services.networkMapCache.getNotary(DUMMY_NOTARY_SERVICE_NAME)!! - oldNotaryParty = clientNodeA.services.networkMapCache.getNotary(DUMMY_NOTARY_SERVICE_NAME.copy(organisation = "Old Dummy Notary"))!! + newNotaryParty = clientNodeA.services.networkMapCache.getNotary(DUMMY_NOTARY.name)!! + oldNotaryParty = clientNodeA.services.networkMapCache.getNotary(oldNotaryName)!! } @After diff --git a/node/src/test/kotlin/net/corda/node/services/messaging/ArtemisMessagingTests.kt b/node/src/test/kotlin/net/corda/node/services/messaging/ArtemisMessagingTests.kt index 225ea6952f..4e38e41f40 100644 --- a/node/src/test/kotlin/net/corda/node/services/messaging/ArtemisMessagingTests.kt +++ b/node/src/test/kotlin/net/corda/node/services/messaging/ArtemisMessagingTests.kt @@ -70,7 +70,7 @@ class ArtemisMessagingTests { LogHelper.setLevel(PersistentUniquenessProvider::class) database = configureDatabase(makeTestDataSourceProperties(), makeTestDatabaseProperties(), ::makeTestIdentityService) networkMapRegistrationFuture = doneFuture(Unit) - networkMapCache = NetworkMapCacheImpl(PersistentNetworkMapCache(database), rigorousMock()) + networkMapCache = NetworkMapCacheImpl(PersistentNetworkMapCache(database, emptyList()), rigorousMock()) } @After diff --git a/samples/notary-demo/src/main/kotlin/net/corda/notarydemo/BFTNotaryCordform.kt b/samples/notary-demo/src/main/kotlin/net/corda/notarydemo/BFTNotaryCordform.kt index e0919a43ab..cb0be14ee0 100644 --- a/samples/notary-demo/src/main/kotlin/net/corda/notarydemo/BFTNotaryCordform.kt +++ b/samples/notary-demo/src/main/kotlin/net/corda/notarydemo/BFTNotaryCordform.kt @@ -5,6 +5,7 @@ import net.corda.cordform.CordformDefinition import net.corda.cordform.CordformNode import net.corda.core.identity.CordaX500Name import net.corda.core.internal.div +import net.corda.core.node.services.NotaryService import net.corda.core.utilities.NetworkHostAndPort import net.corda.node.services.config.BFTSMaRtConfiguration import net.corda.node.services.config.NotaryConfig @@ -62,6 +63,10 @@ class BFTNotaryCordform : CordformDefinition("build" / "notary-demo-nodes") { } override fun setup(context: CordformContext) { - ServiceIdentityGenerator.generateToDisk(notaryNames.map { context.baseDirectory(it.toString()) }, clusterName, threshold = minCorrectReplicas(clusterSize)) + ServiceIdentityGenerator.generateToDisk( + notaryNames.map { context.baseDirectory(it.toString()) }, + clusterName, + NotaryService.constructId(validating = false, bft = true), + minCorrectReplicas(clusterSize)) } } diff --git a/samples/notary-demo/src/main/kotlin/net/corda/notarydemo/RaftNotaryCordform.kt b/samples/notary-demo/src/main/kotlin/net/corda/notarydemo/RaftNotaryCordform.kt index ff128c6edf..677ec08a9a 100644 --- a/samples/notary-demo/src/main/kotlin/net/corda/notarydemo/RaftNotaryCordform.kt +++ b/samples/notary-demo/src/main/kotlin/net/corda/notarydemo/RaftNotaryCordform.kt @@ -5,6 +5,7 @@ import net.corda.cordform.CordformDefinition import net.corda.cordform.CordformNode import net.corda.core.identity.CordaX500Name import net.corda.core.internal.div +import net.corda.core.node.services.NotaryService import net.corda.core.utilities.NetworkHostAndPort import net.corda.node.services.config.NotaryConfig import net.corda.node.services.config.RaftConfig @@ -58,6 +59,9 @@ class RaftNotaryCordform : CordformDefinition("build" / "notary-demo-nodes") { } override fun setup(context: CordformContext) { - ServiceIdentityGenerator.generateToDisk(notaryNames.map { context.baseDirectory(it.toString()) }, clusterName) + ServiceIdentityGenerator.generateToDisk( + notaryNames.map { context.baseDirectory(it.toString()) }, + clusterName, + NotaryService.constructId(validating = true, raft = true)) } } diff --git a/testing/node-driver/src/main/kotlin/net/corda/testing/driver/Driver.kt b/testing/node-driver/src/main/kotlin/net/corda/testing/driver/Driver.kt index 754b57386e..5a9e0d7c6c 100644 --- a/testing/node-driver/src/main/kotlin/net/corda/testing/driver/Driver.kt +++ b/testing/node-driver/src/main/kotlin/net/corda/testing/driver/Driver.kt @@ -16,7 +16,9 @@ import net.corda.core.identity.Party import net.corda.core.internal.* import net.corda.core.internal.concurrent.* import net.corda.core.messaging.CordaRPCOps +import net.corda.core.node.NetworkParameters import net.corda.core.node.NodeInfo +import net.corda.core.node.NotaryInfo import net.corda.core.node.services.NetworkMapCache import net.corda.core.node.services.NotaryService import net.corda.core.toFuture @@ -35,6 +37,8 @@ import net.corda.nodeapi.User import net.corda.nodeapi.config.toConfig import net.corda.nodeapi.internal.addShutdownHook import net.corda.testing.* +import net.corda.testing.common.internal.NetworkParametersCopier +import net.corda.testing.common.internal.testNetworkParameters import net.corda.testing.internal.ProcessUtilities import net.corda.testing.node.ClusterSpec import net.corda.testing.node.MockServices.Companion.MOCK_VERSION_INFO @@ -602,6 +606,7 @@ class DriverDSL( private val countObservables = mutableMapOf>() private lateinit var _notaries: List override val notaryHandles: List get() = _notaries + private lateinit var networkParameters: NetworkParametersCopier class State { val processes = ArrayList() @@ -755,29 +760,33 @@ class DriverDSL( _shutdownManager = ShutdownManager(executorService) shutdownManager.registerShutdown { nodeInfoFilesCopier.close() } val notaryInfos = generateNotaryIdentities() + // The network parameters must be serialised before starting any of the nodes + networkParameters = NetworkParametersCopier(testNetworkParameters(notaryInfos)) val nodeHandles = startNotaries() _notaries = notaryInfos.zip(nodeHandles) { (identity, validating), nodes -> NotaryHandle(identity, validating, nodes) } } - private fun generateNotaryIdentities(): List> { + private fun generateNotaryIdentities(): List { return notarySpecs.map { spec -> val identity = if (spec.cluster == null) { ServiceIdentityGenerator.generateToDisk( dirs = listOf(baseDirectory(spec.name)), - serviceName = spec.name.copy(commonName = NotaryService.constructId(validating = spec.validating)) - ) + serviceName = spec.name, + serviceId = "identity") } else { ServiceIdentityGenerator.generateToDisk( dirs = generateNodeNames(spec).map { baseDirectory(it) }, - serviceName = spec.name - ) + serviceName = spec.name, + serviceId = NotaryService.constructId( + validating = spec.validating, + raft = spec.cluster is ClusterSpec.Raft)) } - Pair(identity, spec.validating) + NotaryInfo(identity, spec.validating) } } private fun generateNodeNames(spec: NotarySpec): List { - return (0 until spec.cluster!!.clusterSize).map { spec.name.copy(commonName = null, organisation = "${spec.name.organisation}-$it") } + return (0 until spec.cluster!!.clusterSize).map { spec.name.copy(organisation = "${spec.name.organisation}-$it") } } private fun startNotaries(): List>> { @@ -914,6 +923,7 @@ class DriverDSL( maximumHeapSize: String): CordaFuture { val configuration = config.parseAsNodeConfiguration() val baseDirectory = configuration.baseDirectory.createDirectories() + networkParameters.install(baseDirectory) nodeInfoFilesCopier.addConfig(baseDirectory) val onNodeExit: () -> Unit = { nodeInfoFilesCopier.removeConfig(baseDirectory) diff --git a/testing/node-driver/src/main/kotlin/net/corda/testing/internal/NodeBasedTest.kt b/testing/node-driver/src/main/kotlin/net/corda/testing/internal/NodeBasedTest.kt index 8b32eabd37..8a107a0fe6 100644 --- a/testing/node-driver/src/main/kotlin/net/corda/testing/internal/NodeBasedTest.kt +++ b/testing/node-driver/src/main/kotlin/net/corda/testing/internal/NodeBasedTest.kt @@ -16,11 +16,14 @@ import net.corda.node.services.config.parseAsNodeConfiguration import net.corda.node.services.config.plus import net.corda.nodeapi.User import net.corda.testing.SerializationEnvironmentRule +import net.corda.testing.common.internal.NetworkParametersCopier +import net.corda.testing.common.internal.testNetworkParameters import net.corda.testing.driver.addressMustNotBeBoundFuture import net.corda.testing.getFreeLocalPorts import net.corda.testing.node.MockServices import org.apache.logging.log4j.Level import org.junit.After +import org.junit.Before import org.junit.Rule import org.junit.rules.TemporaryFolder import java.nio.file.Path @@ -40,6 +43,7 @@ abstract class NodeBasedTest(private val cordappPackages: List = emptyLi @JvmField val tempFolder = TemporaryFolder() + private lateinit var defaultNetworkParameters: NetworkParametersCopier private val nodes = mutableListOf>() private val nodeInfos = mutableListOf() @@ -47,6 +51,11 @@ abstract class NodeBasedTest(private val cordappPackages: List = emptyLi System.setProperty("consoleLogLevel", Level.DEBUG.name().toLowerCase()) } + @Before + fun init() { + defaultNetworkParameters = NetworkParametersCopier(testNetworkParameters(emptyList())) + } + /** * Stops the network map node and all the nodes started by [startNode]. This is called automatically after each test * but can also be called manually within a test. @@ -90,6 +99,7 @@ abstract class NodeBasedTest(private val cordappPackages: List = emptyLi ) val parsedConfig = config.parseAsNodeConfiguration() + defaultNetworkParameters.install(baseDirectory) val node = Node( parsedConfig, MockServices.MOCK_VERSION_INFO.copy(platformVersion = platformVersion), diff --git a/testing/node-driver/src/main/kotlin/net/corda/testing/internal/demorun/DemoRunner.kt b/testing/node-driver/src/main/kotlin/net/corda/testing/internal/demorun/DemoRunner.kt index 188b6a30de..958ac48503 100644 --- a/testing/node-driver/src/main/kotlin/net/corda/testing/internal/demorun/DemoRunner.kt +++ b/testing/node-driver/src/main/kotlin/net/corda/testing/internal/demorun/DemoRunner.kt @@ -15,6 +15,7 @@ fun CordformDefinition.clean() { /** * Creates and starts all nodes required for the demo. */ +// TODO add notaries to cordform! fun CordformDefinition.runNodes() { driver(isDebug = true, driverDirectory = driverDirectory, portAllocation = PortAllocation.Incremental(10001), waitForAllNodesToFinish = true) { setup(this) diff --git a/testing/node-driver/src/main/kotlin/net/corda/testing/node/MockNetworkMapCache.kt b/testing/node-driver/src/main/kotlin/net/corda/testing/node/MockNetworkMapCache.kt index e70a9d4237..26167cbe11 100644 --- a/testing/node-driver/src/main/kotlin/net/corda/testing/node/MockNetworkMapCache.kt +++ b/testing/node-driver/src/main/kotlin/net/corda/testing/node/MockNetworkMapCache.kt @@ -7,6 +7,7 @@ import net.corda.core.internal.concurrent.doneFuture import net.corda.core.node.NodeInfo import net.corda.core.node.services.NetworkMapCache import net.corda.core.utilities.NetworkHostAndPort +import net.corda.node.services.config.NodeConfiguration import net.corda.node.services.network.PersistentNetworkMapCache import net.corda.node.utilities.CordaPersistence import net.corda.testing.getTestPartyAndCertificate @@ -17,7 +18,7 @@ import java.math.BigInteger /** * Network map cache with no backing map service. */ -class MockNetworkMapCache(database: CordaPersistence) : PersistentNetworkMapCache(database) { +class MockNetworkMapCache(database: CordaPersistence) : PersistentNetworkMapCache(database, emptyList()) { private companion object { val BANK_C = getTestPartyAndCertificate(CordaX500Name(organisation = "Bank C", locality = "London", country = "GB"), entropyToKeyPair(BigInteger.valueOf(1000)).public) val BANK_D = getTestPartyAndCertificate(CordaX500Name(organisation = "Bank D", locality = "London", country = "GB"), entropyToKeyPair(BigInteger.valueOf(2000)).public) diff --git a/testing/node-driver/src/main/kotlin/net/corda/testing/node/MockNode.kt b/testing/node-driver/src/main/kotlin/net/corda/testing/node/MockNode.kt index f10b8f9e5e..7d883e1a84 100644 --- a/testing/node-driver/src/main/kotlin/net/corda/testing/node/MockNode.kt +++ b/testing/node-driver/src/main/kotlin/net/corda/testing/node/MockNode.kt @@ -17,6 +17,7 @@ import net.corda.core.messaging.MessageRecipients import net.corda.core.messaging.RPCOps import net.corda.core.messaging.SingleMessageRecipient import net.corda.core.node.NodeInfo +import net.corda.core.node.NotaryInfo import net.corda.core.node.services.IdentityService import net.corda.core.node.services.KeyManagementService import net.corda.core.serialization.SerializationWhitelist @@ -37,10 +38,13 @@ import net.corda.node.services.transactions.InMemoryTransactionVerifierService import net.corda.node.utilities.AffinityExecutor import net.corda.node.utilities.AffinityExecutor.ServiceAffinityExecutor import net.corda.node.utilities.CordaPersistence +import net.corda.node.utilities.ServiceIdentityGenerator import net.corda.testing.DUMMY_NOTARY +import net.corda.testing.common.internal.NetworkParametersCopier +import net.corda.testing.common.internal.testNetworkParameters +import net.corda.testing.setGlobalSerialization import net.corda.testing.node.MockServices.Companion.MOCK_VERSION_INFO import net.corda.testing.node.MockServices.Companion.makeTestDataSourceProperties -import net.corda.testing.setGlobalSerialization import net.corda.testing.testNodeConfiguration import org.apache.activemq.artemis.utils.ReusableLatch import org.apache.sshd.common.util.security.SecurityUtils @@ -138,6 +142,7 @@ class MockNetwork(defaultParameters: MockNetworkParameters = MockNetworkParamete val messagingNetwork = InMemoryMessagingNetwork(networkSendManuallyPumped, servicePeerAllocationStrategy, busyLatch) // A unique identifier for this network to segregate databases with the same nodeID but different networks. private val networkId = random63BitValue() + private val networkParameters: NetworkParametersCopier private val _nodes = mutableListOf() private val serializationEnv = setGlobalSerialization(initialiseSerialization) private val sharedUserCount = AtomicInteger(0) @@ -167,7 +172,7 @@ class MockNetwork(defaultParameters: MockNetworkParameters = MockNetworkParamete * @see defaultNotaryNode */ val defaultNotaryIdentity: Party get() { - return defaultNotaryNode.info.legalIdentities[1] // TODO Resolve once network parameters is merged back in + return defaultNotaryNode.info.legalIdentities.singleOrNull() ?: throw IllegalStateException("Default notary has multiple identities") } /** @@ -202,9 +207,22 @@ class MockNetwork(defaultParameters: MockNetworkParameters = MockNetworkParamete init { filesystem.getPath("/nodes").createDirectory() + val notaryInfos = generateNotaryIdentities() + // The network parameters must be serialised before starting any of the nodes + networkParameters = NetworkParametersCopier(testNetworkParameters(notaryInfos)) notaryNodes = createNotaries() } + private fun generateNotaryIdentities(): List { + return notarySpecs.mapIndexed { index, spec -> + val identity = ServiceIdentityGenerator.generateToDisk( + dirs = listOf(baseDirectory(nextNodeId + index)), + serviceName = spec.name, + serviceId = "identity") + NotaryInfo(identity, spec.validating) + } + } + private fun createNotaries(): List> { return notarySpecs.map { spec -> createNode(MockNodeParameters(legalName = spec.name, configOverrides = { @@ -240,6 +258,7 @@ class MockNetwork(defaultParameters: MockNetworkParameters = MockNetworkParamete override val started: StartedNode? get() = uncheckedCast(super.started) override fun start(): StartedNode { + mockNet.networkParameters.install(configuration.baseDirectory) val started: StartedNode = uncheckedCast(super.start()) advertiseNodeToNetwork(started) return started diff --git a/testing/node-driver/src/main/kotlin/net/corda/testing/node/NotarySpec.kt b/testing/node-driver/src/main/kotlin/net/corda/testing/node/NotarySpec.kt index 852d3e899a..336d6bfc8d 100644 --- a/testing/node-driver/src/main/kotlin/net/corda/testing/node/NotarySpec.kt +++ b/testing/node-driver/src/main/kotlin/net/corda/testing/node/NotarySpec.kt @@ -2,7 +2,6 @@ package net.corda.testing.node import net.corda.core.identity.CordaX500Name import net.corda.node.services.config.VerifierType -import net.corda.node.services.transactions.RaftValidatingNotaryService import net.corda.nodeapi.User data class NotarySpec( @@ -11,15 +10,7 @@ data class NotarySpec( val rpcUsers: List = emptyList(), val verifierType: VerifierType = VerifierType.InMemory, val cluster: ClusterSpec? = null -) { - init { - // TODO This will be removed once network parameters define the notaries - when (cluster) { - is ClusterSpec.Raft -> require(name.commonName == RaftValidatingNotaryService.id) - null -> require(name.commonName == null) - } - } -} +) sealed class ClusterSpec { abstract val clusterSize: Int diff --git a/testing/smoke-test-utils/src/main/kotlin/net/corda/smoketesting/NodeProcess.kt b/testing/smoke-test-utils/src/main/kotlin/net/corda/smoketesting/NodeProcess.kt index b6656977b7..250161faaf 100644 --- a/testing/smoke-test-utils/src/main/kotlin/net/corda/smoketesting/NodeProcess.kt +++ b/testing/smoke-test-utils/src/main/kotlin/net/corda/smoketesting/NodeProcess.kt @@ -2,11 +2,15 @@ package net.corda.smoketesting import net.corda.client.rpc.CordaRPCClient import net.corda.client.rpc.CordaRPCConnection +import net.corda.client.rpc.internal.KryoClientSerializationScheme import net.corda.core.internal.copyTo import net.corda.core.internal.createDirectories import net.corda.core.internal.div import net.corda.core.utilities.NetworkHostAndPort import net.corda.core.utilities.contextLogger +import net.corda.testing.common.internal.NetworkParametersCopier +import net.corda.testing.common.internal.testNetworkParameters +import net.corda.testing.common.internal.asContextEnv import java.nio.file.Path import java.nio.file.Paths import java.time.Instant @@ -49,6 +53,14 @@ class NodeProcess( private companion object { val javaPath: Path = Paths.get(System.getProperty("java.home"), "bin", "java") val formatter: DateTimeFormatter = DateTimeFormatter.ofPattern("yyyyMMddHHmmss").withZone(systemDefault()) + val defaultNetworkParameters = run { + KryoClientSerializationScheme.createSerializationEnv().asContextEnv { + // There are no notaries in the network parameters for smoke test nodes. If this is required then we would + // need to introduce the concept of a "network" which predefines the notaries, like the driver and MockNetwork + NetworkParametersCopier(testNetworkParameters(emptyList())) + } + } + init { try { Class.forName("net.corda.node.Corda") @@ -68,6 +80,7 @@ class NodeProcess( log.info("Node directory: {}", nodeDir) config.toText().byteInputStream().copyTo(nodeDir / "node.conf") + defaultNetworkParameters.install(nodeDir) val process = startNode(nodeDir) val client = CordaRPCClient(NetworkHostAndPort("localhost", config.rpcPort)) diff --git a/testing/test-common/src/main/kotlin/net/corda/testing/common/internal/NetworkParametersCopier.kt b/testing/test-common/src/main/kotlin/net/corda/testing/common/internal/NetworkParametersCopier.kt new file mode 100644 index 0000000000..25ba327a73 --- /dev/null +++ b/testing/test-common/src/main/kotlin/net/corda/testing/common/internal/NetworkParametersCopier.kt @@ -0,0 +1,32 @@ +package net.corda.testing.common.internal + +import net.corda.core.crypto.SignedData +import net.corda.core.crypto.entropyToKeyPair +import net.corda.core.crypto.sign +import net.corda.core.internal.copyTo +import net.corda.core.internal.div +import net.corda.core.node.NetworkParameters +import net.corda.core.serialization.serialize +import java.math.BigInteger +import java.nio.file.FileAlreadyExistsException +import java.nio.file.Path + +class NetworkParametersCopier(networkParameters: NetworkParameters) { + private companion object { + val DUMMY_MAP_KEY = entropyToKeyPair(BigInteger.valueOf(123)) + } + + private val serializedNetworkParameters = networkParameters.let { + val serialize = it.serialize() + val signature = DUMMY_MAP_KEY.sign(serialize) + SignedData(serialize, signature).serialize() + } + + fun install(dir: Path) { + try { + serializedNetworkParameters.open().copyTo(dir / "network-parameters") + } catch (e: FileAlreadyExistsException) { + // Leave the file untouched if it already exists + } + } +} \ No newline at end of file diff --git a/testing/test-common/src/main/kotlin/net/corda/testing/common/internal/ParametersUtilities.kt b/testing/test-common/src/main/kotlin/net/corda/testing/common/internal/ParametersUtilities.kt new file mode 100644 index 0000000000..da40ba6ac3 --- /dev/null +++ b/testing/test-common/src/main/kotlin/net/corda/testing/common/internal/ParametersUtilities.kt @@ -0,0 +1,18 @@ +package net.corda.testing.common.internal + +import net.corda.core.node.NetworkParameters +import net.corda.core.node.NotaryInfo +import net.corda.core.utilities.days +import java.time.Instant + +fun testNetworkParameters(notaries: List): NetworkParameters { + return NetworkParameters( + minimumPlatformVersion = 1, + notaries = notaries, + modifiedTime = Instant.now(), + eventHorizon = 10000.days, + maxMessageSize = 40000, + maxTransactionSize = 40000, + epoch = 1 + ) +} \ No newline at end of file diff --git a/tools/loadtest/src/main/kotlin/net/corda/loadtest/tests/NotaryTest.kt b/tools/loadtest/src/main/kotlin/net/corda/loadtest/tests/NotaryTest.kt index b96f6f4f40..88b517b9db 100644 --- a/tools/loadtest/src/main/kotlin/net/corda/loadtest/tests/NotaryTest.kt +++ b/tools/loadtest/src/main/kotlin/net/corda/loadtest/tests/NotaryTest.kt @@ -24,7 +24,7 @@ val dummyNotarisationTest = LoadTest( val issuerServices = MockServices(DUMMY_CASH_ISSUER_KEY) val generateTx = Generator.pickOne(simpleNodes).flatMap { node -> Generator.int().map { - val issueBuilder = DummyContract.generateInitial(it, notary.info.legalIdentities[1], DUMMY_CASH_ISSUER) // TODO notary choice + val issueBuilder = DummyContract.generateInitial(it, notary.info.legalIdentities[0], DUMMY_CASH_ISSUER) // TODO notary choice val issueTx = issuerServices.signInitialTransaction(issueBuilder) val asset = issueTx.tx.outRef(0) val moveBuilder = DummyContract.move(asset, DUMMY_CASH_ISSUER.party) From eede51cf74e7acd3170a8a98ccb13d6e76fe6ec2 Mon Sep 17 00:00:00 2001 From: Shams Asari Date: Tue, 21 Nov 2017 15:39:31 +0000 Subject: [PATCH 04/23] Update method added to MockNetwork since branching --- .../src/main/kotlin/net/corda/testing/node/MockNode.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/testing/node-driver/src/main/kotlin/net/corda/testing/node/MockNode.kt b/testing/node-driver/src/main/kotlin/net/corda/testing/node/MockNode.kt index 7d883e1a84..bda4b53ed5 100644 --- a/testing/node-driver/src/main/kotlin/net/corda/testing/node/MockNode.kt +++ b/testing/node-driver/src/main/kotlin/net/corda/testing/node/MockNode.kt @@ -180,7 +180,7 @@ class MockNetwork(defaultParameters: MockNetworkParameters = MockNetworkParamete * @see defaultNotaryNode */ val defaultNotaryIdentityAndCert: PartyAndCertificate get() { - return defaultNotaryNode.info.legalIdentitiesAndCerts[1] // TODO Resolve once network parameters is merged back in + return defaultNotaryNode.info.legalIdentitiesAndCerts.singleOrNull() ?: throw IllegalStateException("Default notary has multiple identities") } /** From 20b57a359ff3b2b6bf908829f074497a910b4ab5 Mon Sep 17 00:00:00 2001 From: Joel Dudley Date: Wed, 22 Nov 2017 12:07:53 +0000 Subject: [PATCH 05/23] Removes outdated devmode cert info, links to existing docs to avoid duplication. --- docs/source/setting-up-a-corda-network.rst | 30 ++++++++++------------ 1 file changed, 14 insertions(+), 16 deletions(-) diff --git a/docs/source/setting-up-a-corda-network.rst b/docs/source/setting-up-a-corda-network.rst index 41a44d300f..c5e43e01fd 100644 --- a/docs/source/setting-up-a-corda-network.rst +++ b/docs/source/setting-up-a-corda-network.rst @@ -3,15 +3,20 @@ Creating a Corda network ======================== -A Corda network consists of a number of machines running nodes. These nodes communicate using persistent protocols in order to create and validate transactions. +A Corda network consists of a number of machines running nodes. These nodes communicate using persistent protocols in +order to create and validate transactions. -There are four broader categories of functionality one such node may have. These pieces of functionality are provided as -services, and one node may run several of them. +There are four broader categories of functionality one such node may have. These pieces of functionality are provided +as services, and one node may run several of them. -* Network map: The node running the network map provides a way to resolve identities to physical node addresses and associated public keys. -* Notary: Nodes running a notary service witness state spends and have the final say in whether a transaction is a double-spend or not. -* Oracle: Network services that link the ledger to the outside world by providing facts that affect the validity of transactions. -* Regular node: All nodes have a vault and may start protocols communicating with other nodes, notaries and oracles and evolve their private ledger. +* Network map: The node running the network map provides a way to resolve identities to physical node addresses and + associated public keys +* Notary: Nodes running a notary service witness state spends and have the final say in whether a transaction is a + double-spend or not +* Oracle: Network services that link the ledger to the outside world by providing facts that affect the validity of + transactions +* Regular node: All nodes have a vault and may start protocols communicating with other nodes, notaries and oracles and + evolve their private ledger Setting up your own network --------------------------- @@ -19,15 +24,8 @@ Setting up your own network Certificates ~~~~~~~~~~~~ -All nodes belonging to the same Corda network must have the same root CA. For testing purposes you can -use ``certSigningRequestUtility.jar`` to generate a node certificate with a fixed test root: - -.. sourcecode:: bash - - # Build the jars - ./gradlew buildCordaJAR - # Generate certificate - java -jar build/libs/certSigningRequestUtility.jar --base-dir NODE_DIRECTORY/ +Every node in a given Corda network must have an identity certificate signed by the network's root CA. See +:doc:`permissioning` for more information. Configuration ~~~~~~~~~~~~~ From 8bf6dae29dc65ab9b60c34512bb43b3018adc3dc Mon Sep 17 00:00:00 2001 From: Ross Nicoll Date: Wed, 22 Nov 2017 15:39:48 +0000 Subject: [PATCH 06/23] Re-introduce demo docs (#1981) Document the included samples in the docs again so there's some continuity from v1 docs, while making it clear they're not best practice examples but instead illustrate solutions to various use-cases. --- docs/source/building-a-cordapp-index.rst | 3 ++- docs/source/building-a-cordapp-samples.rst | 21 +++++++++++++++++++++ docs/source/quickstart-index.rst | 1 + docs/source/running-the-demos.rst | 0 4 files changed, 24 insertions(+), 1 deletion(-) create mode 100644 docs/source/building-a-cordapp-samples.rst delete mode 100644 docs/source/running-the-demos.rst diff --git a/docs/source/building-a-cordapp-index.rst b/docs/source/building-a-cordapp-index.rst index b3b4bd8eb6..a1373f9849 100644 --- a/docs/source/building-a-cordapp-index.rst +++ b/docs/source/building-a-cordapp-index.rst @@ -10,4 +10,5 @@ CorDapps building-against-master corda-api flow-cookbook - cheat-sheet \ No newline at end of file + cheat-sheet + building-a-cordapp-samples diff --git a/docs/source/building-a-cordapp-samples.rst b/docs/source/building-a-cordapp-samples.rst new file mode 100644 index 0000000000..40affe1a26 --- /dev/null +++ b/docs/source/building-a-cordapp-samples.rst @@ -0,0 +1,21 @@ +CorDapp samples +=============== + +There are two distinct sets of samples provided with Corda, one introducing new developers to how to write CorDapps, and +more complex worked examples of how solutions to a number of common designs could be implemented in a CorDapp. +The former can be found on `the Corda website `_. In particular, new developers +should start with the :doc:`example CorDapp `. + +The advanced samples are contained within the `samples/` folder of the Corda repository. The most generally useful of +these samples are: + +1. The `trader-demo`, which shows a delivery-vs-payment atomic swap of commercial paper for cash +2. The `attachment-demo`, which demonstrates uploading attachments to nodes +3. The `bank-of-corda-demo`, which shows a node acting as an issuer of assets (the Bank of Corda) while remote client + applications request issuance of some cash on behalf of a node called Big Corporation + +Documentation on running the samples can be found inside the sample directories themselves, in the `README.md` file. + +.. note:: If you would like to see flow activity on the nodes type in the node terminal ``flow watch``. + +Please report any bugs with the samples on `GitHub `_. diff --git a/docs/source/quickstart-index.rst b/docs/source/quickstart-index.rst index a42b469e95..02e9045ac9 100644 --- a/docs/source/quickstart-index.rst +++ b/docs/source/quickstart-index.rst @@ -7,4 +7,5 @@ Quickstart getting-set-up tutorial-cordapp Sample CorDapps + building-against-master CLI-vs-IDE \ No newline at end of file diff --git a/docs/source/running-the-demos.rst b/docs/source/running-the-demos.rst deleted file mode 100644 index e69de29bb2..0000000000 From 228b29110e256e7948d1e6164d77bece7c71aa00 Mon Sep 17 00:00:00 2001 From: Joel Dudley Date: Wed, 22 Nov 2017 17:33:40 +0000 Subject: [PATCH 07/23] Adds upgrade notes for v1 to v2. Minor tweaks (e.g. ToC). --- docs/source/upgrade-notes.rst | 45 ++++++++++++++++++++++++----------- 1 file changed, 31 insertions(+), 14 deletions(-) diff --git a/docs/source/upgrade-notes.rst b/docs/source/upgrade-notes.rst index 1dcb0d72b4..7ddbd362f1 100644 --- a/docs/source/upgrade-notes.rst +++ b/docs/source/upgrade-notes.rst @@ -1,11 +1,14 @@ Upgrade notes ============= -These notes provide helpful instructions to upgrade your Corda Applications (CorDapps) from previous versions, starting -from our first public Beta (:ref:`Milestone 12 `), to :ref:`V1.0 ` +These notes provide instructions for upgrading your CorDapps from previous versions, starting with the upgrade from our +first public Beta (:ref:`Milestone 12 `), to :ref:`V1.0 `. -General -------- +.. contents:: + :depth: 3 + +General rules +------------- Always remember to update the version identifiers in your project gradle file: .. sourcecode:: shell @@ -29,7 +32,7 @@ UNRELEASED ---------- Testing -^^^^^^^ +~~~~~~~ * The registration mechanism for CorDapps in ``MockNetwork`` unit tests has changed. @@ -38,10 +41,24 @@ Testing package names of the CorDapps containing the contract verification code you wish to load. The ``unsetCordappPackages`` method is now redundant and has been removed. -:ref:`Milestone 14 ` +V1.0 to V2.0 ------------ -Build +You only need to update the ``corda_release_version`` identifier in your project gradle file. The +corda_gradle_plugins_version should remain at 1.0.0: + +.. sourcecode:: shell + + ext.corda_release_version = '2.0.0' + ext.corda_gradle_plugins_version = '1.0.0' + +Public Beta (M12) to V1.0 +------------------------- + +:ref:`From Milestone 14 ` +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +Build ^^^^^ * MockNetwork has moved. @@ -151,7 +168,7 @@ Flow framework Note that ``SwapIdentitiesFlow`` must be imported from the *confidential-identities** package ''net.corda.confidential'' Node services (ServiceHub) -^^^^^^^^^^^^^^ +^^^^^^^^^^^^^^^^^^^^^^^^^^ * VaultQueryService: unresolved reference to `vaultQueryService`. @@ -243,8 +260,8 @@ Gotchas The 3rd parameter to ``CashIssueFlow`` should be the ** notary ** (not the ** node identity **) -:ref:`Milestone 13 ` ------------- +:ref:`From Milestone 13 ` +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Core data structures ^^^^^^^^^^^^^^^^^^^^ @@ -266,7 +283,7 @@ Core data structures * No longer need to override Contract ``contract()`` function. Node services (ServiceHub) -^^^^^^^^^^^^^ +^^^^^^^^^^^^^^^^^^^^^^^^^^ * ServiceHub API method changes. @@ -311,8 +328,8 @@ Testing ``CordaX500Name``, instead of using ``getX509Name`` -:ref:`Milestone 12 ` (First Public Beta) ------------------------------------ +:ref:`From Milestone 12 (First Public Beta) ` +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Core data structures ^^^^^^^^^^^^^^^^^^^^ @@ -338,7 +355,7 @@ Build compile "net.corda:rpc:$corda_release_version" -> compile "net.corda:corda-rpc:$corda_release_version" Node services (ServiceHub) -^^^^^^^^^^^^^ +^^^^^^^^^^^^^^^^^^^^^^^^^^ * ServiceHub API changes. From 026d88a2b937c4c9fe85e1beeb08a023e2b4ca11 Mon Sep 17 00:00:00 2001 From: Ross Nicoll Date: Wed, 22 Nov 2017 18:00:43 +0000 Subject: [PATCH 08/23] Add X500 name constraints for non-organisation attributes (#2108) Enforce X500 name constraints consistently across all attributes --- .../net/corda/core/identity/CordaX500Name.kt | 4 +- .../corda/core/internal/LegalNameValidator.kt | 40 ++++++++++++---- .../core/internal/LegalNameValidatorTest.kt | 46 +++++++++---------- docs/source/key-concepts-identity.rst | 13 +++++- .../net/corda/demobench/views/NodeTabView.kt | 4 +- 5 files changed, 70 insertions(+), 37 deletions(-) diff --git a/core/src/main/kotlin/net/corda/core/identity/CordaX500Name.kt b/core/src/main/kotlin/net/corda/core/identity/CordaX500Name.kt index 37e872d562..ee8baa8733 100644 --- a/core/src/main/kotlin/net/corda/core/identity/CordaX500Name.kt +++ b/core/src/main/kotlin/net/corda/core/identity/CordaX500Name.kt @@ -9,7 +9,7 @@ import org.bouncycastle.asn1.ASN1ObjectIdentifier import org.bouncycastle.asn1.x500.AttributeTypeAndValue import org.bouncycastle.asn1.x500.X500Name import org.bouncycastle.asn1.x500.style.BCStyle -import java.util.Locale +import java.util.* import javax.security.auth.x500.X500Principal /** @@ -45,7 +45,7 @@ data class CordaX500Name(val commonName: String?, init { // Legal name checks. - LegalNameValidator.validateLegalName(organisation) + LegalNameValidator.validateOrganization(organisation) // Attribute data width checks. require(country.length == LENGTH_COUNTRY) { "Invalid country '$country' Country code must be $LENGTH_COUNTRY letters ISO code " } diff --git a/core/src/main/kotlin/net/corda/core/internal/LegalNameValidator.kt b/core/src/main/kotlin/net/corda/core/internal/LegalNameValidator.kt index 967afc5e7a..892f83b224 100644 --- a/core/src/main/kotlin/net/corda/core/internal/LegalNameValidator.kt +++ b/core/src/main/kotlin/net/corda/core/internal/LegalNameValidator.kt @@ -6,8 +6,27 @@ import java.util.regex.Pattern import javax.security.auth.x500.X500Principal object LegalNameValidator { + @Deprecated("Use validateOrganization instead", replaceWith = ReplaceWith("validateOrganization(normalizedLegalName)")) + fun validateLegalName(normalizedLegalName: String) = validateOrganization(normalizedLegalName) + /** - * The validation function will validate the input string using the following rules: + * The validation function validates a string for use as part of a legal name. It applies the following rules: + * + * - No blacklisted words like "node", "server". + * - Restrict names to Latin scripts for now to avoid right-to-left issues, debugging issues when we can't pronounce + * names over the phone, and character confusability attacks. + * - No commas or equals signs. + * - No dollars or quote marks, we might need to relax the quote mark constraint in future to handle Irish company names. + * + * @throws IllegalArgumentException if the name does not meet the required rules. The message indicates why not. + */ + fun validateNameAttribute(normalizedNameAttribute: String) { + Rule.baseNameRules.forEach { it.validate(normalizedNameAttribute) } + } + + /** + * The validation function validates a string for use as the organization attribute of a name, which includes additional + * constraints over basic name attribute checks. It applies the following rules: * * - No blacklisted words like "node", "server". * - Restrict names to Latin scripts for now to avoid right-to-left issues, debugging issues when we can't pronounce @@ -18,16 +37,19 @@ object LegalNameValidator { * * @throws IllegalArgumentException if the name does not meet the required rules. The message indicates why not. */ - fun validateLegalName(normalizedLegalName: String) { - Rule.legalNameRules.forEach { it.validate(normalizedLegalName) } + fun validateOrganization(normalizedOrganization: String) { + Rule.legalNameRules.forEach { it.validate(normalizedOrganization) } } + @Deprecated("Use normalize instead", replaceWith = ReplaceWith("normalize(legalName)")) + fun normalizeLegalName(legalName: String): String = normalize(legalName) + /** * The normalize function will trim the input string, replace any multiple spaces with a single space, * and normalize the string according to NFKC normalization form. */ - fun normaliseLegalName(legalName: String): String { - val trimmedLegalName = legalName.trim().replace(WHITESPACE, " ") + fun normalize(nameAttribute: String): String { + val trimmedLegalName = nameAttribute.trim().replace(WHITESPACE, " ") return Normalizer.normalize(trimmedLegalName, Normalizer.Form.NFKC) } @@ -35,15 +57,17 @@ object LegalNameValidator { sealed class Rule { companion object { - val legalNameRules: List> = listOf( + val baseNameRules: List> = listOf( UnicodeNormalizationRule(), CharacterRule(',', '=', '$', '"', '\'', '\\'), WordRule("node", "server"), LengthRule(maxLength = 255), // TODO: Implement confusable character detection if we add more scripts. UnicodeRangeRule(LATIN, COMMON, INHERITED), + X500NameRule() + ) + val legalNameRules: List> = baseNameRules + listOf( CapitalLetterRule(), - X500NameRule(), MustHaveAtLeastTwoLettersRule() ) } @@ -52,7 +76,7 @@ object LegalNameValidator { private class UnicodeNormalizationRule : Rule() { override fun validate(legalName: String) { - require(legalName == normaliseLegalName(legalName)) { "Legal name must be normalized. Please use 'normaliseLegalName' to normalize the legal name before validation." } + require(legalName == normalize(legalName)) { "Legal name must be normalized. Please use 'normalize' to normalize the legal name before validation." } } } diff --git a/core/src/test/kotlin/net/corda/core/internal/LegalNameValidatorTest.kt b/core/src/test/kotlin/net/corda/core/internal/LegalNameValidatorTest.kt index 0f96141922..fa854b42e4 100644 --- a/core/src/test/kotlin/net/corda/core/internal/LegalNameValidatorTest.kt +++ b/core/src/test/kotlin/net/corda/core/internal/LegalNameValidatorTest.kt @@ -8,55 +8,55 @@ class LegalNameValidatorTest { @Test fun `no double spaces`() { assertFailsWith(IllegalArgumentException::class) { - LegalNameValidator.validateLegalName("Test Legal Name") + LegalNameValidator.validateOrganization("Test Legal Name") } - LegalNameValidator.validateLegalName(LegalNameValidator.normaliseLegalName("Test Legal Name")) + LegalNameValidator.validateOrganization(LegalNameValidator.normalize("Test Legal Name")) } @Test fun `no trailing white space`() { assertFailsWith(IllegalArgumentException::class) { - LegalNameValidator.validateLegalName("Test ") + LegalNameValidator.validateOrganization("Test ") } } @Test fun `no prefixed white space`() { assertFailsWith(IllegalArgumentException::class) { - LegalNameValidator.validateLegalName(" Test") + LegalNameValidator.validateOrganization(" Test") } } @Test fun `blacklisted words`() { assertFailsWith(IllegalArgumentException::class) { - LegalNameValidator.validateLegalName("Test Server") + LegalNameValidator.validateOrganization("Test Server") } } @Test fun `blacklisted characters`() { - LegalNameValidator.validateLegalName("Test") + LegalNameValidator.validateOrganization("Test") assertFailsWith(IllegalArgumentException::class) { - LegalNameValidator.validateLegalName("\$Test") + LegalNameValidator.validateOrganization("\$Test") } assertFailsWith(IllegalArgumentException::class) { - LegalNameValidator.validateLegalName("\"Test") + LegalNameValidator.validateOrganization("\"Test") } assertFailsWith(IllegalArgumentException::class) { - LegalNameValidator.validateLegalName("\'Test") + LegalNameValidator.validateOrganization("\'Test") } assertFailsWith(IllegalArgumentException::class) { - LegalNameValidator.validateLegalName("=Test") + LegalNameValidator.validateOrganization("=Test") } } @Test fun `unicode range`() { - LegalNameValidator.validateLegalName("Test A") + LegalNameValidator.validateOrganization("Test A") assertFailsWith(IllegalArgumentException::class) { // Greek letter A. - LegalNameValidator.validateLegalName("Test Α") + LegalNameValidator.validateOrganization("Test Α") } } @@ -66,37 +66,37 @@ class LegalNameValidatorTest { while (longLegalName.length < 255) { longLegalName.append("A") } - LegalNameValidator.validateLegalName(longLegalName.toString()) + LegalNameValidator.validateOrganization(longLegalName.toString()) assertFailsWith(IllegalArgumentException::class) { - LegalNameValidator.validateLegalName(longLegalName.append("A").toString()) + LegalNameValidator.validateOrganization(longLegalName.append("A").toString()) } } @Test fun `legal name should be capitalized`() { - LegalNameValidator.validateLegalName("Good legal name") + LegalNameValidator.validateOrganization("Good legal name") assertFailsWith(IllegalArgumentException::class) { - LegalNameValidator.validateLegalName("bad name") + LegalNameValidator.validateOrganization("bad name") } assertFailsWith(IllegalArgumentException::class) { - LegalNameValidator.validateLegalName("bad Name") + LegalNameValidator.validateOrganization("bad Name") } } @Test fun `correctly handle whitespaces`() { - assertEquals("Legal Name With Tab", LegalNameValidator.normaliseLegalName("Legal Name With\tTab")) - assertEquals("Legal Name With Unicode Whitespaces", LegalNameValidator.normaliseLegalName("Legal Name\u2004With\u0009Unicode\u0020Whitespaces")) - assertEquals("Legal Name With Line Breaks", LegalNameValidator.normaliseLegalName("Legal Name With\n\rLine\nBreaks")) + assertEquals("Legal Name With Tab", LegalNameValidator.normalize("Legal Name With\tTab")) + assertEquals("Legal Name With Unicode Whitespaces", LegalNameValidator.normalize("Legal Name\u2004With\u0009Unicode\u0020Whitespaces")) + assertEquals("Legal Name With Line Breaks", LegalNameValidator.normalize("Legal Name With\n\rLine\nBreaks")) assertFailsWith(IllegalArgumentException::class) { - LegalNameValidator.validateLegalName("Legal Name With\tTab") + LegalNameValidator.validateOrganization("Legal Name With\tTab") } assertFailsWith(IllegalArgumentException::class) { - LegalNameValidator.validateLegalName("Legal Name\u2004With\u0009Unicode\u0020Whitespaces") + LegalNameValidator.validateOrganization("Legal Name\u2004With\u0009Unicode\u0020Whitespaces") } assertFailsWith(IllegalArgumentException::class) { - LegalNameValidator.validateLegalName("Legal Name With\n\rLine\nBreaks") + LegalNameValidator.validateOrganization("Legal Name With\n\rLine\nBreaks") } } } \ No newline at end of file diff --git a/docs/source/key-concepts-identity.rst b/docs/source/key-concepts-identity.rst index 7f1f9bbcc2..de01ee8295 100644 --- a/docs/source/key-concepts-identity.rst +++ b/docs/source/key-concepts-identity.rst @@ -49,8 +49,17 @@ the minimum supported set for X.509 certificates (specified in RFC 3280), plus t * common name (CN) - used only for service identities The organisation, locality and country attributes are required, while state, organisational-unit and common name are -optional. Attributes cannot be be present more than once in the name. The "country" code is strictly restricted to valid -ISO 3166-1 two letter codes. +optional. Attributes cannot be be present more than once in the name. + +All of these attributes have the following set of constraints applied for security reasons: + + - No blacklisted words (currently "node" and "server"). + - Restrict names to Latin scripts for now to avoid right-to-left issues, debugging issues when we can't pronounce names over the phone, and character confusability attacks. + - No commas or equals signs. + - No dollars or quote marks. + +Additionally the "organisation" attribute must consist of at least three letters and starting with a capital letter, +and "country code" is strictly restricted to valid ISO 3166-1 two letter codes. Certificates ------------ diff --git a/tools/demobench/src/main/kotlin/net/corda/demobench/views/NodeTabView.kt b/tools/demobench/src/main/kotlin/net/corda/demobench/views/NodeTabView.kt index cc728b4708..cede37faac 100644 --- a/tools/demobench/src/main/kotlin/net/corda/demobench/views/NodeTabView.kt +++ b/tools/demobench/src/main/kotlin/net/corda/demobench/views/NodeTabView.kt @@ -207,11 +207,11 @@ class NodeTabView : Fragment() { validator { if (it == null) { error("Node name is required") - } else if (nodeController.nameExists(LegalNameValidator.normaliseLegalName(it))) { + } else if (nodeController.nameExists(LegalNameValidator.normalize(it))) { error("Node with this name already exists") } else { try { - LegalNameValidator.validateLegalName(LegalNameValidator.normaliseLegalName(it)) + LegalNameValidator.validateOrganization(LegalNameValidator.normalize(it)) null } catch (e: IllegalArgumentException) { error(e.message) From cc1fba641e8ba8dfecd766fb257363890fa19536 Mon Sep 17 00:00:00 2001 From: Patrick Kuo Date: Tue, 28 Nov 2017 13:58:48 +0000 Subject: [PATCH 09/23] Integration test for http network map service (#2078) * make node info file copying optional by setting "compatabilityZoneURL" in driver integration test for node using http network map using driver some bug fixes * rebase to feature branch and fixup * add initialRegistration flag to driver * remove useFileBaseNetworkMap flag, add network map server to DriverTest * remove useFileBaseNetworkMap flag, add network map server to DriverTest * use PortAllocation.Incremental instead of random * * use PortAllocation.Incremental instead of random * fix NodeInfoWatcher thread leak issue * reset scheduler before create notary * move port allocation out of companion object * move port allocation out of companion object * make node info file copier lateinit to avoid observable thread pool get created on init --- .../services/network/NetworkMapClientTest.kt | 91 ++++++++++++++ .../kotlin/net/corda/node/internal/Node.kt | 14 ++- .../network/PersistentNetworkMapCache.kt | 2 +- .../services/network/NetworkMapClientTest.kt | 109 ++--------------- testing/node-driver/build.gradle | 11 ++ .../net/corda/testing/driver/DriverTests.kt | 62 +++++----- .../kotlin/net/corda/testing/driver/Driver.kt | 95 ++++++++++----- .../net/corda/testing/internal/RPCDriver.kt | 1 + .../testing/node/network/NetworkMapServer.kt | 115 ++++++++++++++++++ .../net/corda/verifier/VerifierDriver.kt | 2 +- 10 files changed, 337 insertions(+), 165 deletions(-) create mode 100644 node/src/integration-test/kotlin/net/corda/node/services/network/NetworkMapClientTest.kt create mode 100644 testing/node-driver/src/main/kotlin/net/corda/testing/node/network/NetworkMapServer.kt diff --git a/node/src/integration-test/kotlin/net/corda/node/services/network/NetworkMapClientTest.kt b/node/src/integration-test/kotlin/net/corda/node/services/network/NetworkMapClientTest.kt new file mode 100644 index 0000000000..c428422eb5 --- /dev/null +++ b/node/src/integration-test/kotlin/net/corda/node/services/network/NetworkMapClientTest.kt @@ -0,0 +1,91 @@ +package net.corda.node.services.network + +import net.corda.core.node.NodeInfo +import net.corda.core.utilities.minutes +import net.corda.core.utilities.seconds +import net.corda.testing.ALICE +import net.corda.testing.BOB +import net.corda.testing.driver.NodeHandle +import net.corda.testing.driver.PortAllocation +import net.corda.testing.driver.driver +import net.corda.testing.node.network.NetworkMapServer +import org.assertj.core.api.Assertions.assertThat +import org.junit.Test +import java.net.URL + +class NetworkMapClientTest { + private val portAllocation = PortAllocation.Incremental(10000) + + @Test + fun `nodes can see each other using the http network map`() { + NetworkMapServer(1.minutes, portAllocation.nextHostAndPort()).use { + val (host, port) = it.start() + driver(portAllocation = portAllocation, compatibilityZoneURL = URL("http://$host:$port")) { + val alice = startNode(providedName = ALICE.name) + val bob = startNode(providedName = BOB.name) + + val notaryNode = defaultNotaryNode.get() + val aliceNode = alice.get() + val bobNode = bob.get() + + notaryNode.onlySees(notaryNode.nodeInfo, aliceNode.nodeInfo, bobNode.nodeInfo) + aliceNode.onlySees(notaryNode.nodeInfo, aliceNode.nodeInfo, bobNode.nodeInfo) + bobNode.onlySees(notaryNode.nodeInfo, aliceNode.nodeInfo, bobNode.nodeInfo) + } + } + } + + @Test + fun `nodes process network map add updates correctly when adding new node to network map`() { + NetworkMapServer(1.seconds, portAllocation.nextHostAndPort()).use { + val (host, port) = it.start() + driver(portAllocation = portAllocation, compatibilityZoneURL = URL("http://$host:$port")) { + val alice = startNode(providedName = ALICE.name) + val notaryNode = defaultNotaryNode.get() + val aliceNode = alice.get() + + notaryNode.onlySees(notaryNode.nodeInfo, aliceNode.nodeInfo) + aliceNode.onlySees(notaryNode.nodeInfo, aliceNode.nodeInfo) + + val bob = startNode(providedName = BOB.name) + val bobNode = bob.get() + + // Wait for network map client to poll for the next update. + Thread.sleep(2.seconds.toMillis()) + + bobNode.onlySees(notaryNode.nodeInfo, aliceNode.nodeInfo, bobNode.nodeInfo) + notaryNode.onlySees(notaryNode.nodeInfo, aliceNode.nodeInfo, bobNode.nodeInfo) + aliceNode.onlySees(notaryNode.nodeInfo, aliceNode.nodeInfo, bobNode.nodeInfo) + } + } + } + + @Test + fun `nodes process network map remove updates correctly`() { + NetworkMapServer(1.seconds, portAllocation.nextHostAndPort()).use { + val (host, port) = it.start() + driver(portAllocation = portAllocation, compatibilityZoneURL = URL("http://$host:$port")) { + val alice = startNode(providedName = ALICE.name) + val bob = startNode(providedName = BOB.name) + + val notaryNode = defaultNotaryNode.get() + val aliceNode = alice.get() + val bobNode = bob.get() + + notaryNode.onlySees(notaryNode.nodeInfo, aliceNode.nodeInfo, bobNode.nodeInfo) + aliceNode.onlySees(notaryNode.nodeInfo, aliceNode.nodeInfo, bobNode.nodeInfo) + bobNode.onlySees(notaryNode.nodeInfo, aliceNode.nodeInfo, bobNode.nodeInfo) + + it.removeNodeInfo(aliceNode.nodeInfo) + + // Wait for network map client to poll for the next update. + Thread.sleep(2.seconds.toMillis()) + + notaryNode.onlySees(notaryNode.nodeInfo, bobNode.nodeInfo) + bobNode.onlySees(notaryNode.nodeInfo, bobNode.nodeInfo) + } + } + } + + private fun NodeHandle.onlySees(vararg nodes: NodeInfo) = assertThat(rpc.networkMapSnapshot()).containsOnly(*nodes) +} diff --git a/node/src/main/kotlin/net/corda/node/internal/Node.kt b/node/src/main/kotlin/net/corda/node/internal/Node.kt index f7824796a4..ea3dd582d6 100644 --- a/node/src/main/kotlin/net/corda/node/internal/Node.kt +++ b/node/src/main/kotlin/net/corda/node/internal/Node.kt @@ -184,11 +184,17 @@ open class Node(configuration: NodeConfiguration, return if (!AddressUtils.isPublic(host)) { val foundPublicIP = AddressUtils.tryDetectPublicIP() if (foundPublicIP == null) { - val retrievedHostName = networkMapClient?.myPublicHostname() - if (retrievedHostName != null) { - log.info("Retrieved public IP from Network Map Service: $this. This will be used instead of the provided \"$host\" as the advertised address.") + try { + val retrievedHostName = networkMapClient?.myPublicHostname() + if (retrievedHostName != null) { + log.info("Retrieved public IP from Network Map Service: $this. This will be used instead of the provided \"$host\" as the advertised address.") + } + retrievedHostName + } catch (ignore: Throwable) { + // Cannot reach the network map service, ignore the exception and use provided P2P address instead. + log.warn("Cannot connect to the network map service for public IP detection.") + null } - retrievedHostName } else { log.info("Detected public IP: ${foundPublicIP.hostAddress}. This will be used instead of the provided \"$host\" as the advertised address.") foundPublicIP.hostAddress diff --git a/node/src/main/kotlin/net/corda/node/services/network/PersistentNetworkMapCache.kt b/node/src/main/kotlin/net/corda/node/services/network/PersistentNetworkMapCache.kt index e454e52ac1..10086d5108 100644 --- a/node/src/main/kotlin/net/corda/node/services/network/PersistentNetworkMapCache.kt +++ b/node/src/main/kotlin/net/corda/node/services/network/PersistentNetworkMapCache.kt @@ -101,7 +101,7 @@ open class PersistentNetworkMapCache( select(get(NodeInfoSchemaV1.PersistentNodeInfo::hash.name)) } } - session.createQuery(query).resultList.map { SecureHash.sha256(it) } + session.createQuery(query).resultList.map { SecureHash.parse(it) } } } diff --git a/node/src/test/kotlin/net/corda/node/services/network/NetworkMapClientTest.kt b/node/src/test/kotlin/net/corda/node/services/network/NetworkMapClientTest.kt index a2bf02db4d..fc4b037de9 100644 --- a/node/src/test/kotlin/net/corda/node/services/network/NetworkMapClientTest.kt +++ b/node/src/test/kotlin/net/corda/node/services/network/NetworkMapClientTest.kt @@ -1,86 +1,43 @@ package net.corda.node.services.network -import com.fasterxml.jackson.databind.ObjectMapper -import net.corda.core.crypto.* -import net.corda.core.identity.CordaX500Name -import net.corda.core.identity.PartyAndCertificate -import net.corda.core.node.NodeInfo -import net.corda.core.serialization.deserialize +import net.corda.core.crypto.sha256 import net.corda.core.serialization.serialize -import net.corda.core.utilities.NetworkHostAndPort import net.corda.core.utilities.seconds import net.corda.node.services.network.TestNodeInfoFactory.createNodeInfo import net.corda.nodeapi.internal.crypto.CertificateType import net.corda.nodeapi.internal.crypto.X509Utilities import net.corda.testing.SerializationEnvironmentRule +import net.corda.testing.driver.PortAllocation +import net.corda.testing.node.network.NetworkMapServer import org.assertj.core.api.Assertions.assertThat -import org.bouncycastle.asn1.x500.X500Name -import org.bouncycastle.cert.X509CertificateHolder -import org.eclipse.jetty.server.Server -import org.eclipse.jetty.server.ServerConnector -import org.eclipse.jetty.server.handler.HandlerCollection -import org.eclipse.jetty.servlet.ServletContextHandler -import org.eclipse.jetty.servlet.ServletHolder -import org.glassfish.jersey.server.ResourceConfig -import org.glassfish.jersey.servlet.ServletContainer import org.junit.After import org.junit.Before import org.junit.Rule import org.junit.Test -import java.io.ByteArrayInputStream -import java.io.InputStream -import java.net.InetSocketAddress import java.net.URL -import java.security.cert.CertPath -import java.security.cert.Certificate -import java.security.cert.CertificateFactory -import java.security.cert.X509Certificate -import javax.ws.rs.* -import javax.ws.rs.core.MediaType -import javax.ws.rs.core.Response -import javax.ws.rs.core.Response.ok import kotlin.test.assertEquals class NetworkMapClientTest { @Rule @JvmField val testSerialization = SerializationEnvironmentRule(true) - private lateinit var server: Server - + private lateinit var server: NetworkMapServer private lateinit var networkMapClient: NetworkMapClient - private val rootCAKey = Crypto.generateKeyPair(X509Utilities.DEFAULT_TLS_SIGNATURE_SCHEME) - private val rootCACert = X509Utilities.createSelfSignedCACertificate(CordaX500Name(commonName = "Corda Node Root CA", organisation = "R3 LTD", locality = "London", country = "GB"), rootCAKey) - private val intermediateCAKey = Crypto.generateKeyPair(X509Utilities.DEFAULT_TLS_SIGNATURE_SCHEME) - private val intermediateCACert = X509Utilities.createCertificate(CertificateType.INTERMEDIATE_CA, rootCACert, rootCAKey, X500Name("CN=Corda Node Intermediate CA,L=London"), intermediateCAKey.public) + + companion object { + private val cacheTimeout = 100000.seconds + } @Before fun setUp() { - server = Server(InetSocketAddress("localhost", 0)).apply { - handler = HandlerCollection().apply { - addHandler(ServletContextHandler().apply { - contextPath = "/" - val resourceConfig = ResourceConfig().apply { - // Add your API provider classes (annotated for JAX-RS) here - register(MockNetworkMapServer()) - } - val jerseyServlet = ServletHolder(ServletContainer(resourceConfig)).apply { initOrder = 0 }// Initialise at server start - addServlet(jerseyServlet, "/*") - }) - } - } - server.start() - - while (!server.isStarted) { - Thread.sleep(100) - } - - val hostAndPort = server.connectors.mapNotNull { it as? ServerConnector }.first() - networkMapClient = NetworkMapClient(URL("http://${hostAndPort.host}:${hostAndPort.localPort}")) + server = NetworkMapServer(cacheTimeout, PortAllocation.Incremental(10000).nextHostAndPort()) + val hostAndPort = server.start() + networkMapClient = NetworkMapClient(URL("http://${hostAndPort.host}:${hostAndPort.port}")) } @After fun tearDown() { - server.stop() + server.close() } @Test @@ -102,7 +59,7 @@ class NetworkMapClientTest { val nodeInfoHash2 = nodeInfo2.serialize().sha256() assertThat(networkMapClient.getNetworkMap().networkMap).containsExactly(nodeInfoHash, nodeInfoHash2) - assertEquals(100000.seconds, networkMapClient.getNetworkMap().cacheMaxAge) + assertEquals(cacheTimeout, networkMapClient.getNetworkMap().cacheMaxAge) assertEquals(nodeInfo2, networkMapClient.getNodeInfo(nodeInfoHash2)) } @@ -111,43 +68,3 @@ class NetworkMapClientTest { assertEquals("test.host.name", networkMapClient.myPublicHostname()) } } - -@Path("network-map") -// This is a stub implementation of the network map rest API. -internal class MockNetworkMapServer { - val nodeInfoMap = mutableMapOf() - @POST - @Path("publish") - @Consumes(MediaType.APPLICATION_OCTET_STREAM) - fun publishNodeInfo(input: InputStream): Response { - val registrationData = input.readBytes().deserialize>() - val nodeInfo = registrationData.verified() - val nodeInfoHash = nodeInfo.serialize().sha256() - nodeInfoMap.put(nodeInfoHash, nodeInfo) - return ok().build() - } - - @GET - @Produces(MediaType.APPLICATION_JSON) - fun getNetworkMap(): Response { - return Response.ok(ObjectMapper().writeValueAsString(nodeInfoMap.keys.map { it.toString() })).header("Cache-Control", "max-age=100000").build() - } - - @GET - @Path("{var}") - @Produces(MediaType.APPLICATION_OCTET_STREAM) - fun getNodeInfo(@PathParam("var") nodeInfoHash: String): Response { - val nodeInfo = nodeInfoMap[SecureHash.parse(nodeInfoHash)] - return if (nodeInfo != null) { - Response.ok(nodeInfo.serialize().bytes) - } else { - Response.status(Response.Status.NOT_FOUND) - }.build() - } - - @GET - @Path("my-hostname") - fun getHostName(): Response { - return Response.ok("test.host.name").build() - } -} diff --git a/testing/node-driver/build.gradle b/testing/node-driver/build.gradle index 7dc2760f8f..d3fda9b9c7 100644 --- a/testing/node-driver/build.gradle +++ b/testing/node-driver/build.gradle @@ -30,6 +30,17 @@ dependencies { // Integration test helpers integrationTestCompile "org.assertj:assertj-core:${assertj_version}" integrationTestCompile "junit:junit:$junit_version" + + // Jetty dependencies for NetworkMapClient test. + // Web stuff: for HTTP[S] servlets + compile "org.eclipse.jetty:jetty-servlet:${jetty_version}" + compile "org.eclipse.jetty:jetty-webapp:${jetty_version}" + compile "javax.servlet:javax.servlet-api:3.1.0" + + // Jersey for JAX-RS implementation for use in Jetty + compile "org.glassfish.jersey.core:jersey-server:${jersey_version}" + compile "org.glassfish.jersey.containers:jersey-container-servlet-core:${jersey_version}" + compile "org.glassfish.jersey.containers:jersey-container-jetty-http:${jersey_version}" } task integrationTest(type: Test) { diff --git a/testing/node-driver/src/integration-test/kotlin/net/corda/testing/driver/DriverTests.kt b/testing/node-driver/src/integration-test/kotlin/net/corda/testing/driver/DriverTests.kt index f99fb75000..729f68fe92 100644 --- a/testing/node-driver/src/integration-test/kotlin/net/corda/testing/driver/DriverTests.kt +++ b/testing/node-driver/src/integration-test/kotlin/net/corda/testing/driver/DriverTests.kt @@ -1,25 +1,28 @@ package net.corda.testing.driver -import com.sun.net.httpserver.HttpExchange -import com.sun.net.httpserver.HttpHandler -import com.sun.net.httpserver.HttpServer import net.corda.core.concurrent.CordaFuture import net.corda.core.internal.div import net.corda.core.internal.list import net.corda.core.internal.readLines import net.corda.core.utilities.getOrThrow +import net.corda.core.utilities.minutes import net.corda.node.internal.NodeStartup import net.corda.testing.DUMMY_BANK_A import net.corda.testing.DUMMY_NOTARY import net.corda.testing.DUMMY_REGULATOR import net.corda.testing.ProjectStructure.projectRootDir import net.corda.testing.node.NotarySpec +import net.corda.testing.node.network.NetworkMapServer import org.assertj.core.api.Assertions.assertThat import org.junit.Test -import java.net.InetSocketAddress import java.net.URL import java.util.concurrent.Executors import java.util.concurrent.ScheduledExecutorService +import javax.ws.rs.GET +import javax.ws.rs.POST +import javax.ws.rs.Path +import javax.ws.rs.core.Response +import javax.ws.rs.core.Response.ok class DriverTests { companion object { @@ -37,6 +40,7 @@ class DriverTests { addressMustNotBeBound(executorService, hostAndPort) } } + private val portAllocation = PortAllocation.Incremental(10000) @Test fun `simple node startup and shutdown`() { @@ -49,7 +53,7 @@ class DriverTests { @Test fun `random free port allocation`() { - val nodeHandle = driver(portAllocation = PortAllocation.RandomFree) { + val nodeHandle = driver(portAllocation = portAllocation) { val nodeInfo = startNode(providedName = DUMMY_BANK_A.name) nodeMustBeUp(nodeInfo) } @@ -58,33 +62,14 @@ class DriverTests { @Test fun `node registration`() { - // Very simple Http handler which counts the requests it has received and always returns the same payload. - val handler = object : HttpHandler { - private val _requests = mutableListOf() - val requests: List - get() = _requests.toList() - - override fun handle(exchange: HttpExchange) { - val response = "reply" - _requests.add(exchange.requestURI.toString()) - exchange.responseHeaders.set("Content-Type", "text/html; charset=" + Charsets.UTF_8) - exchange.sendResponseHeaders(200, response.length.toLong()) - exchange.responseBody.use { it.write(response.toByteArray()) } + val handler = RegistrationHandler() + NetworkMapServer(1.minutes, portAllocation.nextHostAndPort(), handler).use { + val (host, port) = it.start() + driver(portAllocation = portAllocation, compatibilityZoneURL = URL("http://$host:$port")) { + // Wait for the node to have started. + startNode(initialRegistration = true).get() } } - - val inetSocketAddress = InetSocketAddress(0) - val server = HttpServer.create(inetSocketAddress, 0) - val port = server.address.port - server.createContext("/", handler) - server.executor = null // creates a default executor - server.start() - - driver(compatibilityZoneURL = URL("http://localhost:$port")) { - // Wait for the notary to have started. - notaryHandles.first().nodeHandles.get() - } - // We're getting: // a request to sign the certificate then // at least one poll request to see if the request has been approved. @@ -120,3 +105,20 @@ class DriverTests { assertThat(baseDirectory / "process-id").doesNotExist() } } + +@Path("certificate") +class RegistrationHandler { + val requests = mutableListOf() + @POST + fun registration(): Response { + requests += "/certificate" + return ok("reply").build() + } + + @GET + @Path("reply") + fun reply(): Response { + requests += "/certificate/reply" + return ok().build() + } +} diff --git a/testing/node-driver/src/main/kotlin/net/corda/testing/driver/Driver.kt b/testing/node-driver/src/main/kotlin/net/corda/testing/driver/Driver.kt index 5a9e0d7c6c..dae08d3c7d 100644 --- a/testing/node-driver/src/main/kotlin/net/corda/testing/driver/Driver.kt +++ b/testing/node-driver/src/main/kotlin/net/corda/testing/driver/Driver.kt @@ -16,7 +16,6 @@ import net.corda.core.identity.Party import net.corda.core.internal.* import net.corda.core.internal.concurrent.* import net.corda.core.messaging.CordaRPCOps -import net.corda.core.node.NetworkParameters import net.corda.core.node.NodeInfo import net.corda.core.node.NotaryInfo import net.corda.core.node.services.NetworkMapCache @@ -48,6 +47,7 @@ import okhttp3.Request import org.slf4j.Logger import rx.Observable import rx.observables.ConnectableObservable +import rx.schedulers.Schedulers import java.net.* import java.nio.file.Path import java.nio.file.Paths @@ -111,13 +111,14 @@ interface DriverDSLExposedInterface : CordformContext { * Returns the [NotaryHandle] for the single notary on the network. Throws if there are none or more than one. * @see notaryHandles */ - val defaultNotaryHandle: NotaryHandle get() { - return when (notaryHandles.size) { - 0 -> throw IllegalStateException("There are no notaries defined on the network") - 1 -> notaryHandles[0] - else -> throw IllegalStateException("There is more than one notary defined on the network") + val defaultNotaryHandle: NotaryHandle + get() { + return when (notaryHandles.size) { + 0 -> throw IllegalStateException("There are no notaries defined on the network") + 1 -> notaryHandles[0] + else -> throw IllegalStateException("There is more than one notary defined on the network") + } } - } /** * Returns the identity of the single notary on the network. Throws if there are none or more than one. @@ -131,11 +132,12 @@ interface DriverDSLExposedInterface : CordformContext { * @see defaultNotaryHandle * @see notaryHandles */ - val defaultNotaryNode: CordaFuture get() { - return defaultNotaryHandle.nodeHandles.map { - it.singleOrNull() ?: throw IllegalStateException("Default notary is not a single node") + val defaultNotaryNode: CordaFuture + get() { + return defaultNotaryHandle.nodeHandles.map { + it.singleOrNull() ?: throw IllegalStateException("Default notary is not a single node") + } } - } /** * Start a node. @@ -157,7 +159,9 @@ interface DriverDSLExposedInterface : CordformContext { verifierType: VerifierType = defaultParameters.verifierType, customOverrides: Map = defaultParameters.customOverrides, startInSameProcess: Boolean? = defaultParameters.startInSameProcess, - maximumHeapSize: String = defaultParameters.maximumHeapSize): CordaFuture + maximumHeapSize: String = defaultParameters.maximumHeapSize, + initialRegistration: Boolean = defaultParameters.initialRegistration): CordaFuture + /** * Helper function for starting a [Node] with custom parameters from Java. @@ -297,7 +301,8 @@ data class NodeParameters( val verifierType: VerifierType = VerifierType.InMemory, val customOverrides: Map = emptyMap(), val startInSameProcess: Boolean? = null, - val maximumHeapSize: String = "200m" + val maximumHeapSize: String = "200m", + val initialRegistration: Boolean = false ) { fun setProvidedName(providedName: CordaX500Name?) = copy(providedName = providedName) fun setRpcUsers(rpcUsers: List) = copy(rpcUsers = rpcUsers) @@ -332,8 +337,9 @@ data class NodeParameters( * @param useTestClock If true the test clock will be used in Node. * @param startNodesInProcess Provides the default behaviour of whether new nodes should start inside this process or * not. Note that this may be overridden in [DriverDSLExposedInterface.startNode]. - * @param notarySpecs The notaries advertised for this network. These nodes will be started - automatically and will be* available from [DriverDSLExposedInterface.notaryHandles]. Defaults to a simple validating notary.* @param compatibilityZoneURL if not null each node is started once in registration mode (which makes the node register and quit), + * @param notarySpecs The notaries advertised for this network. These nodes will be started automatically and will be + * available from [DriverDSLExposedInterface.notaryHandles]. Defaults to a simple validating notary. + * @param compatibilityZoneURL if not null each node is started once in registration mode (which makes the node register and quit), * and then re-starts the node with the given parameters. * @param dsl The dsl itself. * @return The value returned in the [dsl] closure. @@ -601,7 +607,8 @@ class DriverDSL( // TODO: this object will copy NodeInfo files from started nodes to other nodes additional-node-infos/ // This uses the FileSystem and adds a delay (~5 seconds) given by the time we wait before polling the file system. // Investigate whether we can avoid that. - private val nodeInfoFilesCopier = NodeInfoFilesCopier() + // TODO: NodeInfoFilesCopier create observable threads in the init method, we should move that to a start method instead, changing this to lateinit instead to prevent that. + private lateinit var nodeInfoFilesCopier: NodeInfoFilesCopier // Map from a nodes legal name to an observable emitting the number of nodes in its network map. private val countObservables = mutableMapOf>() private lateinit var _notaries: List @@ -662,30 +669,42 @@ class DriverDSL( verifierType: VerifierType, customOverrides: Map, startInSameProcess: Boolean?, - maximumHeapSize: String + maximumHeapSize: String, + initialRegistration: Boolean ): CordaFuture { val p2pAddress = portAllocation.nextHostAndPort() // TODO: Derive name from the full picked name, don't just wrap the common name val name = providedName ?: CordaX500Name(organisation = "${oneOf(names).organisation}-${p2pAddress.port}", locality = "London", country = "GB") - val registrationFuture = compatibilityZoneURL?.let { registerNode(name, it) } ?: doneFuture(Unit) + + val registrationFuture = if (initialRegistration) { + compatibilityZoneURL ?: throw IllegalArgumentException("Compatibility zone URL must be provided for initial registration.") + registerNode(name, compatibilityZoneURL) + } else { + doneFuture(Unit) + } + return registrationFuture.flatMap { val rpcAddress = portAllocation.nextHostAndPort() val webAddress = portAllocation.nextHostAndPort() val users = rpcUsers.map { it.copy(permissions = it.permissions + DRIVER_REQUIRED_PERMISSIONS) } + val configMap = configOf( + "myLegalName" to name.toString(), + "p2pAddress" to p2pAddress.toString(), + "rpcAddress" to rpcAddress.toString(), + "webAddress" to webAddress.toString(), + "useTestClock" to useTestClock, + "rpcUsers" to if (users.isEmpty()) defaultRpcUserList else users.map { it.toConfig().root().unwrapped() }, + "verifierType" to verifierType.name + ) + customOverrides val config = ConfigHelper.loadConfig( baseDirectory = baseDirectory(name), allowMissingConfig = true, - configOverrides = configOf( - "myLegalName" to name.toString(), - "p2pAddress" to p2pAddress.toString(), - "rpcAddress" to rpcAddress.toString(), - "webAddress" to webAddress.toString(), - "useTestClock" to useTestClock, - "rpcUsers" to if (users.isEmpty()) defaultRpcUserList else users.map { it.toConfig().root().unwrapped() }, - "verifierType" to verifierType.name - ) + customOverrides + configOverrides = if (compatibilityZoneURL != null) { + configMap + mapOf("compatibilityZoneURL" to compatibilityZoneURL.toString()) + } else { + configMap + } ) - startNodeInternal(config, webAddress, startInSameProcess, maximumHeapSize) } } @@ -756,9 +775,15 @@ class DriverDSL( } override fun start() { + if (startNodesInProcess) { + Schedulers.reset() + } _executorService = Executors.newScheduledThreadPool(2, ThreadFactoryBuilder().setNameFormat("driver-pool-thread-%d").build()) _shutdownManager = ShutdownManager(executorService) + + nodeInfoFilesCopier = NodeInfoFilesCopier() shutdownManager.registerShutdown { nodeInfoFilesCopier.close() } + val notaryInfos = generateNotaryIdentities() // The network parameters must be serialised before starting any of the nodes networkParameters = NetworkParametersCopier(testNetworkParameters(notaryInfos)) @@ -923,10 +948,14 @@ class DriverDSL( maximumHeapSize: String): CordaFuture { val configuration = config.parseAsNodeConfiguration() val baseDirectory = configuration.baseDirectory.createDirectories() + // Distribute node info file using file copier when network map service URL (compatibilityZoneURL) is null. + // TODO: need to implement the same in cordformation? + val nodeInfoFilesCopier = if (compatibilityZoneURL == null) nodeInfoFilesCopier else null + + nodeInfoFilesCopier?.addConfig(baseDirectory) networkParameters.install(baseDirectory) - nodeInfoFilesCopier.addConfig(baseDirectory) val onNodeExit: () -> Unit = { - nodeInfoFilesCopier.removeConfig(baseDirectory) + nodeInfoFilesCopier?.removeConfig(baseDirectory) countObservables.remove(configuration.myLegalName) } if (startInProcess ?: startNodesInProcess) { @@ -1017,8 +1046,8 @@ class DriverDSL( node.internals.run() } node to nodeThread - }.flatMap { - nodeAndThread -> addressMustBeBoundFuture(executorService, nodeConf.p2pAddress).map { nodeAndThread } + }.flatMap { nodeAndThread -> + addressMustBeBoundFuture(executorService, nodeConf.p2pAddress).map { nodeAndThread } } } @@ -1063,7 +1092,7 @@ class DriverDSL( } }.toList() - return ProcessUtilities.startCordaProcess( + return ProcessUtilities.startCordaProcess( className = "net.corda.node.Corda", // cannot directly get class for this, so just use string arguments = arguments, jdwpPort = debugPort, diff --git a/testing/node-driver/src/main/kotlin/net/corda/testing/internal/RPCDriver.kt b/testing/node-driver/src/main/kotlin/net/corda/testing/internal/RPCDriver.kt index d8eb0dcaa4..aeffc50ffb 100644 --- a/testing/node-driver/src/main/kotlin/net/corda/testing/internal/RPCDriver.kt +++ b/testing/node-driver/src/main/kotlin/net/corda/testing/internal/RPCDriver.kt @@ -266,6 +266,7 @@ private class SingleUserSecurityManager(val rpcUser: User) : ActiveMQSecurityMan override fun validateUser(user: String?, password: String?, remotingConnection: RemotingConnection?): String? { return validate(user, password) } + override fun validateUserAndRole(user: String?, password: String?, roles: MutableSet?, checkType: CheckType?, address: String?, connection: RemotingConnection?): String? { return validate(user, password) } diff --git a/testing/node-driver/src/main/kotlin/net/corda/testing/node/network/NetworkMapServer.kt b/testing/node-driver/src/main/kotlin/net/corda/testing/node/network/NetworkMapServer.kt new file mode 100644 index 0000000000..94ebdcb199 --- /dev/null +++ b/testing/node-driver/src/main/kotlin/net/corda/testing/node/network/NetworkMapServer.kt @@ -0,0 +1,115 @@ +package net.corda.testing.node.network + +import com.fasterxml.jackson.databind.ObjectMapper +import net.corda.core.crypto.SecureHash +import net.corda.core.crypto.SignedData +import net.corda.core.crypto.sha256 +import net.corda.core.node.NodeInfo +import net.corda.core.serialization.deserialize +import net.corda.core.serialization.serialize +import net.corda.core.utilities.NetworkHostAndPort +import org.eclipse.jetty.server.Server +import org.eclipse.jetty.server.ServerConnector +import org.eclipse.jetty.server.handler.HandlerCollection +import org.eclipse.jetty.servlet.ServletContextHandler +import org.eclipse.jetty.servlet.ServletHolder +import org.glassfish.jersey.server.ResourceConfig +import org.glassfish.jersey.servlet.ServletContainer +import java.io.Closeable +import java.io.InputStream +import java.net.InetSocketAddress +import java.time.Duration +import javax.ws.rs.* +import javax.ws.rs.core.MediaType +import javax.ws.rs.core.Response +import javax.ws.rs.core.Response.ok + +class NetworkMapServer(cacheTimeout: Duration, hostAndPort: NetworkHostAndPort, vararg additionalServices: Any) : Closeable { + private val server: Server + + private val service = InMemoryNetworkMapService(cacheTimeout) + + init { + server = Server(InetSocketAddress(hostAndPort.host, hostAndPort.port)).apply { + handler = HandlerCollection().apply { + addHandler(ServletContextHandler().apply { + contextPath = "/" + val resourceConfig = ResourceConfig().apply { + // Add your API provider classes (annotated for JAX-RS) here + register(service) + additionalServices.forEach { register(it) } + } + val jerseyServlet = ServletHolder(ServletContainer(resourceConfig)).apply { initOrder = 0 }// Initialise at server start + addServlet(jerseyServlet, "/*") + }) + } + } + + } + + fun start(): NetworkHostAndPort { + server.start() + // Wait until server is up to obtain the host and port. + while (!server.isStarted) { + Thread.sleep(500) + } + return server.connectors + .mapNotNull { it as? ServerConnector } + .first() + .let { NetworkHostAndPort(it.host, it.localPort) } + } + + fun removeNodeInfo(nodeInfo: NodeInfo) { + service.removeNodeInfo(nodeInfo) + } + + override fun close() { + server.stop() + } + + @Path("network-map") + class InMemoryNetworkMapService(private val cacheTimeout: Duration) { + private val nodeInfoMap = mutableMapOf() + @POST + @Path("publish") + @Consumes(MediaType.APPLICATION_OCTET_STREAM) + fun publishNodeInfo(input: InputStream): Response { + val registrationData = input.readBytes().deserialize>() + val nodeInfo = registrationData.verified() + val nodeInfoHash = nodeInfo.serialize().sha256() + nodeInfoMap.put(nodeInfoHash, nodeInfo) + return ok().build() + } + + @GET + @Produces(MediaType.APPLICATION_JSON) + fun getNetworkMap(): Response { + return Response.ok(ObjectMapper().writeValueAsString(nodeInfoMap.keys.map { it.toString() })) + .header("Cache-Control", "max-age=${cacheTimeout.seconds}") + .build() + } + + @GET + @Path("{var}") + @Produces(MediaType.APPLICATION_OCTET_STREAM) + fun getNodeInfo(@PathParam("var") nodeInfoHash: String): Response { + val nodeInfo = nodeInfoMap[SecureHash.parse(nodeInfoHash)] + return if (nodeInfo != null) { + Response.ok(nodeInfo.serialize().bytes) + } else { + Response.status(Response.Status.NOT_FOUND) + }.build() + } + + @GET + @Path("my-hostname") + fun getHostName(): Response { + return Response.ok("test.host.name").build() + } + + // Remove nodeInfo for testing. + fun removeNodeInfo(nodeInfo: NodeInfo) { + nodeInfoMap.remove(nodeInfo.serialize().hash) + } + } +} diff --git a/verifier/src/integration-test/kotlin/net/corda/verifier/VerifierDriver.kt b/verifier/src/integration-test/kotlin/net/corda/verifier/VerifierDriver.kt index e367b34a1b..3e88638771 100644 --- a/verifier/src/integration-test/kotlin/net/corda/verifier/VerifierDriver.kt +++ b/verifier/src/integration-test/kotlin/net/corda/verifier/VerifierDriver.kt @@ -16,12 +16,12 @@ import net.corda.core.transactions.LedgerTransaction import net.corda.core.utilities.NetworkHostAndPort import net.corda.core.utilities.contextLogger import net.corda.node.services.config.configureDevKeyAndTrustStores -import net.corda.nodeapi.internal.ArtemisMessagingComponent.Companion.NODE_USER import net.corda.nodeapi.ArtemisTcpTransport import net.corda.nodeapi.ConnectionDirection import net.corda.nodeapi.VerifierApi import net.corda.nodeapi.config.NodeSSLConfiguration import net.corda.nodeapi.config.SSLConfiguration +import net.corda.nodeapi.internal.ArtemisMessagingComponent.Companion.NODE_USER import net.corda.testing.driver.* import net.corda.testing.internal.ProcessUtilities import net.corda.testing.node.NotarySpec From 572c4af40ca011660c2fccb31beb77a9d4de8317 Mon Sep 17 00:00:00 2001 From: Patrick Kuo Date: Wed, 29 Nov 2017 15:55:13 +0000 Subject: [PATCH 10/23] Use NetworkMap and SignedNetworkMap in NetworkMapClient, and enable signature verification. (#2054) * new network map object for network map, and verify signature and root in Signed network map and node info * fixup after rebase * * added certificate and key to network map server * move DigitalSignature.WithCert back to NetworkMap.kt, as its breaking API test, will raise another PR to move it back. * Make DigitalSignature.WithCert not extend WithKey, as per PR discussion. * various fixes after rebase. * move Network map back to core/node, as its breaking API test * revert unintended changes * move network map objects to node-api --- .ci/api-current.txt | 32 -------- .../net/corda/core/node/NetworkParameters.kt | 40 --------- .../net/corda/nodeapi/internal/NetworkMap.kt | 75 +++++++++++++++++ .../serialization/DefaultWhitelist.kt | 8 +- .../kryo/DefaultKryoCustomizer.kt | 3 +- .../node/services/BFTNotaryServiceTests.kt | 2 +- .../services/network/NetworkMapClientTest.kt | 3 +- .../net/corda/node/internal/AbstractNode.kt | 16 ++-- .../node/services/network/NetworkMapClient.kt | 51 +++++++++--- .../network/PersistentNetworkMapCache.kt | 2 +- .../services/network/NetworkMapClientTest.kt | 25 ++++-- .../services/network/NetworkMapUpdaterTest.kt | 5 +- .../net/corda/testing/driver/DriverTests.kt | 3 +- .../kotlin/net/corda/testing/driver/Driver.kt | 2 +- .../kotlin/net/corda/testing/node/MockNode.kt | 31 +++---- .../testing/node/network/NetworkMapServer.kt | 81 ++++++++++++++----- testing/test-common/build.gradle | 1 + .../internal/NetworkParametersCopier.kt | 2 +- .../common/internal/ParametersUtilities.kt | 4 +- .../kotlin/net/corda/testing/TestConstants.kt | 6 ++ 20 files changed, 245 insertions(+), 147 deletions(-) delete mode 100644 core/src/main/kotlin/net/corda/core/node/NetworkParameters.kt create mode 100644 node-api/src/main/kotlin/net/corda/nodeapi/internal/NetworkMap.kt diff --git a/.ci/api-current.txt b/.ci/api-current.txt index a5893b7cb8..84b4d41817 100644 --- a/.ci/api-current.txt +++ b/.ci/api-current.txt @@ -1678,27 +1678,6 @@ public @interface net.corda.core.messaging.RPCReturnsObservables @org.jetbrains.annotations.NotNull public abstract net.corda.core.messaging.FlowHandle startFlow(net.corda.core.flows.FlowLogic) @org.jetbrains.annotations.NotNull public abstract net.corda.core.messaging.FlowProgressHandle startTrackedFlow(net.corda.core.flows.FlowLogic) ## -@net.corda.core.serialization.CordaSerializable public final class net.corda.core.node.NetworkParameters extends java.lang.Object - public (int, List, java.time.Duration, int, int, java.time.Instant, int) - public final int component1() - @org.jetbrains.annotations.NotNull public final List component2() - @org.jetbrains.annotations.NotNull public final java.time.Duration component3() - public final int component4() - public final int component5() - @org.jetbrains.annotations.NotNull public final java.time.Instant component6() - public final int component7() - @org.jetbrains.annotations.NotNull public final net.corda.core.node.NetworkParameters copy(int, List, java.time.Duration, int, int, java.time.Instant, int) - public boolean equals(Object) - public final int getEpoch() - @org.jetbrains.annotations.NotNull public final java.time.Duration getEventHorizon() - public final int getMaxMessageSize() - public final int getMaxTransactionSize() - public final int getMinimumPlatformVersion() - @org.jetbrains.annotations.NotNull public final java.time.Instant getModifiedTime() - @org.jetbrains.annotations.NotNull public final List getNotaries() - public int hashCode() - public String toString() -## @net.corda.core.serialization.CordaSerializable public final class net.corda.core.node.NodeInfo extends java.lang.Object public (List, List, int, long) @org.jetbrains.annotations.NotNull public final List component1() @@ -1717,17 +1696,6 @@ public @interface net.corda.core.messaging.RPCReturnsObservables public final boolean isLegalIdentity(net.corda.core.identity.Party) public String toString() ## -@net.corda.core.serialization.CordaSerializable public final class net.corda.core.node.NotaryInfo extends java.lang.Object - public (net.corda.core.identity.Party, boolean) - @org.jetbrains.annotations.NotNull public final net.corda.core.identity.Party component1() - public final boolean component2() - @org.jetbrains.annotations.NotNull public final net.corda.core.node.NotaryInfo copy(net.corda.core.identity.Party, boolean) - public boolean equals(Object) - @org.jetbrains.annotations.NotNull public final net.corda.core.identity.Party getIdentity() - public final boolean getValidating() - public int hashCode() - public String toString() -## @net.corda.core.DoNotImplement public interface net.corda.core.node.ServiceHub extends net.corda.core.node.ServicesForResolution @org.jetbrains.annotations.NotNull public abstract net.corda.core.transactions.SignedTransaction addSignature(net.corda.core.transactions.SignedTransaction) @org.jetbrains.annotations.NotNull public abstract net.corda.core.transactions.SignedTransaction addSignature(net.corda.core.transactions.SignedTransaction, java.security.PublicKey) diff --git a/core/src/main/kotlin/net/corda/core/node/NetworkParameters.kt b/core/src/main/kotlin/net/corda/core/node/NetworkParameters.kt deleted file mode 100644 index d5a035175a..0000000000 --- a/core/src/main/kotlin/net/corda/core/node/NetworkParameters.kt +++ /dev/null @@ -1,40 +0,0 @@ -package net.corda.core.node - -import net.corda.core.identity.Party -import net.corda.core.serialization.CordaSerializable -import java.time.Duration -import java.time.Instant - -/** - * @property minimumPlatformVersion - * @property notaries - * @property eventHorizon - * @property maxMessageSize Maximum P2P message sent over the wire in bytes. - * @property maxTransactionSize Maximum permitted transaction size in bytes. - * @property modifiedTime - * @property epoch Version number of the network parameters. Starting from 1, this will always increment on each new set - * of parameters. - */ -// TODO Wire up the parameters -@CordaSerializable -data class NetworkParameters( - val minimumPlatformVersion: Int, - val notaries: List, - val eventHorizon: Duration, - val maxMessageSize: Int, - val maxTransactionSize: Int, - val modifiedTime: Instant, - val epoch: Int -) { - init { - require(minimumPlatformVersion > 0) { "minimumPlatformVersion must be at least 1" } - require(notaries.distinctBy { it.identity } == notaries) { "Duplicate notary identities" } - require(epoch > 0) { "epoch must be at least 1" } - } -} - -/** - * - */ -@CordaSerializable -data class NotaryInfo(val identity: Party, val validating: Boolean) diff --git a/node-api/src/main/kotlin/net/corda/nodeapi/internal/NetworkMap.kt b/node-api/src/main/kotlin/net/corda/nodeapi/internal/NetworkMap.kt new file mode 100644 index 0000000000..871b1e514f --- /dev/null +++ b/node-api/src/main/kotlin/net/corda/nodeapi/internal/NetworkMap.kt @@ -0,0 +1,75 @@ +package net.corda.nodeapi.internal + +import net.corda.core.crypto.DigitalSignature +import net.corda.core.crypto.SecureHash +import net.corda.core.crypto.verify +import net.corda.core.identity.Party +import net.corda.core.serialization.CordaSerializable +import net.corda.core.serialization.SerializedBytes +import net.corda.core.serialization.deserialize +import java.security.SignatureException +import java.security.cert.CertPathValidatorException +import java.security.cert.X509Certificate +import java.time.Duration +import java.time.Instant + +// TODO: Need more discussion on rather we should move this class out of internal. +/** + * Data class containing hash of [NetworkParameters] and network participant's [NodeInfo] hashes. + */ +@CordaSerializable +data class NetworkMap(val nodeInfoHashes: List, val networkParameterHash: SecureHash) + +/** + * @property minimumPlatformVersion + * @property notaries + * @property eventHorizon + * @property maxMessageSize Maximum P2P message sent over the wire in bytes. + * @property maxTransactionSize Maximum permitted transaction size in bytes. + * @property modifiedTime + * @property epoch Version number of the network parameters. Starting from 1, this will always increment on each new set + * of parameters. + */ +// TODO Wire up the parameters +@CordaSerializable +data class NetworkParameters( + val minimumPlatformVersion: Int, + val notaries: List, + val eventHorizon: Duration, + val maxMessageSize: Int, + val maxTransactionSize: Int, + val modifiedTime: Instant, + val epoch: Int +) { + init { + require(minimumPlatformVersion > 0) { "minimumPlatformVersion must be at least 1" } + require(notaries.distinctBy { it.identity } == notaries) { "Duplicate notary identities" } + require(epoch > 0) { "epoch must be at least 1" } + } +} + +@CordaSerializable +data class NotaryInfo(val identity: Party, val validating: Boolean) + +/** + * A serialized [NetworkMap] and its signature and certificate. Enforces signature validity in order to deserialize the data + * contained within. + */ +@CordaSerializable +class SignedNetworkMap(val raw: SerializedBytes, val sig: DigitalSignatureWithCert) { + /** + * Return the deserialized NetworkMap if the signature and certificate can be verified. + * + * @throws CertPathValidatorException if the certificate path is invalid. + * @throws SignatureException if the signature is invalid. + */ + @Throws(SignatureException::class) + fun verified(): NetworkMap { + sig.by.publicKey.verify(raw.bytes, sig) + return raw.deserialize() + } +} + +// TODO: This class should reside in the [DigitalSignature] class. +/** A digital signature that identifies who the public key is owned by, and the certificate which provides prove of the identity */ +class DigitalSignatureWithCert(val by: X509Certificate, val signatureBytes: ByteArray) : DigitalSignature(signatureBytes) \ No newline at end of file diff --git a/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/DefaultWhitelist.kt b/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/DefaultWhitelist.kt index 8a5cdbcaa3..c7c8e07232 100644 --- a/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/DefaultWhitelist.kt +++ b/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/DefaultWhitelist.kt @@ -6,6 +6,7 @@ import net.corda.core.utilities.NetworkHostAndPort import org.apache.activemq.artemis.api.core.SimpleString import rx.Notification import rx.exceptions.OnErrorNotImplementedException +import sun.security.x509.X509CertImpl import java.util.* /** @@ -58,6 +59,9 @@ object DefaultWhitelist : SerializationWhitelist { java.util.LinkedHashMap::class.java, BitSet::class.java, OnErrorNotImplementedException::class.java, - StackTraceElement::class.java - ) + StackTraceElement::class.java, + + // Implementation of X509Certificate. + X509CertImpl::class.java + ) } diff --git a/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/kryo/DefaultKryoCustomizer.kt b/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/kryo/DefaultKryoCustomizer.kt index 7d69264a43..5470dd2cf0 100644 --- a/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/kryo/DefaultKryoCustomizer.kt +++ b/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/kryo/DefaultKryoCustomizer.kt @@ -44,6 +44,7 @@ import org.objenesis.strategy.StdInstantiatorStrategy import org.slf4j.Logger import sun.security.ec.ECPublicKeyImpl import sun.security.provider.certpath.X509CertPath +import sun.security.x509.X509CertImpl import java.io.BufferedInputStream import java.io.ByteArrayOutputStream import java.io.FileInputStream @@ -75,6 +76,7 @@ object DefaultKryoCustomizer { addDefaultSerializer(InputStream::class.java, InputStreamSerializer) addDefaultSerializer(SerializeAsToken::class.java, SerializeAsTokenSerializer()) addDefaultSerializer(Logger::class.java, LoggerSerializer) + addDefaultSerializer(X509Certificate::class.java, X509CertificateSerializer) // WARNING: reordering the registrations here will cause a change in the serialized form, since classes // with custom serializers get written as registration ids. This will break backwards-compatibility. @@ -108,7 +110,6 @@ object DefaultKryoCustomizer { register(FileInputStream::class.java, InputStreamSerializer) register(CertPath::class.java, CertPathSerializer) register(X509CertPath::class.java, CertPathSerializer) - register(X509Certificate::class.java, X509CertificateSerializer) register(BCECPrivateKey::class.java, PrivateKeySerializer) register(BCECPublicKey::class.java, publicKeySerializer) register(BCRSAPrivateCrtKey::class.java, PrivateKeySerializer) diff --git a/node/src/integration-test/kotlin/net/corda/node/services/BFTNotaryServiceTests.kt b/node/src/integration-test/kotlin/net/corda/node/services/BFTNotaryServiceTests.kt index e7d05ff80e..441cb780e5 100644 --- a/node/src/integration-test/kotlin/net/corda/node/services/BFTNotaryServiceTests.kt +++ b/node/src/integration-test/kotlin/net/corda/node/services/BFTNotaryServiceTests.kt @@ -13,7 +13,6 @@ import net.corda.core.identity.CordaX500Name import net.corda.core.identity.Party import net.corda.core.internal.deleteIfExists import net.corda.core.internal.div -import net.corda.core.node.NotaryInfo import net.corda.core.node.services.NotaryService import net.corda.core.transactions.SignedTransaction import net.corda.core.transactions.TransactionBuilder @@ -26,6 +25,7 @@ import net.corda.node.services.config.NotaryConfig import net.corda.node.services.transactions.minClusterSize import net.corda.node.services.transactions.minCorrectReplicas import net.corda.node.utilities.ServiceIdentityGenerator +import net.corda.nodeapi.internal.NotaryInfo import net.corda.testing.chooseIdentity import net.corda.testing.common.internal.NetworkParametersCopier import net.corda.testing.common.internal.testNetworkParameters diff --git a/node/src/integration-test/kotlin/net/corda/node/services/network/NetworkMapClientTest.kt b/node/src/integration-test/kotlin/net/corda/node/services/network/NetworkMapClientTest.kt index c428422eb5..d82386ab5b 100644 --- a/node/src/integration-test/kotlin/net/corda/node/services/network/NetworkMapClientTest.kt +++ b/node/src/integration-test/kotlin/net/corda/node/services/network/NetworkMapClientTest.kt @@ -1,7 +1,6 @@ package net.corda.node.services.network import net.corda.core.node.NodeInfo -import net.corda.core.utilities.minutes import net.corda.core.utilities.seconds import net.corda.testing.ALICE import net.corda.testing.BOB @@ -18,7 +17,7 @@ class NetworkMapClientTest { @Test fun `nodes can see each other using the http network map`() { - NetworkMapServer(1.minutes, portAllocation.nextHostAndPort()).use { + NetworkMapServer(1.seconds, portAllocation.nextHostAndPort()).use { val (host, port) = it.start() driver(portAllocation = portAllocation, compatibilityZoneURL = URL("http://$host:$port")) { val alice = startNode(providedName = ALICE.name) diff --git a/node/src/main/kotlin/net/corda/node/internal/AbstractNode.kt b/node/src/main/kotlin/net/corda/node/internal/AbstractNode.kt index 0b6ef924de..2066b43a94 100644 --- a/node/src/main/kotlin/net/corda/node/internal/AbstractNode.kt +++ b/node/src/main/kotlin/net/corda/node/internal/AbstractNode.kt @@ -44,15 +44,8 @@ import net.corda.node.services.events.ScheduledActivityObserver import net.corda.node.services.identity.PersistentIdentityService import net.corda.node.services.keys.PersistentKeyManagementService import net.corda.node.services.messaging.MessagingService -import net.corda.node.services.network.NetworkMapCacheImpl -import net.corda.node.services.network.NodeInfoWatcher -import net.corda.node.services.network.PersistentNetworkMapCache -import net.corda.node.services.persistence.* import net.corda.node.services.network.* -import net.corda.node.services.persistence.DBCheckpointStorage -import net.corda.node.services.persistence.DBTransactionMappingStorage -import net.corda.node.services.persistence.DBTransactionStorage -import net.corda.node.services.persistence.NodeAttachmentService +import net.corda.node.services.persistence.* import net.corda.node.services.schema.HibernateObserver import net.corda.node.services.schema.NodeSchemaService import net.corda.node.services.statemachine.* @@ -64,6 +57,7 @@ import net.corda.node.shell.InteractiveShell import net.corda.node.utilities.AffinityExecutor import net.corda.node.utilities.CordaPersistence import net.corda.node.utilities.configureDatabase +import net.corda.nodeapi.internal.NetworkParameters import net.corda.nodeapi.internal.crypto.* import org.apache.activemq.artemis.utils.ReusableLatch import org.slf4j.Logger @@ -137,7 +131,11 @@ abstract class AbstractNode(val configuration: NodeConfiguration, protected lateinit var network: MessagingService protected val runOnStop = ArrayList<() -> Any?>() protected val _nodeReadyFuture = openFuture() - protected val networkMapClient: NetworkMapClient? by lazy { configuration.compatibilityZoneURL?.let(::NetworkMapClient) } + protected val networkMapClient: NetworkMapClient? by lazy { + configuration.compatibilityZoneURL?.let { + NetworkMapClient(it, services.identityService.trustRoot) + } + } lateinit var userService: RPCUserService get diff --git a/node/src/main/kotlin/net/corda/node/services/network/NetworkMapClient.kt b/node/src/main/kotlin/net/corda/node/services/network/NetworkMapClient.kt index 02f4310061..9842d6776e 100644 --- a/node/src/main/kotlin/net/corda/node/services/network/NetworkMapClient.kt +++ b/node/src/main/kotlin/net/corda/node/services/network/NetworkMapClient.kt @@ -1,6 +1,5 @@ package net.corda.node.services.network -import com.fasterxml.jackson.databind.ObjectMapper import com.google.common.util.concurrent.MoreExecutors import net.corda.core.crypto.SecureHash import net.corda.core.crypto.SignedData @@ -13,6 +12,10 @@ import net.corda.core.utilities.minutes import net.corda.core.utilities.seconds import net.corda.node.services.api.NetworkMapCacheInternal import net.corda.node.utilities.NamedThreadFactory +import net.corda.nodeapi.internal.NetworkMap +import net.corda.nodeapi.internal.NetworkParameters +import net.corda.nodeapi.internal.SignedNetworkMap +import net.corda.nodeapi.internal.crypto.X509Utilities import okhttp3.CacheControl import okhttp3.Headers import rx.Subscription @@ -20,11 +23,12 @@ import java.io.BufferedReader import java.io.Closeable import java.net.HttpURLConnection import java.net.URL +import java.security.cert.X509Certificate import java.time.Duration import java.util.concurrent.Executors import java.util.concurrent.TimeUnit -class NetworkMapClient(compatibilityZoneURL: URL) { +class NetworkMapClient(compatibilityZoneURL: URL, private val trustedRoot: X509Certificate) { private val networkMapUrl = URL("$compatibilityZoneURL/network-map") fun publish(signedNodeInfo: SignedData) { @@ -42,14 +46,30 @@ class NetworkMapClient(compatibilityZoneURL: URL) { fun getNetworkMap(): NetworkMapResponse { val conn = networkMapUrl.openHttpConnection() - val response = conn.inputStream.bufferedReader().use(BufferedReader::readLine) - val networkMap = ObjectMapper().readValue(response, List::class.java).map { SecureHash.parse(it.toString()) } + val signedNetworkMap = conn.inputStream.use { it.readBytes() }.deserialize() + val networkMap = signedNetworkMap.verified() + // Assume network map cert is issued by the root. + X509Utilities.validateCertificateChain(trustedRoot, signedNetworkMap.sig.by, trustedRoot) val timeout = CacheControl.parse(Headers.of(conn.headerFields.filterKeys { it != null }.mapValues { it.value.first() })).maxAgeSeconds().seconds return NetworkMapResponse(networkMap, timeout) } fun getNodeInfo(nodeInfoHash: SecureHash): NodeInfo? { - val conn = URL("$networkMapUrl/$nodeInfoHash").openHttpConnection() + val conn = URL("$networkMapUrl/node-info/$nodeInfoHash").openHttpConnection() + return if (conn.responseCode == HttpURLConnection.HTTP_NOT_FOUND) { + null + } else { + val signedNodeInfo = conn.inputStream.use { it.readBytes() }.deserialize>() + val nodeInfo = signedNodeInfo.verified() + // Verify node info is signed by node identity + // TODO : Validate multiple signatures when NodeInfo supports multiple identities. + require(nodeInfo.legalIdentities.any { it.owningKey == signedNodeInfo.sig.by }) { "NodeInfo must be signed by the node owning key." } + nodeInfo + } + } + + fun getNetworkParameter(networkParameterHash: SecureHash): NetworkParameters? { + val conn = URL("$networkMapUrl/network-parameter/$networkParameterHash").openHttpConnection() return if (conn.responseCode == HttpURLConnection.HTTP_NOT_FOUND) { null } else { @@ -63,7 +83,7 @@ class NetworkMapClient(compatibilityZoneURL: URL) { } } -data class NetworkMapResponse(val networkMap: List, val cacheMaxAge: Duration) +data class NetworkMapResponse(val networkMap: NetworkMap, val cacheMaxAge: Duration) class NetworkMapUpdater(private val networkMapCache: NetworkMapCacheInternal, private val fileWatcher: NodeInfoWatcher, @@ -107,21 +127,28 @@ class NetworkMapUpdater(private val networkMapCache: NetworkMapCacheInternal, val nextScheduleDelay = try { val (networkMap, cacheTimeout) = networkMapClient.getNetworkMap() val currentNodeHashes = networkMapCache.allNodeHashes - (networkMap - currentNodeHashes).mapNotNull { + val hashesFromNetworkMap = networkMap.nodeInfoHashes + (hashesFromNetworkMap - currentNodeHashes).mapNotNull { // Download new node info from network map - networkMapClient.getNodeInfo(it) + try { + networkMapClient.getNodeInfo(it) + } catch (t: Throwable) { + // Failure to retrieve one node info shouldn't stop the whole update, log and return null instead. + logger.warn("Error encountered when downloading node info '$it', skipping...", t) + null + } }.forEach { // Add new node info to the network map cache, these could be new node info or modification of node info for existing nodes. networkMapCache.addNode(it) } // Remove node info from network map. - (currentNodeHashes - networkMap - fileWatcher.processedNodeInfoHashes) + (currentNodeHashes - hashesFromNetworkMap - fileWatcher.processedNodeInfoHashes) .mapNotNull(networkMapCache::getNodeByHash) .forEach(networkMapCache::removeNode) - + // TODO: Check NetworkParameter. cacheTimeout } catch (t: Throwable) { - logger.warn("Error encountered while updating network map, will retry in $retryInterval", t) + logger.warn("Error encountered while updating network map, will retry in ${retryInterval.seconds} seconds", t) retryInterval } // Schedule the next update. @@ -137,7 +164,7 @@ class NetworkMapUpdater(private val networkMapCache: NetworkMapCacheInternal, try { networkMapClient.publish(signedNodeInfo) } catch (t: Throwable) { - logger.warn("Error encountered while publishing node info, will retry in $retryInterval.", t) + logger.warn("Error encountered while publishing node info, will retry in ${retryInterval.seconds} seconds.", t) // TODO: Exponential backoff? executor.schedule(this, retryInterval.toMillis(), TimeUnit.MILLISECONDS) } diff --git a/node/src/main/kotlin/net/corda/node/services/network/PersistentNetworkMapCache.kt b/node/src/main/kotlin/net/corda/node/services/network/PersistentNetworkMapCache.kt index 10086d5108..2fccc140ba 100644 --- a/node/src/main/kotlin/net/corda/node/services/network/PersistentNetworkMapCache.kt +++ b/node/src/main/kotlin/net/corda/node/services/network/PersistentNetworkMapCache.kt @@ -12,7 +12,6 @@ import net.corda.core.internal.concurrent.openFuture import net.corda.core.internal.schemas.NodeInfoSchemaV1 import net.corda.core.messaging.DataFeed import net.corda.core.node.NodeInfo -import net.corda.core.node.NotaryInfo import net.corda.core.node.services.IdentityService import net.corda.core.node.services.NetworkMapCache.MapChange import net.corda.core.node.services.PartyInfo @@ -25,6 +24,7 @@ import net.corda.node.services.api.NetworkMapCacheInternal import net.corda.node.utilities.CordaPersistence import net.corda.node.utilities.bufferUntilDatabaseCommit import net.corda.node.utilities.wrapWithDatabaseTransaction +import net.corda.nodeapi.internal.NotaryInfo import org.hibernate.Session import rx.Observable import rx.subjects.PublishSubject diff --git a/node/src/test/kotlin/net/corda/node/services/network/NetworkMapClientTest.kt b/node/src/test/kotlin/net/corda/node/services/network/NetworkMapClientTest.kt index fc4b037de9..04df884285 100644 --- a/node/src/test/kotlin/net/corda/node/services/network/NetworkMapClientTest.kt +++ b/node/src/test/kotlin/net/corda/node/services/network/NetworkMapClientTest.kt @@ -1,11 +1,14 @@ package net.corda.node.services.network +import net.corda.core.crypto.SecureHash import net.corda.core.crypto.sha256 +import net.corda.core.internal.cert import net.corda.core.serialization.serialize import net.corda.core.utilities.seconds import net.corda.node.services.network.TestNodeInfoFactory.createNodeInfo -import net.corda.nodeapi.internal.crypto.CertificateType -import net.corda.nodeapi.internal.crypto.X509Utilities +import net.corda.testing.DEV_CA +import net.corda.testing.DEV_TRUST_ROOT +import net.corda.testing.ROOT_CA import net.corda.testing.SerializationEnvironmentRule import net.corda.testing.driver.PortAllocation import net.corda.testing.node.network.NetworkMapServer @@ -16,6 +19,7 @@ import org.junit.Rule import org.junit.Test import java.net.URL import kotlin.test.assertEquals +import kotlin.test.assertNotNull class NetworkMapClientTest { @Rule @@ -32,7 +36,7 @@ class NetworkMapClientTest { fun setUp() { server = NetworkMapServer(cacheTimeout, PortAllocation.Incremental(10000).nextHostAndPort()) val hostAndPort = server.start() - networkMapClient = NetworkMapClient(URL("http://${hostAndPort.host}:${hostAndPort.port}")) + networkMapClient = NetworkMapClient(URL("http://${hostAndPort.host}:${hostAndPort.port}"), DEV_TRUST_ROOT.cert) } @After @@ -50,7 +54,7 @@ class NetworkMapClientTest { val nodeInfoHash = nodeInfo.serialize().sha256() - assertThat(networkMapClient.getNetworkMap().networkMap).containsExactly(nodeInfoHash) + assertThat(networkMapClient.getNetworkMap().networkMap.nodeInfoHashes).containsExactly(nodeInfoHash) assertEquals(nodeInfo, networkMapClient.getNodeInfo(nodeInfoHash)) val signedNodeInfo2 = createNodeInfo("Test2") @@ -58,13 +62,22 @@ class NetworkMapClientTest { networkMapClient.publish(signedNodeInfo2) val nodeInfoHash2 = nodeInfo2.serialize().sha256() - assertThat(networkMapClient.getNetworkMap().networkMap).containsExactly(nodeInfoHash, nodeInfoHash2) + assertThat(networkMapClient.getNetworkMap().networkMap.nodeInfoHashes).containsExactly(nodeInfoHash, nodeInfoHash2) assertEquals(cacheTimeout, networkMapClient.getNetworkMap().cacheMaxAge) assertEquals(nodeInfo2, networkMapClient.getNodeInfo(nodeInfoHash2)) } + + @Test + fun `download NetworkParameter correctly`() { + // The test server returns same network parameter for any hash. + val networkParameter = networkMapClient.getNetworkParameter(SecureHash.randomSHA256()) + assertNotNull(networkParameter) + assertEquals(NetworkMapServer.stubNetworkParameter, networkParameter) + } + @Test fun `get hostname string from http response correctly`() { - assertEquals("test.host.name", networkMapClient.myPublicHostname()) + assertEquals("test.host.name", networkMapClient.myPublicHostname()) } } diff --git a/node/src/test/kotlin/net/corda/node/services/network/NetworkMapUpdaterTest.kt b/node/src/test/kotlin/net/corda/node/services/network/NetworkMapUpdaterTest.kt index 3390d32968..12086491ca 100644 --- a/node/src/test/kotlin/net/corda/node/services/network/NetworkMapUpdaterTest.kt +++ b/node/src/test/kotlin/net/corda/node/services/network/NetworkMapUpdaterTest.kt @@ -13,6 +13,7 @@ import net.corda.core.crypto.SignedData import net.corda.core.identity.Party import net.corda.core.internal.div import net.corda.core.internal.uncheckedCast +import net.corda.nodeapi.internal.NetworkMap import net.corda.core.node.NodeInfo import net.corda.core.serialization.serialize import net.corda.core.utilities.NetworkHostAndPort @@ -95,7 +96,7 @@ class NetworkMapUpdaterTest { val signedNodeInfo: SignedData = uncheckedCast(it.arguments.first()) nodeInfoMap.put(signedNodeInfo.verified().serialize().hash, signedNodeInfo) } - on { getNetworkMap() }.then { NetworkMapResponse(nodeInfoMap.keys.toList(), 100.millis) } + on { getNetworkMap() }.then { NetworkMapResponse(NetworkMap(nodeInfoMap.keys.toList(), SecureHash.randomSHA256()), 100.millis) } on { getNodeInfo(any()) }.then { nodeInfoMap[it.arguments.first()]?.verified() } } @@ -149,7 +150,7 @@ class NetworkMapUpdaterTest { val signedNodeInfo: SignedData = uncheckedCast(it.arguments.first()) nodeInfoMap.put(signedNodeInfo.verified().serialize().hash, signedNodeInfo) } - on { getNetworkMap() }.then { NetworkMapResponse(nodeInfoMap.keys.toList(), 100.millis) } + on { getNetworkMap() }.then { NetworkMapResponse(NetworkMap(nodeInfoMap.keys.toList(), SecureHash.randomSHA256()), 100.millis) } on { getNodeInfo(any()) }.then { nodeInfoMap[it.arguments.first()]?.verified() } } diff --git a/testing/node-driver/src/integration-test/kotlin/net/corda/testing/driver/DriverTests.kt b/testing/node-driver/src/integration-test/kotlin/net/corda/testing/driver/DriverTests.kt index 729f68fe92..85526280af 100644 --- a/testing/node-driver/src/integration-test/kotlin/net/corda/testing/driver/DriverTests.kt +++ b/testing/node-driver/src/integration-test/kotlin/net/corda/testing/driver/DriverTests.kt @@ -6,6 +6,7 @@ import net.corda.core.internal.list import net.corda.core.internal.readLines import net.corda.core.utilities.getOrThrow import net.corda.core.utilities.minutes +import net.corda.core.utilities.seconds import net.corda.node.internal.NodeStartup import net.corda.testing.DUMMY_BANK_A import net.corda.testing.DUMMY_NOTARY @@ -63,7 +64,7 @@ class DriverTests { @Test fun `node registration`() { val handler = RegistrationHandler() - NetworkMapServer(1.minutes, portAllocation.nextHostAndPort(), handler).use { + NetworkMapServer(1.seconds, portAllocation.nextHostAndPort(), handler).use { val (host, port) = it.start() driver(portAllocation = portAllocation, compatibilityZoneURL = URL("http://$host:$port")) { // Wait for the node to have started. diff --git a/testing/node-driver/src/main/kotlin/net/corda/testing/driver/Driver.kt b/testing/node-driver/src/main/kotlin/net/corda/testing/driver/Driver.kt index dae08d3c7d..3a61e18f2b 100644 --- a/testing/node-driver/src/main/kotlin/net/corda/testing/driver/Driver.kt +++ b/testing/node-driver/src/main/kotlin/net/corda/testing/driver/Driver.kt @@ -17,7 +17,6 @@ import net.corda.core.internal.* import net.corda.core.internal.concurrent.* import net.corda.core.messaging.CordaRPCOps import net.corda.core.node.NodeInfo -import net.corda.core.node.NotaryInfo import net.corda.core.node.services.NetworkMapCache import net.corda.core.node.services.NotaryService import net.corda.core.toFuture @@ -34,6 +33,7 @@ import net.corda.node.utilities.registration.NetworkRegistrationHelper import net.corda.nodeapi.NodeInfoFilesCopier import net.corda.nodeapi.User import net.corda.nodeapi.config.toConfig +import net.corda.nodeapi.internal.NotaryInfo import net.corda.nodeapi.internal.addShutdownHook import net.corda.testing.* import net.corda.testing.common.internal.NetworkParametersCopier diff --git a/testing/node-driver/src/main/kotlin/net/corda/testing/node/MockNode.kt b/testing/node-driver/src/main/kotlin/net/corda/testing/node/MockNode.kt index 9ae8a956b7..ed5806098f 100644 --- a/testing/node-driver/src/main/kotlin/net/corda/testing/node/MockNode.kt +++ b/testing/node-driver/src/main/kotlin/net/corda/testing/node/MockNode.kt @@ -17,7 +17,6 @@ import net.corda.core.messaging.MessageRecipients import net.corda.core.messaging.RPCOps import net.corda.core.messaging.SingleMessageRecipient import net.corda.core.node.NodeInfo -import net.corda.core.node.NotaryInfo import net.corda.core.node.services.IdentityService import net.corda.core.node.services.KeyManagementService import net.corda.core.serialization.SerializationWhitelist @@ -39,12 +38,13 @@ import net.corda.node.utilities.AffinityExecutor import net.corda.node.utilities.AffinityExecutor.ServiceAffinityExecutor import net.corda.node.utilities.CordaPersistence import net.corda.node.utilities.ServiceIdentityGenerator +import net.corda.nodeapi.internal.NotaryInfo import net.corda.testing.DUMMY_NOTARY import net.corda.testing.common.internal.NetworkParametersCopier import net.corda.testing.common.internal.testNetworkParameters -import net.corda.testing.setGlobalSerialization import net.corda.testing.node.MockServices.Companion.MOCK_VERSION_INFO import net.corda.testing.node.MockServices.Companion.makeTestDataSourceProperties +import net.corda.testing.setGlobalSerialization import net.corda.testing.testNodeConfiguration import org.apache.activemq.artemis.utils.ReusableLatch import org.apache.sshd.common.util.security.SecurityUtils @@ -159,29 +159,32 @@ class MockNetwork(defaultParameters: MockNetworkParameters = MockNetworkParamete * Returns the single notary node on the network. Throws if there are none or more than one. * @see notaryNodes */ - val defaultNotaryNode: StartedNode get() { - return when (notaryNodes.size) { - 0 -> throw IllegalStateException("There are no notaries defined on the network") - 1 -> notaryNodes[0] - else -> throw IllegalStateException("There is more than one notary defined on the network") + val defaultNotaryNode: StartedNode + get() { + return when (notaryNodes.size) { + 0 -> throw IllegalStateException("There are no notaries defined on the network") + 1 -> notaryNodes[0] + else -> throw IllegalStateException("There is more than one notary defined on the network") + } } - } /** * Return the identity of the default notary node. * @see defaultNotaryNode */ - val defaultNotaryIdentity: Party get() { - return defaultNotaryNode.info.legalIdentities.singleOrNull() ?: throw IllegalStateException("Default notary has multiple identities") - } + val defaultNotaryIdentity: Party + get() { + return defaultNotaryNode.info.legalIdentities.singleOrNull() ?: throw IllegalStateException("Default notary has multiple identities") + } /** * Return the identity of the default notary node. * @see defaultNotaryNode */ - val defaultNotaryIdentityAndCert: PartyAndCertificate get() { - return defaultNotaryNode.info.legalIdentitiesAndCerts.singleOrNull() ?: throw IllegalStateException("Default notary has multiple identities") - } + val defaultNotaryIdentityAndCert: PartyAndCertificate + get() { + return defaultNotaryNode.info.legalIdentitiesAndCerts.singleOrNull() ?: throw IllegalStateException("Default notary has multiple identities") + } /** * Because this executor is shared, we need to be careful about nodes shutting it down. diff --git a/testing/node-driver/src/main/kotlin/net/corda/testing/node/network/NetworkMapServer.kt b/testing/node-driver/src/main/kotlin/net/corda/testing/node/network/NetworkMapServer.kt index 94ebdcb199..1e3df5b434 100644 --- a/testing/node-driver/src/main/kotlin/net/corda/testing/node/network/NetworkMapServer.kt +++ b/testing/node-driver/src/main/kotlin/net/corda/testing/node/network/NetworkMapServer.kt @@ -1,13 +1,25 @@ package net.corda.testing.node.network -import com.fasterxml.jackson.databind.ObjectMapper +import net.corda.core.crypto.Crypto import net.corda.core.crypto.SecureHash import net.corda.core.crypto.SignedData import net.corda.core.crypto.sha256 +import net.corda.core.internal.cert +import net.corda.core.internal.toX509CertHolder import net.corda.core.node.NodeInfo import net.corda.core.serialization.deserialize import net.corda.core.serialization.serialize import net.corda.core.utilities.NetworkHostAndPort +import net.corda.core.utilities.hours +import net.corda.nodeapi.internal.DigitalSignatureWithCert +import net.corda.nodeapi.internal.NetworkMap +import net.corda.nodeapi.internal.NetworkParameters +import net.corda.nodeapi.internal.SignedNetworkMap +import net.corda.nodeapi.internal.crypto.CertificateAndKeyPair +import net.corda.nodeapi.internal.crypto.CertificateType +import net.corda.nodeapi.internal.crypto.X509Utilities +import net.corda.testing.ROOT_CA +import org.bouncycastle.asn1.x500.X500Name import org.eclipse.jetty.server.Server import org.eclipse.jetty.server.ServerConnector import org.eclipse.jetty.server.handler.HandlerCollection @@ -19,15 +31,34 @@ import java.io.Closeable import java.io.InputStream import java.net.InetSocketAddress import java.time.Duration +import java.time.Instant import javax.ws.rs.* import javax.ws.rs.core.MediaType import javax.ws.rs.core.Response import javax.ws.rs.core.Response.ok -class NetworkMapServer(cacheTimeout: Duration, hostAndPort: NetworkHostAndPort, vararg additionalServices: Any) : Closeable { - private val server: Server +class NetworkMapServer(cacheTimeout: Duration, + hostAndPort: NetworkHostAndPort, + vararg additionalServices: Any) : Closeable { + companion object { + val stubNetworkParameter = NetworkParameters(1, emptyList(), 1.hours, 10, 10, Instant.now(), 10) - private val service = InMemoryNetworkMapService(cacheTimeout) + private fun networkMapKeyAndCert(rootCAKeyAndCert: CertificateAndKeyPair): CertificateAndKeyPair { + val networkMapKey = Crypto.generateKeyPair(X509Utilities.DEFAULT_TLS_SIGNATURE_SCHEME) + val networkMapCert = X509Utilities.createCertificate( + CertificateType.IDENTITY, + rootCAKeyAndCert.certificate, + rootCAKeyAndCert.keyPair, + X500Name("CN=Corda Network Map,L=London"), + networkMapKey.public).cert + return CertificateAndKeyPair(networkMapCert.toX509CertHolder(), networkMapKey) + } + } + + private val server: Server + // Default to ROOT_CA for testing. + // TODO: make this configurable? + private val service = InMemoryNetworkMapService(cacheTimeout, networkMapKeyAndCert(ROOT_CA)) init { server = Server(InetSocketAddress(hostAndPort.host, hostAndPort.port)).apply { @@ -68,8 +99,9 @@ class NetworkMapServer(cacheTimeout: Duration, hostAndPort: NetworkHostAndPort, } @Path("network-map") - class InMemoryNetworkMapService(private val cacheTimeout: Duration) { - private val nodeInfoMap = mutableMapOf() + class InMemoryNetworkMapService(private val cacheTimeout: Duration, private val networkMapKeyAndCert: CertificateAndKeyPair) { + private val nodeInfoMap = mutableMapOf>() + @POST @Path("publish") @Consumes(MediaType.APPLICATION_OCTET_STREAM) @@ -77,39 +109,48 @@ class NetworkMapServer(cacheTimeout: Duration, hostAndPort: NetworkHostAndPort, val registrationData = input.readBytes().deserialize>() val nodeInfo = registrationData.verified() val nodeInfoHash = nodeInfo.serialize().sha256() - nodeInfoMap.put(nodeInfoHash, nodeInfo) + nodeInfoMap.put(nodeInfoHash, registrationData) return ok().build() } @GET - @Produces(MediaType.APPLICATION_JSON) + @Produces(MediaType.APPLICATION_OCTET_STREAM) fun getNetworkMap(): Response { - return Response.ok(ObjectMapper().writeValueAsString(nodeInfoMap.keys.map { it.toString() })) - .header("Cache-Control", "max-age=${cacheTimeout.seconds}") - .build() + val networkMap = NetworkMap(nodeInfoMap.keys.map { it }, SecureHash.randomSHA256()) + val serializedNetworkMap = networkMap.serialize() + val signature = Crypto.doSign(networkMapKeyAndCert.keyPair.private, serializedNetworkMap.bytes) + val signedNetworkMap = SignedNetworkMap(networkMap.serialize(), DigitalSignatureWithCert(networkMapKeyAndCert.certificate.cert, signature)) + return Response.ok(signedNetworkMap.serialize().bytes).header("Cache-Control", "max-age=${cacheTimeout.seconds}").build() + } + + // Remove nodeInfo for testing. + fun removeNodeInfo(nodeInfo: NodeInfo) { + nodeInfoMap.remove(nodeInfo.serialize().hash) } @GET - @Path("{var}") + @Path("node-info/{var}") @Produces(MediaType.APPLICATION_OCTET_STREAM) fun getNodeInfo(@PathParam("var") nodeInfoHash: String): Response { - val nodeInfo = nodeInfoMap[SecureHash.parse(nodeInfoHash)] - return if (nodeInfo != null) { - Response.ok(nodeInfo.serialize().bytes) + val signedNodeInfo = nodeInfoMap[SecureHash.parse(nodeInfoHash)] + return if (signedNodeInfo != null) { + Response.ok(signedNodeInfo.serialize().bytes) } else { Response.status(Response.Status.NOT_FOUND) }.build() } + @GET + @Path("network-parameter/{var}") + @Produces(MediaType.APPLICATION_OCTET_STREAM) + fun getNetworkParameter(@PathParam("var") networkParameterHash: String): Response { + return Response.ok(stubNetworkParameter.serialize().bytes).build() + } + @GET @Path("my-hostname") fun getHostName(): Response { return Response.ok("test.host.name").build() } - - // Remove nodeInfo for testing. - fun removeNodeInfo(nodeInfo: NodeInfo) { - nodeInfoMap.remove(nodeInfo.serialize().hash) - } } } diff --git a/testing/test-common/build.gradle b/testing/test-common/build.gradle index dd2e2285b5..df37173fc1 100644 --- a/testing/test-common/build.gradle +++ b/testing/test-common/build.gradle @@ -3,6 +3,7 @@ apply plugin: 'com.jfrog.artifactory' dependencies { compile project(':core') + compile project(':node-api') } jar { diff --git a/testing/test-common/src/main/kotlin/net/corda/testing/common/internal/NetworkParametersCopier.kt b/testing/test-common/src/main/kotlin/net/corda/testing/common/internal/NetworkParametersCopier.kt index 25ba327a73..f8a8d26952 100644 --- a/testing/test-common/src/main/kotlin/net/corda/testing/common/internal/NetworkParametersCopier.kt +++ b/testing/test-common/src/main/kotlin/net/corda/testing/common/internal/NetworkParametersCopier.kt @@ -5,8 +5,8 @@ import net.corda.core.crypto.entropyToKeyPair import net.corda.core.crypto.sign import net.corda.core.internal.copyTo import net.corda.core.internal.div -import net.corda.core.node.NetworkParameters import net.corda.core.serialization.serialize +import net.corda.nodeapi.internal.NetworkParameters import java.math.BigInteger import java.nio.file.FileAlreadyExistsException import java.nio.file.Path diff --git a/testing/test-common/src/main/kotlin/net/corda/testing/common/internal/ParametersUtilities.kt b/testing/test-common/src/main/kotlin/net/corda/testing/common/internal/ParametersUtilities.kt index da40ba6ac3..ea84cd88c8 100644 --- a/testing/test-common/src/main/kotlin/net/corda/testing/common/internal/ParametersUtilities.kt +++ b/testing/test-common/src/main/kotlin/net/corda/testing/common/internal/ParametersUtilities.kt @@ -1,8 +1,8 @@ package net.corda.testing.common.internal -import net.corda.core.node.NetworkParameters -import net.corda.core.node.NotaryInfo import net.corda.core.utilities.days +import net.corda.nodeapi.internal.NetworkParameters +import net.corda.nodeapi.internal.NotaryInfo import java.time.Instant fun testNetworkParameters(notaries: List): NetworkParameters { diff --git a/testing/test-utils/src/main/kotlin/net/corda/testing/TestConstants.kt b/testing/test-utils/src/main/kotlin/net/corda/testing/TestConstants.kt index 96f452e74c..4f3eb0f66e 100644 --- a/testing/test-utils/src/main/kotlin/net/corda/testing/TestConstants.kt +++ b/testing/test-utils/src/main/kotlin/net/corda/testing/TestConstants.kt @@ -70,6 +70,12 @@ val DEV_CA: CertificateAndKeyPair by lazy { val caKeyStore = loadKeyStore(ClassLoader.getSystemResourceAsStream("net/corda/node/internal/certificates/cordadevcakeys.jks"), "cordacadevpass") caKeyStore.getCertificateAndKeyPair(X509Utilities.CORDA_INTERMEDIATE_CA, "cordacadevkeypass") } + +val ROOT_CA: CertificateAndKeyPair by lazy { + // TODO: Should be identity scheme + val caKeyStore = loadKeyStore(ClassLoader.getSystemResourceAsStream("net/corda/node/internal/certificates/cordadevcakeys.jks"), "cordacadevpass") + caKeyStore.getCertificateAndKeyPair(X509Utilities.CORDA_ROOT_CA, "cordacadevkeypass") +} val DEV_TRUST_ROOT: X509CertificateHolder by lazy { // TODO: Should be identity scheme val caKeyStore = loadKeyStore(ClassLoader.getSystemResourceAsStream("net/corda/node/internal/certificates/cordadevcakeys.jks"), "cordacadevpass") From c9f3e98795983330548282f58052dc3fa3d005ed Mon Sep 17 00:00:00 2001 From: Katarzyna Streich Date: Thu, 30 Nov 2017 10:39:29 +0000 Subject: [PATCH 11/23] Another approach to fixing deployNodes task and network parameters generation (#2066) * Generate networkParameteres for Cordformation. Fix deployNodes task in Cordformation to generate NetworkParameters before running the nodes. Add TestNetworkParametersGenerator utility loaded after node infos generation step. * Get rid of bouncy castle provider dependency For cordform-common. It caused problems with loading our custom X509EdDSAEngine for generation of network parameters in deployNodes task. --- constants.properties | 2 +- gradle-plugins/cordform-common/build.gradle | 3 - .../net/corda/cordform/CordformContext.java | 1 - .../cordform/NetworkParametersGenerator.java | 16 +++ .../main/kotlin/net/corda/plugins/Cordform.kt | 42 +++++-- .../src/main/kotlin/net/corda/plugins/Node.kt | 52 ++------ .../internal/NetworkParametersCopier.kt | 2 +- .../TestNetworkParametersGenerator.kt | 115 ++++++++++++++++++ .../node/services/BFTNotaryServiceTests.kt | 2 +- .../net/corda/node/internal/AbstractNode.kt | 1 + .../services/messaging/RPCMessagingClient.kt | 1 + samples/irs-demo/cordapp/build.gradle | 2 +- .../net/corda/test/spring/SpringDriver.kt | 2 +- .../kotlin/net/corda/testing/driver/Driver.kt | 2 +- .../corda/testing/internal/NodeBasedTest.kt | 2 +- .../kotlin/net/corda/testing/node/MockNode.kt | 2 +- .../net/corda/smoketesting/NodeProcess.kt | 2 +- 17 files changed, 185 insertions(+), 64 deletions(-) create mode 100644 gradle-plugins/cordform-common/src/main/java/net/corda/cordform/NetworkParametersGenerator.java rename {testing/test-common/src/main/kotlin/net/corda/testing/common => node-api/src/main/kotlin/net/corda/nodeapi}/internal/NetworkParametersCopier.kt (96%) create mode 100644 node-api/src/main/kotlin/net/corda/nodeapi/internal/TestNetworkParametersGenerator.kt diff --git a/constants.properties b/constants.properties index f278032d63..6e842b67ca 100644 --- a/constants.properties +++ b/constants.properties @@ -1,4 +1,4 @@ -gradlePluginsVersion=2.0.9 +gradlePluginsVersion=3.0.0-NETWORKMAP kotlinVersion=1.1.60 guavaVersion=21.0 bouncycastleVersion=1.57 diff --git a/gradle-plugins/cordform-common/build.gradle b/gradle-plugins/cordform-common/build.gradle index 82274e0d09..2c848cbbdb 100644 --- a/gradle-plugins/cordform-common/build.gradle +++ b/gradle-plugins/cordform-common/build.gradle @@ -13,9 +13,6 @@ group 'net.corda.plugins' dependencies { // TypeSafe Config: for simple and human friendly config files. compile "com.typesafe:config:$typesafe_config_version" - - // Bouncy Castle: for X.500 distinguished name manipulation - compile "org.bouncycastle:bcprov-jdk15on:$bouncycastle_version" } publish { diff --git a/gradle-plugins/cordform-common/src/main/java/net/corda/cordform/CordformContext.java b/gradle-plugins/cordform-common/src/main/java/net/corda/cordform/CordformContext.java index 06d4375659..7687f68a11 100644 --- a/gradle-plugins/cordform-common/src/main/java/net/corda/cordform/CordformContext.java +++ b/gradle-plugins/cordform-common/src/main/java/net/corda/cordform/CordformContext.java @@ -1,6 +1,5 @@ package net.corda.cordform; -import org.bouncycastle.asn1.x500.X500Name; import java.nio.file.Path; public interface CordformContext { diff --git a/gradle-plugins/cordform-common/src/main/java/net/corda/cordform/NetworkParametersGenerator.java b/gradle-plugins/cordform-common/src/main/java/net/corda/cordform/NetworkParametersGenerator.java new file mode 100644 index 0000000000..7a8869288f --- /dev/null +++ b/gradle-plugins/cordform-common/src/main/java/net/corda/cordform/NetworkParametersGenerator.java @@ -0,0 +1,16 @@ +package net.corda.cordform; + +import java.nio.file.Path; +import java.util.List; +import java.util.Map; + +public interface NetworkParametersGenerator { + /** + * Run generation of network parameters for [Cordformation]. Nodes need to have already their own [NodeInfo] files in their + * base directories, these files will be used to extract notary identities. + * + * @param nodesDirs - nodes directories that will be used for network parameters generation. Network parameters + * file will be dropped into each directory on this list. + */ + void run(List nodesDirs); +} \ No newline at end of file diff --git a/gradle-plugins/cordformation/src/main/kotlin/net/corda/plugins/Cordform.kt b/gradle-plugins/cordformation/src/main/kotlin/net/corda/plugins/Cordform.kt index 057b1861b5..604caa315b 100644 --- a/gradle-plugins/cordformation/src/main/kotlin/net/corda/plugins/Cordform.kt +++ b/gradle-plugins/cordformation/src/main/kotlin/net/corda/plugins/Cordform.kt @@ -3,15 +3,14 @@ package net.corda.plugins import groovy.lang.Closure import net.corda.cordform.CordformDefinition import net.corda.cordform.CordformNode +import net.corda.cordform.NetworkParametersGenerator import org.apache.tools.ant.filters.FixCrLfFilter import org.gradle.api.DefaultTask import org.gradle.api.GradleException import org.gradle.api.plugins.JavaPluginConvention import org.gradle.api.tasks.SourceSet.MAIN_SOURCE_SET_NAME import org.gradle.api.tasks.TaskAction -import java.io.File import java.net.URLClassLoader -import java.nio.file.Files import java.nio.file.Path import java.nio.file.Paths import java.util.concurrent.TimeUnit @@ -28,6 +27,7 @@ open class Cordform : DefaultTask() { */ @Suppress("MemberVisibilityCanPrivate") var definitionClass: String? = null + private val networkParametersGenClass: String = "net.corda.nodeapi.internal.TestNetworkParametersGenerator" private var directory = Paths.get("build", "nodes") private val nodes = mutableListOf() @@ -113,6 +113,19 @@ open class Cordform : DefaultTask() { .newInstance() } + /** + * The parametersGenerator needn't be compiled until just before our build method, so we load it manually via sourceSets.main.runtimeClasspath. + */ + private fun loadParametersGenerator(): NetworkParametersGenerator { + val plugin = project.convention.getPlugin(JavaPluginConvention::class.java) + val classpath = plugin.sourceSets.getByName(MAIN_SOURCE_SET_NAME).runtimeClasspath + val urls = classpath.files.map { it.toURI().toURL() }.toTypedArray() + return URLClassLoader(urls, NetworkParametersGenerator::class.java.classLoader) + .loadClass(networkParametersGenClass) + .asSubclass(NetworkParametersGenerator::class.java) + .newInstance() + } + /** * This task action will create and install the nodes based on the node configurations added. */ @@ -124,6 +137,7 @@ open class Cordform : DefaultTask() { installRunScript() nodes.forEach(Node::build) generateAndInstallNodeInfos() + generateAndInstallNetworkParameters() } private fun initializeConfiguration() { @@ -142,6 +156,12 @@ open class Cordform : DefaultTask() { } } + private fun generateAndInstallNetworkParameters() { + project.logger.info("Generating and installing network parameters") + val networkParamsGenerator = loadParametersGenerator() + networkParamsGenerator.run(nodes.map { it.fullPath() }) + } + private fun generateAndInstallNodeInfos() { generateNodeInfos() installNodeInfos() @@ -149,7 +169,7 @@ open class Cordform : DefaultTask() { private fun generateNodeInfos() { project.logger.info("Generating node infos") - var nodeProcesses = buildNodeProcesses() + val nodeProcesses = buildNodeProcesses() try { validateNodeProcessess(nodeProcesses) } finally { @@ -158,9 +178,10 @@ open class Cordform : DefaultTask() { } private fun buildNodeProcesses(): Map { - return nodes - .map { buildNodeProcess(it) } - .toMap() + val command = generateNodeInfoCommand() + return nodes.map { + it.makeLogDirectory() + buildProcess(it, command, "generate-info.log") }.toMap() } private fun validateNodeProcessess(nodeProcesses: Map) { @@ -175,14 +196,13 @@ open class Cordform : DefaultTask() { } } - private fun buildNodeProcess(node: Node): Pair { - node.makeLogDirectory() - var process = ProcessBuilder(generateNodeInfoCommand()) + private fun buildProcess(node: Node, command: List, logFile: String): Pair { + val process = ProcessBuilder(command) .directory(node.fullPath().toFile()) .redirectErrorStream(true) // InheritIO causes hangs on windows due the gradle buffer also not being flushed. // Must redirect to output or logger (node log is still written, this is just startup banner) - .redirectOutput(node.logFile().toFile()) + .redirectOutput(node.logFile(logFile).toFile()) .addEnvironment("CAPSULE_CACHE_DIR", Node.capsuleCacheDir) .start() return Pair(node, process) @@ -224,6 +244,6 @@ open class Cordform : DefaultTask() { } } } - private fun Node.logFile(): Path = this.logDirectory().resolve("generate-info.log") + private fun Node.logFile(name: String): Path = this.logDirectory().resolve(name) private fun ProcessBuilder.addEnvironment(key: String, value: String) = this.apply { environment().put(key, value) } } diff --git a/gradle-plugins/cordformation/src/main/kotlin/net/corda/plugins/Node.kt b/gradle-plugins/cordformation/src/main/kotlin/net/corda/plugins/Node.kt index 9358a293b5..bf0a08be14 100644 --- a/gradle-plugins/cordformation/src/main/kotlin/net/corda/plugins/Node.kt +++ b/gradle-plugins/cordformation/src/main/kotlin/net/corda/plugins/Node.kt @@ -2,9 +2,6 @@ package net.corda.plugins import com.typesafe.config.* import net.corda.cordform.CordformNode -import org.bouncycastle.asn1.x500.X500Name -import org.bouncycastle.asn1.x500.RDN -import org.bouncycastle.asn1.x500.style.BCStyle import org.gradle.api.Project import java.io.File import java.nio.charset.StandardCharsets @@ -122,18 +119,10 @@ class Node(private val project: Project) : CordformNode() { project.logger.error("Node has a null name - cannot create node") throw IllegalStateException("Node has a null name - cannot create node") } - - val dirName = try { - val o = X500Name(name).getRDNs(BCStyle.O) - if (o.size > 0) { - o.first().first.value.toString() - } else { - name - } - } catch(_ : IllegalArgumentException) { - // Can't parse as an X500 name, use the full string - name - } + // Parsing O= part directly because importing BouncyCastle provider in Cordformation causes problems + // with loading our custom X509EdDSAEngine. + val organizationName = name.trim().split(",").firstOrNull { it.startsWith("O=") }?.substringAfter("=") + val dirName = organizationName ?: name nodeDir = File(rootDir.toFile(), dirName) } @@ -151,7 +140,7 @@ class Node(private val project: Project) : CordformNode() { * Installs the corda fat JAR to the node directory. */ private fun installCordaJar() { - val cordaJar = verifyAndGetCordaJar() + val cordaJar = verifyAndGetRuntimeJar("corda") project.copy { it.apply { from(cordaJar) @@ -166,7 +155,7 @@ class Node(private val project: Project) : CordformNode() { * Installs the corda webserver JAR to the node directory */ private fun installWebserverJar() { - val webJar = verifyAndGetWebserverJar() + val webJar = verifyAndGetRuntimeJar("corda-webserver") project.copy { it.apply { from(webJar) @@ -250,34 +239,17 @@ class Node(private val project: Project) : CordformNode() { } /** - * Find the corda JAR amongst the dependencies. + * Find the given JAR amongst the dependencies + * @param jarName JAR name without the version part, for example for corda-2.0-SNAPSHOT.jar provide only "corda" as jarName * - * @return A file representing the Corda JAR. + * @return A file representing found JAR */ - private fun verifyAndGetCordaJar(): File { - val maybeCordaJAR = project.configuration("runtime").filter { - it.toString().contains("corda-$releaseVersion.jar") || it.toString().contains("corda-enterprise-$releaseVersion.jar") - } - if (maybeCordaJAR.isEmpty) { - throw RuntimeException("No Corda Capsule JAR found. Have you deployed the Corda project to Maven? Looked for \"corda-$releaseVersion.jar\"") - } else { - val cordaJar = maybeCordaJAR.singleFile - assert(cordaJar.isFile) - return cordaJar - } - } - - /** - * Find the corda JAR amongst the dependencies - * - * @return A file representing the Corda webserver JAR - */ - private fun verifyAndGetWebserverJar(): File { + private fun verifyAndGetRuntimeJar(jarName: String): File { val maybeJar = project.configuration("runtime").filter { - it.toString().contains("corda-webserver-$releaseVersion.jar") + "$jarName-$releaseVersion.jar" in it.toString() || "$jarName-enterprise-$releaseVersion.jar" in it.toString() } if (maybeJar.isEmpty) { - throw RuntimeException("No Corda Webserver JAR found. Have you deployed the Corda project to Maven? Looked for \"corda-webserver-$releaseVersion.jar\"") + throw IllegalStateException("No $jarName JAR found. Have you deployed the Corda project to Maven? Looked for \"$jarName-$releaseVersion.jar\"") } else { val jar = maybeJar.singleFile assert(jar.isFile) diff --git a/testing/test-common/src/main/kotlin/net/corda/testing/common/internal/NetworkParametersCopier.kt b/node-api/src/main/kotlin/net/corda/nodeapi/internal/NetworkParametersCopier.kt similarity index 96% rename from testing/test-common/src/main/kotlin/net/corda/testing/common/internal/NetworkParametersCopier.kt rename to node-api/src/main/kotlin/net/corda/nodeapi/internal/NetworkParametersCopier.kt index f8a8d26952..d464575a4f 100644 --- a/testing/test-common/src/main/kotlin/net/corda/testing/common/internal/NetworkParametersCopier.kt +++ b/node-api/src/main/kotlin/net/corda/nodeapi/internal/NetworkParametersCopier.kt @@ -1,4 +1,4 @@ -package net.corda.testing.common.internal +package net.corda.nodeapi.internal import net.corda.core.crypto.SignedData import net.corda.core.crypto.entropyToKeyPair diff --git a/node-api/src/main/kotlin/net/corda/nodeapi/internal/TestNetworkParametersGenerator.kt b/node-api/src/main/kotlin/net/corda/nodeapi/internal/TestNetworkParametersGenerator.kt new file mode 100644 index 0000000000..cfdf9b83a3 --- /dev/null +++ b/node-api/src/main/kotlin/net/corda/nodeapi/internal/TestNetworkParametersGenerator.kt @@ -0,0 +1,115 @@ +package net.corda.nodeapi.internal + +import com.typesafe.config.ConfigFactory +import net.corda.cordform.CordformNode +import net.corda.cordform.NetworkParametersGenerator +import net.corda.core.crypto.SignedData +import net.corda.core.identity.CordaX500Name +import net.corda.core.internal.div +import net.corda.core.internal.list +import net.corda.core.internal.readAll +import net.corda.core.node.NodeInfo +import net.corda.core.serialization.SerializationContext +import net.corda.core.serialization.deserialize +import net.corda.core.serialization.internal.SerializationEnvironmentImpl +import net.corda.core.serialization.internal._contextSerializationEnv +import net.corda.core.utilities.ByteSequence +import net.corda.core.utilities.contextLogger +import net.corda.core.utilities.days +import net.corda.nodeapi.internal.serialization.* +import net.corda.nodeapi.internal.serialization.amqp.AMQPServerSerializationScheme +import net.corda.nodeapi.internal.serialization.kryo.AbstractKryoSerializationScheme +import net.corda.nodeapi.internal.serialization.kryo.KryoHeaderV0_1 +import java.nio.file.Path +import java.time.Instant +import kotlin.streams.toList + +// This class is used by deployNodes task to generate NetworkParameters in [Cordformation]. +@Suppress("UNUSED") +class TestNetworkParametersGenerator : NetworkParametersGenerator { + companion object { + private val logger = contextLogger() + } + + override fun run(nodesDirs: List) { + logger.info("NetworkParameters generation using node directories: $nodesDirs") + try { + initialiseSerialization() + val notaryInfos = loadAndGatherNotaryIdentities(nodesDirs) + val copier = NetworkParametersCopier(NetworkParameters( + minimumPlatformVersion = 1, + notaries = notaryInfos, + modifiedTime = Instant.now(), + eventHorizon = 10000.days, + maxMessageSize = 40000, + maxTransactionSize = 40000, + epoch = 1 + )) + nodesDirs.forEach { copier.install(it) } + } finally { + _contextSerializationEnv.set(null) + } + } + + private fun loadAndGatherNotaryIdentities(nodesDirs: List): List { + val infos = getAllNodeInfos(nodesDirs) + val configs = nodesDirs.map { ConfigFactory.parseFile((it / "node.conf").toFile()) } + val notaryConfigs = configs.filter { it.hasPath("notary") } + val notaries = notaryConfigs.associateBy( + { CordaX500Name.parse(it.getString("myLegalName")) }, + { it.getConfig("notary").getBoolean("validating") } + ) + // Now get the notary identities based on names passed from configs. There is one problem, for distributed notaries + // in config we specify only node's main name, the notary identity isn't passed there. It's read from keystore on + // node startup, so we have to look it up from node info as a second identity, which is ugly. + return infos.mapNotNull { + info -> notaries[info.legalIdentities[0].name]?.let { NotaryInfo(info.notaryIdentity(), it) } + }.distinct() + } + + /** + * Loads latest NodeInfo files stored in node's base directory. + * Scans main directory and [CordformNode.NODE_INFO_DIRECTORY]. + * Signatures are checked before returning a value. The latest value stored for a given name is returned. + * + * @return list of latest [NodeInfo]s + */ + private fun getAllNodeInfos(nodesDirs: List): List { + val nodeInfoFiles = nodesDirs.map { dir -> dir.list { it.filter { "nodeInfo-" in it.toString() }.toList()[0] } } // We take the first one only + return nodeInfoFiles.mapNotNull { processFile(it) } + } + + private fun processFile(file: Path): NodeInfo? { + return try { + logger.info("Reading NodeInfo from file: $file") + val signedData = file.readAll().deserialize>() + signedData.verified() + } catch (e: Exception) { + logger.warn("Exception parsing NodeInfo from file. $file", e) + null + } + } + + private fun NodeInfo.notaryIdentity() = if (legalIdentities.size == 2) legalIdentities[1] else legalIdentities[0] + + // We need to to set serialization env, because generation of parameters is run from Cordform. + // KryoServerSerializationScheme is not accessible from nodeapi. + private fun initialiseSerialization() { + val context = if (java.lang.Boolean.getBoolean("net.corda.testing.amqp.enable")) AMQP_P2P_CONTEXT else KRYO_P2P_CONTEXT + _contextSerializationEnv.set(SerializationEnvironmentImpl( + SerializationFactoryImpl().apply { + registerScheme(KryoParametersSerializationScheme) + registerScheme(AMQPServerSerializationScheme()) + }, + context)) + } + + private object KryoParametersSerializationScheme : AbstractKryoSerializationScheme() { + override fun canDeserializeVersion(byteSequence: ByteSequence, target: SerializationContext.UseCase): Boolean { + return byteSequence == KryoHeaderV0_1 && target == SerializationContext.UseCase.P2P + } + + override fun rpcClientKryoPool(context: SerializationContext) = throw UnsupportedOperationException() + override fun rpcServerKryoPool(context: SerializationContext) = throw UnsupportedOperationException() + } +} diff --git a/node/src/integration-test/kotlin/net/corda/node/services/BFTNotaryServiceTests.kt b/node/src/integration-test/kotlin/net/corda/node/services/BFTNotaryServiceTests.kt index 441cb780e5..a09fb0048e 100644 --- a/node/src/integration-test/kotlin/net/corda/node/services/BFTNotaryServiceTests.kt +++ b/node/src/integration-test/kotlin/net/corda/node/services/BFTNotaryServiceTests.kt @@ -27,7 +27,7 @@ import net.corda.node.services.transactions.minCorrectReplicas import net.corda.node.utilities.ServiceIdentityGenerator import net.corda.nodeapi.internal.NotaryInfo import net.corda.testing.chooseIdentity -import net.corda.testing.common.internal.NetworkParametersCopier +import net.corda.nodeapi.internal.NetworkParametersCopier import net.corda.testing.common.internal.testNetworkParameters import net.corda.testing.contracts.DummyContract import net.corda.testing.dummyCommand diff --git a/node/src/main/kotlin/net/corda/node/internal/AbstractNode.kt b/node/src/main/kotlin/net/corda/node/internal/AbstractNode.kt index 2066b43a94..0cdfaaa4bd 100644 --- a/node/src/main/kotlin/net/corda/node/internal/AbstractNode.kt +++ b/node/src/main/kotlin/net/corda/node/internal/AbstractNode.kt @@ -63,6 +63,7 @@ import org.apache.activemq.artemis.utils.ReusableLatch import org.slf4j.Logger import rx.Observable import java.io.IOException +import java.io.NotSerializableException import java.lang.reflect.InvocationTargetException import java.security.KeyPair import java.security.KeyStoreException diff --git a/node/src/main/kotlin/net/corda/node/services/messaging/RPCMessagingClient.kt b/node/src/main/kotlin/net/corda/node/services/messaging/RPCMessagingClient.kt index c7033b6baf..aa9fea626c 100644 --- a/node/src/main/kotlin/net/corda/node/services/messaging/RPCMessagingClient.kt +++ b/node/src/main/kotlin/net/corda/node/services/messaging/RPCMessagingClient.kt @@ -27,6 +27,7 @@ class RPCMessagingClient(private val config: SSLConfiguration, serverAddress: Ne } fun stop() = synchronized(this) { + rpcServer?.close() artemis.stop() } } diff --git a/samples/irs-demo/cordapp/build.gradle b/samples/irs-demo/cordapp/build.gradle index 66e7aa6953..26f7839d04 100644 --- a/samples/irs-demo/cordapp/build.gradle +++ b/samples/irs-demo/cordapp/build.gradle @@ -38,7 +38,7 @@ dependencies { // Specify your cordapp's dependencies below, including dependent cordapps compile group: 'commons-io', name: 'commons-io', version: '2.5' - testCompile project(':node-driver') + cordaCompile project(':node-driver') testCompile "junit:junit:$junit_version" testCompile "org.assertj:assertj-core:${assertj_version}" } diff --git a/samples/irs-demo/src/integration-test/kotlin/net/corda/test/spring/SpringDriver.kt b/samples/irs-demo/src/integration-test/kotlin/net/corda/test/spring/SpringDriver.kt index 4de01a0d78..3a09c8200a 100644 --- a/samples/irs-demo/src/integration-test/kotlin/net/corda/test/spring/SpringDriver.kt +++ b/samples/irs-demo/src/integration-test/kotlin/net/corda/test/spring/SpringDriver.kt @@ -40,7 +40,7 @@ fun springDriver( useTestClock: Boolean = defaultParameters.useTestClock, initialiseSerialization: Boolean = defaultParameters.initialiseSerialization, startNodesInProcess: Boolean = defaultParameters.startNodesInProcess, - notarySpecs: List, + notarySpecs: List = defaultParameters.notarySpecs, extraCordappPackagesToScan: List = defaultParameters.extraCordappPackagesToScan, dsl: SpringDriverExposedDSLInterface.() -> A ) = genericDriver( diff --git a/testing/node-driver/src/main/kotlin/net/corda/testing/driver/Driver.kt b/testing/node-driver/src/main/kotlin/net/corda/testing/driver/Driver.kt index 3a61e18f2b..a8ea08015a 100644 --- a/testing/node-driver/src/main/kotlin/net/corda/testing/driver/Driver.kt +++ b/testing/node-driver/src/main/kotlin/net/corda/testing/driver/Driver.kt @@ -36,7 +36,7 @@ import net.corda.nodeapi.config.toConfig import net.corda.nodeapi.internal.NotaryInfo import net.corda.nodeapi.internal.addShutdownHook import net.corda.testing.* -import net.corda.testing.common.internal.NetworkParametersCopier +import net.corda.nodeapi.internal.NetworkParametersCopier import net.corda.testing.common.internal.testNetworkParameters import net.corda.testing.internal.ProcessUtilities import net.corda.testing.node.ClusterSpec diff --git a/testing/node-driver/src/main/kotlin/net/corda/testing/internal/NodeBasedTest.kt b/testing/node-driver/src/main/kotlin/net/corda/testing/internal/NodeBasedTest.kt index 8a107a0fe6..027232f1f5 100644 --- a/testing/node-driver/src/main/kotlin/net/corda/testing/internal/NodeBasedTest.kt +++ b/testing/node-driver/src/main/kotlin/net/corda/testing/internal/NodeBasedTest.kt @@ -16,7 +16,7 @@ import net.corda.node.services.config.parseAsNodeConfiguration import net.corda.node.services.config.plus import net.corda.nodeapi.User import net.corda.testing.SerializationEnvironmentRule -import net.corda.testing.common.internal.NetworkParametersCopier +import net.corda.nodeapi.internal.NetworkParametersCopier import net.corda.testing.common.internal.testNetworkParameters import net.corda.testing.driver.addressMustNotBeBoundFuture import net.corda.testing.getFreeLocalPorts diff --git a/testing/node-driver/src/main/kotlin/net/corda/testing/node/MockNode.kt b/testing/node-driver/src/main/kotlin/net/corda/testing/node/MockNode.kt index ed5806098f..766dd80269 100644 --- a/testing/node-driver/src/main/kotlin/net/corda/testing/node/MockNode.kt +++ b/testing/node-driver/src/main/kotlin/net/corda/testing/node/MockNode.kt @@ -40,7 +40,7 @@ import net.corda.node.utilities.CordaPersistence import net.corda.node.utilities.ServiceIdentityGenerator import net.corda.nodeapi.internal.NotaryInfo import net.corda.testing.DUMMY_NOTARY -import net.corda.testing.common.internal.NetworkParametersCopier +import net.corda.nodeapi.internal.NetworkParametersCopier import net.corda.testing.common.internal.testNetworkParameters import net.corda.testing.node.MockServices.Companion.MOCK_VERSION_INFO import net.corda.testing.node.MockServices.Companion.makeTestDataSourceProperties diff --git a/testing/smoke-test-utils/src/main/kotlin/net/corda/smoketesting/NodeProcess.kt b/testing/smoke-test-utils/src/main/kotlin/net/corda/smoketesting/NodeProcess.kt index 250161faaf..a35d80c2c6 100644 --- a/testing/smoke-test-utils/src/main/kotlin/net/corda/smoketesting/NodeProcess.kt +++ b/testing/smoke-test-utils/src/main/kotlin/net/corda/smoketesting/NodeProcess.kt @@ -8,7 +8,7 @@ import net.corda.core.internal.createDirectories import net.corda.core.internal.div import net.corda.core.utilities.NetworkHostAndPort import net.corda.core.utilities.contextLogger -import net.corda.testing.common.internal.NetworkParametersCopier +import net.corda.nodeapi.internal.NetworkParametersCopier import net.corda.testing.common.internal.testNetworkParameters import net.corda.testing.common.internal.asContextEnv import java.nio.file.Path From 2f0bc249ad0097ceb81eccd1db8a8678e515e195 Mon Sep 17 00:00:00 2001 From: Shams Asari Date: Wed, 29 Nov 2017 18:05:10 +0000 Subject: [PATCH 12/23] Merge fixes and getting tests to pass --- .../java/net/corda/cordform/CordformNode.java | 8 +- .../net/corda/bank/BankOfCordaCordform.kt | 3 +- .../net/corda/notarydemo/BFTNotaryCordform.kt | 1 + .../kotlin/net/corda/testing/driver/Driver.kt | 81 +++++++++++++++++-- .../testing/internal/demorun/DemoRunner.kt | 14 +--- 5 files changed, 85 insertions(+), 22 deletions(-) diff --git a/gradle-plugins/cordform-common/src/main/java/net/corda/cordform/CordformNode.java b/gradle-plugins/cordform-common/src/main/java/net/corda/cordform/CordformNode.java index 33b433a506..285260f6db 100644 --- a/gradle-plugins/cordform-common/src/main/java/net/corda/cordform/CordformNode.java +++ b/gradle-plugins/cordform-common/src/main/java/net/corda/cordform/CordformNode.java @@ -1,14 +1,16 @@ package net.corda.cordform; -import static java.util.Collections.emptyList; -import com.typesafe.config.*; +import com.typesafe.config.Config; +import com.typesafe.config.ConfigFactory; +import com.typesafe.config.ConfigValueFactory; import javax.annotation.Nonnull; import javax.annotation.Nullable; -import java.util.Collections; import java.util.List; import java.util.Map; +import static java.util.Collections.emptyList; + public class CordformNode implements NodeDefinition { /** * Path relative to the running node where the serialized NodeInfos are stored. diff --git a/samples/bank-of-corda-demo/src/main/kotlin/net/corda/bank/BankOfCordaCordform.kt b/samples/bank-of-corda-demo/src/main/kotlin/net/corda/bank/BankOfCordaCordform.kt index 24dda81869..a0afe2ad4b 100644 --- a/samples/bank-of-corda-demo/src/main/kotlin/net/corda/bank/BankOfCordaCordform.kt +++ b/samples/bank-of-corda-demo/src/main/kotlin/net/corda/bank/BankOfCordaCordform.kt @@ -12,7 +12,6 @@ import net.corda.core.transactions.SignedTransaction import net.corda.core.utilities.NetworkHostAndPort import net.corda.node.services.Permissions.Companion.all import net.corda.node.services.config.NotaryConfig -import net.corda.node.services.transactions.ValidatingNotaryService import net.corda.nodeapi.User import net.corda.testing.BOC import net.corda.testing.internal.demorun.* @@ -102,7 +101,7 @@ object IssueCash { } private fun createParams(amount: Amount, notaryName: CordaX500Name): IssueRequestParams { - return IssueRequestParams(amount, BIGCORP_NAME, "1", BOC.name, notaryName.copy(commonName = ValidatingNotaryService.id)) + return IssueRequestParams(amount, BIGCORP_NAME, "1", BOC.name, notaryName) } private fun printHelp(parser: OptionParser) { diff --git a/samples/notary-demo/src/main/kotlin/net/corda/notarydemo/BFTNotaryCordform.kt b/samples/notary-demo/src/main/kotlin/net/corda/notarydemo/BFTNotaryCordform.kt index da3dbeed7e..82f43f4668 100644 --- a/samples/notary-demo/src/main/kotlin/net/corda/notarydemo/BFTNotaryCordform.kt +++ b/samples/notary-demo/src/main/kotlin/net/corda/notarydemo/BFTNotaryCordform.kt @@ -4,6 +4,7 @@ import net.corda.cordform.CordformContext import net.corda.cordform.CordformDefinition import net.corda.cordform.CordformNode import net.corda.core.identity.CordaX500Name +import net.corda.core.node.services.NotaryService import net.corda.core.utilities.NetworkHostAndPort import net.corda.node.services.config.BFTSMaRtConfiguration import net.corda.node.services.config.NotaryConfig diff --git a/testing/node-driver/src/main/kotlin/net/corda/testing/driver/Driver.kt b/testing/node-driver/src/main/kotlin/net/corda/testing/driver/Driver.kt index d23bd9db41..aeec6ef815 100644 --- a/testing/node-driver/src/main/kotlin/net/corda/testing/driver/Driver.kt +++ b/testing/node-driver/src/main/kotlin/net/corda/testing/driver/Driver.kt @@ -2,8 +2,10 @@ package net.corda.testing.driver +import com.google.common.collect.HashMultimap import com.google.common.util.concurrent.ThreadFactoryBuilder import com.typesafe.config.Config +import com.typesafe.config.ConfigFactory import com.typesafe.config.ConfigRenderOptions import net.corda.client.rpc.CordaRPCClient import net.corda.cordform.CordformContext @@ -27,17 +29,22 @@ import net.corda.node.internal.StartedNode import net.corda.node.internal.cordapp.CordappLoader import net.corda.node.services.Permissions.Companion.invokeRpc import net.corda.node.services.config.* +import net.corda.node.services.transactions.BFTNonValidatingNotaryService +import net.corda.node.services.transactions.RaftNonValidatingNotaryService +import net.corda.node.services.transactions.RaftValidatingNotaryService import net.corda.node.utilities.ServiceIdentityGenerator import net.corda.node.utilities.registration.HTTPNetworkRegistrationService import net.corda.node.utilities.registration.NetworkRegistrationHelper import net.corda.nodeapi.NodeInfoFilesCopier import net.corda.nodeapi.User +import net.corda.nodeapi.config.parseAs import net.corda.nodeapi.config.toConfig import net.corda.nodeapi.internal.NotaryInfo import net.corda.nodeapi.internal.addShutdownHook import net.corda.testing.* import net.corda.testing.common.internal.NetworkParametersCopier import net.corda.testing.common.internal.testNetworkParameters +import net.corda.testing.driver.DriverDSL.ClusterType.* import net.corda.testing.internal.ProcessUtilities import net.corda.testing.node.ClusterSpec import net.corda.testing.node.MockServices.Companion.MOCK_VERSION_INFO @@ -62,6 +69,7 @@ import java.util.concurrent.ScheduledExecutorService import java.util.concurrent.TimeUnit.MILLISECONDS import java.util.concurrent.TimeUnit.SECONDS import java.util.concurrent.atomic.AtomicInteger +import kotlin.collections.ArrayList import kotlin.concurrent.thread /** @@ -607,7 +615,7 @@ class DriverDSL( private val countObservables = mutableMapOf>() private lateinit var _notaries: List override val notaryHandles: List get() = _notaries - private lateinit var networkParameters: NetworkParametersCopier + private var networkParameters: NetworkParametersCopier? = null class State { val processes = ArrayList() @@ -724,7 +732,67 @@ class DriverDSL( } } - internal fun startCordformNode(cordform: CordformNode): CordaFuture { + private enum class ClusterType(val validating: Boolean, val clusterName: CordaX500Name) { + VALIDATING_RAFT(true, CordaX500Name(RaftValidatingNotaryService.id, "Raft", "Zurich", "CH")), + NON_VALIDATING_RAFT(false, CordaX500Name(RaftNonValidatingNotaryService.id, "Raft", "Zurich", "CH")), + NON_VALIDATING_BFT(false, CordaX500Name(BFTNonValidatingNotaryService.id, "BFT", "Zurich", "CH")) + } + + internal fun startCordformNodes(cordforms: List): CordaFuture<*> { + val clusterNodes = HashMultimap.create() + val notaryInfos = ArrayList() + + // Go though the node definitions and pick out the notaries so that we can generate their identities to be used + // in the network parameters + for (cordform in cordforms) { + if (cordform.notary == null) continue + val name = CordaX500Name.parse(cordform.name) + val notaryConfig = ConfigFactory.parseMap(cordform.notary).parseAs() + // We need to first group the nodes that form part of a cluser. We assume for simplicity that nodes of the + // same cluster type and validating flag are part of the same cluster. + if (notaryConfig.raft != null) { + val key = if (notaryConfig.validating) VALIDATING_RAFT else NON_VALIDATING_RAFT + clusterNodes.put(key, name) + } else if (notaryConfig.bftSMaRt != null) { + clusterNodes.put(NON_VALIDATING_BFT, name) + } else { + // We have all we need here to generate the identity for single node notaries + val identity = ServiceIdentityGenerator.generateToDisk( + dirs = listOf(baseDirectory(name)), + serviceName = name, + serviceId = "identity" + ) + notaryInfos += NotaryInfo(identity, notaryConfig.validating) + } + } + + clusterNodes.asMap().forEach { type, nodeNames -> + val identity = ServiceIdentityGenerator.generateToDisk( + dirs = nodeNames.map { baseDirectory(it) }, + serviceName = type.clusterName, + serviceId = NotaryService.constructId( + validating = type.validating, + raft = type in setOf(VALIDATING_RAFT, NON_VALIDATING_RAFT), + bft = type == NON_VALIDATING_BFT + ) + ) + notaryInfos += NotaryInfo(identity, type.validating) + } + + networkParameters = NetworkParametersCopier(testNetworkParameters(notaryInfos)) + + return cordforms.map { + val startedNode = startCordformNode(it) + if (it.webAddress != null) { + // Start a webserver if an address for it was specified + startedNode.flatMap { startWebserver(it) } + } else { + startedNode + } + }.transpose() + } + + private fun startCordformNode(cordform: CordformNode): CordaFuture { val name = CordaX500Name.parse(cordform.name) // TODO We shouldn't have to allocate an RPC or web address if they're not specified. We're having to do this because of startNodeInternal val rpcAddress = if (cordform.rpcAddress == null) mapOf("rpcAddress" to portAllocation.nextHostAndPort().toString()) else emptyMap() @@ -789,14 +857,17 @@ class DriverDSL( ServiceIdentityGenerator.generateToDisk( dirs = listOf(baseDirectory(spec.name)), serviceName = spec.name, - serviceId = "identity") + serviceId = "identity" + ) } else { ServiceIdentityGenerator.generateToDisk( dirs = generateNodeNames(spec).map { baseDirectory(it) }, serviceName = spec.name, serviceId = NotaryService.constructId( validating = spec.validating, - raft = spec.cluster is ClusterSpec.Raft)) + raft = spec.cluster is ClusterSpec.Raft + ) + ) } NotaryInfo(identity, spec.validating) } @@ -945,7 +1016,7 @@ class DriverDSL( val nodeInfoFilesCopier = if (compatibilityZoneURL == null) nodeInfoFilesCopier else null nodeInfoFilesCopier?.addConfig(baseDirectory) - networkParameters.install(baseDirectory) + networkParameters!!.install(baseDirectory) val onNodeExit: () -> Unit = { nodeInfoFilesCopier?.removeConfig(baseDirectory) countObservables.remove(configuration.myLegalName) diff --git a/testing/node-driver/src/main/kotlin/net/corda/testing/internal/demorun/DemoRunner.kt b/testing/node-driver/src/main/kotlin/net/corda/testing/internal/demorun/DemoRunner.kt index c5e25b6d91..2d431799be 100644 --- a/testing/node-driver/src/main/kotlin/net/corda/testing/internal/demorun/DemoRunner.kt +++ b/testing/node-driver/src/main/kotlin/net/corda/testing/internal/demorun/DemoRunner.kt @@ -4,8 +4,6 @@ package net.corda.testing.internal.demorun import net.corda.cordform.CordformDefinition import net.corda.cordform.CordformNode -import net.corda.core.internal.concurrent.flatMap -import net.corda.core.internal.concurrent.transpose import net.corda.core.utilities.NetworkHostAndPort import net.corda.core.utilities.getOrThrow import net.corda.testing.driver.DriverDSL @@ -35,6 +33,7 @@ fun CordformDefinition.deployNodesThen(block: () -> Unit) { } private fun CordformDefinition.runNodes(waitForAllNodesToFinish: Boolean, block: () -> Unit) { + clean() val nodes = nodeConfigurers.map { configurer -> CordformNode().also { configurer.accept(it) } } val maxPort = nodes .flatMap { listOf(it.p2pAddress, it.rpcAddress, it.webAddress) } @@ -51,16 +50,7 @@ private fun CordformDefinition.runNodes(waitForAllNodesToFinish: Boolean, block: waitForAllNodesToFinish = waitForAllNodesToFinish ) { setup(this) - this as DriverDSL // startCordformNode is an internal API - nodes.map { - val startedNode = startCordformNode(it) - if (it.webAddress != null) { - // Start a webserver if an address for it was specified - startedNode.flatMap { startWebserver(it) } - } else { - startedNode - } - }.transpose().getOrThrow() // Only proceed once everything is up and running + (this as DriverDSL).startCordformNodes(nodes).getOrThrow() // Only proceed once everything is up and running println("All nodes and webservers are ready...") block() } From c2731c6797bdb8a9a1c7c31f35b1ae20a4f870b4 Mon Sep 17 00:00:00 2001 From: Shams Asari Date: Thu, 30 Nov 2017 12:15:32 +0000 Subject: [PATCH 13/23] More merge fixes and moved NodesInfoFilesCopier into internal package --- constants.properties | 2 +- .../java/net/corda/cordform/NetworkParametersGenerator.java | 1 - .../src/main/kotlin/net/corda/plugins/Cordform.kt | 1 + .../cordformation/src/main/kotlin/net/corda/plugins/Node.kt | 6 +++--- .../net/corda/nodeapi/{ => internal}/NodeInfoFilesCopier.kt | 2 +- .../nodeapi/internal/TestNetworkParametersGenerator.kt | 4 +++- .../kotlin/net/corda/nodeapi/NodeInfoFilesCopierTest.kt | 1 + .../net/corda/node/services/network/NodeInfoWatcherTest.kt | 2 +- .../net/corda/node/services/network/NodeInfoWatcher.kt | 2 +- .../src/main/kotlin/net/corda/testing/driver/Driver.kt | 2 +- .../corda/demobench/model/DemoBenchNodeInfoFilesCopier.kt | 2 +- 11 files changed, 14 insertions(+), 11 deletions(-) rename node-api/src/main/kotlin/net/corda/nodeapi/{ => internal}/NodeInfoFilesCopier.kt (99%) diff --git a/constants.properties b/constants.properties index 8e2f394173..3b0e9df855 100644 --- a/constants.properties +++ b/constants.properties @@ -1,4 +1,4 @@ -gradlePluginsVersion=3.0.0-NETWORKMAP +gradlePluginsVersion=3.0.1-NETWORKMAP kotlinVersion=1.1.60 platformVersion=2 guavaVersion=21.0 diff --git a/gradle-plugins/cordform-common/src/main/java/net/corda/cordform/NetworkParametersGenerator.java b/gradle-plugins/cordform-common/src/main/java/net/corda/cordform/NetworkParametersGenerator.java index 7a8869288f..702e154ed3 100644 --- a/gradle-plugins/cordform-common/src/main/java/net/corda/cordform/NetworkParametersGenerator.java +++ b/gradle-plugins/cordform-common/src/main/java/net/corda/cordform/NetworkParametersGenerator.java @@ -2,7 +2,6 @@ package net.corda.cordform; import java.nio.file.Path; import java.util.List; -import java.util.Map; public interface NetworkParametersGenerator { /** diff --git a/gradle-plugins/cordformation/src/main/kotlin/net/corda/plugins/Cordform.kt b/gradle-plugins/cordformation/src/main/kotlin/net/corda/plugins/Cordform.kt index ca8c4b72cd..3058b052c9 100644 --- a/gradle-plugins/cordformation/src/main/kotlin/net/corda/plugins/Cordform.kt +++ b/gradle-plugins/cordformation/src/main/kotlin/net/corda/plugins/Cordform.kt @@ -10,6 +10,7 @@ import org.gradle.api.GradleException import org.gradle.api.plugins.JavaPluginConvention import org.gradle.api.tasks.SourceSet.MAIN_SOURCE_SET_NAME import org.gradle.api.tasks.TaskAction +import java.io.File import java.net.URLClassLoader import java.nio.file.Path import java.nio.file.Paths diff --git a/gradle-plugins/cordformation/src/main/kotlin/net/corda/plugins/Node.kt b/gradle-plugins/cordformation/src/main/kotlin/net/corda/plugins/Node.kt index 4370887026..70af0e26c5 100644 --- a/gradle-plugins/cordformation/src/main/kotlin/net/corda/plugins/Node.kt +++ b/gradle-plugins/cordformation/src/main/kotlin/net/corda/plugins/Node.kt @@ -1,9 +1,9 @@ package net.corda.plugins -import com.typesafe.config.* +import com.typesafe.config.ConfigFactory +import com.typesafe.config.ConfigRenderOptions +import com.typesafe.config.ConfigValueFactory import net.corda.cordform.CordformNode -import org.bouncycastle.asn1.x500.X500Name -import org.bouncycastle.asn1.x500.style.BCStyle import org.gradle.api.Project import java.io.File import java.nio.charset.StandardCharsets diff --git a/node-api/src/main/kotlin/net/corda/nodeapi/NodeInfoFilesCopier.kt b/node-api/src/main/kotlin/net/corda/nodeapi/internal/NodeInfoFilesCopier.kt similarity index 99% rename from node-api/src/main/kotlin/net/corda/nodeapi/NodeInfoFilesCopier.kt rename to node-api/src/main/kotlin/net/corda/nodeapi/internal/NodeInfoFilesCopier.kt index 7017e89a56..00786c8da1 100644 --- a/node-api/src/main/kotlin/net/corda/nodeapi/NodeInfoFilesCopier.kt +++ b/node-api/src/main/kotlin/net/corda/nodeapi/internal/NodeInfoFilesCopier.kt @@ -1,4 +1,4 @@ -package net.corda.nodeapi +package net.corda.nodeapi.internal import net.corda.cordform.CordformNode import net.corda.core.internal.ThreadBox diff --git a/node-api/src/main/kotlin/net/corda/nodeapi/internal/TestNetworkParametersGenerator.kt b/node-api/src/main/kotlin/net/corda/nodeapi/internal/TestNetworkParametersGenerator.kt index cfdf9b83a3..fe54652c14 100644 --- a/node-api/src/main/kotlin/net/corda/nodeapi/internal/TestNetworkParametersGenerator.kt +++ b/node-api/src/main/kotlin/net/corda/nodeapi/internal/TestNetworkParametersGenerator.kt @@ -75,7 +75,9 @@ class TestNetworkParametersGenerator : NetworkParametersGenerator { * @return list of latest [NodeInfo]s */ private fun getAllNodeInfos(nodesDirs: List): List { - val nodeInfoFiles = nodesDirs.map { dir -> dir.list { it.filter { "nodeInfo-" in it.toString() }.toList()[0] } } // We take the first one only + val nodeInfoFiles = nodesDirs.map { dir -> + dir.list { it.filter { "nodeInfo-" in it.toString() }.findFirst().get() } + } return nodeInfoFiles.mapNotNull { processFile(it) } } diff --git a/node-api/src/test/kotlin/net/corda/nodeapi/NodeInfoFilesCopierTest.kt b/node-api/src/test/kotlin/net/corda/nodeapi/NodeInfoFilesCopierTest.kt index 562717e641..c14f2c854d 100644 --- a/node-api/src/test/kotlin/net/corda/nodeapi/NodeInfoFilesCopierTest.kt +++ b/node-api/src/test/kotlin/net/corda/nodeapi/NodeInfoFilesCopierTest.kt @@ -1,6 +1,7 @@ package net.corda.nodeapi import net.corda.cordform.CordformNode +import net.corda.nodeapi.internal.NodeInfoFilesCopier import net.corda.testing.eventually import org.junit.Before import org.junit.Rule diff --git a/node/src/integration-test/kotlin/net/corda/node/services/network/NodeInfoWatcherTest.kt b/node/src/integration-test/kotlin/net/corda/node/services/network/NodeInfoWatcherTest.kt index 1a0be3d57f..5d6731c625 100644 --- a/node/src/integration-test/kotlin/net/corda/node/services/network/NodeInfoWatcherTest.kt +++ b/node/src/integration-test/kotlin/net/corda/node/services/network/NodeInfoWatcherTest.kt @@ -10,7 +10,7 @@ import net.corda.core.node.NodeInfo import net.corda.core.node.services.KeyManagementService import net.corda.core.serialization.serialize import net.corda.node.services.identity.InMemoryIdentityService -import net.corda.nodeapi.NodeInfoFilesCopier +import net.corda.nodeapi.internal.NodeInfoFilesCopier import net.corda.testing.* import net.corda.testing.node.MockKeyManagementService import org.assertj.core.api.Assertions.assertThat diff --git a/node/src/main/kotlin/net/corda/node/services/network/NodeInfoWatcher.kt b/node/src/main/kotlin/net/corda/node/services/network/NodeInfoWatcher.kt index 76fcf6c472..c921044091 100644 --- a/node/src/main/kotlin/net/corda/node/services/network/NodeInfoWatcher.kt +++ b/node/src/main/kotlin/net/corda/node/services/network/NodeInfoWatcher.kt @@ -9,7 +9,7 @@ import net.corda.core.serialization.deserialize import net.corda.core.serialization.serialize import net.corda.core.utilities.contextLogger import net.corda.core.utilities.seconds -import net.corda.nodeapi.NodeInfoFilesCopier +import net.corda.nodeapi.internal.NodeInfoFilesCopier import rx.Observable import rx.Scheduler import rx.schedulers.Schedulers diff --git a/testing/node-driver/src/main/kotlin/net/corda/testing/driver/Driver.kt b/testing/node-driver/src/main/kotlin/net/corda/testing/driver/Driver.kt index 882eb2ffc1..f0c991ea54 100644 --- a/testing/node-driver/src/main/kotlin/net/corda/testing/driver/Driver.kt +++ b/testing/node-driver/src/main/kotlin/net/corda/testing/driver/Driver.kt @@ -35,7 +35,7 @@ import net.corda.node.services.transactions.RaftValidatingNotaryService import net.corda.node.utilities.ServiceIdentityGenerator import net.corda.node.utilities.registration.HTTPNetworkRegistrationService import net.corda.node.utilities.registration.NetworkRegistrationHelper -import net.corda.nodeapi.NodeInfoFilesCopier +import net.corda.nodeapi.internal.NodeInfoFilesCopier import net.corda.nodeapi.User import net.corda.nodeapi.config.parseAs import net.corda.nodeapi.config.toConfig diff --git a/tools/demobench/src/main/kotlin/net/corda/demobench/model/DemoBenchNodeInfoFilesCopier.kt b/tools/demobench/src/main/kotlin/net/corda/demobench/model/DemoBenchNodeInfoFilesCopier.kt index a0818c3532..3b856ef5ac 100644 --- a/tools/demobench/src/main/kotlin/net/corda/demobench/model/DemoBenchNodeInfoFilesCopier.kt +++ b/tools/demobench/src/main/kotlin/net/corda/demobench/model/DemoBenchNodeInfoFilesCopier.kt @@ -1,6 +1,6 @@ package net.corda.demobench.model -import net.corda.nodeapi.NodeInfoFilesCopier +import net.corda.nodeapi.internal.NodeInfoFilesCopier import rx.Scheduler import rx.schedulers.Schedulers import tornadofx.* From 6958cbbc445e4277095baf72c920d8577e3d2a60 Mon Sep 17 00:00:00 2001 From: Katarzyna Streich Date: Fri, 1 Dec 2017 16:14:03 +0000 Subject: [PATCH 14/23] Fix demobench as part of network parameters work (#2148) * Fix demobench - network parameters Demobench uses ServiceIdentityGenerator to pregenerate notary identity for network parameters. --- .../internal}/ServiceIdentityGenerator.kt | 4 +- .../certificates/cordadevcakeys.jks | Bin .../certificates/cordatruststore.jks | Bin .../node/services/BFTNotaryServiceTests.kt | 2 +- .../messaging/MQSecurityAsNodeTest.kt | 4 +- .../node/services/config/ConfigUtilities.kt | 4 +- .../net/corda/notarydemo/BFTNotaryCordform.kt | 2 +- .../corda/notarydemo/RaftNotaryCordform.kt | 2 +- .../kotlin/net/corda/testing/driver/Driver.kt | 2 +- .../kotlin/net/corda/testing/node/MockNode.kt | 2 +- .../kotlin/net/corda/testing/TestConstants.kt | 6 +-- .../kotlin/net/corda/demobench/DemoBench.kt | 17 ++++++++ .../corda/demobench/model/NodeController.kt | 41 +++++++++++++++++- 13 files changed, 71 insertions(+), 15 deletions(-) rename {node/src/main/kotlin/net/corda/node/utilities => node-api/src/main/kotlin/net/corda/nodeapi/internal}/ServiceIdentityGenerator.kt (95%) rename {node/src/main/resources/net/corda/node/internal => node-api/src/main/resources}/certificates/cordadevcakeys.jks (100%) rename {node/src/main/resources/net/corda/node/internal => node-api/src/main/resources}/certificates/cordatruststore.jks (100%) diff --git a/node/src/main/kotlin/net/corda/node/utilities/ServiceIdentityGenerator.kt b/node-api/src/main/kotlin/net/corda/nodeapi/internal/ServiceIdentityGenerator.kt similarity index 95% rename from node/src/main/kotlin/net/corda/node/utilities/ServiceIdentityGenerator.kt rename to node-api/src/main/kotlin/net/corda/nodeapi/internal/ServiceIdentityGenerator.kt index 34d2851709..21cba120a2 100644 --- a/node/src/main/kotlin/net/corda/node/utilities/ServiceIdentityGenerator.kt +++ b/node-api/src/main/kotlin/net/corda/nodeapi/internal/ServiceIdentityGenerator.kt @@ -1,4 +1,4 @@ -package net.corda.node.utilities +package net.corda.nodeapi.internal import net.corda.core.crypto.CompositeKey import net.corda.core.crypto.generateKeyPair @@ -31,7 +31,7 @@ object ServiceIdentityGenerator { val keyPairs = (1..dirs.size).map { generateKeyPair() } val notaryKey = CompositeKey.Builder().addKeys(keyPairs.map { it.public }).build(threshold) - val caKeyStore = loadKeyStore(javaClass.classLoader.getResourceAsStream("net/corda/node/internal/certificates/cordadevcakeys.jks"), "cordacadevpass") + val caKeyStore = loadKeyStore(javaClass.classLoader.getResourceAsStream("certificates/cordadevcakeys.jks"), "cordacadevpass") val issuer = caKeyStore.getCertificateAndKeyPair(X509Utilities.CORDA_INTERMEDIATE_CA, "cordacadevkeypass") val rootCert = caKeyStore.getCertificate(X509Utilities.CORDA_ROOT_CA) diff --git a/node/src/main/resources/net/corda/node/internal/certificates/cordadevcakeys.jks b/node-api/src/main/resources/certificates/cordadevcakeys.jks similarity index 100% rename from node/src/main/resources/net/corda/node/internal/certificates/cordadevcakeys.jks rename to node-api/src/main/resources/certificates/cordadevcakeys.jks diff --git a/node/src/main/resources/net/corda/node/internal/certificates/cordatruststore.jks b/node-api/src/main/resources/certificates/cordatruststore.jks similarity index 100% rename from node/src/main/resources/net/corda/node/internal/certificates/cordatruststore.jks rename to node-api/src/main/resources/certificates/cordatruststore.jks diff --git a/node/src/integration-test/kotlin/net/corda/node/services/BFTNotaryServiceTests.kt b/node/src/integration-test/kotlin/net/corda/node/services/BFTNotaryServiceTests.kt index a09fb0048e..2c21ecbb26 100644 --- a/node/src/integration-test/kotlin/net/corda/node/services/BFTNotaryServiceTests.kt +++ b/node/src/integration-test/kotlin/net/corda/node/services/BFTNotaryServiceTests.kt @@ -24,7 +24,7 @@ import net.corda.node.services.config.BFTSMaRtConfiguration import net.corda.node.services.config.NotaryConfig import net.corda.node.services.transactions.minClusterSize import net.corda.node.services.transactions.minCorrectReplicas -import net.corda.node.utilities.ServiceIdentityGenerator +import net.corda.nodeapi.internal.ServiceIdentityGenerator import net.corda.nodeapi.internal.NotaryInfo import net.corda.testing.chooseIdentity import net.corda.nodeapi.internal.NetworkParametersCopier diff --git a/node/src/integration-test/kotlin/net/corda/services/messaging/MQSecurityAsNodeTest.kt b/node/src/integration-test/kotlin/net/corda/services/messaging/MQSecurityAsNodeTest.kt index bd579f4f17..ddee610773 100644 --- a/node/src/integration-test/kotlin/net/corda/services/messaging/MQSecurityAsNodeTest.kt +++ b/node/src/integration-test/kotlin/net/corda/services/messaging/MQSecurityAsNodeTest.kt @@ -89,11 +89,11 @@ class MQSecurityAsNodeTest : MQSecurityTest() { val legalName = MEGA_CORP.name certificatesDirectory.createDirectories() if (!trustStoreFile.exists()) { - javaClass.classLoader.getResourceAsStream("net/corda/node/internal/certificates/cordatruststore.jks").copyTo(trustStoreFile) + javaClass.classLoader.getResourceAsStream("certificates/cordatruststore.jks").copyTo(trustStoreFile) } val caKeyStore = loadKeyStore( - javaClass.classLoader.getResourceAsStream("net/corda/node/internal/certificates/cordadevcakeys.jks"), + javaClass.classLoader.getResourceAsStream("certificates/cordadevcakeys.jks"), "cordacadevpass") val rootCACert = caKeyStore.getX509Certificate(X509Utilities.CORDA_ROOT_CA).toX509CertHolder() diff --git a/node/src/main/kotlin/net/corda/node/services/config/ConfigUtilities.kt b/node/src/main/kotlin/net/corda/node/services/config/ConfigUtilities.kt index 17c927f3b6..5ec42af68a 100644 --- a/node/src/main/kotlin/net/corda/node/services/config/ConfigUtilities.kt +++ b/node/src/main/kotlin/net/corda/node/services/config/ConfigUtilities.kt @@ -50,10 +50,10 @@ fun NodeConfiguration.configureWithDevSSLCertificate() = configureDevKeyAndTrust fun SSLConfiguration.configureDevKeyAndTrustStores(myLegalName: CordaX500Name) { certificatesDirectory.createDirectories() if (!trustStoreFile.exists()) { - javaClass.classLoader.getResourceAsStream("net/corda/node/internal/certificates/cordatruststore.jks").copyTo(trustStoreFile) + javaClass.classLoader.getResourceAsStream("certificates/cordatruststore.jks").copyTo(trustStoreFile) } if (!sslKeystore.exists() || !nodeKeystore.exists()) { - val caKeyStore = loadKeyStore(javaClass.classLoader.getResourceAsStream("net/corda/node/internal/certificates/cordadevcakeys.jks"), "cordacadevpass") + val caKeyStore = loadKeyStore(javaClass.classLoader.getResourceAsStream("certificates/cordadevcakeys.jks"), "cordacadevpass") createKeystoreForCordaNode(sslKeystore, nodeKeystore, keyStorePassword, keyStorePassword, caKeyStore, "cordacadevkeypass", myLegalName) // Move distributed service composite key (generated by ServiceIdentityGenerator.generateToDisk) to keystore if exists. diff --git a/samples/notary-demo/src/main/kotlin/net/corda/notarydemo/BFTNotaryCordform.kt b/samples/notary-demo/src/main/kotlin/net/corda/notarydemo/BFTNotaryCordform.kt index 82f43f4668..6735271686 100644 --- a/samples/notary-demo/src/main/kotlin/net/corda/notarydemo/BFTNotaryCordform.kt +++ b/samples/notary-demo/src/main/kotlin/net/corda/notarydemo/BFTNotaryCordform.kt @@ -10,7 +10,7 @@ import net.corda.node.services.config.BFTSMaRtConfiguration import net.corda.node.services.config.NotaryConfig import net.corda.node.services.transactions.BFTNonValidatingNotaryService import net.corda.node.services.transactions.minCorrectReplicas -import net.corda.node.utilities.ServiceIdentityGenerator +import net.corda.nodeapi.internal.ServiceIdentityGenerator import net.corda.testing.ALICE import net.corda.testing.BOB import net.corda.testing.internal.demorun.* diff --git a/samples/notary-demo/src/main/kotlin/net/corda/notarydemo/RaftNotaryCordform.kt b/samples/notary-demo/src/main/kotlin/net/corda/notarydemo/RaftNotaryCordform.kt index 45d93ef041..4ba0b53210 100644 --- a/samples/notary-demo/src/main/kotlin/net/corda/notarydemo/RaftNotaryCordform.kt +++ b/samples/notary-demo/src/main/kotlin/net/corda/notarydemo/RaftNotaryCordform.kt @@ -10,7 +10,7 @@ import net.corda.core.utilities.NetworkHostAndPort import net.corda.node.services.config.NotaryConfig import net.corda.node.services.config.RaftConfig import net.corda.node.services.transactions.RaftValidatingNotaryService -import net.corda.node.utilities.ServiceIdentityGenerator +import net.corda.nodeapi.internal.ServiceIdentityGenerator import net.corda.testing.ALICE import net.corda.testing.BOB import net.corda.testing.internal.demorun.* diff --git a/testing/node-driver/src/main/kotlin/net/corda/testing/driver/Driver.kt b/testing/node-driver/src/main/kotlin/net/corda/testing/driver/Driver.kt index f0c991ea54..d05f0f05b8 100644 --- a/testing/node-driver/src/main/kotlin/net/corda/testing/driver/Driver.kt +++ b/testing/node-driver/src/main/kotlin/net/corda/testing/driver/Driver.kt @@ -32,7 +32,7 @@ import net.corda.node.services.config.* import net.corda.node.services.transactions.BFTNonValidatingNotaryService import net.corda.node.services.transactions.RaftNonValidatingNotaryService import net.corda.node.services.transactions.RaftValidatingNotaryService -import net.corda.node.utilities.ServiceIdentityGenerator +import net.corda.nodeapi.internal.ServiceIdentityGenerator import net.corda.node.utilities.registration.HTTPNetworkRegistrationService import net.corda.node.utilities.registration.NetworkRegistrationHelper import net.corda.nodeapi.internal.NodeInfoFilesCopier diff --git a/testing/node-driver/src/main/kotlin/net/corda/testing/node/MockNode.kt b/testing/node-driver/src/main/kotlin/net/corda/testing/node/MockNode.kt index be0381e4f0..420c66ca77 100644 --- a/testing/node-driver/src/main/kotlin/net/corda/testing/node/MockNode.kt +++ b/testing/node-driver/src/main/kotlin/net/corda/testing/node/MockNode.kt @@ -37,7 +37,7 @@ import net.corda.node.services.transactions.InMemoryTransactionVerifierService import net.corda.node.utilities.AffinityExecutor import net.corda.node.utilities.AffinityExecutor.ServiceAffinityExecutor import net.corda.nodeapi.internal.persistence.CordaPersistence -import net.corda.node.utilities.ServiceIdentityGenerator +import net.corda.nodeapi.internal.ServiceIdentityGenerator import net.corda.nodeapi.internal.NotaryInfo import net.corda.testing.DUMMY_NOTARY import net.corda.nodeapi.internal.NetworkParametersCopier diff --git a/testing/test-utils/src/main/kotlin/net/corda/testing/TestConstants.kt b/testing/test-utils/src/main/kotlin/net/corda/testing/TestConstants.kt index 4f3eb0f66e..947b344fea 100644 --- a/testing/test-utils/src/main/kotlin/net/corda/testing/TestConstants.kt +++ b/testing/test-utils/src/main/kotlin/net/corda/testing/TestConstants.kt @@ -67,18 +67,18 @@ val DUMMY_REGULATOR: Party get() = Party(CordaX500Name(organisation = "Regulator val DEV_CA: CertificateAndKeyPair by lazy { // TODO: Should be identity scheme - val caKeyStore = loadKeyStore(ClassLoader.getSystemResourceAsStream("net/corda/node/internal/certificates/cordadevcakeys.jks"), "cordacadevpass") + val caKeyStore = loadKeyStore(ClassLoader.getSystemResourceAsStream("certificates/cordadevcakeys.jks"), "cordacadevpass") caKeyStore.getCertificateAndKeyPair(X509Utilities.CORDA_INTERMEDIATE_CA, "cordacadevkeypass") } val ROOT_CA: CertificateAndKeyPair by lazy { // TODO: Should be identity scheme - val caKeyStore = loadKeyStore(ClassLoader.getSystemResourceAsStream("net/corda/node/internal/certificates/cordadevcakeys.jks"), "cordacadevpass") + val caKeyStore = loadKeyStore(ClassLoader.getSystemResourceAsStream("certificates/cordadevcakeys.jks"), "cordacadevpass") caKeyStore.getCertificateAndKeyPair(X509Utilities.CORDA_ROOT_CA, "cordacadevkeypass") } val DEV_TRUST_ROOT: X509CertificateHolder by lazy { // TODO: Should be identity scheme - val caKeyStore = loadKeyStore(ClassLoader.getSystemResourceAsStream("net/corda/node/internal/certificates/cordadevcakeys.jks"), "cordacadevpass") + val caKeyStore = loadKeyStore(ClassLoader.getSystemResourceAsStream("certificates/cordadevcakeys.jks"), "cordacadevpass") caKeyStore.getCertificateChain(X509Utilities.CORDA_INTERMEDIATE_CA).last().toX509CertHolder() } diff --git a/tools/demobench/src/main/kotlin/net/corda/demobench/DemoBench.kt b/tools/demobench/src/main/kotlin/net/corda/demobench/DemoBench.kt index 2dd37fd648..1dc8e11f25 100644 --- a/tools/demobench/src/main/kotlin/net/corda/demobench/DemoBench.kt +++ b/tools/demobench/src/main/kotlin/net/corda/demobench/DemoBench.kt @@ -1,7 +1,13 @@ package net.corda.demobench import javafx.scene.image.Image +import net.corda.client.rpc.internal.KryoClientSerializationScheme +import net.corda.core.serialization.internal.SerializationEnvironmentImpl +import net.corda.core.serialization.internal.nodeSerializationEnv import net.corda.demobench.views.DemoBenchView +import net.corda.nodeapi.internal.serialization.KRYO_P2P_CONTEXT +import net.corda.nodeapi.internal.serialization.SerializationFactoryImpl +import net.corda.nodeapi.internal.serialization.amqp.AMQPClientSerializationScheme import tornadofx.* import java.io.InputStreamReader import java.nio.charset.StandardCharsets.UTF_8 @@ -47,6 +53,17 @@ class DemoBench : App(DemoBenchView::class) { init { addStageIcon(Image("cordalogo.png")) + initialiseSerialization() + } + + private fun initialiseSerialization() { + val context = KRYO_P2P_CONTEXT + nodeSerializationEnv = SerializationEnvironmentImpl( + SerializationFactoryImpl().apply { + registerScheme(KryoClientSerializationScheme()) + registerScheme(AMQPClientSerializationScheme()) + }, + context) } } diff --git a/tools/demobench/src/main/kotlin/net/corda/demobench/model/NodeController.kt b/tools/demobench/src/main/kotlin/net/corda/demobench/model/NodeController.kt index 59237e97a8..fd1be25923 100644 --- a/tools/demobench/src/main/kotlin/net/corda/demobench/model/NodeController.kt +++ b/tools/demobench/src/main/kotlin/net/corda/demobench/model/NodeController.kt @@ -2,19 +2,26 @@ package net.corda.demobench.model import javafx.beans.binding.IntegerExpression import net.corda.core.identity.CordaX500Name +import net.corda.core.identity.Party import net.corda.core.internal.copyToDirectory import net.corda.core.internal.createDirectories import net.corda.core.internal.div import net.corda.core.internal.noneOrSingle import net.corda.core.utilities.NetworkHostAndPort +import net.corda.core.utilities.days import net.corda.demobench.plugin.CordappController import net.corda.demobench.pty.R3Pty +import net.corda.nodeapi.internal.NetworkParameters +import net.corda.nodeapi.internal.NetworkParametersCopier +import net.corda.nodeapi.internal.NotaryInfo +import net.corda.nodeapi.internal.ServiceIdentityGenerator import tornadofx.* import java.io.IOException import java.lang.management.ManagementFactory import java.nio.file.Files import java.nio.file.Path import java.text.SimpleDateFormat +import java.time.Instant import java.util.* import java.util.concurrent.atomic.AtomicInteger import java.util.logging.Level @@ -35,6 +42,8 @@ class NodeController(check: atRuntime = ::checkExists) : Controller() { private val command = jvm.commandFor(cordaPath).toTypedArray() private val nodes = LinkedHashMap() + private var notaryIdentity: Party? = null + private var networkParametersCopier: NetworkParametersCopier? = null private val port = AtomicInteger(firstPort) val activeNodes: List @@ -58,6 +67,7 @@ class NodeController(check: atRuntime = ::checkExists) : Controller() { fun IntegerExpression.toLocalAddress() = NetworkHostAndPort("localhost", value) val location = nodeData.nearestCity.value + val notary = nodeData.extraServices.filterIsInstance().noneOrSingle() val nodeConfig = NodeConfig( myLegalName = CordaX500Name( organisation = nodeData.legalName.value.trim(), @@ -67,7 +77,7 @@ class NodeController(check: atRuntime = ::checkExists) : Controller() { p2pAddress = nodeData.p2pPort.toLocalAddress(), rpcAddress = nodeData.rpcPort.toLocalAddress(), webAddress = nodeData.webPort.toLocalAddress(), - notary = nodeData.extraServices.filterIsInstance().noneOrSingle(), + notary = notary, h2port = nodeData.h2Port.value, issuableCurrencies = nodeData.extraServices.filterIsInstance().map { it.currency.toString() } ) @@ -102,6 +112,8 @@ class NodeController(check: atRuntime = ::checkExists) : Controller() { fun runCorda(pty: R3Pty, config: NodeConfigWrapper): Boolean { try { + // Notary can be removed and then added again, that's why we need to perform this check. + require((config.nodeConfig.notary != null).xor(notaryIdentity != null)) { "There must be exactly one notary in the network" } config.nodeDir.createDirectories() // Install any built-in plugins into the working directory. @@ -115,6 +127,7 @@ class NodeController(check: atRuntime = ::checkExists) : Controller() { val cordaEnv = System.getenv().toMutableMap().apply { jvm.setCapsuleCacheDir(this) } + (networkParametersCopier ?: makeNetworkParametersCopier(config)).install(config.nodeDir) pty.run(command, cordaEnv, config.nodeDir.toString()) log.info("Launched node: ${config.nodeConfig.myLegalName}") return true @@ -124,6 +137,30 @@ class NodeController(check: atRuntime = ::checkExists) : Controller() { } } + private fun makeNetworkParametersCopier(config: NodeConfigWrapper): NetworkParametersCopier { + val identity = getNotaryIdentity(config) + val parametersCopier = NetworkParametersCopier(NetworkParameters( + minimumPlatformVersion = 1, + notaries = listOf(NotaryInfo(identity, config.nodeConfig.notary!!.validating)), + modifiedTime = Instant.now(), + eventHorizon = 10000.days, + maxMessageSize = 40000, + maxTransactionSize = 40000, + epoch = 1 + )) + notaryIdentity = identity + networkParametersCopier = parametersCopier + return parametersCopier + } + + // Generate notary identity and save it into node's directory. This identity will be used in network parameters. + private fun getNotaryIdentity(config: NodeConfigWrapper): Party { + return ServiceIdentityGenerator.generateToDisk( + dirs = listOf(config.nodeDir), + serviceName = config.nodeConfig.myLegalName, + serviceId = "identity") + } + fun reset() { baseDir = baseDirFor(System.currentTimeMillis()) log.info("Changed base directory: $baseDir") @@ -131,6 +168,8 @@ class NodeController(check: atRuntime = ::checkExists) : Controller() { // Wipe out any knowledge of previous nodes. nodes.clear() nodeInfoFilesCopier.reset() + notaryIdentity = null + networkParametersCopier = null } /** From d5e3f28303d5cb65bc744d5efdc0265991dde154 Mon Sep 17 00:00:00 2001 From: Shams Asari Date: Fri, 1 Dec 2017 20:57:43 +0000 Subject: [PATCH 15/23] Removed remaining references to networkMapService --- .../rpc/StandaloneCordaRPCJavaClientTest.java | 3 +-- config/dev/generalnodea.conf | 4 ---- docs/source/deploying-a-node.rst | 8 -------- .../src/main/resources/example-node.conf | 4 ---- docs/source/setting-up-a-corda-network.rst | 2 -- .../src/main/kotlin/net/corda/plugins/Node.kt | 15 --------------- .../kotlin/net/corda/smoketesting/NodeConfig.kt | 11 ++--------- 7 files changed, 3 insertions(+), 44 deletions(-) diff --git a/client/rpc/src/smoke-test/java/net/corda/java/rpc/StandaloneCordaRPCJavaClientTest.java b/client/rpc/src/smoke-test/java/net/corda/java/rpc/StandaloneCordaRPCJavaClientTest.java index 692cfe381c..66e1880100 100644 --- a/client/rpc/src/smoke-test/java/net/corda/java/rpc/StandaloneCordaRPCJavaClientTest.java +++ b/client/rpc/src/smoke-test/java/net/corda/java/rpc/StandaloneCordaRPCJavaClientTest.java @@ -48,8 +48,7 @@ public class StandaloneCordaRPCJavaClientTest { port.getAndIncrement(), port.getAndIncrement(), true, - Collections.singletonList(rpcUser), - null + Collections.singletonList(rpcUser) ); @Before diff --git a/config/dev/generalnodea.conf b/config/dev/generalnodea.conf index 0089d1cb24..12a3200105 100644 --- a/config/dev/generalnodea.conf +++ b/config/dev/generalnodea.conf @@ -4,8 +4,4 @@ trustStorePassword : "trustpass" p2pAddress : "localhost:10002" rpcAddress : "localhost:10003" webAddress : "localhost:10004" -networkMapService : { - address : "localhost:10000" - legalName : "O=Network Map Service,OU=corda,L=London,C=GB" -} useHTTPS : false diff --git a/docs/source/deploying-a-node.rst b/docs/source/deploying-a-node.rst index 2b091fddf8..a829ce81a7 100644 --- a/docs/source/deploying-a-node.rst +++ b/docs/source/deploying-a-node.rst @@ -45,10 +45,6 @@ handling, and ensures the Corda service is run at boot. trustStorePassword : "trustpass" useHTTPS : false devMode : false - networkMapService { - address="networkmap.foo.bar.com:10002" - legalName="O=FooBar NetworkMap, L=Dublin, C=IE" - } rpcUsers=[ { user=corda @@ -171,10 +167,6 @@ at boot, and means the Corda service stays running with no users connected to th extraAdvertisedServiceIds: [ "" ] useHTTPS : false devMode : false - networkMapService { - address="networkmap.foo.bar.com:10002" - legalName="O=FooBar NetworkMap, L=Dublin, C=IE" - } rpcUsers=[ { user=corda diff --git a/docs/source/example-code/src/main/resources/example-node.conf b/docs/source/example-code/src/main/resources/example-node.conf index c9bad1eb5b..08066a7964 100644 --- a/docs/source/example-code/src/main/resources/example-node.conf +++ b/docs/source/example-code/src/main/resources/example-node.conf @@ -10,10 +10,6 @@ dataSourceProperties : { p2pAddress : "my-corda-node:10002" rpcAddress : "my-corda-node:10003" webAddress : "localhost:10004" -networkMapService : { - address : "my-network-map:10000" - legalName : "O=Network Map Service,OU=corda,L=London,C=GB" -} useHTTPS : false rpcUsers : [ { username=user1, password=letmein, permissions=[ StartProtocol.net.corda.protocols.CashProtocol ] } diff --git a/docs/source/setting-up-a-corda-network.rst b/docs/source/setting-up-a-corda-network.rst index c5e43e01fd..4b610ef104 100644 --- a/docs/source/setting-up-a-corda-network.rst +++ b/docs/source/setting-up-a-corda-network.rst @@ -45,8 +45,6 @@ The most important fields regarding network configuration are: resolvable name of a machine in a VPN. * ``rpcAddress``: The address to which Artemis will bind for RPC calls. * ``webAddress``: The address the webserver should bind. Note that the port must be distinct from that of ``p2pAddress`` and ``rpcAddress`` if they are on the same machine. -* ``networkMapService``: Details of the node running the network map service. If it's this node that's running the service - then this field must not be specified. Starting the nodes ~~~~~~~~~~~~~~~~~~ diff --git a/gradle-plugins/cordformation/src/main/kotlin/net/corda/plugins/Node.kt b/gradle-plugins/cordformation/src/main/kotlin/net/corda/plugins/Node.kt index 70af0e26c5..2d17b3b7c2 100644 --- a/gradle-plugins/cordformation/src/main/kotlin/net/corda/plugins/Node.kt +++ b/gradle-plugins/cordformation/src/main/kotlin/net/corda/plugins/Node.kt @@ -60,21 +60,6 @@ class Node(private val project: Project) : CordformNode() { config = config.withValue("useTestClock", ConfigValueFactory.fromAnyRef(useTestClock)) } - /** - * Set the network map address for this node. - * - * @warning This should not be directly set unless you know what you are doing. Use the networkMapName in the - * Cordform task instead. - * @param networkMapAddress Network map node address. - * @param networkMapLegalName Network map node legal name. - */ - fun networkMapAddress(networkMapAddress: String, networkMapLegalName: String) { - val networkMapService = mutableMapOf() - networkMapService.put("address", networkMapAddress) - networkMapService.put("legalName", networkMapLegalName) - config = config.withValue("networkMapService", ConfigValueFactory.fromMap(networkMapService)) - } - /** * Enables SSH access on given port * diff --git a/testing/smoke-test-utils/src/main/kotlin/net/corda/smoketesting/NodeConfig.kt b/testing/smoke-test-utils/src/main/kotlin/net/corda/smoketesting/NodeConfig.kt index cc003edc6b..c97f3a8191 100644 --- a/testing/smoke-test-utils/src/main/kotlin/net/corda/smoketesting/NodeConfig.kt +++ b/testing/smoke-test-utils/src/main/kotlin/net/corda/smoketesting/NodeConfig.kt @@ -14,8 +14,7 @@ class NodeConfig( val rpcPort: Int, val webPort: Int, val isNotary: Boolean, - val users: List, - var networkMap: NodeConfig? = null + val users: List ) { companion object { val renderOptions: ConfigRenderOptions = ConfigRenderOptions.defaults().setOriginComments(false) @@ -32,10 +31,6 @@ class NodeConfig( val config = empty() .withValue("myLegalName", valueFor(legalName.toString())) .withValue("p2pAddress", addressValueFor(p2pPort)) - .withFallback(optional("networkMapService", networkMap, { c, n -> - c.withValue("address", addressValueFor(n.p2pPort)) - .withValue("legalName", valueFor(n.legalName.toString())) - })) .withValue("webAddress", addressValueFor(webPort)) .withValue("rpcAddress", addressValueFor(rpcPort)) .withValue("rpcUsers", valueFor(users.map(User::toMap).toList())) @@ -50,8 +45,6 @@ class NodeConfig( fun toText(): String = toFileConfig().root().render(renderOptions) private fun valueFor(any: T): ConfigValue? = ConfigValueFactory.fromAnyRef(any) + private fun addressValueFor(port: Int) = valueFor("localhost:$port") - private inline fun optional(path: String, obj: T?, body: (Config, T) -> Config): Config { - return if (obj == null) empty() else body(empty(), obj).atPath(path) - } } From acd2281b2098b9a853cb57eae17137867a922ca0 Mon Sep 17 00:00:00 2001 From: Alberto Arri <30873160+al-r3@users.noreply.github.com> Date: Mon, 4 Dec 2017 12:53:22 +0000 Subject: [PATCH 16/23] ENT-1125 bootstrap root certificate (#2151) * ENT-1125 make nodes check that the returned signed certificate from Doorman has the expected root --- .../corda/nodeapi/config/SSLConfiguration.kt | 1 + .../internal/ServiceIdentityGenerator.kt | 13 +- .../nodeapi/internal/crypto/X509Utilities.kt | 5 + .../NetworkRegistrationHelperDriverTest.kt | 182 ++++++++++++++++++ .../registration/NetworkRegistrationHelper.kt | 33 +++- .../NetworkisRegistrationHelperTest.kt | 3 + .../net/corda/testing/driver/DriverTests.kt | 43 +---- .../kotlin/net/corda/testing/NodeTestUtils.kt | 1 + .../kotlin/net/corda/testing/driver/Driver.kt | 33 +++- .../net/corda/testing/internal/RPCDriver.kt | 5 +- .../net/corda/verifier/VerifierDriver.kt | 5 +- 11 files changed, 271 insertions(+), 53 deletions(-) create mode 100644 node/src/integration-test/kotlin/net/corda/node/utilities/registration/NetworkRegistrationHelperDriverTest.kt diff --git a/node-api/src/main/kotlin/net/corda/nodeapi/config/SSLConfiguration.kt b/node-api/src/main/kotlin/net/corda/nodeapi/config/SSLConfiguration.kt index 6fb82e508f..a357bf7093 100644 --- a/node-api/src/main/kotlin/net/corda/nodeapi/config/SSLConfiguration.kt +++ b/node-api/src/main/kotlin/net/corda/nodeapi/config/SSLConfiguration.kt @@ -15,4 +15,5 @@ interface SSLConfiguration { interface NodeSSLConfiguration : SSLConfiguration { val baseDirectory: Path override val certificatesDirectory: Path get() = baseDirectory / "certificates" + val rootCaCertFile: Path get() = certificatesDirectory / "rootcacert.cer" } diff --git a/node-api/src/main/kotlin/net/corda/nodeapi/internal/ServiceIdentityGenerator.kt b/node-api/src/main/kotlin/net/corda/nodeapi/internal/ServiceIdentityGenerator.kt index 21cba120a2..44d420d893 100644 --- a/node-api/src/main/kotlin/net/corda/nodeapi/internal/ServiceIdentityGenerator.kt +++ b/node-api/src/main/kotlin/net/corda/nodeapi/internal/ServiceIdentityGenerator.kt @@ -9,8 +9,10 @@ import net.corda.core.internal.createDirectories import net.corda.core.internal.div import net.corda.core.utilities.trace import net.corda.nodeapi.internal.crypto.* +import org.bouncycastle.cert.X509CertificateHolder import org.slf4j.LoggerFactory import java.nio.file.Path +import java.security.cert.Certificate object ServiceIdentityGenerator { private val log = LoggerFactory.getLogger(javaClass) @@ -22,18 +24,25 @@ object ServiceIdentityGenerator { * @param dirs List of node directories to place the generated identity and key pairs in. * @param serviceName The legal name of the distributed service. * @param threshold The threshold for the generated group [CompositeKey]. + * @param rootCertertificate the certificate to use a Corda root CA. If not specified the one in + * net/corda/node/internal/certificates/cordadevcakeys.jks is used. */ fun generateToDisk(dirs: List, serviceName: CordaX500Name, serviceId: String, - threshold: Int = 1): Party { + threshold: Int = 1, + rootCertertificate: X509CertificateHolder? = null): Party { log.trace { "Generating a group identity \"serviceName\" for nodes: ${dirs.joinToString()}" } val keyPairs = (1..dirs.size).map { generateKeyPair() } val notaryKey = CompositeKey.Builder().addKeys(keyPairs.map { it.public }).build(threshold) val caKeyStore = loadKeyStore(javaClass.classLoader.getResourceAsStream("certificates/cordadevcakeys.jks"), "cordacadevpass") val issuer = caKeyStore.getCertificateAndKeyPair(X509Utilities.CORDA_INTERMEDIATE_CA, "cordacadevkeypass") - val rootCert = caKeyStore.getCertificate(X509Utilities.CORDA_ROOT_CA) + val rootCert: Certificate = if (rootCertertificate != null) { + rootCertertificate.cert + } else { + caKeyStore.getCertificate(X509Utilities.CORDA_ROOT_CA) + } keyPairs.zip(dirs) { keyPair, dir -> val serviceKeyCert = X509Utilities.createCertificate(CertificateType.CLIENT_CA, issuer.certificate, issuer.keyPair, serviceName, keyPair.public) diff --git a/node-api/src/main/kotlin/net/corda/nodeapi/internal/crypto/X509Utilities.kt b/node-api/src/main/kotlin/net/corda/nodeapi/internal/crypto/X509Utilities.kt index def4ee9879..dfb6d17e81 100644 --- a/node-api/src/main/kotlin/net/corda/nodeapi/internal/crypto/X509Utilities.kt +++ b/node-api/src/main/kotlin/net/corda/nodeapi/internal/crypto/X509Utilities.kt @@ -313,6 +313,11 @@ class X509CertificateFactory { fun generateCertificate(input: InputStream): X509Certificate { return delegate.generateCertificate(input) as X509Certificate } + + // TODO migrate calls to [CertificateFactory#generateCertPath] to call this instead. + fun generateCertPath(vararg certificates: Certificate): CertPath { + return delegate.generateCertPath(certificates.asList()) + } } enum class CertificateType(val keyUsage: KeyUsage, vararg val purposes: KeyPurposeId, val isCA: Boolean) { diff --git a/node/src/integration-test/kotlin/net/corda/node/utilities/registration/NetworkRegistrationHelperDriverTest.kt b/node/src/integration-test/kotlin/net/corda/node/utilities/registration/NetworkRegistrationHelperDriverTest.kt new file mode 100644 index 0000000000..fb5a59fe14 --- /dev/null +++ b/node/src/integration-test/kotlin/net/corda/node/utilities/registration/NetworkRegistrationHelperDriverTest.kt @@ -0,0 +1,182 @@ +package net.corda.node.utilities.registration + +import net.corda.core.crypto.Crypto +import net.corda.core.identity.CordaX500Name +import net.corda.core.internal.cert +import net.corda.core.internal.toX509CertHolder +import net.corda.core.utilities.contextLogger +import net.corda.core.utilities.minutes +import net.corda.nodeapi.internal.crypto.CertificateAndKeyPair +import net.corda.nodeapi.internal.crypto.CertificateType +import net.corda.nodeapi.internal.crypto.X509CertificateFactory +import net.corda.nodeapi.internal.crypto.X509Utilities +import net.corda.nodeapi.internal.crypto.X509Utilities.CORDA_CLIENT_CA +import net.corda.nodeapi.internal.crypto.X509Utilities.CORDA_INTERMEDIATE_CA +import net.corda.nodeapi.internal.crypto.X509Utilities.CORDA_ROOT_CA +import net.corda.testing.ALICE_NAME +import net.corda.testing.driver.PortAllocation +import net.corda.testing.driver.driver +import net.corda.testing.node.network.NetworkMapServer +import org.assertj.core.api.Assertions.assertThat +import org.assertj.core.api.Assertions.assertThatThrownBy +import org.bouncycastle.pkcs.PKCS10CertificationRequest +import org.bouncycastle.pkcs.jcajce.JcaPKCS10CertificationRequest +import org.junit.After +import org.junit.Before +import org.junit.Test +import java.io.ByteArrayOutputStream +import java.io.InputStream +import java.net.URL +import java.security.KeyPair +import java.security.cert.CertPath +import java.security.cert.Certificate +import java.util.concurrent.TimeUnit +import java.util.concurrent.TimeoutException +import java.util.zip.ZipEntry +import java.util.zip.ZipOutputStream +import javax.ws.rs.* +import javax.ws.rs.core.MediaType +import javax.ws.rs.core.Response + +private const val REQUEST_ID = "requestId" + +private val x509CertificateFactory = X509CertificateFactory() +private val portAllocation = PortAllocation.Incremental(13000) + +/** + * Driver based tests for [NetworkRegistrationHelper] + */ +class NetworkRegistrationHelperDriverTest { + val rootCertAndKeyPair = createSelfKeyAndSelfSignedCertificate() + val rootCert = rootCertAndKeyPair.certificate + val handler = RegistrationHandler(rootCertAndKeyPair) + lateinit var server: NetworkMapServer + lateinit var host: String + var port: Int = 0 + val compatibilityZoneUrl get() = URL("http", host, port, "") + + @Before + fun startServer() { + server = NetworkMapServer(1.minutes, portAllocation.nextHostAndPort(), handler) + val (host, port) = server.start() + this.host = host + this.port = port + } + + @After + fun stopServer() { + server.close() + } + + @Test + fun `node registration correct root cert`() { + driver(portAllocation = portAllocation, + compatibilityZoneURL = compatibilityZoneUrl, + startNodesInProcess = true, + rootCertificate = rootCert + ) { + startNode(providedName = ALICE_NAME, initialRegistration = true).get() + } + + // We're getting: + // a request to sign the certificate then + // at least one poll request to see if the request has been approved. + // all the network map registration and download. + assertThat(handler.requests).startsWith("/certificate", "/certificate/" + REQUEST_ID) + } + + @Test + fun `node registration without root cert`() { + driver(portAllocation = portAllocation, + compatibilityZoneURL = compatibilityZoneUrl, + startNodesInProcess = true + ) { + assertThatThrownBy { + startNode(providedName = ALICE_NAME, initialRegistration = true).get() + }.isInstanceOf(java.nio.file.NoSuchFileException::class.java) + } + } + + @Test + fun `node registration wrong root cert`() { + driver(portAllocation = portAllocation, + compatibilityZoneURL = compatibilityZoneUrl, + startNodesInProcess = true, + rootCertificate = createSelfKeyAndSelfSignedCertificate().certificate + ) { + assertThatThrownBy { + startNode(providedName = ALICE_NAME, initialRegistration = true).get() + }.isInstanceOf(WrongRootCaCertificateException::class.java) + } + } +} + + +/** + * Simple registration handler which can handle a single request, which will be given request id [REQUEST_ID]. + */ +@Path("certificate") +class RegistrationHandler(private val certificateAndKeyPair: CertificateAndKeyPair) { + val requests = mutableListOf() + lateinit var certificationRequest: JcaPKCS10CertificationRequest + + @POST + @Consumes(MediaType.APPLICATION_OCTET_STREAM) + @Produces(MediaType.TEXT_PLAIN) + fun registration(input: InputStream): Response { + requests += "/certificate" + certificationRequest = input.use { JcaPKCS10CertificationRequest(it.readBytes()) } + return Response.ok(REQUEST_ID).build() + } + + @GET + @Path(REQUEST_ID) + fun reply(): Response { + requests += "/certificate/" + REQUEST_ID + val certPath = createSignedClientCertificate(certificationRequest, + certificateAndKeyPair.keyPair, arrayOf(certificateAndKeyPair.certificate.cert)) + return buildDoormanReply(certPath.certificates.toTypedArray()) + } +} + +// TODO this logic is shared with doorman itself, refactor this to be somewhere where both doorman and these tests +// can depend on +private fun createSignedClientCertificate(certificationRequest: PKCS10CertificationRequest, + caKeyPair: KeyPair, + caCertPath: Array): CertPath { + val request = JcaPKCS10CertificationRequest(certificationRequest) + val x509CertificateHolder = X509Utilities.createCertificate(CertificateType.CLIENT_CA, + caCertPath.first().toX509CertHolder(), + caKeyPair, + CordaX500Name.parse(request.subject.toString()).copy(commonName = X509Utilities.CORDA_CLIENT_CA_CN), + request.publicKey, + nameConstraints = null) + return x509CertificateFactory.generateCertPath(x509CertificateHolder.cert, *caCertPath) +} + +// TODO this logic is shared with doorman itself, refactor this to be somewhere where both doorman and these tests +// can depend on +private fun buildDoormanReply(certificates: Array): Response { + // Write certificate chain to a zip stream and extract the bit array output. + val baos = ByteArrayOutputStream() + ZipOutputStream(baos).use { zip -> + // Client certificate must come first and root certificate should come last. + listOf(CORDA_CLIENT_CA, CORDA_INTERMEDIATE_CA, CORDA_ROOT_CA).zip(certificates).forEach { + zip.putNextEntry(ZipEntry("${it.first}.cer")) + zip.write(it.second.encoded) + zip.closeEntry() + } + } + return Response.ok(baos.toByteArray()) + .type("application/zip") + .header("Content-Disposition", "attachment; filename=\"certificates.zip\"").build() +} + +private fun createSelfKeyAndSelfSignedCertificate(): CertificateAndKeyPair { + val rootCAKey = Crypto.generateKeyPair(X509Utilities.DEFAULT_TLS_SIGNATURE_SCHEME) + val rootCACert = X509Utilities.createSelfSignedCACertificate( + CordaX500Name(commonName = "Integration Test Corda Node Root CA", + organisation = "R3 Ltd", locality = "London", + country = "GB"), rootCAKey) + return CertificateAndKeyPair(rootCACert, rootCAKey) +} diff --git a/node/src/main/kotlin/net/corda/node/utilities/registration/NetworkRegistrationHelper.kt b/node/src/main/kotlin/net/corda/node/utilities/registration/NetworkRegistrationHelper.kt index c350c3dd97..83edb78fbe 100644 --- a/node/src/main/kotlin/net/corda/node/utilities/registration/NetworkRegistrationHelper.kt +++ b/node/src/main/kotlin/net/corda/node/utilities/registration/NetworkRegistrationHelper.kt @@ -12,6 +12,7 @@ import net.corda.nodeapi.internal.crypto.X509Utilities.CORDA_ROOT_CA import org.bouncycastle.openssl.jcajce.JcaPEMWriter import org.bouncycastle.util.io.pem.PemObject import java.io.StringWriter +import java.nio.file.Path import java.security.KeyPair import java.security.KeyStore import java.security.cert.Certificate @@ -75,10 +76,15 @@ class NetworkRegistrationHelper(private val config: NodeConfiguration, private v caKeyStore.addOrReplaceKey(CORDA_CLIENT_CA, keyPair.private, privateKeyPassword.toCharArray(), certificates) caKeyStore.deleteEntry(SELF_SIGNED_PRIVATE_KEY) caKeyStore.save(config.nodeKeystore, keystorePassword) + + // Check the root certificate. + val returnedRootCa = certificates.last() + checkReturnedRootCaMatchesExpectedCa(returnedRootCa) + // Save root certificates to trust store. val trustStore = loadOrCreateKeyStore(config.trustStoreFile, config.trustStorePassword) // Assumes certificate chain always starts with client certificate and end with root certificate. - trustStore.addOrReplaceCertificate(CORDA_ROOT_CA, certificates.last()) + trustStore.addOrReplaceCertificate(CORDA_ROOT_CA, returnedRootCa) trustStore.save(config.trustStoreFile, config.trustStorePassword) println("Node private key and certificate stored in ${config.nodeKeystore}.") @@ -98,6 +104,17 @@ class NetworkRegistrationHelper(private val config: NodeConfiguration, private v } } + /** + * Checks that the passed Certificate is the expected root CA. + * @throws WrongRootCaCertificateException if the certificates don't match. + */ + private fun checkReturnedRootCaMatchesExpectedCa(returnedRootCa: Certificate) { + val expected = X509Utilities.loadCertificateFromPEMFile(config.rootCaCertFile).cert + if (expected != returnedRootCa) { + throw WrongRootCaCertificateException(expected, returnedRootCa, config.rootCaCertFile) + } + } + /** * Poll Certificate Signing Server for approved certificate, * enter a slow polling loop if server return null. @@ -151,3 +168,17 @@ class NetworkRegistrationHelper(private val config: NodeConfiguration, private v } } } + +/** + * Exception thrown when the doorman root certificate doesn't match the expected (out-of-band) root certificate. + * This usually means the has been a Man-in-the-middle attack when contacting the doorman. + */ +class WrongRootCaCertificateException(expected: Certificate, + actual: Certificate, + expectedFilePath: Path): + Exception(""" + The Root CA returned back from the registration process does not match the expected Root CA + expected: $expected + actual: $actual + the expected certificate is stored in: $expectedFilePath + """.trimMargin()) diff --git a/node/src/test/kotlin/net/corda/node/utilities/registration/NetworkisRegistrationHelperTest.kt b/node/src/test/kotlin/net/corda/node/utilities/registration/NetworkisRegistrationHelperTest.kt index 65745b7f3d..330b32fbc3 100644 --- a/node/src/test/kotlin/net/corda/node/utilities/registration/NetworkisRegistrationHelperTest.kt +++ b/node/src/test/kotlin/net/corda/node/utilities/registration/NetworkisRegistrationHelperTest.kt @@ -46,6 +46,9 @@ class NetworkRegistrationHelperTest { baseDirectory = tempFolder.root.toPath(), myLegalName = ALICE.name) + config.rootCaCertFile.parent.createDirectories() + X509Utilities.saveCertificateAsPEMFile(certs.last().toX509CertHolder(), config.rootCaCertFile) + assertFalse(config.nodeKeystore.exists()) assertFalse(config.sslKeystore.exists()) assertFalse(config.trustStoreFile.exists()) diff --git a/testing/node-driver/src/integration-test/kotlin/net/corda/testing/driver/DriverTests.kt b/testing/node-driver/src/integration-test/kotlin/net/corda/testing/driver/DriverTests.kt index 108d53c719..e9fa30cffe 100644 --- a/testing/node-driver/src/integration-test/kotlin/net/corda/testing/driver/DriverTests.kt +++ b/testing/node-driver/src/integration-test/kotlin/net/corda/testing/driver/DriverTests.kt @@ -5,25 +5,17 @@ import net.corda.core.internal.div import net.corda.core.internal.list import net.corda.core.internal.readLines import net.corda.core.utilities.getOrThrow -import net.corda.core.utilities.minutes -import net.corda.core.utilities.seconds import net.corda.node.internal.NodeStartup import net.corda.testing.DUMMY_BANK_A import net.corda.testing.DUMMY_NOTARY import net.corda.testing.DUMMY_REGULATOR import net.corda.testing.common.internal.ProjectStructure.projectRootDir import net.corda.testing.node.NotarySpec -import net.corda.testing.node.network.NetworkMapServer import org.assertj.core.api.Assertions.assertThat import org.junit.Test -import java.net.URL import java.util.concurrent.Executors import java.util.concurrent.ScheduledExecutorService -import javax.ws.rs.GET -import javax.ws.rs.POST -import javax.ws.rs.Path -import javax.ws.rs.core.Response -import javax.ws.rs.core.Response.ok + class DriverTests { companion object { @@ -61,22 +53,6 @@ class DriverTests { nodeMustBeDown(nodeHandle) } - @Test - fun `node registration`() { - val handler = RegistrationHandler() - NetworkMapServer(1.seconds, portAllocation.nextHostAndPort(), handler).use { - val (host, port) = it.start() - driver(portAllocation = portAllocation, compatibilityZoneURL = URL("http://$host:$port")) { - // Wait for the node to have started. - startNode(initialRegistration = true).get() - } - } - // We're getting: - // a request to sign the certificate then - // at least one poll request to see if the request has been approved. - // all the network map registration and download. - assertThat(handler.requests).startsWith("/certificate", "/certificate/reply") - } @Test fun `debug mode enables debug logging level`() { @@ -106,20 +82,3 @@ class DriverTests { assertThat(baseDirectory / "process-id").doesNotExist() } } - -@Path("certificate") -class RegistrationHandler { - val requests = mutableListOf() - @POST - fun registration(): Response { - requests += "/certificate" - return ok("reply").build() - } - - @GET - @Path("reply") - fun reply(): Response { - requests += "/certificate/reply" - return ok().build() - } -} diff --git a/testing/node-driver/src/main/kotlin/net/corda/testing/NodeTestUtils.kt b/testing/node-driver/src/main/kotlin/net/corda/testing/NodeTestUtils.kt index 09f1e7918a..9271b57bd1 100644 --- a/testing/node-driver/src/main/kotlin/net/corda/testing/NodeTestUtils.kt +++ b/testing/node-driver/src/main/kotlin/net/corda/testing/NodeTestUtils.kt @@ -78,6 +78,7 @@ fun testNodeConfiguration( doCallRealMethod().whenever(it).trustStoreFile doCallRealMethod().whenever(it).sslKeystore doCallRealMethod().whenever(it).nodeKeystore + doCallRealMethod().whenever(it).rootCaCertFile } } diff --git a/testing/node-driver/src/main/kotlin/net/corda/testing/driver/Driver.kt b/testing/node-driver/src/main/kotlin/net/corda/testing/driver/Driver.kt index d05f0f05b8..12851cdaf5 100644 --- a/testing/node-driver/src/main/kotlin/net/corda/testing/driver/Driver.kt +++ b/testing/node-driver/src/main/kotlin/net/corda/testing/driver/Driver.kt @@ -41,6 +41,7 @@ import net.corda.nodeapi.config.parseAs import net.corda.nodeapi.config.toConfig import net.corda.nodeapi.internal.NotaryInfo import net.corda.nodeapi.internal.addShutdownHook +import net.corda.nodeapi.internal.crypto.X509Utilities import net.corda.testing.* import net.corda.nodeapi.internal.NetworkParametersCopier import net.corda.testing.common.internal.testNetworkParameters @@ -51,6 +52,7 @@ import net.corda.testing.node.MockServices.Companion.MOCK_VERSION_INFO import net.corda.testing.node.NotarySpec import okhttp3.OkHttpClient import okhttp3.Request +import org.bouncycastle.cert.X509CertificateHolder import org.slf4j.Logger import rx.Observable import rx.observables.ConnectableObservable @@ -343,9 +345,11 @@ data class NodeParameters( * available from [DriverDSLExposedInterface.notaryHandles]. Defaults to a simple validating notary. * @param compatibilityZoneURL if not null each node is started once in registration mode (which makes the node register and quit), * and then re-starts the node with the given parameters. + * @param rootCertificate if not null every time a node is started for registration that certificate is written on disk * @param dsl The dsl itself. * @return The value returned in the [dsl] closure. */ +// TODO: make the registration testing parameters internal. fun driver( defaultParameters: DriverParameters = DriverParameters(), isDebug: Boolean = defaultParameters.isDebug, @@ -360,6 +364,7 @@ fun driver( notarySpecs: List = defaultParameters.notarySpecs, extraCordappPackagesToScan: List = defaultParameters.extraCordappPackagesToScan, compatibilityZoneURL: URL? = defaultParameters.compatibilityZoneURL, + rootCertificate: X509CertificateHolder? = defaultParameters.rootCertificate, dsl: DriverDSLExposedInterface.() -> A ): A { return genericDriver( @@ -374,7 +379,8 @@ fun driver( waitForNodesToFinish = waitForAllNodesToFinish, notarySpecs = notarySpecs, extraCordappPackagesToScan = extraCordappPackagesToScan, - compatibilityZoneURL = compatibilityZoneURL + compatibilityZoneURL = compatibilityZoneURL, + rootCertificate = rootCertificate ), coerce = { it }, dsl = dsl, @@ -410,7 +416,8 @@ data class DriverParameters( val waitForNodesToFinish: Boolean = false, val notarySpecs: List = listOf(NotarySpec(DUMMY_NOTARY.name)), val extraCordappPackagesToScan: List = emptyList(), - val compatibilityZoneURL: URL? = null + val compatibilityZoneURL: URL? = null, + val rootCertificate: X509CertificateHolder? = null ) { fun setIsDebug(isDebug: Boolean) = copy(isDebug = isDebug) fun setDriverDirectory(driverDirectory: Path) = copy(driverDirectory = driverDirectory) @@ -421,8 +428,10 @@ data class DriverParameters( fun setInitialiseSerialization(initialiseSerialization: Boolean) = copy(initialiseSerialization = initialiseSerialization) fun setStartNodesInProcess(startNodesInProcess: Boolean) = copy(startNodesInProcess = startNodesInProcess) fun setTerminateNodesOnShutdown(terminateNodesOnShutdown: Boolean) = copy(waitForNodesToFinish = terminateNodesOnShutdown) - fun setExtraCordappPackagesToScan(extraCordappPackagesToScan: List) = copy(extraCordappPackagesToScan = extraCordappPackagesToScan) fun setNotarySpecs(notarySpecs: List) = copy(notarySpecs = notarySpecs) + fun setExtraCordappPackagesToScan(extraCordappPackagesToScan: List) = copy(extraCordappPackagesToScan = extraCordappPackagesToScan) + fun setCompatibilityZoneURL(compatibilityZoneURL: URL?) = copy(compatibilityZoneURL = compatibilityZoneURL) + fun setRootCertificate(rootCertificate: X509CertificateHolder?) = copy(rootCertificate = rootCertificate) } /** @@ -476,6 +485,7 @@ fun genericD notarySpecs: List, extraCordappPackagesToScan: List = defaultParameters.extraCordappPackagesToScan, compatibilityZoneURL: URL? = defaultParameters.compatibilityZoneURL, + rootCertificate: X509CertificateHolder? = defaultParameters.rootCertificate, driverDslWrapper: (DriverDSL) -> D, coerce: (D) -> DI, dsl: DI.() -> A ): A { @@ -492,7 +502,8 @@ fun genericD waitForNodesToFinish = waitForNodesToFinish, extraCordappPackagesToScan = extraCordappPackagesToScan, notarySpecs = notarySpecs, - compatibilityZoneURL = compatibilityZoneURL + compatibilityZoneURL = compatibilityZoneURL, + rootCertificate = rootCertificate ) ) val shutdownHook = addShutdownHook(driverDsl::shutdown) @@ -599,7 +610,8 @@ class DriverDSL( val waitForNodesToFinish: Boolean, extraCordappPackagesToScan: List, val notarySpecs: List, - val compatibilityZoneURL: URL? + val compatibilityZoneURL: URL?, + val rootCertificate: X509CertificateHolder? ) : DriverDSLInternalInterface { private var _executorService: ScheduledExecutorService? = null val executorService get() = _executorService!! @@ -711,6 +723,11 @@ class DriverDSL( } } + private fun writeRootCaCertificateForNode(path: Path, caRootCertificate: X509CertificateHolder) { + path.parent.createDirectories() + X509Utilities.saveCertificateAsPEMFile(caRootCertificate, path) + } + private fun registerNode(providedName: CordaX500Name, compatibilityZoneURL: URL): CordaFuture { val config = ConfigHelper.loadConfig( baseDirectory = baseDirectory(providedName), @@ -720,10 +737,12 @@ class DriverDSL( "compatibilityZoneURL" to compatibilityZoneURL.toString(), "myLegalName" to providedName.toString()) ) + val configuration = config.parseAsNodeConfiguration() + // If a rootCertificate is specified, put that in the node expected path. + rootCertificate?.let { writeRootCaCertificateForNode(configuration.rootCaCertFile, it) } if (startNodesInProcess) { // This is a bit cheating, we're not starting a full node, we're just calling the code nodes call // when registering. - val configuration = config.parseAsNodeConfiguration() NetworkRegistrationHelper(configuration, HTTPNetworkRegistrationService(compatibilityZoneURL)) .buildKeystore() return doneFuture(Unit) @@ -857,12 +876,14 @@ class DriverDSL( ServiceIdentityGenerator.generateToDisk( dirs = listOf(baseDirectory(spec.name)), serviceName = spec.name, + rootCertertificate = rootCertificate, serviceId = "identity" ) } else { ServiceIdentityGenerator.generateToDisk( dirs = generateNodeNames(spec).map { baseDirectory(it) }, serviceName = spec.name, + rootCertertificate = rootCertificate, serviceId = NotaryService.constructId( validating = spec.validating, raft = spec.cluster is ClusterSpec.Raft diff --git a/testing/node-driver/src/main/kotlin/net/corda/testing/internal/RPCDriver.kt b/testing/node-driver/src/main/kotlin/net/corda/testing/internal/RPCDriver.kt index 0646c8423b..1dec5f7076 100644 --- a/testing/node-driver/src/main/kotlin/net/corda/testing/internal/RPCDriver.kt +++ b/testing/node-driver/src/main/kotlin/net/corda/testing/internal/RPCDriver.kt @@ -47,6 +47,7 @@ import org.apache.activemq.artemis.core.settings.impl.AddressFullMessagePolicy import org.apache.activemq.artemis.core.settings.impl.AddressSettings import org.apache.activemq.artemis.spi.core.protocol.RemotingConnection import org.apache.activemq.artemis.spi.core.security.ActiveMQSecurityManager3 +import org.bouncycastle.cert.X509CertificateHolder import java.lang.reflect.Method import java.net.URL import java.nio.file.Path @@ -237,6 +238,7 @@ fun rpcDriver( notarySpecs: List = emptyList(), externalTrace: Trace? = null, compatibilityZoneURL: URL? = null, + rootCertificate: X509CertificateHolder? = null, dsl: RPCDriverExposedDSLInterface.() -> A ) = genericDriver( driverDsl = RPCDriverDSL( @@ -251,7 +253,8 @@ fun rpcDriver( waitForNodesToFinish = waitForNodesToFinish, extraCordappPackagesToScan = extraCordappPackagesToScan, notarySpecs = notarySpecs, - compatibilityZoneURL = compatibilityZoneURL + compatibilityZoneURL = compatibilityZoneURL, + rootCertificate = rootCertificate ), externalTrace ), coerce = { it }, diff --git a/verifier/src/integration-test/kotlin/net/corda/verifier/VerifierDriver.kt b/verifier/src/integration-test/kotlin/net/corda/verifier/VerifierDriver.kt index 3e88638771..61b80faa6b 100644 --- a/verifier/src/integration-test/kotlin/net/corda/verifier/VerifierDriver.kt +++ b/verifier/src/integration-test/kotlin/net/corda/verifier/VerifierDriver.kt @@ -37,6 +37,7 @@ import org.apache.activemq.artemis.core.security.CheckType import org.apache.activemq.artemis.core.security.Role import org.apache.activemq.artemis.core.server.impl.ActiveMQServerImpl import org.apache.activemq.artemis.spi.core.security.ActiveMQSecurityManager +import org.bouncycastle.cert.X509CertificateHolder import java.net.URL import java.nio.file.Path import java.nio.file.Paths @@ -87,6 +88,7 @@ fun verifierDriver( extraCordappPackagesToScan: List = emptyList(), notarySpecs: List = emptyList(), compatibilityZoneURL: URL? = null, + rootCertificate: X509CertificateHolder? = null, dsl: VerifierExposedDSLInterface.() -> A ) = genericDriver( driverDsl = VerifierDriverDSL( @@ -101,7 +103,8 @@ fun verifierDriver( waitForNodesToFinish = waitForNodesToFinish, extraCordappPackagesToScan = extraCordappPackagesToScan, notarySpecs = notarySpecs, - compatibilityZoneURL = compatibilityZoneURL + compatibilityZoneURL = compatibilityZoneURL, + rootCertificate = rootCertificate ) ), coerce = { it }, From 66515d24b5e89b54ae285103a379587cfe6b2db8 Mon Sep 17 00:00:00 2001 From: Shams Asari Date: Mon, 4 Dec 2017 13:50:25 +0000 Subject: [PATCH 17/23] Merge fixes and fix to attachment demo --- samples/attachment-demo/build.gradle | 2 ++ .../src/main/kotlin/net/corda/testing/driver/Driver.kt | 8 ++------ 2 files changed, 4 insertions(+), 6 deletions(-) diff --git a/samples/attachment-demo/build.gradle b/samples/attachment-demo/build.gradle index 93f10fefb1..a898df32c5 100644 --- a/samples/attachment-demo/build.gradle +++ b/samples/attachment-demo/build.gradle @@ -36,7 +36,9 @@ dependencies { task deployNodes(type: net.corda.plugins.Cordform, dependsOn: ['jar']) { ext.rpcUsers = [['username': "demo", 'password': "demo", 'permissions': ["StartFlow.net.corda.attachmentdemo.AttachmentDemoFlow", + "InvokeRpc.wellKnownPartyFromX500Name", "InvokeRpc.attachmentExists", + "InvokeRpc.openAttachment", "InvokeRpc.uploadAttachment", "InvokeRpc.internalVerifiedTransactionsFeed"]]] diff --git a/testing/node-driver/src/main/kotlin/net/corda/testing/driver/Driver.kt b/testing/node-driver/src/main/kotlin/net/corda/testing/driver/Driver.kt index 26f124813c..be2c472d16 100644 --- a/testing/node-driver/src/main/kotlin/net/corda/testing/driver/Driver.kt +++ b/testing/node-driver/src/main/kotlin/net/corda/testing/driver/Driver.kt @@ -31,18 +31,14 @@ import net.corda.node.services.config.* import net.corda.node.services.transactions.BFTNonValidatingNotaryService import net.corda.node.services.transactions.RaftNonValidatingNotaryService import net.corda.node.services.transactions.RaftValidatingNotaryService -import net.corda.nodeapi.internal.ServiceIdentityGenerator import net.corda.node.utilities.registration.HTTPNetworkRegistrationService import net.corda.node.utilities.registration.NetworkRegistrationHelper -import net.corda.nodeapi.internal.NodeInfoFilesCopier +import net.corda.nodeapi.internal.* import net.corda.nodeapi.internal.config.User -import net.corda.nodeapi.config.parseAs +import net.corda.nodeapi.internal.config.parseAs import net.corda.nodeapi.internal.config.toConfig -import net.corda.nodeapi.internal.NotaryInfo -import net.corda.nodeapi.internal.addShutdownHook import net.corda.nodeapi.internal.crypto.X509Utilities import net.corda.testing.* -import net.corda.nodeapi.internal.NetworkParametersCopier import net.corda.testing.common.internal.testNetworkParameters import net.corda.testing.driver.DriverDSL.ClusterType.* import net.corda.testing.internal.InProcessNode From 89256a7f163de7b7114d16b2f81ce5bb6e07bc0e Mon Sep 17 00:00:00 2001 From: Shams Asari Date: Tue, 5 Dec 2017 16:58:35 +0000 Subject: [PATCH 18/23] Moved the CZ URL and node registration logic of the driver to be more internal, not available through the standard driver call, as these are not testing features for an app dev. Also cleanup up some of the related tests. --- .../internal/ServiceIdentityGenerator.kt | 16 +- .../internal/config/SSLConfiguration.kt | 2 +- .../nodeapi/internal/crypto/X509Utilities.kt | 16 +- .../internal/crypto/X509UtilitiesTest.kt | 2 +- .../services/network/NetworkMapClientTest.kt | 103 +++++----- .../NetworkRegistrationHelperDriverTest.kt | 183 +++++++----------- .../registration/NetworkRegistrationHelper.kt | 21 +- ...st.kt => NetworkRegistrationHelperTest.kt} | 77 ++++++-- .../net/corda/testing/driver/DriverTests.kt | 4 +- .../kotlin/net/corda/testing/driver/Driver.kt | 138 +++++++------ .../net/corda/testing/internal/RPCDriver.kt | 8 +- .../testing/internal/demorun/DemoRunner.kt | 7 +- .../net/corda/verifier/VerifierDriver.kt | 9 +- 13 files changed, 304 insertions(+), 282 deletions(-) rename node/src/test/kotlin/net/corda/node/utilities/registration/{NetworkisRegistrationHelperTest.kt => NetworkRegistrationHelperTest.kt} (59%) diff --git a/node-api/src/main/kotlin/net/corda/nodeapi/internal/ServiceIdentityGenerator.kt b/node-api/src/main/kotlin/net/corda/nodeapi/internal/ServiceIdentityGenerator.kt index 44d420d893..8b88df05ec 100644 --- a/node-api/src/main/kotlin/net/corda/nodeapi/internal/ServiceIdentityGenerator.kt +++ b/node-api/src/main/kotlin/net/corda/nodeapi/internal/ServiceIdentityGenerator.kt @@ -9,13 +9,13 @@ import net.corda.core.internal.createDirectories import net.corda.core.internal.div import net.corda.core.utilities.trace import net.corda.nodeapi.internal.crypto.* -import org.bouncycastle.cert.X509CertificateHolder import org.slf4j.LoggerFactory import java.nio.file.Path -import java.security.cert.Certificate +import java.security.cert.X509Certificate object ServiceIdentityGenerator { private val log = LoggerFactory.getLogger(javaClass) + /** * Generates signing key pairs and a common distributed service identity for a set of nodes. * The key pairs and the group identity get serialized to disk in the corresponding node directories. @@ -24,25 +24,21 @@ object ServiceIdentityGenerator { * @param dirs List of node directories to place the generated identity and key pairs in. * @param serviceName The legal name of the distributed service. * @param threshold The threshold for the generated group [CompositeKey]. - * @param rootCertertificate the certificate to use a Corda root CA. If not specified the one in - * net/corda/node/internal/certificates/cordadevcakeys.jks is used. + * @param customRootCert the certificate to use a Corda root CA. If not specified the one in + * certificates/cordadevcakeys.jks is used. */ fun generateToDisk(dirs: List, serviceName: CordaX500Name, serviceId: String, threshold: Int = 1, - rootCertertificate: X509CertificateHolder? = null): Party { + customRootCert: X509Certificate? = null): Party { log.trace { "Generating a group identity \"serviceName\" for nodes: ${dirs.joinToString()}" } val keyPairs = (1..dirs.size).map { generateKeyPair() } val notaryKey = CompositeKey.Builder().addKeys(keyPairs.map { it.public }).build(threshold) val caKeyStore = loadKeyStore(javaClass.classLoader.getResourceAsStream("certificates/cordadevcakeys.jks"), "cordacadevpass") val issuer = caKeyStore.getCertificateAndKeyPair(X509Utilities.CORDA_INTERMEDIATE_CA, "cordacadevkeypass") - val rootCert: Certificate = if (rootCertertificate != null) { - rootCertertificate.cert - } else { - caKeyStore.getCertificate(X509Utilities.CORDA_ROOT_CA) - } + val rootCert = customRootCert ?: caKeyStore.getCertificate(X509Utilities.CORDA_ROOT_CA) keyPairs.zip(dirs) { keyPair, dir -> val serviceKeyCert = X509Utilities.createCertificate(CertificateType.CLIENT_CA, issuer.certificate, issuer.keyPair, serviceName, keyPair.public) diff --git a/node-api/src/main/kotlin/net/corda/nodeapi/internal/config/SSLConfiguration.kt b/node-api/src/main/kotlin/net/corda/nodeapi/internal/config/SSLConfiguration.kt index dbe587373c..b4c8534b93 100644 --- a/node-api/src/main/kotlin/net/corda/nodeapi/internal/config/SSLConfiguration.kt +++ b/node-api/src/main/kotlin/net/corda/nodeapi/internal/config/SSLConfiguration.kt @@ -15,5 +15,5 @@ interface SSLConfiguration { interface NodeSSLConfiguration : SSLConfiguration { val baseDirectory: Path override val certificatesDirectory: Path get() = baseDirectory / "certificates" - val rootCaCertFile: Path get() = certificatesDirectory / "rootcacert.cer" + val rootCertFile: Path get() = certificatesDirectory / "rootcert.pem" } diff --git a/node-api/src/main/kotlin/net/corda/nodeapi/internal/crypto/X509Utilities.kt b/node-api/src/main/kotlin/net/corda/nodeapi/internal/crypto/X509Utilities.kt index dfb6d17e81..d5d40b7a3c 100644 --- a/node-api/src/main/kotlin/net/corda/nodeapi/internal/crypto/X509Utilities.kt +++ b/node-api/src/main/kotlin/net/corda/nodeapi/internal/crypto/X509Utilities.kt @@ -4,8 +4,8 @@ import net.corda.core.crypto.Crypto import net.corda.core.crypto.SignatureScheme import net.corda.core.crypto.random63BitValue import net.corda.core.identity.CordaX500Name +import net.corda.core.internal.cert import net.corda.core.internal.read -import net.corda.core.internal.write import net.corda.core.internal.x500Name import net.corda.core.utilities.days import net.corda.core.utilities.millis @@ -27,10 +27,8 @@ import org.bouncycastle.operator.jcajce.JcaContentVerifierProviderBuilder import org.bouncycastle.pkcs.PKCS10CertificationRequest import org.bouncycastle.pkcs.jcajce.JcaPKCS10CertificationRequestBuilder import org.bouncycastle.util.io.pem.PemReader -import java.io.FileWriter import java.io.InputStream import java.math.BigInteger -import java.nio.file.Files import java.nio.file.Path import java.security.KeyPair import java.security.PublicKey @@ -164,7 +162,7 @@ object X509Utilities { * @param file Target file. */ @JvmStatic - fun saveCertificateAsPEMFile(x509Certificate: X509CertificateHolder, file: Path) { + fun saveCertificateAsPEMFile(x509Certificate: X509Certificate, file: Path) { JcaPEMWriter(file.toFile().writer()).use { it.writeObject(x509Certificate) } @@ -176,14 +174,14 @@ object X509Utilities { * @return The X509Certificate that was encoded in the file. */ @JvmStatic - fun loadCertificateFromPEMFile(file: Path): X509CertificateHolder { - val cert = file.read { + fun loadCertificateFromPEMFile(file: Path): X509Certificate { + return file.read { val reader = PemReader(it.reader()) val pemObject = reader.readPemObject() - X509CertificateHolder(pemObject.content) + val certHolder = X509CertificateHolder(pemObject.content) + certHolder.isValidOn(Date()) + certHolder.cert } - cert.isValidOn(Date()) - return cert } /** diff --git a/node-api/src/test/kotlin/net/corda/nodeapi/internal/crypto/X509UtilitiesTest.kt b/node-api/src/test/kotlin/net/corda/nodeapi/internal/crypto/X509UtilitiesTest.kt index 7ed2ff6527..31bc5505ae 100644 --- a/node-api/src/test/kotlin/net/corda/nodeapi/internal/crypto/X509UtilitiesTest.kt +++ b/node-api/src/test/kotlin/net/corda/nodeapi/internal/crypto/X509UtilitiesTest.kt @@ -71,7 +71,7 @@ class X509UtilitiesTest { fun `load and save a PEM file certificate`() { val tmpCertificateFile = tempFile("cacert.pem") val caKey = generateKeyPair(X509Utilities.DEFAULT_TLS_SIGNATURE_SCHEME) - val caCert = X509Utilities.createSelfSignedCACertificate(CordaX500Name(commonName = "Test Cert", organisation = "R3 Ltd", locality = "London", country = "GB"), caKey) + val caCert = X509Utilities.createSelfSignedCACertificate(CordaX500Name(commonName = "Test Cert", organisation = "R3 Ltd", locality = "London", country = "GB"), caKey).cert X509Utilities.saveCertificateAsPEMFile(caCert, tmpCertificateFile) val readCertificate = X509Utilities.loadCertificateFromPEMFile(tmpCertificateFile) assertEquals(caCert, readCertificate) diff --git a/node/src/integration-test/kotlin/net/corda/node/services/network/NetworkMapClientTest.kt b/node/src/integration-test/kotlin/net/corda/node/services/network/NetworkMapClientTest.kt index d82386ab5b..de53694fdf 100644 --- a/node/src/integration-test/kotlin/net/corda/node/services/network/NetworkMapClientTest.kt +++ b/node/src/integration-test/kotlin/net/corda/node/services/network/NetworkMapClientTest.kt @@ -4,85 +4,96 @@ import net.corda.core.node.NodeInfo import net.corda.core.utilities.seconds import net.corda.testing.ALICE import net.corda.testing.BOB +import net.corda.testing.driver.CompatibilityZoneParams import net.corda.testing.driver.NodeHandle import net.corda.testing.driver.PortAllocation -import net.corda.testing.driver.driver +import net.corda.testing.driver.internalDriver import net.corda.testing.node.network.NetworkMapServer import org.assertj.core.api.Assertions.assertThat +import org.junit.After +import org.junit.Before import org.junit.Test import java.net.URL +// TODO There is a unit test class with the same name. Rename this to something else. class NetworkMapClientTest { + private val cacheTimeout = 1.seconds private val portAllocation = PortAllocation.Incremental(10000) + private lateinit var networkMapServer: NetworkMapServer + private lateinit var compatibilityZone: CompatibilityZoneParams + + @Before + fun start() { + networkMapServer = NetworkMapServer(cacheTimeout, portAllocation.nextHostAndPort()) + val address = networkMapServer.start() + compatibilityZone = CompatibilityZoneParams(URL("http://$address"), rootCert = null) + } + + @After + fun cleanUp() { + networkMapServer.close() + } + @Test fun `nodes can see each other using the http network map`() { - NetworkMapServer(1.seconds, portAllocation.nextHostAndPort()).use { - val (host, port) = it.start() - driver(portAllocation = portAllocation, compatibilityZoneURL = URL("http://$host:$port")) { - val alice = startNode(providedName = ALICE.name) - val bob = startNode(providedName = BOB.name) + internalDriver(portAllocation = portAllocation, compatibilityZone = compatibilityZone) { + val alice = startNode(providedName = ALICE.name) + val bob = startNode(providedName = BOB.name) - val notaryNode = defaultNotaryNode.get() - val aliceNode = alice.get() - val bobNode = bob.get() + val notaryNode = defaultNotaryNode.get() + val aliceNode = alice.get() + val bobNode = bob.get() - notaryNode.onlySees(notaryNode.nodeInfo, aliceNode.nodeInfo, bobNode.nodeInfo) - aliceNode.onlySees(notaryNode.nodeInfo, aliceNode.nodeInfo, bobNode.nodeInfo) - bobNode.onlySees(notaryNode.nodeInfo, aliceNode.nodeInfo, bobNode.nodeInfo) - } + notaryNode.onlySees(notaryNode.nodeInfo, aliceNode.nodeInfo, bobNode.nodeInfo) + aliceNode.onlySees(notaryNode.nodeInfo, aliceNode.nodeInfo, bobNode.nodeInfo) + bobNode.onlySees(notaryNode.nodeInfo, aliceNode.nodeInfo, bobNode.nodeInfo) } } @Test fun `nodes process network map add updates correctly when adding new node to network map`() { - NetworkMapServer(1.seconds, portAllocation.nextHostAndPort()).use { - val (host, port) = it.start() - driver(portAllocation = portAllocation, compatibilityZoneURL = URL("http://$host:$port")) { - val alice = startNode(providedName = ALICE.name) - val notaryNode = defaultNotaryNode.get() - val aliceNode = alice.get() + internalDriver(portAllocation = portAllocation, compatibilityZone = compatibilityZone) { + val alice = startNode(providedName = ALICE.name) + val notaryNode = defaultNotaryNode.get() + val aliceNode = alice.get() - notaryNode.onlySees(notaryNode.nodeInfo, aliceNode.nodeInfo) - aliceNode.onlySees(notaryNode.nodeInfo, aliceNode.nodeInfo) + notaryNode.onlySees(notaryNode.nodeInfo, aliceNode.nodeInfo) + aliceNode.onlySees(notaryNode.nodeInfo, aliceNode.nodeInfo) - val bob = startNode(providedName = BOB.name) - val bobNode = bob.get() + val bob = startNode(providedName = BOB.name) + val bobNode = bob.get() - // Wait for network map client to poll for the next update. - Thread.sleep(2.seconds.toMillis()) + // Wait for network map client to poll for the next update. + Thread.sleep(cacheTimeout.toMillis() * 2) - bobNode.onlySees(notaryNode.nodeInfo, aliceNode.nodeInfo, bobNode.nodeInfo) - notaryNode.onlySees(notaryNode.nodeInfo, aliceNode.nodeInfo, bobNode.nodeInfo) - aliceNode.onlySees(notaryNode.nodeInfo, aliceNode.nodeInfo, bobNode.nodeInfo) - } + bobNode.onlySees(notaryNode.nodeInfo, aliceNode.nodeInfo, bobNode.nodeInfo) + notaryNode.onlySees(notaryNode.nodeInfo, aliceNode.nodeInfo, bobNode.nodeInfo) + aliceNode.onlySees(notaryNode.nodeInfo, aliceNode.nodeInfo, bobNode.nodeInfo) } } @Test fun `nodes process network map remove updates correctly`() { - NetworkMapServer(1.seconds, portAllocation.nextHostAndPort()).use { - val (host, port) = it.start() - driver(portAllocation = portAllocation, compatibilityZoneURL = URL("http://$host:$port")) { - val alice = startNode(providedName = ALICE.name) - val bob = startNode(providedName = BOB.name) + internalDriver(portAllocation = portAllocation, compatibilityZone = compatibilityZone) { + val alice = startNode(providedName = ALICE.name) + val bob = startNode(providedName = BOB.name) - val notaryNode = defaultNotaryNode.get() - val aliceNode = alice.get() - val bobNode = bob.get() + val notaryNode = defaultNotaryNode.get() + val aliceNode = alice.get() + val bobNode = bob.get() - notaryNode.onlySees(notaryNode.nodeInfo, aliceNode.nodeInfo, bobNode.nodeInfo) - aliceNode.onlySees(notaryNode.nodeInfo, aliceNode.nodeInfo, bobNode.nodeInfo) - bobNode.onlySees(notaryNode.nodeInfo, aliceNode.nodeInfo, bobNode.nodeInfo) + notaryNode.onlySees(notaryNode.nodeInfo, aliceNode.nodeInfo, bobNode.nodeInfo) + aliceNode.onlySees(notaryNode.nodeInfo, aliceNode.nodeInfo, bobNode.nodeInfo) + bobNode.onlySees(notaryNode.nodeInfo, aliceNode.nodeInfo, bobNode.nodeInfo) - it.removeNodeInfo(aliceNode.nodeInfo) + networkMapServer.removeNodeInfo(aliceNode.nodeInfo) - // Wait for network map client to poll for the next update. - Thread.sleep(2.seconds.toMillis()) + // Wait for network map client to poll for the next update. + Thread.sleep(cacheTimeout.toMillis() * 2) - notaryNode.onlySees(notaryNode.nodeInfo, bobNode.nodeInfo) - bobNode.onlySees(notaryNode.nodeInfo, bobNode.nodeInfo) - } + notaryNode.onlySees(notaryNode.nodeInfo, bobNode.nodeInfo) + bobNode.onlySees(notaryNode.nodeInfo, bobNode.nodeInfo) } } diff --git a/node/src/integration-test/kotlin/net/corda/node/utilities/registration/NetworkRegistrationHelperDriverTest.kt b/node/src/integration-test/kotlin/net/corda/node/utilities/registration/NetworkRegistrationHelperDriverTest.kt index fb5a59fe14..4e20a5c710 100644 --- a/node/src/integration-test/kotlin/net/corda/node/utilities/registration/NetworkRegistrationHelperDriverTest.kt +++ b/node/src/integration-test/kotlin/net/corda/node/utilities/registration/NetworkRegistrationHelperDriverTest.kt @@ -4,7 +4,7 @@ import net.corda.core.crypto.Crypto import net.corda.core.identity.CordaX500Name import net.corda.core.internal.cert import net.corda.core.internal.toX509CertHolder -import net.corda.core.utilities.contextLogger +import net.corda.core.utilities.getOrThrow import net.corda.core.utilities.minutes import net.corda.nodeapi.internal.crypto.CertificateAndKeyPair import net.corda.nodeapi.internal.crypto.CertificateType @@ -13,12 +13,11 @@ import net.corda.nodeapi.internal.crypto.X509Utilities import net.corda.nodeapi.internal.crypto.X509Utilities.CORDA_CLIENT_CA import net.corda.nodeapi.internal.crypto.X509Utilities.CORDA_INTERMEDIATE_CA import net.corda.nodeapi.internal.crypto.X509Utilities.CORDA_ROOT_CA -import net.corda.testing.ALICE_NAME +import net.corda.testing.driver.CompatibilityZoneParams import net.corda.testing.driver.PortAllocation -import net.corda.testing.driver.driver +import net.corda.testing.driver.internalDriver import net.corda.testing.node.network.NetworkMapServer import org.assertj.core.api.Assertions.assertThat -import org.assertj.core.api.Assertions.assertThatThrownBy import org.bouncycastle.pkcs.PKCS10CertificationRequest import org.bouncycastle.pkcs.jcajce.JcaPKCS10CertificationRequest import org.junit.After @@ -30,37 +29,26 @@ import java.net.URL import java.security.KeyPair import java.security.cert.CertPath import java.security.cert.Certificate -import java.util.concurrent.TimeUnit -import java.util.concurrent.TimeoutException import java.util.zip.ZipEntry import java.util.zip.ZipOutputStream import javax.ws.rs.* import javax.ws.rs.core.MediaType import javax.ws.rs.core.Response -private const val REQUEST_ID = "requestId" - -private val x509CertificateFactory = X509CertificateFactory() -private val portAllocation = PortAllocation.Incremental(13000) - -/** - * Driver based tests for [NetworkRegistrationHelper] - */ +// TODO Rename this to NodeRegistrationTest class NetworkRegistrationHelperDriverTest { - val rootCertAndKeyPair = createSelfKeyAndSelfSignedCertificate() - val rootCert = rootCertAndKeyPair.certificate - val handler = RegistrationHandler(rootCertAndKeyPair) - lateinit var server: NetworkMapServer - lateinit var host: String - var port: Int = 0 - val compatibilityZoneUrl get() = URL("http", host, port, "") + private val portAllocation = PortAllocation.Incremental(13000) + private val rootCertAndKeyPair = createSelfKeyAndSelfSignedCertificate() + private val registrationHandler = RegistrationHandler(rootCertAndKeyPair) + + private lateinit var server: NetworkMapServer + private lateinit var compatibilityZone: CompatibilityZoneParams @Before fun startServer() { - server = NetworkMapServer(1.minutes, portAllocation.nextHostAndPort(), handler) - val (host, port) = server.start() - this.host = host - this.port = port + server = NetworkMapServer(1.minutes, portAllocation.nextHostAndPort(), registrationHandler) + val address = server.start() + compatibilityZone = CompatibilityZoneParams(URL("http://$address"), rootCertAndKeyPair.certificate.cert) } @After @@ -68,115 +56,84 @@ class NetworkRegistrationHelperDriverTest { server.close() } + // TODO Ideally this test should be checking that two nodes that register are able to transact with each other. However + // starting a second node hangs so that needs to be fixed. @Test fun `node registration correct root cert`() { - driver(portAllocation = portAllocation, - compatibilityZoneURL = compatibilityZoneUrl, - startNodesInProcess = true, - rootCertificate = rootCert + internalDriver( + portAllocation = portAllocation, + notarySpecs = emptyList(), + compatibilityZone = compatibilityZone ) { - startNode(providedName = ALICE_NAME, initialRegistration = true).get() - } - - // We're getting: - // a request to sign the certificate then - // at least one poll request to see if the request has been approved. - // all the network map registration and download. - assertThat(handler.requests).startsWith("/certificate", "/certificate/" + REQUEST_ID) - } - - @Test - fun `node registration without root cert`() { - driver(portAllocation = portAllocation, - compatibilityZoneURL = compatibilityZoneUrl, - startNodesInProcess = true - ) { - assertThatThrownBy { - startNode(providedName = ALICE_NAME, initialRegistration = true).get() - }.isInstanceOf(java.nio.file.NoSuchFileException::class.java) + startNode(providedName = CordaX500Name("Alice", "London", "GB")).getOrThrow() + assertThat(registrationHandler.idsPolled).contains("Alice") } } - @Test - fun `node registration wrong root cert`() { - driver(portAllocation = portAllocation, - compatibilityZoneURL = compatibilityZoneUrl, - startNodesInProcess = true, - rootCertificate = createSelfKeyAndSelfSignedCertificate().certificate - ) { - assertThatThrownBy { - startNode(providedName = ALICE_NAME, initialRegistration = true).get() - }.isInstanceOf(WrongRootCaCertificateException::class.java) - } + private fun createSelfKeyAndSelfSignedCertificate(): CertificateAndKeyPair { + val rootCAKey = Crypto.generateKeyPair(X509Utilities.DEFAULT_TLS_SIGNATURE_SCHEME) + val rootCACert = X509Utilities.createSelfSignedCACertificate( + CordaX500Name( + commonName = "Integration Test Corda Node Root CA", + organisation = "R3 Ltd", + locality = "London", + country = "GB"), + rootCAKey) + return CertificateAndKeyPair(rootCACert, rootCAKey) } } - -/** - * Simple registration handler which can handle a single request, which will be given request id [REQUEST_ID]. - */ @Path("certificate") -class RegistrationHandler(private val certificateAndKeyPair: CertificateAndKeyPair) { - val requests = mutableListOf() - lateinit var certificationRequest: JcaPKCS10CertificationRequest +class RegistrationHandler(private val rootCertAndKeyPair: CertificateAndKeyPair) { + private val certPaths = HashMap() + val idsPolled = HashSet() @POST @Consumes(MediaType.APPLICATION_OCTET_STREAM) @Produces(MediaType.TEXT_PLAIN) fun registration(input: InputStream): Response { - requests += "/certificate" - certificationRequest = input.use { JcaPKCS10CertificationRequest(it.readBytes()) } - return Response.ok(REQUEST_ID).build() + val certificationRequest = input.use { JcaPKCS10CertificationRequest(it.readBytes()) } + val (certPath, name) = createSignedClientCertificate( + certificationRequest, + rootCertAndKeyPair.keyPair, + arrayOf(rootCertAndKeyPair.certificate.cert)) + certPaths[name.organisation] = certPath + return Response.ok(name.organisation).build() } @GET - @Path(REQUEST_ID) - fun reply(): Response { - requests += "/certificate/" + REQUEST_ID - val certPath = createSignedClientCertificate(certificationRequest, - certificateAndKeyPair.keyPair, arrayOf(certificateAndKeyPair.certificate.cert)) - return buildDoormanReply(certPath.certificates.toTypedArray()) + @Path("{id}") + fun reply(@PathParam("id") id: String): Response { + idsPolled += id + return buildResponse(certPaths[id]!!.certificates) } -} -// TODO this logic is shared with doorman itself, refactor this to be somewhere where both doorman and these tests -// can depend on -private fun createSignedClientCertificate(certificationRequest: PKCS10CertificationRequest, - caKeyPair: KeyPair, - caCertPath: Array): CertPath { - val request = JcaPKCS10CertificationRequest(certificationRequest) - val x509CertificateHolder = X509Utilities.createCertificate(CertificateType.CLIENT_CA, - caCertPath.first().toX509CertHolder(), - caKeyPair, - CordaX500Name.parse(request.subject.toString()).copy(commonName = X509Utilities.CORDA_CLIENT_CA_CN), - request.publicKey, - nameConstraints = null) - return x509CertificateFactory.generateCertPath(x509CertificateHolder.cert, *caCertPath) -} - -// TODO this logic is shared with doorman itself, refactor this to be somewhere where both doorman and these tests -// can depend on -private fun buildDoormanReply(certificates: Array): Response { - // Write certificate chain to a zip stream and extract the bit array output. - val baos = ByteArrayOutputStream() - ZipOutputStream(baos).use { zip -> - // Client certificate must come first and root certificate should come last. - listOf(CORDA_CLIENT_CA, CORDA_INTERMEDIATE_CA, CORDA_ROOT_CA).zip(certificates).forEach { - zip.putNextEntry(ZipEntry("${it.first}.cer")) - zip.write(it.second.encoded) - zip.closeEntry() + private fun buildResponse(certificates: List): Response { + val baos = ByteArrayOutputStream() + ZipOutputStream(baos).use { zip -> + listOf(CORDA_CLIENT_CA, CORDA_INTERMEDIATE_CA, CORDA_ROOT_CA).zip(certificates).forEach { + zip.putNextEntry(ZipEntry("${it.first}.cer")) + zip.write(it.second.encoded) + zip.closeEntry() + } } + return Response.ok(baos.toByteArray()) + .type("application/zip") + .header("Content-Disposition", "attachment; filename=\"certificates.zip\"").build() } - return Response.ok(baos.toByteArray()) - .type("application/zip") - .header("Content-Disposition", "attachment; filename=\"certificates.zip\"").build() -} -private fun createSelfKeyAndSelfSignedCertificate(): CertificateAndKeyPair { - val rootCAKey = Crypto.generateKeyPair(X509Utilities.DEFAULT_TLS_SIGNATURE_SCHEME) - val rootCACert = X509Utilities.createSelfSignedCACertificate( - CordaX500Name(commonName = "Integration Test Corda Node Root CA", - organisation = "R3 Ltd", locality = "London", - country = "GB"), rootCAKey) - return CertificateAndKeyPair(rootCACert, rootCAKey) + private fun createSignedClientCertificate(certificationRequest: PKCS10CertificationRequest, + caKeyPair: KeyPair, + caCertPath: Array): Pair { + val request = JcaPKCS10CertificationRequest(certificationRequest) + val name = CordaX500Name.parse(request.subject.toString()) + val x509CertificateHolder = X509Utilities.createCertificate(CertificateType.CLIENT_CA, + caCertPath.first().toX509CertHolder(), + caKeyPair, + name, + request.publicKey, + nameConstraints = null) + val certPath = X509CertificateFactory().generateCertPath(x509CertificateHolder.cert, *caCertPath) + return Pair(certPath, name) + } } diff --git a/node/src/main/kotlin/net/corda/node/utilities/registration/NetworkRegistrationHelper.kt b/node/src/main/kotlin/net/corda/node/utilities/registration/NetworkRegistrationHelper.kt index 83edb78fbe..d16797e85b 100644 --- a/node/src/main/kotlin/net/corda/node/utilities/registration/NetworkRegistrationHelper.kt +++ b/node/src/main/kotlin/net/corda/node/utilities/registration/NetworkRegistrationHelper.kt @@ -28,10 +28,18 @@ class NetworkRegistrationHelper(private val config: NodeConfiguration, private v val SELF_SIGNED_PRIVATE_KEY = "Self Signed Private Key" } + init { + require(config.rootCertFile.exists()) { + "${config.rootCertFile} does not exist. This file must contain the root CA cert of your compatibility zone. " + + "Please contact your CZ operator." + } + } + private val requestIdStore = config.certificatesDirectory / "certificate-request-id.txt" private val keystorePassword = config.keyStorePassword // TODO: Use different password for private key. private val privateKeyPassword = config.keyStorePassword + private val rootCert = X509Utilities.loadCertificateFromPEMFile(config.rootCertFile) /** * Ensure the initial keystore for a node is set up; note that this function may cause the process to exit under @@ -106,12 +114,11 @@ class NetworkRegistrationHelper(private val config: NodeConfiguration, private v /** * Checks that the passed Certificate is the expected root CA. - * @throws WrongRootCaCertificateException if the certificates don't match. + * @throws WrongRootCertException if the certificates don't match. */ private fun checkReturnedRootCaMatchesExpectedCa(returnedRootCa: Certificate) { - val expected = X509Utilities.loadCertificateFromPEMFile(config.rootCaCertFile).cert - if (expected != returnedRootCa) { - throw WrongRootCaCertificateException(expected, returnedRootCa, config.rootCaCertFile) + if (rootCert != returnedRootCa) { + throw WrongRootCertException(rootCert, returnedRootCa, config.rootCertFile) } } @@ -173,9 +180,9 @@ class NetworkRegistrationHelper(private val config: NodeConfiguration, private v * Exception thrown when the doorman root certificate doesn't match the expected (out-of-band) root certificate. * This usually means the has been a Man-in-the-middle attack when contacting the doorman. */ -class WrongRootCaCertificateException(expected: Certificate, - actual: Certificate, - expectedFilePath: Path): +class WrongRootCertException(expected: Certificate, + actual: Certificate, + expectedFilePath: Path): Exception(""" The Root CA returned back from the registration process does not match the expected Root CA expected: $expected diff --git a/node/src/test/kotlin/net/corda/node/utilities/registration/NetworkisRegistrationHelperTest.kt b/node/src/test/kotlin/net/corda/node/utilities/registration/NetworkRegistrationHelperTest.kt similarity index 59% rename from node/src/test/kotlin/net/corda/node/utilities/registration/NetworkisRegistrationHelperTest.kt rename to node/src/test/kotlin/net/corda/node/utilities/registration/NetworkRegistrationHelperTest.kt index 330b32fbc3..97a9e2b818 100644 --- a/node/src/test/kotlin/net/corda/node/utilities/registration/NetworkisRegistrationHelperTest.kt +++ b/node/src/test/kotlin/net/corda/node/utilities/registration/NetworkRegistrationHelperTest.kt @@ -1,53 +1,56 @@ package net.corda.node.utilities.registration -import com.nhaarman.mockito_kotlin.* +import com.nhaarman.mockito_kotlin.any +import com.nhaarman.mockito_kotlin.doReturn +import com.nhaarman.mockito_kotlin.eq +import com.nhaarman.mockito_kotlin.whenever import net.corda.core.crypto.Crypto import net.corda.core.crypto.SecureHash import net.corda.core.identity.CordaX500Name import net.corda.core.internal.* +import net.corda.node.services.config.NodeConfiguration import net.corda.nodeapi.internal.crypto.X509Utilities import net.corda.nodeapi.internal.crypto.getX509Certificate import net.corda.nodeapi.internal.crypto.loadKeyStore import net.corda.testing.ALICE import net.corda.testing.rigorousMock import net.corda.testing.testNodeConfiguration -import org.bouncycastle.asn1.x500.X500Name -import org.bouncycastle.asn1.x500.style.BCStyle +import org.assertj.core.api.Assertions.assertThatThrownBy +import org.junit.Before import org.junit.Rule import org.junit.Test import org.junit.rules.TemporaryFolder +import java.security.cert.Certificate import kotlin.test.assertEquals import kotlin.test.assertFalse import kotlin.test.assertTrue -val X500Name.commonName: String? get() = getRDNs(BCStyle.CN).firstOrNull()?.first?.value?.toString() - class NetworkRegistrationHelperTest { @Rule @JvmField val tempFolder = TemporaryFolder() - @Test - fun buildKeyStore() { - val id = SecureHash.randomSHA256().toString() + private val requestId = SecureHash.randomSHA256().toString() + private lateinit var config: NodeConfiguration + @Before + fun init() { + config = testNodeConfiguration(baseDirectory = tempFolder.root.toPath(), myLegalName = ALICE.name) + } + + @Test + fun `successful registration`() { val identities = listOf("CORDA_CLIENT_CA", "CORDA_INTERMEDIATE_CA", "CORDA_ROOT_CA") .map { CordaX500Name(commonName = it, organisation = "R3 Ltd", locality = "London", country = "GB") } val certs = identities.stream().map { X509Utilities.createSelfSignedCACertificate(it, Crypto.generateKeyPair(X509Utilities.DEFAULT_TLS_SIGNATURE_SCHEME)) } .map { it.cert }.toTypedArray() - val certService = rigorousMock().also { - doReturn(id).whenever(it).submitRequest(any()) - doReturn(certs).whenever(it).retrieveCertificates(eq(id)) - } - val config = testNodeConfiguration( - baseDirectory = tempFolder.root.toPath(), - myLegalName = ALICE.name) + val certService = mockRegistrationResponse(*certs) - config.rootCaCertFile.parent.createDirectories() - X509Utilities.saveCertificateAsPEMFile(certs.last().toX509CertHolder(), config.rootCaCertFile) + config.rootCertFile.parent.createDirectories() + X509Utilities.saveCertificateAsPEMFile(certs.last(), config.rootCertFile) assertFalse(config.nodeKeystore.exists()) assertFalse(config.sslKeystore.exists()) @@ -92,4 +95,44 @@ class NetworkRegistrationHelperTest { assertTrue(containsAlias(X509Utilities.CORDA_ROOT_CA)) } } + + @Test + fun `rootCertFile doesn't exist`() { + val certService = rigorousMock() + + assertThatThrownBy { + NetworkRegistrationHelper(config, certService) + }.hasMessageContaining(config.rootCertFile.toString()) + } + + @Test + fun `root cert in response doesn't match expected`() { + val identities = listOf("CORDA_CLIENT_CA", + "CORDA_INTERMEDIATE_CA", + "CORDA_ROOT_CA") + .map { CordaX500Name(commonName = it, organisation = "R3 Ltd", locality = "London", country = "GB") } + val certs = identities.stream().map { X509Utilities.createSelfSignedCACertificate(it, Crypto.generateKeyPair(X509Utilities.DEFAULT_TLS_SIGNATURE_SCHEME)) } + .map { it.cert }.toTypedArray() + + val certService = mockRegistrationResponse(*certs) + + config.rootCertFile.parent.createDirectories() + X509Utilities.saveCertificateAsPEMFile( + X509Utilities.createSelfSignedCACertificate( + CordaX500Name("CORDA_ROOT_CA", "R3 Ltd", "London", "GB"), + Crypto.generateKeyPair(X509Utilities.DEFAULT_TLS_SIGNATURE_SCHEME)).cert, + config.rootCertFile + ) + + assertThatThrownBy { + NetworkRegistrationHelper(config, certService).buildKeystore() + }.isInstanceOf(WrongRootCertException::class.java) + } + + private fun mockRegistrationResponse(vararg response: Certificate): NetworkRegistrationService { + return rigorousMock().also { + doReturn(requestId).whenever(it).submitRequest(any()) + doReturn(response).whenever(it).retrieveCertificates(eq(requestId)) + } + } } diff --git a/testing/node-driver/src/integration-test/kotlin/net/corda/testing/driver/DriverTests.kt b/testing/node-driver/src/integration-test/kotlin/net/corda/testing/driver/DriverTests.kt index e9fa30cffe..6f9ea9daa5 100644 --- a/testing/node-driver/src/integration-test/kotlin/net/corda/testing/driver/DriverTests.kt +++ b/testing/node-driver/src/integration-test/kotlin/net/corda/testing/driver/DriverTests.kt @@ -76,8 +76,8 @@ class DriverTests { assertThat(baseDirectory / "process-id").exists() } - val baseDirectory = driver(notarySpecs = listOf(NotarySpec(DUMMY_NOTARY.name))) { - (this as DriverDSL).baseDirectory(DUMMY_NOTARY.name) + val baseDirectory = internalDriver(notarySpecs = listOf(NotarySpec(DUMMY_NOTARY.name))) { + baseDirectory(DUMMY_NOTARY.name) } assertThat(baseDirectory / "process-id").doesNotExist() } diff --git a/testing/node-driver/src/main/kotlin/net/corda/testing/driver/Driver.kt b/testing/node-driver/src/main/kotlin/net/corda/testing/driver/Driver.kt index be2c472d16..a27fbab11c 100644 --- a/testing/node-driver/src/main/kotlin/net/corda/testing/driver/Driver.kt +++ b/testing/node-driver/src/main/kotlin/net/corda/testing/driver/Driver.kt @@ -48,7 +48,6 @@ import net.corda.testing.node.MockServices.Companion.MOCK_VERSION_INFO import net.corda.testing.node.NotarySpec import okhttp3.OkHttpClient import okhttp3.Request -import org.bouncycastle.cert.X509CertificateHolder import org.slf4j.Logger import rx.Observable import rx.observables.ConnectableObservable @@ -57,6 +56,7 @@ import java.net.* import java.nio.file.Path import java.nio.file.Paths import java.nio.file.StandardCopyOption.REPLACE_EXISTING +import java.security.cert.X509Certificate import java.time.Duration import java.time.Instant import java.time.ZoneOffset.UTC @@ -165,8 +165,8 @@ interface DriverDSLExposedInterface : CordformContext { verifierType: VerifierType = defaultParameters.verifierType, customOverrides: Map = defaultParameters.customOverrides, startInSameProcess: Boolean? = defaultParameters.startInSameProcess, - maximumHeapSize: String = defaultParameters.maximumHeapSize, - initialRegistration: Boolean = defaultParameters.initialRegistration): CordaFuture + maximumHeapSize: String = defaultParameters.maximumHeapSize + ): CordaFuture /** @@ -301,8 +301,7 @@ data class NodeParameters( val verifierType: VerifierType = VerifierType.InMemory, val customOverrides: Map = emptyMap(), val startInSameProcess: Boolean? = null, - val maximumHeapSize: String = "200m", - val initialRegistration: Boolean = false + val maximumHeapSize: String = "200m" ) { fun setProvidedName(providedName: CordaX500Name?) = copy(providedName = providedName) fun setRpcUsers(rpcUsers: List) = copy(rpcUsers = rpcUsers) @@ -339,13 +338,9 @@ data class NodeParameters( * not. Note that this may be overridden in [DriverDSLExposedInterface.startNode]. * @param notarySpecs The notaries advertised for this network. These nodes will be started automatically and will be * available from [DriverDSLExposedInterface.notaryHandles]. Defaults to a simple validating notary. - * @param compatibilityZoneURL if not null each node is started once in registration mode (which makes the node register and quit), - * and then re-starts the node with the given parameters. - * @param rootCertificate if not null every time a node is started for registration that certificate is written on disk * @param dsl The dsl itself. * @return The value returned in the [dsl] closure. */ -// TODO: make the registration testing parameters internal. fun driver( defaultParameters: DriverParameters = DriverParameters(), isDebug: Boolean = defaultParameters.isDebug, @@ -359,8 +354,6 @@ fun driver( waitForAllNodesToFinish: Boolean = defaultParameters.waitForNodesToFinish, notarySpecs: List = defaultParameters.notarySpecs, extraCordappPackagesToScan: List = defaultParameters.extraCordappPackagesToScan, - compatibilityZoneURL: URL? = defaultParameters.compatibilityZoneURL, - rootCertificate: X509CertificateHolder? = defaultParameters.rootCertificate, dsl: DriverDSLExposedInterface.() -> A ): A { return genericDriver( @@ -375,8 +368,51 @@ fun driver( waitForNodesToFinish = waitForAllNodesToFinish, notarySpecs = notarySpecs, extraCordappPackagesToScan = extraCordappPackagesToScan, - compatibilityZoneURL = compatibilityZoneURL, - rootCertificate = rootCertificate + compatibilityZone = null + ), + coerce = { it }, + dsl = dsl, + initialiseSerialization = initialiseSerialization + ) +} + +// TODO Move CompatibilityZoneParams and internalDriver into internal package + +/** + * @property url The base CZ URL for registration and network map updates + * @property rootCert If specified then the node will register itself using [url] and expect the registration response + * to be rooted at this cert. + */ +data class CompatibilityZoneParams(val url: URL, val rootCert: X509Certificate?) + +fun internalDriver( + isDebug: Boolean = DriverParameters().isDebug, + driverDirectory: Path = DriverParameters().driverDirectory, + portAllocation: PortAllocation = DriverParameters().portAllocation, + debugPortAllocation: PortAllocation = DriverParameters().debugPortAllocation, + systemProperties: Map = DriverParameters().systemProperties, + useTestClock: Boolean = DriverParameters().useTestClock, + initialiseSerialization: Boolean = DriverParameters().initialiseSerialization, + startNodesInProcess: Boolean = DriverParameters().startNodesInProcess, + waitForAllNodesToFinish: Boolean = DriverParameters().waitForNodesToFinish, + notarySpecs: List = DriverParameters().notarySpecs, + extraCordappPackagesToScan: List = DriverParameters().extraCordappPackagesToScan, + compatibilityZone: CompatibilityZoneParams? = null, + dsl: DriverDSL.() -> A +): A { + return genericDriver( + driverDsl = DriverDSL( + portAllocation = portAllocation, + debugPortAllocation = debugPortAllocation, + systemProperties = systemProperties, + driverDirectory = driverDirectory.toAbsolutePath(), + useTestClock = useTestClock, + isDebug = isDebug, + startNodesInProcess = startNodesInProcess, + waitForNodesToFinish = waitForAllNodesToFinish, + notarySpecs = notarySpecs, + extraCordappPackagesToScan = extraCordappPackagesToScan, + compatibilityZone = compatibilityZone ), coerce = { it }, dsl = dsl, @@ -411,9 +447,7 @@ data class DriverParameters( val startNodesInProcess: Boolean = false, val waitForNodesToFinish: Boolean = false, val notarySpecs: List = listOf(NotarySpec(DUMMY_NOTARY.name)), - val extraCordappPackagesToScan: List = emptyList(), - val compatibilityZoneURL: URL? = null, - val rootCertificate: X509CertificateHolder? = null + val extraCordappPackagesToScan: List = emptyList() ) { fun setIsDebug(isDebug: Boolean) = copy(isDebug = isDebug) fun setDriverDirectory(driverDirectory: Path) = copy(driverDirectory = driverDirectory) @@ -426,8 +460,6 @@ data class DriverParameters( fun setTerminateNodesOnShutdown(terminateNodesOnShutdown: Boolean) = copy(waitForNodesToFinish = terminateNodesOnShutdown) fun setNotarySpecs(notarySpecs: List) = copy(notarySpecs = notarySpecs) fun setExtraCordappPackagesToScan(extraCordappPackagesToScan: List) = copy(extraCordappPackagesToScan = extraCordappPackagesToScan) - fun setCompatibilityZoneURL(compatibilityZoneURL: URL?) = copy(compatibilityZoneURL = compatibilityZoneURL) - fun setRootCertificate(rootCertificate: X509CertificateHolder?) = copy(rootCertificate = rootCertificate) } /** @@ -480,10 +512,9 @@ fun genericD startNodesInProcess: Boolean = defaultParameters.startNodesInProcess, notarySpecs: List, extraCordappPackagesToScan: List = defaultParameters.extraCordappPackagesToScan, - compatibilityZoneURL: URL? = defaultParameters.compatibilityZoneURL, - rootCertificate: X509CertificateHolder? = defaultParameters.rootCertificate, driverDslWrapper: (DriverDSL) -> D, - coerce: (D) -> DI, dsl: DI.() -> A + coerce: (D) -> DI, + dsl: DI.() -> A ): A { val serializationEnv = setGlobalSerialization(initialiseSerialization) val driverDsl = driverDslWrapper( @@ -498,8 +529,7 @@ fun genericD waitForNodesToFinish = waitForNodesToFinish, extraCordappPackagesToScan = extraCordappPackagesToScan, notarySpecs = notarySpecs, - compatibilityZoneURL = compatibilityZoneURL, - rootCertificate = rootCertificate + compatibilityZone = null ) ) val shutdownHook = addShutdownHook(driverDsl::shutdown) @@ -606,8 +636,7 @@ class DriverDSL( val waitForNodesToFinish: Boolean, extraCordappPackagesToScan: List, val notarySpecs: List, - val compatibilityZoneURL: URL?, - val rootCertificate: X509CertificateHolder? + val compatibilityZone: CompatibilityZoneParams? ) : DriverDSLInternalInterface { private var _executorService: ScheduledExecutorService? = null val executorService get() = _executorService!! @@ -679,16 +708,14 @@ class DriverDSL( verifierType: VerifierType, customOverrides: Map, startInSameProcess: Boolean?, - maximumHeapSize: String, - initialRegistration: Boolean + maximumHeapSize: String ): CordaFuture { val p2pAddress = portAllocation.nextHostAndPort() // TODO: Derive name from the full picked name, don't just wrap the common name val name = providedName ?: CordaX500Name(organisation = "${oneOf(names).organisation}-${p2pAddress.port}", locality = "London", country = "GB") - val registrationFuture = if (initialRegistration) { - compatibilityZoneURL ?: throw IllegalArgumentException("Compatibility zone URL must be provided for initial registration.") - registerNode(name, compatibilityZoneURL) + val registrationFuture = if (compatibilityZone?.rootCert != null) { + nodeRegistration(name, compatibilityZone.rootCert, compatibilityZone.url) } else { doneFuture(Unit) } @@ -709,8 +736,8 @@ class DriverDSL( val config = ConfigHelper.loadConfig( baseDirectory = baseDirectory(name), allowMissingConfig = true, - configOverrides = if (compatibilityZoneURL != null) { - configMap + mapOf("compatibilityZoneURL" to compatibilityZoneURL.toString()) + configOverrides = if (compatibilityZone != null) { + configMap + mapOf("compatibilityZoneURL" to compatibilityZone.url.toString()) } else { configMap } @@ -719,14 +746,10 @@ class DriverDSL( } } - private fun writeRootCaCertificateForNode(path: Path, caRootCertificate: X509CertificateHolder) { - path.parent.createDirectories() - X509Utilities.saveCertificateAsPEMFile(caRootCertificate, path) - } - - private fun registerNode(providedName: CordaX500Name, compatibilityZoneURL: URL): CordaFuture { + private fun nodeRegistration(providedName: CordaX500Name, rootCert: X509Certificate, compatibilityZoneURL: URL): CordaFuture { + val baseDirectory = baseDirectory(providedName).createDirectories() val config = ConfigHelper.loadConfig( - baseDirectory = baseDirectory(providedName), + baseDirectory = baseDirectory, allowMissingConfig = true, configOverrides = configOf( "p2pAddress" to "localhost:1222", // required argument, not really used @@ -734,16 +757,17 @@ class DriverDSL( "myLegalName" to providedName.toString()) ) val configuration = config.parseAsNodeConfiguration() - // If a rootCertificate is specified, put that in the node expected path. - rootCertificate?.let { writeRootCaCertificateForNode(configuration.rootCaCertFile, it) } - if (startNodesInProcess) { + + configuration.rootCertFile.parent.createDirectories() + X509Utilities.saveCertificateAsPEMFile(rootCert, configuration.rootCertFile) + + return if (startNodesInProcess) { // This is a bit cheating, we're not starting a full node, we're just calling the code nodes call // when registering. - NetworkRegistrationHelper(configuration, HTTPNetworkRegistrationService(compatibilityZoneURL)) - .buildKeystore() - return doneFuture(Unit) + NetworkRegistrationHelper(configuration, HTTPNetworkRegistrationService(compatibilityZoneURL)).buildKeystore() + doneFuture(Unit) } else { - return startNodeForRegistration(config) + startOutOfProcessNodeRegistration(config, configuration) } } @@ -872,18 +896,18 @@ class DriverDSL( ServiceIdentityGenerator.generateToDisk( dirs = listOf(baseDirectory(spec.name)), serviceName = spec.name, - rootCertertificate = rootCertificate, - serviceId = "identity" + serviceId = "identity", + customRootCert = compatibilityZone?.rootCert ) } else { ServiceIdentityGenerator.generateToDisk( dirs = generateNodeNames(spec).map { baseDirectory(it) }, serviceName = spec.name, - rootCertertificate = rootCertificate, serviceId = NotaryService.constructId( validating = spec.validating, raft = spec.cluster is ClusterSpec.Raft - ) + ), + customRootCert = compatibilityZone?.rootCert ) } NotaryInfo(identity, spec.validating) @@ -1008,16 +1032,12 @@ class DriverDSL( return future } - private fun startNodeForRegistration(config: Config): CordaFuture { - val maximumHeapSize = "200m" - val configuration = config.parseAsNodeConfiguration() - configuration.baseDirectory.createDirectories() - + private fun startOutOfProcessNodeRegistration(config: Config, configuration: NodeConfiguration): CordaFuture { val debugPort = if (isDebug) debugPortAllocation.nextPort() else null val process = startOutOfProcessNode(configuration, config, quasarJarPath, debugPort, - systemProperties, cordappPackages, maximumHeapSize, initialRegistration = true) + systemProperties, cordappPackages, "200m", initialRegistration = true) - return poll(executorService, "process exit") { + return poll(executorService, "node registration (${configuration.myLegalName})") { if (process.isAlive) null else Unit } } @@ -1030,7 +1050,7 @@ class DriverDSL( val baseDirectory = configuration.baseDirectory.createDirectories() // Distribute node info file using file copier when network map service URL (compatibilityZoneURL) is null. // TODO: need to implement the same in cordformation? - val nodeInfoFilesCopier = if (compatibilityZoneURL == null) nodeInfoFilesCopier else null + val nodeInfoFilesCopier = if (compatibilityZone == null) nodeInfoFilesCopier else null nodeInfoFilesCopier?.addConfig(baseDirectory) networkParameters!!.install(baseDirectory) @@ -1068,7 +1088,7 @@ class DriverDSL( } val p2pReadyFuture = addressMustBeBoundFuture(executorService, configuration.p2pAddress, process) return p2pReadyFuture.flatMap { - val processDeathFuture = poll(executorService, "process death") { + val processDeathFuture = poll(executorService, "process death while waiting for RPC (${configuration.myLegalName})") { if (process.isAlive) null else process } establishRpc(configuration, processDeathFuture).flatMap { rpc -> diff --git a/testing/node-driver/src/main/kotlin/net/corda/testing/internal/RPCDriver.kt b/testing/node-driver/src/main/kotlin/net/corda/testing/internal/RPCDriver.kt index 5dab84146c..acb0532170 100644 --- a/testing/node-driver/src/main/kotlin/net/corda/testing/internal/RPCDriver.kt +++ b/testing/node-driver/src/main/kotlin/net/corda/testing/internal/RPCDriver.kt @@ -47,9 +47,7 @@ import org.apache.activemq.artemis.core.settings.impl.AddressFullMessagePolicy import org.apache.activemq.artemis.core.settings.impl.AddressSettings import org.apache.activemq.artemis.spi.core.protocol.RemotingConnection import org.apache.activemq.artemis.spi.core.security.ActiveMQSecurityManager3 -import org.bouncycastle.cert.X509CertificateHolder import java.lang.reflect.Method -import java.net.URL import java.nio.file.Path import java.nio.file.Paths import java.util.* @@ -225,6 +223,7 @@ val fakeNodeLegalName = CordaX500Name(organisation = "Not:a:real:name", locality // Use a global pool so that we can run RPC tests in parallel private val globalPortAllocation = PortAllocation.Incremental(10000) private val globalDebugPortAllocation = PortAllocation.Incremental(5005) + fun rpcDriver( isDebug: Boolean = false, driverDirectory: Path = Paths.get("build", getTimestampAsDirectoryName()), @@ -237,8 +236,6 @@ fun rpcDriver( extraCordappPackagesToScan: List = emptyList(), notarySpecs: List = emptyList(), externalTrace: Trace? = null, - compatibilityZoneURL: URL? = null, - rootCertificate: X509CertificateHolder? = null, dsl: RPCDriverExposedDSLInterface.() -> A ) = genericDriver( driverDsl = RPCDriverDSL( @@ -253,8 +250,7 @@ fun rpcDriver( waitForNodesToFinish = waitForNodesToFinish, extraCordappPackagesToScan = extraCordappPackagesToScan, notarySpecs = notarySpecs, - compatibilityZoneURL = compatibilityZoneURL, - rootCertificate = rootCertificate + compatibilityZone = null ), externalTrace ), coerce = { it }, diff --git a/testing/node-driver/src/main/kotlin/net/corda/testing/internal/demorun/DemoRunner.kt b/testing/node-driver/src/main/kotlin/net/corda/testing/internal/demorun/DemoRunner.kt index 2d431799be..474e936c21 100644 --- a/testing/node-driver/src/main/kotlin/net/corda/testing/internal/demorun/DemoRunner.kt +++ b/testing/node-driver/src/main/kotlin/net/corda/testing/internal/demorun/DemoRunner.kt @@ -6,9 +6,8 @@ import net.corda.cordform.CordformDefinition import net.corda.cordform.CordformNode import net.corda.core.utilities.NetworkHostAndPort import net.corda.core.utilities.getOrThrow -import net.corda.testing.driver.DriverDSL import net.corda.testing.driver.PortAllocation -import net.corda.testing.driver.driver +import net.corda.testing.driver.internalDriver fun CordformDefinition.clean() { System.err.println("Deleting: $nodesDirectory") @@ -39,7 +38,7 @@ private fun CordformDefinition.runNodes(waitForAllNodesToFinish: Boolean, block: .flatMap { listOf(it.p2pAddress, it.rpcAddress, it.webAddress) } .mapNotNull { address -> address?.let { NetworkHostAndPort.parse(it).port } } .max()!! - driver( + internalDriver( isDebug = true, driverDirectory = nodesDirectory, extraCordappPackagesToScan = cordappPackages, @@ -50,7 +49,7 @@ private fun CordformDefinition.runNodes(waitForAllNodesToFinish: Boolean, block: waitForAllNodesToFinish = waitForAllNodesToFinish ) { setup(this) - (this as DriverDSL).startCordformNodes(nodes).getOrThrow() // Only proceed once everything is up and running + startCordformNodes(nodes).getOrThrow() // Only proceed once everything is up and running println("All nodes and webservers are ready...") block() } diff --git a/verifier/src/integration-test/kotlin/net/corda/verifier/VerifierDriver.kt b/verifier/src/integration-test/kotlin/net/corda/verifier/VerifierDriver.kt index ad93ad819f..5e35b75ec3 100644 --- a/verifier/src/integration-test/kotlin/net/corda/verifier/VerifierDriver.kt +++ b/verifier/src/integration-test/kotlin/net/corda/verifier/VerifierDriver.kt @@ -19,9 +19,9 @@ import net.corda.node.services.config.configureDevKeyAndTrustStores import net.corda.nodeapi.ArtemisTcpTransport import net.corda.nodeapi.ConnectionDirection import net.corda.nodeapi.VerifierApi +import net.corda.nodeapi.internal.ArtemisMessagingComponent.Companion.NODE_USER import net.corda.nodeapi.internal.config.NodeSSLConfiguration import net.corda.nodeapi.internal.config.SSLConfiguration -import net.corda.nodeapi.internal.ArtemisMessagingComponent.Companion.NODE_USER import net.corda.testing.driver.* import net.corda.testing.internal.ProcessUtilities import net.corda.testing.node.NotarySpec @@ -37,8 +37,6 @@ import org.apache.activemq.artemis.core.security.CheckType import org.apache.activemq.artemis.core.security.Role import org.apache.activemq.artemis.core.server.impl.ActiveMQServerImpl import org.apache.activemq.artemis.spi.core.security.ActiveMQSecurityManager -import org.bouncycastle.cert.X509CertificateHolder -import java.net.URL import java.nio.file.Path import java.nio.file.Paths import java.util.concurrent.ConcurrentHashMap @@ -87,8 +85,6 @@ fun verifierDriver( waitForNodesToFinish: Boolean = false, extraCordappPackagesToScan: List = emptyList(), notarySpecs: List = emptyList(), - compatibilityZoneURL: URL? = null, - rootCertificate: X509CertificateHolder? = null, dsl: VerifierExposedDSLInterface.() -> A ) = genericDriver( driverDsl = VerifierDriverDSL( @@ -103,8 +99,7 @@ fun verifierDriver( waitForNodesToFinish = waitForNodesToFinish, extraCordappPackagesToScan = extraCordappPackagesToScan, notarySpecs = notarySpecs, - compatibilityZoneURL = compatibilityZoneURL, - rootCertificate = rootCertificate + compatibilityZone = null ) ), coerce = { it }, From cb11379d987f24de88e8e8fac8c9849160fdd4da Mon Sep 17 00:00:00 2001 From: Shams Asari Date: Wed, 6 Dec 2017 22:01:41 +0000 Subject: [PATCH 19/23] Addressing some of the technical debt --- .../net/corda/core/crypto/X509NameConstraintsTest.kt | 2 +- .../net/corda/nodeapi/internal/crypto/KeyStoreWrapper.kt | 2 +- .../net/corda/nodeapi/internal/crypto/X509Utilities.kt | 8 ++++++-- .../corda/nodeapi/internal/crypto/X509UtilitiesTest.kt | 2 +- .../{NetworkMapClientTest.kt => NetworkMapTest.kt} | 5 ++--- ...trationHelperDriverTest.kt => NodeRegistrationTest.kt} | 5 ++--- .../main/kotlin/net/corda/node/internal/AbstractNode.kt | 2 +- .../node/services/identity/InMemoryIdentityService.kt | 2 +- .../node/services/identity/PersistentIdentityService.kt | 2 +- .../main/kotlin/net/corda/node/services/keys/KMSUtils.kt | 2 +- .../services/identity/InMemoryIdentityServiceTests.kt | 2 +- .../services/identity/PersistentIdentityServiceTests.kt | 2 +- .../corda/node/services/network/TestNodeInfoFactory.kt | 2 +- .../src/main/kotlin/net/corda/testing/driver/Driver.kt | 2 +- .../net/corda/testing/internal/demorun/DemoRunner.kt | 1 - .../net/corda/testing/node/network/NetworkMapServer.kt | 3 +-- .../src/main/kotlin/net/corda/testing/CoreTestUtils.kt | 2 +- 17 files changed, 23 insertions(+), 23 deletions(-) rename node/src/integration-test/kotlin/net/corda/node/services/network/{NetworkMapClientTest.kt => NetworkMapTest.kt} (96%) rename node/src/integration-test/kotlin/net/corda/node/utilities/registration/{NetworkRegistrationHelperDriverTest.kt => NodeRegistrationTest.kt} (97%) diff --git a/core/src/test/kotlin/net/corda/core/crypto/X509NameConstraintsTest.kt b/core/src/test/kotlin/net/corda/core/crypto/X509NameConstraintsTest.kt index b3b1f332e2..e135d1c93c 100644 --- a/core/src/test/kotlin/net/corda/core/crypto/X509NameConstraintsTest.kt +++ b/core/src/test/kotlin/net/corda/core/crypto/X509NameConstraintsTest.kt @@ -50,7 +50,7 @@ class X509NameConstraintsTest { val nameConstraints = NameConstraints(acceptableNames, arrayOf()) val pathValidator = CertPathValidator.getInstance("PKIX") - val certFactory = X509CertificateFactory().delegate + val certFactory = X509CertificateFactory() assertFailsWith(CertPathValidatorException::class) { val (keystore, trustStore) = makeKeyStores(X500Name("CN=Bank B"), nameConstraints) diff --git a/node-api/src/main/kotlin/net/corda/nodeapi/internal/crypto/KeyStoreWrapper.kt b/node-api/src/main/kotlin/net/corda/nodeapi/internal/crypto/KeyStoreWrapper.kt index 3e0d526a58..bb300e9344 100644 --- a/node-api/src/main/kotlin/net/corda/nodeapi/internal/crypto/KeyStoreWrapper.kt +++ b/node-api/src/main/kotlin/net/corda/nodeapi/internal/crypto/KeyStoreWrapper.kt @@ -19,7 +19,7 @@ class KeyStoreWrapper(private val storePath: Path, private val storePassword: St val clientCA = certificateAndKeyPair(X509Utilities.CORDA_CLIENT_CA) // Create new keys and store in keystore. val cert = X509Utilities.createCertificate(CertificateType.IDENTITY, clientCA.certificate, clientCA.keyPair, serviceName, pubKey) - val certPath = X509CertificateFactory().delegate.generateCertPath(listOf(cert.cert) + clientCertPath) + val certPath = X509CertificateFactory().generateCertPath(cert.cert, *clientCertPath) require(certPath.certificates.isNotEmpty()) { "Certificate path cannot be empty" } // TODO: X509Utilities.validateCertificateChain() return certPath diff --git a/node-api/src/main/kotlin/net/corda/nodeapi/internal/crypto/X509Utilities.kt b/node-api/src/main/kotlin/net/corda/nodeapi/internal/crypto/X509Utilities.kt index d5d40b7a3c..21b77c9ed6 100644 --- a/node-api/src/main/kotlin/net/corda/nodeapi/internal/crypto/X509Utilities.kt +++ b/node-api/src/main/kotlin/net/corda/nodeapi/internal/crypto/X509Utilities.kt @@ -151,7 +151,7 @@ object X509Utilities { require(certificates.isNotEmpty()) { "Certificate path must contain at least one certificate" } val params = PKIXParameters(setOf(TrustAnchor(trustedRoot, null))) params.isRevocationEnabled = false - val certPath = X509CertificateFactory().delegate.generateCertPath(certificates.toList()) + val certPath = X509CertificateFactory().generateCertPath(*certificates) val pathValidator = CertPathValidator.getInstance("PKIX") pathValidator.validate(certPath, params) } @@ -308,11 +308,15 @@ object X509Utilities { */ class X509CertificateFactory { val delegate: CertificateFactory = CertificateFactory.getInstance("X.509") + fun generateCertificate(input: InputStream): X509Certificate { return delegate.generateCertificate(input) as X509Certificate } - // TODO migrate calls to [CertificateFactory#generateCertPath] to call this instead. + fun generateCertPath(certificates: List): CertPath { + return delegate.generateCertPath(certificates) + } + fun generateCertPath(vararg certificates: Certificate): CertPath { return delegate.generateCertPath(certificates.asList()) } diff --git a/node-api/src/test/kotlin/net/corda/nodeapi/internal/crypto/X509UtilitiesTest.kt b/node-api/src/test/kotlin/net/corda/nodeapi/internal/crypto/X509UtilitiesTest.kt index 31bc5505ae..ca4d2b2e7e 100644 --- a/node-api/src/test/kotlin/net/corda/nodeapi/internal/crypto/X509UtilitiesTest.kt +++ b/node-api/src/test/kotlin/net/corda/nodeapi/internal/crypto/X509UtilitiesTest.kt @@ -433,7 +433,7 @@ class X509UtilitiesTest { val rootCAKey = Crypto.generateKeyPair(X509Utilities.DEFAULT_TLS_SIGNATURE_SCHEME) val rootCACert = X509Utilities.createSelfSignedCACertificate(ALICE.name, rootCAKey) val certificate = X509Utilities.createCertificate(CertificateType.TLS, rootCACert, rootCAKey, BOB.name.x500Name, BOB_PUBKEY) - val expected = X509CertificateFactory().delegate.generateCertPath(listOf(certificate.cert, rootCACert.cert)) + val expected = X509CertificateFactory().generateCertPath(certificate.cert, rootCACert.cert) val serialized = expected.serialize(factory, context).bytes val actual: CertPath = serialized.deserialize(factory, context) assertEquals(expected, actual) diff --git a/node/src/integration-test/kotlin/net/corda/node/services/network/NetworkMapClientTest.kt b/node/src/integration-test/kotlin/net/corda/node/services/network/NetworkMapTest.kt similarity index 96% rename from node/src/integration-test/kotlin/net/corda/node/services/network/NetworkMapClientTest.kt rename to node/src/integration-test/kotlin/net/corda/node/services/network/NetworkMapTest.kt index de53694fdf..c7972e3b2a 100644 --- a/node/src/integration-test/kotlin/net/corda/node/services/network/NetworkMapClientTest.kt +++ b/node/src/integration-test/kotlin/net/corda/node/services/network/NetworkMapTest.kt @@ -15,8 +15,7 @@ import org.junit.Before import org.junit.Test import java.net.URL -// TODO There is a unit test class with the same name. Rename this to something else. -class NetworkMapClientTest { +class NetworkMapTest { private val cacheTimeout = 1.seconds private val portAllocation = PortAllocation.Incremental(10000) @@ -27,7 +26,7 @@ class NetworkMapClientTest { fun start() { networkMapServer = NetworkMapServer(cacheTimeout, portAllocation.nextHostAndPort()) val address = networkMapServer.start() - compatibilityZone = CompatibilityZoneParams(URL("http://$address"), rootCert = null) + compatibilityZone = CompatibilityZoneParams(URL("http://$address")) } @After diff --git a/node/src/integration-test/kotlin/net/corda/node/utilities/registration/NetworkRegistrationHelperDriverTest.kt b/node/src/integration-test/kotlin/net/corda/node/utilities/registration/NodeRegistrationTest.kt similarity index 97% rename from node/src/integration-test/kotlin/net/corda/node/utilities/registration/NetworkRegistrationHelperDriverTest.kt rename to node/src/integration-test/kotlin/net/corda/node/utilities/registration/NodeRegistrationTest.kt index 4e20a5c710..62826c3d25 100644 --- a/node/src/integration-test/kotlin/net/corda/node/utilities/registration/NetworkRegistrationHelperDriverTest.kt +++ b/node/src/integration-test/kotlin/net/corda/node/utilities/registration/NodeRegistrationTest.kt @@ -35,8 +35,7 @@ import javax.ws.rs.* import javax.ws.rs.core.MediaType import javax.ws.rs.core.Response -// TODO Rename this to NodeRegistrationTest -class NetworkRegistrationHelperDriverTest { +class NodeRegistrationTest { private val portAllocation = PortAllocation.Incremental(13000) private val rootCertAndKeyPair = createSelfKeyAndSelfSignedCertificate() private val registrationHandler = RegistrationHandler(rootCertAndKeyPair) @@ -48,7 +47,7 @@ class NetworkRegistrationHelperDriverTest { fun startServer() { server = NetworkMapServer(1.minutes, portAllocation.nextHostAndPort(), registrationHandler) val address = server.start() - compatibilityZone = CompatibilityZoneParams(URL("http://$address"), rootCertAndKeyPair.certificate.cert) + compatibilityZone = CompatibilityZoneParams(URL("http://$address"), rootCert = rootCertAndKeyPair.certificate.cert) } @After diff --git a/node/src/main/kotlin/net/corda/node/internal/AbstractNode.kt b/node/src/main/kotlin/net/corda/node/internal/AbstractNode.kt index 89e394ec16..79a6d4a283 100644 --- a/node/src/main/kotlin/net/corda/node/internal/AbstractNode.kt +++ b/node/src/main/kotlin/net/corda/node/internal/AbstractNode.kt @@ -718,7 +718,7 @@ abstract class AbstractNode(val configuration: NodeConfiguration, throw ConfigurationException("The name '$singleName' for $id doesn't match what's in the key store: $subject") } - val certPath = X509CertificateFactory().delegate.generateCertPath(certificates) + val certPath = X509CertificateFactory().generateCertPath(certificates) return Pair(PartyAndCertificate(certPath), keyPair) } diff --git a/node/src/main/kotlin/net/corda/node/services/identity/InMemoryIdentityService.kt b/node/src/main/kotlin/net/corda/node/services/identity/InMemoryIdentityService.kt index 7fbbd810b8..ef41a31c41 100644 --- a/node/src/main/kotlin/net/corda/node/services/identity/InMemoryIdentityService.kt +++ b/node/src/main/kotlin/net/corda/node/services/identity/InMemoryIdentityService.kt @@ -79,7 +79,7 @@ class InMemoryIdentityService(identities: Iterable = emptyS if (firstCertWithThisName != identity.certificate) { val certificates = identity.certPath.certificates val idx = certificates.lastIndexOf(firstCertWithThisName) - val firstPath = X509CertificateFactory().delegate.generateCertPath(certificates.slice(idx until certificates.size)) + val firstPath = X509CertificateFactory().generateCertPath(certificates.slice(idx until certificates.size)) verifyAndRegisterIdentity(PartyAndCertificate(firstPath)) } diff --git a/node/src/main/kotlin/net/corda/node/services/identity/PersistentIdentityService.kt b/node/src/main/kotlin/net/corda/node/services/identity/PersistentIdentityService.kt index da2415aad0..0ed46bfeb4 100644 --- a/node/src/main/kotlin/net/corda/node/services/identity/PersistentIdentityService.kt +++ b/node/src/main/kotlin/net/corda/node/services/identity/PersistentIdentityService.kt @@ -134,7 +134,7 @@ class PersistentIdentityService(override val trustRoot: X509Certificate, if (firstCertWithThisName != identity.certificate) { val certificates = identity.certPath.certificates val idx = certificates.lastIndexOf(firstCertWithThisName) - val firstPath = X509CertificateFactory().delegate.generateCertPath(certificates.slice(idx until certificates.size)) + val firstPath = X509CertificateFactory().generateCertPath(certificates.slice(idx until certificates.size)) verifyAndRegisterIdentity(PartyAndCertificate(firstPath)) } diff --git a/node/src/main/kotlin/net/corda/node/services/keys/KMSUtils.kt b/node/src/main/kotlin/net/corda/node/services/keys/KMSUtils.kt index 6f520f37f1..11833c9535 100644 --- a/node/src/main/kotlin/net/corda/node/services/keys/KMSUtils.kt +++ b/node/src/main/kotlin/net/corda/node/services/keys/KMSUtils.kt @@ -37,7 +37,7 @@ fun freshCertificate(identityService: IdentityService, val window = X509Utilities.getCertificateValidityWindow(Duration.ZERO, 3650.days, issuerCert) val ourCertificate = X509Utilities.createCertificate(CertificateType.IDENTITY, issuerCert.subject, issuerSigner, issuer.name, subjectPublicKey, window) - val ourCertPath = X509CertificateFactory().delegate.generateCertPath(listOf(ourCertificate.cert) + issuer.certPath.certificates) + val ourCertPath = X509CertificateFactory().generateCertPath(listOf(ourCertificate.cert) + issuer.certPath.certificates) val anonymisedIdentity = PartyAndCertificate(ourCertPath) identityService.verifyAndRegisterIdentity(anonymisedIdentity) return anonymisedIdentity diff --git a/node/src/test/kotlin/net/corda/node/services/identity/InMemoryIdentityServiceTests.kt b/node/src/test/kotlin/net/corda/node/services/identity/InMemoryIdentityServiceTests.kt index 85857636dd..0a2d4b0a98 100644 --- a/node/src/test/kotlin/net/corda/node/services/identity/InMemoryIdentityServiceTests.kt +++ b/node/src/test/kotlin/net/corda/node/services/identity/InMemoryIdentityServiceTests.kt @@ -159,7 +159,7 @@ class InMemoryIdentityServiceTests { val issuer = getTestPartyAndCertificate(x500Name, issuerKeyPair.public, ca) val txKey = Crypto.generateKeyPair() val txCert = X509Utilities.createCertificate(CertificateType.IDENTITY, issuer.certificate.toX509CertHolder(), issuerKeyPair, x500Name, txKey.public) - val txCertPath = X509CertificateFactory().delegate.generateCertPath(listOf(txCert.cert) + issuer.certPath.certificates) + val txCertPath = X509CertificateFactory().generateCertPath(listOf(txCert.cert) + issuer.certPath.certificates) return Pair(issuer, PartyAndCertificate(txCertPath)) } diff --git a/node/src/test/kotlin/net/corda/node/services/identity/PersistentIdentityServiceTests.kt b/node/src/test/kotlin/net/corda/node/services/identity/PersistentIdentityServiceTests.kt index f3185b58eb..b5e4bac282 100644 --- a/node/src/test/kotlin/net/corda/node/services/identity/PersistentIdentityServiceTests.kt +++ b/node/src/test/kotlin/net/corda/node/services/identity/PersistentIdentityServiceTests.kt @@ -257,7 +257,7 @@ class PersistentIdentityServiceTests { val issuer = getTestPartyAndCertificate(x500Name, issuerKeyPair.public, ca) val txKey = Crypto.generateKeyPair() val txCert = X509Utilities.createCertificate(CertificateType.IDENTITY, issuer.certificate.toX509CertHolder(), issuerKeyPair, x500Name, txKey.public) - val txCertPath = X509CertificateFactory().delegate.generateCertPath(listOf(txCert.cert) + issuer.certPath.certificates) + val txCertPath = X509CertificateFactory().generateCertPath(listOf(txCert.cert) + issuer.certPath.certificates) return Pair(issuer, PartyAndCertificate(txCertPath)) } diff --git a/node/src/test/kotlin/net/corda/node/services/network/TestNodeInfoFactory.kt b/node/src/test/kotlin/net/corda/node/services/network/TestNodeInfoFactory.kt index 13ac5c5657..acfeba503f 100644 --- a/node/src/test/kotlin/net/corda/node/services/network/TestNodeInfoFactory.kt +++ b/node/src/test/kotlin/net/corda/node/services/network/TestNodeInfoFactory.kt @@ -39,7 +39,7 @@ object TestNodeInfoFactory { } private fun buildCertPath(vararg certificates: Certificate): CertPath { - return X509CertificateFactory().delegate.generateCertPath(certificates.asList()) + return X509CertificateFactory().generateCertPath(*certificates) } private fun X509CertificateHolder.toX509Certificate(): X509Certificate { diff --git a/testing/node-driver/src/main/kotlin/net/corda/testing/driver/Driver.kt b/testing/node-driver/src/main/kotlin/net/corda/testing/driver/Driver.kt index a27fbab11c..b117dc3d77 100644 --- a/testing/node-driver/src/main/kotlin/net/corda/testing/driver/Driver.kt +++ b/testing/node-driver/src/main/kotlin/net/corda/testing/driver/Driver.kt @@ -383,7 +383,7 @@ fun driver( * @property rootCert If specified then the node will register itself using [url] and expect the registration response * to be rooted at this cert. */ -data class CompatibilityZoneParams(val url: URL, val rootCert: X509Certificate?) +data class CompatibilityZoneParams(val url: URL, val rootCert: X509Certificate? = null) fun internalDriver( isDebug: Boolean = DriverParameters().isDebug, diff --git a/testing/node-driver/src/main/kotlin/net/corda/testing/internal/demorun/DemoRunner.kt b/testing/node-driver/src/main/kotlin/net/corda/testing/internal/demorun/DemoRunner.kt index 474e936c21..ef3fbd163e 100644 --- a/testing/node-driver/src/main/kotlin/net/corda/testing/internal/demorun/DemoRunner.kt +++ b/testing/node-driver/src/main/kotlin/net/corda/testing/internal/demorun/DemoRunner.kt @@ -18,7 +18,6 @@ fun CordformDefinition.clean() { * Deploy the nodes specified in the given [CordformDefinition]. This will block until all the nodes and webservers * have terminated. */ -// TODO add notaries to cordform! fun CordformDefinition.deployNodes() { runNodes(waitForAllNodesToFinish = true) { } } diff --git a/testing/node-driver/src/main/kotlin/net/corda/testing/node/network/NetworkMapServer.kt b/testing/node-driver/src/main/kotlin/net/corda/testing/node/network/NetworkMapServer.kt index 1e3df5b434..d6814722cf 100644 --- a/testing/node-driver/src/main/kotlin/net/corda/testing/node/network/NetworkMapServer.kt +++ b/testing/node-driver/src/main/kotlin/net/corda/testing/node/network/NetworkMapServer.kt @@ -70,12 +70,11 @@ class NetworkMapServer(cacheTimeout: Duration, register(service) additionalServices.forEach { register(it) } } - val jerseyServlet = ServletHolder(ServletContainer(resourceConfig)).apply { initOrder = 0 }// Initialise at server start + val jerseyServlet = ServletHolder(ServletContainer(resourceConfig)).apply { initOrder = 0 } // Initialise at server start addServlet(jerseyServlet, "/*") }) } } - } fun start(): NetworkHostAndPort { diff --git a/testing/test-utils/src/main/kotlin/net/corda/testing/CoreTestUtils.kt b/testing/test-utils/src/main/kotlin/net/corda/testing/CoreTestUtils.kt index df5d85661c..35c0a859ee 100644 --- a/testing/test-utils/src/main/kotlin/net/corda/testing/CoreTestUtils.kt +++ b/testing/test-utils/src/main/kotlin/net/corda/testing/CoreTestUtils.kt @@ -131,7 +131,7 @@ fun configureTestSSL(legalName: CordaX500Name = MEGA_CORP.name): SSLConfiguratio fun getTestPartyAndCertificate(party: Party, trustRoot: CertificateAndKeyPair = DEV_CA): PartyAndCertificate { val certHolder = X509Utilities.createCertificate(CertificateType.IDENTITY, trustRoot.certificate, trustRoot.keyPair, party.name, party.owningKey) - val certPath = X509CertificateFactory().delegate.generateCertPath(listOf(certHolder.cert, trustRoot.certificate.cert)) + val certPath = X509CertificateFactory().generateCertPath(certHolder.cert, trustRoot.certificate.cert) return PartyAndCertificate(certPath) } From 4a677815efc2930218621c06a569b04f25294c3e Mon Sep 17 00:00:00 2001 From: Shams Asari Date: Mon, 4 Dec 2017 15:39:42 +0000 Subject: [PATCH 20/23] Removing NetworkParametersGenerator as an interface --- constants.properties | 2 +- .../cordform/NetworkParametersGenerator.java | 15 ---- .../main/kotlin/net/corda/plugins/Cordform.kt | 17 +++-- ...rator.kt => NetworkParametersGenerator.kt} | 72 +++++++++---------- 4 files changed, 42 insertions(+), 64 deletions(-) delete mode 100644 gradle-plugins/cordform-common/src/main/java/net/corda/cordform/NetworkParametersGenerator.java rename node-api/src/main/kotlin/net/corda/nodeapi/internal/{TestNetworkParametersGenerator.kt => NetworkParametersGenerator.kt} (58%) diff --git a/constants.properties b/constants.properties index 3b0e9df855..5782e87e76 100644 --- a/constants.properties +++ b/constants.properties @@ -1,4 +1,4 @@ -gradlePluginsVersion=3.0.1-NETWORKMAP +gradlePluginsVersion=3.0.2-NETWORKMAP kotlinVersion=1.1.60 platformVersion=2 guavaVersion=21.0 diff --git a/gradle-plugins/cordform-common/src/main/java/net/corda/cordform/NetworkParametersGenerator.java b/gradle-plugins/cordform-common/src/main/java/net/corda/cordform/NetworkParametersGenerator.java deleted file mode 100644 index 702e154ed3..0000000000 --- a/gradle-plugins/cordform-common/src/main/java/net/corda/cordform/NetworkParametersGenerator.java +++ /dev/null @@ -1,15 +0,0 @@ -package net.corda.cordform; - -import java.nio.file.Path; -import java.util.List; - -public interface NetworkParametersGenerator { - /** - * Run generation of network parameters for [Cordformation]. Nodes need to have already their own [NodeInfo] files in their - * base directories, these files will be used to extract notary identities. - * - * @param nodesDirs - nodes directories that will be used for network parameters generation. Network parameters - * file will be dropped into each directory on this list. - */ - void run(List nodesDirs); -} \ No newline at end of file diff --git a/gradle-plugins/cordformation/src/main/kotlin/net/corda/plugins/Cordform.kt b/gradle-plugins/cordformation/src/main/kotlin/net/corda/plugins/Cordform.kt index 3058b052c9..cffc411f7f 100644 --- a/gradle-plugins/cordformation/src/main/kotlin/net/corda/plugins/Cordform.kt +++ b/gradle-plugins/cordformation/src/main/kotlin/net/corda/plugins/Cordform.kt @@ -3,7 +3,6 @@ package net.corda.plugins import groovy.lang.Closure import net.corda.cordform.CordformDefinition import net.corda.cordform.CordformNode -import net.corda.cordform.NetworkParametersGenerator import org.apache.tools.ant.filters.FixCrLfFilter import org.gradle.api.DefaultTask import org.gradle.api.GradleException @@ -33,7 +32,6 @@ open class Cordform : DefaultTask() { */ @Suppress("MemberVisibilityCanPrivate") var definitionClass: String? = null - private val networkParametersGenClass: String = "net.corda.nodeapi.internal.TestNetworkParametersGenerator" private var directory = defaultDirectory private val nodes = mutableListOf() @@ -122,14 +120,11 @@ open class Cordform : DefaultTask() { /** * The parametersGenerator needn't be compiled until just before our build method, so we load it manually via sourceSets.main.runtimeClasspath. */ - private fun loadParametersGenerator(): NetworkParametersGenerator { + private fun loadNetworkParamsGenClass(): Class<*> { val plugin = project.convention.getPlugin(JavaPluginConvention::class.java) val classpath = plugin.sourceSets.getByName(MAIN_SOURCE_SET_NAME).runtimeClasspath val urls = classpath.files.map { it.toURI().toURL() }.toTypedArray() - return URLClassLoader(urls, NetworkParametersGenerator::class.java.classLoader) - .loadClass(networkParametersGenClass) - .asSubclass(NetworkParametersGenerator::class.java) - .newInstance() + return URLClassLoader(urls, javaClass.classLoader).loadClass("net.corda.nodeapi.internal.NetworkParametersGenerator") } /** @@ -171,8 +166,12 @@ open class Cordform : DefaultTask() { private fun generateAndInstallNetworkParameters() { project.logger.info("Generating and installing network parameters") - val networkParamsGenerator = loadParametersGenerator() - networkParamsGenerator.run(nodes.map { it.fullPath() }) + val networkParamsGenClass = loadNetworkParamsGenClass() + val nodeDirs = nodes.map(Node::fullPath) + val networkParamsGenObject = networkParamsGenClass.newInstance() + val runMethod = networkParamsGenClass.getMethod("run", List::class.java).apply { isAccessible = true } + // Call NetworkParametersGenerator.run + runMethod.invoke(networkParamsGenObject, nodeDirs) } private fun CordformDefinition.getMatchingCordapps(): List { diff --git a/node-api/src/main/kotlin/net/corda/nodeapi/internal/TestNetworkParametersGenerator.kt b/node-api/src/main/kotlin/net/corda/nodeapi/internal/NetworkParametersGenerator.kt similarity index 58% rename from node-api/src/main/kotlin/net/corda/nodeapi/internal/TestNetworkParametersGenerator.kt rename to node-api/src/main/kotlin/net/corda/nodeapi/internal/NetworkParametersGenerator.kt index fe54652c14..185e8bda8f 100644 --- a/node-api/src/main/kotlin/net/corda/nodeapi/internal/TestNetworkParametersGenerator.kt +++ b/node-api/src/main/kotlin/net/corda/nodeapi/internal/NetworkParametersGenerator.kt @@ -1,10 +1,8 @@ package net.corda.nodeapi.internal import com.typesafe.config.ConfigFactory -import net.corda.cordform.CordformNode -import net.corda.cordform.NetworkParametersGenerator import net.corda.core.crypto.SignedData -import net.corda.core.identity.CordaX500Name +import net.corda.core.identity.Party import net.corda.core.internal.div import net.corda.core.internal.list import net.corda.core.internal.readAll @@ -16,26 +14,30 @@ import net.corda.core.serialization.internal._contextSerializationEnv import net.corda.core.utilities.ByteSequence import net.corda.core.utilities.contextLogger import net.corda.core.utilities.days -import net.corda.nodeapi.internal.serialization.* +import net.corda.nodeapi.internal.serialization.AMQP_P2P_CONTEXT +import net.corda.nodeapi.internal.serialization.KRYO_P2P_CONTEXT +import net.corda.nodeapi.internal.serialization.SerializationFactoryImpl import net.corda.nodeapi.internal.serialization.amqp.AMQPServerSerializationScheme import net.corda.nodeapi.internal.serialization.kryo.AbstractKryoSerializationScheme import net.corda.nodeapi.internal.serialization.kryo.KryoHeaderV0_1 import java.nio.file.Path import java.time.Instant -import kotlin.streams.toList -// This class is used by deployNodes task to generate NetworkParameters in [Cordformation]. +/** + * This class is loaded by Cordform using reflection to generate the network parameters. It is assumed that Cordform has + * already asked each node to generate its node info file. + */ @Suppress("UNUSED") -class TestNetworkParametersGenerator : NetworkParametersGenerator { +class NetworkParametersGenerator { companion object { private val logger = contextLogger() } - override fun run(nodesDirs: List) { + fun run(nodesDirs: List) { logger.info("NetworkParameters generation using node directories: $nodesDirs") try { initialiseSerialization() - val notaryInfos = loadAndGatherNotaryIdentities(nodesDirs) + val notaryInfos = gatherNotaryIdentities(nodesDirs) val copier = NetworkParametersCopier(NetworkParameters( minimumPlatformVersion = 1, notaries = notaryInfos, @@ -45,40 +47,34 @@ class TestNetworkParametersGenerator : NetworkParametersGenerator { maxTransactionSize = 40000, epoch = 1 )) - nodesDirs.forEach { copier.install(it) } + nodesDirs.forEach(copier::install) } finally { _contextSerializationEnv.set(null) } } - private fun loadAndGatherNotaryIdentities(nodesDirs: List): List { - val infos = getAllNodeInfos(nodesDirs) - val configs = nodesDirs.map { ConfigFactory.parseFile((it / "node.conf").toFile()) } - val notaryConfigs = configs.filter { it.hasPath("notary") } - val notaries = notaryConfigs.associateBy( - { CordaX500Name.parse(it.getString("myLegalName")) }, - { it.getConfig("notary").getBoolean("validating") } - ) - // Now get the notary identities based on names passed from configs. There is one problem, for distributed notaries - // in config we specify only node's main name, the notary identity isn't passed there. It's read from keystore on - // node startup, so we have to look it up from node info as a second identity, which is ugly. - return infos.mapNotNull { - info -> notaries[info.legalIdentities[0].name]?.let { NotaryInfo(info.notaryIdentity(), it) } - }.distinct() + private fun gatherNotaryIdentities(nodesDirs: List): List { + return nodesDirs.mapNotNull { nodeDir -> + val nodeConfig = ConfigFactory.parseFile((nodeDir / "node.conf").toFile()) + if (nodeConfig.hasPath("notary")) { + val validating = nodeConfig.getConfig("notary").getBoolean("validating") + val nodeInfoFile = nodeDir.list { paths -> paths.filter { it.fileName.toString().startsWith("nodeInfo-") }.findFirst().get() } + processFile(nodeInfoFile)?.let { NotaryInfo(it.notaryIdentity(), validating) } + } else { + null + } + }.distinct() // We need distinct as nodes part of a distributed notary share the same notary identity } - /** - * Loads latest NodeInfo files stored in node's base directory. - * Scans main directory and [CordformNode.NODE_INFO_DIRECTORY]. - * Signatures are checked before returning a value. The latest value stored for a given name is returned. - * - * @return list of latest [NodeInfo]s - */ - private fun getAllNodeInfos(nodesDirs: List): List { - val nodeInfoFiles = nodesDirs.map { dir -> - dir.list { it.filter { "nodeInfo-" in it.toString() }.findFirst().get() } + private fun NodeInfo.notaryIdentity(): Party { + return when (legalIdentities.size) { + // Single node notaries have just one identity like all other nodes. This identity is the notary identity + 1 -> legalIdentities[0] + // Nodes which are part of a distributed notary have a second identity which is the composite identity of the + // cluster and is shared by all the other members. This is the notary identity. + 2 -> legalIdentities[1] + else -> throw IllegalArgumentException("Not sure how to get the notary identity in this scenerio: $this") } - return nodeInfoFiles.mapNotNull { processFile(it) } } private fun processFile(file: Path): NodeInfo? { @@ -92,8 +88,6 @@ class TestNetworkParametersGenerator : NetworkParametersGenerator { } } - private fun NodeInfo.notaryIdentity() = if (legalIdentities.size == 2) legalIdentities[1] else legalIdentities[0] - // We need to to set serialization env, because generation of parameters is run from Cordform. // KryoServerSerializationScheme is not accessible from nodeapi. private fun initialiseSerialization() { @@ -103,14 +97,14 @@ class TestNetworkParametersGenerator : NetworkParametersGenerator { registerScheme(KryoParametersSerializationScheme) registerScheme(AMQPServerSerializationScheme()) }, - context)) + context) + ) } private object KryoParametersSerializationScheme : AbstractKryoSerializationScheme() { override fun canDeserializeVersion(byteSequence: ByteSequence, target: SerializationContext.UseCase): Boolean { return byteSequence == KryoHeaderV0_1 && target == SerializationContext.UseCase.P2P } - override fun rpcClientKryoPool(context: SerializationContext) = throw UnsupportedOperationException() override fun rpcServerKryoPool(context: SerializationContext) = throw UnsupportedOperationException() } From db9eb8a63f148c9ce528db294ea14d505e366ff8 Mon Sep 17 00:00:00 2001 From: Shams Asari Date: Fri, 8 Dec 2017 11:46:10 +0000 Subject: [PATCH 21/23] Preliminary work to make merge with master manageable --- .../net/corda/client/rpc/RPCStabilityTests.kt | 5 +- .../node/services/AttachmentLoadingTests.kt | 6 +- .../services/messaging/P2PMessagingTest.kt | 6 +- .../net/corda/test/spring/SpringDriver.kt | 7 +- .../kotlin/net/corda/testing/driver/Driver.kt | 806 +----------------- .../net/corda/testing/driver/DriverDSL.kt | 90 ++ .../corda/testing/internal/DriverDSLImpl.kt | 696 +++++++++++++++ .../net/corda/testing/internal/RPCDriver.kt | 6 +- .../net/corda/verifier/VerifierDriver.kt | 9 +- 9 files changed, 842 insertions(+), 789 deletions(-) create mode 100644 testing/node-driver/src/main/kotlin/net/corda/testing/driver/DriverDSL.kt create mode 100644 testing/node-driver/src/main/kotlin/net/corda/testing/internal/DriverDSLImpl.kt diff --git a/client/rpc/src/integration-test/kotlin/net/corda/client/rpc/RPCStabilityTests.kt b/client/rpc/src/integration-test/kotlin/net/corda/client/rpc/RPCStabilityTests.kt index 811d77a46e..942c5f491f 100644 --- a/client/rpc/src/integration-test/kotlin/net/corda/client/rpc/RPCStabilityTests.kt +++ b/client/rpc/src/integration-test/kotlin/net/corda/client/rpc/RPCStabilityTests.kt @@ -25,7 +25,10 @@ import rx.Observable import rx.subjects.PublishSubject import rx.subjects.UnicastSubject import java.time.Duration -import java.util.concurrent.* +import java.util.concurrent.ConcurrentLinkedQueue +import java.util.concurrent.Executors +import java.util.concurrent.ScheduledExecutorService +import java.util.concurrent.TimeUnit import java.util.concurrent.atomic.AtomicInteger class RPCStabilityTests { diff --git a/node/src/integration-test/kotlin/net/corda/node/services/AttachmentLoadingTests.kt b/node/src/integration-test/kotlin/net/corda/node/services/AttachmentLoadingTests.kt index 4a2a894154..7c464ce6e0 100644 --- a/node/src/integration-test/kotlin/net/corda/node/services/AttachmentLoadingTests.kt +++ b/node/src/integration-test/kotlin/net/corda/node/services/AttachmentLoadingTests.kt @@ -20,7 +20,7 @@ import net.corda.node.internal.cordapp.CordappProviderImpl import net.corda.testing.* import net.corda.testing.DUMMY_BANK_A import net.corda.testing.DUMMY_NOTARY -import net.corda.testing.driver.DriverDSLExposedInterface +import net.corda.testing.driver.DriverDSL import net.corda.testing.driver.NodeHandle import net.corda.testing.driver.driver import net.corda.testing.node.MockServices @@ -51,14 +51,14 @@ class AttachmentLoadingTests { Class.forName("net.corda.finance.contracts.isolated.IsolatedDummyFlow\$Initiator", true, URLClassLoader(arrayOf(isolatedJAR))) .asSubclass(FlowLogic::class.java) - private fun DriverDSLExposedInterface.createTwoNodes(): List { + private fun DriverDSL.createTwoNodes(): List { return listOf( startNode(providedName = bankAName), startNode(providedName = bankBName) ).transpose().getOrThrow() } - private fun DriverDSLExposedInterface.installIsolatedCordappTo(nodeName: CordaX500Name) { + private fun DriverDSL.installIsolatedCordappTo(nodeName: CordaX500Name) { // Copy the app jar to the first node. The second won't have it. val path = (baseDirectory(nodeName.toString()) / "cordapps").createDirectories() / "isolated.jar" logger.info("Installing isolated jar to $path") diff --git a/node/src/integration-test/kotlin/net/corda/services/messaging/P2PMessagingTest.kt b/node/src/integration-test/kotlin/net/corda/services/messaging/P2PMessagingTest.kt index 6d480bdf87..769015bfba 100644 --- a/node/src/integration-test/kotlin/net/corda/services/messaging/P2PMessagingTest.kt +++ b/node/src/integration-test/kotlin/net/corda/services/messaging/P2PMessagingTest.kt @@ -18,7 +18,7 @@ import net.corda.node.services.messaging.* import net.corda.node.services.transactions.RaftValidatingNotaryService import net.corda.testing.ALICE import net.corda.testing.chooseIdentity -import net.corda.testing.driver.DriverDSLExposedInterface +import net.corda.testing.driver.DriverDSL import net.corda.testing.driver.NodeHandle import net.corda.testing.driver.driver import net.corda.testing.node.ClusterSpec @@ -116,13 +116,13 @@ class P2PMessagingTest { } } - private fun startDriverWithDistributedService(dsl: DriverDSLExposedInterface.(List>) -> Unit) { + private fun startDriverWithDistributedService(dsl: DriverDSL.(List>) -> Unit) { driver(startNodesInProcess = true, notarySpecs = listOf(NotarySpec(DISTRIBUTED_SERVICE_NAME, cluster = ClusterSpec.Raft(clusterSize = 2)))) { dsl(defaultNotaryHandle.nodeHandles.getOrThrow().map { (it as NodeHandle.InProcess).node }) } } - private fun DriverDSLExposedInterface.startAlice(): StartedNode { + private fun DriverDSL.startAlice(): StartedNode { return startNode(providedName = ALICE.name, customOverrides = mapOf("messageRedeliveryDelaySeconds" to 1)) .map { (it as NodeHandle.InProcess).node } .getOrThrow() diff --git a/samples/irs-demo/src/integration-test/kotlin/net/corda/test/spring/SpringDriver.kt b/samples/irs-demo/src/integration-test/kotlin/net/corda/test/spring/SpringDriver.kt index 3a09c8200a..7ba13c4fd7 100644 --- a/samples/irs-demo/src/integration-test/kotlin/net/corda/test/spring/SpringDriver.kt +++ b/samples/irs-demo/src/integration-test/kotlin/net/corda/test/spring/SpringDriver.kt @@ -4,6 +4,7 @@ import net.corda.core.concurrent.CordaFuture import net.corda.core.internal.concurrent.map import net.corda.core.utilities.contextLogger import net.corda.testing.driver.* +import net.corda.testing.internal.DriverDSLImpl import net.corda.testing.internal.ProcessUtilities import net.corda.testing.node.NotarySpec import okhttp3.OkHttpClient @@ -14,7 +15,7 @@ import java.nio.file.Path import java.nio.file.Paths import java.util.concurrent.TimeUnit -interface SpringDriverExposedDSLInterface : DriverDSLExposedInterface { +interface SpringDriverExposedDSLInterface : DriverDSL { /** * Starts a Spring Boot application, passes the RPC connection data as parameters the process. @@ -55,11 +56,11 @@ fun springDriver( startNodesInProcess = startNodesInProcess, extraCordappPackagesToScan = extraCordappPackagesToScan, notarySpecs = notarySpecs, - driverDslWrapper = { driverDSL:DriverDSL -> SpringBootDriverDSL(driverDSL) }, + driverDslWrapper = { driverDSL: DriverDSLImpl -> SpringBootDriverDSL(driverDSL) }, coerce = { it }, dsl = dsl ) -data class SpringBootDriverDSL(private val driverDSL: DriverDSL) : DriverDSLInternalInterface by driverDSL, SpringDriverInternalDSLInterface { +data class SpringBootDriverDSL(private val driverDSL: DriverDSLImpl) : DriverDSLInternalInterface by driverDSL, SpringDriverInternalDSLInterface { companion object { private val log = contextLogger() } diff --git a/testing/node-driver/src/main/kotlin/net/corda/testing/driver/Driver.kt b/testing/node-driver/src/main/kotlin/net/corda/testing/driver/Driver.kt index b117dc3d77..d6e4f1fb9d 100644 --- a/testing/node-driver/src/main/kotlin/net/corda/testing/driver/Driver.kt +++ b/testing/node-driver/src/main/kotlin/net/corda/testing/driver/Driver.kt @@ -2,56 +2,31 @@ package net.corda.testing.driver -import com.google.common.collect.HashMultimap -import com.google.common.util.concurrent.ThreadFactoryBuilder import com.typesafe.config.Config -import com.typesafe.config.ConfigFactory import com.typesafe.config.ConfigRenderOptions import net.corda.client.rpc.CordaRPCClient -import net.corda.cordform.CordformContext -import net.corda.cordform.CordformNode import net.corda.core.CordaException import net.corda.core.concurrent.CordaFuture -import net.corda.core.concurrent.firstOf import net.corda.core.identity.CordaX500Name import net.corda.core.identity.Party -import net.corda.core.internal.* -import net.corda.core.internal.concurrent.* +import net.corda.core.internal.concurrent.openFuture +import net.corda.core.internal.copyTo +import net.corda.core.internal.div +import net.corda.core.internal.times import net.corda.core.messaging.CordaRPCOps import net.corda.core.node.NodeInfo -import net.corda.core.node.services.NetworkMapCache -import net.corda.core.node.services.NotaryService -import net.corda.core.toFuture import net.corda.core.utilities.* import net.corda.node.internal.Node -import net.corda.node.internal.NodeStartup import net.corda.node.internal.StartedNode -import net.corda.node.services.Permissions.Companion.invokeRpc -import net.corda.node.services.config.* -import net.corda.node.services.transactions.BFTNonValidatingNotaryService -import net.corda.node.services.transactions.RaftNonValidatingNotaryService -import net.corda.node.services.transactions.RaftValidatingNotaryService -import net.corda.node.utilities.registration.HTTPNetworkRegistrationService -import net.corda.node.utilities.registration.NetworkRegistrationHelper -import net.corda.nodeapi.internal.* +import net.corda.node.services.config.NodeConfiguration +import net.corda.node.services.config.VerifierType +import net.corda.nodeapi.internal.addShutdownHook import net.corda.nodeapi.internal.config.User -import net.corda.nodeapi.internal.config.parseAs -import net.corda.nodeapi.internal.config.toConfig -import net.corda.nodeapi.internal.crypto.X509Utilities -import net.corda.testing.* -import net.corda.testing.common.internal.testNetworkParameters -import net.corda.testing.driver.DriverDSL.ClusterType.* -import net.corda.testing.internal.InProcessNode -import net.corda.testing.internal.ProcessUtilities -import net.corda.testing.node.ClusterSpec -import net.corda.testing.node.MockServices.Companion.MOCK_VERSION_INFO +import net.corda.testing.DUMMY_NOTARY +import net.corda.testing.internal.DriverDSLImpl import net.corda.testing.node.NotarySpec -import okhttp3.OkHttpClient -import okhttp3.Request +import net.corda.testing.setGlobalSerialization import org.slf4j.Logger -import rx.Observable -import rx.observables.ConnectableObservable -import rx.schedulers.Schedulers import java.net.* import java.nio.file.Path import java.nio.file.Paths @@ -61,14 +36,9 @@ import java.time.Duration import java.time.Instant import java.time.ZoneOffset.UTC import java.time.format.DateTimeFormatter -import java.util.* -import java.util.concurrent.Executors import java.util.concurrent.ScheduledExecutorService import java.util.concurrent.TimeUnit.MILLISECONDS -import java.util.concurrent.TimeUnit.SECONDS import java.util.concurrent.atomic.AtomicInteger -import kotlin.collections.ArrayList -import kotlin.concurrent.thread /** * This file defines a small "Driver" DSL for starting up nodes that is only intended for development, demos and tests. @@ -77,115 +47,18 @@ import kotlin.concurrent.thread * * TODO this file is getting way too big, it should be split into several files. */ -private val log: Logger = loggerFor() - -private val DEFAULT_POLL_INTERVAL = 500.millis - -private const val DEFAULT_WARN_COUNT = 120 - -/** - * A sub-set of permissions that grant most of the essential operations used in the unit/integration tests as well as - * in demo application like NodeExplorer. - */ -private val DRIVER_REQUIRED_PERMISSIONS = setOf( - invokeRpc(CordaRPCOps::nodeInfo), - invokeRpc(CordaRPCOps::networkMapFeed), - invokeRpc(CordaRPCOps::networkMapSnapshot), - invokeRpc(CordaRPCOps::notaryIdentities), - invokeRpc(CordaRPCOps::stateMachinesFeed), - invokeRpc(CordaRPCOps::stateMachineRecordedTransactionMappingFeed), - invokeRpc(CordaRPCOps::nodeInfoFromParty), - invokeRpc(CordaRPCOps::internalVerifiedTransactionsFeed), - invokeRpc("vaultQueryBy"), - invokeRpc("vaultTrackBy"), - invokeRpc(CordaRPCOps::registeredFlows) -) +private val log: Logger = loggerFor() /** * Object ecapsulating a notary started automatically by the driver. */ data class NotaryHandle(val identity: Party, val validating: Boolean, val nodeHandles: CordaFuture>) -/** - * This is the interface that's exposed to DSL users. - */ -interface DriverDSLExposedInterface : CordformContext { - /** Returns a list of [NotaryHandle]s matching the list of [NotarySpec]s passed into [driver]. */ - val notaryHandles: List - - /** - * Returns the [NotaryHandle] for the single notary on the network. Throws if there are none or more than one. - * @see notaryHandles - */ - val defaultNotaryHandle: NotaryHandle - get() { - return when (notaryHandles.size) { - 0 -> throw IllegalStateException("There are no notaries defined on the network") - 1 -> notaryHandles[0] - else -> throw IllegalStateException("There is more than one notary defined on the network") - } - } - - /** - * Returns the identity of the single notary on the network. Throws if there are none or more than one. - * @see defaultNotaryHandle - */ - val defaultNotaryIdentity: Party get() = defaultNotaryHandle.identity - - /** - * Returns a [CordaFuture] on the [NodeHandle] for the single-node notary on the network. Throws if there - * are no notaries or more than one, or if the notary is a distributed cluster. - * @see defaultNotaryHandle - * @see notaryHandles - */ - val defaultNotaryNode: CordaFuture - get() { - return defaultNotaryHandle.nodeHandles.map { - it.singleOrNull() ?: throw IllegalStateException("Default notary is not a single node") - } - } - - /** - * Start a node. - * - * @param defaultParameters The default parameters for the node. Allows the node to be configured in builder style - * when called from Java code. - * @param providedName Optional name of the node, which will be its legal name in [Party]. Defaults to something - * random. Note that this must be unique as the driver uses it as a primary key! - * @param verifierType The type of transaction verifier to use. See: [VerifierType] - * @param rpcUsers List of users who are authorised to use the RPC system. Defaults to empty list. - * @param startInSameProcess Determines if the node should be started inside the same process the Driver is running - * in. If null the Driver-level value will be used. - * @return A [CordaFuture] on the [NodeHandle] to the node. The future will complete when the node is available. - */ - fun startNode( - defaultParameters: NodeParameters = NodeParameters(), - providedName: CordaX500Name? = defaultParameters.providedName, - rpcUsers: List = defaultParameters.rpcUsers, - verifierType: VerifierType = defaultParameters.verifierType, - customOverrides: Map = defaultParameters.customOverrides, - startInSameProcess: Boolean? = defaultParameters.startInSameProcess, - maximumHeapSize: String = defaultParameters.maximumHeapSize - ): CordaFuture - - - /** - * Helper function for starting a [Node] with custom parameters from Java. - * - * @param parameters The default parameters for the driver. - * @return [NodeHandle] that will be available sometime in the future. - */ - fun startNode(parameters: NodeParameters): CordaFuture = startNode(defaultParameters = parameters) - - /** Call [startWebserver] with a default maximumHeapSize. */ - fun startWebserver(handle: NodeHandle): CordaFuture = startWebserver(handle, "200m") - - /** - * Starts a web server for a node - * @param handle The handle for the node that this webserver connects to via RPC. - * @param maximumHeapSize Argument for JVM -Xmx option e.g. "200m". - */ - fun startWebserver(handle: NodeHandle, maximumHeapSize: String): CordaFuture +interface DriverDSLInternalInterface : DriverDSL { + private companion object { + private val DEFAULT_POLL_INTERVAL = 500.millis + private const val DEFAULT_WARN_COUNT = 120 + } /** * Polls a function until it returns a non-null value. Note that there is no timeout on the polling. @@ -206,11 +79,8 @@ interface DriverDSLExposedInterface : CordformContext { return pollUntilNonNull(pollName, pollInterval, warnCount) { if (check()) Unit else null } } - val shutdownManager: ShutdownManager -} - -interface DriverDSLInternalInterface : DriverDSLExposedInterface { fun start() + fun shutdown() } @@ -320,7 +190,7 @@ data class NodeParameters( * (...) * } * - * Note that [DriverDSL.startNode] does not wait for the node to start up synchronously, but rather returns a [CordaFuture] + * Note that [DriverDSLImpl.startNode] does not wait for the node to start up synchronously, but rather returns a [CordaFuture] * of the [NodeInfo] that may be waited on, which completes when the new node registered with the network map service or * loaded node data from database. * @@ -329,15 +199,15 @@ data class NodeParameters( * @param isDebug Indicates whether the spawned nodes should start in jdwt debug mode and have debug level logging. * @param driverDirectory The base directory node directories go into, defaults to "build//". The node * directories themselves are "//", where legalName defaults to "-" - * and may be specified in [DriverDSL.startNode]. + * and may be specified in [DriverDSLImpl.startNode]. * @param portAllocation The port allocation strategy to use for the messaging and the web server addresses. Defaults to incremental. * @param debugPortAllocation The port allocation strategy to use for jvm debugging. Defaults to incremental. * @param systemProperties A Map of extra system properties which will be given to each new node. Defaults to empty. * @param useTestClock If true the test clock will be used in Node. * @param startNodesInProcess Provides the default behaviour of whether new nodes should start inside this process or - * not. Note that this may be overridden in [DriverDSLExposedInterface.startNode]. + * not. Note that this may be overridden in [DriverDSL.startNode]. * @param notarySpecs The notaries advertised for this network. These nodes will be started automatically and will be - * available from [DriverDSLExposedInterface.notaryHandles]. Defaults to a simple validating notary. + * available from [DriverDSL.notaryHandles]. Defaults to a simple validating notary. * @param dsl The dsl itself. * @return The value returned in the [dsl] closure. */ @@ -354,10 +224,10 @@ fun driver( waitForAllNodesToFinish: Boolean = defaultParameters.waitForNodesToFinish, notarySpecs: List = defaultParameters.notarySpecs, extraCordappPackagesToScan: List = defaultParameters.extraCordappPackagesToScan, - dsl: DriverDSLExposedInterface.() -> A + dsl: DriverDSL.() -> A ): A { return genericDriver( - driverDsl = DriverDSL( + driverDsl = DriverDSLImpl( portAllocation = portAllocation, debugPortAllocation = debugPortAllocation, systemProperties = systemProperties, @@ -398,10 +268,10 @@ fun internalDriver( notarySpecs: List = DriverParameters().notarySpecs, extraCordappPackagesToScan: List = DriverParameters().extraCordappPackagesToScan, compatibilityZone: CompatibilityZoneParams? = null, - dsl: DriverDSL.() -> A + dsl: DriverDSLImpl.() -> A ): A { return genericDriver( - driverDsl = DriverDSL( + driverDsl = DriverDSLImpl( portAllocation = portAllocation, debugPortAllocation = debugPortAllocation, systemProperties = systemProperties, @@ -429,7 +299,7 @@ fun internalDriver( */ fun driver( parameters: DriverParameters, - dsl: DriverDSLExposedInterface.() -> A + dsl: DriverDSL.() -> A ): A { return driver(defaultParameters = parameters, dsl = dsl) } @@ -464,13 +334,13 @@ data class DriverParameters( /** * This is a helper method to allow extending of the DSL, along the lines of - * interface SomeOtherExposedDSLInterface : DriverDSLExposedInterface + * interface SomeOtherExposedDSLInterface : DriverDSL * interface SomeOtherInternalDSLInterface : DriverDSLInternalInterface, SomeOtherExposedDSLInterface - * class SomeOtherDSL(val driverDSL : DriverDSL) : DriverDSLInternalInterface by driverDSL, SomeOtherInternalDSLInterface + * class SomeOtherDSL(val driverDSL : DriverDSLImpl) : DriverDSLInternalInterface by driverDSL, SomeOtherInternalDSLInterface * * @param coerce We need this explicit coercion witness because we can't put an extra DI : D bound in a `where` clause. */ -fun genericDriver( +fun genericDriver( driverDsl: D, initialiseSerialization: Boolean = true, coerce: (D) -> DI, @@ -493,13 +363,13 @@ fun genericD /** * This is a helper method to allow extending of the DSL, along the lines of - * interface SomeOtherExposedDSLInterface : DriverDSLExposedInterface + * interface SomeOtherExposedDSLInterface : DriverDSL * interface SomeOtherInternalDSLInterface : DriverDSLInternalInterface, SomeOtherExposedDSLInterface - * class SomeOtherDSL(val driverDSL : DriverDSL) : DriverDSLInternalInterface by driverDSL, SomeOtherInternalDSLInterface + * class SomeOtherDSL(val driverDSL : DriverDSLImpl) : DriverDSLInternalInterface by driverDSL, SomeOtherInternalDSLInterface * * @param coerce We need this explicit coercion witness because we can't put an extra DI : D bound in a `where` clause. */ -fun genericDriver( +fun genericDriver( defaultParameters: DriverParameters = DriverParameters(), isDebug: Boolean = defaultParameters.isDebug, driverDirectory: Path = defaultParameters.driverDirectory, @@ -512,13 +382,13 @@ fun genericD startNodesInProcess: Boolean = defaultParameters.startNodesInProcess, notarySpecs: List, extraCordappPackagesToScan: List = defaultParameters.extraCordappPackagesToScan, - driverDslWrapper: (DriverDSL) -> D, + driverDslWrapper: (DriverDSLImpl) -> D, coerce: (D) -> DI, dsl: DI.() -> A ): A { val serializationEnv = setGlobalSerialization(initialiseSerialization) val driverDsl = driverDslWrapper( - DriverDSL( + DriverDSLImpl( portAllocation = portAllocation, debugPortAllocation = debugPortAllocation, systemProperties = systemProperties, @@ -625,614 +495,6 @@ fun poll( return resultFuture } -class DriverDSL( - val portAllocation: PortAllocation, - val debugPortAllocation: PortAllocation, - val systemProperties: Map, - val driverDirectory: Path, - val useTestClock: Boolean, - val isDebug: Boolean, - val startNodesInProcess: Boolean, - val waitForNodesToFinish: Boolean, - extraCordappPackagesToScan: List, - val notarySpecs: List, - val compatibilityZone: CompatibilityZoneParams? -) : DriverDSLInternalInterface { - private var _executorService: ScheduledExecutorService? = null - val executorService get() = _executorService!! - private var _shutdownManager: ShutdownManager? = null - override val shutdownManager get() = _shutdownManager!! - private val cordappPackages = extraCordappPackagesToScan + getCallerPackage() - // TODO: this object will copy NodeInfo files from started nodes to other nodes additional-node-infos/ - // This uses the FileSystem and adds a delay (~5 seconds) given by the time we wait before polling the file system. - // Investigate whether we can avoid that. - // TODO: NodeInfoFilesCopier create observable threads in the init method, we should move that to a start method instead, changing this to lateinit instead to prevent that. - private lateinit var nodeInfoFilesCopier: NodeInfoFilesCopier - // Map from a nodes legal name to an observable emitting the number of nodes in its network map. - private val countObservables = mutableMapOf>() - private lateinit var _notaries: List - override val notaryHandles: List get() = _notaries - private var networkParameters: NetworkParametersCopier? = null - - class State { - val processes = ArrayList() - } - - private val state = ThreadBox(State()) - - //TODO: remove this once we can bundle quasar properly. - private val quasarJarPath: String by lazy { - val cl = ClassLoader.getSystemClassLoader() - val urls = (cl as URLClassLoader).urLs - val quasarPattern = ".*quasar.*\\.jar$".toRegex() - val quasarFileUrl = urls.first { quasarPattern.matches(it.path) } - Paths.get(quasarFileUrl.toURI()).toString() - } - - override fun shutdown() { - if (waitForNodesToFinish) { - state.locked { - processes.forEach { it.waitFor() } - } - } - _shutdownManager?.shutdown() - _executorService?.shutdownNow() - } - - private fun establishRpc(config: NodeConfiguration, processDeathFuture: CordaFuture): CordaFuture { - val rpcAddress = config.rpcAddress!! - val client = CordaRPCClient(rpcAddress) - val connectionFuture = poll(executorService, "RPC connection") { - try { - client.start(config.rpcUsers[0].username, config.rpcUsers[0].password) - } catch (e: Exception) { - if (processDeathFuture.isDone) throw e - log.error("Exception $e, Retrying RPC connection at $rpcAddress") - null - } - } - return firstOf(connectionFuture, processDeathFuture) { - if (it == processDeathFuture) { - throw ListenProcessDeathException(rpcAddress, processDeathFuture.getOrThrow()) - } - val connection = connectionFuture.getOrThrow() - shutdownManager.registerShutdown(connection::close) - connection.proxy - } - } - - override fun startNode( - defaultParameters: NodeParameters, - providedName: CordaX500Name?, - rpcUsers: List, - verifierType: VerifierType, - customOverrides: Map, - startInSameProcess: Boolean?, - maximumHeapSize: String - ): CordaFuture { - val p2pAddress = portAllocation.nextHostAndPort() - // TODO: Derive name from the full picked name, don't just wrap the common name - val name = providedName ?: CordaX500Name(organisation = "${oneOf(names).organisation}-${p2pAddress.port}", locality = "London", country = "GB") - - val registrationFuture = if (compatibilityZone?.rootCert != null) { - nodeRegistration(name, compatibilityZone.rootCert, compatibilityZone.url) - } else { - doneFuture(Unit) - } - - return registrationFuture.flatMap { - val rpcAddress = portAllocation.nextHostAndPort() - val webAddress = portAllocation.nextHostAndPort() - val users = rpcUsers.map { it.copy(permissions = it.permissions + DRIVER_REQUIRED_PERMISSIONS) } - val configMap = configOf( - "myLegalName" to name.toString(), - "p2pAddress" to p2pAddress.toString(), - "rpcAddress" to rpcAddress.toString(), - "webAddress" to webAddress.toString(), - "useTestClock" to useTestClock, - "rpcUsers" to if (users.isEmpty()) defaultRpcUserList else users.map { it.toConfig().root().unwrapped() }, - "verifierType" to verifierType.name - ) + customOverrides - val config = ConfigHelper.loadConfig( - baseDirectory = baseDirectory(name), - allowMissingConfig = true, - configOverrides = if (compatibilityZone != null) { - configMap + mapOf("compatibilityZoneURL" to compatibilityZone.url.toString()) - } else { - configMap - } - ) - startNodeInternal(config, webAddress, startInSameProcess, maximumHeapSize) - } - } - - private fun nodeRegistration(providedName: CordaX500Name, rootCert: X509Certificate, compatibilityZoneURL: URL): CordaFuture { - val baseDirectory = baseDirectory(providedName).createDirectories() - val config = ConfigHelper.loadConfig( - baseDirectory = baseDirectory, - allowMissingConfig = true, - configOverrides = configOf( - "p2pAddress" to "localhost:1222", // required argument, not really used - "compatibilityZoneURL" to compatibilityZoneURL.toString(), - "myLegalName" to providedName.toString()) - ) - val configuration = config.parseAsNodeConfiguration() - - configuration.rootCertFile.parent.createDirectories() - X509Utilities.saveCertificateAsPEMFile(rootCert, configuration.rootCertFile) - - return if (startNodesInProcess) { - // This is a bit cheating, we're not starting a full node, we're just calling the code nodes call - // when registering. - NetworkRegistrationHelper(configuration, HTTPNetworkRegistrationService(compatibilityZoneURL)).buildKeystore() - doneFuture(Unit) - } else { - startOutOfProcessNodeRegistration(config, configuration) - } - } - - private enum class ClusterType(val validating: Boolean, val clusterName: CordaX500Name) { - VALIDATING_RAFT(true, CordaX500Name(RaftValidatingNotaryService.id, "Raft", "Zurich", "CH")), - NON_VALIDATING_RAFT(false, CordaX500Name(RaftNonValidatingNotaryService.id, "Raft", "Zurich", "CH")), - NON_VALIDATING_BFT(false, CordaX500Name(BFTNonValidatingNotaryService.id, "BFT", "Zurich", "CH")) - } - - internal fun startCordformNodes(cordforms: List): CordaFuture<*> { - val clusterNodes = HashMultimap.create() - val notaryInfos = ArrayList() - - // Go though the node definitions and pick out the notaries so that we can generate their identities to be used - // in the network parameters - for (cordform in cordforms) { - if (cordform.notary == null) continue - val name = CordaX500Name.parse(cordform.name) - val notaryConfig = ConfigFactory.parseMap(cordform.notary).parseAs() - // We need to first group the nodes that form part of a cluser. We assume for simplicity that nodes of the - // same cluster type and validating flag are part of the same cluster. - if (notaryConfig.raft != null) { - val key = if (notaryConfig.validating) VALIDATING_RAFT else NON_VALIDATING_RAFT - clusterNodes.put(key, name) - } else if (notaryConfig.bftSMaRt != null) { - clusterNodes.put(NON_VALIDATING_BFT, name) - } else { - // We have all we need here to generate the identity for single node notaries - val identity = ServiceIdentityGenerator.generateToDisk( - dirs = listOf(baseDirectory(name)), - serviceName = name, - serviceId = "identity" - ) - notaryInfos += NotaryInfo(identity, notaryConfig.validating) - } - } - - clusterNodes.asMap().forEach { type, nodeNames -> - val identity = ServiceIdentityGenerator.generateToDisk( - dirs = nodeNames.map { baseDirectory(it) }, - serviceName = type.clusterName, - serviceId = NotaryService.constructId( - validating = type.validating, - raft = type in setOf(VALIDATING_RAFT, NON_VALIDATING_RAFT), - bft = type == NON_VALIDATING_BFT - ) - ) - notaryInfos += NotaryInfo(identity, type.validating) - } - - networkParameters = NetworkParametersCopier(testNetworkParameters(notaryInfos)) - - return cordforms.map { - val startedNode = startCordformNode(it) - if (it.webAddress != null) { - // Start a webserver if an address for it was specified - startedNode.flatMap { startWebserver(it) } - } else { - startedNode - } - }.transpose() - } - - private fun startCordformNode(cordform: CordformNode): CordaFuture { - val name = CordaX500Name.parse(cordform.name) - // TODO We shouldn't have to allocate an RPC or web address if they're not specified. We're having to do this because of startNodeInternal - val rpcAddress = if (cordform.rpcAddress == null) mapOf("rpcAddress" to portAllocation.nextHostAndPort().toString()) else emptyMap() - val webAddress = cordform.webAddress?.let { NetworkHostAndPort.parse(it) } ?: portAllocation.nextHostAndPort() - val notary = if (cordform.notary != null) mapOf("notary" to cordform.notary) else emptyMap() - val rpcUsers = cordform.rpcUsers - val config = ConfigHelper.loadConfig( - baseDirectory = baseDirectory(name), - allowMissingConfig = true, - configOverrides = cordform.config + rpcAddress + notary + mapOf( - "rpcUsers" to if (rpcUsers.isEmpty()) defaultRpcUserList else rpcUsers - ) - ) - return startNodeInternal(config, webAddress, null, "200m") - } - - private fun queryWebserver(handle: NodeHandle, process: Process): WebserverHandle { - val protocol = if (handle.configuration.useHTTPS) "https://" else "http://" - val url = URL("$protocol${handle.webAddress}/api/status") - val client = OkHttpClient.Builder().connectTimeout(5, SECONDS).readTimeout(60, SECONDS).build() - - while (process.isAlive) try { - val response = client.newCall(Request.Builder().url(url).build()).execute() - if (response.isSuccessful && (response.body().string() == "started")) { - return WebserverHandle(handle.webAddress, process) - } - } catch (e: ConnectException) { - log.debug("Retrying webserver info at ${handle.webAddress}") - } - - throw IllegalStateException("Webserver at ${handle.webAddress} has died") - } - - override fun startWebserver(handle: NodeHandle, maximumHeapSize: String): CordaFuture { - val debugPort = if (isDebug) debugPortAllocation.nextPort() else null - val process = DriverDSL.startWebserver(handle, debugPort, maximumHeapSize) - shutdownManager.registerProcessShutdown(process) - val webReadyFuture = addressMustBeBoundFuture(executorService, handle.webAddress, process) - return webReadyFuture.map { queryWebserver(handle, process) } - } - - override fun start() { - if (startNodesInProcess) { - Schedulers.reset() - } - _executorService = Executors.newScheduledThreadPool(2, ThreadFactoryBuilder().setNameFormat("driver-pool-thread-%d").build()) - _shutdownManager = ShutdownManager(executorService) - - nodeInfoFilesCopier = NodeInfoFilesCopier() - shutdownManager.registerShutdown { nodeInfoFilesCopier.close() } - - val notaryInfos = generateNotaryIdentities() - // The network parameters must be serialised before starting any of the nodes - networkParameters = NetworkParametersCopier(testNetworkParameters(notaryInfos)) - val nodeHandles = startNotaries() - _notaries = notaryInfos.zip(nodeHandles) { (identity, validating), nodes -> NotaryHandle(identity, validating, nodes) } - } - - private fun generateNotaryIdentities(): List { - return notarySpecs.map { spec -> - val identity = if (spec.cluster == null) { - ServiceIdentityGenerator.generateToDisk( - dirs = listOf(baseDirectory(spec.name)), - serviceName = spec.name, - serviceId = "identity", - customRootCert = compatibilityZone?.rootCert - ) - } else { - ServiceIdentityGenerator.generateToDisk( - dirs = generateNodeNames(spec).map { baseDirectory(it) }, - serviceName = spec.name, - serviceId = NotaryService.constructId( - validating = spec.validating, - raft = spec.cluster is ClusterSpec.Raft - ), - customRootCert = compatibilityZone?.rootCert - ) - } - NotaryInfo(identity, spec.validating) - } - } - - private fun generateNodeNames(spec: NotarySpec): List { - return (0 until spec.cluster!!.clusterSize).map { spec.name.copy(organisation = "${spec.name.organisation}-$it") } - } - - private fun startNotaries(): List>> { - return notarySpecs.map { - when { - it.cluster == null -> startSingleNotary(it) - it.cluster is ClusterSpec.Raft -> startRaftNotaryCluster(it) - else -> throw IllegalArgumentException("BFT-SMaRt not supported") - } - } - } - - // TODO This mapping is done is several places including the gradle plugin. In general we need a better way of - // generating the configs for the nodes, probably making use of Any.toConfig() - private fun NotaryConfig.toConfigMap(): Map = mapOf("notary" to toConfig().root().unwrapped()) - - private fun startSingleNotary(spec: NotarySpec): CordaFuture> { - return startNode( - providedName = spec.name, - rpcUsers = spec.rpcUsers, - verifierType = spec.verifierType, - customOverrides = NotaryConfig(spec.validating).toConfigMap() - ).map { listOf(it) } - } - - private fun startRaftNotaryCluster(spec: NotarySpec): CordaFuture> { - fun notaryConfig(nodeAddress: NetworkHostAndPort, clusterAddress: NetworkHostAndPort? = null): Map { - val clusterAddresses = if (clusterAddress != null) listOf(clusterAddress) else emptyList() - val config = NotaryConfig( - validating = spec.validating, - raft = RaftConfig(nodeAddress = nodeAddress, clusterAddresses = clusterAddresses)) - return config.toConfigMap() - } - - val nodeNames = generateNodeNames(spec) - val clusterAddress = portAllocation.nextHostAndPort() - - // Start the first node that will bootstrap the cluster - val firstNodeFuture = startNode( - providedName = nodeNames[0], - rpcUsers = spec.rpcUsers, - verifierType = spec.verifierType, - customOverrides = notaryConfig(clusterAddress) + mapOf( - "database.serverNameTablePrefix" to nodeNames[0].toString().replace(Regex("[^0-9A-Za-z]+"), "") - ) - ) - - // All other nodes will join the cluster - val restNodeFutures = nodeNames.drop(1).map { - val nodeAddress = portAllocation.nextHostAndPort() - startNode( - providedName = it, - rpcUsers = spec.rpcUsers, - verifierType = spec.verifierType, - customOverrides = notaryConfig(nodeAddress, clusterAddress) + mapOf( - "database.serverNameTablePrefix" to it.toString().replace(Regex("[^0-9A-Za-z]+"), "") - ) - ) - } - - return firstNodeFuture.flatMap { first -> - restNodeFutures.transpose().map { rest -> listOf(first) + rest } - } - } - - fun baseDirectory(nodeName: CordaX500Name): Path { - val nodeDirectoryName = nodeName.organisation.filter { !it.isWhitespace() } - return driverDirectory / nodeDirectoryName - } - - override fun baseDirectory(nodeName: String): Path = baseDirectory(CordaX500Name.parse(nodeName)) - - /** - * @param initial number of nodes currently in the network map of a running node. - * @param networkMapCacheChangeObservable an observable returning the updates to the node network map. - * @return a [ConnectableObservable] which emits a new [Int] every time the number of registered nodes changes - * the initial value emitted is always [initial] - */ - private fun nodeCountObservable(initial: Int, networkMapCacheChangeObservable: Observable): - ConnectableObservable { - val count = AtomicInteger(initial) - return networkMapCacheChangeObservable.map { it -> - when (it) { - is NetworkMapCache.MapChange.Added -> count.incrementAndGet() - is NetworkMapCache.MapChange.Removed -> count.decrementAndGet() - is NetworkMapCache.MapChange.Modified -> count.get() - } - }.startWith(initial).replay() - } - - /** - * @param rpc the [CordaRPCOps] of a newly started node. - * @return a [CordaFuture] which resolves when every node started by driver has in its network map a number of nodes - * equal to the number of running nodes. The future will yield the number of connected nodes. - */ - private fun allNodesConnected(rpc: CordaRPCOps): CordaFuture { - val (snapshot, updates) = rpc.networkMapFeed() - val counterObservable = nodeCountObservable(snapshot.size, updates) - countObservables[rpc.nodeInfo().legalIdentities[0].name] = counterObservable - /* TODO: this might not always be the exact number of nodes one has to wait for, - * for example in the following sequence - * 1 start 3 nodes in order, A, B, C. - * 2 before the future returned by this function resolves, kill B - * At that point this future won't ever resolve as it will wait for nodes to know 3 other nodes. - */ - val requiredNodes = countObservables.size - - // This is an observable which yield the minimum number of nodes in each node network map. - val smallestSeenNetworkMapSize = Observable.combineLatest(countObservables.values.toList()) { args: Array -> - args.map { it as Int }.min() ?: 0 - } - val future = smallestSeenNetworkMapSize.filter { it >= requiredNodes }.toFuture() - counterObservable.connect() - return future - } - - private fun startOutOfProcessNodeRegistration(config: Config, configuration: NodeConfiguration): CordaFuture { - val debugPort = if (isDebug) debugPortAllocation.nextPort() else null - val process = startOutOfProcessNode(configuration, config, quasarJarPath, debugPort, - systemProperties, cordappPackages, "200m", initialRegistration = true) - - return poll(executorService, "node registration (${configuration.myLegalName})") { - if (process.isAlive) null else Unit - } - } - - private fun startNodeInternal(config: Config, - webAddress: NetworkHostAndPort, - startInProcess: Boolean?, - maximumHeapSize: String): CordaFuture { - val configuration = config.parseAsNodeConfiguration() - val baseDirectory = configuration.baseDirectory.createDirectories() - // Distribute node info file using file copier when network map service URL (compatibilityZoneURL) is null. - // TODO: need to implement the same in cordformation? - val nodeInfoFilesCopier = if (compatibilityZone == null) nodeInfoFilesCopier else null - - nodeInfoFilesCopier?.addConfig(baseDirectory) - networkParameters!!.install(baseDirectory) - val onNodeExit: () -> Unit = { - nodeInfoFilesCopier?.removeConfig(baseDirectory) - countObservables.remove(configuration.myLegalName) - } - if (startInProcess ?: startNodesInProcess) { - val nodeAndThreadFuture = startInProcessNode(executorService, configuration, config, cordappPackages) - shutdownManager.registerShutdown( - nodeAndThreadFuture.map { (node, thread) -> - { - node.dispose() - thread.interrupt() - } - } - ) - return nodeAndThreadFuture.flatMap { (node, thread) -> - establishRpc(configuration, openFuture()).flatMap { rpc -> - allNodesConnected(rpc).map { - NodeHandle.InProcess(rpc.nodeInfo(), rpc, configuration, webAddress, node, thread, onNodeExit) - } - } - } - } else { - val debugPort = if (isDebug) debugPortAllocation.nextPort() else null - val process = startOutOfProcessNode(configuration, config, quasarJarPath, debugPort, - systemProperties, cordappPackages, maximumHeapSize, initialRegistration = false) - if (waitForNodesToFinish) { - state.locked { - processes += process - } - } else { - shutdownManager.registerProcessShutdown(process) - } - val p2pReadyFuture = addressMustBeBoundFuture(executorService, configuration.p2pAddress, process) - return p2pReadyFuture.flatMap { - val processDeathFuture = poll(executorService, "process death while waiting for RPC (${configuration.myLegalName})") { - if (process.isAlive) null else process - } - establishRpc(configuration, processDeathFuture).flatMap { rpc -> - // Check for all nodes to have all other nodes in background in case RPC is failing over: - val networkMapFuture = executorService.fork { allNodesConnected(rpc) }.flatMap { it } - firstOf(processDeathFuture, networkMapFuture) { - if (it == processDeathFuture) { - throw ListenProcessDeathException(configuration.p2pAddress, process) - } - processDeathFuture.cancel(false) - log.info("Node handle is ready. NodeInfo: ${rpc.nodeInfo()}, WebAddress: $webAddress") - NodeHandle.OutOfProcess(rpc.nodeInfo(), rpc, configuration, webAddress, debugPort, process, - onNodeExit) - } - } - } - } - } - - override fun pollUntilNonNull(pollName: String, pollInterval: Duration, warnCount: Int, check: () -> A?): CordaFuture { - val pollFuture = poll(executorService, pollName, pollInterval, warnCount, check) - shutdownManager.registerShutdown { pollFuture.cancel(true) } - return pollFuture - } - - companion object { - private val defaultRpcUserList = listOf(User("default", "default", setOf("ALL")).toConfig().root().unwrapped()) - - private val names = arrayOf( - ALICE.name, - BOB.name, - DUMMY_BANK_A.name - ) - - private fun oneOf(array: Array) = array[Random().nextInt(array.size)] - - private fun startInProcessNode( - executorService: ScheduledExecutorService, - nodeConf: NodeConfiguration, - config: Config, - cordappPackages: List - ): CordaFuture, Thread>> { - return executorService.fork { - log.info("Starting in-process Node ${nodeConf.myLegalName.organisation}") - // Write node.conf - writeConfig(nodeConf.baseDirectory, "node.conf", config) - // TODO pass the version in? - val node = InProcessNode(nodeConf, MOCK_VERSION_INFO, cordappPackages).start() - val nodeThread = thread(name = nodeConf.myLegalName.organisation) { - node.internals.run() - } - node to nodeThread - }.flatMap { nodeAndThread -> - addressMustBeBoundFuture(executorService, nodeConf.p2pAddress).map { nodeAndThread } - } - } - - private fun startOutOfProcessNode( - nodeConf: NodeConfiguration, - config: Config, - quasarJarPath: String, - debugPort: Int?, - overriddenSystemProperties: Map, - cordappPackages: List, - maximumHeapSize: String, - initialRegistration: Boolean - ): Process { - log.info("Starting out-of-process Node ${nodeConf.myLegalName.organisation}, debug port is " + (debugPort ?: "not enabled")) - // Write node.conf - writeConfig(nodeConf.baseDirectory, "node.conf", config) - - val systemProperties = overriddenSystemProperties + mapOf( - "name" to nodeConf.myLegalName, - "visualvm.display.name" to "corda-${nodeConf.myLegalName}", - Node.scanPackagesSystemProperty to cordappPackages.joinToString(Node.scanPackagesSeparator), - "java.io.tmpdir" to System.getProperty("java.io.tmpdir"), // Inherit from parent process - "log4j2.debug" to if(debugPort != null) "true" else "false" - ) - // See experimental/quasar-hook/README.md for how to generate. - val excludePattern = "x(antlr**;bftsmart**;ch**;co.paralleluniverse**;com.codahale**;com.esotericsoftware**;" + - "com.fasterxml**;com.google**;com.ibm**;com.intellij**;com.jcabi**;com.nhaarman**;com.opengamma**;" + - "com.typesafe**;com.zaxxer**;de.javakaffee**;groovy**;groovyjarjarantlr**;groovyjarjarasm**;io.atomix**;" + - "io.github**;io.netty**;jdk**;joptsimple**;junit**;kotlin**;net.bytebuddy**;net.i2p**;org.apache**;" + - "org.assertj**;org.bouncycastle**;org.codehaus**;org.crsh**;org.dom4j**;org.fusesource**;org.h2**;" + - "org.hamcrest**;org.hibernate**;org.jboss**;org.jcp**;org.joda**;org.junit**;org.mockito**;org.objectweb**;" + - "org.objenesis**;org.slf4j**;org.w3c**;org.xml**;org.yaml**;reflectasm**;rx**)" - val extraJvmArguments = systemProperties.removeResolvedClasspath().map { "-D${it.key}=${it.value}" } + - "-javaagent:$quasarJarPath=$excludePattern" - val loggingLevel = if (debugPort == null) "INFO" else "DEBUG" - - val arguments = mutableListOf( - "--base-directory=${nodeConf.baseDirectory}", - "--logging-level=$loggingLevel", - "--no-local-shell").also { - if (initialRegistration) { - it += "--initial-registration" - } - }.toList() - - return ProcessUtilities.startCordaProcess( - className = "net.corda.node.Corda", // cannot directly get class for this, so just use string - arguments = arguments, - jdwpPort = debugPort, - extraJvmArguments = extraJvmArguments, - errorLogPath = nodeConf.baseDirectory / NodeStartup.LOGS_DIRECTORY_NAME / "error.log", - workingDirectory = nodeConf.baseDirectory, - maximumHeapSize = maximumHeapSize - ) - } - - private fun startWebserver(handle: NodeHandle, debugPort: Int?, maximumHeapSize: String): Process { - val className = "net.corda.webserver.WebServer" - return ProcessUtilities.startCordaProcess( - className = className, // cannot directly get class for this, so just use string - arguments = listOf("--base-directory", handle.configuration.baseDirectory.toString()), - jdwpPort = debugPort, - extraJvmArguments = listOf( - "-Dname=node-${handle.configuration.p2pAddress}-webserver", - "-Djava.io.tmpdir=${System.getProperty("java.io.tmpdir")}" // Inherit from parent process - ), - errorLogPath = Paths.get("error.$className.log"), - workingDirectory = null, - maximumHeapSize = maximumHeapSize - ) - } - - private fun getCallerPackage(): String { - return Exception() - .stackTrace - .first { it.fileName != "Driver.kt" } - .let { Class.forName(it.className).`package`?.name } - ?: throw IllegalStateException("Function instantiating driver must be defined in a package.") - } - - /** - * We have an alternative way of specifying classpath for spawned process: by using "-cp" option. So duplicating the setting of this - * rather long string is un-necessary and can be harmful on Windows. - */ - private fun Map.removeResolvedClasspath(): Map { - return filterNot { it.key == "java.class.path" } - } - } -} - fun writeConfig(path: Path, filename: String, config: Config) { val configString = config.root().render(ConfigRenderOptions.defaults()) configString.byteInputStream().copyTo(path / filename, REPLACE_EXISTING) diff --git a/testing/node-driver/src/main/kotlin/net/corda/testing/driver/DriverDSL.kt b/testing/node-driver/src/main/kotlin/net/corda/testing/driver/DriverDSL.kt new file mode 100644 index 0000000000..73cf42b334 --- /dev/null +++ b/testing/node-driver/src/main/kotlin/net/corda/testing/driver/DriverDSL.kt @@ -0,0 +1,90 @@ +package net.corda.testing.driver + +import net.corda.cordform.CordformContext +import net.corda.core.concurrent.CordaFuture +import net.corda.core.identity.CordaX500Name +import net.corda.core.identity.Party +import net.corda.core.internal.concurrent.map +import net.corda.node.services.config.VerifierType +import net.corda.nodeapi.internal.config.User + +interface DriverDSL : CordformContext { + /** Returns a list of [NotaryHandle]s matching the list of [NotarySpec]s passed into [driver]. */ + val notaryHandles: List + + /** + * Returns the [NotaryHandle] for the single notary on the network. Throws if there are none or more than one. + * @see notaryHandles + */ + val defaultNotaryHandle: NotaryHandle + get() { + return when (notaryHandles.size) { + 0 -> throw IllegalStateException("There are no notaries defined on the network") + 1 -> notaryHandles[0] + else -> throw IllegalStateException("There is more than one notary defined on the network") + } + } + + /** + * Returns the identity of the single notary on the network. Throws if there are none or more than one. + * @see defaultNotaryHandle + */ + val defaultNotaryIdentity: Party get() = defaultNotaryHandle.identity + + /** + * Returns a [CordaFuture] on the [NodeHandle] for the single-node notary on the network. Throws if there + * are no notaries or more than one, or if the notary is a distributed cluster. + * @see defaultNotaryHandle + * @see notaryHandles + */ + val defaultNotaryNode: CordaFuture + get() { + return defaultNotaryHandle.nodeHandles.map { + it.singleOrNull() ?: throw IllegalStateException("Default notary is not a single node") + } + } + + /** + * Start a node. + * + * @param defaultParameters The default parameters for the node. Allows the node to be configured in builder style + * when called from Java code. + * @param providedName Optional name of the node, which will be its legal name in [Party]. Defaults to something + * random. Note that this must be unique as the driver uses it as a primary key! + * @param verifierType The type of transaction verifier to use. See: [VerifierType] + * @param rpcUsers List of users who are authorised to use the RPC system. Defaults to empty list. + * @param startInSameProcess Determines if the node should be started inside the same process the Driver is running + * in. If null the Driver-level value will be used. + * @return A [CordaFuture] on the [NodeHandle] to the node. The future will complete when the node is available. + */ + fun startNode( + defaultParameters: NodeParameters = NodeParameters(), + providedName: CordaX500Name? = defaultParameters.providedName, + rpcUsers: List = defaultParameters.rpcUsers, + verifierType: VerifierType = defaultParameters.verifierType, + customOverrides: Map = defaultParameters.customOverrides, + startInSameProcess: Boolean? = defaultParameters.startInSameProcess, + maximumHeapSize: String = defaultParameters.maximumHeapSize + ): CordaFuture + + + /** + * Helper function for starting a [Node] with custom parameters from Java. + * + * @param parameters The default parameters for the driver. + * @return [NodeHandle] that will be available sometime in the future. + */ + fun startNode(parameters: NodeParameters): CordaFuture = startNode(defaultParameters = parameters) + + /** Call [startWebserver] with a default maximumHeapSize. */ + fun startWebserver(handle: NodeHandle): CordaFuture = startWebserver(handle, "200m") + + /** + * Starts a web server for a node + * @param handle The handle for the node that this webserver connects to via RPC. + * @param maximumHeapSize Argument for JVM -Xmx option e.g. "200m". + */ + fun startWebserver(handle: NodeHandle, maximumHeapSize: String): CordaFuture + + val shutdownManager: ShutdownManager +} \ No newline at end of file diff --git a/testing/node-driver/src/main/kotlin/net/corda/testing/internal/DriverDSLImpl.kt b/testing/node-driver/src/main/kotlin/net/corda/testing/internal/DriverDSLImpl.kt new file mode 100644 index 0000000000..f9f14c3a01 --- /dev/null +++ b/testing/node-driver/src/main/kotlin/net/corda/testing/internal/DriverDSLImpl.kt @@ -0,0 +1,696 @@ +package net.corda.testing.internal + +import com.google.common.collect.HashMultimap +import com.google.common.util.concurrent.ThreadFactoryBuilder +import com.typesafe.config.Config +import com.typesafe.config.ConfigFactory +import net.corda.client.rpc.CordaRPCClient +import net.corda.cordform.CordformNode +import net.corda.core.concurrent.CordaFuture +import net.corda.core.concurrent.firstOf +import net.corda.core.identity.CordaX500Name +import net.corda.core.internal.ThreadBox +import net.corda.core.internal.concurrent.* +import net.corda.core.internal.createDirectories +import net.corda.core.internal.div +import net.corda.core.messaging.CordaRPCOps +import net.corda.core.node.services.NetworkMapCache +import net.corda.core.node.services.NotaryService +import net.corda.core.toFuture +import net.corda.core.utilities.NetworkHostAndPort +import net.corda.core.utilities.contextLogger +import net.corda.core.utilities.getOrThrow +import net.corda.node.internal.Node +import net.corda.node.internal.NodeStartup +import net.corda.node.internal.StartedNode +import net.corda.node.services.Permissions +import net.corda.node.services.config.* +import net.corda.node.services.transactions.BFTNonValidatingNotaryService +import net.corda.node.services.transactions.RaftNonValidatingNotaryService +import net.corda.node.services.transactions.RaftValidatingNotaryService +import net.corda.node.utilities.registration.HTTPNetworkRegistrationService +import net.corda.node.utilities.registration.NetworkRegistrationHelper +import net.corda.nodeapi.internal.NetworkParametersCopier +import net.corda.nodeapi.internal.NodeInfoFilesCopier +import net.corda.nodeapi.internal.NotaryInfo +import net.corda.nodeapi.internal.ServiceIdentityGenerator +import net.corda.nodeapi.internal.config.User +import net.corda.nodeapi.internal.config.parseAs +import net.corda.nodeapi.internal.config.toConfig +import net.corda.nodeapi.internal.crypto.X509Utilities +import net.corda.testing.ALICE +import net.corda.testing.BOB +import net.corda.testing.DUMMY_BANK_A +import net.corda.testing.common.internal.testNetworkParameters +import net.corda.testing.driver.* +import net.corda.testing.internal.DriverDSLImpl.ClusterType.NON_VALIDATING_RAFT +import net.corda.testing.internal.DriverDSLImpl.ClusterType.VALIDATING_RAFT +import net.corda.testing.node.ClusterSpec +import net.corda.testing.node.MockServices +import net.corda.testing.node.NotarySpec +import okhttp3.OkHttpClient +import okhttp3.Request +import rx.Observable +import rx.observables.ConnectableObservable +import rx.schedulers.Schedulers +import java.net.ConnectException +import java.net.URL +import java.net.URLClassLoader +import java.nio.file.Path +import java.nio.file.Paths +import java.security.cert.X509Certificate +import java.time.Duration +import java.util.* +import java.util.concurrent.Executors +import java.util.concurrent.ScheduledExecutorService +import java.util.concurrent.TimeUnit +import java.util.concurrent.atomic.AtomicInteger +import kotlin.concurrent.thread + +class DriverDSLImpl( + val portAllocation: PortAllocation, + val debugPortAllocation: PortAllocation, + val systemProperties: Map, + val driverDirectory: Path, + val useTestClock: Boolean, + val isDebug: Boolean, + val startNodesInProcess: Boolean, + val waitForNodesToFinish: Boolean, + extraCordappPackagesToScan: List, + val notarySpecs: List, + val compatibilityZone: CompatibilityZoneParams? +) : DriverDSLInternalInterface { + private var _executorService: ScheduledExecutorService? = null + val executorService get() = _executorService!! + private var _shutdownManager: ShutdownManager? = null + override val shutdownManager get() = _shutdownManager!! + private val cordappPackages = extraCordappPackagesToScan + getCallerPackage() + // TODO: this object will copy NodeInfo files from started nodes to other nodes additional-node-infos/ + // This uses the FileSystem and adds a delay (~5 seconds) given by the time we wait before polling the file system. + // Investigate whether we can avoid that. + // TODO: NodeInfoFilesCopier create observable threads in the init method, we should move that to a start method instead, changing this to lateinit instead to prevent that. + private lateinit var nodeInfoFilesCopier: NodeInfoFilesCopier + // Map from a nodes legal name to an observable emitting the number of nodes in its network map. + private val countObservables = mutableMapOf>() + private lateinit var _notaries: List + override val notaryHandles: List get() = _notaries + private var networkParameters: NetworkParametersCopier? = null + + class State { + val processes = ArrayList() + } + + private val state = ThreadBox(State()) + + //TODO: remove this once we can bundle quasar properly. + private val quasarJarPath: String by lazy { + val cl = ClassLoader.getSystemClassLoader() + val urls = (cl as URLClassLoader).urLs + val quasarPattern = ".*quasar.*\\.jar$".toRegex() + val quasarFileUrl = urls.first { quasarPattern.matches(it.path) } + Paths.get(quasarFileUrl.toURI()).toString() + } + + override fun shutdown() { + if (waitForNodesToFinish) { + state.locked { + processes.forEach { it.waitFor() } + } + } + _shutdownManager?.shutdown() + _executorService?.shutdownNow() + } + + private fun establishRpc(config: NodeConfiguration, processDeathFuture: CordaFuture): CordaFuture { + val rpcAddress = config.rpcAddress!! + val client = CordaRPCClient(rpcAddress) + val connectionFuture = poll(executorService, "RPC connection") { + try { + client.start(config.rpcUsers[0].username, config.rpcUsers[0].password) + } catch (e: Exception) { + if (processDeathFuture.isDone) throw e + log.error("Exception $e, Retrying RPC connection at $rpcAddress") + null + } + } + return firstOf(connectionFuture, processDeathFuture) { + if (it == processDeathFuture) { + throw ListenProcessDeathException(rpcAddress, processDeathFuture.getOrThrow()) + } + val connection = connectionFuture.getOrThrow() + shutdownManager.registerShutdown(connection::close) + connection.proxy + } + } + + override fun startNode( + defaultParameters: NodeParameters, + providedName: CordaX500Name?, + rpcUsers: List, + verifierType: VerifierType, + customOverrides: Map, + startInSameProcess: Boolean?, + maximumHeapSize: String + ): CordaFuture { + val p2pAddress = portAllocation.nextHostAndPort() + // TODO: Derive name from the full picked name, don't just wrap the common name + val name = providedName ?: CordaX500Name(organisation = "${oneOf(names).organisation}-${p2pAddress.port}", locality = "London", country = "GB") + + val registrationFuture = if (compatibilityZone?.rootCert != null) { + nodeRegistration(name, compatibilityZone.rootCert, compatibilityZone.url) + } else { + doneFuture(Unit) + } + + return registrationFuture.flatMap { + val rpcAddress = portAllocation.nextHostAndPort() + val webAddress = portAllocation.nextHostAndPort() + val users = rpcUsers.map { it.copy(permissions = it.permissions + DRIVER_REQUIRED_PERMISSIONS) } + val configMap = configOf( + "myLegalName" to name.toString(), + "p2pAddress" to p2pAddress.toString(), + "rpcAddress" to rpcAddress.toString(), + "webAddress" to webAddress.toString(), + "useTestClock" to useTestClock, + "rpcUsers" to if (users.isEmpty()) defaultRpcUserList else users.map { it.toConfig().root().unwrapped() }, + "verifierType" to verifierType.name + ) + customOverrides + val config = ConfigHelper.loadConfig( + baseDirectory = baseDirectory(name), + allowMissingConfig = true, + configOverrides = if (compatibilityZone != null) { + configMap + mapOf("compatibilityZoneURL" to compatibilityZone.url.toString()) + } else { + configMap + } + ) + startNodeInternal(config, webAddress, startInSameProcess, maximumHeapSize) + } + } + + private fun nodeRegistration(providedName: CordaX500Name, rootCert: X509Certificate, compatibilityZoneURL: URL): CordaFuture { + val baseDirectory = baseDirectory(providedName).createDirectories() + val config = ConfigHelper.loadConfig( + baseDirectory = baseDirectory, + allowMissingConfig = true, + configOverrides = configOf( + "p2pAddress" to "localhost:1222", // required argument, not really used + "compatibilityZoneURL" to compatibilityZoneURL.toString(), + "myLegalName" to providedName.toString()) + ) + val configuration = config.parseAsNodeConfiguration() + + configuration.rootCertFile.parent.createDirectories() + X509Utilities.saveCertificateAsPEMFile(rootCert, configuration.rootCertFile) + + return if (startNodesInProcess) { + // This is a bit cheating, we're not starting a full node, we're just calling the code nodes call + // when registering. + NetworkRegistrationHelper(configuration, HTTPNetworkRegistrationService(compatibilityZoneURL)).buildKeystore() + doneFuture(Unit) + } else { + startOutOfProcessNodeRegistration(config, configuration) + } + } + + private enum class ClusterType(val validating: Boolean, val clusterName: CordaX500Name) { + VALIDATING_RAFT(true, CordaX500Name(RaftValidatingNotaryService.id, "Raft", "Zurich", "CH")), + NON_VALIDATING_RAFT(false, CordaX500Name(RaftNonValidatingNotaryService.id, "Raft", "Zurich", "CH")), + NON_VALIDATING_BFT(false, CordaX500Name(BFTNonValidatingNotaryService.id, "BFT", "Zurich", "CH")) + } + + internal fun startCordformNodes(cordforms: List): CordaFuture<*> { + val clusterNodes = HashMultimap.create() + val notaryInfos = ArrayList() + + // Go though the node definitions and pick out the notaries so that we can generate their identities to be used + // in the network parameters + for (cordform in cordforms) { + if (cordform.notary == null) continue + val name = CordaX500Name.parse(cordform.name) + val notaryConfig = ConfigFactory.parseMap(cordform.notary).parseAs() + // We need to first group the nodes that form part of a cluser. We assume for simplicity that nodes of the + // same cluster type and validating flag are part of the same cluster. + if (notaryConfig.raft != null) { + val key = if (notaryConfig.validating) VALIDATING_RAFT else NON_VALIDATING_RAFT + clusterNodes.put(key, name) + } else if (notaryConfig.bftSMaRt != null) { + clusterNodes.put(ClusterType.NON_VALIDATING_BFT, name) + } else { + // We have all we need here to generate the identity for single node notaries + val identity = ServiceIdentityGenerator.generateToDisk( + dirs = listOf(baseDirectory(name)), + serviceName = name, + serviceId = "identity" + ) + notaryInfos += NotaryInfo(identity, notaryConfig.validating) + } + } + + clusterNodes.asMap().forEach { type, nodeNames -> + val identity = ServiceIdentityGenerator.generateToDisk( + dirs = nodeNames.map { baseDirectory(it) }, + serviceName = type.clusterName, + serviceId = NotaryService.constructId( + validating = type.validating, + raft = type in setOf(VALIDATING_RAFT, NON_VALIDATING_RAFT), + bft = type == ClusterType.NON_VALIDATING_BFT + ) + ) + notaryInfos += NotaryInfo(identity, type.validating) + } + + networkParameters = NetworkParametersCopier(testNetworkParameters(notaryInfos)) + + return cordforms.map { + val startedNode = startCordformNode(it) + if (it.webAddress != null) { + // Start a webserver if an address for it was specified + startedNode.flatMap { startWebserver(it) } + } else { + startedNode + } + }.transpose() + } + + private fun startCordformNode(cordform: CordformNode): CordaFuture { + val name = CordaX500Name.parse(cordform.name) + // TODO We shouldn't have to allocate an RPC or web address if they're not specified. We're having to do this because of startNodeInternal + val rpcAddress = if (cordform.rpcAddress == null) mapOf("rpcAddress" to portAllocation.nextHostAndPort().toString()) else emptyMap() + val webAddress = cordform.webAddress?.let { NetworkHostAndPort.parse(it) } ?: portAllocation.nextHostAndPort() + val notary = if (cordform.notary != null) mapOf("notary" to cordform.notary) else emptyMap() + val rpcUsers = cordform.rpcUsers + val config = ConfigHelper.loadConfig( + baseDirectory = baseDirectory(name), + allowMissingConfig = true, + configOverrides = cordform.config + rpcAddress + notary + mapOf( + "rpcUsers" to if (rpcUsers.isEmpty()) defaultRpcUserList else rpcUsers + ) + ) + return startNodeInternal(config, webAddress, null, "200m") + } + + private fun queryWebserver(handle: NodeHandle, process: Process): WebserverHandle { + val protocol = if (handle.configuration.useHTTPS) "https://" else "http://" + val url = URL("$protocol${handle.webAddress}/api/status") + val client = OkHttpClient.Builder().connectTimeout(5, TimeUnit.SECONDS).readTimeout(60, TimeUnit.SECONDS).build() + + while (process.isAlive) try { + val response = client.newCall(Request.Builder().url(url).build()).execute() + if (response.isSuccessful && (response.body().string() == "started")) { + return WebserverHandle(handle.webAddress, process) + } + } catch (e: ConnectException) { + log.debug("Retrying webserver info at ${handle.webAddress}") + } + + throw IllegalStateException("Webserver at ${handle.webAddress} has died") + } + + override fun startWebserver(handle: NodeHandle, maximumHeapSize: String): CordaFuture { + val debugPort = if (isDebug) debugPortAllocation.nextPort() else null + val process = startWebserver(handle, debugPort, maximumHeapSize) + shutdownManager.registerProcessShutdown(process) + val webReadyFuture = addressMustBeBoundFuture(executorService, handle.webAddress, process) + return webReadyFuture.map { queryWebserver(handle, process) } + } + + override fun start() { + if (startNodesInProcess) { + Schedulers.reset() + } + _executorService = Executors.newScheduledThreadPool(2, ThreadFactoryBuilder().setNameFormat("driver-pool-thread-%d").build()) + _shutdownManager = ShutdownManager(executorService) + + nodeInfoFilesCopier = NodeInfoFilesCopier() + shutdownManager.registerShutdown { nodeInfoFilesCopier.close() } + + val notaryInfos = generateNotaryIdentities() + // The network parameters must be serialised before starting any of the nodes + networkParameters = NetworkParametersCopier(testNetworkParameters(notaryInfos)) + val nodeHandles = startNotaries() + _notaries = notaryInfos.zip(nodeHandles) { (identity, validating), nodes -> NotaryHandle(identity, validating, nodes) } + } + + private fun generateNotaryIdentities(): List { + return notarySpecs.map { spec -> + val identity = if (spec.cluster == null) { + ServiceIdentityGenerator.generateToDisk( + dirs = listOf(baseDirectory(spec.name)), + serviceName = spec.name, + serviceId = "identity", + customRootCert = compatibilityZone?.rootCert + ) + } else { + ServiceIdentityGenerator.generateToDisk( + dirs = generateNodeNames(spec).map { baseDirectory(it) }, + serviceName = spec.name, + serviceId = NotaryService.constructId( + validating = spec.validating, + raft = spec.cluster is ClusterSpec.Raft + ), + customRootCert = compatibilityZone?.rootCert + ) + } + NotaryInfo(identity, spec.validating) + } + } + + private fun generateNodeNames(spec: NotarySpec): List { + return (0 until spec.cluster!!.clusterSize).map { spec.name.copy(organisation = "${spec.name.organisation}-$it") } + } + + private fun startNotaries(): List>> { + return notarySpecs.map { + when { + it.cluster == null -> startSingleNotary(it) + it.cluster is ClusterSpec.Raft -> startRaftNotaryCluster(it) + else -> throw IllegalArgumentException("BFT-SMaRt not supported") + } + } + } + + // TODO This mapping is done is several places including the gradle plugin. In general we need a better way of + // generating the configs for the nodes, probably making use of Any.toConfig() + private fun NotaryConfig.toConfigMap(): Map = mapOf("notary" to toConfig().root().unwrapped()) + + private fun startSingleNotary(spec: NotarySpec): CordaFuture> { + return startNode( + providedName = spec.name, + rpcUsers = spec.rpcUsers, + verifierType = spec.verifierType, + customOverrides = NotaryConfig(spec.validating).toConfigMap() + ).map { listOf(it) } + } + + private fun startRaftNotaryCluster(spec: NotarySpec): CordaFuture> { + fun notaryConfig(nodeAddress: NetworkHostAndPort, clusterAddress: NetworkHostAndPort? = null): Map { + val clusterAddresses = if (clusterAddress != null) listOf(clusterAddress) else emptyList() + val config = NotaryConfig( + validating = spec.validating, + raft = RaftConfig(nodeAddress = nodeAddress, clusterAddresses = clusterAddresses)) + return config.toConfigMap() + } + + val nodeNames = generateNodeNames(spec) + val clusterAddress = portAllocation.nextHostAndPort() + + // Start the first node that will bootstrap the cluster + val firstNodeFuture = startNode( + providedName = nodeNames[0], + rpcUsers = spec.rpcUsers, + verifierType = spec.verifierType, + customOverrides = notaryConfig(clusterAddress) + mapOf( + "database.serverNameTablePrefix" to nodeNames[0].toString().replace(Regex("[^0-9A-Za-z]+"), "") + ) + ) + + // All other nodes will join the cluster + val restNodeFutures = nodeNames.drop(1).map { + val nodeAddress = portAllocation.nextHostAndPort() + startNode( + providedName = it, + rpcUsers = spec.rpcUsers, + verifierType = spec.verifierType, + customOverrides = notaryConfig(nodeAddress, clusterAddress) + mapOf( + "database.serverNameTablePrefix" to it.toString().replace(Regex("[^0-9A-Za-z]+"), "") + ) + ) + } + + return firstNodeFuture.flatMap { first -> + restNodeFutures.transpose().map { rest -> listOf(first) + rest } + } + } + + fun baseDirectory(nodeName: CordaX500Name): Path { + val nodeDirectoryName = nodeName.organisation.filter { !it.isWhitespace() } + return driverDirectory / nodeDirectoryName + } + + override fun baseDirectory(nodeName: String): Path = baseDirectory(CordaX500Name.parse(nodeName)) + + /** + * @param initial number of nodes currently in the network map of a running node. + * @param networkMapCacheChangeObservable an observable returning the updates to the node network map. + * @return a [ConnectableObservable] which emits a new [Int] every time the number of registered nodes changes + * the initial value emitted is always [initial] + */ + private fun nodeCountObservable(initial: Int, networkMapCacheChangeObservable: Observable): + ConnectableObservable { + val count = AtomicInteger(initial) + return networkMapCacheChangeObservable.map { it -> + when (it) { + is NetworkMapCache.MapChange.Added -> count.incrementAndGet() + is NetworkMapCache.MapChange.Removed -> count.decrementAndGet() + is NetworkMapCache.MapChange.Modified -> count.get() + } + }.startWith(initial).replay() + } + + /** + * @param rpc the [CordaRPCOps] of a newly started node. + * @return a [CordaFuture] which resolves when every node started by driver has in its network map a number of nodes + * equal to the number of running nodes. The future will yield the number of connected nodes. + */ + private fun allNodesConnected(rpc: CordaRPCOps): CordaFuture { + val (snapshot, updates) = rpc.networkMapFeed() + val counterObservable = nodeCountObservable(snapshot.size, updates) + countObservables[rpc.nodeInfo().legalIdentities[0].name] = counterObservable + /* TODO: this might not always be the exact number of nodes one has to wait for, + * for example in the following sequence + * 1 start 3 nodes in order, A, B, C. + * 2 before the future returned by this function resolves, kill B + * At that point this future won't ever resolve as it will wait for nodes to know 3 other nodes. + */ + val requiredNodes = countObservables.size + + // This is an observable which yield the minimum number of nodes in each node network map. + val smallestSeenNetworkMapSize = Observable.combineLatest(countObservables.values.toList()) { args: Array -> + args.map { it as Int }.min() ?: 0 + } + val future = smallestSeenNetworkMapSize.filter { it >= requiredNodes }.toFuture() + counterObservable.connect() + return future + } + + private fun startOutOfProcessNodeRegistration(config: Config, configuration: NodeConfiguration): CordaFuture { + val debugPort = if (isDebug) debugPortAllocation.nextPort() else null + val process = startOutOfProcessNode(configuration, config, quasarJarPath, debugPort, + systemProperties, cordappPackages, "200m", initialRegistration = true) + + return poll(executorService, "node registration (${configuration.myLegalName})") { + if (process.isAlive) null else Unit + } + } + + private fun startNodeInternal(config: Config, + webAddress: NetworkHostAndPort, + startInProcess: Boolean?, + maximumHeapSize: String): CordaFuture { + val configuration = config.parseAsNodeConfiguration() + val baseDirectory = configuration.baseDirectory.createDirectories() + // Distribute node info file using file copier when network map service URL (compatibilityZoneURL) is null. + // TODO: need to implement the same in cordformation? + val nodeInfoFilesCopier = if (compatibilityZone == null) nodeInfoFilesCopier else null + + nodeInfoFilesCopier?.addConfig(baseDirectory) + networkParameters!!.install(baseDirectory) + val onNodeExit: () -> Unit = { + nodeInfoFilesCopier?.removeConfig(baseDirectory) + countObservables.remove(configuration.myLegalName) + } + if (startInProcess ?: startNodesInProcess) { + val nodeAndThreadFuture = startInProcessNode(executorService, configuration, config, cordappPackages) + shutdownManager.registerShutdown( + nodeAndThreadFuture.map { (node, thread) -> + { + node.dispose() + thread.interrupt() + } + } + ) + return nodeAndThreadFuture.flatMap { (node, thread) -> + establishRpc(configuration, openFuture()).flatMap { rpc -> + allNodesConnected(rpc).map { + NodeHandle.InProcess(rpc.nodeInfo(), rpc, configuration, webAddress, node, thread, onNodeExit) + } + } + } + } else { + val debugPort = if (isDebug) debugPortAllocation.nextPort() else null + val process = startOutOfProcessNode(configuration, config, quasarJarPath, debugPort, + systemProperties, cordappPackages, maximumHeapSize, initialRegistration = false) + if (waitForNodesToFinish) { + state.locked { + processes += process + } + } else { + shutdownManager.registerProcessShutdown(process) + } + val p2pReadyFuture = addressMustBeBoundFuture(executorService, configuration.p2pAddress, process) + return p2pReadyFuture.flatMap { + val processDeathFuture = poll(executorService, "process death while waiting for RPC (${configuration.myLegalName})") { + if (process.isAlive) null else process + } + establishRpc(configuration, processDeathFuture).flatMap { rpc -> + // Check for all nodes to have all other nodes in background in case RPC is failing over: + val networkMapFuture = executorService.fork { allNodesConnected(rpc) }.flatMap { it } + firstOf(processDeathFuture, networkMapFuture) { + if (it == processDeathFuture) { + throw ListenProcessDeathException(configuration.p2pAddress, process) + } + processDeathFuture.cancel(false) + log.info("Node handle is ready. NodeInfo: ${rpc.nodeInfo()}, WebAddress: $webAddress") + NodeHandle.OutOfProcess(rpc.nodeInfo(), rpc, configuration, webAddress, debugPort, process, + onNodeExit) + } + } + } + } + } + + override fun pollUntilNonNull(pollName: String, pollInterval: Duration, warnCount: Int, check: () -> A?): CordaFuture { + val pollFuture = poll(executorService, pollName, pollInterval, warnCount, check) + shutdownManager.registerShutdown { pollFuture.cancel(true) } + return pollFuture + } + + companion object { + internal val log = contextLogger() + + private val defaultRpcUserList = listOf(User("default", "default", setOf("ALL")).toConfig().root().unwrapped()) + + private val names = arrayOf( + ALICE.name, + BOB.name, + DUMMY_BANK_A.name + ) + + /** + * A sub-set of permissions that grant most of the essential operations used in the unit/integration tests as well as + * in demo application like NodeExplorer. + */ + private val DRIVER_REQUIRED_PERMISSIONS = setOf( + Permissions.invokeRpc(CordaRPCOps::nodeInfo), + Permissions.invokeRpc(CordaRPCOps::networkMapFeed), + Permissions.invokeRpc(CordaRPCOps::networkMapSnapshot), + Permissions.invokeRpc(CordaRPCOps::notaryIdentities), + Permissions.invokeRpc(CordaRPCOps::stateMachinesFeed), + Permissions.invokeRpc(CordaRPCOps::stateMachineRecordedTransactionMappingFeed), + Permissions.invokeRpc(CordaRPCOps::nodeInfoFromParty), + Permissions.invokeRpc(CordaRPCOps::internalVerifiedTransactionsFeed), + Permissions.invokeRpc("vaultQueryBy"), + Permissions.invokeRpc("vaultTrackBy"), + Permissions.invokeRpc(CordaRPCOps::registeredFlows) + ) + + private fun oneOf(array: Array) = array[Random().nextInt(array.size)] + + private fun startInProcessNode( + executorService: ScheduledExecutorService, + nodeConf: NodeConfiguration, + config: Config, + cordappPackages: List + ): CordaFuture, Thread>> { + return executorService.fork { + log.info("Starting in-process Node ${nodeConf.myLegalName.organisation}") + // Write node.conf + writeConfig(nodeConf.baseDirectory, "node.conf", config) + // TODO pass the version in? + val node = InProcessNode(nodeConf, MockServices.MOCK_VERSION_INFO, cordappPackages).start() + val nodeThread = thread(name = nodeConf.myLegalName.organisation) { + node.internals.run() + } + node to nodeThread + }.flatMap { nodeAndThread -> + addressMustBeBoundFuture(executorService, nodeConf.p2pAddress).map { nodeAndThread } + } + } + + private fun startOutOfProcessNode( + nodeConf: NodeConfiguration, + config: Config, + quasarJarPath: String, + debugPort: Int?, + overriddenSystemProperties: Map, + cordappPackages: List, + maximumHeapSize: String, + initialRegistration: Boolean + ): Process { + log.info("Starting out-of-process Node ${nodeConf.myLegalName.organisation}, debug port is " + (debugPort ?: "not enabled")) + // Write node.conf + writeConfig(nodeConf.baseDirectory, "node.conf", config) + + val systemProperties = overriddenSystemProperties + mapOf( + "name" to nodeConf.myLegalName, + "visualvm.display.name" to "corda-${nodeConf.myLegalName}", + Node.scanPackagesSystemProperty to cordappPackages.joinToString(Node.scanPackagesSeparator), + "java.io.tmpdir" to System.getProperty("java.io.tmpdir"), // Inherit from parent process + "log4j2.debug" to if(debugPort != null) "true" else "false" + ) + // See experimental/quasar-hook/README.md for how to generate. + val excludePattern = "x(antlr**;bftsmart**;ch**;co.paralleluniverse**;com.codahale**;com.esotericsoftware**;" + + "com.fasterxml**;com.google**;com.ibm**;com.intellij**;com.jcabi**;com.nhaarman**;com.opengamma**;" + + "com.typesafe**;com.zaxxer**;de.javakaffee**;groovy**;groovyjarjarantlr**;groovyjarjarasm**;io.atomix**;" + + "io.github**;io.netty**;jdk**;joptsimple**;junit**;kotlin**;net.bytebuddy**;net.i2p**;org.apache**;" + + "org.assertj**;org.bouncycastle**;org.codehaus**;org.crsh**;org.dom4j**;org.fusesource**;org.h2**;" + + "org.hamcrest**;org.hibernate**;org.jboss**;org.jcp**;org.joda**;org.junit**;org.mockito**;org.objectweb**;" + + "org.objenesis**;org.slf4j**;org.w3c**;org.xml**;org.yaml**;reflectasm**;rx**)" + val extraJvmArguments = systemProperties.removeResolvedClasspath().map { "-D${it.key}=${it.value}" } + + "-javaagent:$quasarJarPath=$excludePattern" + val loggingLevel = if (debugPort == null) "INFO" else "DEBUG" + + val arguments = mutableListOf( + "--base-directory=${nodeConf.baseDirectory}", + "--logging-level=$loggingLevel", + "--no-local-shell").also { + if (initialRegistration) { + it += "--initial-registration" + } + }.toList() + + return ProcessUtilities.startCordaProcess( + className = "net.corda.node.Corda", // cannot directly get class for this, so just use string + arguments = arguments, + jdwpPort = debugPort, + extraJvmArguments = extraJvmArguments, + errorLogPath = nodeConf.baseDirectory / NodeStartup.LOGS_DIRECTORY_NAME / "error.log", + workingDirectory = nodeConf.baseDirectory, + maximumHeapSize = maximumHeapSize + ) + } + + private fun startWebserver(handle: NodeHandle, debugPort: Int?, maximumHeapSize: String): Process { + val className = "net.corda.webserver.WebServer" + return ProcessUtilities.startCordaProcess( + className = className, // cannot directly get class for this, so just use string + arguments = listOf("--base-directory", handle.configuration.baseDirectory.toString()), + jdwpPort = debugPort, + extraJvmArguments = listOf( + "-Dname=node-${handle.configuration.p2pAddress}-webserver", + "-Djava.io.tmpdir=${System.getProperty("java.io.tmpdir")}" // Inherit from parent process + ), + errorLogPath = Paths.get("error.$className.log"), + workingDirectory = null, + maximumHeapSize = maximumHeapSize + ) + } + + private fun getCallerPackage(): String { + return Exception() + .stackTrace + .first { it.fileName != "Driver.kt" } + .let { Class.forName(it.className).`package`?.name } + ?: throw IllegalStateException("Function instantiating driver must be defined in a package.") + } + + /** + * We have an alternative way of specifying classpath for spawned process: by using "-cp" option. So duplicating the setting of this + * rather long string is un-necessary and can be harmful on Windows. + */ + private fun Map.removeResolvedClasspath(): Map { + return filterNot { it.key == "java.class.path" } + } + } +} \ No newline at end of file diff --git a/testing/node-driver/src/main/kotlin/net/corda/testing/internal/RPCDriver.kt b/testing/node-driver/src/main/kotlin/net/corda/testing/internal/RPCDriver.kt index acb0532170..9a0dabc379 100644 --- a/testing/node-driver/src/main/kotlin/net/corda/testing/internal/RPCDriver.kt +++ b/testing/node-driver/src/main/kotlin/net/corda/testing/internal/RPCDriver.kt @@ -52,7 +52,7 @@ import java.nio.file.Path import java.nio.file.Paths import java.util.* -interface RPCDriverExposedDSLInterface : DriverDSLExposedInterface { +interface RPCDriverExposedDSLInterface : DriverDSLInternalInterface { /** * Starts an In-VM RPC server. Note that only a single one may be started. * @@ -239,7 +239,7 @@ fun rpcDriver( dsl: RPCDriverExposedDSLInterface.() -> A ) = genericDriver( driverDsl = RPCDriverDSL( - DriverDSL( + DriverDSLImpl( portAllocation = portAllocation, debugPortAllocation = debugPortAllocation, systemProperties = systemProperties, @@ -279,7 +279,7 @@ private class SingleUserSecurityManager(val rpcUser: User) : ActiveMQSecurityMan } data class RPCDriverDSL( - private val driverDSL: DriverDSL, private val externalTrace: Trace? + private val driverDSL: DriverDSLImpl, private val externalTrace: Trace? ) : DriverDSLInternalInterface by driverDSL, RPCDriverInternalDSLInterface { private companion object { val notificationAddress = "notifications" diff --git a/verifier/src/integration-test/kotlin/net/corda/verifier/VerifierDriver.kt b/verifier/src/integration-test/kotlin/net/corda/verifier/VerifierDriver.kt index 5e35b75ec3..3eae27f9c0 100644 --- a/verifier/src/integration-test/kotlin/net/corda/verifier/VerifierDriver.kt +++ b/verifier/src/integration-test/kotlin/net/corda/verifier/VerifierDriver.kt @@ -23,6 +23,7 @@ import net.corda.nodeapi.internal.ArtemisMessagingComponent.Companion.NODE_USER import net.corda.nodeapi.internal.config.NodeSSLConfiguration import net.corda.nodeapi.internal.config.SSLConfiguration import net.corda.testing.driver.* +import net.corda.testing.internal.DriverDSLImpl import net.corda.testing.internal.ProcessUtilities import net.corda.testing.node.NotarySpec import org.apache.activemq.artemis.api.core.SimpleString @@ -44,10 +45,10 @@ import java.util.concurrent.ScheduledExecutorService import java.util.concurrent.atomic.AtomicInteger /** - * This file defines an extension to [DriverDSL] that allows starting of verifier processes and + * This file defines an extension to [DriverDSLImpl] that allows starting of verifier processes and * lightweight verification requestors. */ -interface VerifierExposedDSLInterface : DriverDSLExposedInterface { +interface VerifierExposedDSLInterface : DriverDSL { /** Starts a lightweight verification requestor that implements the Node's Verifier API */ fun startVerificationRequestor(name: CordaX500Name): CordaFuture @@ -88,7 +89,7 @@ fun verifierDriver( dsl: VerifierExposedDSLInterface.() -> A ) = genericDriver( driverDsl = VerifierDriverDSL( - DriverDSL( + DriverDSLImpl( portAllocation = portAllocation, debugPortAllocation = debugPortAllocation, systemProperties = systemProperties, @@ -145,7 +146,7 @@ data class VerificationRequestorHandle( data class VerifierDriverDSL( - val driverDSL: DriverDSL + val driverDSL: DriverDSLImpl ) : DriverDSLInternalInterface by driverDSL, VerifierInternalDSLInterface { val verifierCount = AtomicInteger(0) From 41bfd7a971e771064704d5e41ce0bc1204715667 Mon Sep 17 00:00:00 2001 From: Shams Asari Date: Fri, 8 Dec 2017 18:07:02 +0000 Subject: [PATCH 22/23] Merge fixes --- .../net/corda/node/NodeKeystoreCheckTest.kt | 6 +- .../node/services/network/NetworkMapTest.kt | 4 +- .../registration/NodeRegistrationTest.kt | 6 +- .../net/corda/node/internal/AbstractNode.kt | 8 +-- .../net/corda/test/spring/SpringDriver.kt | 6 +- .../net/corda/testing/driver/DriverTests.kt | 8 +-- .../kotlin/net/corda/testing/driver/Driver.kt | 54 ++------------ .../net/corda/testing/driver/DriverDSL.kt | 2 - .../corda/testing/internal/DriverDSLImpl.kt | 71 ++++++++++++++++--- .../net/corda/testing/internal/RPCDriver.kt | 2 + .../testing/internal/demorun/DemoRunner.kt | 3 +- .../testing/node/network/NetworkMapServer.kt | 2 +- .../net/corda/verifier/VerifierDriver.kt | 8 +-- 13 files changed, 92 insertions(+), 88 deletions(-) diff --git a/node/src/integration-test/kotlin/net/corda/node/NodeKeystoreCheckTest.kt b/node/src/integration-test/kotlin/net/corda/node/NodeKeystoreCheckTest.kt index 9ea387ec9e..ca1a3e9792 100644 --- a/node/src/integration-test/kotlin/net/corda/node/NodeKeystoreCheckTest.kt +++ b/node/src/integration-test/kotlin/net/corda/node/NodeKeystoreCheckTest.kt @@ -6,7 +6,7 @@ import net.corda.core.internal.cert import net.corda.core.internal.div import net.corda.core.utilities.getOrThrow import net.corda.node.services.config.configureDevKeyAndTrustStores -import net.corda.nodeapi.config.SSLConfiguration +import net.corda.nodeapi.internal.config.SSLConfiguration import net.corda.nodeapi.internal.crypto.* import net.corda.testing.ALICE_NAME import net.corda.testing.driver.driver @@ -32,7 +32,7 @@ class NodeKeystoreCheckTest { val config = object : SSLConfiguration { override val keyStorePassword: String = keystorePassword override val trustStorePassword: String = keystorePassword - override val certificatesDirectory: Path = baseDirectory(ALICE_NAME.toString()) / "certificates" + override val certificatesDirectory: Path = baseDirectory(ALICE_NAME) / "certificates" } config.configureDevKeyAndTrustStores(ALICE_NAME) @@ -49,7 +49,7 @@ class NodeKeystoreCheckTest { val badRootKeyPair = Crypto.generateKeyPair() val badRoot = X509Utilities.createSelfSignedCACertificate(CordaX500Name("Bad Root", "Lodnon", "GB"), badRootKeyPair) val nodeCA = keystore.getCertificateAndKeyPair(X509Utilities.CORDA_CLIENT_CA, config.keyStorePassword) - val badNodeCACert = X509Utilities.createCertificate(CertificateType.CLIENT_CA, badRoot, badRootKeyPair, ALICE_NAME, nodeCA.keyPair.public) + val badNodeCACert = X509Utilities.createCertificate(CertificateType.NODE_CA, badRoot, badRootKeyPair, ALICE_NAME, nodeCA.keyPair.public) keystore.setKeyEntry(X509Utilities.CORDA_CLIENT_CA, nodeCA.keyPair.private, config.keyStorePassword.toCharArray(), arrayOf(badNodeCACert.cert, badRoot.cert)) keystore.save(config.nodeKeystore, config.keyStorePassword) diff --git a/node/src/integration-test/kotlin/net/corda/node/services/network/NetworkMapTest.kt b/node/src/integration-test/kotlin/net/corda/node/services/network/NetworkMapTest.kt index c7972e3b2a..8cd555f231 100644 --- a/node/src/integration-test/kotlin/net/corda/node/services/network/NetworkMapTest.kt +++ b/node/src/integration-test/kotlin/net/corda/node/services/network/NetworkMapTest.kt @@ -4,10 +4,10 @@ import net.corda.core.node.NodeInfo import net.corda.core.utilities.seconds import net.corda.testing.ALICE import net.corda.testing.BOB -import net.corda.testing.driver.CompatibilityZoneParams +import net.corda.testing.internal.CompatibilityZoneParams import net.corda.testing.driver.NodeHandle import net.corda.testing.driver.PortAllocation -import net.corda.testing.driver.internalDriver +import net.corda.testing.internal.internalDriver import net.corda.testing.node.network.NetworkMapServer import org.assertj.core.api.Assertions.assertThat import org.junit.After diff --git a/node/src/integration-test/kotlin/net/corda/node/utilities/registration/NodeRegistrationTest.kt b/node/src/integration-test/kotlin/net/corda/node/utilities/registration/NodeRegistrationTest.kt index 62826c3d25..0301c84dd3 100644 --- a/node/src/integration-test/kotlin/net/corda/node/utilities/registration/NodeRegistrationTest.kt +++ b/node/src/integration-test/kotlin/net/corda/node/utilities/registration/NodeRegistrationTest.kt @@ -13,9 +13,9 @@ import net.corda.nodeapi.internal.crypto.X509Utilities import net.corda.nodeapi.internal.crypto.X509Utilities.CORDA_CLIENT_CA import net.corda.nodeapi.internal.crypto.X509Utilities.CORDA_INTERMEDIATE_CA import net.corda.nodeapi.internal.crypto.X509Utilities.CORDA_ROOT_CA -import net.corda.testing.driver.CompatibilityZoneParams +import net.corda.testing.internal.CompatibilityZoneParams import net.corda.testing.driver.PortAllocation -import net.corda.testing.driver.internalDriver +import net.corda.testing.internal.internalDriver import net.corda.testing.node.network.NetworkMapServer import org.assertj.core.api.Assertions.assertThat import org.bouncycastle.pkcs.PKCS10CertificationRequest @@ -126,7 +126,7 @@ class RegistrationHandler(private val rootCertAndKeyPair: CertificateAndKeyPair) caCertPath: Array): Pair { val request = JcaPKCS10CertificationRequest(certificationRequest) val name = CordaX500Name.parse(request.subject.toString()) - val x509CertificateHolder = X509Utilities.createCertificate(CertificateType.CLIENT_CA, + val x509CertificateHolder = X509Utilities.createCertificate(CertificateType.NODE_CA, caCertPath.first().toX509CertHolder(), caKeyPair, name, diff --git a/node/src/main/kotlin/net/corda/node/internal/AbstractNode.kt b/node/src/main/kotlin/net/corda/node/internal/AbstractNode.kt index bb8a0d577e..39e76419c1 100644 --- a/node/src/main/kotlin/net/corda/node/internal/AbstractNode.kt +++ b/node/src/main/kotlin/net/corda/node/internal/AbstractNode.kt @@ -43,7 +43,6 @@ import net.corda.node.services.config.NotaryConfig import net.corda.node.services.config.configureWithDevSSLCertificate import net.corda.node.services.events.NodeSchedulerService import net.corda.node.services.events.ScheduledActivityObserver -import net.corda.node.services.api.IdentityServiceInternal import net.corda.node.services.identity.PersistentIdentityService import net.corda.node.services.keys.PersistentKeyManagementService import net.corda.node.services.messaging.MessagingService @@ -69,7 +68,6 @@ import org.slf4j.Logger import rx.Observable import rx.Scheduler import java.io.IOException -import java.io.NotSerializableException import java.lang.reflect.InvocationTargetException import java.security.KeyPair import java.security.KeyStoreException @@ -183,7 +181,9 @@ abstract class AbstractNode(val configuration: NodeConfiguration, val schemaService = NodeSchemaService(cordappLoader.cordappSchemas) val (identity, identityKeyPair) = obtainIdentity(notaryConfig = null) initialiseDatabasePersistence(schemaService, makeIdentityService(identity.certificate)) { database -> - val persistentNetworkMapCache = PersistentNetworkMapCache(database) + // TODO The fact that we need to specify an empty list of notaries just to generate our node info looks like + // a code smell. + val persistentNetworkMapCache = PersistentNetworkMapCache(database, notaries = emptyList()) val (keyPairs, info) = initNodeInfo(persistentNetworkMapCache, identity, identityKeyPair) val identityKeypair = keyPairs.first { it.public == info.legalIdentities.first().owningKey } val serialisedNodeInfo = info.serialize() @@ -203,7 +203,7 @@ abstract class AbstractNode(val configuration: NodeConfiguration, val identityService = makeIdentityService(identity.certificate) // Do all of this in a database transaction so anything that might need a connection has one. val (startedImpl, schedulerService) = initialiseDatabasePersistence(schemaService, identityService) { database -> - val networkMapCache = NetworkMapCacheImpl(PersistentNetworkMapCache(database), identityService) + val networkMapCache = NetworkMapCacheImpl(PersistentNetworkMapCache(database, networkParameters.notaries), identityService) val (keyPairs, info) = initNodeInfo(networkMapCache, identity, identityKeyPair) identityService.loadIdentities(info.legalIdentitiesAndCerts) val transactionStorage = makeTransactionStorage(database) diff --git a/samples/irs-demo/src/integration-test/kotlin/net/corda/test/spring/SpringDriver.kt b/samples/irs-demo/src/integration-test/kotlin/net/corda/test/spring/SpringDriver.kt index ddbef9b80a..a55e1a3a5c 100644 --- a/samples/irs-demo/src/integration-test/kotlin/net/corda/test/spring/SpringDriver.kt +++ b/samples/irs-demo/src/integration-test/kotlin/net/corda/test/spring/SpringDriver.kt @@ -7,9 +7,7 @@ import net.corda.testing.driver.DriverParameters import net.corda.testing.driver.NodeHandle import net.corda.testing.driver.PortAllocation import net.corda.testing.driver.WebserverHandle -import net.corda.testing.internal.DriverDSLImpl -import net.corda.testing.internal.ProcessUtilities -import net.corda.testing.internal.addressMustBeBoundFuture +import net.corda.testing.internal.* import net.corda.testing.node.NotarySpec import okhttp3.OkHttpClient import okhttp3.Request @@ -93,7 +91,7 @@ data class SpringBootDriverDSL(private val driverDSL: DriverDSLImpl) : InternalD log.debug("Retrying webserver info at ${handle.webAddress}") } - throw IllegalStateException("Webserver at ${handle.webAddress} has died or was not reachable at URL ${url}") + throw IllegalStateException("Webserver at ${handle.webAddress} has died or was not reachable at URL $url") } private fun startApplication(handle: NodeHandle, debugPort: Int?, clazz: Class<*>): Process { diff --git a/testing/node-driver/src/integration-test/kotlin/net/corda/testing/driver/DriverTests.kt b/testing/node-driver/src/integration-test/kotlin/net/corda/testing/driver/DriverTests.kt index eac9e8deab..1d86635c3f 100644 --- a/testing/node-driver/src/integration-test/kotlin/net/corda/testing/driver/DriverTests.kt +++ b/testing/node-driver/src/integration-test/kotlin/net/corda/testing/driver/DriverTests.kt @@ -2,6 +2,8 @@ package net.corda.testing.driver import net.corda.core.concurrent.CordaFuture import net.corda.core.internal.div +import net.corda.core.internal.list +import net.corda.core.internal.readLines import net.corda.core.utilities.getOrThrow import net.corda.node.internal.NodeStartup import net.corda.testing.DUMMY_BANK_A @@ -10,13 +12,13 @@ import net.corda.testing.DUMMY_REGULATOR import net.corda.testing.common.internal.ProjectStructure.projectRootDir import net.corda.testing.internal.addressMustBeBound import net.corda.testing.internal.addressMustNotBeBound +import net.corda.testing.internal.internalDriver import net.corda.testing.node.NotarySpec import org.assertj.core.api.Assertions.assertThat import org.junit.Test import java.util.concurrent.Executors import java.util.concurrent.ScheduledExecutorService - class DriverTests { companion object { private val executorService: ScheduledExecutorService = Executors.newScheduledThreadPool(2) @@ -33,7 +35,6 @@ class DriverTests { addressMustNotBeBound(executorService, hostAndPort) } } - private val portAllocation = PortAllocation.Incremental(10000) @Test fun `simple node startup and shutdown`() { @@ -46,14 +47,13 @@ class DriverTests { @Test fun `random free port allocation`() { - val nodeHandle = driver(portAllocation = portAllocation) { + val nodeHandle = driver(portAllocation = PortAllocation.RandomFree) { val nodeInfo = startNode(providedName = DUMMY_BANK_A.name) nodeMustBeUp(nodeInfo) } nodeMustBeDown(nodeHandle) } - @Test fun `debug mode enables debug logging level`() { // Make sure we're using the log4j2 config which writes to the log file diff --git a/testing/node-driver/src/main/kotlin/net/corda/testing/driver/Driver.kt b/testing/node-driver/src/main/kotlin/net/corda/testing/driver/Driver.kt index 3035fda888..68a197b8c8 100644 --- a/testing/node-driver/src/main/kotlin/net/corda/testing/driver/Driver.kt +++ b/testing/node-driver/src/main/kotlin/net/corda/testing/driver/Driver.kt @@ -16,13 +16,13 @@ import net.corda.node.services.config.VerifierType import net.corda.nodeapi.internal.config.User import net.corda.testing.DUMMY_NOTARY import net.corda.testing.internal.DriverDSLImpl +import net.corda.testing.internal.genericDriver +import net.corda.testing.internal.getTimestampAsDirectoryName import net.corda.testing.node.NotarySpec import java.net.InetSocketAddress import java.net.ServerSocket -import java.net.URL import java.nio.file.Path import java.nio.file.Paths -import java.security.cert.X509Certificate import java.util.concurrent.atomic.AtomicInteger /** @@ -136,7 +136,7 @@ data class NodeParameters( * (...) * } * - * Note that [DriverDSLImpl.startNode] does not wait for the node to start up synchronously, but rather returns a [CordaFuture] + * Note that [DriverDSL.startNode] does not wait for the node to start up synchronously, but rather returns a [CordaFuture] * of the [NodeInfo] that may be waited on, which completes when the new node registered with the network map service or * loaded node data from database. * @@ -145,14 +145,14 @@ data class NodeParameters( * @param isDebug Indicates whether the spawned nodes should start in jdwt debug mode and have debug level logging. * @param driverDirectory The base directory node directories go into, defaults to "build//". The node * directories themselves are "//", where legalName defaults to "-" - * and may be specified in [DriverDSLImpl.startNode]. + * and may be specified in [DriverDSL.startNode]. * @param portAllocation The port allocation strategy to use for the messaging and the web server addresses. Defaults to incremental. * @param debugPortAllocation The port allocation strategy to use for jvm debugging. Defaults to incremental. * @param systemProperties A Map of extra system properties which will be given to each new node. Defaults to empty. * @param useTestClock If true the test clock will be used in Node. * @param startNodesInProcess Provides the default behaviour of whether new nodes should start inside this process or * not. Note that this may be overridden in [DriverDSL.startNode]. - * @param notarySpecs The notaries advertised for this network. These nodes will be started automatically and will be + * @param notarySpecs The notaries advertised for this network. These nodes will be started automatically and will be * available from [DriverDSL.notaryHandles]. Defaults to a simple validating notary. * @param dsl The dsl itself. * @return The value returned in the [dsl] closure. @@ -192,50 +192,6 @@ fun driver( ) } -// TODO Move CompatibilityZoneParams and internalDriver into internal package - -/** - * @property url The base CZ URL for registration and network map updates - * @property rootCert If specified then the node will register itself using [url] and expect the registration response - * to be rooted at this cert. - */ -data class CompatibilityZoneParams(val url: URL, val rootCert: X509Certificate? = null) - -fun internalDriver( - isDebug: Boolean = DriverParameters().isDebug, - driverDirectory: Path = DriverParameters().driverDirectory, - portAllocation: PortAllocation = DriverParameters().portAllocation, - debugPortAllocation: PortAllocation = DriverParameters().debugPortAllocation, - systemProperties: Map = DriverParameters().systemProperties, - useTestClock: Boolean = DriverParameters().useTestClock, - initialiseSerialization: Boolean = DriverParameters().initialiseSerialization, - startNodesInProcess: Boolean = DriverParameters().startNodesInProcess, - waitForAllNodesToFinish: Boolean = DriverParameters().waitForNodesToFinish, - notarySpecs: List = DriverParameters().notarySpecs, - extraCordappPackagesToScan: List = DriverParameters().extraCordappPackagesToScan, - compatibilityZone: CompatibilityZoneParams? = null, - dsl: DriverDSLImpl.() -> A -): A { - return genericDriver( - driverDsl = DriverDSLImpl( - portAllocation = portAllocation, - debugPortAllocation = debugPortAllocation, - systemProperties = systemProperties, - driverDirectory = driverDirectory.toAbsolutePath(), - useTestClock = useTestClock, - isDebug = isDebug, - startNodesInProcess = startNodesInProcess, - waitForNodesToFinish = waitForAllNodesToFinish, - notarySpecs = notarySpecs, - extraCordappPackagesToScan = extraCordappPackagesToScan, - compatibilityZone = compatibilityZone - ), - coerce = { it }, - dsl = dsl, - initialiseSerialization = initialiseSerialization - ) -} - /** * Helper function for starting a [driver] with custom parameters from Java. * diff --git a/testing/node-driver/src/main/kotlin/net/corda/testing/driver/DriverDSL.kt b/testing/node-driver/src/main/kotlin/net/corda/testing/driver/DriverDSL.kt index f5fe3ef304..d8d3d1b49a 100644 --- a/testing/node-driver/src/main/kotlin/net/corda/testing/driver/DriverDSL.kt +++ b/testing/node-driver/src/main/kotlin/net/corda/testing/driver/DriverDSL.kt @@ -64,11 +64,9 @@ interface DriverDSL { verifierType: VerifierType = defaultParameters.verifierType, customOverrides: Map = defaultParameters.customOverrides, startInSameProcess: Boolean? = defaultParameters.startInSameProcess, - maximumHeapSize: String = defaultParameters.maximumHeapSize ): CordaFuture - /** * Helper function for starting a [Node] with custom parameters from Java. * diff --git a/testing/node-driver/src/main/kotlin/net/corda/testing/internal/DriverDSLImpl.kt b/testing/node-driver/src/main/kotlin/net/corda/testing/internal/DriverDSLImpl.kt index 2c398902ed..ce5d2568cb 100644 --- a/testing/node-driver/src/main/kotlin/net/corda/testing/internal/DriverDSLImpl.kt +++ b/testing/node-driver/src/main/kotlin/net/corda/testing/internal/DriverDSLImpl.kt @@ -94,8 +94,7 @@ class DriverDSLImpl( // TODO: this object will copy NodeInfo files from started nodes to other nodes additional-node-infos/ // This uses the FileSystem and adds a delay (~5 seconds) given by the time we wait before polling the file system. // Investigate whether we can avoid that. - // TODO: NodeInfoFilesCopier create observable threads in the init method, we should move that to a start method instead, changing this to lateinit instead to prevent that. - private lateinit var nodeInfoFilesCopier: NodeInfoFilesCopier + private var nodeInfoFilesCopier: NodeInfoFilesCopier? = null // Map from a nodes legal name to an observable emitting the number of nodes in its network map. private val countObservables = mutableMapOf>() private lateinit var _notaries: List @@ -327,7 +326,12 @@ class DriverDSLImpl( } _executorService = Executors.newScheduledThreadPool(2, ThreadFactoryBuilder().setNameFormat("driver-pool-thread-%d").build()) _shutdownManager = ShutdownManager(executorService) - shutdownManager.registerShutdown { nodeInfoFilesCopier.close() } + if (compatibilityZone == null) { + // Without a compatibility zone URL we have to copy the node info files ourselves to make sure the nodes see each other + nodeInfoFilesCopier = NodeInfoFilesCopier().also { + shutdownManager.registerShutdown(it::close) + } + } val notaryInfos = generateNotaryIdentities() // The network parameters must be serialised before starting any of the nodes networkParameters = NetworkParametersCopier(testNetworkParameters(notaryInfos)) @@ -493,10 +497,6 @@ class DriverDSLImpl( maximumHeapSize: String): CordaFuture { val configuration = config.parseAsNodeConfiguration() val baseDirectory = configuration.baseDirectory.createDirectories() - // Distribute node info file using file copier when network map service URL (compatibilityZoneURL) is null. - // TODO: need to implement the same in cordformation? - val nodeInfoFilesCopier = if (compatibilityZone == null) nodeInfoFilesCopier else null - nodeInfoFilesCopier?.addConfig(baseDirectory) networkParameters!!.install(baseDirectory) val onNodeExit: () -> Unit = { @@ -606,7 +606,7 @@ class DriverDSLImpl( node.internals.run() } node to nodeThread -}.flatMap { nodeAndThread -> + }.flatMap { nodeAndThread -> addressMustBeBoundFuture(executorService, nodeConf.p2pAddress).map { nodeAndThread } } } @@ -625,13 +625,19 @@ class DriverDSLImpl( // Write node.conf writeConfig(nodeConf.baseDirectory, "node.conf", config) - val systemProperties = overriddenSystemProperties + mapOf( + val systemProperties = mutableMapOf( "name" to nodeConf.myLegalName, "visualvm.display.name" to "corda-${nodeConf.myLegalName}", - Node.scanPackagesSystemProperty to cordappPackages.joinToString(Node.scanPackagesSeparator), "java.io.tmpdir" to System.getProperty("java.io.tmpdir"), // Inherit from parent process "log4j2.debug" to if(debugPort != null) "true" else "false" ) + + if (cordappPackages.isNotEmpty()) { + systemProperties += Node.scanPackagesSystemProperty to cordappPackages.joinToString(Node.scanPackagesSeparator) + } + + systemProperties += overriddenSystemProperties + // See experimental/quasar-hook/README.md for how to generate. val excludePattern = "x(antlr**;bftsmart**;ch**;co.paralleluniverse**;com.codahale**;com.esotericsoftware**;" + "com.fasterxml**;com.google**;com.ibm**;com.intellij**;com.jcabi**;com.nhaarman**;com.opengamma**;" + @@ -805,7 +811,8 @@ fun genericDriver( startNodesInProcess = startNodesInProcess, waitForNodesToFinish = waitForNodesToFinish, extraCordappPackagesToScan = extraCordappPackagesToScan, - notarySpecs = notarySpecs + notarySpecs = notarySpecs, + compatibilityZone = null ) ) val shutdownHook = addShutdownHook(driverDsl::shutdown) @@ -822,6 +829,48 @@ fun genericDriver( } } +/** + * @property url The base CZ URL for registration and network map updates + * @property rootCert If specified then the node will register itself using [url] and expect the registration response + * to be rooted at this cert. + */ +data class CompatibilityZoneParams(val url: URL, val rootCert: X509Certificate? = null) + +fun internalDriver( + isDebug: Boolean = DriverParameters().isDebug, + driverDirectory: Path = DriverParameters().driverDirectory, + portAllocation: PortAllocation = DriverParameters().portAllocation, + debugPortAllocation: PortAllocation = DriverParameters().debugPortAllocation, + systemProperties: Map = DriverParameters().systemProperties, + useTestClock: Boolean = DriverParameters().useTestClock, + initialiseSerialization: Boolean = DriverParameters().initialiseSerialization, + startNodesInProcess: Boolean = DriverParameters().startNodesInProcess, + waitForAllNodesToFinish: Boolean = DriverParameters().waitForAllNodesToFinish, + notarySpecs: List = DriverParameters().notarySpecs, + extraCordappPackagesToScan: List = DriverParameters().extraCordappPackagesToScan, + compatibilityZone: CompatibilityZoneParams? = null, + dsl: DriverDSLImpl.() -> A +): A { + return genericDriver( + driverDsl = DriverDSLImpl( + portAllocation = portAllocation, + debugPortAllocation = debugPortAllocation, + systemProperties = systemProperties, + driverDirectory = driverDirectory.toAbsolutePath(), + useTestClock = useTestClock, + isDebug = isDebug, + startNodesInProcess = startNodesInProcess, + waitForNodesToFinish = waitForAllNodesToFinish, + notarySpecs = notarySpecs, + extraCordappPackagesToScan = extraCordappPackagesToScan, + compatibilityZone = compatibilityZone + ), + coerce = { it }, + dsl = dsl, + initialiseSerialization = initialiseSerialization + ) +} + fun getTimestampAsDirectoryName(): String { return DateTimeFormatter.ofPattern("yyyyMMddHHmmss").withZone(ZoneOffset.UTC).format(Instant.now()) } diff --git a/testing/node-driver/src/main/kotlin/net/corda/testing/internal/RPCDriver.kt b/testing/node-driver/src/main/kotlin/net/corda/testing/internal/RPCDriver.kt index b0c99dd6b2..949cf2649d 100644 --- a/testing/node-driver/src/main/kotlin/net/corda/testing/internal/RPCDriver.kt +++ b/testing/node-driver/src/main/kotlin/net/corda/testing/internal/RPCDriver.kt @@ -10,7 +10,9 @@ import net.corda.core.context.Trace import net.corda.core.crypto.random63BitValue import net.corda.core.identity.CordaX500Name import net.corda.core.internal.concurrent.doneFuture +import net.corda.core.internal.concurrent.fork import net.corda.core.internal.concurrent.map +import net.corda.core.internal.div import net.corda.core.internal.uncheckedCast import net.corda.core.messaging.RPCOps import net.corda.core.utilities.NetworkHostAndPort diff --git a/testing/node-driver/src/main/kotlin/net/corda/testing/internal/demorun/DemoRunner.kt b/testing/node-driver/src/main/kotlin/net/corda/testing/internal/demorun/DemoRunner.kt index 937f3981d0..fb8423fce8 100644 --- a/testing/node-driver/src/main/kotlin/net/corda/testing/internal/demorun/DemoRunner.kt +++ b/testing/node-driver/src/main/kotlin/net/corda/testing/internal/demorun/DemoRunner.kt @@ -5,8 +5,9 @@ package net.corda.testing.internal.demorun import net.corda.cordform.CordformDefinition import net.corda.cordform.CordformNode import net.corda.core.utilities.NetworkHostAndPort +import net.corda.core.utilities.getOrThrow import net.corda.testing.driver.PortAllocation -import net.corda.testing.driver.internalDriver +import net.corda.testing.internal.internalDriver fun CordformDefinition.clean() { System.err.println("Deleting: $nodesDirectory") diff --git a/testing/node-driver/src/main/kotlin/net/corda/testing/node/network/NetworkMapServer.kt b/testing/node-driver/src/main/kotlin/net/corda/testing/node/network/NetworkMapServer.kt index d6814722cf..4e6eb0a83e 100644 --- a/testing/node-driver/src/main/kotlin/net/corda/testing/node/network/NetworkMapServer.kt +++ b/testing/node-driver/src/main/kotlin/net/corda/testing/node/network/NetworkMapServer.kt @@ -46,7 +46,7 @@ class NetworkMapServer(cacheTimeout: Duration, private fun networkMapKeyAndCert(rootCAKeyAndCert: CertificateAndKeyPair): CertificateAndKeyPair { val networkMapKey = Crypto.generateKeyPair(X509Utilities.DEFAULT_TLS_SIGNATURE_SCHEME) val networkMapCert = X509Utilities.createCertificate( - CertificateType.IDENTITY, + CertificateType.INTERMEDIATE_CA, rootCAKeyAndCert.certificate, rootCAKeyAndCert.keyPair, X500Name("CN=Corda Network Map,L=London"), diff --git a/verifier/src/integration-test/kotlin/net/corda/verifier/VerifierDriver.kt b/verifier/src/integration-test/kotlin/net/corda/verifier/VerifierDriver.kt index 8a5b0d80a0..31f691210a 100644 --- a/verifier/src/integration-test/kotlin/net/corda/verifier/VerifierDriver.kt +++ b/verifier/src/integration-test/kotlin/net/corda/verifier/VerifierDriver.kt @@ -7,7 +7,10 @@ import net.corda.core.crypto.random63BitValue import net.corda.core.identity.CordaX500Name import net.corda.core.internal.concurrent.OpenFuture import net.corda.core.internal.concurrent.doneFuture +import net.corda.core.internal.concurrent.fork import net.corda.core.internal.concurrent.openFuture +import net.corda.core.internal.createDirectories +import net.corda.core.internal.div import net.corda.core.serialization.internal.nodeSerializationEnv import net.corda.core.transactions.LedgerTransaction import net.corda.core.utilities.NetworkHostAndPort @@ -22,9 +25,7 @@ import net.corda.nodeapi.internal.config.SSLConfiguration import net.corda.testing.driver.NodeHandle import net.corda.testing.driver.PortAllocation import net.corda.testing.driver.driver -import net.corda.testing.internal.DriverDSLImpl -import net.corda.testing.internal.ProcessUtilities -import net.corda.testing.internal.poll +import net.corda.testing.internal.* import net.corda.testing.node.NotarySpec import org.apache.activemq.artemis.api.core.SimpleString import org.apache.activemq.artemis.api.core.client.ActiveMQClient @@ -180,7 +181,6 @@ data class VerifierDriverDSL(private val driverDSL: DriverDSLImpl) : InternalDri val securityManager = object : ActiveMQSecurityManager { // We don't need auth, SSL is good enough override fun validateUser(user: String?, password: String?) = true - override fun validateUserAndRole(user: String?, password: String?, roles: MutableSet?, checkType: CheckType?) = true } From 249d8d1ac7acafc5bc212db4c60507f404f5a0f9 Mon Sep 17 00:00:00 2001 From: Shams Asari Date: Mon, 11 Dec 2017 10:42:50 +0000 Subject: [PATCH 23/23] Further merge fixes --- .../src/main/kotlin/net/corda/testing/driver/Driver.kt | 3 +-- .../kotlin/net/corda/testing/internal/DriverDSLImpl.kt | 8 ++++++-- .../main/kotlin/net/corda/testing/internal/RPCDriver.kt | 1 + .../net/corda/testing/internal/demorun/DemoRunner.kt | 1 + .../kotlin/net/corda/verifier/VerifierDriver.kt | 1 - 5 files changed, 9 insertions(+), 5 deletions(-) diff --git a/testing/node-driver/src/main/kotlin/net/corda/testing/driver/Driver.kt b/testing/node-driver/src/main/kotlin/net/corda/testing/driver/Driver.kt index 6b65001a28..364250d13c 100644 --- a/testing/node-driver/src/main/kotlin/net/corda/testing/driver/Driver.kt +++ b/testing/node-driver/src/main/kotlin/net/corda/testing/driver/Driver.kt @@ -177,7 +177,7 @@ fun driver( waitForAllNodesToFinish: Boolean = defaultParameters.waitForAllNodesToFinish, notarySpecs: List = defaultParameters.notarySpecs, extraCordappPackagesToScan: List = defaultParameters.extraCordappPackagesToScan, - jmxPolicy: JmxPolicy = JmxPolicy(), + jmxPolicy: JmxPolicy = defaultParameters.jmxPolicy, dsl: DriverDSL.() -> A ): A { return genericDriver( @@ -230,7 +230,6 @@ data class DriverParameters( val notarySpecs: List = listOf(NotarySpec(DUMMY_NOTARY.name)), val extraCordappPackagesToScan: List = emptyList(), val jmxPolicy: JmxPolicy = JmxPolicy() - ) { fun setIsDebug(isDebug: Boolean) = copy(isDebug = isDebug) fun setDriverDirectory(driverDirectory: Path) = copy(driverDirectory = driverDirectory) diff --git a/testing/node-driver/src/main/kotlin/net/corda/testing/internal/DriverDSLImpl.kt b/testing/node-driver/src/main/kotlin/net/corda/testing/internal/DriverDSLImpl.kt index 839828e9b7..8ecb8cef13 100644 --- a/testing/node-driver/src/main/kotlin/net/corda/testing/internal/DriverDSLImpl.kt +++ b/testing/node-driver/src/main/kotlin/net/corda/testing/internal/DriverDSLImpl.kt @@ -498,7 +498,8 @@ class DriverDSLImpl( private fun startOutOfProcessNodeRegistration(config: Config, configuration: NodeConfiguration): CordaFuture { val debugPort = if (isDebug) debugPortAllocation.nextPort() else null - val process = startOutOfProcessNode(configuration, config, quasarJarPath, debugPort, + val monitorPort = if (jmxPolicy.startJmxHttpServer) jmxPolicy.jmxHttpServerPortAllocation?.nextPort() else null + val process = startOutOfProcessNode(configuration, config, quasarJarPath, debugPort, jolokiaJarPath, monitorPort, systemProperties, cordappPackages, "200m", initialRegistration = true) return poll(executorService, "node registration (${configuration.myLegalName})") { @@ -537,7 +538,8 @@ class DriverDSLImpl( } } else { val debugPort = if (isDebug) debugPortAllocation.nextPort() else null - val monitorPort = if (jmxPolicy.startJmxHttpServer) jmxPolicy.jmxHttpServerPortAllocation?.nextPort() else nullval process = startOutOfProcessNode(configuration, config, quasarJarPath, debugPort,jolokiaJarPath, monitorPort, systemProperties, cordappPackages, maximumHeapSize, initialRegistration = false) + val monitorPort = if (jmxPolicy.startJmxHttpServer) jmxPolicy.jmxHttpServerPortAllocation?.nextPort() else null + val process = startOutOfProcessNode(configuration, config, quasarJarPath, debugPort, jolokiaJarPath, monitorPort, systemProperties, cordappPackages, maximumHeapSize, initialRegistration = false) if (waitForNodesToFinish) { state.locked { processes += process @@ -867,6 +869,7 @@ fun internalDriver( waitForAllNodesToFinish: Boolean = DriverParameters().waitForAllNodesToFinish, notarySpecs: List = DriverParameters().notarySpecs, extraCordappPackagesToScan: List = DriverParameters().extraCordappPackagesToScan, + jmxPolicy: JmxPolicy = DriverParameters().jmxPolicy, compatibilityZone: CompatibilityZoneParams? = null, dsl: DriverDSLImpl.() -> A ): A { @@ -882,6 +885,7 @@ fun internalDriver( waitForNodesToFinish = waitForAllNodesToFinish, notarySpecs = notarySpecs, extraCordappPackagesToScan = extraCordappPackagesToScan, + jmxPolicy = jmxPolicy, compatibilityZone = compatibilityZone ), coerce = { it }, diff --git a/testing/node-driver/src/main/kotlin/net/corda/testing/internal/RPCDriver.kt b/testing/node-driver/src/main/kotlin/net/corda/testing/internal/RPCDriver.kt index 1b416fcbeb..9684db5450 100644 --- a/testing/node-driver/src/main/kotlin/net/corda/testing/internal/RPCDriver.kt +++ b/testing/node-driver/src/main/kotlin/net/corda/testing/internal/RPCDriver.kt @@ -25,6 +25,7 @@ import net.corda.nodeapi.ConnectionDirection import net.corda.nodeapi.RPCApi import net.corda.nodeapi.internal.config.User import net.corda.nodeapi.internal.serialization.KRYO_RPC_CLIENT_CONTEXT +import net.corda.testing.driver.JmxPolicy import net.corda.testing.driver.PortAllocation import net.corda.testing.node.NotarySpec import org.apache.activemq.artemis.api.core.SimpleString diff --git a/testing/node-driver/src/main/kotlin/net/corda/testing/internal/demorun/DemoRunner.kt b/testing/node-driver/src/main/kotlin/net/corda/testing/internal/demorun/DemoRunner.kt index 19279a2a6b..a13badccd4 100644 --- a/testing/node-driver/src/main/kotlin/net/corda/testing/internal/demorun/DemoRunner.kt +++ b/testing/node-driver/src/main/kotlin/net/corda/testing/internal/demorun/DemoRunner.kt @@ -5,6 +5,7 @@ package net.corda.testing.internal.demorun import net.corda.cordform.CordformDefinition import net.corda.cordform.CordformNode import net.corda.core.utilities.NetworkHostAndPort +import net.corda.core.utilities.getOrThrow import net.corda.testing.driver.JmxPolicy import net.corda.testing.driver.PortAllocation import net.corda.testing.internal.internalDriver diff --git a/verifier/src/integration-test/kotlin/net/corda/verifier/VerifierDriver.kt b/verifier/src/integration-test/kotlin/net/corda/verifier/VerifierDriver.kt index 90845ae0b5..4fa35ff2b9 100644 --- a/verifier/src/integration-test/kotlin/net/corda/verifier/VerifierDriver.kt +++ b/verifier/src/integration-test/kotlin/net/corda/verifier/VerifierDriver.kt @@ -22,7 +22,6 @@ import net.corda.nodeapi.VerifierApi import net.corda.nodeapi.internal.ArtemisMessagingComponent.Companion.NODE_USER import net.corda.nodeapi.internal.config.NodeSSLConfiguration import net.corda.nodeapi.internal.config.SSLConfiguration -import net.corda.nodeapi.internal.ArtemisMessagingComponent.Companion.NODE_USER import net.corda.testing.driver.JmxPolicy import net.corda.testing.driver.NodeHandle import net.corda.testing.driver.PortAllocation