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
This commit is contained in:
catbref 2020-11-09 10:16:37 +00:00
parent 806baa6ae4
commit 1f409235e4
5 changed files with 46 additions and 14 deletions

View File

@ -531,7 +531,8 @@ public class BlockChain {
private static void rebuildBlockchain() throws DataException { private static void rebuildBlockchain() throws DataException {
// (Re)build repository // (Re)build repository
RepositoryManager.rebuild(); if (!RepositoryManager.wasPristineAtOpen())
RepositoryManager.rebuild();
try (final Repository repository = RepositoryManager.getRepository()) { try (final Repository repository = RepositoryManager.getRepository()) {
GenesisBlock genesisBlock = GenesisBlock.getInstance(repository); GenesisBlock genesisBlock = GenesisBlock.getInstance(repository);

View File

@ -2,6 +2,8 @@ package org.qortal.repository;
public interface RepositoryFactory { public interface RepositoryFactory {
public boolean wasPristineAtOpen();
public RepositoryFactory reopen() throws DataException; public RepositoryFactory reopen() throws DataException;
public Repository getRepository() throws DataException; public Repository getRepository() throws DataException;

View File

@ -8,6 +8,13 @@ public abstract class RepositoryManager {
repositoryFactory = newRepositoryFactory; 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 { public static Repository getRepository() throws DataException {
if (repositoryFactory == null) if (repositoryFactory == null)
throw new DataException("No repository available"); throw new DataException("No repository available");

View File

@ -18,11 +18,16 @@ public class HSQLDBDatabaseUpdates {
/** /**
* Apply any incremental changes to database schema. * Apply any incremental changes to database schema.
* *
* @return true if database was non-existent/empty, false otherwise
* @throws SQLException * @throws SQLException
*/ */
public static void updateDatabase(Connection connection) throws SQLException { public static boolean updateDatabase(Connection connection) throws SQLException {
while (databaseUpdating(connection)) final boolean wasPristine = fetchDatabaseVersion(connection) == 0;
while (databaseUpdating(connection, wasPristine))
incrementDatabaseVersion(connection); incrementDatabaseVersion(connection);
return wasPristine;
} }
/** /**
@ -40,23 +45,21 @@ public class HSQLDBDatabaseUpdates {
/** /**
* Fetch current version of database schema. * Fetch current version of database schema.
* *
* @return int, 0 if no schema yet * @return database version, or 0 if no schema yet
* @throws SQLException * @throws SQLException
*/ */
private static int fetchDatabaseVersion(Connection connection) throws SQLException { private static int fetchDatabaseVersion(Connection connection) throws SQLException {
int databaseVersion = 0;
try (Statement stmt = connection.createStatement()) { try (Statement stmt = connection.createStatement()) {
if (stmt.execute("SELECT version FROM DatabaseInfo")) if (stmt.execute("SELECT version FROM DatabaseInfo"))
try (ResultSet resultSet = stmt.getResultSet()) { try (ResultSet resultSet = stmt.getResultSet()) {
if (resultSet.next()) if (resultSet.next())
databaseVersion = resultSet.getInt(1); return resultSet.getInt(1);
} }
} catch (SQLException e) { } catch (SQLException e) {
// empty database // empty database
} }
return databaseVersion; return 0;
} }
/** /**
@ -65,7 +68,7 @@ public class HSQLDBDatabaseUpdates {
* @return true - if a schema update happened, false otherwise * @return true - if a schema update happened, false otherwise
* @throws SQLException * @throws SQLException
*/ */
private static boolean databaseUpdating(Connection connection) throws SQLException { private static boolean databaseUpdating(Connection connection, boolean wasPristine) throws SQLException {
int databaseVersion = fetchDatabaseVersion(connection); int databaseVersion = fetchDatabaseVersion(connection);
try (Statement stmt = connection.createStatement()) { try (Statement stmt = connection.createStatement()) {
@ -695,11 +698,24 @@ public class HSQLDBDatabaseUpdates {
case 30: case 30:
// Split AT state data off to new table for better performance/management. // 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 // 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'"); try (ResultSet resultSet = stmt.executeQuery("SELECT COUNT(*) FROM MintingAccounts")) {
stmt.execute("PERFORM EXPORT SCRIPT FOR TABLE TradeBotStates DATA TO 'TradeBotStates.script'"); int rowCount = resultSet.next() ? resultSet.getInt(1) : 0;
LOGGER.info("Exported sensitive/node-local data: minting keys and trade bot states"); 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"); LOGGER.info("If following reshape takes too long, use bootstrap and import node-local data using API's POST /admin/repository/data");
} }

View File

@ -25,6 +25,7 @@ public class HSQLDBRepositoryFactory implements RepositoryFactory {
private String connectionUrl; private String connectionUrl;
private HSQLDBPool connectionPool; private HSQLDBPool connectionPool;
private final boolean wasPristine;
/** /**
* Constructs new RepositoryFactory using passed <tt>connectionUrl</tt>. * Constructs new RepositoryFactory using passed <tt>connectionUrl</tt>.
@ -65,12 +66,17 @@ public class HSQLDBRepositoryFactory implements RepositoryFactory {
// Perform DB updates? // Perform DB updates?
try (final Connection connection = this.connectionPool.getConnection()) { try (final Connection connection = this.connectionPool.getConnection()) {
HSQLDBDatabaseUpdates.updateDatabase(connection); this.wasPristine = HSQLDBDatabaseUpdates.updateDatabase(connection);
} catch (SQLException e) { } catch (SQLException e) {
throw new DataException("Repository initialization error", e); throw new DataException("Repository initialization error", e);
} }
} }
@Override
public boolean wasPristineAtOpen() {
return this.wasPristine;
}
@Override @Override
public RepositoryFactory reopen() throws DataException { public RepositoryFactory reopen() throws DataException {
return new HSQLDBRepositoryFactory(this.connectionUrl); return new HSQLDBRepositoryFactory(this.connectionUrl);