From 1f409235e47189ccae3737614202f82680ff902e Mon Sep 17 00:00:00 2001 From: catbref Date: Mon, 9 Nov 2020 10:16:37 +0000 Subject: [PATCH] Don't rebuild repository or export node-local data during repository build if repository was 'pristine'. Under certain conditions, e.g. non-existent database files, the repository would be created and then immediately be re-created. Not only was this unnecessary, but HSQLDBDatabaseUpdates would attempt to export the node-local data twice, which would cause an error due to existing .script files. The fix is three-pronged: 1. Don't immediately rebuild the repository if it's only just been built 2. Don't export the empty node-local data if repository has only just been built 3. Don't export the node-local data if it's empty --- .../java/org/qortal/block/BlockChain.java | 3 +- .../qortal/repository/RepositoryFactory.java | 2 + .../qortal/repository/RepositoryManager.java | 7 ++++ .../hsqldb/HSQLDBDatabaseUpdates.java | 40 +++++++++++++------ .../hsqldb/HSQLDBRepositoryFactory.java | 8 +++- 5 files changed, 46 insertions(+), 14 deletions(-) diff --git a/src/main/java/org/qortal/block/BlockChain.java b/src/main/java/org/qortal/block/BlockChain.java index 520f8952..e631f930 100644 --- a/src/main/java/org/qortal/block/BlockChain.java +++ b/src/main/java/org/qortal/block/BlockChain.java @@ -531,7 +531,8 @@ public class BlockChain { private static void rebuildBlockchain() throws DataException { // (Re)build repository - RepositoryManager.rebuild(); + if (!RepositoryManager.wasPristineAtOpen()) + RepositoryManager.rebuild(); try (final Repository repository = RepositoryManager.getRepository()) { GenesisBlock genesisBlock = GenesisBlock.getInstance(repository); diff --git a/src/main/java/org/qortal/repository/RepositoryFactory.java b/src/main/java/org/qortal/repository/RepositoryFactory.java index 94c9627e..e5b29d1b 100644 --- a/src/main/java/org/qortal/repository/RepositoryFactory.java +++ b/src/main/java/org/qortal/repository/RepositoryFactory.java @@ -2,6 +2,8 @@ package org.qortal.repository; public interface RepositoryFactory { + public boolean wasPristineAtOpen(); + public RepositoryFactory reopen() throws DataException; public Repository getRepository() throws DataException; diff --git a/src/main/java/org/qortal/repository/RepositoryManager.java b/src/main/java/org/qortal/repository/RepositoryManager.java index e3427954..9f5cf239 100644 --- a/src/main/java/org/qortal/repository/RepositoryManager.java +++ b/src/main/java/org/qortal/repository/RepositoryManager.java @@ -8,6 +8,13 @@ public abstract class RepositoryManager { repositoryFactory = newRepositoryFactory; } + public static boolean wasPristineAtOpen() throws DataException { + if (repositoryFactory == null) + throw new DataException("No repository available"); + + return repositoryFactory.wasPristineAtOpen(); + } + public static Repository getRepository() throws DataException { if (repositoryFactory == null) throw new DataException("No repository available"); diff --git a/src/main/java/org/qortal/repository/hsqldb/HSQLDBDatabaseUpdates.java b/src/main/java/org/qortal/repository/hsqldb/HSQLDBDatabaseUpdates.java index eca8d856..e60616d6 100644 --- a/src/main/java/org/qortal/repository/hsqldb/HSQLDBDatabaseUpdates.java +++ b/src/main/java/org/qortal/repository/hsqldb/HSQLDBDatabaseUpdates.java @@ -18,11 +18,16 @@ public class HSQLDBDatabaseUpdates { /** * Apply any incremental changes to database schema. * + * @return true if database was non-existent/empty, false otherwise * @throws SQLException */ - public static void updateDatabase(Connection connection) throws SQLException { - while (databaseUpdating(connection)) + public static boolean updateDatabase(Connection connection) throws SQLException { + final boolean wasPristine = fetchDatabaseVersion(connection) == 0; + + while (databaseUpdating(connection, wasPristine)) incrementDatabaseVersion(connection); + + return wasPristine; } /** @@ -40,23 +45,21 @@ public class HSQLDBDatabaseUpdates { /** * Fetch current version of database schema. * - * @return int, 0 if no schema yet + * @return database version, or 0 if no schema yet * @throws SQLException */ private static int fetchDatabaseVersion(Connection connection) throws SQLException { - int databaseVersion = 0; - try (Statement stmt = connection.createStatement()) { if (stmt.execute("SELECT version FROM DatabaseInfo")) try (ResultSet resultSet = stmt.getResultSet()) { if (resultSet.next()) - databaseVersion = resultSet.getInt(1); + return resultSet.getInt(1); } } catch (SQLException e) { // empty database } - return databaseVersion; + return 0; } /** @@ -65,7 +68,7 @@ public class HSQLDBDatabaseUpdates { * @return true - if a schema update happened, false otherwise * @throws SQLException */ - private static boolean databaseUpdating(Connection connection) throws SQLException { + private static boolean databaseUpdating(Connection connection, boolean wasPristine) throws SQLException { int databaseVersion = fetchDatabaseVersion(connection); try (Statement stmt = connection.createStatement()) { @@ -695,11 +698,24 @@ public class HSQLDBDatabaseUpdates { case 30: // Split AT state data off to new table for better performance/management. - if (!"mem".equals(HSQLDBRepository.getDbPathname(connection.getMetaData().getURL()))) { + if (!wasPristine && !"mem".equals(HSQLDBRepository.getDbPathname(connection.getMetaData().getURL()))) { // First, backup node-local data in case user wants to avoid long reshape and use bootstrap instead - stmt.execute("PERFORM EXPORT SCRIPT FOR TABLE MintingAccounts DATA TO 'MintingAccounts.script'"); - stmt.execute("PERFORM EXPORT SCRIPT FOR TABLE TradeBotStates DATA TO 'TradeBotStates.script'"); - LOGGER.info("Exported sensitive/node-local data: minting keys and trade bot states"); + try (ResultSet resultSet = stmt.executeQuery("SELECT COUNT(*) FROM MintingAccounts")) { + int rowCount = resultSet.next() ? resultSet.getInt(1) : 0; + if (rowCount > 0) { + stmt.execute("PERFORM EXPORT SCRIPT FOR TABLE MintingAccounts DATA TO 'MintingAccounts.script'"); + LOGGER.info("Exported sensitive/node-local minting keys into MintingAccounts.script"); + } + } + + try (ResultSet resultSet = stmt.executeQuery("SELECT COUNT(*) FROM TradeBotStates")) { + int rowCount = resultSet.next() ? resultSet.getInt(1) : 0; + if (rowCount > 0) { + stmt.execute("PERFORM EXPORT SCRIPT FOR TABLE TradeBotStates DATA TO 'TradeBotStates.script'"); + LOGGER.info("Exported sensitive/node-local trade-bot states into TradeBotStates.script"); + } + } + LOGGER.info("If following reshape takes too long, use bootstrap and import node-local data using API's POST /admin/repository/data"); } diff --git a/src/main/java/org/qortal/repository/hsqldb/HSQLDBRepositoryFactory.java b/src/main/java/org/qortal/repository/hsqldb/HSQLDBRepositoryFactory.java index 8561b698..81bf320b 100644 --- a/src/main/java/org/qortal/repository/hsqldb/HSQLDBRepositoryFactory.java +++ b/src/main/java/org/qortal/repository/hsqldb/HSQLDBRepositoryFactory.java @@ -25,6 +25,7 @@ public class HSQLDBRepositoryFactory implements RepositoryFactory { private String connectionUrl; private HSQLDBPool connectionPool; + private final boolean wasPristine; /** * Constructs new RepositoryFactory using passed connectionUrl. @@ -65,12 +66,17 @@ public class HSQLDBRepositoryFactory implements RepositoryFactory { // Perform DB updates? try (final Connection connection = this.connectionPool.getConnection()) { - HSQLDBDatabaseUpdates.updateDatabase(connection); + this.wasPristine = HSQLDBDatabaseUpdates.updateDatabase(connection); } catch (SQLException e) { throw new DataException("Repository initialization error", e); } } + @Override + public boolean wasPristineAtOpen() { + return this.wasPristine; + } + @Override public RepositoryFactory reopen() throws DataException { return new HSQLDBRepositoryFactory(this.connectionUrl);