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