diff --git a/src/main/java/org/qora/account/Account.java b/src/main/java/org/qora/account/Account.java index 50da8c7b..dacb8c9e 100644 --- a/src/main/java/org/qora/account/Account.java +++ b/src/main/java/org/qora/account/Account.java @@ -163,36 +163,18 @@ public class Account { } /** - * Fetch last reference for account, considering unconfirmed transactions. + * Fetch last reference for account, considering unconfirmed transactions only, or return null. *

* NOTE: a repository savepoint may be used during execution. * - * @return byte[] reference, or null if no reference or account not found. + * @return byte[] reference, or null if no unconfirmed transactions for this account. * @throws DataException */ public byte[] getUnconfirmedLastReference() throws DataException { - byte[] reference = getUnconfirmedLastReference(null); - - if (reference == null) - // No unconfirmed transactions - reference = getLastReference(); - - return reference; - } - - /** - * Fetch last reference for account, considering unconfirmed transactions only, or return defaultReference. - *

- * NOTE: a repository savepoint may be used during execution. - * - * @return byte[] reference, or defaultReference if no unconfirmed transactions for this account. - * @throws DataException - */ - public byte[] getUnconfirmedLastReference(byte[] defaultReference) throws DataException { // Newest unconfirmed transaction takes priority List unconfirmedTransactions = Transaction.getUnconfirmedTransactions(repository); - byte[] reference = defaultReference; + byte[] reference = null; for (TransactionData transactionData : unconfirmedTransactions) { String address = PublicKeyAccount.getAddress(transactionData.getCreatorPublicKey()); diff --git a/src/main/java/org/qora/api/resource/AddressesResource.java b/src/main/java/org/qora/api/resource/AddressesResource.java index 4d3f4dc8..dc42e6ff 100644 --- a/src/main/java/org/qora/api/resource/AddressesResource.java +++ b/src/main/java/org/qora/api/resource/AddressesResource.java @@ -78,7 +78,7 @@ public class AddressesResource { else { // Unconfirmed transactions could update lastReference Account account = new Account(repository, address); - byte[] unconfirmedLastReference = account.getUnconfirmedLastReference(null); + byte[] unconfirmedLastReference = account.getUnconfirmedLastReference(); if (unconfirmedLastReference != null) accountData.setReference(unconfirmedLastReference); } @@ -109,9 +109,12 @@ public class AddressesResource { throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.INVALID_ADDRESS); byte[] lastReference = null; + try (final Repository repository = RepositoryManager.getRepository()) { Account account = new Account(repository, address); lastReference = account.getUnconfirmedLastReference(); + if (lastReference == null) + lastReference = account.getLastReference(); } catch (ApiException e) { throw e; } catch (DataException e) { diff --git a/src/main/java/org/qora/repository/hsqldb/HSQLDBRepository.java b/src/main/java/org/qora/repository/hsqldb/HSQLDBRepository.java index fc61cc21..59aa0aa9 100644 --- a/src/main/java/org/qora/repository/hsqldb/HSQLDBRepository.java +++ b/src/main/java/org/qora/repository/hsqldb/HSQLDBRepository.java @@ -11,8 +11,10 @@ import java.time.Instant; import java.time.OffsetDateTime; import java.time.ZoneOffset; import java.util.ArrayDeque; +import java.util.ArrayList; import java.util.Collections; import java.util.Deque; +import java.util.List; import java.util.TimeZone; import org.apache.logging.log4j.LogManager; @@ -29,12 +31,10 @@ import org.qora.repository.Repository; import org.qora.repository.TransactionRepository; import org.qora.repository.VotingRepository; import org.qora.repository.hsqldb.transaction.HSQLDBTransactionRepository; +import org.qora.settings.Settings; public class HSQLDBRepository implements Repository { - /** Queries that take longer than this (milliseconds) are logged */ - private static final long MAX_QUERY_TIME = 1000L; - private static final Logger LOGGER = LogManager.getLogger(HSQLDBRepository.class); public static final TimeZone UTC = TimeZone.getTimeZone("UTC"); @@ -42,11 +42,17 @@ public class HSQLDBRepository implements Repository { protected Connection connection; protected Deque savepoints; protected boolean debugState = false; + protected Long slowQueryThreshold = null; + protected List queries; // NB: no visibility modifier so only callable from within same package HSQLDBRepository(Connection connection) { this.connection = connection; this.savepoints = new ArrayDeque<>(3); + + this.slowQueryThreshold = Settings.getInstance().getSlowQueryThreshold(); + if (this.slowQueryThreshold != null) + this.queries = new ArrayList(); } @Override @@ -102,6 +108,9 @@ public class HSQLDBRepository implements Repository { throw new DataException("commit error", e); } finally { this.savepoints.clear(); + + if (this.queries != null) + this.queries.clear(); } } @@ -113,6 +122,9 @@ public class HSQLDBRepository implements Repository { throw new DataException("rollback error", e); } finally { this.savepoints.clear(); + + if (this.queries != null) + this.queries.clear(); } } @@ -121,6 +133,9 @@ public class HSQLDBRepository implements Repository { try { Savepoint savepoint = this.connection.setSavepoint(); this.savepoints.push(savepoint); + + if (this.queries != null) + this.queries.add("SAVEPOINT"); } catch (SQLException e) { throw new DataException("savepoint error", e); } @@ -135,6 +150,9 @@ public class HSQLDBRepository implements Repository { try { this.connection.rollback(savepoint); + + if (this.queries != null) + this.queries.add("ROLLBACK TO SAVEPOINT"); } catch (SQLException e) { throw new DataException("savepoint rollback error", e); } @@ -206,9 +224,17 @@ public class HSQLDBRepository implements Repository { ResultSet resultSet = this.checkedExecuteResultSet(preparedStatement, objects); long queryTime = System.currentTimeMillis() - beforeQuery; - if (queryTime > MAX_QUERY_TIME) + if (this.slowQueryThreshold != null && queryTime > this.slowQueryThreshold) { LOGGER.info(String.format("HSQLDB query took %d ms: %s", queryTime, sql)); + if (this.queries != null) + for (String query : this.queries) + LOGGER.info(query); + } + + if (this.queries != null) + this.queries.add(sql); + return resultSet; } @@ -316,8 +342,13 @@ public class HSQLDBRepository implements Repository { * @throws SQLException */ public boolean exists(String tableName, String whereClause, Object... objects) throws SQLException { - try (PreparedStatement preparedStatement = this.connection.prepareStatement("SELECT TRUE FROM " + tableName + " WHERE " + whereClause + " LIMIT 1"); + String sql = "SELECT TRUE FROM " + tableName + " WHERE " + whereClause + " LIMIT 1"; + + try (PreparedStatement preparedStatement = this.connection.prepareStatement(sql); ResultSet resultSet = this.checkedExecuteResultSet(preparedStatement, objects)) { + if (this.queries != null) + this.queries.add(sql); + if (resultSet == null) return false; @@ -334,7 +365,12 @@ public class HSQLDBRepository implements Repository { * @throws SQLException */ public int delete(String tableName, String whereClause, Object... objects) throws SQLException { - try (PreparedStatement preparedStatement = this.connection.prepareStatement("DELETE FROM " + tableName + " WHERE " + whereClause)) { + String sql = "DELETE FROM " + tableName + " WHERE " + whereClause; + + try (PreparedStatement preparedStatement = this.connection.prepareStatement(sql)) { + if (this.queries != null) + this.queries.add(sql); + return this.checkedExecuteUpdateCount(preparedStatement, objects); } } @@ -346,7 +382,12 @@ public class HSQLDBRepository implements Repository { * @throws SQLException */ public int delete(String tableName) throws SQLException { - try (PreparedStatement preparedStatement = this.connection.prepareStatement("DELETE FROM " + tableName)) { + String sql = "DELETE FROM " + tableName; + + try (PreparedStatement preparedStatement = this.connection.prepareStatement(sql)) { + if (this.queries != null) + this.queries.add(sql); + return this.checkedExecuteUpdateCount(preparedStatement); } } @@ -376,6 +417,10 @@ public class HSQLDBRepository implements Repository { public SQLException examineException(SQLException e) throws SQLException { LOGGER.error("SQL error: " + e.getMessage()); + if (this.queries != null) + for (String query : this.queries) + LOGGER.info(query); + // Serialization failure / potential deadlock - so list other sessions try (ResultSet resultSet = this.checkedExecute( "SELECT session_id, transaction, transaction_size, waiting_for_this, this_waiting_for, current_statement FROM Information_schema.system_sessions")) { diff --git a/src/main/java/org/qora/settings/Settings.java b/src/main/java/org/qora/settings/Settings.java index b8012fdc..6f525ce1 100644 --- a/src/main/java/org/qora/settings/Settings.java +++ b/src/main/java/org/qora/settings/Settings.java @@ -62,6 +62,12 @@ public class Settings { private String blockchainConfig = "blockchain.json"; private boolean useBitcoinTestNet = false; + // Repository related + /** Queries that take longer than this are logged. (milliseconds) */ + private Long slowQueryThreshold = null; + /** Repository storage path. */ + private String repositoryPath = null; + // Constructors private Settings() { @@ -228,4 +234,12 @@ public class Settings { return this.useBitcoinTestNet; } + public Long getSlowQueryThreshold() { + return this.slowQueryThreshold; + } + + public String getRepositoryPath() { + return this.repositoryPath; + } + } diff --git a/src/main/java/org/qora/transaction/Transaction.java b/src/main/java/org/qora/transaction/Transaction.java index ad0182b7..2731f8b7 100644 --- a/src/main/java/org/qora/transaction/Transaction.java +++ b/src/main/java/org/qora/transaction/Transaction.java @@ -515,7 +515,10 @@ public abstract class Transaction { if (!this.isValidTxGroupId()) return ValidationResult.INVALID_TX_GROUP_ID; - creator.setLastReference(creator.getUnconfirmedLastReference()); + byte[] unconfirmedLastReference = creator.getUnconfirmedLastReference(); + if (unconfirmedLastReference != null) + creator.setLastReference(unconfirmedLastReference); + ValidationResult result = this.isValid(); // Reject if unconfirmed pile already has X transactions from same creator