From 2dc4251cbe13176c8941c4e50aab24a67f2f6d5c Mon Sep 17 00:00:00 2001 From: Patrick Kuo Date: Mon, 20 Nov 2017 09:57:03 +0000 Subject: [PATCH 01/42] 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/42] 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/42] 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/42] 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/42] 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/42] 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/42] 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/42] 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/42] 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/42] 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/42] 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/42] 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/42] 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/42] 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/42] 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/42] 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/42] 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/42] 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/42] 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/42] 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 81eb0c189841210ee31f4287e8fa65bec16ad5e7 Mon Sep 17 00:00:00 2001 From: Katelyn Baker Date: Wed, 8 Nov 2017 16:23:38 +0000 Subject: [PATCH 21/42] CORDA-780 / CORDA-786 - Enable AMQP for P2P and Storage Contexts Add plugable mechanism for CorDapps such that they can add their own custom serializers --- .../internal/KryoClientSerializationScheme.kt | 2 +- .../kotlin/net/corda/core/cordapp/Cordapp.kt | 5 + .../core/internal/cordapp/CordappImpl.kt | 3 + .../serialization/CordaCustomSerializer.kt | 8 ++ .../SerializationCustomSerializer.kt | 46 ++++++++++ docs/source/cordapp-custom-serializers.rst | 69 ++++++++++++++ docs/source/release-notes.rst | 14 +++ .../amqp/AMQPSerializationScheme.kt | 19 +++- .../amqp/CorDappCustomSerializer.kt | 41 +++++++++ .../serialization/amqp/CustomSerializer.kt | 19 ++-- .../serialization/amqp/SerializationHelper.kt | 10 +- .../serialization/amqp/SerializerFactory.kt | 17 +++- .../amqp/CorDappSerializerTests.kt | 92 +++++++++++++++++++ .../amqp/OverridePKSerializerTest.kt | 2 +- .../amqp/SerializationOutputTests.kt | 2 +- .../kotlin/net/corda/node/internal/Node.kt | 6 +- .../node/internal/cordapp/CordappLoader.kt | 33 ++++--- .../transactions/RaftUniquenessProvider.kt | 2 +- .../net/corda/vega/SimmValuationTest.kt | 2 +- .../corda/vega/contracts/PortfolioState.kt | 2 +- .../corda/vega/plugin/SimmPluginRegistry.kt | 4 +- ...urrencyParameterSensitivitiesSerializer.kt | 21 +++++ .../CurrencyParameterSensitivitySerialiser.kt | 33 +++++++ .../customserializers/CurrencySerializer.kt | 20 ++++ .../DoubleArraySerializer.kt | 20 ++++ .../MultiCurrencyAmountSerializer.kt | 22 +++++ .../TenorDateParameterMetadataSerializer.kt | 23 +++++ .../customserializers/TenorSerializer.kt | 21 +++++ .../kotlin/net/corda/testing/node/MockNode.kt | 2 + .../corda/testing/SerializationTestHelpers.kt | 11 +-- .../corda/testing/node/MockCordappProvider.kt | 13 ++- .../kotlin/net/corda/verifier/Verifier.kt | 10 +- 32 files changed, 539 insertions(+), 55 deletions(-) create mode 100644 core/src/main/kotlin/net/corda/core/serialization/CordaCustomSerializer.kt create mode 100644 core/src/main/kotlin/net/corda/core/serialization/SerializationCustomSerializer.kt create mode 100644 docs/source/cordapp-custom-serializers.rst create mode 100644 node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/amqp/CorDappCustomSerializer.kt create mode 100644 node-api/src/test/kotlin/net/corda/nodeapi/internal/serialization/amqp/CorDappSerializerTests.kt create mode 100644 samples/simm-valuation-demo/src/main/kotlin/net/corda/vega/plugin/customserializers/CurrencyParameterSensitivitiesSerializer.kt create mode 100644 samples/simm-valuation-demo/src/main/kotlin/net/corda/vega/plugin/customserializers/CurrencyParameterSensitivitySerialiser.kt create mode 100644 samples/simm-valuation-demo/src/main/kotlin/net/corda/vega/plugin/customserializers/CurrencySerializer.kt create mode 100644 samples/simm-valuation-demo/src/main/kotlin/net/corda/vega/plugin/customserializers/DoubleArraySerializer.kt create mode 100644 samples/simm-valuation-demo/src/main/kotlin/net/corda/vega/plugin/customserializers/MultiCurrencyAmountSerializer.kt create mode 100644 samples/simm-valuation-demo/src/main/kotlin/net/corda/vega/plugin/customserializers/TenorDateParameterMetadataSerializer.kt create mode 100644 samples/simm-valuation-demo/src/main/kotlin/net/corda/vega/plugin/customserializers/TenorSerializer.kt diff --git a/client/rpc/src/main/kotlin/net/corda/client/rpc/internal/KryoClientSerializationScheme.kt b/client/rpc/src/main/kotlin/net/corda/client/rpc/internal/KryoClientSerializationScheme.kt index eb9f22e937..5a6adc58b9 100644 --- a/client/rpc/src/main/kotlin/net/corda/client/rpc/internal/KryoClientSerializationScheme.kt +++ b/client/rpc/src/main/kotlin/net/corda/client/rpc/internal/KryoClientSerializationScheme.kt @@ -41,7 +41,7 @@ class KryoClientSerializationScheme : AbstractKryoSerializationScheme() { return SerializationEnvironmentImpl( SerializationFactoryImpl().apply { registerScheme(KryoClientSerializationScheme()) - registerScheme(AMQPClientSerializationScheme()) + registerScheme(AMQPClientSerializationScheme(emptyList())) }, KRYO_P2P_CONTEXT, rpcClientContext = KRYO_RPC_CLIENT_CONTEXT) diff --git a/core/src/main/kotlin/net/corda/core/cordapp/Cordapp.kt b/core/src/main/kotlin/net/corda/core/cordapp/Cordapp.kt index f4fe71ead0..d4df6eb5ec 100644 --- a/core/src/main/kotlin/net/corda/core/cordapp/Cordapp.kt +++ b/core/src/main/kotlin/net/corda/core/cordapp/Cordapp.kt @@ -3,6 +3,7 @@ package net.corda.core.cordapp import net.corda.core.DoNotImplement import net.corda.core.flows.FlowLogic import net.corda.core.schemas.MappedSchema +import net.corda.core.serialization.SerializationCustomSerializer import net.corda.core.serialization.SerializationWhitelist import net.corda.core.serialization.SerializeAsToken import java.net.URL @@ -22,6 +23,8 @@ import java.net.URL * @property schedulableFlows List of flows startable by the scheduler * @property services List of RPC services * @property serializationWhitelists List of Corda plugin registries + * @property serializationCustomSerializerProxies List of Proxy classes used by the custom serializers + * @property serializationCustomSerializers List of serializers * @property customSchemas List of custom schemas * @property jarPath The path to the JAR for this CorDapp */ @@ -35,6 +38,8 @@ interface Cordapp { val schedulableFlows: List>> val services: List> val serializationWhitelists: List + val serializationCustomSerializerProxies: List> + val serializationCustomSerializers: List> val customSchemas: Set val jarPath: URL val cordappClasses: List diff --git a/core/src/main/kotlin/net/corda/core/internal/cordapp/CordappImpl.kt b/core/src/main/kotlin/net/corda/core/internal/cordapp/CordappImpl.kt index 02410db37a..7aaab3b8b3 100644 --- a/core/src/main/kotlin/net/corda/core/internal/cordapp/CordappImpl.kt +++ b/core/src/main/kotlin/net/corda/core/internal/cordapp/CordappImpl.kt @@ -3,6 +3,7 @@ package net.corda.core.internal.cordapp import net.corda.core.cordapp.Cordapp import net.corda.core.flows.FlowLogic import net.corda.core.schemas.MappedSchema +import net.corda.core.serialization.SerializationCustomSerializer import net.corda.core.serialization.SerializationWhitelist import net.corda.core.serialization.SerializeAsToken import java.io.File @@ -16,6 +17,8 @@ data class CordappImpl( override val schedulableFlows: List>>, override val services: List>, override val serializationWhitelists: List, + override val serializationCustomSerializerProxies: List>, + override val serializationCustomSerializers: List>, override val customSchemas: Set, override val jarPath: URL) : Cordapp { override val name: String = File(jarPath.toURI()).name.removeSuffix(".jar") diff --git a/core/src/main/kotlin/net/corda/core/serialization/CordaCustomSerializer.kt b/core/src/main/kotlin/net/corda/core/serialization/CordaCustomSerializer.kt new file mode 100644 index 0000000000..724e8d7db8 --- /dev/null +++ b/core/src/main/kotlin/net/corda/core/serialization/CordaCustomSerializer.kt @@ -0,0 +1,8 @@ +package net.corda.core.serialization + +@Target(AnnotationTarget.CLASS) +annotation class CordaCustomSerializer + +@Target(AnnotationTarget.CLASS) +annotation class CordaCustomSerializerProxy + diff --git a/core/src/main/kotlin/net/corda/core/serialization/SerializationCustomSerializer.kt b/core/src/main/kotlin/net/corda/core/serialization/SerializationCustomSerializer.kt new file mode 100644 index 0000000000..0f384196f5 --- /dev/null +++ b/core/src/main/kotlin/net/corda/core/serialization/SerializationCustomSerializer.kt @@ -0,0 +1,46 @@ +package net.corda.core.serialization + +import java.lang.reflect.Type + +/** + * Allows CorDapps to provide custom serializers for third party libraries where those libraries cannot + * be recompiled with the -parmater flag rendering their classes natively serializable by Corda. In this case + * a proxy serializer can be written that extends this type whose purpose is to move between those an + * unserializable types and an intermediate representation + * + * NOTE: The proxy object must be specified as a seperate class. However, this can be defined within the + * scope of the serializer. Also, that class must be annotated with the [CordaCustomSerializerProxy] + * annotation + * + * For instances of this class to be discoverable they must be annotated with the [CordaCustomSerializer] + * annotation. + * + * Failing to apply either annotation will result in the class not being loaded by Corda and thus serialization + * failing + * + * @property type the type of the object that this class is proxying + * @property ptype the type of the proxy object used as an intermediate representation of [type] + */ +interface SerializationCustomSerializer { + /** + * Should facilitate the conversion of the third party object into the serializable + * local class specified by [ptype] + */ + fun toProxy(obj: Any) : Any + + /** + * Should facilitate the conversion of the proxy object into a new instance of the + * unserializable type + */ + fun fromProxy(proxy: Any) : Any + + /** + * Should be set to the type of the object being proxied + */ + val type: Type + + /** + * Should be set to the proxy objects type + */ + val ptype: Type +} \ No newline at end of file diff --git a/docs/source/cordapp-custom-serializers.rst b/docs/source/cordapp-custom-serializers.rst new file mode 100644 index 0000000000..e5e38e32b8 --- /dev/null +++ b/docs/source/cordapp-custom-serializers.rst @@ -0,0 +1,69 @@ +Pluggable Serializers for CorDapps +================================== + +To be serializable by Corda Java classes must be compiled with the -parameter switch to enable mathcing of ass property +to constructor parameter. However, when this isn't possible CorDapps can provide custom proxy serialiszers that Corda +can use to move from types it cannot serialiser to an interim represtnation that it can with the transformation to and +from this proxy object being handled by the supplied serialiser. + +Serializer Location +------------------- +Custom serializers should be placed in the plugins directory fo a CorDapp or a sub directory (placing it in a sub +directory however does require that directory be added to the list of locations scanned within the jar) + +Writing a Custom Serializer +-------------------------- + +Serializers must + * Inherit from net.corda.core.serialization.SerializationCustomSerializer + * Be annotated with the @CordaCustomSerializer annotation + * Provide a proxy class to transform the objectto and from + * Have that proxy class annotated with the @CordaCustomSerializerProxy annotation + +Serializers inheriting from SerializationCustomSerializer have to implement two methods and two types + +Example +------- + +Consider this example class + +.. sourcecode:: java + public final class Example { + private final Int a + private final Int b + + private Example(Int a, Int b) { + this.a = a; + this.b = b; + } + + public static Example of (int[] a) { return Example(a[0], a[1]); } + + public int getA() { return a; } + public int getB() { return b; } + } + +This would require a serialiser as follows + +.. sourcecode:: kotlin + @CordaCustomSerializer + class ExampleSerializer : SerializationCustomSerializer { + @CordaCustomSerializerProxy + data class Proxy(val a: Int, val b: Int) + + override fun toProxy(obj: Any): Any = Proxy((obj as Example).a, obj.b) + + override fun fromProxy(proxy: Any): Any { + val constructorArg = IntArray(2); + constructorArg[0] = (proxy as Proxy).a + constructorArg[1] = proxy.b + return Example.create(constructorArg) + } + + override val type: Type get() = Example::class.java + override val ptype: Type get() = Proxy::class.java + } + + + + diff --git a/docs/source/release-notes.rst b/docs/source/release-notes.rst index 36aeb6c6c5..631a20a628 100644 --- a/docs/source/release-notes.rst +++ b/docs/source/release-notes.rst @@ -12,6 +12,20 @@ Unreleased That is the ability to alter an enum constant and, as long as certain rules are followed and the correct annotations applied, have older and newer instances of that enumeration be understood. +* **AMQP Enabled** + +AMQP Serialization is now enabled for both peer to peer communication and writing states to the vault. This change +brings a stable format Corda can support internally throughout it's lifetime that meets the needs of Corda and our +users. + +* **CorDapp Custom Serializers** + +To allow interop with third party libraries that cannot be recompiled we add functionality that allows custom serialises +to be written for those classes provided. If needed, a proxy object can be created as an interim step that allows Corda's internal +serialisers to operate on those types. + +A good example of this is the SIMM valuation demo which has a number of such serializers defined in the plugin/customserializers package + Release 2.0 ---------- Following quickly on the heels of the release of Corda 1.0, Corda version 2.0 consolidates diff --git a/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/amqp/AMQPSerializationScheme.kt b/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/amqp/AMQPSerializationScheme.kt index 4327d114ea..62172dc12c 100644 --- a/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/amqp/AMQPSerializationScheme.kt +++ b/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/amqp/AMQPSerializationScheme.kt @@ -2,6 +2,7 @@ package net.corda.nodeapi.internal.serialization.amqp +import net.corda.core.cordapp.Cordapp import net.corda.core.serialization.* import net.corda.core.utilities.ByteSequence import net.corda.nodeapi.internal.serialization.DefaultWhitelist @@ -24,7 +25,8 @@ fun SerializerFactory.addToWhitelist(vararg types: Class<*>) { } } -abstract class AbstractAMQPSerializationScheme : SerializationScheme { +abstract class AbstractAMQPSerializationScheme(val cordappLoader: List) : SerializationScheme { + companion object { private val serializationWhitelists: List by lazy { ServiceLoader.load(SerializationWhitelist::class.java, this::class.java.classLoader).toList() + DefaultWhitelist @@ -62,8 +64,15 @@ abstract class AbstractAMQPSerializationScheme : SerializationScheme { register(net.corda.nodeapi.internal.serialization.amqp.custom.EnumSetSerializer(this)) register(net.corda.nodeapi.internal.serialization.amqp.custom.ContractAttachmentSerializer(this)) } - for (whitelistProvider in serializationWhitelists) + for (whitelistProvider in serializationWhitelists) { factory.addToWhitelist(*whitelistProvider.whitelist.toTypedArray()) + } + + cordappLoader.forEach { ca -> + ca.serializationCustomSerializers.forEach { + factory.registerExternal(CorDappCustomSerializer(it.newInstance(), factory)) + } + } } private val serializerFactoriesForContexts = ConcurrentHashMap, SerializerFactory>() @@ -97,11 +106,11 @@ abstract class AbstractAMQPSerializationScheme : SerializationScheme { return SerializationOutput(serializerFactory).serialize(obj) } - protected fun canDeserializeVersion(byteSequence: ByteSequence): Boolean = AMQP_ENABLED && byteSequence == AmqpHeaderV1_0 + protected fun canDeserializeVersion(byteSequence: ByteSequence): Boolean = byteSequence == AmqpHeaderV1_0 } // TODO: This will eventually cover server RPC as well and move to node module, but for now this is not implemented -class AMQPServerSerializationScheme : AbstractAMQPSerializationScheme() { +class AMQPServerSerializationScheme(cordapps: List = emptyList()) : AbstractAMQPSerializationScheme(cordapps) { override fun rpcClientSerializerFactory(context: SerializationContext): SerializerFactory { throw UnsupportedOperationException() } @@ -118,7 +127,7 @@ class AMQPServerSerializationScheme : AbstractAMQPSerializationScheme() { } // TODO: This will eventually cover client RPC as well and move to client module, but for now this is not implemented -class AMQPClientSerializationScheme : AbstractAMQPSerializationScheme() { +class AMQPClientSerializationScheme(cordapps: List = emptyList()) : AbstractAMQPSerializationScheme(cordapps) { override fun rpcClientSerializerFactory(context: SerializationContext): SerializerFactory { TODO("not implemented") //To change body of created functions use File | Settings | File Templates. } diff --git a/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/amqp/CorDappCustomSerializer.kt b/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/amqp/CorDappCustomSerializer.kt new file mode 100644 index 0000000000..87be4312d1 --- /dev/null +++ b/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/amqp/CorDappCustomSerializer.kt @@ -0,0 +1,41 @@ +package net.corda.nodeapi.internal.serialization.amqp + +import net.corda.core.internal.uncheckedCast +import net.corda.core.serialization.SerializationCustomSerializer +import net.corda.nodeapi.internal.serialization.amqp.SerializerFactory.Companion.nameForType +import org.apache.qpid.proton.amqp.Symbol +import org.apache.qpid.proton.codec.Data +import java.lang.reflect.Type + +class CorDappCustomSerializer( + private val serialiser: SerializationCustomSerializer, + factory: SerializerFactory) + : AMQPSerializer, SerializerFor { + override val revealSubclassesInSchema: Boolean get() = false + override val type: Type get() = serialiser.type + override val typeDescriptor = Symbol.valueOf("$DESCRIPTOR_DOMAIN:${nameForType(type)}") + val descriptor: Descriptor = Descriptor(typeDescriptor) + + private val proxySerializer: ObjectSerializer by lazy { ObjectSerializer(serialiser.ptype, factory) } + + override fun writeClassInfo(output: SerializationOutput) {} + + override fun writeObject(obj: Any, data: Data, type: Type, output: SerializationOutput) { + val proxy = serialiser.toProxy(obj) + + data.withDescribed(descriptor) { + data.withList { + for (property in proxySerializer.propertySerializers) { + property.writeProperty(proxy, this, output) + } + } + } + } + + override fun readObject(obj: Any, schema: Schema, input: DeserializationInput): Any { + return serialiser.fromProxy(uncheckedCast(proxySerializer.readObject(obj, schema, input))) + } + + override fun isSerializerFor(clazz: Class<*>): Boolean = clazz == type +} + diff --git a/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/amqp/CustomSerializer.kt b/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/amqp/CustomSerializer.kt index 870bfbaccc..d2180b7518 100644 --- a/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/amqp/CustomSerializer.kt +++ b/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/amqp/CustomSerializer.kt @@ -6,22 +6,27 @@ import org.apache.qpid.proton.amqp.Symbol import org.apache.qpid.proton.codec.Data import java.lang.reflect.Type +interface SerializerFor { + /** + * This method should return true if the custom serializer can serialize an instance of the class passed as the + * parameter. + */ + fun isSerializerFor(clazz: Class<*>): Boolean + + val revealSubclassesInSchema: Boolean +} + /** * Base class for serializers of core platform types that do not conform to the usual serialization rules and thus * cannot be automatically serialized. */ -abstract class CustomSerializer : AMQPSerializer { +abstract class CustomSerializer : AMQPSerializer, SerializerFor { /** * This is a collection of custom serializers that this custom serializer depends on. e.g. for proxy objects * that refer to other custom types etc. */ open val additionalSerializers: Iterable> = emptyList() - /** - * This method should return true if the custom serializer can serialize an instance of the class passed as the - * parameter. - */ - abstract fun isSerializerFor(clazz: Class<*>): Boolean protected abstract val descriptor: Descriptor /** @@ -33,7 +38,7 @@ abstract class CustomSerializer : AMQPSerializer { /** * Whether subclasses using this serializer via inheritance should have a mapping in the schema. */ - open val revealSubclassesInSchema: Boolean = false + override val revealSubclassesInSchema: Boolean get() = false override fun writeObject(obj: Any, data: Data, type: Type, output: SerializationOutput) { data.withDescribed(descriptor) { diff --git a/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/amqp/SerializationHelper.kt b/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/amqp/SerializationHelper.kt index 6a1377d083..8b677630d2 100644 --- a/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/amqp/SerializationHelper.kt +++ b/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/amqp/SerializationHelper.kt @@ -82,11 +82,13 @@ private fun propertiesForSerializationFromConstructor(kotlinConstructo for (param in kotlinConstructor.parameters) { val name = param.name ?: throw NotSerializableException("Constructor parameter of $clazz has no name.") val matchingProperty = properties[name] ?: - throw NotSerializableException("No property matching constructor parameter named '$name' of '$clazz'." + - " If using Java, check that you have the -parameters option specified in the Java compiler.") + throw NotSerializableException("No property matching constructor parameter named '$name' of '$clazz'. " + + "If using Java, check that you have the -parameters option specified in the Java compiler. " + + "Alternately, provide a proxy serializer (SerializationCustomSerializer) if recompiling isn't an option") // Check that the method has a getter in java. - val getter = matchingProperty.readMethod ?: throw NotSerializableException("Property has no getter method for $name of $clazz." + - " If using Java and the parameter name looks anonymous, check that you have the -parameters option specified in the Java compiler.") + val getter = matchingProperty.readMethod ?: throw NotSerializableException("Property has no getter method for $name of $clazz. " + + "If using Java and the parameter name looks anonymous, check that you have the -parameters option specified in the Java compiler." + + "Alternately, provide a proxy serializer (SerializationCustomSerializer) if recompiling isn't an option") val returnType = resolveTypeVariables(getter.genericReturnType, type) if (constructorParamTakesReturnTypeOfGetter(returnType, getter.genericReturnType, param)) { rc += PropertySerializer.make(name, getter, returnType, factory) diff --git a/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/amqp/SerializerFactory.kt b/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/amqp/SerializerFactory.kt index 39abcc58f5..0f091029cb 100644 --- a/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/amqp/SerializerFactory.kt +++ b/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/amqp/SerializerFactory.kt @@ -36,7 +36,7 @@ data class FactorySchemaAndDescriptor(val schemas: SerializationSchemas, val typ open class SerializerFactory(val whitelist: ClassWhitelist, cl: ClassLoader) { private val serializersByType = ConcurrentHashMap>() private val serializersByDescriptor = ConcurrentHashMap>() - private val customSerializers = CopyOnWriteArrayList>() + private val customSerializers = CopyOnWriteArrayList() val transformsCache = ConcurrentHashMap>>() open val classCarpenter = ClassCarpenter(cl, whitelist) @@ -196,6 +196,13 @@ open class SerializerFactory(val whitelist: ClassWhitelist, cl: ClassLoader) { } } + fun registerExternal(customSerializer: CorDappCustomSerializer) { + if (!serializersByDescriptor.containsKey(customSerializer.typeDescriptor)) { + customSerializers += customSerializer + serializersByDescriptor[customSerializer.typeDescriptor] = customSerializer + } + } + /** * Iterate over an AMQP schema, for each type ascertain weather it's on ClassPath of [classloader] amd * if not use the [ClassCarpenter] to generate a class to use in it's place @@ -267,11 +274,13 @@ open class SerializerFactory(val whitelist: ClassWhitelist, cl: ClassLoader) { for (customSerializer in customSerializers) { if (customSerializer.isSerializerFor(clazz)) { val declaredSuperClass = declaredType.asClass()?.superclass - if (declaredSuperClass == null || !customSerializer.isSerializerFor(declaredSuperClass) || !customSerializer.revealSubclassesInSchema) { - return customSerializer + return if (declaredSuperClass == null + || !customSerializer.isSerializerFor(declaredSuperClass) + || !customSerializer.revealSubclassesInSchema) { + customSerializer as? AMQPSerializer } else { // Make a subclass serializer for the subclass and return that... - return CustomSerializer.SubClass(clazz, uncheckedCast(customSerializer)) + CustomSerializer.SubClass(clazz, uncheckedCast(customSerializer)) } } } diff --git a/node-api/src/test/kotlin/net/corda/nodeapi/internal/serialization/amqp/CorDappSerializerTests.kt b/node-api/src/test/kotlin/net/corda/nodeapi/internal/serialization/amqp/CorDappSerializerTests.kt new file mode 100644 index 0000000000..213bc37a44 --- /dev/null +++ b/node-api/src/test/kotlin/net/corda/nodeapi/internal/serialization/amqp/CorDappSerializerTests.kt @@ -0,0 +1,92 @@ +package net.corda.nodeapi.internal.serialization.amqp + +import org.junit.Test +import net.corda.core.serialization.CordaCustomSerializer +import net.corda.core.serialization.CordaCustomSerializerProxy +import net.corda.core.serialization.SerializationCustomSerializer +import java.lang.reflect.Type +import kotlin.test.assertEquals + +class CorDappSerializerTests { + data class NeedsProxy (val a: String) + + @CordaCustomSerializer + class NeedsProxyProxySerializer : SerializationCustomSerializer { + @CordaCustomSerializerProxy + data class Proxy(val proxy_a_: String) + + override val type: Type get() = NeedsProxy::class.java + override val ptype: Type get() = Proxy::class.java + + override fun fromProxy(proxy: Any) : Any { + println ("NeedsProxyProxySerialiser - fromProxy") + return NeedsProxy((proxy as Proxy).proxy_a_) + } + override fun toProxy(obj: Any) : Any { + println ("NeedsProxyProxySerialiser - to Proxy") + return Proxy((obj as NeedsProxy).a) + } + } + + // Standard proxy serialiser used internally, here for comparison purposes + class InternalProxySerialiser(factory: SerializerFactory) : + CustomSerializer.Proxy ( + NeedsProxy::class.java, + InternalProxySerialiser.Proxy::class.java, + factory) { + data class Proxy(val proxy_a_: String) + + override fun toProxy(obj: NeedsProxy): Proxy { + println ("InternalProxySerialiser - toProxy") + return Proxy(obj.a) + } + + override fun fromProxy(proxy: Proxy): NeedsProxy { + println ("InternalProxySerialiser - fromProxy") + return NeedsProxy(proxy.proxy_a_) + } + } + + @Test + fun `type uses proxy`() { + val internalProxyFactory = testDefaultFactory() + val proxyFactory = testDefaultFactory() + val defaultFactory = testDefaultFactory() + + val msg = "help" + + proxyFactory.registerExternal (CorDappCustomSerializer(NeedsProxyProxySerializer(), proxyFactory)) + internalProxyFactory.register (InternalProxySerialiser(internalProxyFactory)) + + val needsProxy = NeedsProxy(msg) + + val bAndSProxy = SerializationOutput(proxyFactory).serializeAndReturnSchema (needsProxy) + val bAndSInternal = SerializationOutput(internalProxyFactory).serializeAndReturnSchema (needsProxy) + val bAndSDefault = SerializationOutput(defaultFactory).serializeAndReturnSchema (needsProxy) + + val objFromDefault = DeserializationInput(defaultFactory).deserializeAndReturnEnvelope(bAndSDefault.obj) + val objFromInternal = DeserializationInput(internalProxyFactory).deserializeAndReturnEnvelope(bAndSInternal.obj) + val objFromProxy = DeserializationInput(proxyFactory).deserializeAndReturnEnvelope(bAndSProxy.obj) + + assertEquals(msg, objFromDefault.obj.a) + assertEquals(msg, objFromInternal.obj.a) + assertEquals(msg, objFromProxy.obj.a) + } + + @Test + fun proxiedTypeIsNested() { + data class A (val a: Int, val b: NeedsProxy) + + val factory = testDefaultFactory() + factory.registerExternal (CorDappCustomSerializer(NeedsProxyProxySerializer(), factory)) + + val tv1 = 100 + val tv2 = "pants schmants" + val bAndS = SerializationOutput(factory).serializeAndReturnSchema (A(tv1, NeedsProxy(tv2))) + + val objFromDefault = DeserializationInput(factory).deserializeAndReturnEnvelope(bAndS.obj) + + assertEquals(tv1, objFromDefault.obj.a) + assertEquals(tv2, objFromDefault.obj.b.a) + } +} \ No newline at end of file diff --git a/node-api/src/test/kotlin/net/corda/nodeapi/internal/serialization/amqp/OverridePKSerializerTest.kt b/node-api/src/test/kotlin/net/corda/nodeapi/internal/serialization/amqp/OverridePKSerializerTest.kt index 108f2328f4..2b6ee350f6 100644 --- a/node-api/src/test/kotlin/net/corda/nodeapi/internal/serialization/amqp/OverridePKSerializerTest.kt +++ b/node-api/src/test/kotlin/net/corda/nodeapi/internal/serialization/amqp/OverridePKSerializerTest.kt @@ -25,7 +25,7 @@ class OverridePKSerializerTest { get() = TODO("not implemented") //To change initializer of created properties use File | Settings | File Templates. } - class AMQPTestSerializationScheme : AbstractAMQPSerializationScheme() { + class AMQPTestSerializationScheme : AbstractAMQPSerializationScheme(emptyList()) { override fun rpcServerSerializerFactory(context: SerializationContext): SerializerFactory { TODO("not implemented") //To change body of created functions use File | Settings | File Templates. } diff --git a/node-api/src/test/kotlin/net/corda/nodeapi/internal/serialization/amqp/SerializationOutputTests.kt b/node-api/src/test/kotlin/net/corda/nodeapi/internal/serialization/amqp/SerializationOutputTests.kt index 04c390202d..9a987f1b8a 100644 --- a/node-api/src/test/kotlin/net/corda/nodeapi/internal/serialization/amqp/SerializationOutputTests.kt +++ b/node-api/src/test/kotlin/net/corda/nodeapi/internal/serialization/amqp/SerializationOutputTests.kt @@ -593,7 +593,7 @@ class SerializationOutputTests { fun `test transaction state`() { val state = TransactionState(FooState(), FOO_PROGRAM_ID, MEGA_CORP) - val scheme = AMQPServerSerializationScheme() + val scheme = AMQPServerSerializationScheme(emptyList()) val func = scheme::class.superclasses.single { it.simpleName == "AbstractAMQPSerializationScheme" } .java.getDeclaredMethod("registerCustomSerializers", SerializerFactory::class.java) func.isAccessible = true 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 2ddba4a393..fd13f32fc0 100644 --- a/node/src/main/kotlin/net/corda/node/internal/Node.kt +++ b/node/src/main/kotlin/net/corda/node/internal/Node.kt @@ -300,11 +300,11 @@ open class Node(configuration: NodeConfiguration, nodeSerializationEnv = SerializationEnvironmentImpl( SerializationFactoryImpl().apply { registerScheme(KryoServerSerializationScheme()) - registerScheme(AMQPServerSerializationScheme()) + registerScheme(AMQPServerSerializationScheme(cordappLoader.cordapps)) }, - KRYO_P2P_CONTEXT.withClassLoader(classloader), + p2pContext = AMQP_P2P_CONTEXT.withClassLoader(classloader), rpcServerContext = KRYO_RPC_SERVER_CONTEXT.withClassLoader(classloader), - storageContext = KRYO_STORAGE_CONTEXT.withClassLoader(classloader), + storageContext = AMQP_STORAGE_CONTEXT.withClassLoader(classloader), checkpointContext = KRYO_CHECKPOINT_CONTEXT.withClassLoader(classloader)) } diff --git a/node/src/main/kotlin/net/corda/node/internal/cordapp/CordappLoader.kt b/node/src/main/kotlin/net/corda/node/internal/cordapp/CordappLoader.kt index 4863471957..7dc11cbeb7 100644 --- a/node/src/main/kotlin/net/corda/node/internal/cordapp/CordappLoader.kt +++ b/node/src/main/kotlin/net/corda/node/internal/cordapp/CordappLoader.kt @@ -10,9 +10,8 @@ import net.corda.core.internal.* import net.corda.core.internal.cordapp.CordappImpl import net.corda.core.node.services.CordaService import net.corda.core.schemas.MappedSchema -import net.corda.core.serialization.SerializationWhitelist -import net.corda.core.serialization.SerializeAsToken import net.corda.core.utilities.contextLogger +import net.corda.core.serialization.* import net.corda.node.internal.classloading.requireAnnotation import net.corda.node.services.config.NodeConfiguration import net.corda.nodeapi.internal.serialization.DefaultWhitelist @@ -175,15 +174,17 @@ class CordappLoader private constructor(private val cordappJarPaths: List> { + return scanResult.getClassesWithAnnotation(Class::class, CordaCustomSerializerProxy::class) + } + + private fun findSerialzers(scanResult: RestrictedScanResult) : List> { + return scanResult.getClassesWithAnnotation(SerializationCustomSerializer::class, CordaCustomSerializer::class) + } + private fun findCustomSchemas(scanResult: RestrictedScanResult): Set { return scanResult.getClassesWithSuperclass(MappedSchema::class).toSet() } diff --git a/node/src/main/kotlin/net/corda/node/services/transactions/RaftUniquenessProvider.kt b/node/src/main/kotlin/net/corda/node/services/transactions/RaftUniquenessProvider.kt index 3b7b78a374..16590715fd 100644 --- a/node/src/main/kotlin/net/corda/node/services/transactions/RaftUniquenessProvider.kt +++ b/node/src/main/kotlin/net/corda/node/services/transactions/RaftUniquenessProvider.kt @@ -239,4 +239,4 @@ private fun readMap(buffer: BufferInput>, serializer: Seriali put(serializer.readObject(buffer), serializer.readObject(buffer)) } } -} \ No newline at end of file +} diff --git a/samples/simm-valuation-demo/src/integration-test/kotlin/net/corda/vega/SimmValuationTest.kt b/samples/simm-valuation-demo/src/integration-test/kotlin/net/corda/vega/SimmValuationTest.kt index ee92826c94..be6c0e17ff 100644 --- a/samples/simm-valuation-demo/src/integration-test/kotlin/net/corda/vega/SimmValuationTest.kt +++ b/samples/simm-valuation-demo/src/integration-test/kotlin/net/corda/vega/SimmValuationTest.kt @@ -27,7 +27,7 @@ class SimmValuationTest { @Test fun `runs SIMM valuation demo`() { - driver(isDebug = true, extraCordappPackagesToScan = listOf("net.corda.vega.contracts")) { + driver(isDebug = true, extraCordappPackagesToScan = listOf("net.corda.vega.contracts", "net.corda.vega.plugin.customserializers")) { val nodeAFuture = startNode(providedName = nodeALegalName) val nodeBFuture = startNode(providedName = nodeBLegalName) val (nodeA, nodeB) = listOf(nodeAFuture, nodeBFuture).map { it.getOrThrow() } diff --git a/samples/simm-valuation-demo/src/main/kotlin/net/corda/vega/contracts/PortfolioState.kt b/samples/simm-valuation-demo/src/main/kotlin/net/corda/vega/contracts/PortfolioState.kt index 01de81585b..8b9b1d7414 100644 --- a/samples/simm-valuation-demo/src/main/kotlin/net/corda/vega/contracts/PortfolioState.kt +++ b/samples/simm-valuation-demo/src/main/kotlin/net/corda/vega/contracts/PortfolioState.kt @@ -19,7 +19,7 @@ val PORTFOLIO_SWAP_PROGRAM_ID = "net.corda.vega.contracts.PortfolioSwap" * given point in time. This state can be consumed to create a new state with a mutated valuation or portfolio. */ data class PortfolioState(val portfolio: List, - private val _parties: Pair, + val _parties: Pair, val valuationDate: LocalDate, val valuation: PortfolioValuation? = null, override val linearId: UniqueIdentifier = UniqueIdentifier()) diff --git a/samples/simm-valuation-demo/src/main/kotlin/net/corda/vega/plugin/SimmPluginRegistry.kt b/samples/simm-valuation-demo/src/main/kotlin/net/corda/vega/plugin/SimmPluginRegistry.kt index 626de57761..2c8bc6f473 100644 --- a/samples/simm-valuation-demo/src/main/kotlin/net/corda/vega/plugin/SimmPluginRegistry.kt +++ b/samples/simm-valuation-demo/src/main/kotlin/net/corda/vega/plugin/SimmPluginRegistry.kt @@ -10,6 +10,7 @@ import com.opengamma.strata.market.curve.CurveName import com.opengamma.strata.market.param.CurrencyParameterSensitivities import com.opengamma.strata.market.param.CurrencyParameterSensitivity import com.opengamma.strata.market.param.TenorDateParameterMetadata +import com.opengamma.strata.market.param.ParameterMetadata import net.corda.core.serialization.SerializationWhitelist import net.corda.vega.analytics.CordaMarketData import net.corda.vega.analytics.InitialMarginTriple @@ -34,6 +35,7 @@ class SimmPluginRegistry : SerializationWhitelist { DoubleArray::class.java, CurveName::class.java, TenorDateParameterMetadata::class.java, - Tenor::class.java + Tenor::class.java, + ParameterMetadata::class.java ) } diff --git a/samples/simm-valuation-demo/src/main/kotlin/net/corda/vega/plugin/customserializers/CurrencyParameterSensitivitiesSerializer.kt b/samples/simm-valuation-demo/src/main/kotlin/net/corda/vega/plugin/customserializers/CurrencyParameterSensitivitiesSerializer.kt new file mode 100644 index 0000000000..5d77d98e3d --- /dev/null +++ b/samples/simm-valuation-demo/src/main/kotlin/net/corda/vega/plugin/customserializers/CurrencyParameterSensitivitiesSerializer.kt @@ -0,0 +1,21 @@ +package net.corda.vega.plugin.customserializers + +import com.opengamma.strata.market.param.CurrencyParameterSensitivities +import com.opengamma.strata.market.param.CurrencyParameterSensitivity +import net.corda.core.serialization.CordaCustomSerializer +import net.corda.core.serialization.CordaCustomSerializerProxy +import net.corda.core.serialization.SerializationCustomSerializer +import java.lang.reflect.Type + +@CordaCustomSerializer +@Suppress("UNUSED") +class CurrencyParameterSensitivitiesSerializer : SerializationCustomSerializer { + @CordaCustomSerializerProxy + data class Proxy(val sensitivities: List) + + override val type: Type get() = CurrencyParameterSensitivities::class.java + override val ptype: Type get() = Proxy::class.java + + override fun fromProxy(proxy: Any): Any = CurrencyParameterSensitivities.of ((proxy as Proxy).sensitivities) + override fun toProxy(obj: Any): Any = Proxy ((obj as CurrencyParameterSensitivities).sensitivities) +} \ No newline at end of file diff --git a/samples/simm-valuation-demo/src/main/kotlin/net/corda/vega/plugin/customserializers/CurrencyParameterSensitivitySerialiser.kt b/samples/simm-valuation-demo/src/main/kotlin/net/corda/vega/plugin/customserializers/CurrencyParameterSensitivitySerialiser.kt new file mode 100644 index 0000000000..7de99cac66 --- /dev/null +++ b/samples/simm-valuation-demo/src/main/kotlin/net/corda/vega/plugin/customserializers/CurrencyParameterSensitivitySerialiser.kt @@ -0,0 +1,33 @@ +package net.corda.vega.plugin.customserializers + +import com.opengamma.strata.market.param.CurrencyParameterSensitivity +import com.opengamma.strata.market.param.ParameterMetadata +import com.opengamma.strata.data.MarketDataName +import com.opengamma.strata.collect.array.DoubleArray +import com.opengamma.strata.basics.currency.Currency +import net.corda.core.serialization.CordaCustomSerializer +import net.corda.core.serialization.CordaCustomSerializerProxy +import net.corda.core.serialization.SerializationCustomSerializer +import java.lang.reflect.Type + +@CordaCustomSerializer +@Suppress("UNUSED") +class CurrencyParameterSensitivitySerializer : SerializationCustomSerializer { + @CordaCustomSerializerProxy + data class Proxy(val currency: Currency, val marketDataName: MarketDataName<*>, + val parameterMetadata: List, + val sensitivity: DoubleArray) + + override val type: Type get() = CurrencyParameterSensitivity::class.java + override val ptype: Type get() = Proxy::class.java + + override fun fromProxy(proxy: Any): Any = + CurrencyParameterSensitivity.of( + (proxy as Proxy).marketDataName, + proxy.parameterMetadata, + proxy.currency, + proxy.sensitivity) + + override fun toProxy(obj: Any): Any = Proxy ((obj as CurrencyParameterSensitivity).currency, + obj.marketDataName, obj.parameterMetadata, obj.sensitivity) +} diff --git a/samples/simm-valuation-demo/src/main/kotlin/net/corda/vega/plugin/customserializers/CurrencySerializer.kt b/samples/simm-valuation-demo/src/main/kotlin/net/corda/vega/plugin/customserializers/CurrencySerializer.kt new file mode 100644 index 0000000000..19f4de4195 --- /dev/null +++ b/samples/simm-valuation-demo/src/main/kotlin/net/corda/vega/plugin/customserializers/CurrencySerializer.kt @@ -0,0 +1,20 @@ +package net.corda.vega.plugin.customserializers + +import com.opengamma.strata.basics.currency.Currency +import net.corda.core.serialization.CordaCustomSerializer +import net.corda.core.serialization.CordaCustomSerializerProxy +import net.corda.core.serialization.SerializationCustomSerializer +import java.lang.reflect.Type + +@CordaCustomSerializer +@Suppress("UNUSED") +class CurrencySerializer : SerializationCustomSerializer { + @CordaCustomSerializerProxy + data class Proxy(val currency: String) + + override val type: Type get() = Currency::class.java + override val ptype: Type get() = Proxy::class.java + + override fun fromProxy(proxy: Any): Any = Currency.parse((proxy as Proxy).currency) + override fun toProxy(obj: Any): Any = Proxy((obj as Currency).toString()) +} diff --git a/samples/simm-valuation-demo/src/main/kotlin/net/corda/vega/plugin/customserializers/DoubleArraySerializer.kt b/samples/simm-valuation-demo/src/main/kotlin/net/corda/vega/plugin/customserializers/DoubleArraySerializer.kt new file mode 100644 index 0000000000..e429fc9d10 --- /dev/null +++ b/samples/simm-valuation-demo/src/main/kotlin/net/corda/vega/plugin/customserializers/DoubleArraySerializer.kt @@ -0,0 +1,20 @@ +package net.corda.vega.plugin.customserializers + +import net.corda.core.serialization.CordaCustomSerializer +import net.corda.core.serialization.CordaCustomSerializerProxy +import net.corda.core.serialization.SerializationCustomSerializer +import com.opengamma.strata.collect.array.DoubleArray +import java.lang.reflect.Type + +@CordaCustomSerializer +@Suppress("UNUSED") +class DoubleArraySerializer : SerializationCustomSerializer { + @CordaCustomSerializerProxy + data class Proxy(val amount: kotlin.DoubleArray) + + override val type: Type get() = DoubleArray::class.java + override val ptype: Type get() = Proxy::class.java + + override fun fromProxy(proxy: Any): Any = DoubleArray.copyOf((proxy as Proxy).amount) + override fun toProxy(obj: Any): Any = Proxy((obj as DoubleArray).toArray()) +} diff --git a/samples/simm-valuation-demo/src/main/kotlin/net/corda/vega/plugin/customserializers/MultiCurrencyAmountSerializer.kt b/samples/simm-valuation-demo/src/main/kotlin/net/corda/vega/plugin/customserializers/MultiCurrencyAmountSerializer.kt new file mode 100644 index 0000000000..fed97f7358 --- /dev/null +++ b/samples/simm-valuation-demo/src/main/kotlin/net/corda/vega/plugin/customserializers/MultiCurrencyAmountSerializer.kt @@ -0,0 +1,22 @@ +package net.corda.vega.plugin.customserializers + +import com.opengamma.strata.basics.currency.MultiCurrencyAmount +import com.opengamma.strata.basics.currency.Currency +import net.corda.core.serialization.* +import java.lang.reflect.Type + +@CordaCustomSerializer +@Suppress("UNUSED") +class MultiCurrencyAmountSerializer : SerializationCustomSerializer { + @CordaCustomSerializerProxy + data class Proxy(val curencies : Map) + + override fun toProxy(obj: Any): Any = Proxy((obj as MultiCurrencyAmount).toMap()) + override fun fromProxy(proxy: Any): Any = MultiCurrencyAmount.of((proxy as Proxy).curencies) + + override val type: Type get() = MultiCurrencyAmount::class.java + override val ptype: Type get() = Proxy::class.java +} + + + diff --git a/samples/simm-valuation-demo/src/main/kotlin/net/corda/vega/plugin/customserializers/TenorDateParameterMetadataSerializer.kt b/samples/simm-valuation-demo/src/main/kotlin/net/corda/vega/plugin/customserializers/TenorDateParameterMetadataSerializer.kt new file mode 100644 index 0000000000..84aac96da8 --- /dev/null +++ b/samples/simm-valuation-demo/src/main/kotlin/net/corda/vega/plugin/customserializers/TenorDateParameterMetadataSerializer.kt @@ -0,0 +1,23 @@ +package net.corda.vega.plugin.customserializers + +import com.opengamma.strata.basics.date.Tenor +import com.opengamma.strata.market.param.TenorDateParameterMetadata +import net.corda.core.serialization.* +import java.lang.reflect.Type +import java.time.LocalDate + +@CordaCustomSerializer +@Suppress("UNUSED") +class TenorDateParameterMetadataSerializer : SerializationCustomSerializer { + @CordaCustomSerializerProxy + data class Proxy(val tenor: Tenor, val date: LocalDate, val identifier: Tenor, val label: String) + + override val type: Type get() = TenorDateParameterMetadata::class.java + override val ptype: Type get() = Proxy::class.java + + override fun toProxy(obj: Any): Any = Proxy( + (obj as TenorDateParameterMetadata).tenor, obj.date, obj.identifier, obj.label) + + override fun fromProxy(proxy: Any): Any = TenorDateParameterMetadata.of( + (proxy as Proxy).date, proxy.tenor, proxy.label) +} diff --git a/samples/simm-valuation-demo/src/main/kotlin/net/corda/vega/plugin/customserializers/TenorSerializer.kt b/samples/simm-valuation-demo/src/main/kotlin/net/corda/vega/plugin/customserializers/TenorSerializer.kt new file mode 100644 index 0000000000..8fc397ee34 --- /dev/null +++ b/samples/simm-valuation-demo/src/main/kotlin/net/corda/vega/plugin/customserializers/TenorSerializer.kt @@ -0,0 +1,21 @@ +package net.corda.vega.plugin.customserializers + +import com.opengamma.strata.basics.date.Tenor +import net.corda.core.serialization.* +import java.lang.reflect.Type +import java.time.Period + +@CordaCustomSerializer +@Suppress("UNUSED") +class TenorSerializer : SerializationCustomSerializer { + @CordaCustomSerializerProxy + data class Proxy(val years: Int, val months: Int, val days: Int, val name: String) + + override val type: Type get() = Tenor::class.java + override val ptype: Type get() = Proxy::class.java + + override fun toProxy(obj: Any): Any = Proxy( + (obj as Tenor).period.years, obj.period.months, obj.period.days, obj.toString()) + + override fun fromProxy(proxy: Any): Any = Tenor.of (Period.of((proxy as Proxy).years, proxy.months, proxy.days)) +} 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 aa3776c02c..0d21e8c1ed 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 @@ -47,6 +47,8 @@ import net.corda.testing.testNodeConfiguration import org.apache.activemq.artemis.utils.ReusableLatch import org.apache.sshd.common.util.security.SecurityUtils import rx.internal.schedulers.CachedThreadScheduler +import org.slf4j.Logger +import java.io.Closeable import java.math.BigInteger import java.nio.file.Path import java.security.KeyPair diff --git a/testing/test-utils/src/main/kotlin/net/corda/testing/SerializationTestHelpers.kt b/testing/test-utils/src/main/kotlin/net/corda/testing/SerializationTestHelpers.kt index 17f9a83295..8581534d0f 100644 --- a/testing/test-utils/src/main/kotlin/net/corda/testing/SerializationTestHelpers.kt +++ b/testing/test-utils/src/main/kotlin/net/corda/testing/SerializationTestHelpers.kt @@ -62,7 +62,7 @@ fun withTestSerialization(inheritable: Boolean = false, callable: (Serializa /** * For example your test class uses [SerializationEnvironmentRule] but you want to turn it off for one method. - * Use sparingly, ideally a test class shouldn't mix serialization init mechanisms. + * Use sparingly, ideally a test class shouldn't mix serializers init mechanisms. */ fun withoutTestSerialization(callable: () -> T): T { val (property, env) = listOf(_contextSerializationEnv, _inheritableContextSerializationEnv).map { Pair(it, it.get()) }.single { it.second != null } @@ -99,13 +99,12 @@ private fun createTestSerializationEnv(label: String) = object : SerializationEn SerializationFactoryImpl().apply { registerScheme(KryoClientSerializationScheme()) registerScheme(KryoServerSerializationScheme()) - registerScheme(AMQPClientSerializationScheme()) - registerScheme(AMQPServerSerializationScheme()) + registerScheme(AMQPClientSerializationScheme(emptyList())) + registerScheme(AMQPServerSerializationScheme(emptyList())) }, - if (isAmqpEnabled()) AMQP_P2P_CONTEXT else KRYO_P2P_CONTEXT, + AMQP_P2P_CONTEXT, KRYO_RPC_SERVER_CONTEXT, - KRYO_RPC_CLIENT_CONTEXT, - if (isAmqpEnabled()) AMQP_STORAGE_CONTEXT else KRYO_STORAGE_CONTEXT, + AMQP_STORAGE_CONTEXT, KRYO_CHECKPOINT_CONTEXT) { override fun toString() = "testSerializationEnv($label)" } diff --git a/testing/test-utils/src/main/kotlin/net/corda/testing/node/MockCordappProvider.kt b/testing/test-utils/src/main/kotlin/net/corda/testing/node/MockCordappProvider.kt index 5c8736335e..aa8e19965f 100644 --- a/testing/test-utils/src/main/kotlin/net/corda/testing/node/MockCordappProvider.kt +++ b/testing/test-utils/src/main/kotlin/net/corda/testing/node/MockCordappProvider.kt @@ -14,7 +14,18 @@ class MockCordappProvider(cordappLoader: CordappLoader, attachmentStorage: Attac val cordappRegistry = mutableListOf>() fun addMockCordapp(contractClassName: ContractClassName, attachments: MockAttachmentStorage) { - val cordapp = CordappImpl(listOf(contractClassName), emptyList(), emptyList(), emptyList(), emptyList(), emptyList(), emptyList(), emptySet(), Paths.get(".").toUri().toURL()) + val cordapp = CordappImpl( + contractClassNames = listOf(contractClassName), + initiatedFlows = emptyList(), + rpcFlows = emptyList(), + serviceFlows = emptyList(), + schedulableFlows = emptyList(), + services = emptyList(), + serializationWhitelists = emptyList(), + serializationCustomSerializerProxies = emptyList(), + serializationCustomSerializers = emptyList(), + customSchemas = emptySet(), + jarPath = Paths.get(".").toUri().toURL()) if (cordappRegistry.none { it.first.contractClassNames.contains(contractClassName) }) { cordappRegistry.add(Pair(cordapp, findOrImportAttachment(contractClassName.toByteArray(), attachments))) } diff --git a/verifier/src/main/kotlin/net/corda/verifier/Verifier.kt b/verifier/src/main/kotlin/net/corda/verifier/Verifier.kt index 5db494cad1..81571dd722 100644 --- a/verifier/src/main/kotlin/net/corda/verifier/Verifier.kt +++ b/verifier/src/main/kotlin/net/corda/verifier/Verifier.kt @@ -91,11 +91,7 @@ class Verifier { registerScheme(KryoVerifierSerializationScheme) registerScheme(AMQPVerifierSerializationScheme) }, - /** - * Even though default context is set to Kryo P2P, the encoding will be adjusted depending on the incoming - * request received, see use of [context] in [main] method. - */ - KRYO_P2P_CONTEXT) + AMQP_P2P_CONTEXT) } } @@ -108,7 +104,7 @@ class Verifier { override fun rpcServerKryoPool(context: SerializationContext) = throw UnsupportedOperationException() } - private object AMQPVerifierSerializationScheme : AbstractAMQPSerializationScheme() { + private object AMQPVerifierSerializationScheme : AbstractAMQPSerializationScheme(emptyList()) { override fun canDeserializeVersion(byteSequence: ByteSequence, target: SerializationContext.UseCase): Boolean { return (byteSequence == AmqpHeaderV1_0 && (target == SerializationContext.UseCase.P2P)) } @@ -116,4 +112,4 @@ class Verifier { override fun rpcClientSerializerFactory(context: SerializationContext): SerializerFactory = throw UnsupportedOperationException() override fun rpcServerSerializerFactory(context: SerializationContext): SerializerFactory = throw UnsupportedOperationException() } -} \ No newline at end of file +} From f931b74aec56e8bda66c49cefe2ad3455615e033 Mon Sep 17 00:00:00 2001 From: Katelyn Baker Date: Thu, 23 Nov 2017 17:53:31 +0000 Subject: [PATCH 22/42] CORDA-780 / CORDA-786 - Doc updates --- .../corda/core/serialization/CordaCustomSerializer.kt | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/core/src/main/kotlin/net/corda/core/serialization/CordaCustomSerializer.kt b/core/src/main/kotlin/net/corda/core/serialization/CordaCustomSerializer.kt index 724e8d7db8..df3cb035aa 100644 --- a/core/src/main/kotlin/net/corda/core/serialization/CordaCustomSerializer.kt +++ b/core/src/main/kotlin/net/corda/core/serialization/CordaCustomSerializer.kt @@ -1,8 +1,18 @@ package net.corda.core.serialization +/** + * This annotation marks a class as being a serializer provided by a CorDapp to proxy some third party + * class Corda couldn't otherwise serialize. It should be applied to classes that implement the + * [SerializationCustomSerializer] interface + */ @Target(AnnotationTarget.CLASS) annotation class CordaCustomSerializer +/** + * This annotation marks a class as being a proxy for some third party class and used by some + * implementation of [SerializationCustomSerializer]. Such classes must be annotated to allow + * them to be discovered in a CorDapp jar and loaded + */ @Target(AnnotationTarget.CLASS) annotation class CordaCustomSerializerProxy From 6881350493931c4bb772d0a6fc557fbd03404190 Mon Sep 17 00:00:00 2001 From: Katelyn Baker Date: Fri, 24 Nov 2017 12:04:17 +0000 Subject: [PATCH 23/42] CORDA-786 - Add whitelist testing for external custom serializers Update Docs --- docs/source/cordapp-custom-serializers.rst | 31 +++--- .../amqp/CorDappSerializerTests.kt | 96 ++++++++++++++++--- 2 files changed, 99 insertions(+), 28 deletions(-) diff --git a/docs/source/cordapp-custom-serializers.rst b/docs/source/cordapp-custom-serializers.rst index e5e38e32b8..fdf8f82e59 100644 --- a/docs/source/cordapp-custom-serializers.rst +++ b/docs/source/cordapp-custom-serializers.rst @@ -1,30 +1,29 @@ Pluggable Serializers for CorDapps ================================== - -To be serializable by Corda Java classes must be compiled with the -parameter switch to enable mathcing of ass property -to constructor parameter. However, when this isn't possible CorDapps can provide custom proxy serialiszers that Corda -can use to move from types it cannot serialiser to an interim represtnation that it can with the transformation to and -from this proxy object being handled by the supplied serialiser. +To be serializable by Corda Java classes must be compiled with the -parameter switch to enable matching of it's properties +to constructor parameters. This is important because Corda's internal AMQP serialization scheme will only constuct +objects using their constructors. However, when recompilation isn't possible, or classes are built in such a way that +they cannot be easily modified for simple serailization, CorDapps can provide custom proxy serializers that Corda +can use to move from types it cannot serializer to an interim representation that it can with the transformation to and +from this proxy object being handled by the supplied serializer. Serializer Location ------------------- -Custom serializers should be placed in the plugins directory fo a CorDapp or a sub directory (placing it in a sub -directory however does require that directory be added to the list of locations scanned within the jar) +Custom serializers should be placed in the plugins directory of a CorDapp or a sub directory thereof. These +classes will be scanned and loaded by the CorDapp loading process. Writing a Custom Serializer --------------------------- - +--------------------------- Serializers must * Inherit from net.corda.core.serialization.SerializationCustomSerializer * Be annotated with the @CordaCustomSerializer annotation - * Provide a proxy class to transform the objectto and from + * Provide a proxy class to transform the object to and from * Have that proxy class annotated with the @CordaCustomSerializerProxy annotation Serializers inheriting from SerializationCustomSerializer have to implement two methods and two types Example ------- - Consider this example class .. sourcecode:: java @@ -43,7 +42,10 @@ Consider this example class public int getB() { return b; } } -This would require a serialiser as follows +Without a custom serializer we cannot serialise this class as there is no public constructor that facilitates the +initialisation of al of its's properties. + +To be serializable by Corda this would require a custom serializer as follows .. sourcecode:: kotlin @CordaCustomSerializer @@ -64,6 +66,9 @@ This would require a serialiser as follows override val ptype: Type get() = Proxy::class.java } - +Whitelisting +------------ +By writing a custom serializer for a class it has the effect of adding that class to the whitelist, meaning such +classes don't need explicitly adding to the CorDapp's whitelist diff --git a/node-api/src/test/kotlin/net/corda/nodeapi/internal/serialization/amqp/CorDappSerializerTests.kt b/node-api/src/test/kotlin/net/corda/nodeapi/internal/serialization/amqp/CorDappSerializerTests.kt index 213bc37a44..fdd9b11a40 100644 --- a/node-api/src/test/kotlin/net/corda/nodeapi/internal/serialization/amqp/CorDappSerializerTests.kt +++ b/node-api/src/test/kotlin/net/corda/nodeapi/internal/serialization/amqp/CorDappSerializerTests.kt @@ -3,7 +3,10 @@ package net.corda.nodeapi.internal.serialization.amqp import org.junit.Test import net.corda.core.serialization.CordaCustomSerializer import net.corda.core.serialization.CordaCustomSerializerProxy +import net.corda.core.serialization.ClassWhitelist import net.corda.core.serialization.SerializationCustomSerializer +import org.assertj.core.api.Assertions +import java.io.NotSerializableException import java.lang.reflect.Type import kotlin.test.assertEquals @@ -18,31 +21,25 @@ class CorDappSerializerTests { override val type: Type get() = NeedsProxy::class.java override val ptype: Type get() = Proxy::class.java - override fun fromProxy(proxy: Any) : Any { - println ("NeedsProxyProxySerialiser - fromProxy") - return NeedsProxy((proxy as Proxy).proxy_a_) - } - override fun toProxy(obj: Any) : Any { - println ("NeedsProxyProxySerialiser - to Proxy") - return Proxy((obj as NeedsProxy).a) - } + override fun fromProxy(proxy: Any) : Any = NeedsProxy((proxy as Proxy).proxy_a_) + override fun toProxy(obj: Any) : Any = Proxy((obj as NeedsProxy).a) } - // Standard proxy serialiser used internally, here for comparison purposes - class InternalProxySerialiser(factory: SerializerFactory) : - CustomSerializer.Proxy ( + // Standard proxy serializer used internally, here for comparison purposes + class InternalProxySerializer(factory: SerializerFactory) : + CustomSerializer.Proxy ( NeedsProxy::class.java, - InternalProxySerialiser.Proxy::class.java, + InternalProxySerializer.Proxy::class.java, factory) { data class Proxy(val proxy_a_: String) override fun toProxy(obj: NeedsProxy): Proxy { - println ("InternalProxySerialiser - toProxy") + println ("InternalProxySerializer - toProxy") return Proxy(obj.a) } override fun fromProxy(proxy: Proxy): NeedsProxy { - println ("InternalProxySerialiser - fromProxy") + println ("InternalProxySerializer - fromProxy") return NeedsProxy(proxy.proxy_a_) } } @@ -56,7 +53,7 @@ class CorDappSerializerTests { val msg = "help" proxyFactory.registerExternal (CorDappCustomSerializer(NeedsProxyProxySerializer(), proxyFactory)) - internalProxyFactory.register (InternalProxySerialiser(internalProxyFactory)) + internalProxyFactory.register (InternalProxySerializer(internalProxyFactory)) val needsProxy = NeedsProxy(msg) @@ -89,4 +86,73 @@ class CorDappSerializerTests { assertEquals(tv1, objFromDefault.obj.a) assertEquals(tv2, objFromDefault.obj.b.a) } + + @Test + fun testWithWhitelistNotAllowed() { + data class A (val a: Int, val b: NeedsProxy) + + class WL : ClassWhitelist { + private val allowedClasses = emptySet() + + override fun hasListed(type: Class<*>): Boolean = type.name in allowedClasses + } + + val factory = SerializerFactory(WL(), ClassLoader.getSystemClassLoader()) + factory.registerExternal (CorDappCustomSerializer(NeedsProxyProxySerializer(), factory)) + + val tv1 = 100 + val tv2 = "pants schmants" + Assertions.assertThatThrownBy { + SerializationOutput(factory).serialize(A(tv1, NeedsProxy(tv2))) + }.isInstanceOf(NotSerializableException::class.java) + } + + @Test + fun testWithWhitelistAllowed() { + data class A (val a: Int, val b: NeedsProxy) + + class WL : ClassWhitelist { + private val allowedClasses = hashSetOf( + A::class.java.name, + NeedsProxy::class.java.name) + + override fun hasListed(type: Class<*>): Boolean = type.name in allowedClasses + } + + val factory = SerializerFactory(WL(), ClassLoader.getSystemClassLoader()) + factory.registerExternal (CorDappCustomSerializer(NeedsProxyProxySerializer(), factory)) + + val tv1 = 100 + val tv2 = "pants schmants" + val obj = DeserializationInput(factory).deserialize( + SerializationOutput(factory).serialize(A(tv1, NeedsProxy(tv2)))) + + assertEquals(tv1, obj.a) + assertEquals(tv2, obj.b.a) + } + + // The custom type not being whitelisted won't matter here because the act of adding a + // custom serializer bypasses the whitelist + @Test + fun testWithWhitelistAllowedOuterOnly() { + data class A (val a: Int, val b: NeedsProxy) + + class WL : ClassWhitelist { + // explicitly don't add NeedsProxy + private val allowedClasses = hashSetOf(A::class.java.name) + + override fun hasListed(type: Class<*>): Boolean = type.name in allowedClasses + } + + val factory = SerializerFactory(WL(), ClassLoader.getSystemClassLoader()) + factory.registerExternal (CorDappCustomSerializer(NeedsProxyProxySerializer(), factory)) + + val tv1 = 100 + val tv2 = "pants schmants" + val obj = DeserializationInput(factory).deserialize( + SerializationOutput(factory).serialize(A(tv1, NeedsProxy(tv2)))) + + assertEquals(tv1, obj.a) + assertEquals(tv2, obj.b.a) + } } \ No newline at end of file From d25b7f560c1cdeb8fca7a35aea8f0d8f768d06e2 Mon Sep 17 00:00:00 2001 From: Katelyn Baker Date: Thu, 30 Nov 2017 14:58:53 +0000 Subject: [PATCH 24/42] CORDA-786 - Genericise the CorDapp Proxy --- .../main/kotlin/net/corda/core/cordapp/Cordapp.kt | 2 +- .../net/corda/core/internal/cordapp/CordappImpl.kt | 2 +- .../serialization/SerializationCustomSerializer.kt | 6 +++--- .../serialization/amqp/CorDappCustomSerializer.kt | 12 +++++++----- .../net/corda/node/internal/cordapp/CordappLoader.kt | 2 +- .../CurrencyParameterSensitivitiesSerializer.kt | 7 ++++--- .../CurrencyParameterSensitivitySerialiser.kt | 9 +++++---- .../plugin/customserializers/CurrencySerializer.kt | 6 +++--- .../customserializers/DoubleArraySerializer.kt | 6 +++--- .../MultiCurrencyAmountSerializer.kt | 7 ++++--- .../TenorDateParameterMetadataSerializer.kt | 9 ++++----- .../vega/plugin/customserializers/TenorSerializer.kt | 8 +++----- 12 files changed, 39 insertions(+), 37 deletions(-) diff --git a/core/src/main/kotlin/net/corda/core/cordapp/Cordapp.kt b/core/src/main/kotlin/net/corda/core/cordapp/Cordapp.kt index d4df6eb5ec..959f0e79d1 100644 --- a/core/src/main/kotlin/net/corda/core/cordapp/Cordapp.kt +++ b/core/src/main/kotlin/net/corda/core/cordapp/Cordapp.kt @@ -39,7 +39,7 @@ interface Cordapp { val services: List> val serializationWhitelists: List val serializationCustomSerializerProxies: List> - val serializationCustomSerializers: List> + val serializationCustomSerializers: List>> val customSchemas: Set val jarPath: URL val cordappClasses: List diff --git a/core/src/main/kotlin/net/corda/core/internal/cordapp/CordappImpl.kt b/core/src/main/kotlin/net/corda/core/internal/cordapp/CordappImpl.kt index 7aaab3b8b3..f644a9890d 100644 --- a/core/src/main/kotlin/net/corda/core/internal/cordapp/CordappImpl.kt +++ b/core/src/main/kotlin/net/corda/core/internal/cordapp/CordappImpl.kt @@ -18,7 +18,7 @@ data class CordappImpl( override val services: List>, override val serializationWhitelists: List, override val serializationCustomSerializerProxies: List>, - override val serializationCustomSerializers: List>, + override val serializationCustomSerializers: List>>, override val customSchemas: Set, override val jarPath: URL) : Cordapp { override val name: String = File(jarPath.toURI()).name.removeSuffix(".jar") diff --git a/core/src/main/kotlin/net/corda/core/serialization/SerializationCustomSerializer.kt b/core/src/main/kotlin/net/corda/core/serialization/SerializationCustomSerializer.kt index 0f384196f5..d5fb39d30e 100644 --- a/core/src/main/kotlin/net/corda/core/serialization/SerializationCustomSerializer.kt +++ b/core/src/main/kotlin/net/corda/core/serialization/SerializationCustomSerializer.kt @@ -21,18 +21,18 @@ import java.lang.reflect.Type * @property type the type of the object that this class is proxying * @property ptype the type of the proxy object used as an intermediate representation of [type] */ -interface SerializationCustomSerializer { +interface SerializationCustomSerializer { /** * Should facilitate the conversion of the third party object into the serializable * local class specified by [ptype] */ - fun toProxy(obj: Any) : Any + fun toProxy(obj: OBJ) : PROXY /** * Should facilitate the conversion of the proxy object into a new instance of the * unserializable type */ - fun fromProxy(proxy: Any) : Any + fun fromProxy(proxy: PROXY) : OBJ /** * Should be set to the type of the object being proxied diff --git a/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/amqp/CorDappCustomSerializer.kt b/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/amqp/CorDappCustomSerializer.kt index 87be4312d1..cdf6d59325 100644 --- a/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/amqp/CorDappCustomSerializer.kt +++ b/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/amqp/CorDappCustomSerializer.kt @@ -8,7 +8,7 @@ import org.apache.qpid.proton.codec.Data import java.lang.reflect.Type class CorDappCustomSerializer( - private val serialiser: SerializationCustomSerializer, + private val serialiser: SerializationCustomSerializer<*, *>, factory: SerializerFactory) : AMQPSerializer, SerializerFor { override val revealSubclassesInSchema: Boolean get() = false @@ -21,7 +21,8 @@ class CorDappCustomSerializer( override fun writeClassInfo(output: SerializationOutput) {} override fun writeObject(obj: Any, data: Data, type: Type, output: SerializationOutput) { - val proxy = serialiser.toProxy(obj) + @Suppress("UNCHECKED_CAST") + val proxy = (serialiser as SerializationCustomSerializer).toProxy(obj) data.withDescribed(descriptor) { data.withList { @@ -32,9 +33,10 @@ class CorDappCustomSerializer( } } - override fun readObject(obj: Any, schema: Schema, input: DeserializationInput): Any { - return serialiser.fromProxy(uncheckedCast(proxySerializer.readObject(obj, schema, input))) - } + override fun readObject(obj: Any, schema: Schema, input: DeserializationInput) = + @Suppress("UNCHECKED_CAST") + (serialiser as SerializationCustomSerializer).fromProxy( + uncheckedCast(proxySerializer.readObject(obj, schema, input)))!! override fun isSerializerFor(clazz: Class<*>): Boolean = clazz == type } diff --git a/node/src/main/kotlin/net/corda/node/internal/cordapp/CordappLoader.kt b/node/src/main/kotlin/net/corda/node/internal/cordapp/CordappLoader.kt index 7dc11cbeb7..7688a611db 100644 --- a/node/src/main/kotlin/net/corda/node/internal/cordapp/CordappLoader.kt +++ b/node/src/main/kotlin/net/corda/node/internal/cordapp/CordappLoader.kt @@ -254,7 +254,7 @@ class CordappLoader private constructor(private val cordappJarPaths: List> { + private fun findSerialzers(scanResult: RestrictedScanResult) : List>> { return scanResult.getClassesWithAnnotation(SerializationCustomSerializer::class, CordaCustomSerializer::class) } diff --git a/samples/simm-valuation-demo/src/main/kotlin/net/corda/vega/plugin/customserializers/CurrencyParameterSensitivitiesSerializer.kt b/samples/simm-valuation-demo/src/main/kotlin/net/corda/vega/plugin/customserializers/CurrencyParameterSensitivitiesSerializer.kt index 5d77d98e3d..42eb965689 100644 --- a/samples/simm-valuation-demo/src/main/kotlin/net/corda/vega/plugin/customserializers/CurrencyParameterSensitivitiesSerializer.kt +++ b/samples/simm-valuation-demo/src/main/kotlin/net/corda/vega/plugin/customserializers/CurrencyParameterSensitivitiesSerializer.kt @@ -9,13 +9,14 @@ import java.lang.reflect.Type @CordaCustomSerializer @Suppress("UNUSED") -class CurrencyParameterSensitivitiesSerializer : SerializationCustomSerializer { +class CurrencyParameterSensitivitiesSerializer : + SerializationCustomSerializer { @CordaCustomSerializerProxy data class Proxy(val sensitivities: List) override val type: Type get() = CurrencyParameterSensitivities::class.java override val ptype: Type get() = Proxy::class.java - override fun fromProxy(proxy: Any): Any = CurrencyParameterSensitivities.of ((proxy as Proxy).sensitivities) - override fun toProxy(obj: Any): Any = Proxy ((obj as CurrencyParameterSensitivities).sensitivities) + override fun fromProxy(proxy: Proxy) = CurrencyParameterSensitivities.of(proxy.sensitivities) + override fun toProxy(obj: CurrencyParameterSensitivities) = Proxy(obj.sensitivities) } \ No newline at end of file diff --git a/samples/simm-valuation-demo/src/main/kotlin/net/corda/vega/plugin/customserializers/CurrencyParameterSensitivitySerialiser.kt b/samples/simm-valuation-demo/src/main/kotlin/net/corda/vega/plugin/customserializers/CurrencyParameterSensitivitySerialiser.kt index 7de99cac66..98dde48d8d 100644 --- a/samples/simm-valuation-demo/src/main/kotlin/net/corda/vega/plugin/customserializers/CurrencyParameterSensitivitySerialiser.kt +++ b/samples/simm-valuation-demo/src/main/kotlin/net/corda/vega/plugin/customserializers/CurrencyParameterSensitivitySerialiser.kt @@ -12,7 +12,8 @@ import java.lang.reflect.Type @CordaCustomSerializer @Suppress("UNUSED") -class CurrencyParameterSensitivitySerializer : SerializationCustomSerializer { +class CurrencyParameterSensitivitySerializer : + SerializationCustomSerializer { @CordaCustomSerializerProxy data class Proxy(val currency: Currency, val marketDataName: MarketDataName<*>, val parameterMetadata: List, @@ -21,13 +22,13 @@ class CurrencyParameterSensitivitySerializer : SerializationCustomSerializer { override val type: Type get() = CurrencyParameterSensitivity::class.java override val ptype: Type get() = Proxy::class.java - override fun fromProxy(proxy: Any): Any = + override fun fromProxy(proxy: CurrencyParameterSensitivitySerializer.Proxy) = CurrencyParameterSensitivity.of( - (proxy as Proxy).marketDataName, + proxy.marketDataName, proxy.parameterMetadata, proxy.currency, proxy.sensitivity) - override fun toProxy(obj: Any): Any = Proxy ((obj as CurrencyParameterSensitivity).currency, + override fun toProxy(obj: CurrencyParameterSensitivity) = Proxy((obj as CurrencyParameterSensitivity).currency, obj.marketDataName, obj.parameterMetadata, obj.sensitivity) } diff --git a/samples/simm-valuation-demo/src/main/kotlin/net/corda/vega/plugin/customserializers/CurrencySerializer.kt b/samples/simm-valuation-demo/src/main/kotlin/net/corda/vega/plugin/customserializers/CurrencySerializer.kt index 19f4de4195..f3b5534459 100644 --- a/samples/simm-valuation-demo/src/main/kotlin/net/corda/vega/plugin/customserializers/CurrencySerializer.kt +++ b/samples/simm-valuation-demo/src/main/kotlin/net/corda/vega/plugin/customserializers/CurrencySerializer.kt @@ -8,13 +8,13 @@ import java.lang.reflect.Type @CordaCustomSerializer @Suppress("UNUSED") -class CurrencySerializer : SerializationCustomSerializer { +class CurrencySerializer : SerializationCustomSerializer { @CordaCustomSerializerProxy data class Proxy(val currency: String) override val type: Type get() = Currency::class.java override val ptype: Type get() = Proxy::class.java - override fun fromProxy(proxy: Any): Any = Currency.parse((proxy as Proxy).currency) - override fun toProxy(obj: Any): Any = Proxy((obj as Currency).toString()) + override fun fromProxy(proxy: Proxy) = Currency.parse(proxy.currency) + override fun toProxy(obj: Currency) = Proxy(obj.toString()) } diff --git a/samples/simm-valuation-demo/src/main/kotlin/net/corda/vega/plugin/customserializers/DoubleArraySerializer.kt b/samples/simm-valuation-demo/src/main/kotlin/net/corda/vega/plugin/customserializers/DoubleArraySerializer.kt index e429fc9d10..d9ad8e2c10 100644 --- a/samples/simm-valuation-demo/src/main/kotlin/net/corda/vega/plugin/customserializers/DoubleArraySerializer.kt +++ b/samples/simm-valuation-demo/src/main/kotlin/net/corda/vega/plugin/customserializers/DoubleArraySerializer.kt @@ -8,13 +8,13 @@ import java.lang.reflect.Type @CordaCustomSerializer @Suppress("UNUSED") -class DoubleArraySerializer : SerializationCustomSerializer { +class DoubleArraySerializer : SerializationCustomSerializer { @CordaCustomSerializerProxy data class Proxy(val amount: kotlin.DoubleArray) override val type: Type get() = DoubleArray::class.java override val ptype: Type get() = Proxy::class.java - override fun fromProxy(proxy: Any): Any = DoubleArray.copyOf((proxy as Proxy).amount) - override fun toProxy(obj: Any): Any = Proxy((obj as DoubleArray).toArray()) + override fun fromProxy(proxy: Proxy) = DoubleArray.copyOf(proxy.amount) + override fun toProxy(obj: DoubleArray) = Proxy(obj.toArray()) } diff --git a/samples/simm-valuation-demo/src/main/kotlin/net/corda/vega/plugin/customserializers/MultiCurrencyAmountSerializer.kt b/samples/simm-valuation-demo/src/main/kotlin/net/corda/vega/plugin/customserializers/MultiCurrencyAmountSerializer.kt index fed97f7358..2df9ca801e 100644 --- a/samples/simm-valuation-demo/src/main/kotlin/net/corda/vega/plugin/customserializers/MultiCurrencyAmountSerializer.kt +++ b/samples/simm-valuation-demo/src/main/kotlin/net/corda/vega/plugin/customserializers/MultiCurrencyAmountSerializer.kt @@ -7,12 +7,13 @@ import java.lang.reflect.Type @CordaCustomSerializer @Suppress("UNUSED") -class MultiCurrencyAmountSerializer : SerializationCustomSerializer { +class MultiCurrencyAmountSerializer : + SerializationCustomSerializer { @CordaCustomSerializerProxy data class Proxy(val curencies : Map) - override fun toProxy(obj: Any): Any = Proxy((obj as MultiCurrencyAmount).toMap()) - override fun fromProxy(proxy: Any): Any = MultiCurrencyAmount.of((proxy as Proxy).curencies) + override fun toProxy(obj: MultiCurrencyAmount) = Proxy(obj.toMap()) + override fun fromProxy(proxy: Proxy) = MultiCurrencyAmount.of(proxy.curencies) override val type: Type get() = MultiCurrencyAmount::class.java override val ptype: Type get() = Proxy::class.java diff --git a/samples/simm-valuation-demo/src/main/kotlin/net/corda/vega/plugin/customserializers/TenorDateParameterMetadataSerializer.kt b/samples/simm-valuation-demo/src/main/kotlin/net/corda/vega/plugin/customserializers/TenorDateParameterMetadataSerializer.kt index 84aac96da8..9814dbbf3f 100644 --- a/samples/simm-valuation-demo/src/main/kotlin/net/corda/vega/plugin/customserializers/TenorDateParameterMetadataSerializer.kt +++ b/samples/simm-valuation-demo/src/main/kotlin/net/corda/vega/plugin/customserializers/TenorDateParameterMetadataSerializer.kt @@ -8,16 +8,15 @@ import java.time.LocalDate @CordaCustomSerializer @Suppress("UNUSED") -class TenorDateParameterMetadataSerializer : SerializationCustomSerializer { +class TenorDateParameterMetadataSerializer : + SerializationCustomSerializer { @CordaCustomSerializerProxy data class Proxy(val tenor: Tenor, val date: LocalDate, val identifier: Tenor, val label: String) override val type: Type get() = TenorDateParameterMetadata::class.java override val ptype: Type get() = Proxy::class.java - override fun toProxy(obj: Any): Any = Proxy( - (obj as TenorDateParameterMetadata).tenor, obj.date, obj.identifier, obj.label) - override fun fromProxy(proxy: Any): Any = TenorDateParameterMetadata.of( - (proxy as Proxy).date, proxy.tenor, proxy.label) + override fun toProxy(obj: TenorDateParameterMetadata) = Proxy(obj.tenor, obj.date, obj.identifier, obj.label) + override fun fromProxy(proxy: Proxy) = TenorDateParameterMetadata.of(proxy.date, proxy.tenor, proxy.label) } diff --git a/samples/simm-valuation-demo/src/main/kotlin/net/corda/vega/plugin/customserializers/TenorSerializer.kt b/samples/simm-valuation-demo/src/main/kotlin/net/corda/vega/plugin/customserializers/TenorSerializer.kt index 8fc397ee34..f798a289fe 100644 --- a/samples/simm-valuation-demo/src/main/kotlin/net/corda/vega/plugin/customserializers/TenorSerializer.kt +++ b/samples/simm-valuation-demo/src/main/kotlin/net/corda/vega/plugin/customserializers/TenorSerializer.kt @@ -7,15 +7,13 @@ import java.time.Period @CordaCustomSerializer @Suppress("UNUSED") -class TenorSerializer : SerializationCustomSerializer { +class TenorSerializer : SerializationCustomSerializer { @CordaCustomSerializerProxy data class Proxy(val years: Int, val months: Int, val days: Int, val name: String) override val type: Type get() = Tenor::class.java override val ptype: Type get() = Proxy::class.java - override fun toProxy(obj: Any): Any = Proxy( - (obj as Tenor).period.years, obj.period.months, obj.period.days, obj.toString()) - - override fun fromProxy(proxy: Any): Any = Tenor.of (Period.of((proxy as Proxy).years, proxy.months, proxy.days)) + override fun toProxy(obj: Tenor) = Proxy(obj.period.years, obj.period.months, obj.period.days, obj.toString()) + override fun fromProxy(proxy: Proxy) = Tenor.of (Period.of(proxy.years, proxy.months, proxy.days)) } From fcec60e23297e0ca258904877ae590f6be8f7775 Mon Sep 17 00:00:00 2001 From: Katelyn Baker Date: Mon, 4 Dec 2017 13:10:02 +0000 Subject: [PATCH 25/42] CORDA-786 - Use reflection to infer proxy and proxied types in CorDapp custom serializers This removes any need for the user implement and override types from the super class * CORDA-786 - Docs update * CORDA-786 - Remove unneeded second annotation on the proxy objects * Fix merge conflicts --- .../kotlin/net/corda/core/cordapp/Cordapp.kt | 1 - .../core/internal/cordapp/CordappImpl.kt | 1 - .../serialization/CordaCustomSerializer.kt | 8 --- .../SerializationCustomSerializer.kt | 13 ---- docs/source/cordapp-custom-serializers.rst | 32 +++++---- .../amqp/CorDappCustomSerializer.kt | 69 +++++++++++++++---- .../serialization/amqp/SerializerFactory.kt | 4 +- .../amqp/CorDappSerializerTests.kt | 14 +--- .../node/internal/cordapp/CordappLoader.kt | 6 -- ...urrencyParameterSensitivitiesSerializer.kt | 6 -- .../CurrencyParameterSensitivitySerialiser.kt | 6 -- .../customserializers/CurrencySerializer.kt | 6 -- .../DoubleArraySerializer.kt | 6 -- .../MultiCurrencyAmountSerializer.kt | 5 -- .../TenorDateParameterMetadataSerializer.kt | 6 -- .../customserializers/TenorSerializer.kt | 5 -- .../corda/testing/SerializationTestHelpers.kt | 1 + .../corda/testing/node/MockCordappProvider.kt | 1 - 18 files changed, 80 insertions(+), 110 deletions(-) diff --git a/core/src/main/kotlin/net/corda/core/cordapp/Cordapp.kt b/core/src/main/kotlin/net/corda/core/cordapp/Cordapp.kt index 959f0e79d1..3729da353d 100644 --- a/core/src/main/kotlin/net/corda/core/cordapp/Cordapp.kt +++ b/core/src/main/kotlin/net/corda/core/cordapp/Cordapp.kt @@ -38,7 +38,6 @@ interface Cordapp { val schedulableFlows: List>> val services: List> val serializationWhitelists: List - val serializationCustomSerializerProxies: List> val serializationCustomSerializers: List>> val customSchemas: Set val jarPath: URL diff --git a/core/src/main/kotlin/net/corda/core/internal/cordapp/CordappImpl.kt b/core/src/main/kotlin/net/corda/core/internal/cordapp/CordappImpl.kt index f644a9890d..d0d5c4dec3 100644 --- a/core/src/main/kotlin/net/corda/core/internal/cordapp/CordappImpl.kt +++ b/core/src/main/kotlin/net/corda/core/internal/cordapp/CordappImpl.kt @@ -17,7 +17,6 @@ data class CordappImpl( override val schedulableFlows: List>>, override val services: List>, override val serializationWhitelists: List, - override val serializationCustomSerializerProxies: List>, override val serializationCustomSerializers: List>>, override val customSchemas: Set, override val jarPath: URL) : Cordapp { diff --git a/core/src/main/kotlin/net/corda/core/serialization/CordaCustomSerializer.kt b/core/src/main/kotlin/net/corda/core/serialization/CordaCustomSerializer.kt index df3cb035aa..4847452b83 100644 --- a/core/src/main/kotlin/net/corda/core/serialization/CordaCustomSerializer.kt +++ b/core/src/main/kotlin/net/corda/core/serialization/CordaCustomSerializer.kt @@ -8,11 +8,3 @@ package net.corda.core.serialization @Target(AnnotationTarget.CLASS) annotation class CordaCustomSerializer -/** - * This annotation marks a class as being a proxy for some third party class and used by some - * implementation of [SerializationCustomSerializer]. Such classes must be annotated to allow - * them to be discovered in a CorDapp jar and loaded - */ -@Target(AnnotationTarget.CLASS) -annotation class CordaCustomSerializerProxy - diff --git a/core/src/main/kotlin/net/corda/core/serialization/SerializationCustomSerializer.kt b/core/src/main/kotlin/net/corda/core/serialization/SerializationCustomSerializer.kt index d5fb39d30e..e40b2c884f 100644 --- a/core/src/main/kotlin/net/corda/core/serialization/SerializationCustomSerializer.kt +++ b/core/src/main/kotlin/net/corda/core/serialization/SerializationCustomSerializer.kt @@ -17,9 +17,6 @@ import java.lang.reflect.Type * * Failing to apply either annotation will result in the class not being loaded by Corda and thus serialization * failing - * - * @property type the type of the object that this class is proxying - * @property ptype the type of the proxy object used as an intermediate representation of [type] */ interface SerializationCustomSerializer { /** @@ -33,14 +30,4 @@ interface SerializationCustomSerializer { * unserializable type */ fun fromProxy(proxy: PROXY) : OBJ - - /** - * Should be set to the type of the object being proxied - */ - val type: Type - - /** - * Should be set to the proxy objects type - */ - val ptype: Type } \ No newline at end of file diff --git a/docs/source/cordapp-custom-serializers.rst b/docs/source/cordapp-custom-serializers.rst index fdf8f82e59..9a232c5831 100644 --- a/docs/source/cordapp-custom-serializers.rst +++ b/docs/source/cordapp-custom-serializers.rst @@ -1,16 +1,18 @@ Pluggable Serializers for CorDapps ================================== -To be serializable by Corda Java classes must be compiled with the -parameter switch to enable matching of it's properties -to constructor parameters. This is important because Corda's internal AMQP serialization scheme will only constuct + +.. contents:: + +To be serializable by Corda Java classes must be compiled with the -parameters switch to enable matching of it's properties +to constructor parameters. This is important because Corda's internal AMQP serialization scheme will only construct objects using their constructors. However, when recompilation isn't possible, or classes are built in such a way that they cannot be easily modified for simple serailization, CorDapps can provide custom proxy serializers that Corda -can use to move from types it cannot serializer to an interim representation that it can with the transformation to and +can use to move from types it cannot serialize to an interim representation that it can with the transformation to and from this proxy object being handled by the supplied serializer. Serializer Location ------------------- -Custom serializers should be placed in the plugins directory of a CorDapp or a sub directory thereof. These -classes will be scanned and loaded by the CorDapp loading process. +Custom serializer classes should follow the rules for including classes found in :doc:`cordapp-build-systems` Writing a Custom Serializer --------------------------- @@ -26,7 +28,9 @@ Example ------- Consider this example class + .. sourcecode:: java + public final class Example { private final Int a private final Int b @@ -42,33 +46,31 @@ Consider this example class public int getB() { return b; } } -Without a custom serializer we cannot serialise this class as there is no public constructor that facilitates the -initialisation of al of its's properties. +Without a custom serializer we cannot serialize this class as there is no public constructor that facilitates the +initialisation of all of its's properties. To be serializable by Corda this would require a custom serializer as follows .. sourcecode:: kotlin + @CordaCustomSerializer - class ExampleSerializer : SerializationCustomSerializer { + class ExampleSerializer : SerializationCustomSerializer { @CordaCustomSerializerProxy data class Proxy(val a: Int, val b: Int) - override fun toProxy(obj: Any): Any = Proxy((obj as Example).a, obj.b) + override fun toProxy(obj: Example) = Proxy(obj.a, obj.b) - override fun fromProxy(proxy: Any): Any { + override fun fromProxy(proxy: Proxy) : Example { val constructorArg = IntArray(2); - constructorArg[0] = (proxy as Proxy).a + constructorArg[0] = proxy.a constructorArg[1] = proxy.b return Example.create(constructorArg) } - - override val type: Type get() = Example::class.java - override val ptype: Type get() = Proxy::class.java } Whitelisting ------------ By writing a custom serializer for a class it has the effect of adding that class to the whitelist, meaning such -classes don't need explicitly adding to the CorDapp's whitelist +classes don't need explicitly adding to the CorDapp's whitelist. diff --git a/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/amqp/CorDappCustomSerializer.kt b/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/amqp/CorDappCustomSerializer.kt index cdf6d59325..eaa27db161 100644 --- a/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/amqp/CorDappCustomSerializer.kt +++ b/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/amqp/CorDappCustomSerializer.kt @@ -5,24 +5,70 @@ import net.corda.core.serialization.SerializationCustomSerializer import net.corda.nodeapi.internal.serialization.amqp.SerializerFactory.Companion.nameForType import org.apache.qpid.proton.amqp.Symbol import org.apache.qpid.proton.codec.Data +import java.io.NotSerializableException import java.lang.reflect.Type +import kotlin.reflect.jvm.javaType +import kotlin.reflect.jvm.jvmErasure +/** + * Index into the types list of the parent type of the serializer object, should be the + * type that this object proxies for + */ +const val CORDAPP_TYPE = 0 + +/** + * Index into the types list of the parent type of the serializer object, should be the + * type of the proxy object that we're using to represent the object we're proxying for + */ +const val PROXY_TYPE = 1 + +/** + * Wrapper class for user provided serializers + * + * Through the CorDapp JAR scanner we will have a list of custom serializer types that implement + * the toProxy and fromProxy methods. This class takes an instance of one of those objects and + * embeds it within a serialization context associated with a serializer factory by creating + * and instance of this class and registering that with a [SerializerFactory] + * + * Proxy serializers should transform an unserializable class into a representation that we can serialize + * + * @property serializer in instance of a user written serialization proxy, normally scanned and loaded + * automatically + * @property type the Java [Type] of the class which this serializes, inferred via reflection of the + * [serializer]'s super type + * @property proxyType the Java [Type] of the class into which instances of [type] are proxied for use byt + * the underlying serialisation engine + * + * @param factory a [SerializerFactory] belonging to the context this serializer is being instantiated + * for + */ class CorDappCustomSerializer( - private val serialiser: SerializationCustomSerializer<*, *>, - factory: SerializerFactory) - : AMQPSerializer, SerializerFor { + private val serializer: SerializationCustomSerializer<*, *>, + factory: SerializerFactory) : AMQPSerializer, SerializerFor { override val revealSubclassesInSchema: Boolean get() = false - override val type: Type get() = serialiser.type + private val types = serializer::class.supertypes.filter { it.jvmErasure == SerializationCustomSerializer::class } + .flatMap { it.arguments } + .map { it.type!!.javaType } + + init { + if (types.size != 2) { + throw NotSerializableException("Unable to determine serializer parent types") + } + } + + override val type = types[CORDAPP_TYPE] + val proxyType = types[PROXY_TYPE] + override val typeDescriptor = Symbol.valueOf("$DESCRIPTOR_DOMAIN:${nameForType(type)}") val descriptor: Descriptor = Descriptor(typeDescriptor) - private val proxySerializer: ObjectSerializer by lazy { ObjectSerializer(serialiser.ptype, factory) } + private val proxySerializer: ObjectSerializer by lazy { ObjectSerializer(proxyType, factory) } override fun writeClassInfo(output: SerializationOutput) {} override fun writeObject(obj: Any, data: Data, type: Type, output: SerializationOutput) { - @Suppress("UNCHECKED_CAST") - val proxy = (serialiser as SerializationCustomSerializer).toProxy(obj) + val proxy = uncheckedCast, + SerializationCustomSerializer> (serializer).toProxy(obj) data.withDescribed(descriptor) { data.withList { @@ -33,11 +79,10 @@ class CorDappCustomSerializer( } } - override fun readObject(obj: Any, schema: Schema, input: DeserializationInput) = - @Suppress("UNCHECKED_CAST") - (serialiser as SerializationCustomSerializer).fromProxy( - uncheckedCast(proxySerializer.readObject(obj, schema, input)))!! + override fun readObject(obj: Any, schemas: SerializationSchemas, input: DeserializationInput) = + uncheckedCast, SerializationCustomSerializer> ( + serializer).fromProxy(uncheckedCast(proxySerializer.readObject(obj, schemas, input)))!! - override fun isSerializerFor(clazz: Class<*>): Boolean = clazz == type + override fun isSerializerFor(clazz: Class<*>) = clazz == type } diff --git a/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/amqp/SerializerFactory.kt b/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/amqp/SerializerFactory.kt index 0f091029cb..0dbc3f8be9 100644 --- a/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/amqp/SerializerFactory.kt +++ b/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/amqp/SerializerFactory.kt @@ -204,8 +204,8 @@ open class SerializerFactory(val whitelist: ClassWhitelist, cl: ClassLoader) { } /** - * Iterate over an AMQP schema, for each type ascertain weather it's on ClassPath of [classloader] amd - * if not use the [ClassCarpenter] to generate a class to use in it's place + * Iterate over an AMQP schema, for each type ascertain whether it's on ClassPath of [classloader] and, + * if not, use the [ClassCarpenter] to generate a class to use in it's place. */ private fun processSchema(schemaAndDescriptor: FactorySchemaAndDescriptor, sentinel: Boolean = false) { val metaSchema = CarpenterMetaSchema.newInstance() diff --git a/node-api/src/test/kotlin/net/corda/nodeapi/internal/serialization/amqp/CorDappSerializerTests.kt b/node-api/src/test/kotlin/net/corda/nodeapi/internal/serialization/amqp/CorDappSerializerTests.kt index fdd9b11a40..07d4c1ad3c 100644 --- a/node-api/src/test/kotlin/net/corda/nodeapi/internal/serialization/amqp/CorDappSerializerTests.kt +++ b/node-api/src/test/kotlin/net/corda/nodeapi/internal/serialization/amqp/CorDappSerializerTests.kt @@ -2,27 +2,21 @@ package net.corda.nodeapi.internal.serialization.amqp import org.junit.Test import net.corda.core.serialization.CordaCustomSerializer -import net.corda.core.serialization.CordaCustomSerializerProxy import net.corda.core.serialization.ClassWhitelist import net.corda.core.serialization.SerializationCustomSerializer import org.assertj.core.api.Assertions import java.io.NotSerializableException -import java.lang.reflect.Type import kotlin.test.assertEquals class CorDappSerializerTests { data class NeedsProxy (val a: String) @CordaCustomSerializer - class NeedsProxyProxySerializer : SerializationCustomSerializer { - @CordaCustomSerializerProxy + class NeedsProxyProxySerializer : SerializationCustomSerializer { data class Proxy(val proxy_a_: String) - override val type: Type get() = NeedsProxy::class.java - override val ptype: Type get() = Proxy::class.java - - override fun fromProxy(proxy: Any) : Any = NeedsProxy((proxy as Proxy).proxy_a_) - override fun toProxy(obj: Any) : Any = Proxy((obj as NeedsProxy).a) + override fun fromProxy(proxy: Proxy) = NeedsProxy(proxy.proxy_a_) + override fun toProxy(obj: NeedsProxy) = Proxy(obj.a) } // Standard proxy serializer used internally, here for comparison purposes @@ -34,12 +28,10 @@ class CorDappSerializerTests { data class Proxy(val proxy_a_: String) override fun toProxy(obj: NeedsProxy): Proxy { - println ("InternalProxySerializer - toProxy") return Proxy(obj.a) } override fun fromProxy(proxy: Proxy): NeedsProxy { - println ("InternalProxySerializer - fromProxy") return NeedsProxy(proxy.proxy_a_) } } diff --git a/node/src/main/kotlin/net/corda/node/internal/cordapp/CordappLoader.kt b/node/src/main/kotlin/net/corda/node/internal/cordapp/CordappLoader.kt index 7688a611db..47e31ee761 100644 --- a/node/src/main/kotlin/net/corda/node/internal/cordapp/CordappLoader.kt +++ b/node/src/main/kotlin/net/corda/node/internal/cordapp/CordappLoader.kt @@ -181,7 +181,6 @@ class CordappLoader private constructor(private val cordappJarPaths: List> { - return scanResult.getClassesWithAnnotation(Class::class, CordaCustomSerializerProxy::class) - } - private fun findSerialzers(scanResult: RestrictedScanResult) : List>> { return scanResult.getClassesWithAnnotation(SerializationCustomSerializer::class, CordaCustomSerializer::class) } diff --git a/samples/simm-valuation-demo/src/main/kotlin/net/corda/vega/plugin/customserializers/CurrencyParameterSensitivitiesSerializer.kt b/samples/simm-valuation-demo/src/main/kotlin/net/corda/vega/plugin/customserializers/CurrencyParameterSensitivitiesSerializer.kt index 42eb965689..e7d979a383 100644 --- a/samples/simm-valuation-demo/src/main/kotlin/net/corda/vega/plugin/customserializers/CurrencyParameterSensitivitiesSerializer.kt +++ b/samples/simm-valuation-demo/src/main/kotlin/net/corda/vega/plugin/customserializers/CurrencyParameterSensitivitiesSerializer.kt @@ -3,20 +3,14 @@ package net.corda.vega.plugin.customserializers import com.opengamma.strata.market.param.CurrencyParameterSensitivities import com.opengamma.strata.market.param.CurrencyParameterSensitivity import net.corda.core.serialization.CordaCustomSerializer -import net.corda.core.serialization.CordaCustomSerializerProxy import net.corda.core.serialization.SerializationCustomSerializer -import java.lang.reflect.Type @CordaCustomSerializer @Suppress("UNUSED") class CurrencyParameterSensitivitiesSerializer : SerializationCustomSerializer { - @CordaCustomSerializerProxy data class Proxy(val sensitivities: List) - override val type: Type get() = CurrencyParameterSensitivities::class.java - override val ptype: Type get() = Proxy::class.java - override fun fromProxy(proxy: Proxy) = CurrencyParameterSensitivities.of(proxy.sensitivities) override fun toProxy(obj: CurrencyParameterSensitivities) = Proxy(obj.sensitivities) } \ No newline at end of file diff --git a/samples/simm-valuation-demo/src/main/kotlin/net/corda/vega/plugin/customserializers/CurrencyParameterSensitivitySerialiser.kt b/samples/simm-valuation-demo/src/main/kotlin/net/corda/vega/plugin/customserializers/CurrencyParameterSensitivitySerialiser.kt index 98dde48d8d..d72dd118c5 100644 --- a/samples/simm-valuation-demo/src/main/kotlin/net/corda/vega/plugin/customserializers/CurrencyParameterSensitivitySerialiser.kt +++ b/samples/simm-valuation-demo/src/main/kotlin/net/corda/vega/plugin/customserializers/CurrencyParameterSensitivitySerialiser.kt @@ -6,22 +6,16 @@ import com.opengamma.strata.data.MarketDataName import com.opengamma.strata.collect.array.DoubleArray import com.opengamma.strata.basics.currency.Currency import net.corda.core.serialization.CordaCustomSerializer -import net.corda.core.serialization.CordaCustomSerializerProxy import net.corda.core.serialization.SerializationCustomSerializer -import java.lang.reflect.Type @CordaCustomSerializer @Suppress("UNUSED") class CurrencyParameterSensitivitySerializer : SerializationCustomSerializer { - @CordaCustomSerializerProxy data class Proxy(val currency: Currency, val marketDataName: MarketDataName<*>, val parameterMetadata: List, val sensitivity: DoubleArray) - override val type: Type get() = CurrencyParameterSensitivity::class.java - override val ptype: Type get() = Proxy::class.java - override fun fromProxy(proxy: CurrencyParameterSensitivitySerializer.Proxy) = CurrencyParameterSensitivity.of( proxy.marketDataName, diff --git a/samples/simm-valuation-demo/src/main/kotlin/net/corda/vega/plugin/customserializers/CurrencySerializer.kt b/samples/simm-valuation-demo/src/main/kotlin/net/corda/vega/plugin/customserializers/CurrencySerializer.kt index f3b5534459..faef83c94a 100644 --- a/samples/simm-valuation-demo/src/main/kotlin/net/corda/vega/plugin/customserializers/CurrencySerializer.kt +++ b/samples/simm-valuation-demo/src/main/kotlin/net/corda/vega/plugin/customserializers/CurrencySerializer.kt @@ -2,19 +2,13 @@ package net.corda.vega.plugin.customserializers import com.opengamma.strata.basics.currency.Currency import net.corda.core.serialization.CordaCustomSerializer -import net.corda.core.serialization.CordaCustomSerializerProxy import net.corda.core.serialization.SerializationCustomSerializer -import java.lang.reflect.Type @CordaCustomSerializer @Suppress("UNUSED") class CurrencySerializer : SerializationCustomSerializer { - @CordaCustomSerializerProxy data class Proxy(val currency: String) - override val type: Type get() = Currency::class.java - override val ptype: Type get() = Proxy::class.java - override fun fromProxy(proxy: Proxy) = Currency.parse(proxy.currency) override fun toProxy(obj: Currency) = Proxy(obj.toString()) } diff --git a/samples/simm-valuation-demo/src/main/kotlin/net/corda/vega/plugin/customserializers/DoubleArraySerializer.kt b/samples/simm-valuation-demo/src/main/kotlin/net/corda/vega/plugin/customserializers/DoubleArraySerializer.kt index d9ad8e2c10..05fa8c8a7d 100644 --- a/samples/simm-valuation-demo/src/main/kotlin/net/corda/vega/plugin/customserializers/DoubleArraySerializer.kt +++ b/samples/simm-valuation-demo/src/main/kotlin/net/corda/vega/plugin/customserializers/DoubleArraySerializer.kt @@ -1,20 +1,14 @@ package net.corda.vega.plugin.customserializers import net.corda.core.serialization.CordaCustomSerializer -import net.corda.core.serialization.CordaCustomSerializerProxy import net.corda.core.serialization.SerializationCustomSerializer import com.opengamma.strata.collect.array.DoubleArray -import java.lang.reflect.Type @CordaCustomSerializer @Suppress("UNUSED") class DoubleArraySerializer : SerializationCustomSerializer { - @CordaCustomSerializerProxy data class Proxy(val amount: kotlin.DoubleArray) - override val type: Type get() = DoubleArray::class.java - override val ptype: Type get() = Proxy::class.java - override fun fromProxy(proxy: Proxy) = DoubleArray.copyOf(proxy.amount) override fun toProxy(obj: DoubleArray) = Proxy(obj.toArray()) } diff --git a/samples/simm-valuation-demo/src/main/kotlin/net/corda/vega/plugin/customserializers/MultiCurrencyAmountSerializer.kt b/samples/simm-valuation-demo/src/main/kotlin/net/corda/vega/plugin/customserializers/MultiCurrencyAmountSerializer.kt index 2df9ca801e..884617d1ff 100644 --- a/samples/simm-valuation-demo/src/main/kotlin/net/corda/vega/plugin/customserializers/MultiCurrencyAmountSerializer.kt +++ b/samples/simm-valuation-demo/src/main/kotlin/net/corda/vega/plugin/customserializers/MultiCurrencyAmountSerializer.kt @@ -3,20 +3,15 @@ package net.corda.vega.plugin.customserializers import com.opengamma.strata.basics.currency.MultiCurrencyAmount import com.opengamma.strata.basics.currency.Currency import net.corda.core.serialization.* -import java.lang.reflect.Type @CordaCustomSerializer @Suppress("UNUSED") class MultiCurrencyAmountSerializer : SerializationCustomSerializer { - @CordaCustomSerializerProxy data class Proxy(val curencies : Map) override fun toProxy(obj: MultiCurrencyAmount) = Proxy(obj.toMap()) override fun fromProxy(proxy: Proxy) = MultiCurrencyAmount.of(proxy.curencies) - - override val type: Type get() = MultiCurrencyAmount::class.java - override val ptype: Type get() = Proxy::class.java } diff --git a/samples/simm-valuation-demo/src/main/kotlin/net/corda/vega/plugin/customserializers/TenorDateParameterMetadataSerializer.kt b/samples/simm-valuation-demo/src/main/kotlin/net/corda/vega/plugin/customserializers/TenorDateParameterMetadataSerializer.kt index 9814dbbf3f..1fa1ffd1c0 100644 --- a/samples/simm-valuation-demo/src/main/kotlin/net/corda/vega/plugin/customserializers/TenorDateParameterMetadataSerializer.kt +++ b/samples/simm-valuation-demo/src/main/kotlin/net/corda/vega/plugin/customserializers/TenorDateParameterMetadataSerializer.kt @@ -3,20 +3,14 @@ package net.corda.vega.plugin.customserializers import com.opengamma.strata.basics.date.Tenor import com.opengamma.strata.market.param.TenorDateParameterMetadata import net.corda.core.serialization.* -import java.lang.reflect.Type import java.time.LocalDate @CordaCustomSerializer @Suppress("UNUSED") class TenorDateParameterMetadataSerializer : SerializationCustomSerializer { - @CordaCustomSerializerProxy data class Proxy(val tenor: Tenor, val date: LocalDate, val identifier: Tenor, val label: String) - override val type: Type get() = TenorDateParameterMetadata::class.java - override val ptype: Type get() = Proxy::class.java - - override fun toProxy(obj: TenorDateParameterMetadata) = Proxy(obj.tenor, obj.date, obj.identifier, obj.label) override fun fromProxy(proxy: Proxy) = TenorDateParameterMetadata.of(proxy.date, proxy.tenor, proxy.label) } diff --git a/samples/simm-valuation-demo/src/main/kotlin/net/corda/vega/plugin/customserializers/TenorSerializer.kt b/samples/simm-valuation-demo/src/main/kotlin/net/corda/vega/plugin/customserializers/TenorSerializer.kt index f798a289fe..177a5ee84a 100644 --- a/samples/simm-valuation-demo/src/main/kotlin/net/corda/vega/plugin/customserializers/TenorSerializer.kt +++ b/samples/simm-valuation-demo/src/main/kotlin/net/corda/vega/plugin/customserializers/TenorSerializer.kt @@ -2,18 +2,13 @@ package net.corda.vega.plugin.customserializers import com.opengamma.strata.basics.date.Tenor import net.corda.core.serialization.* -import java.lang.reflect.Type import java.time.Period @CordaCustomSerializer @Suppress("UNUSED") class TenorSerializer : SerializationCustomSerializer { - @CordaCustomSerializerProxy data class Proxy(val years: Int, val months: Int, val days: Int, val name: String) - override val type: Type get() = Tenor::class.java - override val ptype: Type get() = Proxy::class.java - override fun toProxy(obj: Tenor) = Proxy(obj.period.years, obj.period.months, obj.period.days, obj.toString()) override fun fromProxy(proxy: Proxy) = Tenor.of (Period.of(proxy.years, proxy.months, proxy.days)) } diff --git a/testing/test-utils/src/main/kotlin/net/corda/testing/SerializationTestHelpers.kt b/testing/test-utils/src/main/kotlin/net/corda/testing/SerializationTestHelpers.kt index 8581534d0f..026c76e56d 100644 --- a/testing/test-utils/src/main/kotlin/net/corda/testing/SerializationTestHelpers.kt +++ b/testing/test-utils/src/main/kotlin/net/corda/testing/SerializationTestHelpers.kt @@ -104,6 +104,7 @@ private fun createTestSerializationEnv(label: String) = object : SerializationEn }, AMQP_P2P_CONTEXT, KRYO_RPC_SERVER_CONTEXT, + KRYO_RPC_CLIENT_CONTEXT, AMQP_STORAGE_CONTEXT, KRYO_CHECKPOINT_CONTEXT) { override fun toString() = "testSerializationEnv($label)" diff --git a/testing/test-utils/src/main/kotlin/net/corda/testing/node/MockCordappProvider.kt b/testing/test-utils/src/main/kotlin/net/corda/testing/node/MockCordappProvider.kt index aa8e19965f..e50f4dea84 100644 --- a/testing/test-utils/src/main/kotlin/net/corda/testing/node/MockCordappProvider.kt +++ b/testing/test-utils/src/main/kotlin/net/corda/testing/node/MockCordappProvider.kt @@ -22,7 +22,6 @@ class MockCordappProvider(cordappLoader: CordappLoader, attachmentStorage: Attac schedulableFlows = emptyList(), services = emptyList(), serializationWhitelists = emptyList(), - serializationCustomSerializerProxies = emptyList(), serializationCustomSerializers = emptyList(), customSchemas = emptySet(), jarPath = Paths.get(".").toUri().toURL()) From 1f7083cd61ab84e6c0b2b82d66368582d3a2ff0c Mon Sep 17 00:00:00 2001 From: Katelyn Baker Date: Mon, 4 Dec 2017 16:55:59 +0000 Subject: [PATCH 26/42] CORDA-553 - Review comments --- core/src/main/kotlin/net/corda/core/cordapp/Cordapp.kt | 1 - docs/source/cordapp-custom-serializers.rst | 7 +++---- 2 files changed, 3 insertions(+), 5 deletions(-) diff --git a/core/src/main/kotlin/net/corda/core/cordapp/Cordapp.kt b/core/src/main/kotlin/net/corda/core/cordapp/Cordapp.kt index 3729da353d..1344d91660 100644 --- a/core/src/main/kotlin/net/corda/core/cordapp/Cordapp.kt +++ b/core/src/main/kotlin/net/corda/core/cordapp/Cordapp.kt @@ -23,7 +23,6 @@ import java.net.URL * @property schedulableFlows List of flows startable by the scheduler * @property services List of RPC services * @property serializationWhitelists List of Corda plugin registries - * @property serializationCustomSerializerProxies List of Proxy classes used by the custom serializers * @property serializationCustomSerializers List of serializers * @property customSchemas List of custom schemas * @property jarPath The path to the JAR for this CorDapp diff --git a/docs/source/cordapp-custom-serializers.rst b/docs/source/cordapp-custom-serializers.rst index 9a232c5831..daa4c4cbd6 100644 --- a/docs/source/cordapp-custom-serializers.rst +++ b/docs/source/cordapp-custom-serializers.rst @@ -6,7 +6,7 @@ Pluggable Serializers for CorDapps To be serializable by Corda Java classes must be compiled with the -parameters switch to enable matching of it's properties to constructor parameters. This is important because Corda's internal AMQP serialization scheme will only construct objects using their constructors. However, when recompilation isn't possible, or classes are built in such a way that -they cannot be easily modified for simple serailization, CorDapps can provide custom proxy serializers that Corda +they cannot be easily modified for simple serialization, CorDapps can provide custom proxy serializers that Corda can use to move from types it cannot serialize to an interim representation that it can with the transformation to and from this proxy object being handled by the supplied serializer. @@ -20,7 +20,7 @@ Serializers must * Inherit from net.corda.core.serialization.SerializationCustomSerializer * Be annotated with the @CordaCustomSerializer annotation * Provide a proxy class to transform the object to and from - * Have that proxy class annotated with the @CordaCustomSerializerProxy annotation + * Implmenet the ``toProxy`` and ``fromProxy`` methods Serializers inheriting from SerializationCustomSerializer have to implement two methods and two types @@ -47,7 +47,7 @@ Consider this example class } Without a custom serializer we cannot serialize this class as there is no public constructor that facilitates the -initialisation of all of its's properties. +initialisation of all of it's properties. To be serializable by Corda this would require a custom serializer as follows @@ -55,7 +55,6 @@ To be serializable by Corda this would require a custom serializer as follows @CordaCustomSerializer class ExampleSerializer : SerializationCustomSerializer { - @CordaCustomSerializerProxy data class Proxy(val a: Int, val b: Int) override fun toProxy(obj: Example) = Proxy(obj.a, obj.b) From c4d5d3817cb50d227cc53d553a8016467cac18db Mon Sep 17 00:00:00 2001 From: Katelyn Baker Date: Mon, 4 Dec 2017 17:01:46 +0000 Subject: [PATCH 27/42] CORDA-553 - Update api checker With the addition of the CordaCustomSerializer class for third part serializers we need to update the ci reference file --- .ci/api-current.txt | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/.ci/api-current.txt b/.ci/api-current.txt index 6c4eac9c83..c991ab4198 100644 --- a/.ci/api-current.txt +++ b/.ci/api-current.txt @@ -2724,6 +2724,10 @@ public static final class net.corda.core.serialization.SerializationContext$UseC public static net.corda.core.serialization.SerializationContext$UseCase valueOf(String) public static net.corda.core.serialization.SerializationContext$UseCase[] values() ## +public interface net.corda.core.serialization.SerializationCustomSerializer + public abstract Object fromProxy(Object) + public abstract Object toProxy(Object) +## public final class net.corda.core.serialization.SerializationDefaults extends java.lang.Object @org.jetbrains.annotations.NotNull public final net.corda.core.serialization.SerializationContext getCHECKPOINT_CONTEXT() @org.jetbrains.annotations.NotNull public final net.corda.core.serialization.SerializationContext getP2P_CONTEXT() From 8878fa99a06aaee214128639ec5c58f399aafbd3 Mon Sep 17 00:00:00 2001 From: Katelyn Baker Date: Mon, 4 Dec 2017 17:56:27 +0000 Subject: [PATCH 28/42] CORDA-553 - Remove CordaCustomSerializer annotation It isn't actually needed as we can scan the jar for classes implementing the interface. --- .../kotlin/net/corda/core/cordapp/Cordapp.kt | 2 +- .../core/internal/cordapp/CordappImpl.kt | 2 +- .../serialization/CordaCustomSerializer.kt | 10 ---------- .../SerializationCustomSerializer.kt | 19 +++++-------------- docs/source/cordapp-custom-serializers.rst | 12 +++++------- docs/source/release-notes.rst | 8 ++++---- .../amqp/AMQPSerializationScheme.kt | 2 +- .../amqp/CorDappSerializerTests.kt | 2 -- .../node/internal/cordapp/CordappLoader.kt | 13 +++++++++++-- ...urrencyParameterSensitivitiesSerializer.kt | 2 -- .../CurrencyParameterSensitivitySerialiser.kt | 2 -- .../customserializers/CurrencySerializer.kt | 2 -- .../DoubleArraySerializer.kt | 2 -- .../MultiCurrencyAmountSerializer.kt | 1 - .../TenorDateParameterMetadataSerializer.kt | 1 - .../customserializers/TenorSerializer.kt | 1 - 16 files changed, 28 insertions(+), 53 deletions(-) delete mode 100644 core/src/main/kotlin/net/corda/core/serialization/CordaCustomSerializer.kt diff --git a/core/src/main/kotlin/net/corda/core/cordapp/Cordapp.kt b/core/src/main/kotlin/net/corda/core/cordapp/Cordapp.kt index 1344d91660..2ce4c24ce1 100644 --- a/core/src/main/kotlin/net/corda/core/cordapp/Cordapp.kt +++ b/core/src/main/kotlin/net/corda/core/cordapp/Cordapp.kt @@ -37,7 +37,7 @@ interface Cordapp { val schedulableFlows: List>> val services: List> val serializationWhitelists: List - val serializationCustomSerializers: List>> + val serializationCustomSerializers: List> val customSchemas: Set val jarPath: URL val cordappClasses: List diff --git a/core/src/main/kotlin/net/corda/core/internal/cordapp/CordappImpl.kt b/core/src/main/kotlin/net/corda/core/internal/cordapp/CordappImpl.kt index d0d5c4dec3..034f3a9d10 100644 --- a/core/src/main/kotlin/net/corda/core/internal/cordapp/CordappImpl.kt +++ b/core/src/main/kotlin/net/corda/core/internal/cordapp/CordappImpl.kt @@ -17,7 +17,7 @@ data class CordappImpl( override val schedulableFlows: List>>, override val services: List>, override val serializationWhitelists: List, - override val serializationCustomSerializers: List>>, + override val serializationCustomSerializers: List>, override val customSchemas: Set, override val jarPath: URL) : Cordapp { override val name: String = File(jarPath.toURI()).name.removeSuffix(".jar") diff --git a/core/src/main/kotlin/net/corda/core/serialization/CordaCustomSerializer.kt b/core/src/main/kotlin/net/corda/core/serialization/CordaCustomSerializer.kt deleted file mode 100644 index 4847452b83..0000000000 --- a/core/src/main/kotlin/net/corda/core/serialization/CordaCustomSerializer.kt +++ /dev/null @@ -1,10 +0,0 @@ -package net.corda.core.serialization - -/** - * This annotation marks a class as being a serializer provided by a CorDapp to proxy some third party - * class Corda couldn't otherwise serialize. It should be applied to classes that implement the - * [SerializationCustomSerializer] interface - */ -@Target(AnnotationTarget.CLASS) -annotation class CordaCustomSerializer - diff --git a/core/src/main/kotlin/net/corda/core/serialization/SerializationCustomSerializer.kt b/core/src/main/kotlin/net/corda/core/serialization/SerializationCustomSerializer.kt index e40b2c884f..bf81af2898 100644 --- a/core/src/main/kotlin/net/corda/core/serialization/SerializationCustomSerializer.kt +++ b/core/src/main/kotlin/net/corda/core/serialization/SerializationCustomSerializer.kt @@ -1,27 +1,18 @@ package net.corda.core.serialization -import java.lang.reflect.Type - /** * Allows CorDapps to provide custom serializers for third party libraries where those libraries cannot - * be recompiled with the -parmater flag rendering their classes natively serializable by Corda. In this case + * be recompiled with the -parameters flag rendering their classes natively serializable by Corda. In this case * a proxy serializer can be written that extends this type whose purpose is to move between those an - * unserializable types and an intermediate representation + * unserializable types and an intermediate representation. * - * NOTE: The proxy object must be specified as a seperate class. However, this can be defined within the - * scope of the serializer. Also, that class must be annotated with the [CordaCustomSerializerProxy] - * annotation - * - * For instances of this class to be discoverable they must be annotated with the [CordaCustomSerializer] - * annotation. - * - * Failing to apply either annotation will result in the class not being loaded by Corda and thus serialization - * failing + * NOTE: The proxy object must should be specified as a seperate class. However, this can be defined within the + * scope of the custom serializer. */ interface SerializationCustomSerializer { /** * Should facilitate the conversion of the third party object into the serializable - * local class specified by [ptype] + * local class specified by [PROXY] */ fun toProxy(obj: OBJ) : PROXY diff --git a/docs/source/cordapp-custom-serializers.rst b/docs/source/cordapp-custom-serializers.rst index daa4c4cbd6..4541d18f3d 100644 --- a/docs/source/cordapp-custom-serializers.rst +++ b/docs/source/cordapp-custom-serializers.rst @@ -3,7 +3,7 @@ Pluggable Serializers for CorDapps .. contents:: -To be serializable by Corda Java classes must be compiled with the -parameters switch to enable matching of it's properties +To be serializable by Corda Java classes must be compiled with the -parameters switch to enable matching of its properties to constructor parameters. This is important because Corda's internal AMQP serialization scheme will only construct objects using their constructors. However, when recompilation isn't possible, or classes are built in such a way that they cannot be easily modified for simple serialization, CorDapps can provide custom proxy serializers that Corda @@ -18,11 +18,10 @@ Writing a Custom Serializer --------------------------- Serializers must * Inherit from net.corda.core.serialization.SerializationCustomSerializer - * Be annotated with the @CordaCustomSerializer annotation * Provide a proxy class to transform the object to and from - * Implmenet the ``toProxy`` and ``fromProxy`` methods + * Implement the ``toProxy`` and ``fromProxy`` methods -Serializers inheriting from SerializationCustomSerializer have to implement two methods and two types +Serializers inheriting from SerializationCustomSerializer have to implement two methods and two types. Example ------- @@ -47,13 +46,12 @@ Consider this example class } Without a custom serializer we cannot serialize this class as there is no public constructor that facilitates the -initialisation of all of it's properties. +initialisation of all of its properties. -To be serializable by Corda this would require a custom serializer as follows +To be serializable by Corda this would require a custom serializer as follows: .. sourcecode:: kotlin - @CordaCustomSerializer class ExampleSerializer : SerializationCustomSerializer { data class Proxy(val a: Int, val b: Int) diff --git a/docs/source/release-notes.rst b/docs/source/release-notes.rst index 631a20a628..2816383bc8 100644 --- a/docs/source/release-notes.rst +++ b/docs/source/release-notes.rst @@ -18,11 +18,11 @@ AMQP Serialization is now enabled for both peer to peer communication and writin brings a stable format Corda can support internally throughout it's lifetime that meets the needs of Corda and our users. -* **CorDapp Custom Serializers** +* **Custom Serializers** -To allow interop with third party libraries that cannot be recompiled we add functionality that allows custom serialises -to be written for those classes provided. If needed, a proxy object can be created as an interim step that allows Corda's internal -serialisers to operate on those types. +To allow interop with third party libraries that cannot be recompiled we add functionality that allows custom serializers +to be written for those classes. If needed, a proxy object can be created as an interim step that allows Corda's internal +serializers to operate on those types. A good example of this is the SIMM valuation demo which has a number of such serializers defined in the plugin/customserializers package diff --git a/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/amqp/AMQPSerializationScheme.kt b/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/amqp/AMQPSerializationScheme.kt index 62172dc12c..ea4c90ebf5 100644 --- a/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/amqp/AMQPSerializationScheme.kt +++ b/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/amqp/AMQPSerializationScheme.kt @@ -70,7 +70,7 @@ abstract class AbstractAMQPSerializationScheme(val cordappLoader: List) cordappLoader.forEach { ca -> ca.serializationCustomSerializers.forEach { - factory.registerExternal(CorDappCustomSerializer(it.newInstance(), factory)) + factory.registerExternal(CorDappCustomSerializer(it, factory)) } } } diff --git a/node-api/src/test/kotlin/net/corda/nodeapi/internal/serialization/amqp/CorDappSerializerTests.kt b/node-api/src/test/kotlin/net/corda/nodeapi/internal/serialization/amqp/CorDappSerializerTests.kt index 07d4c1ad3c..d809f7beda 100644 --- a/node-api/src/test/kotlin/net/corda/nodeapi/internal/serialization/amqp/CorDappSerializerTests.kt +++ b/node-api/src/test/kotlin/net/corda/nodeapi/internal/serialization/amqp/CorDappSerializerTests.kt @@ -1,7 +1,6 @@ package net.corda.nodeapi.internal.serialization.amqp import org.junit.Test -import net.corda.core.serialization.CordaCustomSerializer import net.corda.core.serialization.ClassWhitelist import net.corda.core.serialization.SerializationCustomSerializer import org.assertj.core.api.Assertions @@ -11,7 +10,6 @@ import kotlin.test.assertEquals class CorDappSerializerTests { data class NeedsProxy (val a: String) - @CordaCustomSerializer class NeedsProxyProxySerializer : SerializationCustomSerializer { data class Proxy(val proxy_a_: String) diff --git a/node/src/main/kotlin/net/corda/node/internal/cordapp/CordappLoader.kt b/node/src/main/kotlin/net/corda/node/internal/cordapp/CordappLoader.kt index 47e31ee761..38f20c951a 100644 --- a/node/src/main/kotlin/net/corda/node/internal/cordapp/CordappLoader.kt +++ b/node/src/main/kotlin/net/corda/node/internal/cordapp/CordappLoader.kt @@ -248,8 +248,9 @@ class CordappLoader private constructor(private val cordappJarPaths: List>> { - return scanResult.getClassesWithAnnotation(SerializationCustomSerializer::class, CordaCustomSerializer::class) + private fun findSerialzers(scanResult: RestrictedScanResult) : List> { +// return scanResult.getClassesWithAnnotation(SerializationCustomSerializer::class, CordaCustomSerializer::class) + return scanResult.getClassesImplementing(SerializationCustomSerializer::class) } private fun findCustomSchemas(scanResult: RestrictedScanResult): Set { @@ -308,6 +309,14 @@ class CordappLoader private constructor(private val cordappJarPaths: List getClassesImplementing(type: KClass): List { + return scanResult.getNamesOfClassesImplementing(type.java) + .filter { it.startsWith(qualifiedNamePrefix) } + .mapNotNull { loadClass(it, type) } + .filterNot { Modifier.isAbstract(it.modifiers) } + .map { it.kotlin.objectOrNewInstance() } + } + fun getClassesWithAnnotation(type: KClass, annotation: KClass): List> { return scanResult.getNamesOfClassesWithAnnotation(annotation.java) .filter { it.startsWith(qualifiedNamePrefix) } diff --git a/samples/simm-valuation-demo/src/main/kotlin/net/corda/vega/plugin/customserializers/CurrencyParameterSensitivitiesSerializer.kt b/samples/simm-valuation-demo/src/main/kotlin/net/corda/vega/plugin/customserializers/CurrencyParameterSensitivitiesSerializer.kt index e7d979a383..e5f2e3b496 100644 --- a/samples/simm-valuation-demo/src/main/kotlin/net/corda/vega/plugin/customserializers/CurrencyParameterSensitivitiesSerializer.kt +++ b/samples/simm-valuation-demo/src/main/kotlin/net/corda/vega/plugin/customserializers/CurrencyParameterSensitivitiesSerializer.kt @@ -2,10 +2,8 @@ package net.corda.vega.plugin.customserializers import com.opengamma.strata.market.param.CurrencyParameterSensitivities import com.opengamma.strata.market.param.CurrencyParameterSensitivity -import net.corda.core.serialization.CordaCustomSerializer import net.corda.core.serialization.SerializationCustomSerializer -@CordaCustomSerializer @Suppress("UNUSED") class CurrencyParameterSensitivitiesSerializer : SerializationCustomSerializer { diff --git a/samples/simm-valuation-demo/src/main/kotlin/net/corda/vega/plugin/customserializers/CurrencyParameterSensitivitySerialiser.kt b/samples/simm-valuation-demo/src/main/kotlin/net/corda/vega/plugin/customserializers/CurrencyParameterSensitivitySerialiser.kt index d72dd118c5..f0762ec9be 100644 --- a/samples/simm-valuation-demo/src/main/kotlin/net/corda/vega/plugin/customserializers/CurrencyParameterSensitivitySerialiser.kt +++ b/samples/simm-valuation-demo/src/main/kotlin/net/corda/vega/plugin/customserializers/CurrencyParameterSensitivitySerialiser.kt @@ -5,10 +5,8 @@ import com.opengamma.strata.market.param.ParameterMetadata import com.opengamma.strata.data.MarketDataName import com.opengamma.strata.collect.array.DoubleArray import com.opengamma.strata.basics.currency.Currency -import net.corda.core.serialization.CordaCustomSerializer import net.corda.core.serialization.SerializationCustomSerializer -@CordaCustomSerializer @Suppress("UNUSED") class CurrencyParameterSensitivitySerializer : SerializationCustomSerializer { diff --git a/samples/simm-valuation-demo/src/main/kotlin/net/corda/vega/plugin/customserializers/CurrencySerializer.kt b/samples/simm-valuation-demo/src/main/kotlin/net/corda/vega/plugin/customserializers/CurrencySerializer.kt index faef83c94a..2d93977323 100644 --- a/samples/simm-valuation-demo/src/main/kotlin/net/corda/vega/plugin/customserializers/CurrencySerializer.kt +++ b/samples/simm-valuation-demo/src/main/kotlin/net/corda/vega/plugin/customserializers/CurrencySerializer.kt @@ -1,10 +1,8 @@ package net.corda.vega.plugin.customserializers import com.opengamma.strata.basics.currency.Currency -import net.corda.core.serialization.CordaCustomSerializer import net.corda.core.serialization.SerializationCustomSerializer -@CordaCustomSerializer @Suppress("UNUSED") class CurrencySerializer : SerializationCustomSerializer { data class Proxy(val currency: String) diff --git a/samples/simm-valuation-demo/src/main/kotlin/net/corda/vega/plugin/customserializers/DoubleArraySerializer.kt b/samples/simm-valuation-demo/src/main/kotlin/net/corda/vega/plugin/customserializers/DoubleArraySerializer.kt index 05fa8c8a7d..0b105726c9 100644 --- a/samples/simm-valuation-demo/src/main/kotlin/net/corda/vega/plugin/customserializers/DoubleArraySerializer.kt +++ b/samples/simm-valuation-demo/src/main/kotlin/net/corda/vega/plugin/customserializers/DoubleArraySerializer.kt @@ -1,10 +1,8 @@ package net.corda.vega.plugin.customserializers -import net.corda.core.serialization.CordaCustomSerializer import net.corda.core.serialization.SerializationCustomSerializer import com.opengamma.strata.collect.array.DoubleArray -@CordaCustomSerializer @Suppress("UNUSED") class DoubleArraySerializer : SerializationCustomSerializer { data class Proxy(val amount: kotlin.DoubleArray) diff --git a/samples/simm-valuation-demo/src/main/kotlin/net/corda/vega/plugin/customserializers/MultiCurrencyAmountSerializer.kt b/samples/simm-valuation-demo/src/main/kotlin/net/corda/vega/plugin/customserializers/MultiCurrencyAmountSerializer.kt index 884617d1ff..a651aff9a1 100644 --- a/samples/simm-valuation-demo/src/main/kotlin/net/corda/vega/plugin/customserializers/MultiCurrencyAmountSerializer.kt +++ b/samples/simm-valuation-demo/src/main/kotlin/net/corda/vega/plugin/customserializers/MultiCurrencyAmountSerializer.kt @@ -4,7 +4,6 @@ import com.opengamma.strata.basics.currency.MultiCurrencyAmount import com.opengamma.strata.basics.currency.Currency import net.corda.core.serialization.* -@CordaCustomSerializer @Suppress("UNUSED") class MultiCurrencyAmountSerializer : SerializationCustomSerializer { diff --git a/samples/simm-valuation-demo/src/main/kotlin/net/corda/vega/plugin/customserializers/TenorDateParameterMetadataSerializer.kt b/samples/simm-valuation-demo/src/main/kotlin/net/corda/vega/plugin/customserializers/TenorDateParameterMetadataSerializer.kt index 1fa1ffd1c0..dbcbc2b6fd 100644 --- a/samples/simm-valuation-demo/src/main/kotlin/net/corda/vega/plugin/customserializers/TenorDateParameterMetadataSerializer.kt +++ b/samples/simm-valuation-demo/src/main/kotlin/net/corda/vega/plugin/customserializers/TenorDateParameterMetadataSerializer.kt @@ -5,7 +5,6 @@ import com.opengamma.strata.market.param.TenorDateParameterMetadata import net.corda.core.serialization.* import java.time.LocalDate -@CordaCustomSerializer @Suppress("UNUSED") class TenorDateParameterMetadataSerializer : SerializationCustomSerializer { diff --git a/samples/simm-valuation-demo/src/main/kotlin/net/corda/vega/plugin/customserializers/TenorSerializer.kt b/samples/simm-valuation-demo/src/main/kotlin/net/corda/vega/plugin/customserializers/TenorSerializer.kt index 177a5ee84a..12e2dee1a5 100644 --- a/samples/simm-valuation-demo/src/main/kotlin/net/corda/vega/plugin/customserializers/TenorSerializer.kt +++ b/samples/simm-valuation-demo/src/main/kotlin/net/corda/vega/plugin/customserializers/TenorSerializer.kt @@ -4,7 +4,6 @@ import com.opengamma.strata.basics.date.Tenor import net.corda.core.serialization.* import java.time.Period -@CordaCustomSerializer @Suppress("UNUSED") class TenorSerializer : SerializationCustomSerializer { data class Proxy(val years: Int, val months: Int, val days: Int, val name: String) From db9eb8a63f148c9ce528db294ea14d505e366ff8 Mon Sep 17 00:00:00 2001 From: Shams Asari Date: Fri, 8 Dec 2017 11:46:10 +0000 Subject: [PATCH 29/42] 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 c396842b0e1520dc613ff08041e4a50a580de9b9 Mon Sep 17 00:00:00 2001 From: Katelyn Baker Date: Fri, 8 Dec 2017 11:49:05 +0000 Subject: [PATCH 30/42] CORDA-553 - Review comments --- .../serialization/SerializationCustomSerializer.kt | 4 ++-- .../serialization/amqp/AMQPSerializationScheme.kt | 6 +++--- .../serialization/amqp/CorDappCustomSerializer.kt | 10 ++++------ .../net/corda/node/internal/cordapp/CordappLoader.kt | 9 +++++---- 4 files changed, 14 insertions(+), 15 deletions(-) diff --git a/core/src/main/kotlin/net/corda/core/serialization/SerializationCustomSerializer.kt b/core/src/main/kotlin/net/corda/core/serialization/SerializationCustomSerializer.kt index bf81af2898..2ad2aded38 100644 --- a/core/src/main/kotlin/net/corda/core/serialization/SerializationCustomSerializer.kt +++ b/core/src/main/kotlin/net/corda/core/serialization/SerializationCustomSerializer.kt @@ -6,7 +6,7 @@ package net.corda.core.serialization * a proxy serializer can be written that extends this type whose purpose is to move between those an * unserializable types and an intermediate representation. * - * NOTE: The proxy object must should be specified as a seperate class. However, this can be defined within the + * NOTE: The proxy object should be specified as a seperate class. However, this can be defined within the * scope of the custom serializer. */ interface SerializationCustomSerializer { @@ -21,4 +21,4 @@ interface SerializationCustomSerializer { * unserializable type */ fun fromProxy(proxy: PROXY) : OBJ -} \ No newline at end of file +} diff --git a/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/amqp/AMQPSerializationScheme.kt b/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/amqp/AMQPSerializationScheme.kt index ea4c90ebf5..00a7825d06 100644 --- a/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/amqp/AMQPSerializationScheme.kt +++ b/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/amqp/AMQPSerializationScheme.kt @@ -68,9 +68,9 @@ abstract class AbstractAMQPSerializationScheme(val cordappLoader: List) factory.addToWhitelist(*whitelistProvider.whitelist.toTypedArray()) } - cordappLoader.forEach { ca -> - ca.serializationCustomSerializers.forEach { - factory.registerExternal(CorDappCustomSerializer(it, factory)) + for (loader in cordappLoader) { + for (schema in loader.serializationCustomSerializers) { + factory.registerExternal(CorDappCustomSerializer(schema, factory)) } } } diff --git a/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/amqp/CorDappCustomSerializer.kt b/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/amqp/CorDappCustomSerializer.kt index eaa27db161..72373488b3 100644 --- a/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/amqp/CorDappCustomSerializer.kt +++ b/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/amqp/CorDappCustomSerializer.kt @@ -36,8 +36,8 @@ const val PROXY_TYPE = 1 * automatically * @property type the Java [Type] of the class which this serializes, inferred via reflection of the * [serializer]'s super type - * @property proxyType the Java [Type] of the class into which instances of [type] are proxied for use byt - * the underlying serialisation engine + * @property proxyType the Java [Type] of the class into which instances of [type] are proxied for use by + * the underlying serialization engine * * @param factory a [SerializerFactory] belonging to the context this serializer is being instantiated * for @@ -58,17 +58,15 @@ class CorDappCustomSerializer( override val type = types[CORDAPP_TYPE] val proxyType = types[PROXY_TYPE] - override val typeDescriptor = Symbol.valueOf("$DESCRIPTOR_DOMAIN:${nameForType(type)}") val descriptor: Descriptor = Descriptor(typeDescriptor) - private val proxySerializer: ObjectSerializer by lazy { ObjectSerializer(proxyType, factory) } override fun writeClassInfo(output: SerializationOutput) {} override fun writeObject(obj: Any, data: Data, type: Type, output: SerializationOutput) { val proxy = uncheckedCast, - SerializationCustomSerializer> (serializer).toProxy(obj) + SerializationCustomSerializer>(serializer).toProxy(obj) data.withDescribed(descriptor) { data.withList { @@ -80,7 +78,7 @@ class CorDappCustomSerializer( } override fun readObject(obj: Any, schemas: SerializationSchemas, input: DeserializationInput) = - uncheckedCast, SerializationCustomSerializer> ( + uncheckedCast, SerializationCustomSerializer>( serializer).fromProxy(uncheckedCast(proxySerializer.readObject(obj, schemas, input)))!! override fun isSerializerFor(clazz: Class<*>) = clazz == type diff --git a/node/src/main/kotlin/net/corda/node/internal/cordapp/CordappLoader.kt b/node/src/main/kotlin/net/corda/node/internal/cordapp/CordappLoader.kt index 38f20c951a..50f0140338 100644 --- a/node/src/main/kotlin/net/corda/node/internal/cordapp/CordappLoader.kt +++ b/node/src/main/kotlin/net/corda/node/internal/cordapp/CordappLoader.kt @@ -10,8 +10,10 @@ import net.corda.core.internal.* import net.corda.core.internal.cordapp.CordappImpl import net.corda.core.node.services.CordaService import net.corda.core.schemas.MappedSchema +import net.corda.core.serialization.SerializationCustomSerializer +import net.corda.core.serialization.SerializationWhitelist +import net.corda.core.serialization.SerializeAsToken import net.corda.core.utilities.contextLogger -import net.corda.core.serialization.* import net.corda.node.internal.classloading.requireAnnotation import net.corda.node.services.config.NodeConfiguration import net.corda.nodeapi.internal.serialization.DefaultWhitelist @@ -197,7 +199,7 @@ class CordappLoader private constructor(private val cordappJarPaths: List> { -// return scanResult.getClassesWithAnnotation(SerializationCustomSerializer::class, CordaCustomSerializer::class) + private fun findSerializers(scanResult: RestrictedScanResult): List> { return scanResult.getClassesImplementing(SerializationCustomSerializer::class) } From c66026e54bdfa436bc66a32d7f9de37398acba01 Mon Sep 17 00:00:00 2001 From: Katelyn Baker Date: Fri, 8 Dec 2017 15:50:58 +0000 Subject: [PATCH 31/42] Code Cleanup --- .../serialization/CordaSerializationTransformRename.kt | 6 +++--- .../DeprecatedConstructorForDeserialization.kt | 4 ++-- .../core/serialization/MissingAttachmentsException.kt | 2 +- .../net/corda/core/serialization/SerializationToken.kt | 2 +- .../net/corda/core/transactions/SignedTransaction.kt | 2 +- .../nodeapi/internal/serialization/CordaClassResolver.kt | 9 ++++----- .../nodeapi/internal/serialization/DefaultWhitelist.kt | 4 ++-- .../serialization/amqp/AMQPDescriptorRegistry.kt | 2 +- .../internal/serialization/amqp/ArraySerializer.kt | 2 +- .../internal/serialization/amqp/CustomSerializer.kt | 4 ++-- .../internal/serialization/amqp/EvolutionSerializer.kt | 8 +++++--- .../serialization/carpenter/AMQPSchemaExtensions.kt | 3 ++- .../nodeapi/internal/serialization/carpenter/Schema.kt | 4 ++-- 13 files changed, 27 insertions(+), 25 deletions(-) diff --git a/core/src/main/kotlin/net/corda/core/serialization/CordaSerializationTransformRename.kt b/core/src/main/kotlin/net/corda/core/serialization/CordaSerializationTransformRename.kt index c8ae7e2aec..abd556bb99 100644 --- a/core/src/main/kotlin/net/corda/core/serialization/CordaSerializationTransformRename.kt +++ b/core/src/main/kotlin/net/corda/core/serialization/CordaSerializationTransformRename.kt @@ -18,11 +18,11 @@ annotation class CordaSerializationTransformRenames(vararg val value: CordaSeria // TODO When we have class renaming update the docs /** * This annotation is used to mark a class has having had a property element. It is used by the - * AMQP deserialiser to allow instances with different versions of the class on their Class Path + * AMQP deserializer to allow instances with different versions of the class on their Class Path * to successfully deserialize the object * - * NOTE: Renaming of the class itself is not be done with this annotation. For class renaming - * see ??? + * NOTE: Renaming of the class itself isn't done with this annotation or, at present, supported + * by Corda * * @property to [String] representation of the properties new name * @property from [String] representation of the properties old new diff --git a/core/src/main/kotlin/net/corda/core/serialization/DeprecatedConstructorForDeserialization.kt b/core/src/main/kotlin/net/corda/core/serialization/DeprecatedConstructorForDeserialization.kt index 71d2691e7e..ff76109f03 100644 --- a/core/src/main/kotlin/net/corda/core/serialization/DeprecatedConstructorForDeserialization.kt +++ b/core/src/main/kotlin/net/corda/core/serialization/DeprecatedConstructorForDeserialization.kt @@ -2,10 +2,10 @@ package net.corda.core.serialization /** * This annotation is a marker to indicate which secondary constructors should be considered, and in which - * order, for evolving objects during their deserialisation. + * order, for evolving objects during their deserialization. * * Versions will be considered in descending order, currently duplicate versions will result in - * non deterministic behaviour when deserialising objects + * non deterministic behaviour when deserializing objects */ @Target(AnnotationTarget.CONSTRUCTOR) @Retention(AnnotationRetention.RUNTIME) diff --git a/core/src/main/kotlin/net/corda/core/serialization/MissingAttachmentsException.kt b/core/src/main/kotlin/net/corda/core/serialization/MissingAttachmentsException.kt index a5934be42d..0799e9a270 100644 --- a/core/src/main/kotlin/net/corda/core/serialization/MissingAttachmentsException.kt +++ b/core/src/main/kotlin/net/corda/core/serialization/MissingAttachmentsException.kt @@ -3,6 +3,6 @@ package net.corda.core.serialization import net.corda.core.CordaException import net.corda.core.crypto.SecureHash -/** Thrown during deserialisation to indicate that an attachment needed to construct the [WireTransaction] is not found. */ +/** Thrown during deserialization to indicate that an attachment needed to construct the [WireTransaction] is not found. */ @CordaSerializable class MissingAttachmentsException(val ids: List) : CordaException() \ No newline at end of file diff --git a/core/src/main/kotlin/net/corda/core/serialization/SerializationToken.kt b/core/src/main/kotlin/net/corda/core/serialization/SerializationToken.kt index 8ca6820711..9ec9fb75c0 100644 --- a/core/src/main/kotlin/net/corda/core/serialization/SerializationToken.kt +++ b/core/src/main/kotlin/net/corda/core/serialization/SerializationToken.kt @@ -6,7 +6,7 @@ import net.corda.core.serialization.SingletonSerializationToken.Companion.single /** * The interfaces and classes in this file allow large, singleton style classes to * mark themselves as needing converting to some form of token representation in the serialised form - * and converting back again when deserialising. + * and converting back again when deserializing. * * Typically these classes would be used for node services and subsystems that might become reachable from * Fibers and thus sucked into serialization when they are checkpointed. diff --git a/core/src/main/kotlin/net/corda/core/transactions/SignedTransaction.kt b/core/src/main/kotlin/net/corda/core/transactions/SignedTransaction.kt index c34fd26065..015e745987 100644 --- a/core/src/main/kotlin/net/corda/core/transactions/SignedTransaction.kt +++ b/core/src/main/kotlin/net/corda/core/transactions/SignedTransaction.kt @@ -50,7 +50,7 @@ data class SignedTransaction(val txBits: SerializedBytes, @Volatile @Transient private var cachedTransaction: CoreTransaction? = null - /** Lazily calculated access to the deserialised/hashed transaction data. */ + /** Lazily calculated access to the deserialized/hashed transaction data. */ private val transaction: CoreTransaction get() = cachedTransaction ?: txBits.deserialize().apply { cachedTransaction = this } /** The id of the contained [WireTransaction]. */ diff --git a/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/CordaClassResolver.kt b/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/CordaClassResolver.kt index d4c98fc1e1..c41d3fe3ef 100644 --- a/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/CordaClassResolver.kt +++ b/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/CordaClassResolver.kt @@ -27,10 +27,8 @@ import java.util.* class CordaClassResolver(serializationContext: SerializationContext) : DefaultClassResolver() { val whitelist: ClassWhitelist = TransientClassWhiteList(serializationContext.whitelist) - /* - * These classes are assignment-compatible Java equivalents of Kotlin classes. - * The point is that we do not want to send Kotlin types "over the wire" via RPC. - */ + // These classes are assignment-compatible Java equivalents of Kotlin classes. + // The point is that we do not want to send Kotlin types "over the wire" via RPC. private val javaAliases: Map, Class<*>> = mapOf( listOf().javaClass to Collections.emptyList().javaClass, setOf().javaClass to Collections.emptySet().javaClass, @@ -176,7 +174,8 @@ class GlobalTransientClassWhiteList(delegate: ClassWhitelist) : AbstractMutableC } /** - * A whitelist that can be customised via the [net.corda.core.node.SerializationWhitelist], since it implements [MutableClassWhitelist]. + * A whitelist that can be customised via the [net.corda.core.serialization.SerializationWhitelist], + * since it implements [MutableClassWhitelist]. */ class TransientClassWhiteList(delegate: ClassWhitelist) : AbstractMutableClassWhitelist(Collections.synchronizedSet(mutableSetOf()), delegate) 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..710939f8b2 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 @@ -49,8 +49,8 @@ object DefaultWhitelist : SerializationWhitelist { java.time.YearMonth::class.java, java.time.MonthDay::class.java, java.time.Period::class.java, - java.time.DayOfWeek::class.java, // No custom serialiser but it's an enum. - java.time.Month::class.java, // No custom serialiser but it's an enum. + java.time.DayOfWeek::class.java, // No custom serializer but it's an enum. + java.time.Month::class.java, // No custom serializer but it's an enum. java.util.Collections.emptyMap().javaClass, java.util.Collections.emptySet().javaClass, diff --git a/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/amqp/AMQPDescriptorRegistry.kt b/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/amqp/AMQPDescriptorRegistry.kt index 78ef9dc52a..3907a86fe8 100644 --- a/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/amqp/AMQPDescriptorRegistry.kt +++ b/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/amqp/AMQPDescriptorRegistry.kt @@ -13,7 +13,7 @@ import org.apache.qpid.proton.amqp.UnsignedLong const val DESCRIPTOR_TOP_32BITS: Long = 0xc562L shl(32 + 16) /** - * AMQP desriptor ID's for our custom types. + * AMQP descriptor ID's for our custom types. * * NEVER DELETE OR CHANGE THE ID ASSOCIATED WITH A TYPE * diff --git a/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/amqp/ArraySerializer.kt b/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/amqp/ArraySerializer.kt index 732723a493..06ddb2b264 100644 --- a/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/amqp/ArraySerializer.kt +++ b/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/amqp/ArraySerializer.kt @@ -21,7 +21,7 @@ open class ArraySerializer(override val type: Type, factory: SerializerFactory) // id to generate it properly (it will always return [[[Ljava.lang.type -> type[][][] // for example). // - // We *need* to retain knowledge for AMQP deserialisation weather that lowest primitive + // We *need* to retain knowledge for AMQP deserialization weather that lowest primitive // was boxed or unboxed so just infer it recursively private fun calcTypeName(type: Type): String = if (type.componentType().isArray()) { diff --git a/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/amqp/CustomSerializer.kt b/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/amqp/CustomSerializer.kt index 870bfbaccc..408345fc68 100644 --- a/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/amqp/CustomSerializer.kt +++ b/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/amqp/CustomSerializer.kt @@ -147,8 +147,8 @@ abstract class CustomSerializer : AMQPSerializer { * * @param clazz The type to be marshalled * @param withInheritance Whether subclasses of the class can also be marshalled. - * @param make A lambda for constructing an instance, that defaults to calling a constructor that expects a string. - * @param unmake A lambda that extracts the string value for an instance, that defaults to the [toString] method. + * @param maker A lambda for constructing an instance, that defaults to calling a constructor that expects a string. + * @param unmaker A lambda that extracts the string value for an instance, that defaults to the [toString] method. */ abstract class ToString(clazz: Class, withInheritance: Boolean = false, private val maker: (String) -> T = clazz.getConstructor(String::class.java).let { `constructor` -> diff --git a/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/amqp/EvolutionSerializer.kt b/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/amqp/EvolutionSerializer.kt index 411e405aad..f06980e7fb 100644 --- a/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/amqp/EvolutionSerializer.kt +++ b/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/amqp/EvolutionSerializer.kt @@ -10,7 +10,7 @@ import kotlin.reflect.full.findAnnotation import kotlin.reflect.jvm.javaType /** - * Serializer for deserialising objects whose definition has changed since they + * Serializer for deserializing objects whose definition has changed since they * were serialised */ class EvolutionSerializer( @@ -38,7 +38,7 @@ class EvolutionSerializer( companion object { /** - * Unlike the generic deserialisation case where we need to locate the primary constructor + * Unlike the generic deserialization case where we need to locate the primary constructor * for the object (or our best guess) in the case of an object whose structure has changed * since serialisation we need to attempt to locate a constructor that we can use. I.e. * it's parameters match the serialised members and it will initialise any newly added @@ -47,7 +47,7 @@ class EvolutionSerializer( * TODO: Type evolution * TODO: rename annotation */ - internal fun getEvolverConstructor(type: Type, oldArgs: Map): KFunction? { + private fun getEvolverConstructor(type: Type, oldArgs: Map): KFunction? { val clazz: Class<*> = type.asClass()!! if (!isConcrete(clazz)) return null @@ -77,6 +77,8 @@ class EvolutionSerializer( * as it was serialised and the type descriptor of that type * @param new is the Serializer built for the Class as it exists now, not * how it was serialised and persisted. + * @param factory the [SerializerFactory] associated with the serialization + * context this serializer is being built for */ fun make(old: CompositeType, new: ObjectSerializer, factory: SerializerFactory): AMQPSerializer { diff --git a/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/carpenter/AMQPSchemaExtensions.kt b/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/carpenter/AMQPSchemaExtensions.kt index 0bbdf01033..6a0c3c9784 100644 --- a/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/carpenter/AMQPSchemaExtensions.kt +++ b/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/carpenter/AMQPSchemaExtensions.kt @@ -6,6 +6,7 @@ import net.corda.nodeapi.internal.serialization.amqp.CompositeType import net.corda.nodeapi.internal.serialization.amqp.RestrictedType import net.corda.nodeapi.internal.serialization.amqp.Field as AMQPField import net.corda.nodeapi.internal.serialization.amqp.Schema as AMQPSchema +import net.corda.core.serialization.SerializationContext fun AMQPSchema.carpenterSchema(classloader: ClassLoader): CarpenterMetaSchema { val rtn = CarpenterMetaSchema.newInstance() @@ -34,7 +35,7 @@ fun AMQPField.typeAsString() = if (type == "*") requires[0] else type * b) add the class to the dependency tree in [carpenterSchemas] if it cannot be instantiated * at this time * - * @param classloader the class loader provided dby the [SerializationContext] + * @param classloader the class loader provided by the [SerializationContext] * @param carpenterSchemas structure that holds the dependency tree and list of classes that * need constructing * @param force by default a schema is not added to [carpenterSchemas] if it already exists diff --git a/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/carpenter/Schema.kt b/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/carpenter/Schema.kt index 352ad498da..3660ce4d6d 100644 --- a/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/carpenter/Schema.kt +++ b/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/carpenter/Schema.kt @@ -9,7 +9,7 @@ enum class SchemaFlags { } /** - * A Schema is the representation of an object the Carpenter can contsruct + * A Schema is the representation of an object the Carpenter can construct * * Known Sub Classes * - [ClassSchema] @@ -111,7 +111,7 @@ class EnumSchema( } /** - * Factory object used by the serialiser when building [Schema]s based + * Factory object used by the serializer when building [Schema]s based * on an AMQP schema */ object CarpenterSchemaFactory { From 23292543903a3066086c1ebe917d909a773c2db6 Mon Sep 17 00:00:00 2001 From: Katelyn Baker Date: Fri, 8 Dec 2017 17:00:15 +0000 Subject: [PATCH 32/42] Additional comment corrections --- .../CordaSerializationTransformRename.kt | 2 +- .../internal/serialization/amqp/ArraySerializer.kt | 2 +- .../serialization/amqp/EvolutionSerializer.kt | 14 +++++++------- .../internal/serialization/carpenter/Schema.kt | 8 ++++---- 4 files changed, 13 insertions(+), 13 deletions(-) diff --git a/core/src/main/kotlin/net/corda/core/serialization/CordaSerializationTransformRename.kt b/core/src/main/kotlin/net/corda/core/serialization/CordaSerializationTransformRename.kt index abd556bb99..dc37459899 100644 --- a/core/src/main/kotlin/net/corda/core/serialization/CordaSerializationTransformRename.kt +++ b/core/src/main/kotlin/net/corda/core/serialization/CordaSerializationTransformRename.kt @@ -19,7 +19,7 @@ annotation class CordaSerializationTransformRenames(vararg val value: CordaSeria /** * This annotation is used to mark a class has having had a property element. It is used by the * AMQP deserializer to allow instances with different versions of the class on their Class Path - * to successfully deserialize the object + * to successfully deserialize the object. * * NOTE: Renaming of the class itself isn't done with this annotation or, at present, supported * by Corda diff --git a/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/amqp/ArraySerializer.kt b/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/amqp/ArraySerializer.kt index 06ddb2b264..46046a88f2 100644 --- a/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/amqp/ArraySerializer.kt +++ b/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/amqp/ArraySerializer.kt @@ -22,7 +22,7 @@ open class ArraySerializer(override val type: Type, factory: SerializerFactory) // for example). // // We *need* to retain knowledge for AMQP deserialization weather that lowest primitive - // was boxed or unboxed so just infer it recursively + // was boxed or unboxed so just infer it recursively. private fun calcTypeName(type: Type): String = if (type.componentType().isArray()) { val typeName = calcTypeName(type.componentType()); "$typeName[]" diff --git a/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/amqp/EvolutionSerializer.kt b/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/amqp/EvolutionSerializer.kt index f06980e7fb..797943cf93 100644 --- a/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/amqp/EvolutionSerializer.kt +++ b/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/amqp/EvolutionSerializer.kt @@ -11,7 +11,7 @@ import kotlin.reflect.jvm.javaType /** * Serializer for deserializing objects whose definition has changed since they - * were serialised + * were serialised. */ class EvolutionSerializer( clazz: Type, @@ -40,9 +40,9 @@ class EvolutionSerializer( /** * Unlike the generic deserialization case where we need to locate the primary constructor * for the object (or our best guess) in the case of an object whose structure has changed - * since serialisation we need to attempt to locate a constructor that we can use. I.e. - * it's parameters match the serialised members and it will initialise any newly added - * elements + * since serialisation we need to attempt to locate a constructor that we can use. For example, + * its parameters match the serialised members and it will initialise any newly added + * elements. * * TODO: Type evolution * TODO: rename annotation @@ -70,8 +70,8 @@ class EvolutionSerializer( } /** - * Build a serialization object for deserialisation only of objects serialised - * as different versions of a class + * Build a serialization object for deserialization only of objects serialised + * as different versions of a class. * * @param old is an object holding the schema that represents the object * as it was serialised and the type descriptor of that type @@ -119,7 +119,7 @@ class EvolutionSerializer( * to the object list of values we need to map that list, which is ordered per the * constructor of the original state of the object, we need to map the new parameter order * of the current constructor onto that list inserting nulls where new parameters are - * encountered + * encountered. * * TODO: Object references */ diff --git a/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/carpenter/Schema.kt b/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/carpenter/Schema.kt index 3660ce4d6d..a5de6bf2b0 100644 --- a/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/carpenter/Schema.kt +++ b/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/carpenter/Schema.kt @@ -62,7 +62,7 @@ fun EnumMap.simpleFieldAccess(): Boolean { } /** - * Represents a concrete object + * Represents a concrete object. */ class ClassSchema( name: String, @@ -77,7 +77,7 @@ class ClassSchema( /** * Represents an interface. Carpented interfaces can be used within [ClassSchema]s - * if that class should be implementing that interface + * if that class should be implementing that interface. */ class InterfaceSchema( name: String, @@ -91,7 +91,7 @@ class InterfaceSchema( } /** - * Represents an enumerated type + * Represents an enumerated type. */ class EnumSchema( name: String, @@ -112,7 +112,7 @@ class EnumSchema( /** * Factory object used by the serializer when building [Schema]s based - * on an AMQP schema + * on an AMQP schema. */ object CarpenterSchemaFactory { fun newInstance( From 89d452819f04609f9e9e7d7ff57013babc2625cb Mon Sep 17 00:00:00 2001 From: Katelyn Baker Date: Fri, 8 Dec 2017 14:04:35 +0000 Subject: [PATCH 33/42] Rename Tansform -> Transform in filename --- .../serialization/amqp/{TansformTypes.kt => TransformTypes.kt} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/amqp/{TansformTypes.kt => TransformTypes.kt} (100%) diff --git a/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/amqp/TansformTypes.kt b/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/amqp/TransformTypes.kt similarity index 100% rename from node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/amqp/TansformTypes.kt rename to node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/amqp/TransformTypes.kt From 41bfd7a971e771064704d5e41ce0bc1204715667 Mon Sep 17 00:00:00 2001 From: Shams Asari Date: Fri, 8 Dec 2017 18:07:02 +0000 Subject: [PATCH 34/42] 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 35/42] 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 From 544e19e277b8bb1c89bee938861e89b02cd14bd3 Mon Sep 17 00:00:00 2001 From: Andrzej Cichocki Date: Mon, 11 Dec 2017 11:44:01 +0000 Subject: [PATCH 36/42] CORDA-654 Remove key constants from NodeTestUtils (#2205) * DUMMY_NOTARY was hiding in a couple more places --- .../core/crypto/PartialMerkleTreeTest.kt | 4 +- .../TransactionEncumbranceTests.kt | 22 +++++------ .../tutorial/testdsl/CommercialPaperTest.java | 22 ++++++----- .../docs/tutorial/testdsl/TutorialTestDSL.kt | 23 ++++++------ .../corda/finance/contracts/universal/Cap.kt | 4 +- .../contracts/asset/CashTestsJava.java | 8 +++- .../finance/contracts/CommercialPaperTests.kt | 8 +++- .../finance/contracts/asset/CashTests.kt | 6 ++- .../contracts/asset/ObligationTests.kt | 37 +++++++++++-------- .../node/messaging/TwoPartyTradeFlowTests.kt | 17 ++++----- .../kotlin/net/corda/irs/contract/IRSTests.kt | 12 ++++-- .../kotlin/net/corda/testing/NodeTestUtils.kt | 23 +++++------- .../net/corda/testing/LedgerDSLInterpreter.kt | 23 +++++++----- .../main/kotlin/net/corda/testing/TestDSL.kt | 22 ++++------- .../testing/TransactionDSLInterpreter.kt | 18 +++++---- 15 files changed, 138 insertions(+), 111 deletions(-) diff --git a/core/src/test/kotlin/net/corda/core/crypto/PartialMerkleTreeTest.kt b/core/src/test/kotlin/net/corda/core/crypto/PartialMerkleTreeTest.kt index c0346e30fd..b1d768b2cb 100644 --- a/core/src/test/kotlin/net/corda/core/crypto/PartialMerkleTreeTest.kt +++ b/core/src/test/kotlin/net/corda/core/crypto/PartialMerkleTreeTest.kt @@ -10,6 +10,8 @@ import net.corda.finance.DOLLARS import net.corda.finance.`issued by` import net.corda.finance.contracts.asset.Cash import net.corda.testing.* +import net.corda.testing.node.MockServices +import net.corda.testing.node.makeTestIdentityService import org.junit.Before import org.junit.Rule import org.junit.Test @@ -35,7 +37,7 @@ class PartialMerkleTreeTest { hashed = nodes.map { it.serialize().sha256() } expectedRoot = MerkleTree.getMerkleTree(hashed.toMutableList() + listOf(zeroHash, zeroHash)).hash merkleTree = MerkleTree.getMerkleTree(hashed) - testLedger = ledger { + testLedger = MockServices(makeTestIdentityService(listOf(MEGA_CORP_IDENTITY, MINI_CORP_IDENTITY, DUMMY_CASH_ISSUER_IDENTITY, DUMMY_NOTARY_IDENTITY)), MEGA_CORP.name).ledger(DUMMY_NOTARY) { unverifiedTransaction { attachments(Cash.PROGRAM_ID) output(Cash.PROGRAM_ID, "MEGA_CORP cash", diff --git a/core/src/test/kotlin/net/corda/core/transactions/TransactionEncumbranceTests.kt b/core/src/test/kotlin/net/corda/core/transactions/TransactionEncumbranceTests.kt index 4cb955faa5..630933cdcc 100644 --- a/core/src/test/kotlin/net/corda/core/transactions/TransactionEncumbranceTests.kt +++ b/core/src/test/kotlin/net/corda/core/transactions/TransactionEncumbranceTests.kt @@ -7,10 +7,9 @@ import net.corda.core.identity.AbstractParty import net.corda.finance.DOLLARS import net.corda.finance.`issued by` import net.corda.finance.contracts.asset.Cash -import net.corda.testing.MEGA_CORP -import net.corda.testing.MINI_CORP -import net.corda.testing.SerializationEnvironmentRule -import net.corda.testing.ledger +import net.corda.testing.* +import net.corda.testing.node.MockServices +import net.corda.testing.node.makeTestIdentityService import org.junit.Rule import org.junit.Test import java.time.Instant @@ -50,9 +49,10 @@ class TransactionEncumbranceTests { } } + private val ledgerServices = MockServices(makeTestIdentityService(listOf(MEGA_CORP_IDENTITY, MINI_CORP_IDENTITY, DUMMY_CASH_ISSUER_IDENTITY, DUMMY_NOTARY_IDENTITY)), MEGA_CORP.name) @Test fun `state can be encumbered`() { - ledger { + ledgerServices.ledger(DUMMY_NOTARY) { transaction { attachments(Cash.PROGRAM_ID, TEST_TIMELOCK_ID) input(Cash.PROGRAM_ID, state) @@ -66,7 +66,7 @@ class TransactionEncumbranceTests { @Test fun `state can transition if encumbrance rules are met`() { - ledger { + ledgerServices.ledger(DUMMY_NOTARY) { unverifiedTransaction { attachments(Cash.PROGRAM_ID, TEST_TIMELOCK_ID) output(Cash.PROGRAM_ID, "state encumbered by 5pm time-lock", state) @@ -87,7 +87,7 @@ class TransactionEncumbranceTests { @Test fun `state cannot transition if the encumbrance contract fails to verify`() { - ledger { + ledgerServices.ledger(DUMMY_NOTARY) { unverifiedTransaction { attachments(Cash.PROGRAM_ID, TEST_TIMELOCK_ID) output(Cash.PROGRAM_ID, "state encumbered by 5pm time-lock", state) @@ -108,7 +108,7 @@ class TransactionEncumbranceTests { @Test fun `state must be consumed along with its encumbrance`() { - ledger { + ledgerServices.ledger(DUMMY_NOTARY) { unverifiedTransaction { attachments(Cash.PROGRAM_ID, TEST_TIMELOCK_ID) output(Cash.PROGRAM_ID, "state encumbered by 5pm time-lock", encumbrance = 1, contractState = state) @@ -127,7 +127,7 @@ class TransactionEncumbranceTests { @Test fun `state cannot be encumbered by itself`() { - ledger { + ledgerServices.ledger(DUMMY_NOTARY) { transaction { attachments(Cash.PROGRAM_ID) input(Cash.PROGRAM_ID, state) @@ -140,7 +140,7 @@ class TransactionEncumbranceTests { @Test fun `encumbrance state index must be valid`() { - ledger { + ledgerServices.ledger(DUMMY_NOTARY) { transaction { attachments(Cash.PROGRAM_ID, TEST_TIMELOCK_ID) input(Cash.PROGRAM_ID, state) @@ -154,7 +154,7 @@ class TransactionEncumbranceTests { @Test fun `correct encumbrance state must be provided`() { - ledger { + ledgerServices.ledger(DUMMY_NOTARY) { unverifiedTransaction { attachments(Cash.PROGRAM_ID, TEST_TIMELOCK_ID) output(Cash.PROGRAM_ID, "state encumbered by some other state", encumbrance = 1, contractState = state) diff --git a/docs/source/example-code/src/main/java/net/corda/docs/java/tutorial/testdsl/CommercialPaperTest.java b/docs/source/example-code/src/main/java/net/corda/docs/java/tutorial/testdsl/CommercialPaperTest.java index 3a302d09e0..a064678504 100644 --- a/docs/source/example-code/src/main/java/net/corda/docs/java/tutorial/testdsl/CommercialPaperTest.java +++ b/docs/source/example-code/src/main/java/net/corda/docs/java/tutorial/testdsl/CommercialPaperTest.java @@ -7,10 +7,12 @@ import net.corda.finance.contracts.ICommercialPaperState; import net.corda.finance.contracts.JavaCommercialPaper; import net.corda.finance.contracts.asset.Cash; import net.corda.testing.SerializationEnvironmentRule; +import net.corda.testing.node.MockServices; import org.junit.Rule; import org.junit.Test; import java.time.temporal.ChronoUnit; +import java.util.Arrays; import static net.corda.finance.Currencies.DOLLARS; import static net.corda.finance.Currencies.issuedBy; @@ -19,11 +21,13 @@ import static net.corda.testing.CoreTestUtils.*; import static net.corda.testing.NodeTestUtils.ledger; import static net.corda.testing.NodeTestUtils.transaction; import static net.corda.testing.TestConstants.*; +import static net.corda.testing.node.MockServicesKt.makeTestIdentityService; public class CommercialPaperTest { @Rule public final SerializationEnvironmentRule testSerialization = new SerializationEnvironmentRule(); private final OpaqueBytes defaultRef = new OpaqueBytes(new byte[]{123}); + private final MockServices ledgerServices = new MockServices(makeTestIdentityService(Arrays.asList(getMEGA_CORP_IDENTITY(), getMINI_CORP_IDENTITY(), getDUMMY_CASH_ISSUER_IDENTITY(), getDUMMY_NOTARY_IDENTITY())), getMEGA_CORP().getName()); // DOCSTART 1 private ICommercialPaperState getPaper() { @@ -40,7 +44,7 @@ public class CommercialPaperTest { @Test public void simpleCP() { ICommercialPaperState inState = getPaper(); - ledger(l -> { + ledger(ledgerServices, getDUMMY_NOTARY(), l -> { l.transaction(tx -> { tx.attachments(JCP_PROGRAM_ID); tx.input(JCP_PROGRAM_ID, inState); @@ -55,7 +59,7 @@ public class CommercialPaperTest { @Test public void simpleCPMove() { ICommercialPaperState inState = getPaper(); - ledger(l -> { + ledger(ledgerServices, getDUMMY_NOTARY(), l -> { l.transaction(tx -> { tx.input(JCP_PROGRAM_ID, inState); tx.command(getMEGA_CORP_PUBKEY(), new JavaCommercialPaper.Commands.Move()); @@ -71,7 +75,7 @@ public class CommercialPaperTest { @Test public void simpleCPMoveFails() { ICommercialPaperState inState = getPaper(); - ledger(l -> { + ledger(ledgerServices, getDUMMY_NOTARY(), l -> { l.transaction(tx -> { tx.input(JCP_PROGRAM_ID, inState); tx.command(getMEGA_CORP_PUBKEY(), new JavaCommercialPaper.Commands.Move()); @@ -87,7 +91,7 @@ public class CommercialPaperTest { @Test public void simpleCPMoveSuccess() { ICommercialPaperState inState = getPaper(); - ledger(l -> { + ledger(ledgerServices, getDUMMY_NOTARY(), l -> { l.transaction(tx -> { tx.input(JCP_PROGRAM_ID, inState); tx.command(getMEGA_CORP_PUBKEY(), new JavaCommercialPaper.Commands.Move()); @@ -104,7 +108,7 @@ public class CommercialPaperTest { // DOCSTART 6 @Test public void simpleIssuanceWithTweak() { - ledger(l -> { + ledger(ledgerServices, getDUMMY_NOTARY(), l -> { l.transaction(tx -> { tx.output(JCP_PROGRAM_ID, "paper", getPaper()); // Some CP is issued onto the ledger by MegaCorp. tx.attachments(JCP_PROGRAM_ID); @@ -125,7 +129,7 @@ public class CommercialPaperTest { // DOCSTART 7 @Test public void simpleIssuanceWithTweakTopLevelTx() { - transaction(tx -> { + transaction(ledgerServices, getDUMMY_NOTARY(), tx -> { tx.output(JCP_PROGRAM_ID, "paper", getPaper()); // Some CP is issued onto the ledger by MegaCorp. tx.attachments(JCP_PROGRAM_ID); tx.tweak(tw -> { @@ -144,7 +148,7 @@ public class CommercialPaperTest { @Test public void chainCommercialPaper() { PartyAndReference issuer = getMEGA_CORP().ref(defaultRef); - ledger(l -> { + ledger(ledgerServices, getDUMMY_NOTARY(), l -> { l.unverifiedTransaction(tx -> { tx.output(Cash.PROGRAM_ID, "alice's $900", new Cash.State(issuedBy(DOLLARS(900), issuer), getALICE())); @@ -180,7 +184,7 @@ public class CommercialPaperTest { @Test public void chainCommercialPaperDoubleSpend() { PartyAndReference issuer = getMEGA_CORP().ref(defaultRef); - ledger(l -> { + ledger(ledgerServices, getDUMMY_NOTARY(), l -> { l.unverifiedTransaction(tx -> { tx.output(Cash.PROGRAM_ID, "alice's $900", new Cash.State(issuedBy(DOLLARS(900), issuer), getALICE())); @@ -226,7 +230,7 @@ public class CommercialPaperTest { @Test public void chainCommercialPaperTweak() { PartyAndReference issuer = getMEGA_CORP().ref(defaultRef); - ledger(l -> { + ledger(ledgerServices, getDUMMY_NOTARY(), l -> { l.unverifiedTransaction(tx -> { tx.output(Cash.PROGRAM_ID, "alice's $900", new Cash.State(issuedBy(DOLLARS(900), issuer), getALICE())); diff --git a/docs/source/example-code/src/main/kotlin/net/corda/docs/tutorial/testdsl/TutorialTestDSL.kt b/docs/source/example-code/src/main/kotlin/net/corda/docs/tutorial/testdsl/TutorialTestDSL.kt index cfde0084a1..d1da1b3c56 100644 --- a/docs/source/example-code/src/main/kotlin/net/corda/docs/tutorial/testdsl/TutorialTestDSL.kt +++ b/docs/source/example-code/src/main/kotlin/net/corda/docs/tutorial/testdsl/TutorialTestDSL.kt @@ -9,6 +9,8 @@ import net.corda.finance.contracts.ICommercialPaperState import net.corda.finance.contracts.asset.CASH import net.corda.finance.contracts.asset.Cash import net.corda.testing.* +import net.corda.testing.node.MockServices +import net.corda.testing.node.makeTestIdentityService import org.junit.Rule import org.junit.Test @@ -16,7 +18,7 @@ class CommercialPaperTest { @Rule @JvmField val testSerialization = SerializationEnvironmentRule() - + private val ledgerServices = MockServices(makeTestIdentityService(listOf(MEGA_CORP_IDENTITY, MINI_CORP_IDENTITY, DUMMY_CASH_ISSUER_IDENTITY, DUMMY_NOTARY_IDENTITY)), MEGA_CORP.name) // DOCSTART 1 fun getPaper(): ICommercialPaperState = CommercialPaper.State( issuance = MEGA_CORP.ref(123), @@ -30,7 +32,7 @@ class CommercialPaperTest { @Test fun simpleCP() { val inState = getPaper() - ledger { + ledgerServices.ledger(DUMMY_NOTARY) { transaction { attachments(CP_PROGRAM_ID) input(CP_PROGRAM_ID, inState) @@ -44,7 +46,7 @@ class CommercialPaperTest { @Test fun simpleCPMove() { val inState = getPaper() - ledger { + ledgerServices.ledger(DUMMY_NOTARY) { transaction { input(CP_PROGRAM_ID, inState) command(MEGA_CORP_PUBKEY, CommercialPaper.Commands.Move()) @@ -59,7 +61,7 @@ class CommercialPaperTest { @Test fun simpleCPMoveFails() { val inState = getPaper() - ledger { + ledgerServices.ledger(DUMMY_NOTARY) { transaction { input(CP_PROGRAM_ID, inState) command(MEGA_CORP_PUBKEY, CommercialPaper.Commands.Move()) @@ -74,7 +76,7 @@ class CommercialPaperTest { @Test fun simpleCPMoveSuccess() { val inState = getPaper() - ledger { + ledgerServices.ledger(DUMMY_NOTARY) { transaction { input(CP_PROGRAM_ID, inState) command(MEGA_CORP_PUBKEY, CommercialPaper.Commands.Move()) @@ -90,7 +92,7 @@ class CommercialPaperTest { // DOCSTART 6 @Test fun `simple issuance with tweak`() { - ledger { + ledgerServices.ledger(DUMMY_NOTARY) { transaction { output(CP_PROGRAM_ID, "paper", getPaper()) // Some CP is issued onto the ledger by MegaCorp. attachments(CP_PROGRAM_ID) @@ -111,7 +113,7 @@ class CommercialPaperTest { // DOCSTART 7 @Test fun `simple issuance with tweak and top level transaction`() { - transaction { + ledgerServices.transaction(DUMMY_NOTARY) { output(CP_PROGRAM_ID, "paper", getPaper()) // Some CP is issued onto the ledger by MegaCorp. attachments(CP_PROGRAM_ID) tweak { @@ -131,8 +133,7 @@ class CommercialPaperTest { @Test fun `chain commercial paper`() { val issuer = MEGA_CORP.ref(123) - - ledger { + ledgerServices.ledger(DUMMY_NOTARY) { unverifiedTransaction { attachments(Cash.PROGRAM_ID) output(Cash.PROGRAM_ID, "alice's $900", 900.DOLLARS.CASH issuedBy issuer ownedBy ALICE) @@ -165,7 +166,7 @@ class CommercialPaperTest { @Test fun `chain commercial paper double spend`() { val issuer = MEGA_CORP.ref(123) - ledger { + ledgerServices.ledger(DUMMY_NOTARY) { unverifiedTransaction { attachments(Cash.PROGRAM_ID) output(Cash.PROGRAM_ID, "alice's $900", 900.DOLLARS.CASH issuedBy issuer ownedBy ALICE) @@ -207,7 +208,7 @@ class CommercialPaperTest { @Test fun `chain commercial tweak`() { val issuer = MEGA_CORP.ref(123) - ledger { + ledgerServices.ledger(DUMMY_NOTARY) { unverifiedTransaction { attachments(Cash.PROGRAM_ID) output(Cash.PROGRAM_ID, "alice's $900", 900.DOLLARS.CASH issuedBy issuer ownedBy ALICE) diff --git a/experimental/src/test/kotlin/net/corda/finance/contracts/universal/Cap.kt b/experimental/src/test/kotlin/net/corda/finance/contracts/universal/Cap.kt index 9d683682a6..7ecbfbe906 100644 --- a/experimental/src/test/kotlin/net/corda/finance/contracts/universal/Cap.kt +++ b/experimental/src/test/kotlin/net/corda/finance/contracts/universal/Cap.kt @@ -5,6 +5,8 @@ import net.corda.finance.contracts.FixOf import net.corda.finance.contracts.Frequency import net.corda.finance.contracts.Tenor import net.corda.testing.* +import net.corda.testing.node.MockServices +import net.corda.testing.node.makeTestIdentityService import org.junit.Ignore import org.junit.Rule import org.junit.Test @@ -12,7 +14,7 @@ import java.time.Instant import java.time.LocalDate fun transaction(script: TransactionDSL.() -> EnforceVerifyOrFail) = run { - net.corda.testing.transaction(cordappPackages = listOf("net.corda.finance.contracts.universal"), dsl = script) + MockServices(listOf("net.corda.finance.contracts.universal"), makeTestIdentityService(listOf(MEGA_CORP_IDENTITY, MINI_CORP_IDENTITY, DUMMY_CASH_ISSUER_IDENTITY, DUMMY_NOTARY_IDENTITY)), MEGA_CORP.name).transaction(DUMMY_NOTARY, script) } class Cap { diff --git a/finance/src/test/java/net/corda/finance/contracts/asset/CashTestsJava.java b/finance/src/test/java/net/corda/finance/contracts/asset/CashTestsJava.java index bfff1987d1..4e010df5b3 100644 --- a/finance/src/test/java/net/corda/finance/contracts/asset/CashTestsJava.java +++ b/finance/src/test/java/net/corda/finance/contracts/asset/CashTestsJava.java @@ -5,13 +5,19 @@ import net.corda.core.identity.AnonymousParty; import net.corda.core.utilities.OpaqueBytes; import net.corda.testing.DummyCommandData; import net.corda.testing.SerializationEnvironmentRule; +import net.corda.testing.node.MockServices; import org.junit.Rule; import org.junit.Test; +import java.util.Arrays; + import static net.corda.finance.Currencies.DOLLARS; import static net.corda.finance.Currencies.issuedBy; import static net.corda.testing.CoreTestUtils.*; import static net.corda.testing.NodeTestUtils.transaction; +import static net.corda.testing.TestConstants.getDUMMY_NOTARY; +import static net.corda.testing.TestConstants.getDUMMY_NOTARY_IDENTITY; +import static net.corda.testing.node.MockServicesKt.makeTestIdentityService; /** * This is an incomplete Java replica of CashTests.kt to show how to use the Java test DSL @@ -26,7 +32,7 @@ public class CashTestsJava { @Test public void trivial() { - transaction(tx -> { + transaction(new MockServices(makeTestIdentityService(Arrays.asList(getMEGA_CORP_IDENTITY(), getMINI_CORP_IDENTITY(), getDUMMY_CASH_ISSUER_IDENTITY(), getDUMMY_NOTARY_IDENTITY())), getMEGA_CORP().getName()), getDUMMY_NOTARY(), tx -> { tx.attachment(Cash.PROGRAM_ID); tx.input(Cash.PROGRAM_ID, inState); diff --git a/finance/src/test/kotlin/net/corda/finance/contracts/CommercialPaperTests.kt b/finance/src/test/kotlin/net/corda/finance/contracts/CommercialPaperTests.kt index fc02955c6a..31755d5246 100644 --- a/finance/src/test/kotlin/net/corda/finance/contracts/CommercialPaperTests.kt +++ b/finance/src/test/kotlin/net/corda/finance/contracts/CommercialPaperTests.kt @@ -92,11 +92,11 @@ class CommercialPaperTestsGeneric { @JvmField val testSerialization = SerializationEnvironmentRule() val issuer = MEGA_CORP.ref(123) - + private val ledgerServices = MockServices(makeTestIdentityService(listOf(MEGA_CORP_IDENTITY, MINI_CORP_IDENTITY, DUMMY_CASH_ISSUER_IDENTITY, DUMMY_NOTARY_IDENTITY)), MEGA_CORP.name) @Test fun `trade lifecycle test`() { val someProfits = 1200.DOLLARS `issued by` issuer - ledger { + ledgerServices.ledger(DUMMY_NOTARY) { unverifiedTransaction { attachment(Cash.PROGRAM_ID) output(Cash.PROGRAM_ID, "alice's $900", 900.DOLLARS.CASH issuedBy issuer ownedBy ALICE) @@ -162,6 +162,10 @@ class CommercialPaperTestsGeneric { } } + private fun transaction(script: TransactionDSL.() -> EnforceVerifyOrFail) = run { + ledgerServices.transaction(DUMMY_NOTARY, script) + } + @Test fun `key mismatch at issue`() { transaction { diff --git a/finance/src/test/kotlin/net/corda/finance/contracts/asset/CashTests.kt b/finance/src/test/kotlin/net/corda/finance/contracts/asset/CashTests.kt index bb027e880c..23e223fd4c 100644 --- a/finance/src/test/kotlin/net/corda/finance/contracts/asset/CashTests.kt +++ b/finance/src/test/kotlin/net/corda/finance/contracts/asset/CashTests.kt @@ -113,6 +113,10 @@ class CashTests { database.close() } + private fun transaction(script: TransactionDSL.() -> EnforceVerifyOrFail) = run { + MockServices(makeTestIdentityService(listOf(MEGA_CORP_IDENTITY, MINI_CORP_IDENTITY, DUMMY_CASH_ISSUER_IDENTITY, DUMMY_NOTARY_IDENTITY)), MEGA_CORP.name).transaction(DUMMY_NOTARY, script) + } + @Test fun trivial() { transaction { @@ -779,7 +783,7 @@ class CashTests { val mockService = MockServices(listOf("net.corda.finance.contracts.asset"), rigorousMock().also { doReturn(MEGA_CORP).whenever(it).partyFromKey(MEGA_CORP_PUBKEY) }, MEGA_CORP.name, MEGA_CORP_KEY) - ledger(mockService) { + mockService.ledger(DUMMY_NOTARY) { unverifiedTransaction { attachment(Cash.PROGRAM_ID) output(Cash.PROGRAM_ID, "MEGA_CORP cash", diff --git a/finance/src/test/kotlin/net/corda/finance/contracts/asset/ObligationTests.kt b/finance/src/test/kotlin/net/corda/finance/contracts/asset/ObligationTests.kt index 207955b0dd..dec5493225 100644 --- a/finance/src/test/kotlin/net/corda/finance/contracts/asset/ObligationTests.kt +++ b/finance/src/test/kotlin/net/corda/finance/contracts/asset/ObligationTests.kt @@ -22,6 +22,7 @@ import net.corda.testing.* import net.corda.testing.contracts.DummyContract import net.corda.testing.contracts.DummyState import net.corda.testing.node.MockServices +import net.corda.testing.node.makeTestIdentityService import org.junit.Rule import org.junit.Test import java.time.Instant @@ -61,7 +62,7 @@ class ObligationTests { doReturn(null).whenever(it).partyFromKey(BOB_PUBKEY) doReturn(MEGA_CORP).whenever(it).partyFromKey(MEGA_CORP_PUBKEY) }, MEGA_CORP.name) - + private val ledgerServices get() = MockServices(makeTestIdentityService(listOf(MEGA_CORP_IDENTITY, MINI_CORP_IDENTITY, DUMMY_CASH_ISSUER_IDENTITY, DUMMY_NOTARY_IDENTITY)), MEGA_CORP.name) private fun cashObligationTestRoots( group: LedgerDSL ) = group.apply { @@ -74,6 +75,10 @@ class ObligationTests { } } + private fun transaction(script: TransactionDSL.() -> EnforceVerifyOrFail) = run { + ledgerServices.transaction(DUMMY_NOTARY, script) + } + @Test fun trivial() { transaction { @@ -347,7 +352,7 @@ class ObligationTests { @Test fun `close-out netting`() { // Try netting out two obligations - ledger(mockService) { + mockService.ledger(DUMMY_NOTARY) { cashObligationTestRoots(this) transaction("Issuance") { attachments(Obligation.PROGRAM_ID) @@ -363,7 +368,7 @@ class ObligationTests { // Try netting out two obligations, with the third uninvolved obligation left // as-is - ledger(mockService) { + mockService.ledger(DUMMY_NOTARY) { cashObligationTestRoots(this) transaction("Issuance") { attachments(Obligation.PROGRAM_ID) @@ -379,7 +384,7 @@ class ObligationTests { } // Try having outputs mis-match the inputs - ledger { + ledgerServices.ledger(DUMMY_NOTARY) { cashObligationTestRoots(this) transaction("Issuance") { attachments(Obligation.PROGRAM_ID) @@ -393,7 +398,7 @@ class ObligationTests { } // Have the wrong signature on the transaction - ledger { + ledgerServices.ledger(DUMMY_NOTARY) { cashObligationTestRoots(this) transaction("Issuance") { attachments(Obligation.PROGRAM_ID) @@ -409,7 +414,7 @@ class ObligationTests { @Test fun `payment netting`() { // Try netting out two obligations - ledger(mockService) { + mockService.ledger(DUMMY_NOTARY) { cashObligationTestRoots(this) transaction("Issuance") { attachments(Obligation.PROGRAM_ID) @@ -424,7 +429,7 @@ class ObligationTests { // Try netting out two obligations, but only provide one signature. Unlike close-out netting, we need both // signatures for payment netting - ledger { + ledgerServices.ledger(DUMMY_NOTARY) { cashObligationTestRoots(this) transaction("Issuance") { attachments(Obligation.PROGRAM_ID) @@ -437,7 +442,7 @@ class ObligationTests { } // Multilateral netting, A -> B -> C which can net down to A -> C - ledger(mockService) { + mockService.ledger(DUMMY_NOTARY) { cashObligationTestRoots(this) transaction("Issuance") { attachments(Obligation.PROGRAM_ID) @@ -452,7 +457,7 @@ class ObligationTests { } // Multilateral netting without the key of the receiving party - ledger(mockService) { + mockService.ledger(DUMMY_NOTARY) { cashObligationTestRoots(this) transaction("Issuance") { attachments(Obligation.PROGRAM_ID) @@ -469,7 +474,7 @@ class ObligationTests { @Test fun `cash settlement`() { // Try settling an obligation - ledger { + ledgerServices.ledger(DUMMY_NOTARY) { cashObligationTestRoots(this) transaction("Settlement") { attachments(Obligation.PROGRAM_ID) @@ -485,7 +490,7 @@ class ObligationTests { // Try partial settling of an obligation val halfAMillionDollars = 500000.DOLLARS `issued by` defaultIssuer - ledger { + ledgerServices.ledger(DUMMY_NOTARY) { transaction("Settlement") { attachments(Obligation.PROGRAM_ID, Cash.PROGRAM_ID) input(Obligation.PROGRAM_ID, oneMillionDollars.OBLIGATION between Pair(ALICE, BOB)) @@ -501,7 +506,7 @@ class ObligationTests { // Make sure we can't settle an obligation that's defaulted val defaultedObligation: Obligation.State = (oneMillionDollars.OBLIGATION between Pair(ALICE, BOB)).copy(lifecycle = Lifecycle.DEFAULTED) - ledger { + ledgerServices.ledger(DUMMY_NOTARY) { transaction("Settlement") { attachments(Obligation.PROGRAM_ID, Cash.PROGRAM_ID) input(Obligation.PROGRAM_ID, defaultedObligation) // Alice's defaulted $1,000,000 obligation to Bob @@ -514,7 +519,7 @@ class ObligationTests { } // Make sure settlement amount must match the amount leaving the ledger - ledger { + ledgerServices.ledger(DUMMY_NOTARY) { cashObligationTestRoots(this) transaction("Settlement") { attachments(Obligation.PROGRAM_ID) @@ -538,7 +543,7 @@ class ObligationTests { val oneUnitFcojObligation = Obligation.State(Obligation.Lifecycle.NORMAL, ALICE, obligationDef, oneUnitFcoj.quantity, NULL_PARTY) // Try settling a simple commodity obligation - ledger { + ledgerServices.ledger(DUMMY_NOTARY) { unverifiedTransaction { attachments(Obligation.PROGRAM_ID) output(Obligation.PROGRAM_ID, "Alice's 1 FCOJ obligation to Bob", oneUnitFcojObligation between Pair(ALICE, BOB)) @@ -560,7 +565,7 @@ class ObligationTests { @Test fun `payment default`() { // Try defaulting an obligation without a time-window. - ledger { + ledgerServices.ledger(DUMMY_NOTARY) { cashObligationTestRoots(this) transaction("Settlement") { attachments(Obligation.PROGRAM_ID) @@ -584,7 +589,7 @@ class ObligationTests { } // Try defaulting an obligation that is now in the past - ledger { + ledgerServices.ledger(DUMMY_NOTARY) { transaction { attachments(Obligation.PROGRAM_ID) input(Obligation.PROGRAM_ID, oneMillionDollars.OBLIGATION between Pair(ALICE, BOB) `at` pastTestTime) diff --git a/node/src/test/kotlin/net/corda/node/messaging/TwoPartyTradeFlowTests.kt b/node/src/test/kotlin/net/corda/node/messaging/TwoPartyTradeFlowTests.kt index 0daa972cc2..b311425257 100644 --- a/node/src/test/kotlin/net/corda/node/messaging/TwoPartyTradeFlowTests.kt +++ b/node/src/test/kotlin/net/corda/node/messaging/TwoPartyTradeFlowTests.kt @@ -94,7 +94,7 @@ class TwoPartyTradeFlowTests(private val anonymous: Boolean) { // allow interruption half way through. mockNet = MockNetwork(threadPerNode = true, cordappPackages = cordappPackages) val ledgerIdentityService = rigorousMock() - ledger(MockServices(cordappPackages, ledgerIdentityService, MEGA_CORP.name)) { + MockServices(cordappPackages, ledgerIdentityService, MEGA_CORP.name).ledger(DUMMY_NOTARY) { val notaryNode = mockNet.defaultNotaryNode val aliceNode = mockNet.createPartyNode(ALICE_NAME) val bobNode = mockNet.createPartyNode(BOB_NAME) @@ -146,7 +146,7 @@ class TwoPartyTradeFlowTests(private val anonymous: Boolean) { fun `trade cash for commercial paper fails using soft locking`() { mockNet = MockNetwork(threadPerNode = true, cordappPackages = cordappPackages) val ledgerIdentityService = rigorousMock() - ledger(MockServices(cordappPackages, ledgerIdentityService, MEGA_CORP.name)) { + MockServices(cordappPackages, ledgerIdentityService, MEGA_CORP.name).ledger(DUMMY_NOTARY) { val notaryNode = mockNet.defaultNotaryNode val aliceNode = mockNet.createPartyNode(ALICE_NAME) val bobNode = mockNet.createPartyNode(BOB_NAME) @@ -204,7 +204,7 @@ class TwoPartyTradeFlowTests(private val anonymous: Boolean) { fun `shutdown and restore`() { mockNet = MockNetwork(cordappPackages = cordappPackages) val ledgerIdentityService = rigorousMock() - ledger(MockServices(cordappPackages, ledgerIdentityService, MEGA_CORP.name)) { + MockServices(cordappPackages, ledgerIdentityService, MEGA_CORP.name).ledger(DUMMY_NOTARY) { val notaryNode = mockNet.defaultNotaryNode val aliceNode = mockNet.createPartyNode(ALICE_NAME) var bobNode = mockNet.createPartyNode(BOB_NAME) @@ -325,9 +325,7 @@ class TwoPartyTradeFlowTests(private val anonymous: Boolean) { val bob = bobNode.info.singleIdentity() val bank = bankNode.info.singleIdentity() val issuer = bank.ref(1, 2, 3) - - ledger(aliceNode.services) { - + aliceNode.services.ledger(DUMMY_NOTARY) { // Insert a prospectus type attachment into the commercial paper transaction. val stream = ByteArrayOutputStream() JarOutputStream(stream).use { @@ -431,8 +429,7 @@ class TwoPartyTradeFlowTests(private val anonymous: Boolean) { val bank: Party = bankNode.info.singleIdentity() val bob = bobNode.info.singleIdentity() val issuer = bank.ref(1, 2, 3) - - ledger(aliceNode.services) { + aliceNode.services.ledger(DUMMY_NOTARY) { // Insert a prospectus type attachment into the commercial paper transaction. val stream = ByteArrayOutputStream() JarOutputStream(stream).use { @@ -501,7 +498,7 @@ class TwoPartyTradeFlowTests(private val anonymous: Boolean) { fun `dependency with error on buyer side`() { mockNet = MockNetwork(cordappPackages = cordappPackages) val ledgerIdentityService = rigorousMock() - ledger(MockServices(cordappPackages, ledgerIdentityService, MEGA_CORP.name)) { + MockServices(cordappPackages, ledgerIdentityService, MEGA_CORP.name).ledger(DUMMY_NOTARY) { runWithError(ledgerIdentityService, true, false, "at least one cash input") } } @@ -510,7 +507,7 @@ class TwoPartyTradeFlowTests(private val anonymous: Boolean) { fun `dependency with error on seller side`() { mockNet = MockNetwork(cordappPackages = cordappPackages) val ledgerIdentityService = rigorousMock() - ledger(MockServices(cordappPackages, ledgerIdentityService, MEGA_CORP.name)) { + MockServices(cordappPackages, ledgerIdentityService, MEGA_CORP.name).ledger(DUMMY_NOTARY) { runWithError(ledgerIdentityService, false, true, "Issuances have a time-window") } } diff --git a/samples/irs-demo/cordapp/src/test/kotlin/net/corda/irs/contract/IRSTests.kt b/samples/irs-demo/cordapp/src/test/kotlin/net/corda/irs/contract/IRSTests.kt index 9dd3180bd8..b512beba0d 100644 --- a/samples/irs-demo/cordapp/src/test/kotlin/net/corda/irs/contract/IRSTests.kt +++ b/samples/irs-demo/cordapp/src/test/kotlin/net/corda/irs/contract/IRSTests.kt @@ -26,6 +26,7 @@ import net.corda.finance.contracts.Tenor import net.corda.node.services.api.IdentityServiceInternal import net.corda.testing.* import net.corda.testing.node.MockServices +import net.corda.testing.node.makeTestIdentityService import org.junit.Rule import org.junit.Test import java.math.BigDecimal @@ -222,6 +223,7 @@ class IRSTests { private val megaCorpServices = MockServices(listOf("net.corda.irs.contract"), rigorousMock(), MEGA_CORP.name, MEGA_CORP_KEY) private val miniCorpServices = MockServices(listOf("net.corda.irs.contract"), rigorousMock(), MINI_CORP.name, MINI_CORP_KEY) private val notaryServices = MockServices(listOf("net.corda.irs.contract"), rigorousMock(), DUMMY_NOTARY.name, DUMMY_NOTARY_KEY) + private val ledgerServices get() = MockServices(makeTestIdentityService(listOf(MEGA_CORP_IDENTITY, MINI_CORP_IDENTITY, DUMMY_CASH_ISSUER_IDENTITY, DUMMY_NOTARY_IDENTITY)), MEGA_CORP.name) @Test fun ok() { trade().verifies() @@ -391,8 +393,7 @@ class IRSTests { val ld = LocalDate.of(2016, 3, 8) val bd = BigDecimal("0.0063518") - - return ledger { + return ledgerServices.ledger(DUMMY_NOTARY) { transaction("Agreement") { attachments(IRS_PROGRAM_ID) output(IRS_PROGRAM_ID, "irs post agreement", singleIRS()) @@ -419,6 +420,10 @@ class IRSTests { } } + private fun transaction(script: TransactionDSL.() -> EnforceVerifyOrFail) = run { + ledgerServices.transaction(DUMMY_NOTARY, script) + } + @Test fun `ensure failure occurs when there are inbound states for an agreement command`() { val irs = singleIRS() @@ -656,8 +661,7 @@ class IRSTests { val bd1 = BigDecimal("0.0063518") val irs = singleIRS() - - return ledger { + return ledgerServices.ledger(DUMMY_NOTARY) { transaction("Agreement") { attachments(IRS_PROGRAM_ID) output(IRS_PROGRAM_ID, "irs post agreement1", 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 85fa0eef1f..b2544b3245 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 @@ -10,6 +10,7 @@ import net.corda.core.context.InvocationContext import net.corda.core.context.Origin import net.corda.core.flows.FlowLogic import net.corda.core.identity.CordaX500Name +import net.corda.core.identity.Party import net.corda.core.internal.FlowStateMachine import net.corda.core.node.ServiceHub import net.corda.core.transactions.TransactionBuilder @@ -21,21 +22,17 @@ import net.corda.nodeapi.internal.persistence.DatabaseConfig import net.corda.node.services.config.NodeConfiguration import net.corda.node.services.config.VerifierType import net.corda.nodeapi.internal.config.User -import net.corda.testing.node.MockServices import net.corda.testing.node.MockServices.Companion.makeTestDataSourceProperties -import net.corda.testing.node.makeTestIdentityService import java.nio.file.Path /** - * Creates and tests a ledger built by the passed in dsl. The provided services can be customised, otherwise a default - * of a freshly built [MockServices] is used. + * Creates and tests a ledger built by the passed in dsl. */ -@JvmOverloads -fun ledger( - services: ServiceHub = MockServices(makeTestIdentityService(listOf(MEGA_CORP_IDENTITY, MINI_CORP_IDENTITY, DUMMY_CASH_ISSUER_IDENTITY, DUMMY_NOTARY_IDENTITY)), MEGA_CORP.name), +fun ServiceHub.ledger( + notary: Party, dsl: LedgerDSL.() -> Unit ): LedgerDSL { - return LedgerDSL(TestLedgerDSLInterpreter(services)).also { dsl(it) } + return LedgerDSL(TestLedgerDSLInterpreter(this), notary).apply(dsl) } /** @@ -43,13 +40,11 @@ fun ledger( * * @see LedgerDSLInterpreter._transaction */ -@JvmOverloads -fun transaction( - transactionBuilder: TransactionBuilder = TransactionBuilder(notary = DUMMY_NOTARY), - cordappPackages: List = emptyList(), +fun ServiceHub.transaction( + notary: Party, dsl: TransactionDSL.() -> EnforceVerifyOrFail -) = ledger(services = MockServices(cordappPackages, makeTestIdentityService(listOf(MEGA_CORP_IDENTITY, MINI_CORP_IDENTITY, DUMMY_CASH_ISSUER_IDENTITY, DUMMY_NOTARY_IDENTITY)), MEGA_CORP.name)) { - dsl(TransactionDSL(TestTransactionDSLInterpreter(this.interpreter, transactionBuilder))) +) = ledger(notary) { + dsl(TransactionDSL(TestTransactionDSLInterpreter(interpreter, TransactionBuilder(notary)), notary)) } fun testNodeConfiguration( diff --git a/testing/test-utils/src/main/kotlin/net/corda/testing/LedgerDSLInterpreter.kt b/testing/test-utils/src/main/kotlin/net/corda/testing/LedgerDSLInterpreter.kt index 9f4f829433..47d3f68b62 100644 --- a/testing/test-utils/src/main/kotlin/net/corda/testing/LedgerDSLInterpreter.kt +++ b/testing/test-utils/src/main/kotlin/net/corda/testing/LedgerDSLInterpreter.kt @@ -4,6 +4,8 @@ import net.corda.core.contracts.ContractState import net.corda.core.contracts.StateAndRef import net.corda.core.contracts.TransactionState import net.corda.core.crypto.SecureHash +import net.corda.core.identity.Party +import net.corda.core.internal.uncheckedCast import net.corda.core.transactions.TransactionBuilder import net.corda.core.transactions.WireTransaction import java.io.InputStream @@ -14,7 +16,7 @@ import java.io.InputStream */ interface OutputStateLookup { /** - * Retrieves an output previously defined by [TransactionDSLInterpreter._output] with a label passed in. + * Retrieves an output previously defined by [TransactionDSLInterpreter.output] with a label passed in. * @param clazz The class object holding the type of the output state expected. * @param label The label of the to-be-retrieved output state. * @return The output [StateAndRef]. @@ -90,7 +92,7 @@ interface LedgerDSLInterpreter : Verifies, Ou * @return The final [WireTransaction] of the built transaction. */ fun _transaction(transactionLabel: String?, transactionBuilder: TransactionBuilder, - dsl: TransactionDSL.() -> EnforceVerifyOrFail): WireTransaction + dsl: T.() -> EnforceVerifyOrFail): WireTransaction /** * Creates and adds a transaction to the ledger that will not be verified by [verifies]. @@ -100,13 +102,13 @@ interface LedgerDSLInterpreter : Verifies, Ou * @return The final [WireTransaction] of the built transaction. */ fun _unverifiedTransaction(transactionLabel: String?, transactionBuilder: TransactionBuilder, - dsl: TransactionDSL.() -> Unit): WireTransaction + dsl: T.() -> Unit): WireTransaction /** * Creates a local scoped copy of the ledger. * @param dsl The ledger DSL to be interpreted using the copy. */ - fun tweak(dsl: LedgerDSL>.() -> Unit) + fun _tweak(dsl: LedgerDSLInterpreter.() -> Unit) /** * Adds an attachment to the ledger. @@ -123,24 +125,27 @@ interface LedgerDSLInterpreter : Verifies, Ou * functionality then first add your primitive to [LedgerDSLInterpreter] and then add the convenience defaults/extension * methods here. */ -class LedgerDSL>(val interpreter: L) : +class LedgerDSL>(val interpreter: L, private val notary: Party) : LedgerDSLInterpreter by interpreter { /** * Creates and adds a transaction to the ledger. */ @JvmOverloads - fun transaction(label: String? = null, transactionBuilder: TransactionBuilder = TransactionBuilder(notary = DUMMY_NOTARY), + fun transaction(label: String? = null, transactionBuilder: TransactionBuilder = TransactionBuilder(notary = notary), dsl: TransactionDSL.() -> EnforceVerifyOrFail) = - _transaction(label, transactionBuilder, dsl) + _transaction(label, transactionBuilder) { TransactionDSL(this, notary).dsl() } /** * Creates and adds a transaction to the ledger that will not be verified by [verifies]. */ @JvmOverloads - fun unverifiedTransaction(label: String? = null, transactionBuilder: TransactionBuilder = TransactionBuilder(notary = DUMMY_NOTARY), + fun unverifiedTransaction(label: String? = null, transactionBuilder: TransactionBuilder = TransactionBuilder(notary = notary), dsl: TransactionDSL.() -> Unit) = - _unverifiedTransaction(label, transactionBuilder, dsl) + _unverifiedTransaction(label, transactionBuilder) { TransactionDSL(this, notary).dsl() } + + /** Creates a local scoped copy of the ledger. */ + fun tweak(dsl: LedgerDSL.() -> Unit) = _tweak { LedgerDSL(uncheckedCast(this), notary).dsl() } /** * Retrieves an output previously defined by [TransactionDSLInterpreter._output] with a label passed in. diff --git a/testing/test-utils/src/main/kotlin/net/corda/testing/TestDSL.kt b/testing/test-utils/src/main/kotlin/net/corda/testing/TestDSL.kt index 3167eb7a38..cf32668002 100644 --- a/testing/test-utils/src/main/kotlin/net/corda/testing/TestDSL.kt +++ b/testing/test-utils/src/main/kotlin/net/corda/testing/TestDSL.kt @@ -133,9 +133,7 @@ data class TestTransactionDSLInterpreter private constructor( transactionBuilder.setTimeWindow(data) } - override fun tweak( - dsl: TransactionDSL.() -> EnforceVerifyOrFail - ) = dsl(TransactionDSL(copy())) + override fun _tweak(dsl: TransactionDSLInterpreter.() -> EnforceVerifyOrFail) = copy().dsl() override fun _attachment(contractClassName: ContractClassName) { (services.cordappProvider as MockCordappProvider).addMockCordapp(contractClassName, services.attachments as MockAttachmentStorage) @@ -205,11 +203,9 @@ data class TestLedgerDSLInterpreter private constructor( private fun interpretTransactionDsl( transactionBuilder: TransactionBuilder, - dsl: TransactionDSL.() -> R + dsl: TestTransactionDSLInterpreter.() -> R ): TestTransactionDSLInterpreter { - val transactionInterpreter = TestTransactionDSLInterpreter(this, transactionBuilder) - dsl(TransactionDSL(transactionInterpreter)) - return transactionInterpreter + return TestTransactionDSLInterpreter(this, transactionBuilder).apply { dsl() } } fun transactionName(transactionHash: SecureHash): String? { @@ -227,7 +223,7 @@ data class TestLedgerDSLInterpreter private constructor( private fun recordTransactionWithTransactionMap( transactionLabel: String?, transactionBuilder: TransactionBuilder, - dsl: TransactionDSL.() -> R, + dsl: TestTransactionDSLInterpreter.() -> R, transactionMap: HashMap = HashMap(), /** If set to true, will add dummy components to [transactionBuilder] to make it valid. */ fillTransaction: Boolean = false @@ -267,19 +263,17 @@ data class TestLedgerDSLInterpreter private constructor( override fun _transaction( transactionLabel: String?, transactionBuilder: TransactionBuilder, - dsl: TransactionDSL.() -> EnforceVerifyOrFail + dsl: TestTransactionDSLInterpreter.() -> EnforceVerifyOrFail ) = recordTransactionWithTransactionMap(transactionLabel, transactionBuilder, dsl, transactionWithLocations) override fun _unverifiedTransaction( transactionLabel: String?, transactionBuilder: TransactionBuilder, - dsl: TransactionDSL.() -> Unit + dsl: TestTransactionDSLInterpreter.() -> Unit ) = recordTransactionWithTransactionMap(transactionLabel, transactionBuilder, dsl, nonVerifiedTransactionWithLocations, fillTransaction = true) - override fun tweak( - dsl: LedgerDSL>.() -> Unit) = - dsl(LedgerDSL(copy())) + override fun _tweak(dsl: LedgerDSLInterpreter.() -> Unit) = + copy().dsl() override fun attachment(attachment: InputStream): SecureHash { return services.attachments.importAttachment(attachment) diff --git a/testing/test-utils/src/main/kotlin/net/corda/testing/TransactionDSLInterpreter.kt b/testing/test-utils/src/main/kotlin/net/corda/testing/TransactionDSLInterpreter.kt index 356323f613..8547a1f9a6 100644 --- a/testing/test-utils/src/main/kotlin/net/corda/testing/TransactionDSLInterpreter.kt +++ b/testing/test-utils/src/main/kotlin/net/corda/testing/TransactionDSLInterpreter.kt @@ -65,7 +65,7 @@ interface TransactionDSLInterpreter : Verifies, OutputStateLookup { * Creates a local scoped copy of the transaction. * @param dsl The transaction DSL to be interpreted using the copy. */ - fun tweak(dsl: TransactionDSL.() -> EnforceVerifyOrFail): EnforceVerifyOrFail + fun _tweak(dsl: TransactionDSLInterpreter.() -> EnforceVerifyOrFail): EnforceVerifyOrFail /** * Attaches an attachment containing the named contract to the transaction @@ -74,7 +74,7 @@ interface TransactionDSLInterpreter : Verifies, OutputStateLookup { fun _attachment(contractClassName: ContractClassName) } -class TransactionDSL(interpreter: T) : TransactionDSLInterpreter by interpreter { +class TransactionDSL(interpreter: T, private val notary: Party) : TransactionDSLInterpreter by interpreter { /** * Looks up the output label and adds the found state as an input. * @param stateLabel The label of the output state specified when calling [TransactionDSLInterpreter.output] and friends. @@ -87,7 +87,7 @@ class TransactionDSL(interpreter: T) : Transa * @param state The state to be added. */ fun input(contractClassName: ContractClassName, state: ContractState) { - val transaction = ledgerInterpreter._unverifiedTransaction(null, TransactionBuilder(notary = DUMMY_NOTARY)) { + val transaction = ledgerInterpreter._unverifiedTransaction(null, TransactionBuilder(notary)) { output(contractClassName, null, DUMMY_NOTARY, null, AlwaysAcceptAttachmentConstraint, state) } input(transaction.outRef(0).ref) @@ -103,13 +103,13 @@ class TransactionDSL(interpreter: T) : Transa * Adds a labelled output to the transaction. */ fun output(contractClassName: ContractClassName, label: String, encumbrance: Int, contractState: ContractState) = - output(contractClassName, label, DUMMY_NOTARY, encumbrance, AutomaticHashConstraint, contractState) + output(contractClassName, label, notary, encumbrance, AutomaticHashConstraint, contractState) /** * Adds a labelled output to the transaction. */ fun output(contractClassName: ContractClassName, label: String, contractState: ContractState) = - output(contractClassName, label, DUMMY_NOTARY, null, AutomaticHashConstraint, contractState) + output(contractClassName, label, notary, null, AutomaticHashConstraint, contractState) /** * Adds an output to the transaction. @@ -121,13 +121,13 @@ class TransactionDSL(interpreter: T) : Transa * Adds an output to the transaction. */ fun output(contractClassName: ContractClassName, encumbrance: Int, contractState: ContractState) = - output(contractClassName, null, DUMMY_NOTARY, encumbrance, AutomaticHashConstraint, contractState) + output(contractClassName, null, notary, encumbrance, AutomaticHashConstraint, contractState) /** * Adds an output to the transaction. */ fun output(contractClassName: ContractClassName, contractState: ContractState) = - output(contractClassName, null, DUMMY_NOTARY, null, AutomaticHashConstraint, contractState) + output(contractClassName, null, notary, null, AutomaticHashConstraint, contractState) /** * Adds a command to the transaction. @@ -143,6 +143,10 @@ class TransactionDSL(interpreter: T) : Transa fun timeWindow(time: Instant, tolerance: Duration = 30.seconds) = timeWindow(TimeWindow.withTolerance(time, tolerance)) + /** Creates a local scoped copy of the transaction. */ + fun tweak(dsl: TransactionDSL.() -> EnforceVerifyOrFail) = + _tweak { TransactionDSL(this, notary).dsl() } + /** * @see TransactionDSLInterpreter._attachment */ From d8c7f0ae234155d36687c5d5adc045391730e07e Mon Sep 17 00:00:00 2001 From: Andrzej Cichocki Date: Mon, 11 Dec 2017 16:17:20 +0000 Subject: [PATCH 37/42] Inline DUMMY_CASH/OBLIGATION_ISSUER. (#2214) --- .../core/crypto/PartialMerkleTreeTest.kt | 8 ++- .../TransactionEncumbranceTests.kt | 9 ++- .../core/transactions/TransactionTests.kt | 3 +- .../tutorial/testdsl/CommercialPaperTest.java | 14 ++++- .../docs/tutorial/testdsl/TutorialTestDSL.kt | 11 +++- .../corda/finance/contracts/universal/Cap.kt | 13 ++++- .../net/corda/finance/contracts/asset/Cash.kt | 12 +--- .../finance/contracts/asset/Obligation.kt | 8 --- .../contracts/asset/CashTestsJava.java | 11 ++-- .../finance/contracts/CommercialPaperTests.kt | 17 +++++- .../finance/contracts/asset/CashTests.kt | 20 +++++-- .../contracts/asset/ObligationTests.kt | 18 ++++-- .../services/vault/VaultQueryJavaTests.java | 55 +++++++++++-------- .../persistence/HibernateConfigurationTest.kt | 13 ++++- .../services/vault/NodeVaultServiceTest.kt | 23 ++++---- .../node/services/vault/VaultQueryTests.kt | 10 +++- .../node/services/vault/VaultWithCashTest.kt | 19 ++++--- .../kotlin/net/corda/irs/contract/IRSTests.kt | 8 ++- .../kotlin/net/corda/testing/CoreTestUtils.kt | 6 -- .../net/corda/loadtest/tests/NotaryTest.kt | 9 ++- 20 files changed, 180 insertions(+), 107 deletions(-) diff --git a/core/src/test/kotlin/net/corda/core/crypto/PartialMerkleTreeTest.kt b/core/src/test/kotlin/net/corda/core/crypto/PartialMerkleTreeTest.kt index b1d768b2cb..be02cc4599 100644 --- a/core/src/test/kotlin/net/corda/core/crypto/PartialMerkleTreeTest.kt +++ b/core/src/test/kotlin/net/corda/core/crypto/PartialMerkleTreeTest.kt @@ -1,5 +1,7 @@ package net.corda.core.crypto +import com.nhaarman.mockito_kotlin.doReturn +import com.nhaarman.mockito_kotlin.whenever import net.corda.core.contracts.* import net.corda.core.crypto.SecureHash.Companion.zeroHash import net.corda.core.identity.Party @@ -9,9 +11,9 @@ import net.corda.core.transactions.WireTransaction import net.corda.finance.DOLLARS import net.corda.finance.`issued by` import net.corda.finance.contracts.asset.Cash +import net.corda.node.services.api.IdentityServiceInternal import net.corda.testing.* import net.corda.testing.node.MockServices -import net.corda.testing.node.makeTestIdentityService import org.junit.Before import org.junit.Rule import org.junit.Test @@ -37,7 +39,9 @@ class PartialMerkleTreeTest { hashed = nodes.map { it.serialize().sha256() } expectedRoot = MerkleTree.getMerkleTree(hashed.toMutableList() + listOf(zeroHash, zeroHash)).hash merkleTree = MerkleTree.getMerkleTree(hashed) - testLedger = MockServices(makeTestIdentityService(listOf(MEGA_CORP_IDENTITY, MINI_CORP_IDENTITY, DUMMY_CASH_ISSUER_IDENTITY, DUMMY_NOTARY_IDENTITY)), MEGA_CORP.name).ledger(DUMMY_NOTARY) { + testLedger = MockServices(rigorousMock().also { + doReturn(MEGA_CORP).whenever(it).partyFromKey(MEGA_CORP_PUBKEY) + }, MEGA_CORP.name).ledger(DUMMY_NOTARY) { unverifiedTransaction { attachments(Cash.PROGRAM_ID) output(Cash.PROGRAM_ID, "MEGA_CORP cash", diff --git a/core/src/test/kotlin/net/corda/core/transactions/TransactionEncumbranceTests.kt b/core/src/test/kotlin/net/corda/core/transactions/TransactionEncumbranceTests.kt index 630933cdcc..b4c824026d 100644 --- a/core/src/test/kotlin/net/corda/core/transactions/TransactionEncumbranceTests.kt +++ b/core/src/test/kotlin/net/corda/core/transactions/TransactionEncumbranceTests.kt @@ -1,5 +1,7 @@ package net.corda.core.transactions +import com.nhaarman.mockito_kotlin.doReturn +import com.nhaarman.mockito_kotlin.whenever import net.corda.core.contracts.Contract import net.corda.core.contracts.ContractState import net.corda.core.contracts.requireThat @@ -7,9 +9,9 @@ import net.corda.core.identity.AbstractParty import net.corda.finance.DOLLARS import net.corda.finance.`issued by` import net.corda.finance.contracts.asset.Cash +import net.corda.node.services.api.IdentityServiceInternal import net.corda.testing.* import net.corda.testing.node.MockServices -import net.corda.testing.node.makeTestIdentityService import org.junit.Rule import org.junit.Test import java.time.Instant @@ -49,7 +51,10 @@ class TransactionEncumbranceTests { } } - private val ledgerServices = MockServices(makeTestIdentityService(listOf(MEGA_CORP_IDENTITY, MINI_CORP_IDENTITY, DUMMY_CASH_ISSUER_IDENTITY, DUMMY_NOTARY_IDENTITY)), MEGA_CORP.name) + private val ledgerServices = MockServices(rigorousMock().also { + doReturn(MEGA_CORP).whenever(it).partyFromKey(MEGA_CORP_PUBKEY) + }, MEGA_CORP.name) + @Test fun `state can be encumbered`() { ledgerServices.ledger(DUMMY_NOTARY) { diff --git a/core/src/test/kotlin/net/corda/core/transactions/TransactionTests.kt b/core/src/test/kotlin/net/corda/core/transactions/TransactionTests.kt index c5a4976876..fadd968dd9 100644 --- a/core/src/test/kotlin/net/corda/core/transactions/TransactionTests.kt +++ b/core/src/test/kotlin/net/corda/core/transactions/TransactionTests.kt @@ -4,12 +4,12 @@ import net.corda.core.contracts.* import net.corda.core.crypto.* import net.corda.core.crypto.CompositeKey import net.corda.core.identity.Party -import net.corda.finance.contracts.asset.DUMMY_CASH_ISSUER_KEY import net.corda.testing.* import net.corda.testing.contracts.DummyContract import net.corda.testing.node.MockAttachment import org.junit.Rule import org.junit.Test +import java.math.BigInteger import java.security.KeyPair import kotlin.test.assertEquals import kotlin.test.assertFailsWith @@ -19,6 +19,7 @@ class TransactionTests { private companion object { val DUMMY_KEY_1 = generateKeyPair() val DUMMY_KEY_2 = generateKeyPair() + val DUMMY_CASH_ISSUER_KEY = entropyToKeyPair(BigInteger.valueOf(10)) } @Rule diff --git a/docs/source/example-code/src/main/java/net/corda/docs/java/tutorial/testdsl/CommercialPaperTest.java b/docs/source/example-code/src/main/java/net/corda/docs/java/tutorial/testdsl/CommercialPaperTest.java index a064678504..07f96740a0 100644 --- a/docs/source/example-code/src/main/java/net/corda/docs/java/tutorial/testdsl/CommercialPaperTest.java +++ b/docs/source/example-code/src/main/java/net/corda/docs/java/tutorial/testdsl/CommercialPaperTest.java @@ -6,13 +6,13 @@ import net.corda.core.utilities.OpaqueBytes; import net.corda.finance.contracts.ICommercialPaperState; import net.corda.finance.contracts.JavaCommercialPaper; import net.corda.finance.contracts.asset.Cash; +import net.corda.node.services.api.IdentityServiceInternal; import net.corda.testing.SerializationEnvironmentRule; import net.corda.testing.node.MockServices; import org.junit.Rule; import org.junit.Test; import java.time.temporal.ChronoUnit; -import java.util.Arrays; import static net.corda.finance.Currencies.DOLLARS; import static net.corda.finance.Currencies.issuedBy; @@ -21,13 +21,21 @@ import static net.corda.testing.CoreTestUtils.*; import static net.corda.testing.NodeTestUtils.ledger; import static net.corda.testing.NodeTestUtils.transaction; import static net.corda.testing.TestConstants.*; -import static net.corda.testing.node.MockServicesKt.makeTestIdentityService; +import static org.mockito.Mockito.doReturn; public class CommercialPaperTest { @Rule public final SerializationEnvironmentRule testSerialization = new SerializationEnvironmentRule(); private final OpaqueBytes defaultRef = new OpaqueBytes(new byte[]{123}); - private final MockServices ledgerServices = new MockServices(makeTestIdentityService(Arrays.asList(getMEGA_CORP_IDENTITY(), getMINI_CORP_IDENTITY(), getDUMMY_CASH_ISSUER_IDENTITY(), getDUMMY_NOTARY_IDENTITY())), getMEGA_CORP().getName()); + private final MockServices ledgerServices; + + { + IdentityServiceInternal identityService = rigorousMock(IdentityServiceInternal.class); + doReturn(getMEGA_CORP()).when(identityService).partyFromKey(getMEGA_CORP_PUBKEY()); + doReturn(null).when(identityService).partyFromKey(getBIG_CORP_PUBKEY()); + doReturn(null).when(identityService).partyFromKey(getALICE_PUBKEY()); + ledgerServices = new MockServices(identityService, getMEGA_CORP().getName()); + } // DOCSTART 1 private ICommercialPaperState getPaper() { diff --git a/docs/source/example-code/src/main/kotlin/net/corda/docs/tutorial/testdsl/TutorialTestDSL.kt b/docs/source/example-code/src/main/kotlin/net/corda/docs/tutorial/testdsl/TutorialTestDSL.kt index d1da1b3c56..eceadf55a7 100644 --- a/docs/source/example-code/src/main/kotlin/net/corda/docs/tutorial/testdsl/TutorialTestDSL.kt +++ b/docs/source/example-code/src/main/kotlin/net/corda/docs/tutorial/testdsl/TutorialTestDSL.kt @@ -1,5 +1,7 @@ package net.corda.docs.tutorial.testdsl +import com.nhaarman.mockito_kotlin.doReturn +import com.nhaarman.mockito_kotlin.whenever import net.corda.core.utilities.days import net.corda.finance.DOLLARS import net.corda.finance.`issued by` @@ -8,9 +10,9 @@ import net.corda.finance.contracts.CommercialPaper import net.corda.finance.contracts.ICommercialPaperState import net.corda.finance.contracts.asset.CASH import net.corda.finance.contracts.asset.Cash +import net.corda.node.services.api.IdentityServiceInternal import net.corda.testing.* import net.corda.testing.node.MockServices -import net.corda.testing.node.makeTestIdentityService import org.junit.Rule import org.junit.Test @@ -18,7 +20,12 @@ class CommercialPaperTest { @Rule @JvmField val testSerialization = SerializationEnvironmentRule() - private val ledgerServices = MockServices(makeTestIdentityService(listOf(MEGA_CORP_IDENTITY, MINI_CORP_IDENTITY, DUMMY_CASH_ISSUER_IDENTITY, DUMMY_NOTARY_IDENTITY)), MEGA_CORP.name) + private val ledgerServices = MockServices(rigorousMock().also { + doReturn(MEGA_CORP).whenever(it).partyFromKey(MEGA_CORP_PUBKEY) + doReturn(null).whenever(it).partyFromKey(BIG_CORP_PUBKEY) + doReturn(null).whenever(it).partyFromKey(ALICE_PUBKEY) + }, MEGA_CORP.name) + // DOCSTART 1 fun getPaper(): ICommercialPaperState = CommercialPaper.State( issuance = MEGA_CORP.ref(123), diff --git a/experimental/src/test/kotlin/net/corda/finance/contracts/universal/Cap.kt b/experimental/src/test/kotlin/net/corda/finance/contracts/universal/Cap.kt index 7ecbfbe906..e77ca43f4c 100644 --- a/experimental/src/test/kotlin/net/corda/finance/contracts/universal/Cap.kt +++ b/experimental/src/test/kotlin/net/corda/finance/contracts/universal/Cap.kt @@ -1,20 +1,31 @@ package net.corda.finance.contracts.universal +import com.nhaarman.mockito_kotlin.doReturn +import com.nhaarman.mockito_kotlin.whenever +import net.corda.core.crypto.entropyToKeyPair +import net.corda.core.identity.CordaX500Name +import net.corda.core.identity.Party import net.corda.finance.contracts.BusinessCalendar import net.corda.finance.contracts.FixOf import net.corda.finance.contracts.Frequency import net.corda.finance.contracts.Tenor +import net.corda.node.services.api.IdentityServiceInternal import net.corda.testing.* import net.corda.testing.node.MockServices import net.corda.testing.node.makeTestIdentityService import org.junit.Ignore import org.junit.Rule import org.junit.Test +import java.math.BigInteger import java.time.Instant import java.time.LocalDate fun transaction(script: TransactionDSL.() -> EnforceVerifyOrFail) = run { - MockServices(listOf("net.corda.finance.contracts.universal"), makeTestIdentityService(listOf(MEGA_CORP_IDENTITY, MINI_CORP_IDENTITY, DUMMY_CASH_ISSUER_IDENTITY, DUMMY_NOTARY_IDENTITY)), MEGA_CORP.name).transaction(DUMMY_NOTARY, script) + MockServices(listOf("net.corda.finance.contracts.universal"), rigorousMock().also { + listOf(acmeCorp, highStreetBank, momAndPop).forEach { party -> + doReturn(null).whenever(it).partyFromKey(party.owningKey) + } + }, MEGA_CORP.name).transaction(DUMMY_NOTARY, script) } class Cap { diff --git a/finance/src/main/kotlin/net/corda/finance/contracts/asset/Cash.kt b/finance/src/main/kotlin/net/corda/finance/contracts/asset/Cash.kt index 6234c7ab73..998edb5326 100644 --- a/finance/src/main/kotlin/net/corda/finance/contracts/asset/Cash.kt +++ b/finance/src/main/kotlin/net/corda/finance/contracts/asset/Cash.kt @@ -7,10 +7,8 @@ import co.paralleluniverse.fibers.Suspendable import net.corda.core.contracts.* import net.corda.core.contracts.Amount.Companion.sumOrThrow import net.corda.core.crypto.NullKeys.NULL_PARTY -import net.corda.core.crypto.entropyToKeyPair import net.corda.core.crypto.toStringShort import net.corda.core.identity.AbstractParty -import net.corda.core.identity.CordaX500Name import net.corda.core.identity.Party import net.corda.core.identity.PartyAndCertificate import net.corda.core.internal.Emoji @@ -25,7 +23,6 @@ import net.corda.finance.schemas.CashSchemaV1 import net.corda.finance.utils.sumCash import net.corda.finance.utils.sumCashOrNull import net.corda.finance.utils.sumCashOrZero -import java.math.BigInteger import java.security.PublicKey import java.util.* @@ -342,14 +339,7 @@ class Cash : OnLedgerAsset() { } // Unit testing helpers. These could go in a separate file but it's hardly worth it for just a few functions. - -/** A dummy, randomly generated issuer party by the name of "Snake Oil Issuer" */ -val DUMMY_CASH_ISSUER_NAME = CordaX500Name(organisation = "Snake Oil Issuer", locality = "London", country = "GB") -/** A randomly generated key. */ -val DUMMY_CASH_ISSUER_KEY by lazy { entropyToKeyPair(BigInteger.valueOf(10)) } -/** A dummy, randomly generated issuer party by the name of "Snake Oil Issuer" */ -val DUMMY_CASH_ISSUER by lazy { Party(DUMMY_CASH_ISSUER_NAME, DUMMY_CASH_ISSUER_KEY.public).ref(1) } /** An extension property that lets you write 100.DOLLARS.CASH */ -val Amount.CASH: Cash.State get() = Cash.State(Amount(quantity, Issued(DUMMY_CASH_ISSUER, token)), NULL_PARTY) +val Amount.CASH: Cash.State get() = Cash.State(Amount(quantity, Issued(NULL_PARTY.ref(1), token)), NULL_PARTY) /** An extension property that lets you get a cash state from an issued token, under the [NULL_PARTY] */ val Amount>.STATE: Cash.State get() = Cash.State(this, NULL_PARTY) diff --git a/finance/src/main/kotlin/net/corda/finance/contracts/asset/Obligation.kt b/finance/src/main/kotlin/net/corda/finance/contracts/asset/Obligation.kt index c30ddc0875..23d905288f 100644 --- a/finance/src/main/kotlin/net/corda/finance/contracts/asset/Obligation.kt +++ b/finance/src/main/kotlin/net/corda/finance/contracts/asset/Obligation.kt @@ -6,10 +6,8 @@ import net.corda.finance.contracts.NetType import net.corda.finance.contracts.NettableState import net.corda.finance.contracts.asset.Obligation.Lifecycle.NORMAL import net.corda.core.crypto.SecureHash -import net.corda.core.crypto.entropyToKeyPair import net.corda.core.identity.AbstractParty import net.corda.core.identity.AnonymousParty -import net.corda.core.identity.CordaX500Name import net.corda.core.identity.Party import net.corda.core.internal.Emoji import net.corda.core.internal.VisibleForTesting @@ -22,7 +20,6 @@ import net.corda.finance.utils.sumFungibleOrNull import net.corda.finance.utils.sumObligations import net.corda.finance.utils.sumObligationsOrNull import net.corda.finance.utils.sumObligationsOrZero -import java.math.BigInteger import java.security.PublicKey import java.time.Duration import java.time.Instant @@ -791,8 +788,3 @@ fun Obligation.State.ownedBy(owner: AbstractParty) = copy(beneficia @Suppress("unused") fun Obligation.State.issuedBy(party: AnonymousParty) = copy(obligor = party) - -/** A randomly generated key. */ -val DUMMY_OBLIGATION_ISSUER_KEY by lazy { entropyToKeyPair(BigInteger.valueOf(10)) } -/** A dummy, randomly generated issuer party by the name of "Snake Oil Issuer" */ -val DUMMY_OBLIGATION_ISSUER by lazy { Party(CordaX500Name(organisation = "Snake Oil Issuer", locality = "London", country = "GB"), DUMMY_OBLIGATION_ISSUER_KEY.public) } diff --git a/finance/src/test/java/net/corda/finance/contracts/asset/CashTestsJava.java b/finance/src/test/java/net/corda/finance/contracts/asset/CashTestsJava.java index 4e010df5b3..5ab3569b25 100644 --- a/finance/src/test/java/net/corda/finance/contracts/asset/CashTestsJava.java +++ b/finance/src/test/java/net/corda/finance/contracts/asset/CashTestsJava.java @@ -3,21 +3,19 @@ package net.corda.finance.contracts.asset; import net.corda.core.contracts.PartyAndReference; import net.corda.core.identity.AnonymousParty; import net.corda.core.utilities.OpaqueBytes; +import net.corda.node.services.api.IdentityServiceInternal; import net.corda.testing.DummyCommandData; import net.corda.testing.SerializationEnvironmentRule; import net.corda.testing.node.MockServices; import org.junit.Rule; import org.junit.Test; -import java.util.Arrays; - import static net.corda.finance.Currencies.DOLLARS; import static net.corda.finance.Currencies.issuedBy; import static net.corda.testing.CoreTestUtils.*; import static net.corda.testing.NodeTestUtils.transaction; import static net.corda.testing.TestConstants.getDUMMY_NOTARY; -import static net.corda.testing.TestConstants.getDUMMY_NOTARY_IDENTITY; -import static net.corda.testing.node.MockServicesKt.makeTestIdentityService; +import static org.mockito.Mockito.doReturn; /** * This is an incomplete Java replica of CashTests.kt to show how to use the Java test DSL @@ -32,7 +30,10 @@ public class CashTestsJava { @Test public void trivial() { - transaction(new MockServices(makeTestIdentityService(Arrays.asList(getMEGA_CORP_IDENTITY(), getMINI_CORP_IDENTITY(), getDUMMY_CASH_ISSUER_IDENTITY(), getDUMMY_NOTARY_IDENTITY())), getMEGA_CORP().getName()), getDUMMY_NOTARY(), tx -> { + IdentityServiceInternal identityService = rigorousMock(IdentityServiceInternal.class); + doReturn(getMEGA_CORP()).when(identityService).partyFromKey(getMEGA_CORP_PUBKEY()); + doReturn(getMINI_CORP()).when(identityService).partyFromKey(getMINI_CORP_PUBKEY()); + transaction(new MockServices(identityService, getMEGA_CORP().getName()), getDUMMY_NOTARY(), tx -> { tx.attachment(Cash.PROGRAM_ID); tx.input(Cash.PROGRAM_ID, inState); diff --git a/finance/src/test/kotlin/net/corda/finance/contracts/CommercialPaperTests.kt b/finance/src/test/kotlin/net/corda/finance/contracts/CommercialPaperTests.kt index 31755d5246..ff6d194737 100644 --- a/finance/src/test/kotlin/net/corda/finance/contracts/CommercialPaperTests.kt +++ b/finance/src/test/kotlin/net/corda/finance/contracts/CommercialPaperTests.kt @@ -1,7 +1,11 @@ package net.corda.finance.contracts +import com.nhaarman.mockito_kotlin.doReturn +import com.nhaarman.mockito_kotlin.whenever import net.corda.core.contracts.* +import net.corda.core.crypto.entropyToKeyPair import net.corda.core.identity.AnonymousParty +import net.corda.core.identity.CordaX500Name import net.corda.core.identity.Party import net.corda.core.node.services.Vault import net.corda.core.node.services.VaultService @@ -12,6 +16,7 @@ import net.corda.core.utilities.seconds import net.corda.finance.DOLLARS import net.corda.finance.`issued by` import net.corda.finance.contracts.asset.* +import net.corda.node.services.api.IdentityServiceInternal import net.corda.testing.* import net.corda.testing.contracts.VaultFiller import net.corda.testing.node.MockServices @@ -21,6 +26,7 @@ import org.junit.Rule import org.junit.Test import org.junit.runner.RunWith import org.junit.runners.Parameterized +import java.math.BigInteger import java.time.Instant import java.util.* import kotlin.test.assertFailsWith @@ -84,6 +90,10 @@ class CommercialPaperTestsGeneric { @Parameterized.Parameters @JvmStatic fun data() = listOf(JavaCommercialPaperTest(), KotlinCommercialPaperTest(), KotlinCommercialPaperLegacyTest()) + + private val DUMMY_CASH_ISSUER_KEY = entropyToKeyPair(BigInteger.valueOf(10)) + private val DUMMY_CASH_ISSUER_IDENTITY = getTestPartyAndCertificate(Party(CordaX500Name("Snake Oil Issuer", "London", "GB"), DUMMY_CASH_ISSUER_KEY.public)) + private val DUMMY_CASH_ISSUER = DUMMY_CASH_ISSUER_IDENTITY.party.ref(1) } @Parameterized.Parameter @@ -92,7 +102,12 @@ class CommercialPaperTestsGeneric { @JvmField val testSerialization = SerializationEnvironmentRule() val issuer = MEGA_CORP.ref(123) - private val ledgerServices = MockServices(makeTestIdentityService(listOf(MEGA_CORP_IDENTITY, MINI_CORP_IDENTITY, DUMMY_CASH_ISSUER_IDENTITY, DUMMY_NOTARY_IDENTITY)), MEGA_CORP.name) + private val ledgerServices = MockServices(rigorousMock().also { + doReturn(MEGA_CORP).whenever(it).partyFromKey(MEGA_CORP_PUBKEY) + doReturn(MINI_CORP).whenever(it).partyFromKey(MINI_CORP_PUBKEY) + doReturn(null).whenever(it).partyFromKey(ALICE_PUBKEY) + }, MEGA_CORP.name) + @Test fun `trade lifecycle test`() { val someProfits = 1200.DOLLARS `issued by` issuer diff --git a/finance/src/test/kotlin/net/corda/finance/contracts/asset/CashTests.kt b/finance/src/test/kotlin/net/corda/finance/contracts/asset/CashTests.kt index 23e223fd4c..261ee64b95 100644 --- a/finance/src/test/kotlin/net/corda/finance/contracts/asset/CashTests.kt +++ b/finance/src/test/kotlin/net/corda/finance/contracts/asset/CashTests.kt @@ -3,11 +3,9 @@ package net.corda.finance.contracts.asset import com.nhaarman.mockito_kotlin.* import net.corda.core.contracts.* import net.corda.core.crypto.SecureHash +import net.corda.core.crypto.entropyToKeyPair import net.corda.core.crypto.generateKeyPair -import net.corda.core.identity.AbstractParty -import net.corda.core.identity.AnonymousParty -import net.corda.core.identity.CordaX500Name -import net.corda.core.identity.Party +import net.corda.core.identity.* import net.corda.core.node.ServiceHub import net.corda.core.node.services.VaultService import net.corda.core.node.services.queryBy @@ -32,10 +30,15 @@ import org.junit.After import org.junit.Before import org.junit.Rule import org.junit.Test +import java.math.BigInteger import java.util.* import kotlin.test.* class CashTests { + companion object { + private val DUMMY_CASH_ISSUER_IDENTITY = getTestPartyAndCertificate(Party(CordaX500Name("Snake Oil Issuer", "London", "GB"), entropyToKeyPair(BigInteger.valueOf(10)).public)) + } + @Rule @JvmField val testSerialization = SerializationEnvironmentRule() @@ -86,7 +89,7 @@ class CashTests { ourIdentity = ourServices.myInfo.singleIdentity() miniCorpAnonymised = miniCorpServices.myInfo.singleIdentityAndCert().party.anonymise() (miniCorpServices.myInfo.legalIdentitiesAndCerts + megaCorpServices.myInfo.legalIdentitiesAndCerts + notaryServices.myInfo.legalIdentitiesAndCerts).forEach { identity -> - ourServices.identityService.verifyAndRegisterIdentity(identity) + ourServices.identityService.verifyAndRegisterIdentity(identity) // TODO: Configure a mock identity service instead. } // Create some cash. Any attempt to spend >$500 will require multiple issuers to be involved. @@ -114,7 +117,12 @@ class CashTests { } private fun transaction(script: TransactionDSL.() -> EnforceVerifyOrFail) = run { - MockServices(makeTestIdentityService(listOf(MEGA_CORP_IDENTITY, MINI_CORP_IDENTITY, DUMMY_CASH_ISSUER_IDENTITY, DUMMY_NOTARY_IDENTITY)), MEGA_CORP.name).transaction(DUMMY_NOTARY, script) + MockServices(rigorousMock().also { + doReturn(MEGA_CORP).whenever(it).partyFromKey(MEGA_CORP_PUBKEY) + doReturn(MINI_CORP).whenever(it).partyFromKey(MINI_CORP_PUBKEY) + doReturn(null).whenever(it).partyFromKey(ALICE_PUBKEY) + doReturn(null).whenever(it).partyFromKey(BOB_PUBKEY) + }, MEGA_CORP.name).transaction(DUMMY_NOTARY, script) } @Test diff --git a/finance/src/test/kotlin/net/corda/finance/contracts/asset/ObligationTests.kt b/finance/src/test/kotlin/net/corda/finance/contracts/asset/ObligationTests.kt index dec5493225..803954541f 100644 --- a/finance/src/test/kotlin/net/corda/finance/contracts/asset/ObligationTests.kt +++ b/finance/src/test/kotlin/net/corda/finance/contracts/asset/ObligationTests.kt @@ -5,9 +5,12 @@ import com.nhaarman.mockito_kotlin.whenever import net.corda.core.contracts.* import net.corda.core.crypto.NullKeys.NULL_PARTY import net.corda.core.crypto.SecureHash +import net.corda.core.crypto.entropyToKeyPair import net.corda.core.crypto.sha256 import net.corda.core.identity.AbstractParty import net.corda.core.identity.AnonymousParty +import net.corda.core.identity.CordaX500Name +import net.corda.core.identity.Party import net.corda.core.transactions.TransactionBuilder import net.corda.core.utilities.NonEmptySet import net.corda.core.utilities.OpaqueBytes @@ -22,9 +25,9 @@ import net.corda.testing.* import net.corda.testing.contracts.DummyContract import net.corda.testing.contracts.DummyState import net.corda.testing.node.MockServices -import net.corda.testing.node.makeTestIdentityService import org.junit.Rule import org.junit.Test +import java.math.BigInteger import java.time.Instant import java.time.temporal.ChronoUnit import java.util.* @@ -34,6 +37,10 @@ import kotlin.test.assertNotEquals import kotlin.test.assertTrue class ObligationTests { + companion object { + private val DUMMY_OBLIGATION_ISSUER = Party(CordaX500Name("Snake Oil Issuer", "London", "GB"), entropyToKeyPair(BigInteger.valueOf(10)).public) + } + @Rule @JvmField val testSerialization = SerializationEnvironmentRule() @@ -57,12 +64,15 @@ class ObligationTests { private val outState = inState.copy(beneficiary = AnonymousParty(BOB_PUBKEY)) private val miniCorpServices = MockServices(listOf("net.corda.finance.contracts.asset"), rigorousMock(), MINI_CORP.name, MINI_CORP_KEY) private val notaryServices = MockServices(rigorousMock(), MEGA_CORP.name, DUMMY_NOTARY_KEY) - private val mockService = MockServices(listOf("net.corda.finance.contracts.asset"), rigorousMock().also { + private val identityService = rigorousMock().also { doReturn(null).whenever(it).partyFromKey(ALICE_PUBKEY) doReturn(null).whenever(it).partyFromKey(BOB_PUBKEY) + doReturn(null).whenever(it).partyFromKey(CHARLIE.owningKey) doReturn(MEGA_CORP).whenever(it).partyFromKey(MEGA_CORP_PUBKEY) - }, MEGA_CORP.name) - private val ledgerServices get() = MockServices(makeTestIdentityService(listOf(MEGA_CORP_IDENTITY, MINI_CORP_IDENTITY, DUMMY_CASH_ISSUER_IDENTITY, DUMMY_NOTARY_IDENTITY)), MEGA_CORP.name) + doReturn(MINI_CORP).whenever(it).partyFromKey(MINI_CORP_PUBKEY) + } + private val mockService = MockServices(listOf("net.corda.finance.contracts.asset"), identityService, MEGA_CORP.name) + private val ledgerServices get() = MockServices(identityService, MEGA_CORP.name) private fun cashObligationTestRoots( group: LedgerDSL ) = group.apply { diff --git a/node/src/test/java/net/corda/node/services/vault/VaultQueryJavaTests.java b/node/src/test/java/net/corda/node/services/vault/VaultQueryJavaTests.java index 48aa58a07f..f8a19ddb5f 100644 --- a/node/src/test/java/net/corda/node/services/vault/VaultQueryJavaTests.java +++ b/node/src/test/java/net/corda/node/services/vault/VaultQueryJavaTests.java @@ -6,6 +6,9 @@ import kotlin.Triple; import net.corda.core.contracts.*; import net.corda.core.crypto.CryptoUtils; import net.corda.core.identity.AbstractParty; +import net.corda.core.identity.CordaX500Name; +import net.corda.core.identity.Party; +import net.corda.core.identity.PartyAndCertificate; import net.corda.core.messaging.DataFeed; import net.corda.core.node.services.Vault; import net.corda.core.node.services.VaultQueryException; @@ -33,17 +36,19 @@ import rx.Observable; import java.io.IOException; import java.lang.reflect.Field; +import java.math.BigInteger; import java.security.InvalidAlgorithmParameterException; +import java.security.KeyPair; import java.security.cert.CertificateException; import java.util.*; import java.util.stream.Collectors; import java.util.stream.Stream; import java.util.stream.StreamSupport; +import static net.corda.core.crypto.CryptoUtils.entropyToKeyPair; import static net.corda.core.node.services.vault.QueryCriteriaUtils.DEFAULT_PAGE_NUM; import static net.corda.core.node.services.vault.QueryCriteriaUtils.MAX_PAGE_SIZE; import static net.corda.core.utilities.ByteArrays.toHexString; -import static net.corda.finance.contracts.asset.CashUtilities.*; import static net.corda.testing.CoreTestUtils.*; import static net.corda.testing.TestConstants.*; import static net.corda.testing.node.MockServices.makeTestDatabaseAndMockServices; @@ -51,6 +56,10 @@ import static net.corda.testing.node.MockServicesKt.makeTestIdentityService; import static org.assertj.core.api.Assertions.assertThat; public class VaultQueryJavaTests { + private static final CordaX500Name DUMMY_CASH_ISSUER_NAME = new CordaX500Name("Snake Oil Issuer", "London", "GB"); + private static final KeyPair DUMMY_CASH_ISSUER_KEY = entropyToKeyPair(BigInteger.valueOf(10)); + private static final PartyAndCertificate DUMMY_CASH_ISSUER_IDENTITY = getTestPartyAndCertificate(new Party(DUMMY_CASH_ISSUER_NAME, DUMMY_CASH_ISSUER_KEY.getPublic())); + private static final PartyAndReference DUMMY_CASH_ISSUER = DUMMY_CASH_ISSUER_IDENTITY.getParty().ref((byte) 1); @Rule public final SerializationEnvironmentRule testSerialization = new SerializationEnvironmentRule(); private VaultFiller vaultFiller; @@ -61,13 +70,13 @@ public class VaultQueryJavaTests { @Before public void setUp() throws CertificateException, InvalidAlgorithmParameterException { List cordappPackages = Arrays.asList("net.corda.testing.contracts", "net.corda.finance.contracts.asset", CashSchemaV1.class.getPackage().getName()); - IdentityServiceInternal identitySvc = makeTestIdentityService(Arrays.asList(getMEGA_CORP_IDENTITY(), getDUMMY_CASH_ISSUER_IDENTITY(), getDUMMY_NOTARY_IDENTITY())); + IdentityServiceInternal identitySvc = makeTestIdentityService(Arrays.asList(getMEGA_CORP_IDENTITY(), DUMMY_CASH_ISSUER_IDENTITY, getDUMMY_NOTARY_IDENTITY())); Pair databaseAndServices = makeTestDatabaseAndMockServices( Arrays.asList(getMEGA_CORP_KEY(), getDUMMY_NOTARY_KEY()), identitySvc, cordappPackages, getMEGA_CORP().getName()); - issuerServices = new MockServices(cordappPackages, rigorousMock(IdentityServiceInternal.class), getDUMMY_CASH_ISSUER_NAME(), getDUMMY_CASH_ISSUER_KEY(), getBOC_KEY()); + issuerServices = new MockServices(cordappPackages, rigorousMock(IdentityServiceInternal.class), DUMMY_CASH_ISSUER_NAME, DUMMY_CASH_ISSUER_KEY, getBOC_KEY()); database = databaseAndServices.getFirst(); MockServices services = databaseAndServices.getSecond(); vaultFiller = new VaultFiller(services, getDUMMY_NOTARY(), getDUMMY_NOTARY_KEY()); @@ -138,7 +147,7 @@ public class VaultQueryJavaTests { new Amount<>(100, Currency.getInstance("USD")), issuerServices, 3, - getDUMMY_CASH_ISSUER(), + DUMMY_CASH_ISSUER, null, new Random()); return tx; @@ -213,10 +222,10 @@ public class VaultQueryJavaTests { Amount dollars100 = new Amount<>(100, Currency.getInstance("USD")); Amount dollars10 = new Amount<>(10, Currency.getInstance("USD")); Amount dollars1 = new Amount<>(1, Currency.getInstance("USD")); - vaultFiller.fillWithSomeTestCash(pounds, issuerServices, 1, getDUMMY_CASH_ISSUER()); - vaultFiller.fillWithSomeTestCash(dollars100, issuerServices, 1, getDUMMY_CASH_ISSUER()); - vaultFiller.fillWithSomeTestCash(dollars10, issuerServices, 1, getDUMMY_CASH_ISSUER()); - vaultFiller.fillWithSomeTestCash(dollars1, issuerServices, 1, getDUMMY_CASH_ISSUER()); + vaultFiller.fillWithSomeTestCash(pounds, issuerServices, 1, DUMMY_CASH_ISSUER); + vaultFiller.fillWithSomeTestCash(dollars100, issuerServices, 1, DUMMY_CASH_ISSUER); + vaultFiller.fillWithSomeTestCash(dollars10, issuerServices, 1, DUMMY_CASH_ISSUER); + vaultFiller.fillWithSomeTestCash(dollars1, issuerServices, 1, DUMMY_CASH_ISSUER); return tx; }); database.transaction(tx -> { @@ -257,7 +266,7 @@ public class VaultQueryJavaTests { new Amount<>(100, Currency.getInstance("USD")), issuerServices, 3, - getDUMMY_CASH_ISSUER(), + DUMMY_CASH_ISSUER, null, new Random()); return tx; @@ -331,11 +340,11 @@ public class VaultQueryJavaTests { Amount dollars300 = new Amount<>(300, Currency.getInstance("USD")); Amount pounds = new Amount<>(400, Currency.getInstance("GBP")); Amount swissfrancs = new Amount<>(500, Currency.getInstance("CHF")); - vaultFiller.fillWithSomeTestCash(dollars100, issuerServices, 1, getDUMMY_CASH_ISSUER()); - vaultFiller.fillWithSomeTestCash(dollars200, issuerServices, 2, getDUMMY_CASH_ISSUER()); - vaultFiller.fillWithSomeTestCash(dollars300, issuerServices, 3, getDUMMY_CASH_ISSUER()); - vaultFiller.fillWithSomeTestCash(pounds, issuerServices, 4, getDUMMY_CASH_ISSUER()); - vaultFiller.fillWithSomeTestCash(swissfrancs, issuerServices, 5, getDUMMY_CASH_ISSUER()); + vaultFiller.fillWithSomeTestCash(dollars100, issuerServices, 1, DUMMY_CASH_ISSUER); + vaultFiller.fillWithSomeTestCash(dollars200, issuerServices, 2, DUMMY_CASH_ISSUER); + vaultFiller.fillWithSomeTestCash(dollars300, issuerServices, 3, DUMMY_CASH_ISSUER); + vaultFiller.fillWithSomeTestCash(pounds, issuerServices, 4, DUMMY_CASH_ISSUER); + vaultFiller.fillWithSomeTestCash(swissfrancs, issuerServices, 5, DUMMY_CASH_ISSUER); return tx; }); database.transaction(tx -> { @@ -377,11 +386,11 @@ public class VaultQueryJavaTests { Amount dollars300 = new Amount<>(300, Currency.getInstance("USD")); Amount pounds = new Amount<>(400, Currency.getInstance("GBP")); Amount swissfrancs = new Amount<>(500, Currency.getInstance("CHF")); - vaultFiller.fillWithSomeTestCash(dollars100, issuerServices, 1, getDUMMY_CASH_ISSUER()); - vaultFiller.fillWithSomeTestCash(dollars200, issuerServices, 2, getDUMMY_CASH_ISSUER()); - vaultFiller.fillWithSomeTestCash(dollars300, issuerServices, 3, getDUMMY_CASH_ISSUER()); - vaultFiller.fillWithSomeTestCash(pounds, issuerServices, 4, getDUMMY_CASH_ISSUER()); - vaultFiller.fillWithSomeTestCash(swissfrancs, issuerServices, 5, getDUMMY_CASH_ISSUER()); + vaultFiller.fillWithSomeTestCash(dollars100, issuerServices, 1, DUMMY_CASH_ISSUER); + vaultFiller.fillWithSomeTestCash(dollars200, issuerServices, 2, DUMMY_CASH_ISSUER); + vaultFiller.fillWithSomeTestCash(dollars300, issuerServices, 3, DUMMY_CASH_ISSUER); + vaultFiller.fillWithSomeTestCash(pounds, issuerServices, 4, DUMMY_CASH_ISSUER); + vaultFiller.fillWithSomeTestCash(swissfrancs, issuerServices, 5, DUMMY_CASH_ISSUER); return tx; }); database.transaction(tx -> { @@ -438,9 +447,9 @@ public class VaultQueryJavaTests { Amount dollars200 = new Amount<>(200, Currency.getInstance("USD")); Amount pounds300 = new Amount<>(300, Currency.getInstance("GBP")); Amount pounds400 = new Amount<>(400, Currency.getInstance("GBP")); - vaultFiller.fillWithSomeTestCash(dollars100, issuerServices, 1, getDUMMY_CASH_ISSUER()); + vaultFiller.fillWithSomeTestCash(dollars100, issuerServices, 1, DUMMY_CASH_ISSUER); vaultFiller.fillWithSomeTestCash(dollars200, issuerServices, 2, getBOC().ref(new OpaqueBytes("1".getBytes()))); - vaultFiller.fillWithSomeTestCash(pounds300, issuerServices, 3, getDUMMY_CASH_ISSUER()); + vaultFiller.fillWithSomeTestCash(pounds300, issuerServices, 3, DUMMY_CASH_ISSUER); vaultFiller.fillWithSomeTestCash(pounds400, issuerServices, 4, getBOC().ref(new OpaqueBytes("1".getBytes()))); return tx; }); @@ -460,13 +469,13 @@ public class VaultQueryJavaTests { assertThat(results.getOtherResults().get(1)).isEqualTo(CryptoUtils.toStringShort(getBOC_PUBKEY())); assertThat(results.getOtherResults().get(2)).isEqualTo("GBP"); assertThat(results.getOtherResults().get(3)).isEqualTo(300L); - assertThat(results.getOtherResults().get(4)).isEqualTo(CryptoUtils.toStringShort(getDUMMY_CASH_ISSUER().getParty().getOwningKey())); + assertThat(results.getOtherResults().get(4)).isEqualTo(CryptoUtils.toStringShort(DUMMY_CASH_ISSUER_KEY.getPublic())); assertThat(results.getOtherResults().get(5)).isEqualTo("GBP"); assertThat(results.getOtherResults().get(6)).isEqualTo(200L); assertThat(results.getOtherResults().get(7)).isEqualTo(CryptoUtils.toStringShort(getBOC_PUBKEY())); assertThat(results.getOtherResults().get(8)).isEqualTo("USD"); assertThat(results.getOtherResults().get(9)).isEqualTo(100L); - assertThat(results.getOtherResults().get(10)).isEqualTo(CryptoUtils.toStringShort(getDUMMY_CASH_ISSUER().getParty().getOwningKey())); + assertThat(results.getOtherResults().get(10)).isEqualTo(CryptoUtils.toStringShort(DUMMY_CASH_ISSUER_KEY.getPublic())); assertThat(results.getOtherResults().get(11)).isEqualTo("USD"); } catch (NoSuchFieldException e) { diff --git a/node/src/test/kotlin/net/corda/node/services/persistence/HibernateConfigurationTest.kt b/node/src/test/kotlin/net/corda/node/services/persistence/HibernateConfigurationTest.kt index 98c06829b8..58666e4204 100644 --- a/node/src/test/kotlin/net/corda/node/services/persistence/HibernateConfigurationTest.kt +++ b/node/src/test/kotlin/net/corda/node/services/persistence/HibernateConfigurationTest.kt @@ -6,8 +6,10 @@ import net.corda.core.contracts.StateAndRef import net.corda.core.contracts.StateRef import net.corda.core.contracts.TransactionState import net.corda.core.crypto.SecureHash +import net.corda.core.crypto.entropyToKeyPair import net.corda.core.crypto.generateKeyPair import net.corda.core.identity.AbstractParty +import net.corda.core.identity.CordaX500Name import net.corda.core.identity.Party import net.corda.core.node.StatesToRecord import net.corda.core.node.services.IdentityService @@ -22,8 +24,6 @@ import net.corda.finance.DOLLARS import net.corda.finance.POUNDS import net.corda.finance.SWISS_FRANCS import net.corda.finance.contracts.asset.Cash -import net.corda.finance.contracts.asset.DUMMY_CASH_ISSUER_KEY -import net.corda.finance.contracts.asset.DUMMY_CASH_ISSUER_NAME import net.corda.finance.contracts.asset.DummyFungibleContract import net.corda.finance.schemas.CashSchemaV1 import net.corda.finance.schemas.SampleCashSchemaV2 @@ -48,6 +48,7 @@ import org.assertj.core.api.Assertions.assertThat import org.hibernate.SessionFactory import org.junit.* import java.math.BigDecimal +import java.math.BigInteger import java.time.Instant import java.util.* import javax.persistence.EntityManager @@ -55,6 +56,12 @@ import javax.persistence.Tuple import javax.persistence.criteria.CriteriaBuilder class HibernateConfigurationTest { + private companion object { + val DUMMY_CASH_ISSUER_NAME = CordaX500Name("Snake Oil Issuer", "London", "GB") + val DUMMY_CASH_ISSUER_KEY = entropyToKeyPair(BigInteger.valueOf(10)) + val DUMMY_CASH_ISSUER = Party(DUMMY_CASH_ISSUER_NAME, DUMMY_CASH_ISSUER_KEY.public) + } + @Rule @JvmField val testSerialization = SerializationEnvironmentRule() @@ -91,7 +98,7 @@ class HibernateConfigurationTest { val dataSourceProps = makeTestDataSourceProperties() val identityService = rigorousMock().also { mock -> doReturn(null).whenever(mock).wellKnownPartyFromAnonymous(any()) - listOf(DUMMY_CASH_ISSUER_IDENTITY.party, DUMMY_NOTARY).forEach { + listOf(DUMMY_CASH_ISSUER, DUMMY_NOTARY).forEach { doReturn(it).whenever(mock).wellKnownPartyFromAnonymous(it) doReturn(it).whenever(mock).wellKnownPartyFromX500Name(it.name) } diff --git a/node/src/test/kotlin/net/corda/node/services/vault/NodeVaultServiceTest.kt b/node/src/test/kotlin/net/corda/node/services/vault/NodeVaultServiceTest.kt index 8002741b5c..d40bc86126 100644 --- a/node/src/test/kotlin/net/corda/node/services/vault/NodeVaultServiceTest.kt +++ b/node/src/test/kotlin/net/corda/node/services/vault/NodeVaultServiceTest.kt @@ -4,16 +4,11 @@ import co.paralleluniverse.fibers.Suspendable import com.nhaarman.mockito_kotlin.argThat import com.nhaarman.mockito_kotlin.doNothing import com.nhaarman.mockito_kotlin.whenever -import net.corda.core.contracts.Amount -import net.corda.core.contracts.Issued -import net.corda.core.contracts.StateAndRef -import net.corda.core.contracts.StateRef +import net.corda.core.contracts.* import net.corda.core.crypto.NullKeys +import net.corda.core.crypto.entropyToKeyPair import net.corda.core.crypto.generateKeyPair -import net.corda.core.identity.AbstractParty -import net.corda.core.identity.AnonymousParty -import net.corda.core.identity.Party -import net.corda.core.identity.PartyAndCertificate +import net.corda.core.identity.* import net.corda.core.internal.packageName import net.corda.core.node.StatesToRecord import net.corda.core.node.services.StatesNotAvailableException @@ -31,9 +26,6 @@ import net.corda.core.utilities.OpaqueBytes import net.corda.core.utilities.toNonEmptySet import net.corda.finance.* import net.corda.finance.contracts.asset.Cash -import net.corda.finance.contracts.asset.DUMMY_CASH_ISSUER -import net.corda.finance.contracts.asset.DUMMY_CASH_ISSUER_KEY -import net.corda.finance.contracts.asset.DUMMY_CASH_ISSUER_NAME import net.corda.finance.contracts.getCashBalance import net.corda.finance.schemas.CashSchemaV1 import net.corda.finance.utils.sumCash @@ -51,6 +43,7 @@ import org.junit.Rule import org.junit.Test import rx.observers.TestSubscriber import java.math.BigDecimal +import java.math.BigInteger import java.util.* import java.util.concurrent.CountDownLatch import java.util.concurrent.Executors @@ -59,8 +52,12 @@ import kotlin.test.assertFalse import kotlin.test.assertTrue class NodeVaultServiceTest { - companion object { - private val cordappPackages = listOf("net.corda.finance.contracts.asset", CashSchemaV1::class.packageName) + private companion object { + val cordappPackages = listOf("net.corda.finance.contracts.asset", CashSchemaV1::class.packageName) + val DUMMY_CASH_ISSUER_NAME = CordaX500Name("Snake Oil Issuer", "London", "GB") + val DUMMY_CASH_ISSUER_KEY = entropyToKeyPair(BigInteger.valueOf(10)) + val DUMMY_CASH_ISSUER_IDENTITY = getTestPartyAndCertificate(Party(DUMMY_CASH_ISSUER_NAME, DUMMY_CASH_ISSUER_KEY.public)) + val DUMMY_CASH_ISSUER = DUMMY_CASH_ISSUER_IDENTITY.party.ref(1) } @Rule diff --git a/node/src/test/kotlin/net/corda/node/services/vault/VaultQueryTests.kt b/node/src/test/kotlin/net/corda/node/services/vault/VaultQueryTests.kt index ed558efbe1..33b15435b1 100644 --- a/node/src/test/kotlin/net/corda/node/services/vault/VaultQueryTests.kt +++ b/node/src/test/kotlin/net/corda/node/services/vault/VaultQueryTests.kt @@ -21,9 +21,6 @@ import net.corda.finance.contracts.CommercialPaper import net.corda.finance.contracts.Commodity import net.corda.finance.contracts.DealState import net.corda.finance.contracts.asset.Cash -import net.corda.finance.contracts.asset.DUMMY_CASH_ISSUER -import net.corda.finance.contracts.asset.DUMMY_CASH_ISSUER_KEY -import net.corda.finance.contracts.asset.DUMMY_OBLIGATION_ISSUER import net.corda.finance.schemas.CashSchemaV1 import net.corda.finance.schemas.CashSchemaV1.PersistentCashState import net.corda.finance.schemas.CommercialPaperSchemaV1 @@ -51,6 +48,13 @@ import java.time.temporal.ChronoUnit import java.util.* class VaultQueryTests { + private companion object { + val DUMMY_CASH_ISSUER_KEY = entropyToKeyPair(BigInteger.valueOf(10)) + val DUMMY_CASH_ISSUER_IDENTITY = getTestPartyAndCertificate(Party(CordaX500Name("Snake Oil Issuer", "London", "GB"), DUMMY_CASH_ISSUER_KEY.public)) + val DUMMY_CASH_ISSUER = DUMMY_CASH_ISSUER_IDENTITY.party.ref(1) + val DUMMY_OBLIGATION_ISSUER = Party(CordaX500Name("Snake Oil Issuer", "London", "GB"), entropyToKeyPair(BigInteger.valueOf(10)).public) + } + @Rule @JvmField val testSerialization = SerializationEnvironmentRule() diff --git a/node/src/test/kotlin/net/corda/node/services/vault/VaultWithCashTest.kt b/node/src/test/kotlin/net/corda/node/services/vault/VaultWithCashTest.kt index 451a82d9de..2bed2fd84e 100644 --- a/node/src/test/kotlin/net/corda/node/services/vault/VaultWithCashTest.kt +++ b/node/src/test/kotlin/net/corda/node/services/vault/VaultWithCashTest.kt @@ -1,11 +1,10 @@ package net.corda.node.services.vault -import net.corda.core.contracts.ContractState -import net.corda.core.contracts.InsufficientBalanceException -import net.corda.core.contracts.LinearState -import net.corda.core.contracts.UniqueIdentifier +import net.corda.core.contracts.* +import net.corda.core.crypto.entropyToKeyPair import net.corda.core.crypto.generateKeyPair import net.corda.core.identity.AnonymousParty +import net.corda.core.identity.CordaX500Name import net.corda.core.identity.Party import net.corda.core.internal.concurrent.fork import net.corda.core.internal.concurrent.transpose @@ -19,9 +18,6 @@ import net.corda.core.transactions.TransactionBuilder import net.corda.core.utilities.getOrThrow import net.corda.finance.* import net.corda.finance.contracts.asset.Cash -import net.corda.finance.contracts.asset.DUMMY_CASH_ISSUER -import net.corda.finance.contracts.asset.DUMMY_CASH_ISSUER_KEY -import net.corda.finance.contracts.asset.DUMMY_CASH_ISSUER_NAME import net.corda.finance.contracts.getCashBalance import net.corda.finance.schemas.CashSchemaV1 import net.corda.nodeapi.internal.persistence.CordaPersistence @@ -36,6 +32,7 @@ import org.junit.After import org.junit.Before import org.junit.Rule import org.junit.Test +import java.math.BigInteger import java.util.concurrent.Executors import kotlin.test.assertEquals import kotlin.test.fail @@ -43,8 +40,12 @@ import kotlin.test.fail // TODO: Move this to the cash contract tests once mock services are further split up. class VaultWithCashTest { - companion object { - private val cordappPackages = listOf("net.corda.testing.contracts", "net.corda.finance.contracts.asset", CashSchemaV1::class.packageName) + private companion object { + val cordappPackages = listOf("net.corda.testing.contracts", "net.corda.finance.contracts.asset", CashSchemaV1::class.packageName) + val DUMMY_CASH_ISSUER_NAME = CordaX500Name("Snake Oil Issuer", "London", "GB") + val DUMMY_CASH_ISSUER_KEY = entropyToKeyPair(BigInteger.valueOf(10)) + val DUMMY_CASH_ISSUER_IDENTITY = getTestPartyAndCertificate(Party(DUMMY_CASH_ISSUER_NAME, DUMMY_CASH_ISSUER_KEY.public)) + val DUMMY_CASH_ISSUER = DUMMY_CASH_ISSUER_IDENTITY.party.ref(1) } @Rule diff --git a/samples/irs-demo/cordapp/src/test/kotlin/net/corda/irs/contract/IRSTests.kt b/samples/irs-demo/cordapp/src/test/kotlin/net/corda/irs/contract/IRSTests.kt index b512beba0d..5e0f0971bb 100644 --- a/samples/irs-demo/cordapp/src/test/kotlin/net/corda/irs/contract/IRSTests.kt +++ b/samples/irs-demo/cordapp/src/test/kotlin/net/corda/irs/contract/IRSTests.kt @@ -26,7 +26,6 @@ import net.corda.finance.contracts.Tenor import net.corda.node.services.api.IdentityServiceInternal import net.corda.testing.* import net.corda.testing.node.MockServices -import net.corda.testing.node.makeTestIdentityService import org.junit.Rule import org.junit.Test import java.math.BigDecimal @@ -223,7 +222,12 @@ class IRSTests { private val megaCorpServices = MockServices(listOf("net.corda.irs.contract"), rigorousMock(), MEGA_CORP.name, MEGA_CORP_KEY) private val miniCorpServices = MockServices(listOf("net.corda.irs.contract"), rigorousMock(), MINI_CORP.name, MINI_CORP_KEY) private val notaryServices = MockServices(listOf("net.corda.irs.contract"), rigorousMock(), DUMMY_NOTARY.name, DUMMY_NOTARY_KEY) - private val ledgerServices get() = MockServices(makeTestIdentityService(listOf(MEGA_CORP_IDENTITY, MINI_CORP_IDENTITY, DUMMY_CASH_ISSUER_IDENTITY, DUMMY_NOTARY_IDENTITY)), MEGA_CORP.name) + private val ledgerServices + get() = MockServices(rigorousMock().also { + doReturn(MEGA_CORP).whenever(it).partyFromKey(MEGA_CORP_PUBKEY) + doReturn(null).whenever(it).partyFromKey(ORACLE_PUBKEY) + }, MEGA_CORP.name) + @Test fun ok() { trade().verifies() 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 da67d7a289..ee899499a4 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 @@ -16,7 +16,6 @@ import net.corda.core.node.NodeInfo import net.corda.core.utilities.NetworkHostAndPort import net.corda.core.utilities.OpaqueBytes import net.corda.core.utilities.loggerFor -import net.corda.finance.contracts.asset.DUMMY_CASH_ISSUER import net.corda.node.services.config.configureDevKeyAndTrustStores import net.corda.nodeapi.internal.config.SSLConfiguration import net.corda.nodeapi.internal.crypto.CertificateAndKeyPair @@ -69,8 +68,6 @@ val ORACLE_PUBKEY: PublicKey get() = ORACLE_KEY.public val ALICE_PUBKEY: PublicKey get() = ALICE_KEY.public val BOB_PUBKEY: PublicKey get() = BOB_KEY.public -val CHARLIE_PUBKEY: PublicKey get() = CHARLIE_KEY.public - val MEGA_CORP_IDENTITY: PartyAndCertificate get() = getTestPartyAndCertificate(CordaX500Name(organisation = "MegaCorp", locality = "London", country = "GB"), MEGA_CORP_PUBKEY) val MEGA_CORP: Party get() = MEGA_CORP_IDENTITY.party val MINI_CORP_IDENTITY: PartyAndCertificate get() = getTestPartyAndCertificate(CordaX500Name(organisation = "MiniCorp", locality = "London", country = "GB"), MINI_CORP_PUBKEY) @@ -89,9 +86,6 @@ val BIG_CORP: Party get() = BIG_CORP_IDENTITY.party val BIG_CORP_PARTY_REF = BIG_CORP.ref(OpaqueBytes.of(1)).reference val ALL_TEST_KEYS: List get() = listOf(MEGA_CORP_KEY, MINI_CORP_KEY, ALICE_KEY, BOB_KEY, DUMMY_NOTARY_KEY) - -val DUMMY_CASH_ISSUER_IDENTITY: PartyAndCertificate get() = getTestPartyAndCertificate(DUMMY_CASH_ISSUER.party as Party) - val MOCK_HOST_AND_PORT = NetworkHostAndPort("mockHost", 30000) fun generateStateRef() = StateRef(SecureHash.randomSHA256(), 0) 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 5c8900ebb3..659fb6474a 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 @@ -1,13 +1,14 @@ package net.corda.loadtest.tests import net.corda.client.mock.Generator +import net.corda.core.crypto.entropyToKeyPair import net.corda.core.flows.FinalityFlow import net.corda.core.flows.FlowException +import net.corda.core.identity.CordaX500Name +import net.corda.core.identity.Party import net.corda.core.internal.concurrent.thenMatch import net.corda.core.messaging.startFlow import net.corda.core.transactions.SignedTransaction -import net.corda.finance.contracts.asset.DUMMY_CASH_ISSUER -import net.corda.finance.contracts.asset.DUMMY_CASH_ISSUER_KEY import net.corda.loadtest.LoadTest import net.corda.loadtest.NodeConnection import net.corda.testing.* @@ -15,8 +16,12 @@ import net.corda.testing.contracts.DummyContract import net.corda.testing.node.MockServices import net.corda.testing.node.makeTestIdentityService import org.slf4j.LoggerFactory +import java.math.BigInteger private val log = LoggerFactory.getLogger("NotaryTest") +private val DUMMY_CASH_ISSUER_KEY = entropyToKeyPair(BigInteger.valueOf(10)) +private val DUMMY_CASH_ISSUER_IDENTITY = getTestPartyAndCertificate(Party(CordaX500Name("Snake Oil Issuer", "London", "GB"), DUMMY_CASH_ISSUER_KEY.public)) +private val DUMMY_CASH_ISSUER = DUMMY_CASH_ISSUER_IDENTITY.party.ref(1) data class NotariseCommand(val issueTx: SignedTransaction, val moveTx: SignedTransaction, val node: NodeConnection) From 6dcac410c6d6f1ac82c6a850701dec1abc2a9c67 Mon Sep 17 00:00:00 2001 From: Viktor Kolomeyko Date: Mon, 11 Dec 2017 17:14:57 +0000 Subject: [PATCH 38/42] CORDA-822 - Partially revert changes to ExplorerSimulator done by JMX Jolokia instrumentation (#2197) --- .../src/main/kotlin/net/corda/explorer/ExplorerSimulation.kt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tools/explorer/src/main/kotlin/net/corda/explorer/ExplorerSimulation.kt b/tools/explorer/src/main/kotlin/net/corda/explorer/ExplorerSimulation.kt index ff22278612..8836b2f4bc 100644 --- a/tools/explorer/src/main/kotlin/net/corda/explorer/ExplorerSimulation.kt +++ b/tools/explorer/src/main/kotlin/net/corda/explorer/ExplorerSimulation.kt @@ -67,12 +67,12 @@ class ExplorerSimulation(private val options: OptionSet) { val portAllocation = PortAllocation.Incremental(20000) driver(portAllocation = portAllocation, extraCordappPackagesToScan = listOf("net.corda.finance"), waitForAllNodesToFinish = true, jmxPolicy = JmxPolicy(true)) { // TODO : Supported flow should be exposed somehow from the node instead of set of ServiceInfo. - val alice = startNode(providedName = ALICE.name, rpcUsers = listOf(user), customOverrides = mapOf("devMode" to "true")) + val alice = startNode(providedName = ALICE.name, rpcUsers = listOf(user)) val bob = startNode(providedName = BOB.name, rpcUsers = listOf(user)) val ukBankName = CordaX500Name(organisation = "UK Bank Plc", locality = "London", country = "GB") val usaBankName = CordaX500Name(organisation = "USA Bank Corp", locality = "New York", country = "US") val issuerGBP = startNode(providedName = ukBankName, rpcUsers = listOf(manager), - customOverrides = mapOf("issuableCurrencies" to listOf("GBP"), "" to "true")) + customOverrides = mapOf("issuableCurrencies" to listOf("GBP"))) val issuerUSD = startNode(providedName = usaBankName, rpcUsers = listOf(manager), customOverrides = mapOf("issuableCurrencies" to listOf("USD"))) From 1fb1d6fb72b0ca6608d97d6b4aa3f601463bee4e Mon Sep 17 00:00:00 2001 From: Katelyn Baker Date: Mon, 11 Dec 2017 18:32:12 +0000 Subject: [PATCH 39/42] CORDA-852 - Fix AMQP serialisation of nested generic --- .../carpenter/AMQPSchemaExtensions.kt | 3 +- .../serialization/amqp/GenericsTests.kt | 114 ++++++++++++++++++ 2 files changed, 116 insertions(+), 1 deletion(-) create mode 100644 node-api/src/test/kotlin/net/corda/nodeapi/internal/serialization/amqp/GenericsTests.kt diff --git a/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/carpenter/AMQPSchemaExtensions.kt b/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/carpenter/AMQPSchemaExtensions.kt index 6a0c3c9784..6117695b77 100644 --- a/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/carpenter/AMQPSchemaExtensions.kt +++ b/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/carpenter/AMQPSchemaExtensions.kt @@ -122,7 +122,8 @@ val typeStrToType: Map, Class> = mapOf( fun AMQPField.getTypeAsClass(classloader: ClassLoader) = typeStrToType[Pair(type, mandatory)] ?: when (type) { "string" -> String::class.java - "*" -> classloader.loadClass(requires[0]) + "binary" -> ByteArray::class.java + "*" -> if (requires.isEmpty()) Any::class.java else classloader.loadClass(requires[0]) else -> classloader.loadClass(type) } diff --git a/node-api/src/test/kotlin/net/corda/nodeapi/internal/serialization/amqp/GenericsTests.kt b/node-api/src/test/kotlin/net/corda/nodeapi/internal/serialization/amqp/GenericsTests.kt new file mode 100644 index 0000000000..9882c79b41 --- /dev/null +++ b/node-api/src/test/kotlin/net/corda/nodeapi/internal/serialization/amqp/GenericsTests.kt @@ -0,0 +1,114 @@ +package net.corda.nodeapi.internal.serialization.amqp + +import net.corda.core.serialization.SerializedBytes +import net.corda.nodeapi.internal.serialization.AllWhitelist +import org.junit.Test +import kotlin.test.assertEquals + +class GenericsTests { + + @Test + fun nestedSerializationOfGenerics() { + data class G(val a: T) + data class Wrapper(val a: Int, val b: G) + + val factory = SerializerFactory(AllWhitelist, ClassLoader.getSystemClassLoader()) + val altContextFactory = SerializerFactory(AllWhitelist, ClassLoader.getSystemClassLoader()) + val ser = SerializationOutput(factory) + + val bytes = ser.serializeAndReturnSchema(G("hi")) + + assertEquals("hi", DeserializationInput(factory).deserialize(bytes.obj).a) + assertEquals("hi", DeserializationInput(altContextFactory).deserialize(bytes.obj).a) + + val bytes2 = ser.serializeAndReturnSchema(Wrapper(1, G("hi"))) + + DeserializationInput(factory).deserialize(bytes2.obj).apply { + assertEquals(1, a) + assertEquals("hi", b.a) + } + + DeserializationInput(altContextFactory).deserialize(bytes2.obj).apply { + assertEquals(1, a) + assertEquals("hi", b.a) + } + } + + @Test + fun nestedGenericsReferencesByteArrayViaSerializedBytes() { + data class G(val a : Int) + data class Wrapper(val a: Int, val b: SerializedBytes) + + val factory = SerializerFactory(AllWhitelist, ClassLoader.getSystemClassLoader()) + val factory2 = SerializerFactory(AllWhitelist, ClassLoader.getSystemClassLoader()) + val ser = SerializationOutput(factory) + + val gBytes = ser.serialize(G(1)) + val bytes2 = ser.serializeAndReturnSchema(Wrapper(1, gBytes)) + + DeserializationInput(factory).deserialize(bytes2.obj).apply { + assertEquals(1, a) + assertEquals(1, DeserializationInput(factory).deserialize(b).a) + } + DeserializationInput(factory2).deserialize(bytes2.obj).apply { + assertEquals(1, a) + assertEquals(1, DeserializationInput(factory).deserialize(b).a) + } + } + + @Test + fun nestedSerializationInMultipleContextsDoesntColideGenericTypes() { + data class InnerA(val a_a: Int) + data class InnerB(val a_b: Int) + data class InnerC(val a_c: String) + data class Container(val b: T) + data class Wrapper(val c: Container) + + val factory = SerializerFactory(AllWhitelist, ClassLoader.getSystemClassLoader()) + val factories = listOf(factory, SerializerFactory(AllWhitelist, ClassLoader.getSystemClassLoader())) + val ser = SerializationOutput(factory) + + ser.serialize(Wrapper(Container(InnerA(1)))).apply { + factories.forEach { + DeserializationInput(it).deserialize(this).apply { assertEquals(1, c.b.a_a) } + } + } + + ser.serialize(Wrapper(Container(InnerB(1)))).apply { + factories.forEach { + DeserializationInput(it).deserialize(this).apply { assertEquals(1, c.b.a_b) } + } + } + + ser.serialize(Wrapper(Container(InnerC("Ho ho ho")))).apply { + factories.forEach { + DeserializationInput(it).deserialize(this).apply { assertEquals("Ho ho ho", c.b.a_c) } + } + } + } + + @Test + fun nestedSerializationWhereGenericDoesntImpactFingerprint() { + data class Inner(val a : Int) + data class Container(val b: Inner) + data class Wrapper(val c: Container) + + val factorys = listOf( + SerializerFactory(AllWhitelist, ClassLoader.getSystemClassLoader()), + SerializerFactory(AllWhitelist, ClassLoader.getSystemClassLoader())) + + val ser = SerializationOutput(factorys[0]) + + ser.serialize(Wrapper(Container(Inner(1)))).apply { + factorys.forEach { + assertEquals(1, DeserializationInput(it).deserialize(this).c.b.a) + } + } + + ser.serialize(Wrapper(Container(Inner(1)))).apply { + factorys.forEach { + assertEquals(1, DeserializationInput(it).deserialize(this).c.b.a) + } + } + } +} \ No newline at end of file From 5b12c5177e3d327439490cded0917972da7a0444 Mon Sep 17 00:00:00 2001 From: Shams Asari Date: Mon, 11 Dec 2017 13:07:29 +0000 Subject: [PATCH 40/42] Correct version strings --- build.gradle | 2 +- constants.properties | 2 +- .../net/corda/nodeapi/internal/config/SSLConfiguration.kt | 1 + 3 files changed, 3 insertions(+), 2 deletions(-) diff --git a/build.gradle b/build.gradle index f539d212fe..b9d73bfdf5 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-NETWORKMAP-SNAPSHOT" + ext.corda_release_version = "3.0-SNAPSHOT" // Increment this on any release that changes public APIs anywhere in the Corda platform ext.corda_platform_version = constants.getProperty("platformVersion") ext.gradle_plugins_version = constants.getProperty("gradlePluginsVersion") diff --git a/constants.properties b/constants.properties index 60e52a6b0c..a5b866aef0 100644 --- a/constants.properties +++ b/constants.properties @@ -1,4 +1,4 @@ -gradlePluginsVersion=3.0.2-NETWORKMAP +gradlePluginsVersion=3.0.1 kotlinVersion=1.1.60 platformVersion=2 guavaVersion=21.0 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 b4c8534b93..590b89110d 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,6 @@ interface SSLConfiguration { interface NodeSSLConfiguration : SSLConfiguration { val baseDirectory: Path override val certificatesDirectory: Path get() = baseDirectory / "certificates" + // TODO This will be removed. Instead we will just check against the truststore, which will be provided out-of-band, along with its password val rootCertFile: Path get() = certificatesDirectory / "rootcert.pem" } From 2dc73ecf3b748362d5a0a11baa521c05db49cbfc Mon Sep 17 00:00:00 2001 From: Alberto Arri <30873160+al-r3@users.noreply.github.com> Date: Tue, 12 Dec 2017 10:39:52 +0000 Subject: [PATCH 41/42] Changed logic to check for initial certificate, now using the truststore instead of the .cer file (#2213) --- .../internal/config/SSLConfiguration.kt | 2 - .../registration/NodeRegistrationTest.kt | 26 +++++++- .../registration/NetworkRegistrationHelper.kt | 55 +++++++--------- .../NetworkRegistrationHelperTest.kt | 66 ++++++++----------- .../net/corda/testing/driver/DriverTests.kt | 17 +++++ .../corda/testing/internal/DriverDSLImpl.kt | 10 ++- 6 files changed, 100 insertions(+), 76 deletions(-) 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 590b89110d..90544e92a6 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,6 +15,4 @@ interface SSLConfiguration { interface NodeSSLConfiguration : SSLConfiguration { val baseDirectory: Path override val certificatesDirectory: Path get() = baseDirectory / "certificates" - // TODO This will be removed. Instead we will just check against the truststore, which will be provided out-of-band, along with its password - val rootCertFile: Path get() = certificatesDirectory / "rootcert.pem" } 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 0301c84dd3..6cd0058792 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 @@ -1,9 +1,11 @@ package net.corda.node.utilities.registration +import com.google.common.net.HostAndPort 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.NetworkHostAndPort import net.corda.core.utilities.getOrThrow import net.corda.core.utilities.minutes import net.corda.nodeapi.internal.crypto.CertificateAndKeyPair @@ -18,6 +20,7 @@ import net.corda.testing.driver.PortAllocation import net.corda.testing.internal.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 @@ -41,13 +44,12 @@ class NodeRegistrationTest { private val registrationHandler = RegistrationHandler(rootCertAndKeyPair) private lateinit var server: NetworkMapServer - private lateinit var compatibilityZone: CompatibilityZoneParams + private lateinit var serverHostAndPort: NetworkHostAndPort @Before fun startServer() { server = NetworkMapServer(1.minutes, portAllocation.nextHostAndPort(), registrationHandler) - val address = server.start() - compatibilityZone = CompatibilityZoneParams(URL("http://$address"), rootCert = rootCertAndKeyPair.certificate.cert) + serverHostAndPort = server.start() } @After @@ -59,6 +61,7 @@ class NodeRegistrationTest { // starting a second node hangs so that needs to be fixed. @Test fun `node registration correct root cert`() { + val compatibilityZone = CompatibilityZoneParams(URL("http://$serverHostAndPort"), rootCert = rootCertAndKeyPair.certificate.cert) internalDriver( portAllocation = portAllocation, notarySpecs = emptyList(), @@ -69,6 +72,23 @@ class NodeRegistrationTest { } } + @Test + fun `node registration wrong root cert`() { + val someCert = createSelfKeyAndSelfSignedCertificate().certificate.cert + val compatibilityZone = CompatibilityZoneParams(URL("http://$serverHostAndPort"), rootCert = someCert) + internalDriver( + portAllocation = portAllocation, + notarySpecs = emptyList(), + compatibilityZone = compatibilityZone, + // Changing the content of the truststore makes the node fail in a number of ways if started out process. + startNodesInProcess = true + ) { + assertThatThrownBy { + startNode(providedName = CordaX500Name("Alice", "London", "GB")).getOrThrow() + }.isInstanceOf(WrongRootCertException::class.java) + } + } + private fun createSelfKeyAndSelfSignedCertificate(): CertificateAndKeyPair { val rootCAKey = Crypto.generateKeyPair(X509Utilities.DEFAULT_TLS_SIGNATURE_SCHEME) val rootCACert = X509Utilities.createSelfSignedCACertificate( 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 08808b066f..f24cf56f11 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 @@ -27,18 +27,27 @@ 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) + private val trustStore: KeyStore + private val rootCert: Certificate + + init { + require(config.trustStoreFile.exists()) { + "${config.trustStoreFile} does not exist. This file must contain the root CA cert of your compatibility zone. " + + "Please contact your CZ operator." + } + trustStore = loadKeyStore(config.trustStoreFile, config.trustStorePassword) + val rootCert = trustStore.getCertificate(CORDA_ROOT_CA) + require(rootCert != null) { + "${config.trustStoreFile} does not contain a certificate with the key $CORDA_ROOT_CA." + + "This file must contain the root CA cert of your compatibility zone. " + + "Please contact your CZ operator." + } + this.rootCert = rootCert + } /** * Ensure the initial keystore for a node is set up. @@ -83,18 +92,14 @@ 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, returnedRootCa) - trustStore.save(config.trustStoreFile, config.trustStorePassword) println("Node private key and certificate stored in ${config.nodeKeystore}.") + // Check that the root of the signed certificate matches the expected certificate in the truststore. + if (rootCert != certificates.last()) { + // Assumes certificate chain always starts with client certificate and end with root certificate. + throw WrongRootCertException(rootCert, certificates.last(), config.trustStoreFile) + } + println("Generating SSL certificate for node messaging service.") val sslKey = Crypto.generateKeyPair(X509Utilities.DEFAULT_TLS_SIGNATURE_SCHEME) val caCert = caKeyStore.getX509Certificate(CORDA_CLIENT_CA).toX509CertHolder() @@ -111,16 +116,6 @@ class NetworkRegistrationHelper(private val config: NodeConfiguration, private v } } - /** - * Checks that the passed Certificate is the expected root CA. - * @throws WrongRootCertException if the certificates don't match. - */ - private fun checkReturnedRootCaMatchesExpectedCa(returnedRootCa: Certificate) { - if (rootCert != returnedRootCa) { - throw WrongRootCertException(rootCert, returnedRootCa, config.rootCertFile) - } - } - /** * Poll Certificate Signing Server for approved certificate, * enter a slow polling loop if server return null. @@ -177,7 +172,7 @@ 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. + * This usually means that there has been a Man-in-the-middle attack when contacting the doorman. */ class WrongRootCertException(expected: Certificate, actual: Certificate, @@ -186,5 +181,5 @@ class WrongRootCertException(expected: Certificate, 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 + the expected certificate is stored in: $expectedFilePath with alias $CORDA_ROOT_CA """.trimMargin()) diff --git a/node/src/test/kotlin/net/corda/node/utilities/registration/NetworkRegistrationHelperTest.kt b/node/src/test/kotlin/net/corda/node/utilities/registration/NetworkRegistrationHelperTest.kt index 97a9e2b818..8c76a0ad82 100644 --- a/node/src/test/kotlin/net/corda/node/utilities/registration/NetworkRegistrationHelperTest.kt +++ b/node/src/test/kotlin/net/corda/node/utilities/registration/NetworkRegistrationHelperTest.kt @@ -9,9 +9,8 @@ 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.node.services.config.createKeystoreForCordaNode +import net.corda.nodeapi.internal.crypto.* import net.corda.testing.ALICE import net.corda.testing.rigorousMock import net.corda.testing.testNodeConfiguration @@ -33,6 +32,15 @@ class NetworkRegistrationHelperTest { private val requestId = SecureHash.randomSHA256().toString() private lateinit var config: NodeConfiguration + private val identities = listOf("CORDA_CLIENT_CA", + "CORDA_INTERMEDIATE_CA", + "CORDA_ROOT_CA") + .map { CordaX500Name(commonName = it, organisation = "R3 Ltd", locality = "London", country = "GB") } + private val certs = identities.map { X509Utilities.createSelfSignedCACertificate(it, Crypto.generateKeyPair(X509Utilities.DEFAULT_TLS_SIGNATURE_SCHEME)) } + .map { it.cert }.toTypedArray() + + private val certService = mockRegistrationResponse(*certs) + @Before fun init() { config = testNodeConfiguration(baseDirectory = tempFolder.root.toPath(), myLegalName = ALICE.name) @@ -40,21 +48,13 @@ class NetworkRegistrationHelperTest { @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 = mockRegistrationResponse(*certs) - - config.rootCertFile.parent.createDirectories() - X509Utilities.saveCertificateAsPEMFile(certs.last(), config.rootCertFile) - assertFalse(config.nodeKeystore.exists()) assertFalse(config.sslKeystore.exists()) - assertFalse(config.trustStoreFile.exists()) + config.trustStoreFile.parent.createDirectories() + loadOrCreateKeyStore(config.trustStoreFile, config.trustStorePassword).also { + it.addOrReplaceCertificate(X509Utilities.CORDA_ROOT_CA, certs.last()) + it.save(config.trustStoreFile, config.trustStorePassword) + } NetworkRegistrationHelper(config, certService).buildKeystore() @@ -97,33 +97,21 @@ class NetworkRegistrationHelperTest { } @Test - fun `rootCertFile doesn't exist`() { - val certService = rigorousMock() - + fun `missing truststore`() { assertThatThrownBy { - NetworkRegistrationHelper(config, certService) - }.hasMessageContaining(config.rootCertFile.toString()) + NetworkRegistrationHelper(config, certService).buildKeystore() + }.hasMessageContaining("This file must contain the root CA cert of your compatibility zone. Please contact your CZ operator.") + .isInstanceOf(IllegalArgumentException::class.java) } @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 - ) - + fun `wrong root cert in truststore`() { + val someCert = X509Utilities.createSelfSignedCACertificate(CordaX500Name("Foo", "MU", "GB"), Crypto.generateKeyPair(X509Utilities.DEFAULT_TLS_SIGNATURE_SCHEME)).cert + config.trustStoreFile.parent.createDirectories() + loadOrCreateKeyStore(config.trustStoreFile, config.trustStorePassword).also { + it.addOrReplaceCertificate(X509Utilities.CORDA_ROOT_CA, someCert) + it.save(config.trustStoreFile, config.trustStorePassword) + } assertThatThrownBy { NetworkRegistrationHelper(config, certService).buildKeystore() }.isInstanceOf(WrongRootCertException::class.java) 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 89f8076973..a429d8f204 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,28 +1,45 @@ package net.corda.testing.driver import net.corda.core.concurrent.CordaFuture +import net.corda.core.internal.copyTo import net.corda.core.internal.div import net.corda.core.internal.list import net.corda.core.internal.readLines import net.corda.core.utilities.NetworkHostAndPort import net.corda.core.utilities.getOrThrow +import net.corda.core.utilities.seconds import net.corda.node.internal.NodeStartup +import net.corda.nodeapi.internal.crypto.X509Utilities +import net.corda.nodeapi.internal.crypto.getX509Certificate +import net.corda.nodeapi.internal.crypto.loadOrCreateKeyStore 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.http.HttpApi +import net.corda.testing.internal.CompatibilityZoneParams 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 net.corda.testing.node.network.NetworkMapServer import org.assertj.core.api.Assertions.assertThat import org.json.simple.JSONObject +import org.junit.Rule import org.junit.Test +import org.junit.rules.TemporaryFolder +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 { private val executorService: ScheduledExecutorService = Executors.newScheduledThreadPool(2) 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 8ecb8cef13..09ad8aa7ac 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 @@ -39,6 +39,9 @@ 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.nodeapi.internal.crypto.addOrReplaceCertificate +import net.corda.nodeapi.internal.crypto.loadOrCreateKeyStore +import net.corda.nodeapi.internal.crypto.save import net.corda.testing.ALICE import net.corda.testing.BOB import net.corda.testing.DUMMY_BANK_A @@ -220,8 +223,11 @@ class DriverDSLImpl( ) val configuration = config.parseAsNodeConfiguration() - configuration.rootCertFile.parent.createDirectories() - X509Utilities.saveCertificateAsPEMFile(rootCert, configuration.rootCertFile) + configuration.trustStoreFile.parent.createDirectories() + loadOrCreateKeyStore(configuration.trustStoreFile, configuration.trustStorePassword).also { + it.addOrReplaceCertificate(X509Utilities.CORDA_ROOT_CA, rootCert) + it.save(configuration.trustStoreFile, configuration.trustStorePassword) + } return if (startNodesInProcess) { // This is a bit cheating, we're not starting a full node, we're just calling the code nodes call From 91f7dbe658483c6e1fb58568a30e0d2f9f68aad6 Mon Sep 17 00:00:00 2001 From: Joel Dudley Date: Tue, 12 Dec 2017 10:42:48 +0000 Subject: [PATCH 42/42] Removes R3 references from the docs. --- docs/source/CLI-vs-IDE.rst | 10 +++++----- docs/source/codestyle.rst | 2 +- .../example-code/src/main/resources/example-node.conf | 3 +-- docs/source/glossary.rst | 2 -- 4 files changed, 7 insertions(+), 10 deletions(-) diff --git a/docs/source/CLI-vs-IDE.rst b/docs/source/CLI-vs-IDE.rst index 7da89e8cb1..a4888a7adb 100644 --- a/docs/source/CLI-vs-IDE.rst +++ b/docs/source/CLI-vs-IDE.rst @@ -9,11 +9,11 @@ a developer environment. IDE - IntelliJ -------------- -IntelliJ (R3's preferred IDE) integrates well with gradle (our chosen build, deployment and CLI tool). IntelliJ understands gradle -tasks and dependencies, automatically loading them in the background when a project is first opened or the gradle -project changes. Occasionally, however, you may need to refresh the gradle project manually - but this is hinted to you -by the IDE. It's a good idea to do this before carrying on with other work (and in fact you may find it is essential to pick -up new libraries, etc.). +IntelliJ (the preferred IDE for Corda) integrates well with gradle (Corda's default build, deployment and CLI tool). +IntelliJ understands gradle tasks and dependencies, automatically loading them in the background when a project is +first opened or the gradle project changes. Occasionally, however, you may need to refresh the gradle project manually +- but this is hinted to you by the IDE. It's a good idea to do this before carrying on with other work (and in fact you +may find it is essential to pick up new libraries, etc.). There are some great resources about how to get started using IntelliJ. As opposed to trying to repeat them here, we advise you to go to the `IntelliJ docs here `_. diff --git a/docs/source/codestyle.rst b/docs/source/codestyle.rst index 930e7f9868..71388c5e0d 100644 --- a/docs/source/codestyle.rst +++ b/docs/source/codestyle.rst @@ -1,7 +1,7 @@ Code style guide ================ -This document explains the coding style used in the R3 prototyping repository. You will be expected to follow these +This document explains the coding style used in the Corda repository. You will be expected to follow these recommendations when submitting patches for review. Please take the time to read them and internalise them, to save time during code review. 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 08066a7964..8ae9a42475 100644 --- a/docs/source/example-code/src/main/resources/example-node.conf +++ b/docs/source/example-code/src/main/resources/example-node.conf @@ -15,5 +15,4 @@ rpcUsers : [ { username=user1, password=letmein, permissions=[ StartProtocol.net.corda.protocols.CashProtocol ] } ] devMode : true -// Certificate signing service will be hosted by R3 in the near future. -//certificateSigningService : "https://testnet.certificate.corda.net" +// certificateSigningService : "https://testnet.certificate.corda.net" diff --git a/docs/source/glossary.rst b/docs/source/glossary.rst index 05d5211fa6..4432ddb2a2 100644 --- a/docs/source/glossary.rst +++ b/docs/source/glossary.rst @@ -53,8 +53,6 @@ Protocol The old name for a Corda "Flow" Quasar A library that provides performant lightweight threads that can be suspended and restored extremely quickly. -R3 - The consortium behind Corda SIMM Standard Initial Margin Model. A way of determining a counterparty's margin payment to another counterparty based on a collection of trades such that, in the event of default, the receiving counterparty has limited exposure. Serialization