mirror of
https://github.com/Qortal/qortal.git
synced 2025-02-12 02:05:50 +00:00
HSQLDB debugging & optimization for Transaction.isValidUnconfirmed
This commit is contained in:
parent
51fd029e22
commit
0296c4bbb1
@ -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.
|
||||||
* <p>
|
* <p>
|
||||||
* NOTE: a repository savepoint may be used during execution.
|
* 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
|
* @throws DataException
|
||||||
*/
|
*/
|
||||||
public byte[] getUnconfirmedLastReference() 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.
|
|
||||||
* <p>
|
|
||||||
* 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
|
// Newest unconfirmed transaction takes priority
|
||||||
List<TransactionData> unconfirmedTransactions = Transaction.getUnconfirmedTransactions(repository);
|
List<TransactionData> unconfirmedTransactions = Transaction.getUnconfirmedTransactions(repository);
|
||||||
|
|
||||||
byte[] reference = defaultReference;
|
byte[] reference = null;
|
||||||
|
|
||||||
for (TransactionData transactionData : unconfirmedTransactions) {
|
for (TransactionData transactionData : unconfirmedTransactions) {
|
||||||
String address = PublicKeyAccount.getAddress(transactionData.getCreatorPublicKey());
|
String address = PublicKeyAccount.getAddress(transactionData.getCreatorPublicKey());
|
||||||
|
@ -78,7 +78,7 @@ public class AddressesResource {
|
|||||||
else {
|
else {
|
||||||
// Unconfirmed transactions could update lastReference
|
// Unconfirmed transactions could update lastReference
|
||||||
Account account = new Account(repository, address);
|
Account account = new Account(repository, address);
|
||||||
byte[] unconfirmedLastReference = account.getUnconfirmedLastReference(null);
|
byte[] unconfirmedLastReference = account.getUnconfirmedLastReference();
|
||||||
if (unconfirmedLastReference != null)
|
if (unconfirmedLastReference != null)
|
||||||
accountData.setReference(unconfirmedLastReference);
|
accountData.setReference(unconfirmedLastReference);
|
||||||
}
|
}
|
||||||
@ -109,9 +109,12 @@ public class AddressesResource {
|
|||||||
throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.INVALID_ADDRESS);
|
throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.INVALID_ADDRESS);
|
||||||
|
|
||||||
byte[] lastReference = null;
|
byte[] lastReference = null;
|
||||||
|
|
||||||
try (final Repository repository = RepositoryManager.getRepository()) {
|
try (final Repository repository = RepositoryManager.getRepository()) {
|
||||||
Account account = new Account(repository, address);
|
Account account = new Account(repository, address);
|
||||||
lastReference = account.getUnconfirmedLastReference();
|
lastReference = account.getUnconfirmedLastReference();
|
||||||
|
if (lastReference == null)
|
||||||
|
lastReference = account.getLastReference();
|
||||||
} catch (ApiException e) {
|
} catch (ApiException e) {
|
||||||
throw e;
|
throw e;
|
||||||
} catch (DataException e) {
|
} catch (DataException e) {
|
||||||
|
@ -11,8 +11,10 @@ import java.time.Instant;
|
|||||||
import java.time.OffsetDateTime;
|
import java.time.OffsetDateTime;
|
||||||
import java.time.ZoneOffset;
|
import java.time.ZoneOffset;
|
||||||
import java.util.ArrayDeque;
|
import java.util.ArrayDeque;
|
||||||
|
import java.util.ArrayList;
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
import java.util.Deque;
|
import java.util.Deque;
|
||||||
|
import java.util.List;
|
||||||
import java.util.TimeZone;
|
import java.util.TimeZone;
|
||||||
|
|
||||||
import org.apache.logging.log4j.LogManager;
|
import org.apache.logging.log4j.LogManager;
|
||||||
@ -29,12 +31,10 @@ import org.qora.repository.Repository;
|
|||||||
import org.qora.repository.TransactionRepository;
|
import org.qora.repository.TransactionRepository;
|
||||||
import org.qora.repository.VotingRepository;
|
import org.qora.repository.VotingRepository;
|
||||||
import org.qora.repository.hsqldb.transaction.HSQLDBTransactionRepository;
|
import org.qora.repository.hsqldb.transaction.HSQLDBTransactionRepository;
|
||||||
|
import org.qora.settings.Settings;
|
||||||
|
|
||||||
public class HSQLDBRepository implements Repository {
|
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);
|
private static final Logger LOGGER = LogManager.getLogger(HSQLDBRepository.class);
|
||||||
|
|
||||||
public static final TimeZone UTC = TimeZone.getTimeZone("UTC");
|
public static final TimeZone UTC = TimeZone.getTimeZone("UTC");
|
||||||
@ -42,11 +42,17 @@ public class HSQLDBRepository implements Repository {
|
|||||||
protected Connection connection;
|
protected Connection connection;
|
||||||
protected Deque<Savepoint> savepoints;
|
protected Deque<Savepoint> savepoints;
|
||||||
protected boolean debugState = false;
|
protected boolean debugState = false;
|
||||||
|
protected Long slowQueryThreshold = null;
|
||||||
|
protected List<String> queries;
|
||||||
|
|
||||||
// NB: no visibility modifier so only callable from within same package
|
// NB: no visibility modifier so only callable from within same package
|
||||||
HSQLDBRepository(Connection connection) {
|
HSQLDBRepository(Connection connection) {
|
||||||
this.connection = connection;
|
this.connection = connection;
|
||||||
this.savepoints = new ArrayDeque<>(3);
|
this.savepoints = new ArrayDeque<>(3);
|
||||||
|
|
||||||
|
this.slowQueryThreshold = Settings.getInstance().getSlowQueryThreshold();
|
||||||
|
if (this.slowQueryThreshold != null)
|
||||||
|
this.queries = new ArrayList<String>();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@ -102,6 +108,9 @@ public class HSQLDBRepository implements Repository {
|
|||||||
throw new DataException("commit error", e);
|
throw new DataException("commit error", e);
|
||||||
} finally {
|
} finally {
|
||||||
this.savepoints.clear();
|
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);
|
throw new DataException("rollback error", e);
|
||||||
} finally {
|
} finally {
|
||||||
this.savepoints.clear();
|
this.savepoints.clear();
|
||||||
|
|
||||||
|
if (this.queries != null)
|
||||||
|
this.queries.clear();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -121,6 +133,9 @@ public class HSQLDBRepository implements Repository {
|
|||||||
try {
|
try {
|
||||||
Savepoint savepoint = this.connection.setSavepoint();
|
Savepoint savepoint = this.connection.setSavepoint();
|
||||||
this.savepoints.push(savepoint);
|
this.savepoints.push(savepoint);
|
||||||
|
|
||||||
|
if (this.queries != null)
|
||||||
|
this.queries.add("SAVEPOINT");
|
||||||
} catch (SQLException e) {
|
} catch (SQLException e) {
|
||||||
throw new DataException("savepoint error", e);
|
throw new DataException("savepoint error", e);
|
||||||
}
|
}
|
||||||
@ -135,6 +150,9 @@ public class HSQLDBRepository implements Repository {
|
|||||||
|
|
||||||
try {
|
try {
|
||||||
this.connection.rollback(savepoint);
|
this.connection.rollback(savepoint);
|
||||||
|
|
||||||
|
if (this.queries != null)
|
||||||
|
this.queries.add("ROLLBACK TO SAVEPOINT");
|
||||||
} catch (SQLException e) {
|
} catch (SQLException e) {
|
||||||
throw new DataException("savepoint rollback error", e);
|
throw new DataException("savepoint rollback error", e);
|
||||||
}
|
}
|
||||||
@ -206,9 +224,17 @@ public class HSQLDBRepository implements Repository {
|
|||||||
ResultSet resultSet = this.checkedExecuteResultSet(preparedStatement, objects);
|
ResultSet resultSet = this.checkedExecuteResultSet(preparedStatement, objects);
|
||||||
|
|
||||||
long queryTime = System.currentTimeMillis() - beforeQuery;
|
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));
|
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;
|
return resultSet;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -316,8 +342,13 @@ public class HSQLDBRepository implements Repository {
|
|||||||
* @throws SQLException
|
* @throws SQLException
|
||||||
*/
|
*/
|
||||||
public boolean exists(String tableName, String whereClause, Object... objects) 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)) {
|
ResultSet resultSet = this.checkedExecuteResultSet(preparedStatement, objects)) {
|
||||||
|
if (this.queries != null)
|
||||||
|
this.queries.add(sql);
|
||||||
|
|
||||||
if (resultSet == null)
|
if (resultSet == null)
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
@ -334,7 +365,12 @@ public class HSQLDBRepository implements Repository {
|
|||||||
* @throws SQLException
|
* @throws SQLException
|
||||||
*/
|
*/
|
||||||
public int delete(String tableName, String whereClause, Object... objects) 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);
|
return this.checkedExecuteUpdateCount(preparedStatement, objects);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -346,7 +382,12 @@ public class HSQLDBRepository implements Repository {
|
|||||||
* @throws SQLException
|
* @throws SQLException
|
||||||
*/
|
*/
|
||||||
public int delete(String tableName) 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);
|
return this.checkedExecuteUpdateCount(preparedStatement);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -376,6 +417,10 @@ public class HSQLDBRepository implements Repository {
|
|||||||
public SQLException examineException(SQLException e) throws SQLException {
|
public SQLException examineException(SQLException e) throws SQLException {
|
||||||
LOGGER.error("SQL error: " + e.getMessage());
|
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
|
// Serialization failure / potential deadlock - so list other sessions
|
||||||
try (ResultSet resultSet = this.checkedExecute(
|
try (ResultSet resultSet = this.checkedExecute(
|
||||||
"SELECT session_id, transaction, transaction_size, waiting_for_this, this_waiting_for, current_statement FROM Information_schema.system_sessions")) {
|
"SELECT session_id, transaction, transaction_size, waiting_for_this, this_waiting_for, current_statement FROM Information_schema.system_sessions")) {
|
||||||
|
@ -62,6 +62,12 @@ public class Settings {
|
|||||||
private String blockchainConfig = "blockchain.json";
|
private String blockchainConfig = "blockchain.json";
|
||||||
private boolean useBitcoinTestNet = false;
|
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
|
// Constructors
|
||||||
|
|
||||||
private Settings() {
|
private Settings() {
|
||||||
@ -228,4 +234,12 @@ public class Settings {
|
|||||||
return this.useBitcoinTestNet;
|
return this.useBitcoinTestNet;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public Long getSlowQueryThreshold() {
|
||||||
|
return this.slowQueryThreshold;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getRepositoryPath() {
|
||||||
|
return this.repositoryPath;
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -515,7 +515,10 @@ public abstract class Transaction {
|
|||||||
if (!this.isValidTxGroupId())
|
if (!this.isValidTxGroupId())
|
||||||
return ValidationResult.INVALID_TX_GROUP_ID;
|
return ValidationResult.INVALID_TX_GROUP_ID;
|
||||||
|
|
||||||
creator.setLastReference(creator.getUnconfirmedLastReference());
|
byte[] unconfirmedLastReference = creator.getUnconfirmedLastReference();
|
||||||
|
if (unconfirmedLastReference != null)
|
||||||
|
creator.setLastReference(unconfirmedLastReference);
|
||||||
|
|
||||||
ValidationResult result = this.isValid();
|
ValidationResult result = this.isValid();
|
||||||
|
|
||||||
// Reject if unconfirmed pile already has X transactions from same creator
|
// Reject if unconfirmed pile already has X transactions from same creator
|
||||||
|
Loading…
x
Reference in New Issue
Block a user