From 99e314d017a26099156b1e14db58185e095c833e Mon Sep 17 00:00:00 2001 From: Chris Rankin Date: Wed, 27 Jun 2018 14:01:49 +0100 Subject: [PATCH 01/10] ENT-2145: Configure DemoBench nodes to issue selected currencies. (#3452) --- tools/demobench/build.gradle | 2 +- .../net/corda/demobench/model/NodeConfig.kt | 65 +++++----- .../corda/demobench/model/NodeConfigTest.kt | 114 +++++++++++------- 3 files changed, 108 insertions(+), 73 deletions(-) diff --git a/tools/demobench/build.gradle b/tools/demobench/build.gradle index 45d4c21292..9083d042b0 100644 --- a/tools/demobench/build.gradle +++ b/tools/demobench/build.gradle @@ -36,8 +36,8 @@ repositories { flatDir { dirs 'libs' } + jcenter() maven { - jcenter() url 'http://www.sparetimelabs.com/maven2' } } diff --git a/tools/demobench/src/main/kotlin/net/corda/demobench/model/NodeConfig.kt b/tools/demobench/src/main/kotlin/net/corda/demobench/model/NodeConfig.kt index fbe58670ad..0270d12cfb 100644 --- a/tools/demobench/src/main/kotlin/net/corda/demobench/model/NodeConfig.kt +++ b/tools/demobench/src/main/kotlin/net/corda/demobench/model/NodeConfig.kt @@ -1,9 +1,7 @@ package net.corda.demobench.model -import com.typesafe.config.Config +import com.typesafe.config.* import com.typesafe.config.ConfigFactory.empty -import com.typesafe.config.ConfigRenderOptions -import com.typesafe.config.ConfigValueFactory import net.corda.core.identity.CordaX500Name import net.corda.core.internal.copyToDirectory import net.corda.core.internal.createDirectories @@ -45,13 +43,22 @@ data class NodeConfig( } fun nodeConf(): Config { - - val basic = NodeConfigurationData(myLegalName, p2pAddress, rpcAddress, notary, h2port, rpcUsers, useTestClock, detectPublicIp, devMode).toConfig() - val rpcSettings = empty() - .withValue("address", ConfigValueFactory.fromAnyRef(rpcAddress.toString())) - .withValue("adminAddress", ConfigValueFactory.fromAnyRef(rpcAdminAddress.toString())) - .root() - return basic.withoutPath("rpcAddress").withoutPath("rpcAdminAddress").withValue("rpcSettings", rpcSettings) + val rpcSettings: ConfigObject = empty() + .withValue("address", valueFor(rpcAddress.toString())) + .withValue("adminAddress", valueFor(rpcAdminAddress.toString())) + .root() + val customMap: Map = HashMap().also { + if (issuableCurrencies.isNotEmpty()) { + it["issuableCurrencies"] = issuableCurrencies + } + } + val custom: ConfigObject = ConfigFactory.parseMap(customMap).root() + return NodeConfigurationData(myLegalName, p2pAddress, rpcAddress, notary, h2port, rpcUsers, useTestClock, detectPublicIp, devMode) + .toConfig() + .withoutPath("rpcAddress") + .withoutPath("rpcAdminAddress") + .withValue("rpcSettings", rpcSettings) + .withOptionalValue("custom", custom) } fun webServerConf() = WebServerConfigurationData(myLegalName, rpcAddress, webAddress, rpcUsers).asConfig() @@ -60,33 +67,29 @@ data class NodeConfig( fun toWebServerConfText() = webServerConf().render() - fun serialiseAsString(): String { - - return toConfig().render() - } + fun serialiseAsString(): String = toConfig().render() private fun Config.render(): String = root().render(renderOptions) } private data class NodeConfigurationData( - val myLegalName: CordaX500Name, - val p2pAddress: NetworkHostAndPort, - val rpcAddress: NetworkHostAndPort, - val notary: NotaryService?, - val h2port: Int, - val rpcUsers: List = listOf(NodeConfig.defaultUser), - val useTestClock: Boolean, - val detectPublicIp: Boolean, - val devMode: Boolean + val myLegalName: CordaX500Name, + val p2pAddress: NetworkHostAndPort, + val rpcAddress: NetworkHostAndPort, + val notary: NotaryService?, + val h2port: Int, + val rpcUsers: List = listOf(NodeConfig.defaultUser), + val useTestClock: Boolean, + val detectPublicIp: Boolean, + val devMode: Boolean ) private data class WebServerConfigurationData( - val myLegalName: CordaX500Name, - val rpcAddress: NetworkHostAndPort, - val webAddress: NetworkHostAndPort, - val rpcUsers: List + val myLegalName: CordaX500Name, + val rpcAddress: NetworkHostAndPort, + val webAddress: NetworkHostAndPort, + val rpcUsers: List ) { - fun asConfig() = toConfig() } @@ -117,3 +120,9 @@ data class NodeConfigWrapper(val baseDir: Path, val nodeConfig: NodeConfig) : Ha fun user(name: String) = User(name, "letmein", setOf("ALL")) fun String.toKey() = filter { !it.isWhitespace() }.toLowerCase() + +fun valueFor(any: T): ConfigValue = ConfigValueFactory.fromAnyRef(any) + +private fun Config.withOptionalValue(path: String, obj: ConfigObject): Config { + return if (obj.isEmpty()) this else this.withValue(path, obj) +} diff --git a/tools/demobench/src/test/kotlin/net/corda/demobench/model/NodeConfigTest.kt b/tools/demobench/src/test/kotlin/net/corda/demobench/model/NodeConfigTest.kt index 2eea77fc60..d85acad092 100644 --- a/tools/demobench/src/test/kotlin/net/corda/demobench/model/NodeConfigTest.kt +++ b/tools/demobench/src/test/kotlin/net/corda/demobench/model/NodeConfigTest.kt @@ -1,19 +1,16 @@ package net.corda.demobench.model +import com.typesafe.config.ConfigException import com.typesafe.config.ConfigFactory -import com.typesafe.config.ConfigValueFactory import net.corda.core.identity.CordaX500Name import net.corda.core.utilities.NetworkHostAndPort import net.corda.node.services.config.parseAsNodeConfiguration import net.corda.nodeapi.internal.config.User import net.corda.webserver.WebServerConfig -import org.assertj.core.api.Assertions.assertThat import org.junit.Test import java.nio.file.Path import java.nio.file.Paths -import kotlin.test.assertEquals -import kotlin.test.assertFalse -import kotlin.test.assertTrue +import kotlin.test.* class NodeConfigTest { companion object { @@ -24,23 +21,26 @@ class NodeConfigTest { @Test fun `reading node configuration`() { val config = createConfig( - legalName = myLegalName, - p2pPort = 10001, - rpcPort = 40002, - rpcAdminPort = 40005, - webPort = 20001, - h2port = 30001, - notary = NotaryService(validating = false), - users = listOf(user("jenny")) + legalName = myLegalName, + p2pPort = 10001, + rpcPort = 40002, + rpcAdminPort = 40005, + webPort = 20001, + h2port = 30001, + notary = NotaryService(validating = false), + users = listOf(user("jenny")) ) val nodeConfig = config.nodeConf() - .withValue("baseDirectory", ConfigValueFactory.fromAnyRef(baseDir.toString())) - .withFallback(ConfigFactory.parseResources("reference.conf")) - .withFallback(ConfigFactory.parseMap(mapOf("devMode" to true))) - .resolve() + .withValue("baseDirectory", valueFor(baseDir.toString())) + .withFallback(ConfigFactory.parseResources("reference.conf")) + .withFallback(ConfigFactory.parseMap(mapOf("devMode" to true))) + .resolve() val fullConfig = nodeConfig.parseAsNodeConfiguration() + // No custom configuration is created by default. + assertFailsWith { nodeConfig.getConfig("custom") } + assertEquals(myLegalName, fullConfig.myLegalName) assertEquals(localPort(40002), fullConfig.rpcOptions.address) assertEquals(localPort(10001), fullConfig.p2pAddress) @@ -49,25 +49,49 @@ class NodeConfigTest { assertFalse(fullConfig.detectPublicIp) } + @Test + fun `reading node configuration with currencies`() { + val config = createConfig( + legalName = myLegalName, + p2pPort = 10001, + rpcPort = 10002, + rpcAdminPort = 10003, + webPort = 10004, + h2port = 10005, + notary = NotaryService(validating = false), + issuableCurrencies = listOf("GBP") + ) + + val nodeConfig = config.nodeConf() + .withValue("baseDirectory", valueFor(baseDir.toString())) + .withFallback(ConfigFactory.parseResources("reference.conf")) + .resolve() + val custom = nodeConfig.getConfig("custom") + assertEquals(listOf("GBP"), custom.getAnyRefList("issuableCurrencies")) + } + @Test fun `reading webserver configuration`() { val config = createConfig( - legalName = myLegalName, - p2pPort = 10001, - rpcPort = 40002, - rpcAdminPort = 40003, - webPort = 20001, - h2port = 30001, - notary = NotaryService(validating = false), - users = listOf(user("jenny")) + legalName = myLegalName, + p2pPort = 10001, + rpcPort = 40002, + rpcAdminPort = 40003, + webPort = 20001, + h2port = 30001, + notary = NotaryService(validating = false), + users = listOf(user("jenny")) ) val nodeConfig = config.webServerConf() - .withValue("baseDirectory", ConfigValueFactory.fromAnyRef(baseDir.toString())) - .withFallback(ConfigFactory.parseResources("web-reference.conf")) - .resolve() + .withValue("baseDirectory", valueFor(baseDir.toString())) + .withFallback(ConfigFactory.parseResources("web-reference.conf")) + .resolve() val webConfig = WebServerConfig(baseDir, nodeConfig) + // No custom configuration is created by default. + assertFailsWith { nodeConfig.getConfig("custom") } + assertEquals(localPort(20001), webConfig.webAddress) assertEquals(localPort(40002), webConfig.rpcAddress) assertEquals("trustpass", webConfig.trustStorePassword) @@ -75,24 +99,26 @@ class NodeConfigTest { } private fun createConfig( - legalName: CordaX500Name = CordaX500Name(organisation = "Unknown", locality = "Nowhere", country = "GB"), - p2pPort: Int = -1, - rpcPort: Int = -1, - rpcAdminPort: Int = -1, - webPort: Int = -1, - h2port: Int = -1, - notary: NotaryService?, - users: List = listOf(user("guest")) + legalName: CordaX500Name = CordaX500Name(organisation = "Unknown", locality = "Nowhere", country = "GB"), + p2pPort: Int = -1, + rpcPort: Int = -1, + rpcAdminPort: Int = -1, + webPort: Int = -1, + h2port: Int = -1, + notary: NotaryService?, + users: List = listOf(user("guest")), + issuableCurrencies: List = emptyList() ): NodeConfig { return NodeConfig( - myLegalName = legalName, - p2pAddress = localPort(p2pPort), - rpcAddress = localPort(rpcPort), - rpcAdminAddress = localPort(rpcAdminPort), - webAddress = localPort(webPort), - h2port = h2port, - notary = notary, - rpcUsers = users + myLegalName = legalName, + p2pAddress = localPort(p2pPort), + rpcAddress = localPort(rpcPort), + rpcAdminAddress = localPort(rpcAdminPort), + webAddress = localPort(webPort), + h2port = h2port, + notary = notary, + rpcUsers = users, + issuableCurrencies = issuableCurrencies ) } From 8c956c004f9637ec1db3e6760381e20754301abc Mon Sep 17 00:00:00 2001 From: Joel Dudley Date: Wed, 27 Jun 2018 15:29:57 +0100 Subject: [PATCH 02/10] Removes links to Corda source and templates (#3456) --- docs/source/getting-set-up.rst | 32 ++++++-------------------------- 1 file changed, 6 insertions(+), 26 deletions(-) diff --git a/docs/source/getting-set-up.rst b/docs/source/getting-set-up.rst index d08b349e21..b9848e506e 100644 --- a/docs/source/getting-set-up.rst +++ b/docs/source/getting-set-up.rst @@ -75,12 +75,12 @@ IntelliJ Download a sample project ^^^^^^^^^^^^^^^^^^^^^^^^^ 1. Open a command prompt -2. Clone the CorDapp example repo by running ``git clone https://github.com/corda/cordapp-example`` +2. Clone the ``cordapp-example`` repo by running ``git clone https://github.com/corda/cordapp-example`` 3. Move into the ``cordapp-example`` folder by running ``cd cordapp-example`` Run from the command prompt ^^^^^^^^^^^^^^^^^^^^^^^^^^^ -1. From the cordapp-example folder, deploy the nodes by running ``gradlew deployNodes`` +1. From the ``cordapp-example`` folder, deploy the nodes by running ``gradlew deployNodes`` 2. Start the nodes by running ``call kotlin-source/build/nodes/runnodes.bat`` 3. Wait until all the terminal windows display either "Webserver started up in XX.X sec" or "Node for "NodeC" started up and registered in XX.XX sec" 4. Test the CorDapp is running correctly by visiting the front end at http://localhost:10009/web/example/ @@ -129,12 +129,12 @@ IntelliJ Download a sample project ^^^^^^^^^^^^^^^^^^^^^^^^^ 1. Open a terminal -2. Clone the CorDapp example repo by running ``git clone https://github.com/corda/cordapp-example`` +2. Clone the ``cordapp-example`` repo by running ``git clone https://github.com/corda/cordapp-example`` 3. Move into the ``cordapp-example`` folder by running ``cd cordapp-example`` Run from the terminal ^^^^^^^^^^^^^^^^^^^^^ -1. From the cordapp-example folder, deploy the nodes by running ``./gradlew deployNodes`` +1. From the ``cordapp-example`` folder, deploy the nodes by running ``./gradlew deployNodes`` 2. Start the nodes by running ``kotlin-source/build/nodes/runnodes``. Do not click while 7 additional terminal windows start up. 3. Wait until all the terminal windows display either "Webserver started up in XX.X sec" or "Node for "NodeC" started up and registered in XX.XX sec" 4. Test the CorDapp is running correctly by visiting the front end at http://localhost:10009/web/example/ @@ -159,31 +159,11 @@ Run from IntelliJ 7. Wait until the run windows displays the message ``Webserver started up in XX.X sec`` 8. Test the CorDapp is running correctly by visiting the front end at http://localhost:10009/web/example/ -Corda source code ------------------ - -The Corda platform source code is available here: - - https://github.com/corda/corda.git - -A CorDapp template that you can use as the basis for your own CorDapps is available in both Java and Kotlin versions: - - https://github.com/corda/cordapp-template-java.git - - https://github.com/corda/cordapp-template-kotlin.git - -And a list of simple sample CorDapps for you to explore basic concepts is available here: - - https://www.corda.net/samples/ - -You can clone these repos to your local machine by running the command ``git clone [repo URL]``. - Next steps ---------- -The best way to check that everything is working fine is by taking a deeper look at the -:doc:`example CorDapp `. +First, explore the example CorDapp you just ran :doc:`here `. -Next, you should read through :doc:`Corda Key Concepts ` to understand how Corda works. +Next, read through :doc:`Corda Key Concepts ` to understand how Corda works. By then, you'll be ready to start writing your own CorDapps. Learn how to do this in the :doc:`Hello, World tutorial `. You may want to refer to the API documentation, the From 4cc4e3f01bd95c9454b3487d6e1f29b53aace077 Mon Sep 17 00:00:00 2001 From: Viktor Kolomeyko Date: Wed, 27 Jun 2018 16:45:39 +0100 Subject: [PATCH 03/10] Fix DemoBench Windows installer (#3451) Specify paths to build tools correctly or else bugfixes will not applied. --- tools/demobench/package-demobench-exe.bat | 2 +- tools/demobench/package/bugfixes/apply.bat | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/tools/demobench/package-demobench-exe.bat b/tools/demobench/package-demobench-exe.bat index c2f0b41e80..5cbfd851a6 100644 --- a/tools/demobench/package-demobench-exe.bat +++ b/tools/demobench/package-demobench-exe.bat @@ -11,7 +11,7 @@ if "%DIRNAME%" == "" set DIRNAME=. call %DIRNAME%\..\..\gradlew -PpackageType=exe javapackage %* if ERRORLEVEL 1 goto Fail @echo -@echo Wrote installer to %DIRNAME%\build\javapackage\bundles\ +@echo Wrote installer to %DIRNAME%build\javapackage\bundles\ @echo goto end diff --git a/tools/demobench/package/bugfixes/apply.bat b/tools/demobench/package/bugfixes/apply.bat index 747dc67852..ba500032c1 100644 --- a/tools/demobench/package/bugfixes/apply.bat +++ b/tools/demobench/package/bugfixes/apply.bat @@ -29,14 +29,14 @@ if exist "%BUILDDIR%" rmdir /s /q "%BUILDDIR%" mkdir "%BUILDDIR%" for /r "%SOURCEDIR%" %%j in (*.java) do ( - javac -O -d "%BUILDDIR%" "%%j" + "%JAVA_HOME%\bin\javac" -O -d "%BUILDDIR%" "%%j" if ERRORLEVEL 1 ( @echo "Failed to compile %%j" exit /b 1 ) ) -jar uvf %1 -C "%BUILDDIR%" . +"%JAVA_HOME%\bin\jar" uvf %1 -C "%BUILDDIR%" . if ERRORLEVEL 1 ( @echo "Failed to update %1" exit /b 1 From f51407cd5dcf1784af74a1c8e6e7acb21c60d079 Mon Sep 17 00:00:00 2001 From: Christian Sailer Date: Wed, 27 Jun 2018 16:48:46 +0100 Subject: [PATCH 04/10] Add comment about schema search path (#3444) --- docs/source/node-database.rst | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/docs/source/node-database.rst b/docs/source/node-database.rst index 6aeca342d4..94eed5454b 100644 --- a/docs/source/node-database.rst +++ b/docs/source/node-database.rst @@ -65,3 +65,8 @@ Note that: * The ``database.schema`` property is optional * The value of ``database.schema`` is not wrapped in double quotes and Postgres always treats it as a lower-case value (e.g. ``AliceCorp`` becomes ``alicecorp``) +* If you provide a custom ``database.schema``, its value must either match the ``dataSource.user`` value to end up + on the standard schema search path according to the + `PostgreSQL documentation `_, or + the schema search path must be set explicitly for the user. + From eee2563bfafecbcda8c53563983d8a938511d6d7 Mon Sep 17 00:00:00 2001 From: Shams Asari Date: Wed, 27 Jun 2018 17:02:35 +0100 Subject: [PATCH 05/10] CORDA-1660: Wiring up the CordaRPCClient class loader to the p2p serialisation context. (#3454) This is to allow the standalone shell to be able to receive WireTransactions containing Cash.State objects. --- .../corda/client/rpc/CordaRPCClientTest.kt | 104 ++++++++++++++---- .../net/corda/client/rpc/CordaRPCClient.kt | 2 +- .../amqp/AMQPClientSerializationScheme.kt | 20 ++-- .../corda/core/contracts/TransactionState.kt | 5 +- .../internal/amqp/SerializerFactory.kt | 4 +- .../testing/node/internal/ProcessUtilities.kt | 5 +- .../net/corda/smoketesting/NodeProcess.kt | 8 +- .../common/internal/TestCommonUtils.kt | 10 ++ .../net/corda/tools/shell/InteractiveShell.kt | 23 +--- 9 files changed, 119 insertions(+), 62 deletions(-) create mode 100644 testing/test-common/src/main/kotlin/net/corda/testing/common/internal/TestCommonUtils.kt diff --git a/client/rpc/src/integration-test/kotlin/net/corda/client/rpc/CordaRPCClientTest.kt b/client/rpc/src/integration-test/kotlin/net/corda/client/rpc/CordaRPCClientTest.kt index 8c00a925ee..2e6996978b 100644 --- a/client/rpc/src/integration-test/kotlin/net/corda/client/rpc/CordaRPCClientTest.kt +++ b/client/rpc/src/integration-test/kotlin/net/corda/client/rpc/CordaRPCClientTest.kt @@ -1,26 +1,33 @@ package net.corda.client.rpc +import net.corda.client.rpc.internal.createCordaRPCClientWithSslAndClassLoader import net.corda.core.context.* +import net.corda.core.contracts.FungibleAsset import net.corda.core.crypto.random63BitValue import net.corda.core.identity.Party import net.corda.core.internal.concurrent.flatMap -import net.corda.core.internal.packageName +import net.corda.core.internal.location +import net.corda.core.internal.toPath import net.corda.core.messaging.* +import net.corda.core.utilities.NetworkHostAndPort import net.corda.core.utilities.OpaqueBytes import net.corda.core.utilities.getOrThrow import net.corda.finance.DOLLARS +import net.corda.finance.POUNDS import net.corda.finance.USD +import net.corda.finance.contracts.asset.Cash import net.corda.finance.contracts.getCashBalance import net.corda.finance.contracts.getCashBalances import net.corda.finance.flows.CashIssueFlow import net.corda.finance.flows.CashPaymentFlow -import net.corda.finance.schemas.CashSchemaV1 import net.corda.node.internal.Node import net.corda.node.internal.StartedNode import net.corda.node.services.Permissions.Companion.all +import net.corda.testing.common.internal.checkNotOnClasspath import net.corda.testing.core.* import net.corda.testing.node.User import net.corda.testing.node.internal.NodeBasedTest +import net.corda.testing.node.internal.ProcessUtilities import org.apache.activemq.artemis.api.core.ActiveMQSecurityException import org.assertj.core.api.Assertions.assertThat import org.assertj.core.api.Assertions.assertThatExceptionOfType @@ -28,6 +35,10 @@ import org.junit.After import org.junit.Before import org.junit.Test import rx.subjects.PublishSubject +import java.io.File.pathSeparator +import java.net.URLClassLoader +import java.nio.file.Paths +import java.util.* import java.util.concurrent.CountDownLatch import java.util.concurrent.Executors import java.util.concurrent.ScheduledExecutorService @@ -36,9 +47,11 @@ import kotlin.test.assertEquals import kotlin.test.assertFalse import kotlin.test.assertTrue -class CordaRPCClientTest : NodeBasedTest(listOf("net.corda.finance.contracts", CashSchemaV1::class.packageName)) { - private val rpcUser = User("user1", "test", permissions = setOf(all()) - ) +class CordaRPCClientTest : NodeBasedTest(listOf("net.corda.finance")) { + companion object { + val rpcUser = User("user1", "test", permissions = setOf(all())) + } + private lateinit var node: StartedNode private lateinit var identity: Party private lateinit var client: CordaRPCClient @@ -51,7 +64,7 @@ class CordaRPCClientTest : NodeBasedTest(listOf("net.corda.finance.contracts", C @Before fun setUp() { node = startNode(ALICE_NAME, rpcUsers = listOf(rpcUser)) - client = CordaRPCClient(node.internals.configuration.rpcOptions.address!!, CordaRPCClientConfiguration.DEFAULT.copy( + client = CordaRPCClient(node.internals.configuration.rpcOptions.address, CordaRPCClientConfiguration.DEFAULT.copy( maxReconnectAttempts = 5 )) identity = node.info.identityFromX500Name(ALICE_NAME) @@ -83,7 +96,6 @@ class CordaRPCClientTest : NodeBasedTest(listOf("net.corda.finance.contracts", C @Test fun `shutdown command stops the node`() { - val nodeIsShut: PublishSubject = PublishSubject.create() val latch = CountDownLatch(1) var successful = false @@ -130,7 +142,6 @@ class CordaRPCClientTest : NodeBasedTest(listOf("net.corda.finance.contracts", C } private class CloseableExecutor(private val delegate: ScheduledExecutorService) : AutoCloseable, ScheduledExecutorService by delegate { - override fun close() { delegate.shutdown() } @@ -209,19 +220,70 @@ class CordaRPCClientTest : NodeBasedTest(listOf("net.corda.finance.contracts", C ) } } -} -private fun checkShellNotification(info: StateMachineInfo) { - val context = info.invocationContext - assertThat(context.origin).isInstanceOf(InvocationOrigin.Shell::class.java) -} + // WireTransaction stores its components as blobs which are deserialised in its constructor. This test makes sure + // the extra class loader given to the CordaRPCClient is used in this deserialisation, as otherwise any WireTransaction + // containing Cash.State objects are not receivable by the client. + // + // We run the client in a separate process, without the finance module on its system classpath to ensure that the + // additional class loader that we give it is used. Cash.State objects are used as they can't be synthesised fully + // by the carpenter, and thus avoiding any false-positive results. + @Test + fun `additional class loader used by WireTransaction when it deserialises its components`() { + val financeLocation = Cash::class.java.location.toPath().toString() + val classpathWithoutFinance = ProcessUtilities.defaultClassPath + .split(pathSeparator) + .filter { financeLocation !in it } + .joinToString(pathSeparator) -private fun checkRpcNotification(info: StateMachineInfo, rpcUsername: String, historicalIds: MutableSet, externalTrace: Trace?, impersonatedActor: Actor?) { - val context = info.invocationContext - assertThat(context.origin).isInstanceOf(InvocationOrigin.RPC::class.java) - assertThat(context.externalTrace).isEqualTo(externalTrace) - assertThat(context.impersonatedActor).isEqualTo(impersonatedActor) - assertThat(context.actor?.id?.value).isEqualTo(rpcUsername) - assertThat(historicalIds).doesNotContain(context.trace.invocationId) - historicalIds.add(context.trace.invocationId) + // Create a Cash.State object for the StandaloneCashRpcClient to get + node.services.startFlow(CashIssueFlow(100.POUNDS, OpaqueBytes.of(1), identity), InvocationContext.shell()) + val outOfProcessRpc = ProcessUtilities.startJavaProcess( + classpath = classpathWithoutFinance, + arguments = listOf(node.internals.configuration.rpcOptions.address.toString(), financeLocation) + ) + assertThat(outOfProcessRpc.waitFor()).isZero() // i.e. no exceptions were thrown + } + + private fun checkShellNotification(info: StateMachineInfo) { + val context = info.invocationContext + assertThat(context.origin).isInstanceOf(InvocationOrigin.Shell::class.java) + } + + private fun checkRpcNotification(info: StateMachineInfo, + rpcUsername: String, + historicalIds: MutableSet, + externalTrace: Trace?, + impersonatedActor: Actor?) { + val context = info.invocationContext + assertThat(context.origin).isInstanceOf(InvocationOrigin.RPC::class.java) + assertThat(context.externalTrace).isEqualTo(externalTrace) + assertThat(context.impersonatedActor).isEqualTo(impersonatedActor) + assertThat(context.actor?.id?.value).isEqualTo(rpcUsername) + assertThat(historicalIds).doesNotContain(context.trace.invocationId) + historicalIds.add(context.trace.invocationId) + } + + private object StandaloneCashRpcClient { + @JvmStatic + fun main(args: Array) { + checkNotOnClasspath("net.corda.finance.contracts.asset.Cash") { + "The finance module cannot be on the system classpath" + } + val address = NetworkHostAndPort.parse(args[0]) + val financeClassLoader = URLClassLoader(arrayOf(Paths.get(args[1]).toUri().toURL())) + val rpcUser = CordaRPCClientTest.rpcUser + val client = createCordaRPCClientWithSslAndClassLoader(address, classLoader = financeClassLoader) + val state = client.use(rpcUser.username, rpcUser.password) { + // financeClassLoader should be allowing the Cash.State to materialise + @Suppress("DEPRECATION") + it.proxy.internalVerifiedTransactionsSnapshot()[0].tx.outputsOfType>()[0] + } + assertThat(state.javaClass.name).isEqualTo("net.corda.finance.contracts.asset.Cash${'$'}State") + assertThat(state.amount.quantity).isEqualTo(10000) + assertThat(state.amount.token.product).isEqualTo(Currency.getInstance("GBP")) + // This particular check assures us that the Cash.State that we have hasn't been carpented. + assertThat(state.participants).isEqualTo(listOf(state.owner)) + } + } } diff --git a/client/rpc/src/main/kotlin/net/corda/client/rpc/CordaRPCClient.kt b/client/rpc/src/main/kotlin/net/corda/client/rpc/CordaRPCClient.kt index 968620cef9..e41a7ed75c 100644 --- a/client/rpc/src/main/kotlin/net/corda/client/rpc/CordaRPCClient.kt +++ b/client/rpc/src/main/kotlin/net/corda/client/rpc/CordaRPCClient.kt @@ -286,7 +286,7 @@ class CordaRPCClient private constructor( effectiveSerializationEnv } catch (e: IllegalStateException) { try { - AMQPClientSerializationScheme.initialiseSerialization() + AMQPClientSerializationScheme.initialiseSerialization(classLoader) } catch (e: IllegalStateException) { // Race e.g. two of these constructed in parallel, ignore. } diff --git a/client/rpc/src/main/kotlin/net/corda/client/rpc/internal/serialization/amqp/AMQPClientSerializationScheme.kt b/client/rpc/src/main/kotlin/net/corda/client/rpc/internal/serialization/amqp/AMQPClientSerializationScheme.kt index 389609f84c..1f82119356 100644 --- a/client/rpc/src/main/kotlin/net/corda/client/rpc/internal/serialization/amqp/AMQPClientSerializationScheme.kt +++ b/client/rpc/src/main/kotlin/net/corda/client/rpc/internal/serialization/amqp/AMQPClientSerializationScheme.kt @@ -3,6 +3,7 @@ package net.corda.client.rpc.internal.serialization.amqp import net.corda.core.cordapp.Cordapp import net.corda.core.serialization.ClassWhitelist import net.corda.core.serialization.SerializationContext +import net.corda.core.serialization.SerializationContext.* import net.corda.core.serialization.SerializationCustomSerializer import net.corda.core.serialization.internal.SerializationEnvironment import net.corda.core.serialization.internal.SerializationEnvironmentImpl @@ -29,25 +30,26 @@ class AMQPClientSerializationScheme( companion object { /** Call from main only. */ - fun initialiseSerialization() { - nodeSerializationEnv = createSerializationEnv() + fun initialiseSerialization(classLoader: ClassLoader? = null) { + nodeSerializationEnv = createSerializationEnv(classLoader) } - fun createSerializationEnv(): SerializationEnvironment { + fun createSerializationEnv(classLoader: ClassLoader? = null): SerializationEnvironment { return SerializationEnvironmentImpl( SerializationFactoryImpl().apply { registerScheme(AMQPClientSerializationScheme(emptyList())) }, storageContext = AMQP_STORAGE_CONTEXT, - p2pContext = AMQP_P2P_CONTEXT, + p2pContext = if (classLoader != null) AMQP_P2P_CONTEXT.withClassLoader(classLoader) else AMQP_P2P_CONTEXT, rpcClientContext = AMQP_RPC_CLIENT_CONTEXT, - rpcServerContext = AMQP_RPC_SERVER_CONTEXT) + rpcServerContext = AMQP_RPC_SERVER_CONTEXT + ) } } - override fun canDeserializeVersion(magic: CordaSerializationMagic, target: SerializationContext.UseCase) = - magic == amqpMagic && ( - target == SerializationContext.UseCase.RPCClient || target == SerializationContext.UseCase.P2P) + override fun canDeserializeVersion(magic: CordaSerializationMagic, target: SerializationContext.UseCase): Boolean { + return magic == amqpMagic && (target == UseCase.RPCClient || target == UseCase.P2P) + } override fun rpcClientSerializerFactory(context: SerializationContext): SerializerFactory { return SerializerFactory(context.whitelist, ClassLoader.getSystemClassLoader(), context.lenientCarpenterEnabled).apply { @@ -60,4 +62,4 @@ class AMQPClientSerializationScheme( override fun rpcServerSerializerFactory(context: SerializationContext): SerializerFactory { throw UnsupportedOperationException() } -} \ No newline at end of file +} diff --git a/core/src/main/kotlin/net/corda/core/contracts/TransactionState.kt b/core/src/main/kotlin/net/corda/core/contracts/TransactionState.kt index 49864ef3b5..7ad5129538 100644 --- a/core/src/main/kotlin/net/corda/core/contracts/TransactionState.kt +++ b/core/src/main/kotlin/net/corda/core/contracts/TransactionState.kt @@ -24,9 +24,8 @@ data class TransactionState @JvmOverloads constructor( * Currently these are loaded from the classpath of the node which includes the cordapp directory - at some * point these will also be loaded and run from the attachment store directly, allowing contracts to be * sent across, and run, from the network from within a sandbox environment. - * - * TODO: Implement the contract sandbox loading of the contract attachments - * */ + */ + // TODO: Implement the contract sandbox loading of the contract attachments val contract: ContractClassName, /** Identity of the notary that ensures the state is not used as an input to a transaction more than once */ val notary: Party, diff --git a/serialization/src/main/kotlin/net/corda/serialization/internal/amqp/SerializerFactory.kt b/serialization/src/main/kotlin/net/corda/serialization/internal/amqp/SerializerFactory.kt index 8871907eed..8be1bab049 100644 --- a/serialization/src/main/kotlin/net/corda/serialization/internal/amqp/SerializerFactory.kt +++ b/serialization/src/main/kotlin/net/corda/serialization/internal/amqp/SerializerFactory.kt @@ -74,11 +74,11 @@ open class SerializerFactory( @DeleteForDJVM constructor(whitelist: ClassWhitelist, - classLoader: ClassLoader, + carpenterClassLoader: ClassLoader, lenientCarpenter: Boolean = false, evolutionSerializerGetter: EvolutionSerializerGetterBase = EvolutionSerializerGetter(), fingerPrinter: FingerPrinter = SerializerFingerPrinter() - ) : this(whitelist, ClassCarpenterImpl(whitelist, classLoader, lenientCarpenter), evolutionSerializerGetter, fingerPrinter) + ) : this(whitelist, ClassCarpenterImpl(whitelist, carpenterClassLoader, lenientCarpenter), evolutionSerializerGetter, fingerPrinter) init { fingerPrinter.setOwner(this) diff --git a/testing/node-driver/src/main/kotlin/net/corda/testing/node/internal/ProcessUtilities.kt b/testing/node-driver/src/main/kotlin/net/corda/testing/node/internal/ProcessUtilities.kt index 4bb77cd503..309c3af8b3 100644 --- a/testing/node-driver/src/main/kotlin/net/corda/testing/node/internal/ProcessUtilities.kt +++ b/testing/node-driver/src/main/kotlin/net/corda/testing/node/internal/ProcessUtilities.kt @@ -1,17 +1,16 @@ package net.corda.testing.node.internal import net.corda.core.internal.div -import net.corda.core.internal.exists -import java.io.File.pathSeparator import java.nio.file.Path object ProcessUtilities { inline fun startJavaProcess( arguments: List, + classpath: String = defaultClassPath, jdwpPort: Int? = null, extraJvmArguments: List = emptyList() ): Process { - return startJavaProcessImpl(C::class.java.name, arguments, defaultClassPath, jdwpPort, extraJvmArguments, null, null) + return startJavaProcessImpl(C::class.java.name, arguments, classpath, jdwpPort, extraJvmArguments, null, null) } fun startCordaProcess( 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 a339d1040a..37b994641d 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,6 +8,7 @@ import net.corda.core.utilities.NetworkHostAndPort import net.corda.core.utilities.contextLogger import net.corda.nodeapi.internal.network.NetworkParametersCopier import net.corda.testing.common.internal.asContextEnv +import net.corda.testing.common.internal.checkNotOnClasspath import net.corda.testing.common.internal.testNetworkParameters import java.nio.file.Path import java.nio.file.Paths @@ -67,11 +68,8 @@ class NodeProcess( } init { - try { - Class.forName("net.corda.node.Corda") - throw Error("Smoke test has the node in its classpath. Please remove the offending dependency.") - } catch (e: ClassNotFoundException) { - // If the class can't be found then we're good! + checkNotOnClasspath("net.corda.node.Corda") { + "Smoke test has the node in its classpath. Please remove the offending dependency." } } } diff --git a/testing/test-common/src/main/kotlin/net/corda/testing/common/internal/TestCommonUtils.kt b/testing/test-common/src/main/kotlin/net/corda/testing/common/internal/TestCommonUtils.kt new file mode 100644 index 0000000000..5ae693498e --- /dev/null +++ b/testing/test-common/src/main/kotlin/net/corda/testing/common/internal/TestCommonUtils.kt @@ -0,0 +1,10 @@ +package net.corda.testing.common.internal + +inline fun checkNotOnClasspath(className: String, errorMessage: () -> Any) { + try { + Class.forName(className) + throw IllegalStateException(errorMessage().toString()) + } catch (e: ClassNotFoundException) { + // If the class can't be found then we're good! + } +} diff --git a/tools/shell/src/main/kotlin/net/corda/tools/shell/InteractiveShell.kt b/tools/shell/src/main/kotlin/net/corda/tools/shell/InteractiveShell.kt index 1afe13d0da..21814df176 100644 --- a/tools/shell/src/main/kotlin/net/corda/tools/shell/InteractiveShell.kt +++ b/tools/shell/src/main/kotlin/net/corda/tools/shell/InteractiveShell.kt @@ -15,18 +15,10 @@ import net.corda.core.CordaException import net.corda.core.concurrent.CordaFuture import net.corda.core.contracts.UniqueIdentifier import net.corda.core.flows.FlowLogic -import net.corda.core.internal.Emoji +import net.corda.core.internal.* import net.corda.core.internal.concurrent.doneFuture import net.corda.core.internal.concurrent.openFuture -import net.corda.core.internal.createDirectories -import net.corda.core.internal.div -import net.corda.core.internal.rootCause -import net.corda.core.internal.uncheckedCast -import net.corda.core.messaging.CordaRPCOps -import net.corda.core.messaging.DataFeed -import net.corda.core.messaging.FlowProgressHandle -import net.corda.core.messaging.StateMachineUpdate -import net.corda.core.messaging.pendingFlowsCount +import net.corda.core.messaging.* import net.corda.tools.shell.utlities.ANSIProgressRenderer import net.corda.tools.shell.utlities.StdoutANSIProgressRenderer import org.crsh.command.InvocationContext @@ -131,8 +123,7 @@ object InteractiveShell { config["crash.ssh.port"] = configuration.sshdPort?.toString() config["crash.auth"] = "corda" configuration.sshHostKeyDirectory?.apply { - val sshKeysDir = configuration.sshHostKeyDirectory - sshKeysDir.createDirectories() + val sshKeysDir = configuration.sshHostKeyDirectory.createDirectories() config["crash.ssh.keypath"] = (sshKeysDir / "hostkey.pem").toString() config["crash.ssh.keygen"] = "true" } @@ -275,7 +266,7 @@ object InteractiveShell { val stateObservable = runFlowFromString({ clazz, args -> rpcOps.startTrackedFlowDynamic(clazz, *args) }, inputData, flowClazz, om) val latch = CountDownLatch(1) - ansiProgressRenderer.render(stateObservable, { latch.countDown() }) + ansiProgressRenderer.render(stateObservable, latch::countDown) // Wait for the flow to end and the progress tracker to notice. By the time the latch is released // the tracker is done with the screen. while (!Thread.currentThread().isInterrupted) { @@ -291,11 +282,7 @@ object InteractiveShell { } } } - stateObservable.returnValue.get()?.apply { - if (this !is Throwable) { - output.println("Flow completed with result: $this") - } - } + output.println("Flow completed with result: ${stateObservable.returnValue.get()}") } catch (e: NoApplicableConstructor) { output.println("No matching constructor found:", Color.red) e.errors.forEach { output.println("- $it", Color.red) } From e401eb604db656af39423bc036bcb5856fe37b27 Mon Sep 17 00:00:00 2001 From: Shams Asari Date: Wed, 27 Jun 2018 20:23:26 +0100 Subject: [PATCH 06/10] Fixed CordaRPCClient test to avoid potential flakiness (#3459) --- .../kotlin/net/corda/client/rpc/CordaRPCClientTest.kt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/client/rpc/src/integration-test/kotlin/net/corda/client/rpc/CordaRPCClientTest.kt b/client/rpc/src/integration-test/kotlin/net/corda/client/rpc/CordaRPCClientTest.kt index 2e6996978b..bd51bd3a36 100644 --- a/client/rpc/src/integration-test/kotlin/net/corda/client/rpc/CordaRPCClientTest.kt +++ b/client/rpc/src/integration-test/kotlin/net/corda/client/rpc/CordaRPCClientTest.kt @@ -237,7 +237,7 @@ class CordaRPCClientTest : NodeBasedTest(listOf("net.corda.finance")) { .joinToString(pathSeparator) // Create a Cash.State object for the StandaloneCashRpcClient to get - node.services.startFlow(CashIssueFlow(100.POUNDS, OpaqueBytes.of(1), identity), InvocationContext.shell()) + node.services.startFlow(CashIssueFlow(100.POUNDS, OpaqueBytes.of(1), identity), InvocationContext.shell()).flatMap { it.resultFuture }.getOrThrow() val outOfProcessRpc = ProcessUtilities.startJavaProcess( classpath = classpathWithoutFinance, arguments = listOf(node.internals.configuration.rpcOptions.address.toString(), financeLocation) @@ -282,7 +282,7 @@ class CordaRPCClientTest : NodeBasedTest(listOf("net.corda.finance")) { assertThat(state.javaClass.name).isEqualTo("net.corda.finance.contracts.asset.Cash${'$'}State") assertThat(state.amount.quantity).isEqualTo(10000) assertThat(state.amount.token.product).isEqualTo(Currency.getInstance("GBP")) - // This particular check assures us that the Cash.State that we have hasn't been carpented. + // This particular check assures us that the Cash.State we have hasn't been carpented. assertThat(state.participants).isEqualTo(listOf(state.owner)) } } From 4e52a957d1e8a2ab45f87323f3e46790bf999f54 Mon Sep 17 00:00:00 2001 From: Mike Hearn Date: Wed, 27 Jun 2018 22:15:11 +0200 Subject: [PATCH 07/10] Minor: better error message when the config file isn't found (#3460) --- .../kotlin/net/corda/node/internal/NodeStartup.kt | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/node/src/main/kotlin/net/corda/node/internal/NodeStartup.kt b/node/src/main/kotlin/net/corda/node/internal/NodeStartup.kt index 587cd875e3..95a03d4c48 100644 --- a/node/src/main/kotlin/net/corda/node/internal/NodeStartup.kt +++ b/node/src/main/kotlin/net/corda/node/internal/NodeStartup.kt @@ -2,6 +2,7 @@ package net.corda.node.internal import com.jcabi.manifests.Manifests import com.typesafe.config.Config +import com.typesafe.config.ConfigException import com.typesafe.config.ConfigRenderOptions import io.netty.channel.unix.Errors import net.corda.core.crypto.Crypto @@ -106,8 +107,16 @@ open class NodeStartup(val args: Array) { } catch (e: UnknownConfigurationKeysException) { logger.error(e.message) return false + } catch (e: ConfigException.IO) { + println(""" + Unable to load the node config file from '${cmdlineOptions.configFile}'. + + Try experimenting with the --base-directory flag to change which directory the node + is looking in, or use the --config-file flag to specify it explicitly. + """.trimIndent()) + return false } catch (e: Exception) { - logger.error("Exception during node configuration", e) + logger.error("Unexpected error whilst reading node configuration", e) return false } val errors = conf.validate() From 34923506f469270a25c8ccb05d6637d261970d40 Mon Sep 17 00:00:00 2001 From: Viktor Kolomeyko Date: Thu, 28 Jun 2018 08:53:30 +0100 Subject: [PATCH 08/10] CORDA-1686: Make "rpc-client-sender" daemon (#3455) Such that they do not prevent application that started them from exiting. --- .../net/corda/client/rpc/internal/RPCClientProxyHandler.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/client/rpc/src/main/kotlin/net/corda/client/rpc/internal/RPCClientProxyHandler.kt b/client/rpc/src/main/kotlin/net/corda/client/rpc/internal/RPCClientProxyHandler.kt index 06117704c3..45d65c6644 100644 --- a/client/rpc/src/main/kotlin/net/corda/client/rpc/internal/RPCClientProxyHandler.kt +++ b/client/rpc/src/main/kotlin/net/corda/client/rpc/internal/RPCClientProxyHandler.kt @@ -201,7 +201,7 @@ class RPCClientProxyHandler( ThreadFactoryBuilder().setNameFormat("rpc-client-reaper-%d").setDaemon(true).build() ) sendExecutor = Executors.newSingleThreadExecutor( - ThreadFactoryBuilder().setNameFormat("rpc-client-sender-%d").build() + ThreadFactoryBuilder().setNameFormat("rpc-client-sender-%d").setDaemon(true).build() ) reaperScheduledFuture = reaperExecutor!!.scheduleAtFixedRate( this::reapObservablesAndNotify, From 322a8d4f54d92a331d7462e54501491329966828 Mon Sep 17 00:00:00 2001 From: Rick Parker Date: Thu, 28 Jun 2018 09:25:56 +0100 Subject: [PATCH 09/10] ENT-2124 Flush before detach in Hibernate (#3462) --- .../net/corda/node/utilities/AppendOnlyPersistentMap.kt | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/node/src/main/kotlin/net/corda/node/utilities/AppendOnlyPersistentMap.kt b/node/src/main/kotlin/net/corda/node/utilities/AppendOnlyPersistentMap.kt index 639a02ff85..945cc2135a 100644 --- a/node/src/main/kotlin/net/corda/node/utilities/AppendOnlyPersistentMap.kt +++ b/node/src/main/kotlin/net/corda/node/utilities/AppendOnlyPersistentMap.kt @@ -123,8 +123,12 @@ abstract class AppendOnlyPersistentMapBase( } protected fun loadValue(key: K): V? { - val result = currentDBSession().find(persistentEntityClass, toPersistentEntityKey(key)) - return result?.apply { currentDBSession().detach(result) }?.let(fromPersistentEntity)?.second + val session = currentDBSession() + // IMPORTANT: The flush is needed because detach() makes the queue of unflushed entries invalid w.r.t. Hibernate internal state if the found entity is unflushed. + // We want the detach() so that we rely on our cache memory management and don't retain strong references in the Hibernate session. + session.flush() + val result = session.find(persistentEntityClass, toPersistentEntityKey(key)) + return result?.apply { session.detach(result) }?.let(fromPersistentEntity)?.second } operator fun contains(key: K) = get(key) != null From 84a86d32b7fb9e8075f1d94df4d5887a3c5301a0 Mon Sep 17 00:00:00 2001 From: Shams Asari Date: Thu, 28 Jun 2018 09:43:29 +0100 Subject: [PATCH 10/10] Fixed broken smoke test caused by no notaries in the network parameters (#3461) For now the workaround is to manually select the notary. The proper solution is to add notaries to the network parameters in smoke tests. --- .../kotlin/net/corda/kotlin/rpc/StandaloneCordaRPClientTest.kt | 2 +- .../src/main/kotlin/net/corda/smoketesting/NodeProcess.kt | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/client/rpc/src/smoke-test/kotlin/net/corda/kotlin/rpc/StandaloneCordaRPClientTest.kt b/client/rpc/src/smoke-test/kotlin/net/corda/kotlin/rpc/StandaloneCordaRPClientTest.kt index f8b28eb37b..bc0174c4f4 100644 --- a/client/rpc/src/smoke-test/kotlin/net/corda/kotlin/rpc/StandaloneCordaRPClientTest.kt +++ b/client/rpc/src/smoke-test/kotlin/net/corda/kotlin/rpc/StandaloneCordaRPClientTest.kt @@ -212,7 +212,7 @@ class StandaloneCordaRPClientTest { assertEquals(1, queryResults.totalStatesAvailable) assertEquals(queryResults.states.first().state.data.amount.quantity, 629.POUNDS.quantity) - rpcProxy.startFlow(::CashPaymentFlow, 100.POUNDS, notaryNodeIdentity).returnValue.getOrThrow() + rpcProxy.startFlow(::CashPaymentFlow, 100.POUNDS, notaryNodeIdentity, true, notaryNodeIdentity).returnValue.getOrThrow() val moreResults = rpcProxy.vaultQueryBy(criteria, paging, sorting) assertEquals(3, moreResults.totalStatesAvailable) // 629 - 100 + 100 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 37b994641d..afe958ca85 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 @@ -61,7 +61,7 @@ class NodeProcess( val formatter: DateTimeFormatter = DateTimeFormatter.ofPattern("yyyyMMddHHmmss").withZone(systemDefault()) val defaultNetworkParameters = run { AMQPClientSerializationScheme.createSerializationEnv().asContextEnv { - // There are no notaries in the network parameters for smoke test nodes. If this is required then we would + // TODO 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()) }