From 2e3952ee1f1d097b5e0cb7ca981c167df6e862c4 Mon Sep 17 00:00:00 2001 From: "rick.parker" Date: Tue, 4 Oct 2016 19:01:09 +0100 Subject: [PATCH] H2 database exported via configurable port number. --- docs/source/creating-a-cordapp.rst | 12 ++++++++ docs/source/persistence.rst | 4 ++- .../kotlin/com/r3corda/node/internal/Node.kt | 29 +++++++++++++++++++ node/src/main/resources/reference.conf | 5 ++-- .../com/r3corda/core/testing/IRSDemoTest.kt | 3 +- .../r3corda/core/testing/TraderDemoTest.kt | 6 ++-- src/main/kotlin/com/r3corda/demos/IRSDemo.kt | 16 ++++++++-- .../kotlin/com/r3corda/demos/TraderDemo.kt | 8 +++-- 8 files changed, 72 insertions(+), 11 deletions(-) diff --git a/docs/source/creating-a-cordapp.rst b/docs/source/creating-a-cordapp.rst index 1c81c9a5ee..8f67960851 100644 --- a/docs/source/creating-a-cordapp.rst +++ b/docs/source/creating-a-cordapp.rst @@ -75,6 +75,18 @@ To enable remote debugging of the corda process use a command line such as: This command line will start the debugger on port 5005 and pause the process awaiting debugger attachment. +Viewing persisted state of your Node +------------------------------------ + +To make examining the persisted contract states of your node or the internal node database tables easier, and providing you are +using the default database configuration used for demos, you should be able to connect to the internal node database over +a JDBC connection at the URL that is output to the logs at node start up. That URL will be of the form ``jdbc:h2:tcp://:/node``. + +The user name and password for the login are as per the node data source configuration. + +The name and column layout of the internal node tables is in a state of flux and should not be relied upon to remain static +at the present time, and should certainly be treated as read-only. + .. _CordaPluginRegistry: api/com.r3corda.core.node/-corda-plugin-registry/index.html .. _ServiceHubInternal: api/com.r3corda.node.services.api/-service-hub-internal/index.html .. _ServiceHub: api/com.r3corda.node.services.api/-service-hub/index.html diff --git a/docs/source/persistence.rst b/docs/source/persistence.rst index 3abe759045..b62e8e8e6c 100644 --- a/docs/source/persistence.rst +++ b/docs/source/persistence.rst @@ -11,7 +11,9 @@ as annotations and is converted to database table rows by the node automatically node's local vault as part of a transaction. .. note:: Presently the node includes an instance of the H2 database but any database that supports JDBC is a candidate and - the node will in the future support a range of database implementations via their JDBC drivers. + the node will in the future support a range of database implementations via their JDBC drivers. Much of the node + internal state is also persisted there. If a node is using the default H2 JDBC configuration you should be able to connect + to the H2 instance using the JDBC URL output to the logs at startup of the form ``jdbc:h2:tcp://:/node`` Schemas ------- diff --git a/node/src/main/kotlin/com/r3corda/node/internal/Node.kt b/node/src/main/kotlin/com/r3corda/node/internal/Node.kt index c1fccb1e36..98674024b3 100644 --- a/node/src/main/kotlin/com/r3corda/node/internal/Node.kt +++ b/node/src/main/kotlin/com/r3corda/node/internal/Node.kt @@ -235,6 +235,35 @@ class Node(override val configuration: FullNodeConfiguration, networkMapAddress: override fun makeUniquenessProvider() = PersistentUniquenessProvider() + /** + * If the node is persisting to an embedded H2 database, then expose this via TCP with a JDBC URL of the form: + * jdbc:h2:tcp://:/node + * with username and password as per the DataSource connection details. The key element to enabling this support is to + * ensure that you specify a JDBC connection URL of the form jdbc:h2:file: in the node config and that you include + * the H2 option AUTO_SERVER_PORT set to the port you desire to use (0 will give a dynamically allocated port number) + * but exclude the H2 option AUTO_SERVER=TRUE. + * This is not using the H2 "automatic mixed mode" directly but leans on many of the underpinnings. For more details + * on H2 URLs and configuration see: http://www.h2database.com/html/features.html#database_url + */ + override fun initialiseDatabasePersistence(insideTransaction: () -> Unit) { + val databaseUrl = configuration.dataSourceProperties.getProperty("dataSource.url") + val h2Prefix = "jdbc:h2:file:" + if (databaseUrl != null && databaseUrl.startsWith(h2Prefix)) { + val h2Port = databaseUrl.substringAfter(";AUTO_SERVER_PORT=", "").substringBefore(';') + if (h2Port.isNotBlank()) { + val databaseName = databaseUrl.removePrefix(h2Prefix).substringBefore(';') + val server = org.h2.tools.Server.createTcpServer( + "-tcpPort", h2Port, + "-tcpAllowOthers", + "-tcpDaemon", + "-key", "node", databaseName) + val url = server.start().url + log.info("H2 JDBC url is jdbc:h2:$url/node") + } + } + super.initialiseDatabasePersistence(insideTransaction) + } + override fun start(): Node { alreadyRunningNodeCheck() super.start() diff --git a/node/src/main/resources/reference.conf b/node/src/main/resources/reference.conf index 71afcfe511..cb914d4cae 100644 --- a/node/src/main/resources/reference.conf +++ b/node/src/main/resources/reference.conf @@ -6,10 +6,11 @@ keyStorePassword = "cordacadevpass" trustStorePassword = "trustpass" dataSourceProperties = { dataSourceClassName = org.h2.jdbcx.JdbcDataSource - "dataSource.url" = "jdbc:h2:"${basedir}"/persistence;DB_CLOSE_ON_EXIT=FALSE;LOCK_TIMEOUT=10000;MVCC=true;MV_STORE=true;WRITE_DELAY=0" + "dataSource.url" = "jdbc:h2:file:"${basedir}"/persistence;DB_CLOSE_ON_EXIT=FALSE;LOCK_TIMEOUT=10000;MVCC=true;MV_STORE=true;WRITE_DELAY=0;AUTO_SERVER_PORT="${h2port} "dataSource.user" = sa "dataSource.password" = "" } devMode = true certificateSigningService = "https://cordaci-netperm.corda.r3cev.com" -useHTTPS = false \ No newline at end of file +useHTTPS = false +h2port = 0 \ No newline at end of file diff --git a/src/integration-test/kotlin/com/r3corda/core/testing/IRSDemoTest.kt b/src/integration-test/kotlin/com/r3corda/core/testing/IRSDemoTest.kt index 219f3b1b4d..eeed79838c 100644 --- a/src/integration-test/kotlin/com/r3corda/core/testing/IRSDemoTest.kt +++ b/src/integration-test/kotlin/com/r3corda/core/testing/IRSDemoTest.kt @@ -66,7 +66,8 @@ private fun startNode(baseDirectory: Path, "--base-directory", baseDirectory.toString(), "--network-address", nodeAddr.toString(), "--network-map-address", networkMapAddr.toString(), - "--api-address", apiAddr.toString()) + "--api-address", apiAddr.toString(), + "--h2-port", "0") val proc = spawn("com.r3corda.demos.IRSDemoKt", args, "IRSDemo$nodeType") NodeApi.ensureNodeStartsOrKill(proc, apiAddr) return proc diff --git a/src/integration-test/kotlin/com/r3corda/core/testing/TraderDemoTest.kt b/src/integration-test/kotlin/com/r3corda/core/testing/TraderDemoTest.kt index cbcb964a41..233139ca41 100644 --- a/src/integration-test/kotlin/com/r3corda/core/testing/TraderDemoTest.kt +++ b/src/integration-test/kotlin/com/r3corda/core/testing/TraderDemoTest.kt @@ -26,7 +26,8 @@ class TraderDemoTest { "--role", "BUYER", "--network-address", buyerAddr.toString(), "--api-address", buyerApiAddr.toString(), - "--base-directory", baseDirectory + "--base-directory", baseDirectory, + "--h2-port", "0" ) val proc = spawn("com.r3corda.demos.TraderDemoKt", args, "TradeDemoBuyer") NodeApi.ensureNodeStartsOrKill(proc, buyerApiAddr) @@ -42,7 +43,8 @@ class TraderDemoTest { "--network-address", sellerAddr.toString(), "--api-address", sellerApiAddr.toString(), "--other-network-address", buyerAddr.toString(), - "--base-directory", baseDirectory + "--base-directory", baseDirectory, + "--h2-port", "0" ) val proc = spawn("com.r3corda.demos.TraderDemoKt", args, "TradeDemoSeller") assertExitOrKill(proc) diff --git a/src/main/kotlin/com/r3corda/demos/IRSDemo.kt b/src/main/kotlin/com/r3corda/demos/IRSDemo.kt index c99992be59..3c19c93aca 100644 --- a/src/main/kotlin/com/r3corda/demos/IRSDemo.kt +++ b/src/main/kotlin/com/r3corda/demos/IRSDemo.kt @@ -87,7 +87,8 @@ sealed class CliParams { val tradeWithIdentities: List, val uploadRates: Boolean, val defaultLegalName: String, - val autoSetup: Boolean // Run Setup for both nodes automatically with default arguments + val autoSetup: Boolean, // Run Setup for both nodes automatically with default arguments + val h2Port: Int ) : CliParams() /** @@ -151,6 +152,12 @@ sealed class CliParams { IRSDemoNode.NodeB -> Node.DEFAULT_PORT + 3 } + private fun defaultH2Port(node: IRSDemoNode) = + when (node) { + IRSDemoNode.NodeA -> Node.DEFAULT_PORT + 4 + IRSDemoNode.NodeB -> Node.DEFAULT_PORT + 5 + } + private fun parseRunNode(options: OptionSet, node: IRSDemoNode): RunNode { val dir = nodeDirectory(options, node) @@ -171,7 +178,8 @@ sealed class CliParams { }, uploadRates = node == IRSDemoNode.NodeB, defaultLegalName = legalName(node), - autoSetup = !options.has(CliParamsSpec.baseDirectoryArg) && !options.has(CliParamsSpec.fakeTradeWithIdentityFile) + autoSetup = !options.has(CliParamsSpec.baseDirectoryArg) && !options.has(CliParamsSpec.fakeTradeWithIdentityFile), + h2Port = options.valueOf(CliParamsSpec.h2PortArg.defaultsTo(defaultH2Port(node))) ) } @@ -263,6 +271,7 @@ object CliParamsSpec { val fakeTradeWithIdentityFile = parser.accepts("fake-trade-with-identity-file", "Extra identities to be registered with the identity service") .withOptionalArg() + val h2PortArg = parser.accepts("h2-port").withRequiredArg().ofType(Int::class.java) val nonOptions = parser.nonOptions() val help = parser.accepts("help", "Prints this help").forHelp() } @@ -449,7 +458,8 @@ private fun getNodeConfig(cliParams: CliParams.RunNode): FullNodeConfiguration { val configFile = cliParams.dir.resolve("config") val configOverrides = mapOf( "artemisAddress" to cliParams.networkAddress.toString(), - "webAddress" to cliParams.apiAddress.toString() + "webAddress" to cliParams.apiAddress.toString(), + "h2port" to cliParams.h2Port.toString() ) return loadConfigFile(cliParams.dir, configFile, configOverrides, cliParams.defaultLegalName) } diff --git a/src/main/kotlin/com/r3corda/demos/TraderDemo.kt b/src/main/kotlin/com/r3corda/demos/TraderDemo.kt index f201e0b750..4d4c0c770f 100644 --- a/src/main/kotlin/com/r3corda/demos/TraderDemo.kt +++ b/src/main/kotlin/com/r3corda/demos/TraderDemo.kt @@ -80,7 +80,7 @@ fun main(args: Array) { val theirNetworkAddress = parser.accepts("other-network-address").withRequiredArg().defaultsTo("localhost") val apiNetworkAddress = parser.accepts("api-address").withRequiredArg().defaultsTo("localhost") val baseDirectoryArg = parser.accepts("base-directory").withRequiredArg().defaultsTo(DEFAULT_BASE_DIRECTORY) - + val h2PortArg = parser.accepts("h2-port").withRequiredArg().ofType(Int::class.java).defaultsTo(-1) val options = try { parser.parse(*args) } catch (e: Exception) { @@ -104,6 +104,9 @@ fun main(args: Array) { } ) val apiNetAddr = HostAndPort.fromString(options.valueOf(apiNetworkAddress)).withDefaultPort(myNetAddr.port + 1) + val h2Port = if (options.valueOf(h2PortArg) < 0) { + myNetAddr.port + 2 + } else options.valueOf(h2PortArg) val baseDirectory = options.valueOf(baseDirectoryArg)!! @@ -125,7 +128,8 @@ fun main(args: Array) { val configOverrides = mapOf( "myLegalName" to myLegalName, "artemisAddress" to myNetAddr.toString(), - "webAddress" to apiNetAddr.toString() + "webAddress" to apiNetAddr.toString(), + "h2port" to h2Port.toString() ) FullNodeConfiguration(NodeConfiguration.loadConfig(directory, allowMissingConfig = true, configOverrides = configOverrides)) }