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 {
// (Re)build repository
RepositoryManager.rebuild();
if (!RepositoryManager.wasPristineAtOpen())
RepositoryManager.rebuild();
try (final Repository repository = RepositoryManager.getRepository()) {
GenesisBlock genesisBlock = GenesisBlock.getInstance(repository);

View File

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

View File

@ -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");

View File

@ -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");
}

View File

@ -25,6 +25,7 @@ public class HSQLDBRepositoryFactory implements RepositoryFactory {
private String connectionUrl;
private HSQLDBPool connectionPool;
private final boolean wasPristine;
/**
* Constructs new RepositoryFactory using passed <tt>connectionUrl</tt>.
@ -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);