diff --git a/docs/source/changelog.rst b/docs/source/changelog.rst index 1dcf850d87..6517ea9423 100644 --- a/docs/source/changelog.rst +++ b/docs/source/changelog.rst @@ -7,6 +7,11 @@ release, see :doc:`upgrade-notes`. Unreleased ========== +* H2 database changes: + * The node's H2 database now listens on ``localhost`` by default. + * The database server address must also be enabled in the node configuration. + * A new ``h2Settings`` configuration block supercedes the ``h2Port`` option. + * Improved documentation PDF quality. Building the documentation now requires ``LaTex`` to be installed on the OS. * Add ``devModeOptions.allowCompatibilityZone`` to re-enable the use of a compatibility zone and ``devMode`` @@ -47,6 +52,8 @@ Unreleased * The node's configuration is only printed on startup if ``devMode`` is ``true``, avoiding the risk of printing passwords in a production setup. +* ``NodeStartup`` will now only print node's configuration if ``devMode`` is ``true``, avoiding the risk of printing passwords in a production setup. + * SLF4J's MDC will now only be printed to the console if not empty. No more log lines ending with "{}". * ``WireTransaction.Companion.createComponentGroups`` has been marked as ``@CordaInternal``. It was never intended to be diff --git a/docs/source/node-administration.rst b/docs/source/node-administration.rst index 9943a0099d..4fbd3a2243 100644 --- a/docs/source/node-administration.rst +++ b/docs/source/node-administration.rst @@ -59,11 +59,10 @@ Node can be configured to run SSH server. See :doc:`shell` for details. Database access --------------- - -The node exposes its internal database over a socket which can be browsed using any tool that can use JDBC drivers. +The node can be configured to expose its internal database over socket which can be browsed using any tool that can use JDBC drivers. The JDBC URL is printed during node startup to the log and will typically look like this: - ``jdbc:h2:tcp://192.168.0.31:31339/node`` + ``jdbc:h2:tcp://localhost:31339/node`` The username and password can be altered in the :doc:`corda-configuration-file` but default to username "sa" and a blank password. @@ -72,6 +71,19 @@ Any database browsing tool that supports JDBC can be used, but if you have Intel a tool integrated with your IDE. Just open the database window and add an H2 data source with the above details. You will now be able to browse the tables and row data within them. +By default the node will expose its database on the localhost network interface. This behaviour can be +overridden by specifying the full network address (interface and port), using the new h2Settings +syntax in the node configuration: + +.. sourcecode:: groovy + h2Settings { + address: "localhost:12345" + } + +The configuration above will restrict the H2 service to run on localhost. If remote access is required, the address +can be changed to 0.0.0.0. However it is recommended to change the default username and password +before doing so. + Monitoring your node -------------------- diff --git a/docs/source/node-database.rst b/docs/source/node-database.rst index 2f9a4dc04e..5f7ea0815c 100644 --- a/docs/source/node-database.rst +++ b/docs/source/node-database.rst @@ -6,6 +6,13 @@ Default in-memory database By default, nodes store their data in an H2 database. You can connect directly to a running node's database to see its stored states, transactions and attachments as follows: +* Enable the H2 database access in the node configuration using the following syntax: + + .. sourcecode:: groovy + h2Settings { + address: "localhost:0" + } + * Download the **last stable** `h2 platform-independent zip `_, unzip the zip, and navigate in a terminal window to the unzipped folder * Change directories to the bin folder: ``cd h2/bin`` @@ -25,6 +32,10 @@ stored states, transactions and attachments as follows: You will be presented with a web interface that shows the contents of your node's storage and vault, and provides an interface for you to query them using SQL. +The default behaviour is to expose the H2 database on localhost. This can be overridden in the +node configuration using ``h2Settings.address`` and specifying the address of the network interface to listen on, +or simply using ``0.0.0.0:0`` to listen on all interfaces. + PostgreSQL ---------- Nodes also have untested support for PostgreSQL 9.6, using PostgreSQL JDBC Driver 42.1.4. 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 e40eb5d243..f983ee1825 100644 --- a/node/src/main/kotlin/net/corda/node/internal/Node.kt +++ b/node/src/main/kotlin/net/corda/node/internal/Node.kt @@ -336,15 +336,19 @@ open class Node(configuration: NodeConfiguration, wellKnownPartyFromAnonymous: (AbstractParty) -> Party?): CordaPersistence { 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 effectiveH2Settings = configuration.effectiveH2Settings + + if(effectiveH2Settings != null && effectiveH2Settings.address != null) { val databaseName = databaseUrl.removePrefix(h2Prefix).substringBefore(';') val server = org.h2.tools.Server.createTcpServer( - "-tcpPort", h2Port, + "-tcpPort", effectiveH2Settings.address.port.toString(), "-tcpAllowOthers", "-tcpDaemon", "-key", "node", databaseName) + // override interface that createTcpServer listens on (which is always 0.0.0.0) + System.setProperty("h2.bindAddress", effectiveH2Settings.address.host) runOnStop += server::stop val url = server.start().url printBasicNodeInfo("Database connection url is", "jdbc:h2:$url/node") 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 7bf2b33c87..0a3e9e4a98 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 @@ -61,7 +61,7 @@ interface NodeConfiguration : NodeSSLConfiguration { val extraNetworkMapKeys: List val tlsCertCrlDistPoint: URL? val tlsCertCrlIssuer: String? - + val effectiveH2Settings: NodeH2Settings? fun validate(): List companion object { @@ -190,12 +190,14 @@ data class NodeConfigurationImpl( override val attachmentCacheBound: Long = NodeConfiguration.defaultAttachmentCacheBound, override val extraNetworkMapKeys: List = emptyList(), // do not use or remove (breaks DemoBench together with rejection of unknown configuration keys during parsing) - private val h2port: Int = 0, + private val h2port: Int? = null, + private val h2Settings: NodeH2Settings? = null, // do not use or remove (used by Capsule) private val jarDirs: List = emptyList() ) : NodeConfiguration { companion object { private val logger = loggerFor() + } override val rpcOptions: NodeRpcOptions = initialiseRpcOptions(rpcAddress, rpcSettings, BrokerRpcSslOptions(baseDirectory / "certificates" / "nodekeystore.jks", keyStorePassword)) @@ -215,6 +217,7 @@ data class NodeConfigurationImpl( }.asOptions(fallbackSslOptions) } + private fun validateTlsCertCrlConfig(): List { val errors = mutableListOf() if (tlsCertCrlIssuer != null) { @@ -239,6 +242,15 @@ data class NodeConfigurationImpl( errors += validateRpcOptions(rpcOptions) errors += validateTlsCertCrlConfig() errors += validateNetworkServices() + errors += validateH2Settings() + return errors + } + + private fun validateH2Settings(): List { + val errors = mutableListOf() + if (h2port != null && h2Settings != null) { + errors += "Cannot specify both 'h2port' and 'h2Settings' in configuration" + } return errors } @@ -286,6 +298,11 @@ data class NodeConfigurationImpl( override val attachmentContentCacheSizeBytes: Long get() = attachmentContentCacheSizeMegaBytes?.MB ?: super.attachmentContentCacheSizeBytes + override val effectiveH2Settings: NodeH2Settings? + get() = when { + h2port != null -> NodeH2Settings(address = NetworkHostAndPort(host="localhost", port=h2port)) + else -> h2Settings + } init { // This is a sanity feature do not remove. @@ -303,9 +320,12 @@ data class NodeConfigurationImpl( if (compatibilityZoneURL != null && networkServices == null) { networkServices = NetworkServicesConfig(compatibilityZoneURL, compatibilityZoneURL, true) } + require(h2port == null || h2Settings == null) { "Cannot specify both 'h2port' and 'h2Settings' in configuration" } } } + + data class NodeRpcSettings( val address: NetworkHostAndPort?, val adminAddress: NetworkHostAndPort?, @@ -328,6 +348,10 @@ data class NodeRpcSettings( } } +data class NodeH2Settings( + val address: NetworkHostAndPort? +) + enum class VerifierType { InMemory, OutOfProcess diff --git a/node/src/main/resources/reference.conf b/node/src/main/resources/reference.conf index f11ed7355e..ffb1c12ed8 100644 --- a/node/src/main/resources/reference.conf +++ b/node/src/main/resources/reference.conf @@ -5,7 +5,7 @@ crlCheckSoftFail = true lazyBridgeStart = true dataSourceProperties = { dataSourceClassName = org.h2.jdbcx.JdbcDataSource - dataSource.url = "jdbc:h2:file:"${baseDirectory}"/persistence;DB_CLOSE_ON_EXIT=FALSE;WRITE_DELAY=0;LOCK_TIMEOUT=10000;AUTO_SERVER_PORT="${h2port} + dataSource.url = "jdbc:h2:file:"${baseDirectory}"/persistence;DB_CLOSE_ON_EXIT=FALSE;WRITE_DELAY=0;LOCK_TIMEOUT=10000" dataSource.user = sa dataSource.password = "" } @@ -13,7 +13,7 @@ database = { transactionIsolationLevel = "REPEATABLE_READ" exportHibernateJMXStatistics = "false" } -h2port = 0 + useTestClock = false verifierType = InMemory rpcSettings = { 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 c5660ab4f9..2eea77fc60 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 @@ -45,7 +45,6 @@ class NodeConfigTest { assertEquals(localPort(40002), fullConfig.rpcOptions.address) assertEquals(localPort(10001), fullConfig.p2pAddress) assertEquals(listOf(user("jenny")), fullConfig.rpcUsers) - assertThat(fullConfig.dataSourceProperties["dataSource.url"] as String).contains("AUTO_SERVER_PORT=30001") assertTrue(fullConfig.useTestClock) assertFalse(fullConfig.detectPublicIp) }