From a8cd1eea2b57b8407310f55dfd0fd247c8fb1cb5 Mon Sep 17 00:00:00 2001 From: szymonsztuka Date: Mon, 16 Jul 2018 09:58:37 +0100 Subject: [PATCH] Back-ports CORDA-1499 and CORDA-1804 (#3607) * Fix a typo in node_attchments_contracts table name. (#3202) (cherry picked from commit 57d379597bcff397edd3b71ffb8ac9901be06b22) * CORDA-1804 Corda node stops when detecting not migrated node_attachments_contracts table name (#3593) Database table NODE_ATTACHMENTS_CONTRACT_CLASS_NAME in v3.0 was changed to NODE_ATTCHMENTS_CONTRACTS in v3.1 and then finally NODE_ATTACHMENTS_CONTRACTS on current master. Users may omit the upgrade note and run into errors. After the change the node will not start if the new table name is not found and any other older ones is found. (cherry picked from commit 208ac49da0caaf0dfc5ade9f8bd8a49d13f17aae) * Fixes after cherry-pick of 208ac49da0caaf0dfc5ade9f8bd8a49d13f17aae. --- docs/source/changelog.rst | 2 + .../internal/persistence/CordaPersistence.kt | 27 ++++++++ ...gratedAttachmentContractsTableNameTests.kt | 67 +++++++++++++++++++ .../kotlin/net/corda/node/internal/Node.kt | 8 +++ .../net/corda/node/internal/NodeStartup.kt | 5 ++ .../persistence/NodeAttachmentService.kt | 2 +- 6 files changed, 110 insertions(+), 1 deletion(-) create mode 100644 node/src/integration-test/kotlin/net/corda/node/persistence/FailNodeOnNotMigratedAttachmentContractsTableNameTests.kt diff --git a/docs/source/changelog.rst b/docs/source/changelog.rst index 5bc6346258..68ce2ffb7d 100644 --- a/docs/source/changelog.rst +++ b/docs/source/changelog.rst @@ -36,6 +36,8 @@ Version 3.2 the same server. Current ``compatibilityZoneURL`` configurations remain valid. See both :doc:`corda-configuration-file` and :doc:`permissioning` for details. +* Table name with a typo changed from ``NODE_ATTCHMENTS_CONTRACTS`` to ``NODE_ATTACHMENTS_CONTRACTS``. + .. _changelog_v3.1: Version 3.1 diff --git a/node-api/src/main/kotlin/net/corda/nodeapi/internal/persistence/CordaPersistence.kt b/node-api/src/main/kotlin/net/corda/nodeapi/internal/persistence/CordaPersistence.kt index 4884c21832..2deb8b8015 100644 --- a/node-api/src/main/kotlin/net/corda/nodeapi/internal/persistence/CordaPersistence.kt +++ b/node-api/src/main/kotlin/net/corda/nodeapi/internal/persistence/CordaPersistence.kt @@ -82,6 +82,8 @@ class CordaPersistence( // Check not in read-only mode. transaction { check(!connection.metaData.isReadOnly) { "Database should not be readonly." } + + checkCorrectAttachmentsContractsTableName(connection) } } @@ -245,3 +247,28 @@ fun rx.Observable.wrapWithDatabaseTransaction(db: CordaPersistence? } } } + +/** Check if any nested cause is of [SQLException] type. */ +private fun Throwable.hasSQLExceptionCause(): Boolean = + when (cause) { + null -> false + is SQLException -> true + else -> cause?.hasSQLExceptionCause() ?: false + } + +class CouldNotCreateDataSourceException(override val message: String?, override val cause: Throwable? = null) : Exception() + +class IncompatibleAttachmentsContractsTableName(override val message: String?, override val cause: Throwable? = null) : Exception() + +private fun checkCorrectAttachmentsContractsTableName(connection: Connection) { + val correctName = "NODE_ATTACHMENTS_CONTRACTS" + val incorrectV30Name = "NODE_ATTACHMENTS_CONTRACT_CLASS_NAME" + val incorrectV31Name = "NODE_ATTCHMENTS_CONTRACTS" + + fun warning(incorrectName: String, version: String) = "The database contains the older table name $incorrectName instead of $correctName, see upgrade notes to migrate from Corda database version $version https://docs.corda.net/head/upgrade-notes.html." + + if (!connection.metaData.getTables(null, null, correctName, null).next()) { + if (connection.metaData.getTables(null, null, incorrectV30Name, null).next()) { throw IncompatibleAttachmentsContractsTableName(warning(incorrectV30Name, "3.0")) } + if (connection.metaData.getTables(null, null, incorrectV31Name, null).next()) { throw IncompatibleAttachmentsContractsTableName(warning(incorrectV31Name, "3.1")) } + } +} diff --git a/node/src/integration-test/kotlin/net/corda/node/persistence/FailNodeOnNotMigratedAttachmentContractsTableNameTests.kt b/node/src/integration-test/kotlin/net/corda/node/persistence/FailNodeOnNotMigratedAttachmentContractsTableNameTests.kt new file mode 100644 index 0000000000..4563cc13dc --- /dev/null +++ b/node/src/integration-test/kotlin/net/corda/node/persistence/FailNodeOnNotMigratedAttachmentContractsTableNameTests.kt @@ -0,0 +1,67 @@ +package net.corda.node.persistence + +import net.corda.client.rpc.CordaRPCClient +import net.corda.core.internal.packageName +import net.corda.core.messaging.startFlow +import net.corda.core.utilities.getOrThrow +import net.corda.node.services.Permissions +import net.corda.test.node.Message +import net.corda.test.node.MessageState +import net.corda.test.node.SendMessageFlow +import net.corda.testing.core.singleIdentity +import net.corda.testing.driver.DriverParameters +import net.corda.testing.driver.driver +import net.corda.testing.driver.internal.RandomFree +import net.corda.testing.node.User +import org.junit.Test +import java.nio.file.Path +import java.sql.DriverManager +import kotlin.test.* + +class FailNodeOnNotMigratedAttachmentContractsTableNameTests { + @Test + fun `node fails when detecting table name not migrated from version 3 dot 0`() { + `node fails when not detecting compatible table name`("NODE_ATTACHMENTS_CONTRACTS", "NODE_ATTACHMENTS_CONTRACT_CLASS_NAME") + } + + @Test + fun `node fails when detecting table name not migrated from version 3 dot 1`() { + `node fails when not detecting compatible table name`("NODE_ATTACHMENTS_CONTRACTS", "NODE_ATTCHMENTS_CONTRACTS") + } + + fun `node fails when not detecting compatible table name`(tableNameFromMapping: String, tableNameInDB: String) { + val user = User("mark", "dadada", setOf(Permissions.startFlow(), Permissions.invokeRpc("vaultQuery"))) + val message = Message("Hello world!") + val baseDir: Path = driver(DriverParameters(startNodesInProcess = true, + portAllocation = RandomFree, extraCordappPackagesToScan = listOf(MessageState::class.packageName))) { + val (nodeName, baseDir) = { + val nodeHandle = startNode(rpcUsers = listOf(user)).getOrThrow() + val nodeName = nodeHandle.nodeInfo.singleIdentity().name + CordaRPCClient(nodeHandle.rpcAddress).start(user.username, user.password).use { + it.proxy.startFlow(::SendMessageFlow, message, defaultNotaryIdentity).returnValue.getOrThrow() + } + nodeHandle.stop() + Pair(nodeName, nodeHandle.baseDirectory) + }() + + // replace the correct table name with one from the former release + DriverManager.getConnection("jdbc:h2:file://$baseDir/persistence", "sa", "").use { + it.createStatement().execute("ALTER TABLE $tableNameFromMapping RENAME TO $tableNameInDB") + it.commit() + } + assertFailsWith(net.corda.nodeapi.internal.persistence.IncompatibleAttachmentsContractsTableName::class) { + val nodeHandle = startNode(providedName = nodeName, rpcUsers = listOf(user)).getOrThrow() + nodeHandle.stop() + } + baseDir + } + + // check that the node didn't recreated the correct table matching it's entity mapping + val (hasTableFromMapping, hasTableFromDB) = DriverManager.getConnection("jdbc:h2:file://$baseDir/persistence", "sa", "").use { + Pair(it.metaData.getTables(null, null, tableNameFromMapping, null).next(), + it.metaData.getTables(null, null, tableNameInDB, null).next()) + } + assertFalse(hasTableFromMapping) + assertTrue(hasTableFromDB) + } +} \ No newline at end of file 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 673d7bf73a..183e103c42 100644 --- a/node/src/main/kotlin/net/corda/node/internal/Node.kt +++ b/node/src/main/kotlin/net/corda/node/internal/Node.kt @@ -2,6 +2,7 @@ package net.corda.node.internal import com.codahale.metrics.JmxReporter import net.corda.core.concurrent.CordaFuture +import net.corda.core.internal.Emoji import net.corda.core.internal.concurrent.openFuture import net.corda.core.internal.concurrent.thenMatch import net.corda.core.internal.div @@ -74,6 +75,13 @@ open class Node(configuration: NodeConfiguration, LoggerFactory.getLogger(loggerName).info(msg) } + fun printWarning(message: String) { + Emoji.renderIfSupported { + println("ATTENTION: $message") + } + staticLog.warn(message) + } + internal fun failStartUp(message: String): Nothing { println(message) println("Corda will now exit...") 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 e1ec13a64a..b907ed97d7 100644 --- a/node/src/main/kotlin/net/corda/node/internal/NodeStartup.kt +++ b/node/src/main/kotlin/net/corda/node/internal/NodeStartup.kt @@ -16,6 +16,7 @@ import net.corda.node.shell.InteractiveShell import net.corda.node.utilities.registration.HTTPNetworkRegistrationService import net.corda.node.utilities.registration.NetworkRegistrationHelper import net.corda.nodeapi.internal.addShutdownHook +import net.corda.nodeapi.internal.persistence.IncompatibleAttachmentsContractsTableName import org.fusesource.jansi.Ansi import org.fusesource.jansi.AnsiConsole import org.slf4j.bridge.SLF4JBridgeHandler @@ -112,6 +113,10 @@ open class NodeStartup(val args: Array) { try { cmdlineOptions.baseDirectory.createDirectories() startNode(conf, versionInfo, startTime, cmdlineOptions) + } catch (e: IncompatibleAttachmentsContractsTableName) { + e.message?.let { Node.printWarning(it) } + logger.error(e.message) + return false } catch (e: Exception) { if (e.message?.startsWith("Unknown named curve:") == true) { logger.error("Exception during node startup - ${e.message}. " + diff --git a/node/src/main/kotlin/net/corda/node/services/persistence/NodeAttachmentService.kt b/node/src/main/kotlin/net/corda/node/services/persistence/NodeAttachmentService.kt index 9593cfc634..83cfbf5c8a 100644 --- a/node/src/main/kotlin/net/corda/node/services/persistence/NodeAttachmentService.kt +++ b/node/src/main/kotlin/net/corda/node/services/persistence/NodeAttachmentService.kt @@ -93,7 +93,7 @@ class NodeAttachmentService( @ElementCollection @Column(name = "contract_class_name") - @CollectionTable(name = "node_attchments_contracts", joinColumns = arrayOf( + @CollectionTable(name = "${NODE_DATABASE_PREFIX}attachments_contracts", joinColumns = arrayOf( JoinColumn(name = "att_id", referencedColumnName = "att_id")), foreignKey = ForeignKey(name = "FK__ctr_class__attachments")) var contractClassNames: List? = null