mirror of
https://github.com/Qortal/qortal.git
synced 2025-02-11 17:55:50 +00:00
Convertion to thread-local Connection
Much tidier code thanks to not having to pass Connection objects around as params. Also no need for two forms of the same method, one with Connection param, one without. Also corrected SQL-Transaction-related methods in DB, e.g. commit, rollback, etc. so they use the proper underlying JDBC methods.
This commit is contained in:
parent
3a6276c4a9
commit
5b78268915
@ -7,6 +7,7 @@ import java.sql.Connection;
|
||||
import java.sql.PreparedStatement;
|
||||
import java.sql.ResultSet;
|
||||
import java.sql.SQLException;
|
||||
import java.sql.Savepoint;
|
||||
import java.util.Arrays;
|
||||
|
||||
import org.hsqldb.jdbc.JDBCPool;
|
||||
@ -17,10 +18,21 @@ import com.google.common.primitives.Bytes;
|
||||
* Helper methods for common database actions.
|
||||
*
|
||||
*/
|
||||
public class DB {
|
||||
public abstract class DB {
|
||||
|
||||
private static JDBCPool connectionPool;
|
||||
private static String connectionUrl;
|
||||
private static ThreadLocal<Connection> local = new ThreadLocal<Connection>() {
|
||||
@Override
|
||||
protected Connection initialValue() {
|
||||
Connection conn = null;
|
||||
try {
|
||||
conn = connectionPool.getConnection();
|
||||
} catch (SQLException e) {
|
||||
}
|
||||
return conn;
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Open connection pool to database using prior set connection URL.
|
||||
@ -49,38 +61,51 @@ public class DB {
|
||||
}
|
||||
|
||||
/**
|
||||
* Return an on-demand Connection from connection pool.
|
||||
* <p>
|
||||
* Mostly used in database-read scenarios whereas database-write scenarios, especially multi-statement transactions, are likely to pass around a Connection
|
||||
* object.
|
||||
* Return thread-local Connection from connection pool.
|
||||
* <p>
|
||||
* By default HSQLDB will wait up to 30 seconds for a pooled connection to become free.
|
||||
*
|
||||
* @return Connection
|
||||
* @throws SQLException
|
||||
*/
|
||||
public static Connection getConnection() throws SQLException {
|
||||
return connectionPool.getConnection();
|
||||
public static Connection getConnection() {
|
||||
return local.get();
|
||||
}
|
||||
|
||||
public static void startTransaction(Connection c) throws SQLException {
|
||||
c.prepareStatement("START TRANSACTION").execute();
|
||||
public static void releaseConnection() {
|
||||
Connection connection = local.get();
|
||||
if (connection != null)
|
||||
try {
|
||||
connection.close();
|
||||
} catch (SQLException e) {
|
||||
}
|
||||
|
||||
public static void commit(Connection c) throws SQLException {
|
||||
c.prepareStatement("COMMIT").execute();
|
||||
local.remove();
|
||||
}
|
||||
|
||||
public static void rollback(Connection c) throws SQLException {
|
||||
c.prepareStatement("ROLLBACK").execute();
|
||||
public static void startTransaction() throws SQLException {
|
||||
Connection connection = DB.getConnection();
|
||||
connection.setTransactionIsolation(Connection.TRANSACTION_SERIALIZABLE);
|
||||
connection.setAutoCommit(false);
|
||||
}
|
||||
|
||||
public static void createSavepoint(Connection c, String savepointName) throws SQLException {
|
||||
c.prepareStatement("SAVEPOINT " + savepointName).execute();
|
||||
public static void commit() throws SQLException {
|
||||
Connection connection = DB.getConnection();
|
||||
connection.commit();
|
||||
connection.setAutoCommit(true);
|
||||
}
|
||||
|
||||
public static void rollbackToSavepoint(Connection c, String savepointName) throws SQLException {
|
||||
c.prepareStatement("ROLLBACK TO SAVEPOINT " + savepointName).execute();
|
||||
public static void rollback() throws SQLException {
|
||||
Connection connection = DB.getConnection();
|
||||
connection.rollback();
|
||||
connection.setAutoCommit(true);
|
||||
}
|
||||
|
||||
public static Savepoint createSavepoint(String savepointName) throws SQLException {
|
||||
return DB.getConnection().setSavepoint(savepointName);
|
||||
}
|
||||
|
||||
public static void rollbackToSavepoint(Savepoint savepoint) throws SQLException {
|
||||
DB.getConnection().rollback(savepoint);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -93,15 +118,16 @@ public class DB {
|
||||
*
|
||||
* @throws SQLException
|
||||
*/
|
||||
public static void close() throws SQLException {
|
||||
getConnection().createStatement().execute("SHUTDOWN");
|
||||
public static void shutdown() throws SQLException {
|
||||
DB.getConnection().createStatement().execute("SHUTDOWN");
|
||||
DB.releaseConnection();
|
||||
connectionPool.close(0);
|
||||
}
|
||||
|
||||
/**
|
||||
* Shutdown and delete database, then rebuild it.
|
||||
* <p>
|
||||
* See {@link DB#close()} for warnings about connections.
|
||||
* See {@link DB#shutdown()} for warnings about connections.
|
||||
* <p>
|
||||
* Note that this only rebuilds the database schema, not the data itself.
|
||||
*
|
||||
@ -109,7 +135,7 @@ public class DB {
|
||||
*/
|
||||
public static void rebuild() throws SQLException {
|
||||
// Shutdown database and close any access
|
||||
DB.close();
|
||||
DB.shutdown();
|
||||
|
||||
// Wipe files (if any)
|
||||
// TODO
|
||||
@ -188,26 +214,8 @@ public class DB {
|
||||
* @throws SQLException
|
||||
*/
|
||||
public static ResultSet checkedExecute(String sql, Object... objects) throws SQLException {
|
||||
try (final Connection connection = DB.getConnection()) {
|
||||
return checkedExecute(connection, sql, objects);
|
||||
}
|
||||
}
|
||||
PreparedStatement preparedStatement = DB.getConnection().prepareStatement(sql);
|
||||
|
||||
/**
|
||||
* Execute SQL using connection and return ResultSet with but added checking.
|
||||
* <p>
|
||||
* Typically for use within an ongoing SQL Transaction.
|
||||
* <p>
|
||||
* <b>Note: calls ResultSet.next()</b> therefore returned ResultSet is already pointing to first row.
|
||||
*
|
||||
* @param connection
|
||||
* @param sql
|
||||
* @param objects
|
||||
* @return ResultSet, or null if there are no found rows
|
||||
* @throws SQLException
|
||||
*/
|
||||
public static ResultSet checkedExecute(Connection connection, String sql, Object... objects) throws SQLException {
|
||||
PreparedStatement preparedStatement = connection.prepareStatement(sql);
|
||||
for (int i = 0; i < objects.length; ++i)
|
||||
// Special treatment for BigDecimals so that they retain their "scale",
|
||||
// which would otherwise be assumed as 0.
|
||||
@ -216,7 +224,7 @@ public class DB {
|
||||
else
|
||||
preparedStatement.setObject(i + 1, objects[i]);
|
||||
|
||||
return checkedExecute(preparedStatement);
|
||||
return DB.checkedExecute(preparedStatement);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -249,12 +257,11 @@ public class DB {
|
||||
* <p>
|
||||
* Typically used after INSERTing NULL as the IDENTIY column's value to fetch what value was actually stored by HSQLDB.
|
||||
*
|
||||
* @param connection
|
||||
* @return Long
|
||||
* @throws SQLException
|
||||
*/
|
||||
public static Long callIdentity(Connection connection) throws SQLException {
|
||||
PreparedStatement preparedStatement = connection.prepareStatement("CALL IDENTITY()");
|
||||
public static Long callIdentity() throws SQLException {
|
||||
PreparedStatement preparedStatement = DB.getConnection().prepareStatement("CALL IDENTITY()");
|
||||
ResultSet resultSet = DB.checkedExecute(preparedStatement);
|
||||
if (resultSet == null)
|
||||
return null;
|
||||
@ -280,34 +287,7 @@ public class DB {
|
||||
* @throws SQLException
|
||||
*/
|
||||
public static boolean exists(String tableName, String whereClause, Object... objects) throws SQLException {
|
||||
try (final Connection connection = DB.getConnection()) {
|
||||
return exists(connection, tableName, whereClause, objects);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Efficiently query database, using connection, for existing of matching row.
|
||||
* <p>
|
||||
* Typically for use within an ongoing SQL Transaction.
|
||||
* <p>
|
||||
* {@code whereClause} is SQL "WHERE" clause containing "?" placeholders suitable for use with PreparedStatements.
|
||||
* <p>
|
||||
* Example call:
|
||||
* <p>
|
||||
* {@code Connection connection = DB.getConnection();}<br>
|
||||
* {@code String manufacturer = "Lamborghini";}<br>
|
||||
* {@code int maxMileage = 100_000;}<br>
|
||||
* {@code boolean isAvailable = DB.exists(connection, "Cars", "manufacturer = ? AND mileage <= ?", manufacturer, maxMileage);}
|
||||
*
|
||||
* @param connection
|
||||
* @param tableName
|
||||
* @param whereClause
|
||||
* @param objects
|
||||
* @return true if matching row found in database, false otherwise
|
||||
* @throws SQLException
|
||||
*/
|
||||
public static boolean exists(Connection connection, String tableName, String whereClause, Object... objects) throws SQLException {
|
||||
PreparedStatement preparedStatement = connection
|
||||
PreparedStatement preparedStatement = DB.getConnection()
|
||||
.prepareStatement("SELECT TRUE FROM " + tableName + " WHERE " + whereClause + " ORDER BY NULL LIMIT 1");
|
||||
ResultSet resultSet = DB.checkedExecute(preparedStatement);
|
||||
if (resultSet == null)
|
||||
|
@ -1,6 +1,5 @@
|
||||
package database;
|
||||
|
||||
import java.sql.Connection;
|
||||
import java.sql.ResultSet;
|
||||
import java.sql.SQLException;
|
||||
import java.sql.Statement;
|
||||
@ -23,10 +22,7 @@ public class DatabaseUpdates {
|
||||
* @throws SQLException
|
||||
*/
|
||||
private static void incrementDatabaseVersion() throws SQLException {
|
||||
try (final Connection c = DB.getConnection()) {
|
||||
Statement stmt = c.createStatement();
|
||||
stmt.execute("UPDATE DatabaseInfo SET version = version + 1");
|
||||
}
|
||||
DB.getConnection().createStatement().execute("UPDATE DatabaseInfo SET version = version + 1");
|
||||
}
|
||||
|
||||
/**
|
||||
@ -38,8 +34,8 @@ public class DatabaseUpdates {
|
||||
private static int fetchDatabaseVersion() throws SQLException {
|
||||
int databaseVersion = 0;
|
||||
|
||||
try (final Connection c = DB.getConnection()) {
|
||||
Statement stmt = c.createStatement();
|
||||
try {
|
||||
Statement stmt = DB.getConnection().createStatement();
|
||||
if (stmt.execute("SELECT version FROM DatabaseInfo")) {
|
||||
ResultSet rs = stmt.getResultSet();
|
||||
|
||||
@ -62,17 +58,16 @@ public class DatabaseUpdates {
|
||||
private static boolean databaseUpdating() throws SQLException {
|
||||
int databaseVersion = fetchDatabaseVersion();
|
||||
|
||||
try (final Connection c = DB.getConnection()) {
|
||||
Statement stmt = c.createStatement();
|
||||
Statement stmt = DB.getConnection().createStatement();
|
||||
|
||||
/*
|
||||
* Try not to add too many constraints as much of these checks will be performed during transaction validation. Also some constraints might be too
|
||||
* harsh on competing unconfirmed transactions.
|
||||
* Try not to add too many constraints as much of these checks will be performed during transaction validation. Also some constraints might be too harsh
|
||||
* on competing unconfirmed transactions.
|
||||
*
|
||||
* Only really add "ON DELETE CASCADE" to sub-tables that store type-specific data. For example on sub-types of Transactions like
|
||||
* PaymentTransactions. A counterexample would be adding "ON DELETE CASCADE" to Assets using Assets' "reference" as a foreign key referring to
|
||||
* Transactions' "signature". We want to database to automatically delete complete transaction data (Transactions row and corresponding
|
||||
* PaymentTransactions row), but leave deleting less related table rows (Assets) to the Java logic.
|
||||
* Only really add "ON DELETE CASCADE" to sub-tables that store type-specific data. For example on sub-types of Transactions like PaymentTransactions. A
|
||||
* counterexample would be adding "ON DELETE CASCADE" to Assets using Assets' "reference" as a foreign key referring to Transactions' "signature". We
|
||||
* want to database to automatically delete complete transaction data (Transactions row and corresponding PaymentTransactions row), but leave deleting
|
||||
* less related table rows (Assets) to the Java logic.
|
||||
*/
|
||||
|
||||
switch (databaseVersion) {
|
||||
@ -290,7 +285,6 @@ public class DatabaseUpdates {
|
||||
// nothing to do
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// database was updated
|
||||
return true;
|
||||
|
@ -1,7 +1,6 @@
|
||||
package database;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.sql.Connection;
|
||||
import java.sql.PreparedStatement;
|
||||
import java.sql.SQLException;
|
||||
import java.util.ArrayList;
|
||||
@ -13,14 +12,13 @@ import java.util.List;
|
||||
* <p>
|
||||
* Columns, and corresponding values, are bound via close-coupled pairs in a chain thus:
|
||||
* <p>
|
||||
* {@code SaveHelper helper = new SaveHelper(connection, "TableName"); }<br>
|
||||
* {@code SaveHelper helper = new SaveHelper("TableName"); }<br>
|
||||
* {@code helper.bind("column_name", someColumnValue).bind("column2", columnValue2); }<br>
|
||||
* {@code helper.execute(); }<br>
|
||||
*
|
||||
*/
|
||||
public class SaveHelper {
|
||||
|
||||
private Connection connection;
|
||||
private String table;
|
||||
|
||||
private List<String> columns = new ArrayList<String>();
|
||||
@ -29,11 +27,9 @@ public class SaveHelper {
|
||||
/**
|
||||
* Construct a SaveHelper, using SQL Connection and table name.
|
||||
*
|
||||
* @param connection
|
||||
* @param table
|
||||
*/
|
||||
public SaveHelper(Connection connection, String table) {
|
||||
this.connection = connection;
|
||||
public SaveHelper(String table) {
|
||||
this.table = table;
|
||||
}
|
||||
|
||||
@ -52,8 +48,6 @@ public class SaveHelper {
|
||||
|
||||
/**
|
||||
* Build PreparedStatement using bound column-value pairs then execute it.
|
||||
* <p>
|
||||
* Note that after this call, the SaveHelper's Connection is set to null and so this object is not reusable.
|
||||
*
|
||||
* @return the result from {@link PreparedStatement#execute()}
|
||||
* @throws SQLException
|
||||
@ -61,15 +55,11 @@ public class SaveHelper {
|
||||
public boolean execute() throws SQLException {
|
||||
String sql = this.formatInsertWithPlaceholders();
|
||||
|
||||
PreparedStatement preparedStatement = connection.prepareStatement(sql);
|
||||
PreparedStatement preparedStatement = DB.getConnection().prepareStatement(sql);
|
||||
|
||||
this.bindValues(preparedStatement);
|
||||
|
||||
try {
|
||||
return preparedStatement.execute();
|
||||
} finally {
|
||||
this.connection = null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -1,7 +1,6 @@
|
||||
package qora.account;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.sql.Connection;
|
||||
import java.sql.ResultSet;
|
||||
import java.sql.SQLException;
|
||||
|
||||
@ -41,28 +40,21 @@ public class Account {
|
||||
}
|
||||
|
||||
public BigDecimal getConfirmedBalance(long assetId) throws SQLException {
|
||||
try (final Connection connection = DB.getConnection()) {
|
||||
return getConfirmedBalance(connection, assetId);
|
||||
}
|
||||
}
|
||||
|
||||
public BigDecimal getConfirmedBalance(Connection connection, long assetId) throws SQLException {
|
||||
ResultSet resultSet = DB.checkedExecute(connection, "SELECT balance FROM AccountBalances WHERE account = ? and asset_id = ?", this.getAddress(),
|
||||
assetId);
|
||||
ResultSet resultSet = DB.checkedExecute("SELECT balance FROM AccountBalances WHERE account = ? and asset_id = ?", this.getAddress(), assetId);
|
||||
if (resultSet == null)
|
||||
return BigDecimal.ZERO.setScale(8);
|
||||
|
||||
return resultSet.getBigDecimal(1);
|
||||
}
|
||||
|
||||
public void setConfirmedBalance(Connection connection, long assetId, BigDecimal balance) throws SQLException {
|
||||
SaveHelper saveHelper = new SaveHelper(connection, "AccountBalances");
|
||||
public void setConfirmedBalance(long assetId, BigDecimal balance) throws SQLException {
|
||||
SaveHelper saveHelper = new SaveHelper("AccountBalances");
|
||||
saveHelper.bind("account", this.getAddress()).bind("asset_id", assetId).bind("balance", balance);
|
||||
saveHelper.execute();
|
||||
}
|
||||
|
||||
public void deleteBalance(Connection connection, long assetId) throws SQLException {
|
||||
DB.checkedExecute(connection, "DELETE FROM AccountBalances WHERE account = ? and asset_id = ?", this.getAddress(), assetId);
|
||||
public void deleteBalance(long assetId) throws SQLException {
|
||||
DB.checkedExecute("DELETE FROM AccountBalances WHERE account = ? and asset_id = ?", this.getAddress(), assetId);
|
||||
}
|
||||
|
||||
// Reference manipulations
|
||||
@ -74,22 +66,7 @@ public class Account {
|
||||
* @throws SQLException
|
||||
*/
|
||||
public byte[] getLastReference() throws SQLException {
|
||||
try (final Connection connection = DB.getConnection()) {
|
||||
return getLastReference(connection);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetch last reference for account using supplied DB connection.
|
||||
* <p>
|
||||
* Typically for use within an ongoing SQL Transaction.
|
||||
*
|
||||
* @param connection
|
||||
* @return byte[] reference, or null if no reference or account not found.
|
||||
* @throws SQLException
|
||||
*/
|
||||
public byte[] getLastReference(Connection connection) throws SQLException {
|
||||
ResultSet resultSet = DB.checkedExecute(connection, "SELECT reference FROM Accounts WHERE account = ?", this.getAddress());
|
||||
ResultSet resultSet = DB.checkedExecute("SELECT reference FROM Accounts WHERE account = ?", this.getAddress());
|
||||
if (resultSet == null)
|
||||
return null;
|
||||
|
||||
@ -99,13 +76,12 @@ public class Account {
|
||||
/**
|
||||
* Set last reference for account.
|
||||
*
|
||||
* @param connection
|
||||
* @param reference
|
||||
* -- null allowed
|
||||
* @throws SQLException
|
||||
*/
|
||||
public void setLastReference(Connection connection, byte[] reference) throws SQLException {
|
||||
SaveHelper saveHelper = new SaveHelper(connection, "Accounts");
|
||||
public void setLastReference(byte[] reference) throws SQLException {
|
||||
SaveHelper saveHelper = new SaveHelper("Accounts");
|
||||
saveHelper.bind("account", this.getAddress()).bind("reference", reference);
|
||||
saveHelper.execute();
|
||||
}
|
||||
|
@ -1,6 +1,5 @@
|
||||
package qora.assets;
|
||||
|
||||
import java.sql.Connection;
|
||||
import java.sql.ResultSet;
|
||||
import java.sql.SQLException;
|
||||
|
||||
@ -103,34 +102,26 @@ public class Asset {
|
||||
}
|
||||
}
|
||||
|
||||
public void save(Connection connection) throws SQLException {
|
||||
SaveHelper saveHelper = new SaveHelper(connection, "Assets");
|
||||
public void save() throws SQLException {
|
||||
SaveHelper saveHelper = new SaveHelper("Assets");
|
||||
saveHelper.bind("asset_id", this.assetId).bind("owner", this.owner.getAddress()).bind("asset_name", this.name).bind("description", this.description)
|
||||
.bind("quantity", this.quantity).bind("is_divisible", this.isDivisible).bind("reference", this.reference);
|
||||
saveHelper.execute();
|
||||
|
||||
if (this.assetId == null)
|
||||
this.assetId = DB.callIdentity(connection);
|
||||
this.assetId = DB.callIdentity();
|
||||
}
|
||||
|
||||
public void delete(Connection connection) throws SQLException {
|
||||
DB.checkedExecute(connection, "DELETE FROM Assets WHERE asset_id = ?", this.assetId);
|
||||
public void delete() throws SQLException {
|
||||
DB.checkedExecute("DELETE FROM Assets WHERE asset_id = ?", this.assetId);
|
||||
}
|
||||
|
||||
public static boolean exists(long assetId) throws SQLException {
|
||||
return DB.exists("Assets", "asset_id = ?", assetId);
|
||||
}
|
||||
|
||||
public static boolean exists(Connection connection, long assetId) throws SQLException {
|
||||
return DB.exists(connection, "Assets", "asset_id = ?", assetId);
|
||||
}
|
||||
|
||||
public static boolean exists(String assetName) throws SQLException {
|
||||
return DB.exists("Assets", "asset_name = ?", assetName);
|
||||
}
|
||||
|
||||
public static boolean exists(Connection connection, String assetName) throws SQLException {
|
||||
return DB.exists(connection, "Assets", "asset_name = ?", assetName);
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -8,6 +8,7 @@ import java.sql.Connection;
|
||||
import java.sql.PreparedStatement;
|
||||
import java.sql.ResultSet;
|
||||
import java.sql.SQLException;
|
||||
import java.sql.Savepoint;
|
||||
import java.sql.Timestamp;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
@ -395,8 +396,7 @@ public class Block {
|
||||
* @throws SQLException
|
||||
*/
|
||||
public static Block fromHeight(int height) throws SQLException {
|
||||
try (final Connection connection = DB.getConnection()) {
|
||||
PreparedStatement preparedStatement = connection.prepareStatement("SELECT " + DB_COLUMNS + " FROM Blocks WHERE height = ?");
|
||||
PreparedStatement preparedStatement = DB.getConnection().prepareStatement("SELECT " + DB_COLUMNS + " FROM Blocks WHERE height = ?");
|
||||
preparedStatement.setInt(1, height);
|
||||
|
||||
try {
|
||||
@ -405,10 +405,9 @@ public class Block {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected void save(Connection connection) throws SQLException {
|
||||
SaveHelper saveHelper = new SaveHelper(connection, "Blocks");
|
||||
protected void save() throws SQLException {
|
||||
SaveHelper saveHelper = new SaveHelper("Blocks");
|
||||
|
||||
saveHelper.bind("signature", this.getSignature()).bind("version", this.version).bind("reference", this.reference)
|
||||
.bind("transaction_count", this.transactionCount).bind("total_fees", this.totalFees).bind("transactions_signature", this.transactionsSignature)
|
||||
@ -742,17 +741,16 @@ public class Block {
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether Block is valid using passed connection.
|
||||
* Returns whether Block is valid.
|
||||
* <p>
|
||||
* Performs various tests like checking for parent block, correct block timestamp, version, generating balance, etc.
|
||||
* <p>
|
||||
* Checks block's transactions using an HSQLDB "SAVEPOINT" and hence needs to be called within an ongoing SQL Transaction.
|
||||
*
|
||||
* @param connection
|
||||
* @return true if block is valid, false otherwise.
|
||||
* @throws SQLException
|
||||
*/
|
||||
public boolean isValid(Connection connection) throws SQLException {
|
||||
public boolean isValid() throws SQLException {
|
||||
// TODO
|
||||
|
||||
// Check parent blocks exists
|
||||
@ -796,7 +794,7 @@ public class Block {
|
||||
}
|
||||
|
||||
// Check transactions
|
||||
DB.createSavepoint(connection, "BLOCK_TRANSACTIONS");
|
||||
Savepoint savepoint = DB.createSavepoint("BLOCK_TRANSACTIONS");
|
||||
try {
|
||||
for (Transaction transaction : this.getTransactions()) {
|
||||
// GenesisTransactions are not allowed (GenesisBlock overrides isValid() to allow them)
|
||||
@ -809,12 +807,12 @@ public class Block {
|
||||
|
||||
// Check transaction is even valid
|
||||
// NOTE: in Gen1 there was an extra block height passed to DeployATTransaction.isValid
|
||||
if (transaction.isValid(connection) != Transaction.ValidationResult.OK)
|
||||
if (transaction.isValid() != Transaction.ValidationResult.OK)
|
||||
return false;
|
||||
|
||||
// Process transaction to make sure other transactions validate properly
|
||||
try {
|
||||
transaction.process(connection);
|
||||
transaction.process();
|
||||
} catch (Exception e) {
|
||||
// LOGGER.error("Exception during transaction processing, tx " + Base58.encode(transaction.getSignature()), e);
|
||||
return false;
|
||||
@ -823,7 +821,7 @@ public class Block {
|
||||
} finally {
|
||||
// Revert back to savepoint
|
||||
try {
|
||||
DB.rollbackToSavepoint(connection, "BLOCK_TRANSACTIONS");
|
||||
DB.rollbackToSavepoint(savepoint);
|
||||
} catch (SQLException e) {
|
||||
/*
|
||||
* Rollback failure most likely due to prior SQLException, so catch rollback's SQLException and discard. A "return false" in try-block will
|
||||
@ -836,16 +834,16 @@ public class Block {
|
||||
return true;
|
||||
}
|
||||
|
||||
public void process(Connection connection) throws SQLException {
|
||||
public void process() throws SQLException {
|
||||
// Process transactions (we'll link them to this block after saving the block itself)
|
||||
List<Transaction> transactions = this.getTransactions();
|
||||
for (Transaction transaction : transactions)
|
||||
transaction.process(connection);
|
||||
transaction.process();
|
||||
|
||||
// If fees are non-zero then add fees to generator's balance
|
||||
BigDecimal blockFee = this.getTotalFees();
|
||||
if (blockFee.compareTo(BigDecimal.ZERO) == 1)
|
||||
this.generator.setConfirmedBalance(connection, Asset.QORA, this.generator.getConfirmedBalance(Asset.QORA).add(blockFee));
|
||||
this.generator.setConfirmedBalance(Asset.QORA, this.generator.getConfirmedBalance(Asset.QORA).add(blockFee));
|
||||
|
||||
// Link block into blockchain by fetching signature of highest block and setting that as our reference
|
||||
int blockchainHeight = BlockChain.getHeight();
|
||||
@ -854,7 +852,7 @@ public class Block {
|
||||
this.reference = latestBlock.getSignature();
|
||||
|
||||
this.height = blockchainHeight + 1;
|
||||
this.save(connection);
|
||||
this.save();
|
||||
|
||||
// Link transactions to this block, thus removing them from unconfirmed transactions list.
|
||||
for (int sequence = 0; sequence < transactions.size(); ++sequence) {
|
||||
@ -862,7 +860,7 @@ public class Block {
|
||||
|
||||
// Link transaction to this block
|
||||
BlockTransaction blockTransaction = new BlockTransaction(this.getSignature(), sequence, transaction.getSignature());
|
||||
blockTransaction.save(connection);
|
||||
blockTransaction.save();
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1,7 +1,6 @@
|
||||
package qora.block;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.sql.Connection;
|
||||
import java.sql.ResultSet;
|
||||
import java.sql.SQLException;
|
||||
|
||||
@ -58,17 +57,15 @@ public class BlockChain {
|
||||
// (Re)build database
|
||||
DB.rebuild();
|
||||
|
||||
try (final Connection connection = DB.getConnection()) {
|
||||
// Add Genesis Block
|
||||
GenesisBlock genesisBlock = GenesisBlock.getInstance();
|
||||
genesisBlock.process(connection);
|
||||
genesisBlock.process();
|
||||
|
||||
// Add QORA asset.
|
||||
// NOTE: Asset's transaction reference is Genesis Block's generator signature which doesn't exist as a transaction!
|
||||
Asset qoraAsset = new Asset(Asset.QORA, genesisBlock.getGenerator().getAddress(), "Qora", "This is the simulated Qora asset.", 10_000_000_000L,
|
||||
true, genesisBlock.getGeneratorSignature());
|
||||
qoraAsset.save(connection);
|
||||
}
|
||||
Asset qoraAsset = new Asset(Asset.QORA, genesisBlock.getGenerator().getAddress(), "Qora", "This is the simulated Qora asset.", 10_000_000_000L, true,
|
||||
genesisBlock.getGeneratorSignature());
|
||||
qoraAsset.save();
|
||||
}
|
||||
|
||||
/**
|
||||
@ -93,14 +90,12 @@ public class BlockChain {
|
||||
* @throws SQLException
|
||||
*/
|
||||
public static int getHeight() throws SQLException {
|
||||
try (final Connection connection = DB.getConnection()) {
|
||||
ResultSet rs = DB.checkedExecute(connection.prepareStatement("SELECT MAX(height) FROM Blocks"));
|
||||
ResultSet rs = DB.checkedExecute("SELECT MAX(height) FROM Blocks");
|
||||
if (rs == null)
|
||||
return 0;
|
||||
|
||||
return rs.getInt(1);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Return Qora balance adjusted to within min/max limits.
|
||||
|
@ -1,6 +1,5 @@
|
||||
package qora.block;
|
||||
|
||||
import java.sql.Connection;
|
||||
import java.sql.PreparedStatement;
|
||||
import java.sql.SQLException;
|
||||
|
||||
@ -40,8 +39,7 @@ public class BlockFactory {
|
||||
if (height == 1)
|
||||
return GenesisBlock.getInstance();
|
||||
|
||||
try (final Connection connection = DB.getConnection()) {
|
||||
PreparedStatement preparedStatement = connection.prepareStatement("SELECT signature FROM Blocks WHERE height = ?");
|
||||
PreparedStatement preparedStatement = DB.getConnection().prepareStatement("SELECT signature FROM Blocks WHERE height = ?");
|
||||
preparedStatement.setInt(1, height);
|
||||
|
||||
try {
|
||||
@ -50,7 +48,6 @@ public class BlockFactory {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Navigation
|
||||
|
||||
|
@ -1,6 +1,5 @@
|
||||
package qora.block;
|
||||
|
||||
import java.sql.Connection;
|
||||
import java.sql.ResultSet;
|
||||
import java.sql.SQLException;
|
||||
|
||||
@ -44,7 +43,6 @@ public class BlockTransaction {
|
||||
// Load/Save
|
||||
|
||||
protected BlockTransaction(byte[] blockSignature, int sequence) throws SQLException {
|
||||
try (final Connection connection = DB.getConnection()) {
|
||||
ResultSet rs = DB.checkedExecute("SELECT transaction_signature FROM BlockTransactions WHERE block_signature = ? and sequence = ?", blockSignature,
|
||||
sequence);
|
||||
if (rs == null)
|
||||
@ -54,7 +52,6 @@ public class BlockTransaction {
|
||||
this.sequence = sequence;
|
||||
this.transactionSignature = DB.getResultSetBytes(rs.getBinaryStream(1), Transaction.SIGNATURE_LENGTH);
|
||||
}
|
||||
}
|
||||
|
||||
protected BlockTransaction(byte[] transactionSignature) throws SQLException {
|
||||
ResultSet rs = DB.checkedExecute("SELECT block_signature, sequence FROM BlockTransactions WHERE transaction_signature = ?", transactionSignature);
|
||||
@ -97,8 +94,8 @@ public class BlockTransaction {
|
||||
}
|
||||
}
|
||||
|
||||
protected void save(Connection connection) throws SQLException {
|
||||
SaveHelper saveHelper = new SaveHelper(connection, "BlockTransactions");
|
||||
protected void save() throws SQLException {
|
||||
SaveHelper saveHelper = new SaveHelper("BlockTransactions");
|
||||
saveHelper.bind("block_signature", this.blockSignature).bind("sequence", this.sequence).bind("transaction_signature", this.transactionSignature);
|
||||
saveHelper.execute();
|
||||
}
|
||||
|
@ -3,7 +3,6 @@ package qora.block;
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.math.BigDecimal;
|
||||
import java.sql.Connection;
|
||||
import java.sql.ResultSet;
|
||||
import java.sql.SQLException;
|
||||
import java.util.ArrayList;
|
||||
@ -329,14 +328,14 @@ public class GenesisBlock extends Block {
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isValid(Connection connection) throws SQLException {
|
||||
public boolean isValid() throws SQLException {
|
||||
// Check there is no other block in DB
|
||||
if (BlockChain.getHeight() != 0)
|
||||
return false;
|
||||
|
||||
// Validate transactions
|
||||
for (Transaction transaction : this.getTransactions())
|
||||
if (transaction.isValid(connection) != Transaction.ValidationResult.OK)
|
||||
if (transaction.isValid() != Transaction.ValidationResult.OK)
|
||||
return false;
|
||||
|
||||
return true;
|
||||
|
@ -5,7 +5,6 @@ import java.io.IOException;
|
||||
import java.math.BigDecimal;
|
||||
import java.math.BigInteger;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.sql.Connection;
|
||||
import java.sql.ResultSet;
|
||||
import java.sql.SQLException;
|
||||
|
||||
@ -98,10 +97,10 @@ public class CreateOrderTransaction extends Transaction {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void save(Connection connection) throws SQLException {
|
||||
super.save(connection);
|
||||
public void save() throws SQLException {
|
||||
super.save();
|
||||
|
||||
SaveHelper saveHelper = new SaveHelper(connection, "CreateAssetOrderTransactions");
|
||||
SaveHelper saveHelper = new SaveHelper("CreateAssetOrderTransactions");
|
||||
saveHelper.bind("signature", this.signature).bind("creator", this.creator.getPublicKey()).bind("have_asset_id", this.order.getHaveAssetId())
|
||||
.bind("amount", this.order.getAmount()).bind("want_asset_id", this.order.getWantAssetId()).bind("price", this.order.getPrice());
|
||||
saveHelper.execute();
|
||||
@ -144,20 +143,20 @@ public class CreateOrderTransaction extends Transaction {
|
||||
|
||||
// Processing
|
||||
|
||||
public ValidationResult isValid(Connection connection) throws SQLException {
|
||||
public ValidationResult isValid() throws SQLException {
|
||||
// TODO
|
||||
|
||||
return ValidationResult.OK;
|
||||
}
|
||||
|
||||
public void process(Connection connection) throws SQLException {
|
||||
this.save(connection);
|
||||
public void process() throws SQLException {
|
||||
this.save();
|
||||
|
||||
// TODO
|
||||
}
|
||||
|
||||
public void orphan(Connection connection) throws SQLException {
|
||||
this.delete(connection);
|
||||
public void orphan() throws SQLException {
|
||||
this.delete();
|
||||
|
||||
// TODO
|
||||
}
|
||||
|
@ -4,7 +4,6 @@ import java.io.ByteArrayOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.math.BigDecimal;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.sql.Connection;
|
||||
import java.sql.ResultSet;
|
||||
import java.sql.SQLException;
|
||||
import java.util.Arrays;
|
||||
@ -103,10 +102,10 @@ public class GenesisTransaction extends Transaction {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void save(Connection connection) throws SQLException {
|
||||
super.save(connection);
|
||||
public void save() throws SQLException {
|
||||
super.save();
|
||||
|
||||
SaveHelper saveHelper = new SaveHelper(connection, "GenesisTransactions");
|
||||
SaveHelper saveHelper = new SaveHelper("GenesisTransactions");
|
||||
saveHelper.bind("signature", this.signature).bind("recipient", this.recipient.getAddress()).bind("amount", this.amount);
|
||||
saveHelper.execute();
|
||||
}
|
||||
@ -194,7 +193,7 @@ public class GenesisTransaction extends Transaction {
|
||||
}
|
||||
|
||||
@Override
|
||||
public ValidationResult isValid(Connection connection) {
|
||||
public ValidationResult isValid() {
|
||||
// Check amount is zero or positive
|
||||
if (this.amount.compareTo(BigDecimal.ZERO) == -1)
|
||||
return ValidationResult.NEGATIVE_AMOUNT;
|
||||
@ -207,25 +206,25 @@ public class GenesisTransaction extends Transaction {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void process(Connection connection) throws SQLException {
|
||||
this.save(connection);
|
||||
public void process() throws SQLException {
|
||||
this.save();
|
||||
|
||||
// Set recipient's balance
|
||||
this.recipient.setConfirmedBalance(connection, Asset.QORA, this.amount);
|
||||
this.recipient.setConfirmedBalance(Asset.QORA, this.amount);
|
||||
|
||||
// Set recipient's reference
|
||||
recipient.setLastReference(connection, this.signature);
|
||||
recipient.setLastReference(this.signature);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void orphan(Connection connection) throws SQLException {
|
||||
this.delete(connection);
|
||||
public void orphan() throws SQLException {
|
||||
this.delete();
|
||||
|
||||
// Reset recipient's balance
|
||||
this.recipient.deleteBalance(connection, Asset.QORA);
|
||||
this.recipient.deleteBalance(Asset.QORA);
|
||||
|
||||
// Set recipient's reference
|
||||
recipient.setLastReference(connection, null);
|
||||
recipient.setLastReference(null);
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -4,7 +4,6 @@ import java.io.ByteArrayOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.math.BigDecimal;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.sql.Connection;
|
||||
import java.sql.ResultSet;
|
||||
import java.sql.SQLException;
|
||||
import java.util.Arrays;
|
||||
@ -184,10 +183,10 @@ public class IssueAssetTransaction extends Transaction {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void save(Connection connection) throws SQLException {
|
||||
super.save(connection);
|
||||
public void save() throws SQLException {
|
||||
super.save();
|
||||
|
||||
SaveHelper saveHelper = new SaveHelper(connection, "IssueAssetTransactions");
|
||||
SaveHelper saveHelper = new SaveHelper("IssueAssetTransactions");
|
||||
saveHelper.bind("signature", this.signature).bind("creator", this.creator.getPublicKey()).bind("asset_name", this.assetName)
|
||||
.bind("description", this.description).bind("quantity", this.quantity).bind("is_divisible", this.isDivisible).bind("asset_id", this.assetId);
|
||||
saveHelper.execute();
|
||||
@ -268,7 +267,7 @@ public class IssueAssetTransaction extends Transaction {
|
||||
|
||||
// Processing
|
||||
|
||||
public ValidationResult isValid(Connection connection) throws SQLException {
|
||||
public ValidationResult isValid() throws SQLException {
|
||||
// Lowest cost checks first
|
||||
|
||||
// Are IssueAssetTransactions even allowed at this point?
|
||||
@ -297,55 +296,55 @@ public class IssueAssetTransaction extends Transaction {
|
||||
return ValidationResult.NEGATIVE_FEE;
|
||||
|
||||
// Check reference is correct
|
||||
if (!Arrays.equals(this.issuer.getLastReference(connection), this.reference))
|
||||
if (!Arrays.equals(this.issuer.getLastReference(), this.reference))
|
||||
return ValidationResult.INVALID_REFERENCE;
|
||||
|
||||
// Check issuer has enough funds
|
||||
if (this.issuer.getConfirmedBalance(connection, Asset.QORA).compareTo(this.fee) == -1)
|
||||
if (this.issuer.getConfirmedBalance(Asset.QORA).compareTo(this.fee) == -1)
|
||||
return ValidationResult.NO_BALANCE;
|
||||
|
||||
// XXX: Surely we want to check the asset name isn't already taken?
|
||||
if (Asset.exists(connection, this.assetName))
|
||||
if (Asset.exists(this.assetName))
|
||||
return ValidationResult.ASSET_ALREADY_EXISTS;
|
||||
|
||||
return ValidationResult.OK;
|
||||
}
|
||||
|
||||
public void process(Connection connection) throws SQLException {
|
||||
public void process() throws SQLException {
|
||||
// Issue asset
|
||||
Asset asset = new Asset(owner.getAddress(), this.assetName, this.description, this.quantity, this.isDivisible, this.reference);
|
||||
asset.save(connection);
|
||||
asset.save();
|
||||
|
||||
// Note newly assigned asset ID in our transaction record
|
||||
this.assetId = asset.getAssetId();
|
||||
|
||||
this.save(connection);
|
||||
this.save();
|
||||
|
||||
// Update issuer's balance
|
||||
this.issuer.setConfirmedBalance(connection, Asset.QORA, this.issuer.getConfirmedBalance(connection, Asset.QORA).subtract(this.fee));
|
||||
this.issuer.setConfirmedBalance(Asset.QORA, this.issuer.getConfirmedBalance(Asset.QORA).subtract(this.fee));
|
||||
|
||||
// Update issuer's reference
|
||||
this.issuer.setLastReference(connection, this.signature);
|
||||
this.issuer.setLastReference(this.signature);
|
||||
|
||||
// Add asset to owner
|
||||
this.owner.setConfirmedBalance(connection, this.assetId, BigDecimal.valueOf(this.quantity).setScale(8));
|
||||
this.owner.setConfirmedBalance(this.assetId, BigDecimal.valueOf(this.quantity).setScale(8));
|
||||
}
|
||||
|
||||
public void orphan(Connection connection) throws SQLException {
|
||||
public void orphan() throws SQLException {
|
||||
// Remove asset from owner
|
||||
this.owner.deleteBalance(connection, this.assetId);
|
||||
this.owner.deleteBalance(this.assetId);
|
||||
|
||||
// Unissue asset
|
||||
Asset asset = Asset.fromAssetId(this.assetId);
|
||||
asset.delete(connection);
|
||||
asset.delete();
|
||||
|
||||
this.delete(connection);
|
||||
this.delete();
|
||||
|
||||
// Update issuer's balance
|
||||
this.issuer.setConfirmedBalance(connection, Asset.QORA, this.issuer.getConfirmedBalance(connection, Asset.QORA).add(this.fee));
|
||||
this.issuer.setConfirmedBalance(Asset.QORA, this.issuer.getConfirmedBalance(Asset.QORA).add(this.fee));
|
||||
|
||||
// Update issuer's reference
|
||||
this.issuer.setLastReference(connection, this.reference);
|
||||
this.issuer.setLastReference(this.reference);
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -5,7 +5,6 @@ import java.io.IOException;
|
||||
import java.math.BigDecimal;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.charset.Charset;
|
||||
import java.sql.Connection;
|
||||
import java.sql.ResultSet;
|
||||
import java.sql.SQLException;
|
||||
import java.util.Arrays;
|
||||
@ -163,10 +162,10 @@ public class MessageTransaction extends Transaction {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void save(Connection connection) throws SQLException {
|
||||
super.save(connection);
|
||||
public void save() throws SQLException {
|
||||
super.save();
|
||||
|
||||
SaveHelper saveHelper = new SaveHelper(connection, "MessageTransactions");
|
||||
SaveHelper saveHelper = new SaveHelper("MessageTransactions");
|
||||
saveHelper.bind("signature", this.signature).bind("version", this.version).bind("sender", this.sender.getPublicKey())
|
||||
.bind("recipient", this.recipient.getAddress()).bind("is_text", this.isText).bind("is_encrypted", this.isEncrypted).bind("amount", this.amount)
|
||||
.bind("asset_id", this.assetId).bind("data", this.data);
|
||||
@ -274,7 +273,7 @@ public class MessageTransaction extends Transaction {
|
||||
|
||||
// Processing
|
||||
|
||||
public ValidationResult isValid(Connection connection) throws SQLException {
|
||||
public ValidationResult isValid() throws SQLException {
|
||||
// Lowest cost checks first
|
||||
|
||||
// Are message transactions even allowed at this point?
|
||||
@ -307,70 +306,70 @@ public class MessageTransaction extends Transaction {
|
||||
return ValidationResult.NEGATIVE_FEE;
|
||||
|
||||
// Check reference is correct
|
||||
if (!Arrays.equals(this.sender.getLastReference(connection), this.reference))
|
||||
if (!Arrays.equals(this.sender.getLastReference(), this.reference))
|
||||
return ValidationResult.INVALID_REFERENCE;
|
||||
|
||||
// Does asset exist? (This test not present in gen1)
|
||||
if (this.assetId != Asset.QORA && !Asset.exists(connection, this.assetId))
|
||||
if (this.assetId != Asset.QORA && !Asset.exists(this.assetId))
|
||||
return ValidationResult.ASSET_DOES_NOT_EXIST;
|
||||
|
||||
// If asset is QORA then we need to check amount + fee in one go
|
||||
if (this.assetId == Asset.QORA) {
|
||||
// Check sender has enough funds for amount + fee in QORA
|
||||
if (this.sender.getConfirmedBalance(connection, Asset.QORA).compareTo(this.amount.add(this.fee)) == -1)
|
||||
if (this.sender.getConfirmedBalance(Asset.QORA).compareTo(this.amount.add(this.fee)) == -1)
|
||||
return ValidationResult.NO_BALANCE;
|
||||
} else {
|
||||
// Check sender has enough funds for amount in whatever asset
|
||||
if (this.sender.getConfirmedBalance(connection, this.assetId).compareTo(this.amount) == -1)
|
||||
if (this.sender.getConfirmedBalance(this.assetId).compareTo(this.amount) == -1)
|
||||
return ValidationResult.NO_BALANCE;
|
||||
|
||||
// Check sender has enough funds for fee in QORA
|
||||
if (this.sender.getConfirmedBalance(connection, Asset.QORA).compareTo(this.fee) == -1)
|
||||
if (this.sender.getConfirmedBalance(Asset.QORA).compareTo(this.fee) == -1)
|
||||
return ValidationResult.NO_BALANCE;
|
||||
}
|
||||
|
||||
return ValidationResult.OK;
|
||||
}
|
||||
|
||||
public void process(Connection connection) throws SQLException {
|
||||
this.save(connection);
|
||||
public void process() throws SQLException {
|
||||
this.save();
|
||||
|
||||
// Update sender's balance due to amount
|
||||
this.sender.setConfirmedBalance(connection, this.assetId, this.sender.getConfirmedBalance(connection, this.assetId).subtract(this.amount));
|
||||
this.sender.setConfirmedBalance(this.assetId, this.sender.getConfirmedBalance(this.assetId).subtract(this.amount));
|
||||
// Update sender's balance due to fee
|
||||
this.sender.setConfirmedBalance(connection, Asset.QORA, this.sender.getConfirmedBalance(connection, Asset.QORA).subtract(this.fee));
|
||||
this.sender.setConfirmedBalance(Asset.QORA, this.sender.getConfirmedBalance(Asset.QORA).subtract(this.fee));
|
||||
|
||||
// Update recipient's balance
|
||||
this.recipient.setConfirmedBalance(connection, this.assetId, this.recipient.getConfirmedBalance(connection, this.assetId).add(this.amount));
|
||||
this.recipient.setConfirmedBalance(this.assetId, this.recipient.getConfirmedBalance(this.assetId).add(this.amount));
|
||||
|
||||
// Update sender's reference
|
||||
this.sender.setLastReference(connection, this.signature);
|
||||
this.sender.setLastReference(this.signature);
|
||||
|
||||
// For QORA amounts only: if recipient has no reference yet, then this is their starting reference
|
||||
if (this.assetId == Asset.QORA && this.recipient.getLastReference(connection) == null)
|
||||
this.recipient.setLastReference(connection, this.signature);
|
||||
if (this.assetId == Asset.QORA && this.recipient.getLastReference() == null)
|
||||
this.recipient.setLastReference(this.signature);
|
||||
}
|
||||
|
||||
public void orphan(Connection connection) throws SQLException {
|
||||
this.delete(connection);
|
||||
public void orphan() throws SQLException {
|
||||
this.delete();
|
||||
|
||||
// Update sender's balance due to amount
|
||||
this.sender.setConfirmedBalance(connection, this.assetId, this.sender.getConfirmedBalance(connection, this.assetId).add(this.amount));
|
||||
this.sender.setConfirmedBalance(this.assetId, this.sender.getConfirmedBalance(this.assetId).add(this.amount));
|
||||
// Update sender's balance due to fee
|
||||
this.sender.setConfirmedBalance(connection, Asset.QORA, this.sender.getConfirmedBalance(connection, Asset.QORA).add(this.fee));
|
||||
this.sender.setConfirmedBalance(Asset.QORA, this.sender.getConfirmedBalance(Asset.QORA).add(this.fee));
|
||||
|
||||
// Update recipient's balance
|
||||
this.recipient.setConfirmedBalance(connection, this.assetId, this.recipient.getConfirmedBalance(connection, this.assetId).subtract(this.amount));
|
||||
this.recipient.setConfirmedBalance(this.assetId, this.recipient.getConfirmedBalance(this.assetId).subtract(this.amount));
|
||||
|
||||
// Update sender's reference
|
||||
this.sender.setLastReference(connection, this.reference);
|
||||
this.sender.setLastReference(this.reference);
|
||||
|
||||
/*
|
||||
* For QORA amounts only: If recipient's last reference is this transaction's signature, then they can't have made any transactions of their own (which
|
||||
* would have changed their last reference) thus this is their first reference so remove it.
|
||||
*/
|
||||
if (this.assetId == Asset.QORA && Arrays.equals(this.recipient.getLastReference(connection), this.signature))
|
||||
this.recipient.setLastReference(connection, null);
|
||||
if (this.assetId == Asset.QORA && Arrays.equals(this.recipient.getLastReference(), this.signature))
|
||||
this.recipient.setLastReference(null);
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -4,7 +4,6 @@ import java.io.ByteArrayOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.math.BigDecimal;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.sql.Connection;
|
||||
import java.sql.ResultSet;
|
||||
import java.sql.SQLException;
|
||||
import java.util.Arrays;
|
||||
@ -111,10 +110,10 @@ public class PaymentTransaction extends Transaction {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void save(Connection connection) throws SQLException {
|
||||
super.save(connection);
|
||||
public void save() throws SQLException {
|
||||
super.save();
|
||||
|
||||
SaveHelper saveHelper = new SaveHelper(connection, "PaymentTransactions");
|
||||
SaveHelper saveHelper = new SaveHelper("PaymentTransactions");
|
||||
saveHelper.bind("signature", this.signature).bind("sender", this.sender.getPublicKey()).bind("recipient", this.recipient.getAddress()).bind("amount",
|
||||
this.amount);
|
||||
saveHelper.execute();
|
||||
@ -174,7 +173,7 @@ public class PaymentTransaction extends Transaction {
|
||||
|
||||
// Processing
|
||||
|
||||
public ValidationResult isValid(Connection connection) throws SQLException {
|
||||
public ValidationResult isValid() throws SQLException {
|
||||
// Lowest cost checks first
|
||||
|
||||
// Check recipient is a valid address
|
||||
@ -190,51 +189,51 @@ public class PaymentTransaction extends Transaction {
|
||||
return ValidationResult.NEGATIVE_FEE;
|
||||
|
||||
// Check reference is correct
|
||||
if (!Arrays.equals(this.sender.getLastReference(connection), this.reference))
|
||||
if (!Arrays.equals(this.sender.getLastReference(), this.reference))
|
||||
return ValidationResult.INVALID_REFERENCE;
|
||||
|
||||
// Check sender has enough funds
|
||||
if (this.sender.getConfirmedBalance(connection, Asset.QORA).compareTo(this.amount.add(this.fee)) == -1)
|
||||
if (this.sender.getConfirmedBalance(Asset.QORA).compareTo(this.amount.add(this.fee)) == -1)
|
||||
return ValidationResult.NO_BALANCE;
|
||||
|
||||
return ValidationResult.OK;
|
||||
}
|
||||
|
||||
public void process(Connection connection) throws SQLException {
|
||||
this.save(connection);
|
||||
public void process() throws SQLException {
|
||||
this.save();
|
||||
|
||||
// Update sender's balance
|
||||
this.sender.setConfirmedBalance(connection, Asset.QORA, this.sender.getConfirmedBalance(connection, Asset.QORA).subtract(this.amount).subtract(this.fee));
|
||||
this.sender.setConfirmedBalance(Asset.QORA, this.sender.getConfirmedBalance(Asset.QORA).subtract(this.amount).subtract(this.fee));
|
||||
|
||||
// Update recipient's balance
|
||||
this.recipient.setConfirmedBalance(connection, Asset.QORA, this.recipient.getConfirmedBalance(connection, Asset.QORA).add(this.amount));
|
||||
this.recipient.setConfirmedBalance(Asset.QORA, this.recipient.getConfirmedBalance(Asset.QORA).add(this.amount));
|
||||
|
||||
// Update sender's reference
|
||||
this.sender.setLastReference(connection, this.signature);
|
||||
this.sender.setLastReference(this.signature);
|
||||
|
||||
// If recipient has no reference yet, then this is their starting reference
|
||||
if (this.recipient.getLastReference(connection) == null)
|
||||
this.recipient.setLastReference(connection, this.signature);
|
||||
if (this.recipient.getLastReference() == null)
|
||||
this.recipient.setLastReference(this.signature);
|
||||
}
|
||||
|
||||
public void orphan(Connection connection) throws SQLException {
|
||||
this.delete(connection);
|
||||
public void orphan() throws SQLException {
|
||||
this.delete();
|
||||
|
||||
// Update sender's balance
|
||||
this.sender.setConfirmedBalance(connection, Asset.QORA, this.sender.getConfirmedBalance(connection, Asset.QORA).add(this.amount).add(this.fee));
|
||||
this.sender.setConfirmedBalance(Asset.QORA, this.sender.getConfirmedBalance(Asset.QORA).add(this.amount).add(this.fee));
|
||||
|
||||
// Update recipient's balance
|
||||
this.recipient.setConfirmedBalance(connection, Asset.QORA, this.recipient.getConfirmedBalance(connection, Asset.QORA).subtract(this.amount));
|
||||
this.recipient.setConfirmedBalance(Asset.QORA, this.recipient.getConfirmedBalance(Asset.QORA).subtract(this.amount));
|
||||
|
||||
// Update sender's reference
|
||||
this.sender.setLastReference(connection, this.reference);
|
||||
this.sender.setLastReference(this.reference);
|
||||
|
||||
/*
|
||||
* If recipient's last reference is this transaction's signature, then they can't have made any transactions of their own (which would have changed
|
||||
* their last reference) thus this is their first reference so remove it.
|
||||
*/
|
||||
if (Arrays.equals(this.recipient.getLastReference(connection), this.signature))
|
||||
this.recipient.setLastReference(connection, null);
|
||||
if (Arrays.equals(this.recipient.getLastReference(), this.signature))
|
||||
this.recipient.setLastReference(null);
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -3,7 +3,6 @@ package qora.transaction;
|
||||
import java.math.BigDecimal;
|
||||
import java.math.MathContext;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.sql.Connection;
|
||||
import java.sql.ResultSet;
|
||||
import java.sql.SQLException;
|
||||
import java.sql.Timestamp;
|
||||
@ -247,18 +246,18 @@ public abstract class Transaction {
|
||||
this.signature = signature;
|
||||
}
|
||||
|
||||
protected void save(Connection connection) throws SQLException {
|
||||
SaveHelper saveHelper = new SaveHelper(connection, "Transactions");
|
||||
protected void save() throws SQLException {
|
||||
SaveHelper saveHelper = new SaveHelper("Transactions");
|
||||
saveHelper.bind("signature", this.signature).bind("reference", this.reference).bind("type", this.type.value)
|
||||
.bind("creator", this.creator.getPublicKey()).bind("creation", new Timestamp(this.timestamp)).bind("fee", this.fee)
|
||||
.bind("milestone_block", null);
|
||||
saveHelper.execute();
|
||||
}
|
||||
|
||||
protected void delete(Connection connection) throws SQLException {
|
||||
protected void delete() throws SQLException {
|
||||
// NOTE: The corresponding row in sub-table is deleted automatically by the database thanks to "ON DELETE CASCADE" in the sub-table's FOREIGN KEY
|
||||
// definition.
|
||||
DB.checkedExecute(connection, "DELETE FROM Transactions WHERE signature = ?", this.signature);
|
||||
DB.checkedExecute("DELETE FROM Transactions WHERE signature = ?", this.signature);
|
||||
}
|
||||
|
||||
// Navigation
|
||||
@ -403,40 +402,37 @@ public abstract class Transaction {
|
||||
/**
|
||||
* Returns whether transaction can be added to the blockchain.
|
||||
* <p>
|
||||
* Checks if transaction can have {@link Transaction#process(Connection)} called.
|
||||
* Checks if transaction can have {@link Transaction#process()} called.
|
||||
* <p>
|
||||
* Expected to be called within an ongoing SQL Transaction, typically by {@link Block#process(Connection)}, hence the need for the Connection parameter.
|
||||
* Expected to be called within an ongoing SQL Transaction, typically by {@link Block#process()}.
|
||||
* <p>
|
||||
* Transactions that have already been processed will return false.
|
||||
*
|
||||
* @param connection
|
||||
* @return true if transaction can be processed, false otherwise
|
||||
* @throws SQLException
|
||||
*/
|
||||
public abstract ValidationResult isValid(Connection connection) throws SQLException;
|
||||
public abstract ValidationResult isValid() throws SQLException;
|
||||
|
||||
/**
|
||||
* Actually process a transaction, updating the blockchain.
|
||||
* <p>
|
||||
* Processes transaction, updating balances, references, assets, etc. as appropriate.
|
||||
* <p>
|
||||
* Expected to be called within an ongoing SQL Transaction, typically by {@link Block#process(Connection)}, hence the need for the Connection parameter.
|
||||
* Expected to be called within an ongoing SQL Transaction, typically by {@link Block#process()}.
|
||||
*
|
||||
* @param connection
|
||||
* @throws SQLException
|
||||
*/
|
||||
public abstract void process(Connection connection) throws SQLException;
|
||||
public abstract void process() throws SQLException;
|
||||
|
||||
/**
|
||||
* Undo transaction, updating the blockchain.
|
||||
* <p>
|
||||
* Undoes transaction, updating balances, references, assets, etc. as appropriate.
|
||||
* <p>
|
||||
* Expected to be called within an ongoing SQL Transaction, typically by {@link Block#process(Connection)}, hence the need for the Connection parameter.
|
||||
* Expected to be called within an ongoing SQL Transaction, typically by {@link Block#process()}.
|
||||
*
|
||||
* @param connection
|
||||
* @throws SQLException
|
||||
*/
|
||||
public abstract void orphan(Connection connection) throws SQLException;
|
||||
public abstract void orphan() throws SQLException;
|
||||
|
||||
}
|
||||
|
@ -3,13 +3,11 @@ package test;
|
||||
import static org.junit.Assert.*;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.sql.Connection;
|
||||
import java.sql.SQLException;
|
||||
import java.util.List;
|
||||
|
||||
import org.junit.Test;
|
||||
|
||||
import database.DB;
|
||||
import qora.block.Block;
|
||||
import qora.block.GenesisBlock;
|
||||
import qora.transaction.Transaction;
|
||||
@ -19,7 +17,6 @@ public class blocks extends common {
|
||||
|
||||
@Test
|
||||
public void testGenesisBlockTransactions() throws SQLException {
|
||||
try (final Connection connection = DB.getConnection()) {
|
||||
GenesisBlock block = GenesisBlock.getInstance();
|
||||
assertNotNull(block);
|
||||
assertTrue(block.isSignatureValid());
|
||||
@ -35,7 +32,7 @@ public class blocks extends common {
|
||||
assertTrue(transaction.getFee().compareTo(BigDecimal.ZERO) == 0);
|
||||
assertNull(transaction.getReference());
|
||||
assertTrue(transaction.isSignatureValid());
|
||||
assertEquals(Transaction.ValidationResult.OK, transaction.isValid(connection));
|
||||
assertEquals(Transaction.ValidationResult.OK, transaction.isValid());
|
||||
}
|
||||
|
||||
// Attempt to load first transaction directly from database
|
||||
@ -45,13 +42,11 @@ public class blocks extends common {
|
||||
assertTrue(transaction.getFee().compareTo(BigDecimal.ZERO) == 0);
|
||||
assertNull(transaction.getReference());
|
||||
assertTrue(transaction.isSignatureValid());
|
||||
assertEquals(Transaction.ValidationResult.OK, transaction.isValid(connection));
|
||||
}
|
||||
assertEquals(Transaction.ValidationResult.OK, transaction.isValid());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testBlockPaymentTransactions() throws SQLException {
|
||||
try (final Connection connection = DB.getConnection()) {
|
||||
// Block 949 has lots of varied transactions
|
||||
// Blocks 390 & 754 have only payment transactions
|
||||
Block block = Block.fromHeight(754);
|
||||
@ -77,11 +72,9 @@ public class blocks extends common {
|
||||
assertNotNull(transaction.getReference());
|
||||
assertTrue(transaction.isSignatureValid());
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testBlockSerialization() throws SQLException {
|
||||
try (final Connection connection = DB.getConnection()) {
|
||||
// Block 949 has lots of varied transactions
|
||||
// Blocks 390 & 754 have only payment transactions
|
||||
Block block = Block.fromHeight(754);
|
||||
@ -92,6 +85,5 @@ public class blocks extends common {
|
||||
|
||||
assertEquals(block.getDataLength(), bytes.length);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -21,7 +21,7 @@ public class common {
|
||||
|
||||
@AfterClass
|
||||
public static void closeDatabase() throws SQLException {
|
||||
DB.close();
|
||||
DB.shutdown();
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -13,37 +13,29 @@ public class connections extends common {
|
||||
|
||||
@Test
|
||||
public void testConnection() {
|
||||
try {
|
||||
Connection c = DB.getConnection();
|
||||
c.close();
|
||||
} catch (SQLException e) {
|
||||
e.printStackTrace();
|
||||
fail();
|
||||
}
|
||||
Connection connection = DB.getConnection();
|
||||
assertNotNull(connection);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSimultaneousConnections() {
|
||||
// First connection is the thread-local one
|
||||
Connection connection = DB.getConnection();
|
||||
assertNotNull(connection);
|
||||
|
||||
int n_connections = 5;
|
||||
Connection[] connections = new Connection[n_connections];
|
||||
|
||||
try {
|
||||
for (int i = 0; i < n_connections; ++i)
|
||||
for (int i = 0; i < n_connections; ++i) {
|
||||
connections[i] = DB.getConnection();
|
||||
|
||||
// Close in same order as opening
|
||||
for (int i = 0; i < n_connections; ++i)
|
||||
connections[i].close();
|
||||
} catch (SQLException e) {
|
||||
e.printStackTrace();
|
||||
fail();
|
||||
assertEquals(connection, connections[i]);
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testConnectionAfterShutdown() {
|
||||
try {
|
||||
DB.close();
|
||||
DB.shutdown();
|
||||
} catch (SQLException e) {
|
||||
e.printStackTrace();
|
||||
fail();
|
||||
@ -51,8 +43,8 @@ public class connections extends common {
|
||||
|
||||
try {
|
||||
DB.open();
|
||||
Connection c = DB.getConnection();
|
||||
c.close();
|
||||
Connection connection = DB.getConnection();
|
||||
assertNotNull(connection);
|
||||
} catch (SQLException e) {
|
||||
e.printStackTrace();
|
||||
fail();
|
||||
|
@ -164,7 +164,7 @@ public class migrate extends common {
|
||||
|
||||
JSONArray transactions = (JSONArray) json.get("transactions");
|
||||
|
||||
DB.startTransaction(c);
|
||||
DB.startTransaction();
|
||||
|
||||
// Blocks:
|
||||
// signature, version, reference, transaction_count, total_fees, transactions_signature, height, generation, generating_balance, generator,
|
||||
@ -590,7 +590,7 @@ public class migrate extends common {
|
||||
blockTxPStmt.execute();
|
||||
blockTxPStmt.clearParameters();
|
||||
|
||||
DB.commit(c);
|
||||
DB.commit();
|
||||
}
|
||||
|
||||
// new milestone block every 500 blocks?
|
||||
@ -600,7 +600,6 @@ public class migrate extends common {
|
||||
++height;
|
||||
}
|
||||
|
||||
c.close();
|
||||
System.out.println("Migration finished with new blockchain height " + BlockChain.getHeight());
|
||||
}
|
||||
|
||||
|
@ -1,13 +1,11 @@
|
||||
package test;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.sql.Connection;
|
||||
import java.sql.SQLException;
|
||||
import java.time.Instant;
|
||||
|
||||
import org.junit.Test;
|
||||
|
||||
import database.DB;
|
||||
import qora.account.PublicKeyAccount;
|
||||
import qora.transaction.PaymentTransaction;
|
||||
import utils.Base58;
|
||||
@ -25,9 +23,7 @@ public class save extends common {
|
||||
PaymentTransaction paymentTransaction = new PaymentTransaction(sender, "Qrecipient", BigDecimal.valueOf(12345L), BigDecimal.ONE,
|
||||
Instant.now().getEpochSecond(), reference, signature);
|
||||
|
||||
try (final Connection connection = DB.getConnection()) {
|
||||
paymentTransaction.save(connection);
|
||||
}
|
||||
paymentTransaction.save();
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -2,14 +2,12 @@ package test;
|
||||
|
||||
import static org.junit.Assert.*;
|
||||
|
||||
import java.sql.Connection;
|
||||
import java.sql.SQLException;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
|
||||
import org.junit.Test;
|
||||
|
||||
import database.DB;
|
||||
import qora.block.Block;
|
||||
import qora.block.GenesisBlock;
|
||||
import qora.transaction.GenesisTransaction;
|
||||
@ -50,7 +48,6 @@ public class transactions extends common {
|
||||
|
||||
@Test
|
||||
public void testPaymentSerialization() throws SQLException, ParseException {
|
||||
try (final Connection connection = DB.getConnection()) {
|
||||
// Block 949 has lots of varied transactions
|
||||
// Blocks 390 & 754 have only payment transactions
|
||||
Block block = Block.fromHeight(754);
|
||||
@ -63,7 +60,6 @@ public class transactions extends common {
|
||||
for (Transaction transaction : transactions)
|
||||
testGenericSerialization(transaction);
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testMessageSerialization() throws SQLException, ParseException {
|
||||
|
Loading…
x
Reference in New Issue
Block a user