From e93b26204903ef94a63f8fedfe522ff52796e286 Mon Sep 17 00:00:00 2001 From: catbref Date: Tue, 6 Oct 2020 16:38:22 +0100 Subject: [PATCH] WIP: starting to split into two DBs --- .../org/qortal/RepositoryMaintenance.java | 3 +- .../org/qortal/controller/Controller.java | 9 +- .../repository/hsqldb/HSQLDBRepository.java | 5 +- .../hsqldb/HSQLDBRepositoryFactory.java | 111 ++++++++++++++---- .../test/apps/DecodeOnlineAccounts.java | 3 +- .../java/org/qortal/test/apps/orphan.java | 3 +- .../org/qortal/test/btcacct/BuildP2SH.java | 3 +- .../org/qortal/test/btcacct/CheckP2SH.java | 3 +- .../org/qortal/test/btcacct/DeployAT.java | 3 +- .../java/org/qortal/test/btcacct/Redeem.java | 3 +- .../java/org/qortal/test/btcacct/Refund.java | 3 +- .../java/org/qortal/test/common/Common.java | 6 +- 12 files changed, 99 insertions(+), 56 deletions(-) diff --git a/src/main/java/org/qortal/RepositoryMaintenance.java b/src/main/java/org/qortal/RepositoryMaintenance.java index c3ae0616..c0d5626d 100644 --- a/src/main/java/org/qortal/RepositoryMaintenance.java +++ b/src/main/java/org/qortal/RepositoryMaintenance.java @@ -6,7 +6,6 @@ import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; import org.bouncycastle.jce.provider.BouncyCastleProvider; import org.bouncycastle.jsse.provider.BouncyCastleJsseProvider; -import org.qortal.controller.Controller; import org.qortal.repository.DataException; import org.qortal.repository.Repository; import org.qortal.repository.RepositoryFactory; @@ -42,7 +41,7 @@ public class RepositoryMaintenance { LOGGER.info("Opening repository"); try { - RepositoryFactory repositoryFactory = new HSQLDBRepositoryFactory(Controller.getRepositoryUrl()); + RepositoryFactory repositoryFactory = new HSQLDBRepositoryFactory(Settings.getInstance().getRepositoryPath()); RepositoryManager.setRepositoryFactory(repositoryFactory); } catch (DataException e) { // If exception has no cause then repository is in use by some other process. diff --git a/src/main/java/org/qortal/controller/Controller.java b/src/main/java/org/qortal/controller/Controller.java index a7d39d3c..0f27c2bc 100644 --- a/src/main/java/org/qortal/controller/Controller.java +++ b/src/main/java/org/qortal/controller/Controller.java @@ -1,7 +1,6 @@ package org.qortal.controller; import java.awt.TrayIcon.MessageType; -import java.io.File; import java.io.IOException; import java.io.InputStream; import java.security.SecureRandom; @@ -108,7 +107,7 @@ public class Controller extends Thread { private static final long MISBEHAVIOUR_COOLOFF = 10 * 60 * 1000L; // ms private static final int MAX_BLOCKCHAIN_TIP_AGE = 5; // blocks private static final Object shutdownLock = new Object(); - private static final String repositoryUrlTemplate = "jdbc:hsqldb:file:%s" + File.separator + "blockchain;create=true;hsqldb.full_log_replay=true"; + private static final long ARBITRARY_REQUEST_TIMEOUT = 5 * 1000L; // ms private static final long NTP_PRE_SYNC_CHECK_PERIOD = 5 * 1000L; // ms private static final long NTP_POST_SYNC_CHECK_PERIOD = 5 * 60 * 1000L; // ms @@ -224,10 +223,6 @@ public class Controller extends Thread { // Getters / setters - public static String getRepositoryUrl() { - return String.format(repositoryUrlTemplate, Settings.getInstance().getRepositoryPath()); - } - public long getBuildTimestamp() { return this.buildTimestamp; } @@ -315,7 +310,7 @@ public class Controller extends Thread { LOGGER.info("Starting repository"); try { - RepositoryFactory repositoryFactory = new HSQLDBRepositoryFactory(getRepositoryUrl()); + RepositoryFactory repositoryFactory = new HSQLDBRepositoryFactory(Settings.getInstance().getRepositoryPath()); RepositoryManager.setRepositoryFactory(repositoryFactory); } catch (DataException e) { // If exception has no cause then repository is in use by some other process. diff --git a/src/main/java/org/qortal/repository/hsqldb/HSQLDBRepository.java b/src/main/java/org/qortal/repository/hsqldb/HSQLDBRepository.java index 760d1ebc..f02ae3e4 100644 --- a/src/main/java/org/qortal/repository/hsqldb/HSQLDBRepository.java +++ b/src/main/java/org/qortal/repository/hsqldb/HSQLDBRepository.java @@ -76,9 +76,8 @@ public class HSQLDBRepository implements Repository { // Constructors - // NB: no visibility modifier so only callable from within same package - /* package */ HSQLDBRepository(Connection connection) throws DataException { - this.connection = connection; + /*package*/ HSQLDBRepository(Connection blockchainConnection, Connection nodeLocalConnection) throws DataException { + this.connection = blockchainConnection; this.slowQueryThreshold = Settings.getInstance().getSlowQueryThreshold(); if (this.slowQueryThreshold != null) diff --git a/src/main/java/org/qortal/repository/hsqldb/HSQLDBRepositoryFactory.java b/src/main/java/org/qortal/repository/hsqldb/HSQLDBRepositoryFactory.java index 8561b698..bdeffa11 100644 --- a/src/main/java/org/qortal/repository/hsqldb/HSQLDBRepositoryFactory.java +++ b/src/main/java/org/qortal/repository/hsqldb/HSQLDBRepositoryFactory.java @@ -1,5 +1,6 @@ package org.qortal.repository.hsqldb; +import java.io.File; import java.sql.Connection; import java.sql.DriverManager; import java.sql.SQLException; @@ -20,25 +21,44 @@ public class HSQLDBRepositoryFactory implements RepositoryFactory { private static final Logger LOGGER = LogManager.getLogger(HSQLDBRepositoryFactory.class); private static final int POOL_SIZE = 100; + private static final String blockchainFileUrlTemplate = "jdbc:hsqldb:file:%s" + File.separator + "blockchain;create=true;hsqldb.full_log_replay=true"; + private static final String blockchainMemoryUrl = "jdbc:hsqldb:mem:blockchain"; + + private static final String nodeLocalFileUrlTemplate = "jdbc:hsqldb:file:%s" + File.separator + "node-local;create=true;hsqldb.full_log_replay=true"; + private static final String nodeLocalMemoryUrl = "jdbc:hsqldb:mem:node-local"; + /** Log getConnection() calls that take longer than this. (ms) */ private static final long SLOW_CONNECTION_THRESHOLD = 1000L; - private String connectionUrl; - private HSQLDBPool connectionPool; + private final String repositoryPath; + + private final String blockchainConnectionUrl; + private final String nodeLocalConnectionUrl; + + private final HSQLDBPool blockchainConnectionPool; + private final HSQLDBPool nodeLocalConnectionPool; /** - * Constructs new RepositoryFactory using passed connectionUrl. + * Constructs new RepositoryFactory using passed repository path, or null for in-memory. * - * @param connectionUrl + * @param repositoryPath * @throws DataException without throwable if repository in use by another process. * @throws DataException with throwable if repository cannot be opened for some other reason. */ - public HSQLDBRepositoryFactory(String connectionUrl) throws DataException { + public HSQLDBRepositoryFactory(String repositoryPath) throws DataException { + this.repositoryPath = repositoryPath; + // one-time initialization goes in here - this.connectionUrl = connectionUrl; + if (repositoryPath != null) { + this.blockchainConnectionUrl = String.format(blockchainFileUrlTemplate, repositoryPath); + this.nodeLocalConnectionUrl = String.format(nodeLocalFileUrlTemplate, repositoryPath); + } else { + this.blockchainConnectionUrl = blockchainMemoryUrl; + this.nodeLocalConnectionUrl = nodeLocalMemoryUrl; + } // Check no-one else is accessing database - try (Connection connection = DriverManager.getConnection(this.connectionUrl)) { + try (Connection connection = DriverManager.getConnection(this.blockchainConnectionUrl)) { // We only need to check we can obtain connection. It will be auto-closed. } catch (SQLException e) { Throwable cause = e.getCause(); @@ -53,19 +73,27 @@ public class HSQLDBRepositoryFactory implements RepositoryFactory { throw new DataException("Unable to read repository: " + e.getMessage(), e); // Attempt recovery? - HSQLDBRepository.attemptRecovery(connectionUrl); + HSQLDBRepository.attemptRecovery(repositoryPath); } - this.connectionPool = new HSQLDBPool(POOL_SIZE); - this.connectionPool.setUrl(this.connectionUrl); + this.blockchainConnectionPool = new HSQLDBPool(POOL_SIZE); + this.blockchainConnectionPool.setUrl(this.blockchainConnectionUrl); - Properties properties = new Properties(); - properties.setProperty("close_result", "true"); // Auto-close old ResultSet if Statement creates new ResultSet - this.connectionPool.setProperties(properties); + Properties blockchainProperties = new Properties(); + blockchainProperties.setProperty("close_result", "true"); // Auto-close old ResultSet if Statement creates new ResultSet + this.blockchainConnectionPool.setProperties(blockchainProperties); + + this.nodeLocalConnectionPool = new HSQLDBPool(POOL_SIZE); + this.nodeLocalConnectionPool.setUrl(this.nodeLocalConnectionUrl); + + Properties nodeLocalProperties = new Properties(); + nodeLocalProperties.setProperty("close_result", "true"); // Auto-close old ResultSet if Statement creates new ResultSet + this.nodeLocalConnectionPool.setProperties(nodeLocalProperties); // Perform DB updates? - try (final Connection connection = this.connectionPool.getConnection()) { - HSQLDBDatabaseUpdates.updateDatabase(connection); + try (final Connection blockchainConnection = this.blockchainConnectionPool.getConnection(); + final Connection nodeLocalConnection = this.nodeLocalConnectionPool.getConnection()) { + HSQLDBDatabaseUpdates.updateDatabase(blockchainConnection); } catch (SQLException e) { throw new DataException("Repository initialization error", e); } @@ -73,13 +101,13 @@ public class HSQLDBRepositoryFactory implements RepositoryFactory { @Override public RepositoryFactory reopen() throws DataException { - return new HSQLDBRepositoryFactory(this.connectionUrl); + return new HSQLDBRepositoryFactory(this.repositoryPath); } @Override public Repository getRepository() throws DataException { try { - return new HSQLDBRepository(this.getConnection()); + return new HSQLDBRepository(this.getBlockchainConnection(), this.getNodeLocalConnection()); } catch (SQLException e) { throw new DataException("Repository instantiation error", e); } @@ -88,27 +116,54 @@ public class HSQLDBRepositoryFactory implements RepositoryFactory { @Override public Repository tryRepository() throws DataException { try { - return new HSQLDBRepository(this.tryConnection()); + Connection blockchainConnection = this.tryBlockchainConnection(); + if (blockchainConnection == null) + return null; + + Connection nodeLocalConnection = this.tryNodeLocalConnection(); + if (nodeLocalConnection == null) + return null; + + return new HSQLDBRepository(blockchainConnection, nodeLocalConnection); } catch (SQLException e) { throw new DataException("Repository instantiation error", e); } } - private Connection getConnection() throws SQLException { + private Connection getBlockchainConnection() throws SQLException { + return getConnection(this.blockchainConnectionPool, "blockchain"); + } + + private Connection tryBlockchainConnection() throws SQLException { + return tryConnection(this.blockchainConnectionPool); + } + + private Connection getNodeLocalConnection() throws SQLException { + return getConnection(this.nodeLocalConnectionPool, "node-local"); + } + + private Connection tryNodeLocalConnection() throws SQLException { + return tryConnection(this.nodeLocalConnectionPool); + } + + private Connection getConnection(HSQLDBPool pool, String poolName) throws SQLException { final long before = System.currentTimeMillis(); - Connection connection = this.connectionPool.getConnection(); + Connection connection = pool.getConnection(); final long delay = System.currentTimeMillis() - before; if (delay > SLOW_CONNECTION_THRESHOLD) // This could be an indication of excessive repository use, or insufficient pool size - LOGGER.warn(() -> String.format("Fetching repository connection from pool took %dms (threshold: %dms)", delay, SLOW_CONNECTION_THRESHOLD)); + LOGGER.warn(() -> String.format("Fetching repository connection from %s pool took %dms (threshold: %dms)", + poolName, + delay, + SLOW_CONNECTION_THRESHOLD)); setupConnection(connection); return connection; } - private Connection tryConnection() throws SQLException { - Connection connection = this.connectionPool.tryConnection(); + private Connection tryConnection(HSQLDBPool pool) throws SQLException { + Connection connection = pool.tryConnection(); if (connection == null) return null; @@ -126,10 +181,16 @@ public class HSQLDBRepositoryFactory implements RepositoryFactory { public void close() throws DataException { try { // Close all existing connections immediately - this.connectionPool.close(0); + this.blockchainConnectionPool.close(0); + this.nodeLocalConnectionPool.close(0); // Now that all connections are closed, create a dedicated connection to shut down repository - try (Connection connection = DriverManager.getConnection(this.connectionUrl); + try (Connection connection = DriverManager.getConnection(this.blockchainConnectionUrl); + Statement stmt = connection.createStatement()) { + stmt.execute("SHUTDOWN"); + } + + try (Connection connection = DriverManager.getConnection(this.nodeLocalConnectionUrl); Statement stmt = connection.createStatement()) { stmt.execute("SHUTDOWN"); } diff --git a/src/test/java/org/qortal/test/apps/DecodeOnlineAccounts.java b/src/test/java/org/qortal/test/apps/DecodeOnlineAccounts.java index 1781f719..d62eb104 100644 --- a/src/test/java/org/qortal/test/apps/DecodeOnlineAccounts.java +++ b/src/test/java/org/qortal/test/apps/DecodeOnlineAccounts.java @@ -7,7 +7,6 @@ import org.bitcoinj.core.Base58; import org.bouncycastle.jce.provider.BouncyCastleProvider; import org.bouncycastle.jsse.provider.BouncyCastleJsseProvider; import org.qortal.block.BlockChain; -import org.qortal.controller.Controller; import org.qortal.data.account.RewardShareData; import org.qortal.gui.Gui; import org.qortal.repository.DataException; @@ -65,7 +64,7 @@ public class DecodeOnlineAccounts { } try { - RepositoryFactory repositoryFactory = new HSQLDBRepositoryFactory(Controller.getRepositoryUrl()); + RepositoryFactory repositoryFactory = new HSQLDBRepositoryFactory(Settings.getInstance().getRepositoryPath()); RepositoryManager.setRepositoryFactory(repositoryFactory); } catch (DataException e) { System.err.println("Couldn't connect to repository: " + e.getMessage()); diff --git a/src/test/java/org/qortal/test/apps/orphan.java b/src/test/java/org/qortal/test/apps/orphan.java index f847f98d..f081a880 100644 --- a/src/test/java/org/qortal/test/apps/orphan.java +++ b/src/test/java/org/qortal/test/apps/orphan.java @@ -3,7 +3,6 @@ import java.security.Security; import org.bouncycastle.jce.provider.BouncyCastleProvider; import org.qortal.block.BlockChain; -import org.qortal.controller.Controller; import org.qortal.repository.DataException; import org.qortal.repository.RepositoryFactory; import org.qortal.repository.RepositoryManager; @@ -32,7 +31,7 @@ public class orphan { int targetHeight = Integer.parseInt(args[argIndex]); try { - RepositoryFactory repositoryFactory = new HSQLDBRepositoryFactory(Controller.getRepositoryUrl()); + RepositoryFactory repositoryFactory = new HSQLDBRepositoryFactory(Settings.getInstance().getRepositoryPath()); RepositoryManager.setRepositoryFactory(repositoryFactory); } catch (DataException e) { System.err.println("Couldn't connect to repository: " + e.getMessage()); diff --git a/src/test/java/org/qortal/test/btcacct/BuildP2SH.java b/src/test/java/org/qortal/test/btcacct/BuildP2SH.java index 6b6b16e1..d7b3992c 100644 --- a/src/test/java/org/qortal/test/btcacct/BuildP2SH.java +++ b/src/test/java/org/qortal/test/btcacct/BuildP2SH.java @@ -11,7 +11,6 @@ import org.bitcoinj.core.LegacyAddress; import org.bitcoinj.core.NetworkParameters; import org.bitcoinj.script.Script.ScriptType; import org.bouncycastle.jce.provider.BouncyCastleProvider; -import org.qortal.controller.Controller; import org.qortal.crosschain.BTC; import org.qortal.crosschain.BTCP2SH; import org.qortal.crypto.Crypto; @@ -85,7 +84,7 @@ public class BuildP2SH { } try { - RepositoryFactory repositoryFactory = new HSQLDBRepositoryFactory(Controller.getRepositoryUrl()); + RepositoryFactory repositoryFactory = new HSQLDBRepositoryFactory(Settings.getInstance().getRepositoryPath()); RepositoryManager.setRepositoryFactory(repositoryFactory); } catch (DataException e) { throw new RuntimeException("Repository startup issue: " + e.getMessage()); diff --git a/src/test/java/org/qortal/test/btcacct/CheckP2SH.java b/src/test/java/org/qortal/test/btcacct/CheckP2SH.java index e7d96bc1..0556fde8 100644 --- a/src/test/java/org/qortal/test/btcacct/CheckP2SH.java +++ b/src/test/java/org/qortal/test/btcacct/CheckP2SH.java @@ -13,7 +13,6 @@ import org.bitcoinj.core.NetworkParameters; import org.bitcoinj.core.TransactionOutput; import org.bitcoinj.script.Script.ScriptType; import org.bouncycastle.jce.provider.BouncyCastleProvider; -import org.qortal.controller.Controller; import org.qortal.crosschain.BTC; import org.qortal.crosschain.BTCP2SH; import org.qortal.crosschain.BitcoinException; @@ -94,7 +93,7 @@ public class CheckP2SH { } try { - RepositoryFactory repositoryFactory = new HSQLDBRepositoryFactory(Controller.getRepositoryUrl()); + RepositoryFactory repositoryFactory = new HSQLDBRepositoryFactory(Settings.getInstance().getRepositoryPath()); RepositoryManager.setRepositoryFactory(repositoryFactory); } catch (DataException e) { throw new RuntimeException("Repository startup issue: " + e.getMessage()); diff --git a/src/test/java/org/qortal/test/btcacct/DeployAT.java b/src/test/java/org/qortal/test/btcacct/DeployAT.java index ef5a0295..9e8ba4ad 100644 --- a/src/test/java/org/qortal/test/btcacct/DeployAT.java +++ b/src/test/java/org/qortal/test/btcacct/DeployAT.java @@ -5,7 +5,6 @@ import java.security.Security; import org.bouncycastle.jce.provider.BouncyCastleProvider; import org.qortal.account.PrivateKeyAccount; import org.qortal.asset.Asset; -import org.qortal.controller.Controller; import org.qortal.crosschain.BTCACCT; import org.qortal.data.transaction.BaseTransactionData; import org.qortal.data.transaction.DeployAtTransactionData; @@ -95,7 +94,7 @@ public class DeployAT { } try { - RepositoryFactory repositoryFactory = new HSQLDBRepositoryFactory(Controller.getRepositoryUrl()); + RepositoryFactory repositoryFactory = new HSQLDBRepositoryFactory(Settings.getInstance().getRepositoryPath()); RepositoryManager.setRepositoryFactory(repositoryFactory); } catch (DataException e) { throw new RuntimeException("Repository startup issue: " + e.getMessage()); diff --git a/src/test/java/org/qortal/test/btcacct/Redeem.java b/src/test/java/org/qortal/test/btcacct/Redeem.java index 0ca20608..fbb34c5f 100644 --- a/src/test/java/org/qortal/test/btcacct/Redeem.java +++ b/src/test/java/org/qortal/test/btcacct/Redeem.java @@ -16,7 +16,6 @@ import org.bitcoinj.core.Transaction; import org.bitcoinj.core.TransactionOutput; import org.bitcoinj.script.Script.ScriptType; import org.bouncycastle.jce.provider.BouncyCastleProvider; -import org.qortal.controller.Controller; import org.qortal.crosschain.BTC; import org.qortal.crosschain.BTCP2SH; import org.qortal.crosschain.BitcoinException; @@ -98,7 +97,7 @@ public class Redeem { } try { - RepositoryFactory repositoryFactory = new HSQLDBRepositoryFactory(Controller.getRepositoryUrl()); + RepositoryFactory repositoryFactory = new HSQLDBRepositoryFactory(Settings.getInstance().getRepositoryPath()); RepositoryManager.setRepositoryFactory(repositoryFactory); } catch (DataException e) { throw new RuntimeException("Repository startup issue: " + e.getMessage()); diff --git a/src/test/java/org/qortal/test/btcacct/Refund.java b/src/test/java/org/qortal/test/btcacct/Refund.java index 184985d9..368c0bcc 100644 --- a/src/test/java/org/qortal/test/btcacct/Refund.java +++ b/src/test/java/org/qortal/test/btcacct/Refund.java @@ -16,7 +16,6 @@ import org.bitcoinj.core.Transaction; import org.bitcoinj.core.TransactionOutput; import org.bitcoinj.script.Script.ScriptType; import org.bouncycastle.jce.provider.BouncyCastleProvider; -import org.qortal.controller.Controller; import org.qortal.crosschain.BTC; import org.qortal.crosschain.BTCP2SH; import org.qortal.crosschain.BitcoinException; @@ -98,7 +97,7 @@ public class Refund { } try { - RepositoryFactory repositoryFactory = new HSQLDBRepositoryFactory(Controller.getRepositoryUrl()); + RepositoryFactory repositoryFactory = new HSQLDBRepositoryFactory(Settings.getInstance().getRepositoryPath()); RepositoryManager.setRepositoryFactory(repositoryFactory); } catch (DataException e) { throw new RuntimeException("Repository startup issue: " + e.getMessage()); diff --git a/src/test/java/org/qortal/test/common/Common.java b/src/test/java/org/qortal/test/common/Common.java index 24c86690..18a21ba2 100644 --- a/src/test/java/org/qortal/test/common/Common.java +++ b/src/test/java/org/qortal/test/common/Common.java @@ -46,10 +46,6 @@ public class Common { private static final Logger LOGGER = LogManager.getLogger(Common.class); - public static final String testConnectionUrl = "jdbc:hsqldb:mem:testdb"; - // For debugging, use this instead to write DB to disk for examination: - // public static final String testConnectionUrl = "jdbc:hsqldb:file:testdb/blockchain;create=true"; - public static final String testSettingsFilename = "test-settings-v2.json"; static { @@ -188,7 +184,7 @@ public class Common { @BeforeClass public static void setRepository() throws DataException { - RepositoryFactory repositoryFactory = new HSQLDBRepositoryFactory(testConnectionUrl); + RepositoryFactory repositoryFactory = new HSQLDBRepositoryFactory(null); RepositoryManager.setRepositoryFactory(repositoryFactory); }