3
0
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:
catbref 2018-06-06 11:39:58 +01:00
parent 3a6276c4a9
commit 5b78268915
22 changed files with 483 additions and 600 deletions

View File

@ -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) {
}
local.remove();
}
public static void commit(Connection c) throws SQLException {
c.prepareStatement("COMMIT").execute();
public static void startTransaction() throws SQLException {
Connection connection = DB.getConnection();
connection.setTransactionIsolation(Connection.TRANSACTION_SERIALIZABLE);
connection.setAutoCommit(false);
}
public static void rollback(Connection c) throws SQLException {
c.prepareStatement("ROLLBACK").execute();
public static void commit() throws SQLException {
Connection connection = DB.getConnection();
connection.commit();
connection.setAutoCommit(true);
}
public static void createSavepoint(Connection c, String savepointName) throws SQLException {
c.prepareStatement("SAVEPOINT " + savepointName).execute();
public static void rollback() throws SQLException {
Connection connection = DB.getConnection();
connection.rollback();
connection.setAutoCommit(true);
}
public static void rollbackToSavepoint(Connection c, String savepointName) throws SQLException {
c.prepareStatement("ROLLBACK TO SAVEPOINT " + savepointName).execute();
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)

View File

@ -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,234 +58,232 @@ 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.
*
* 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.
*/
/*
* 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.
*/
switch (databaseVersion) {
case 0:
// create from new
stmt.execute("SET DATABASE DEFAULT TABLE TYPE CACHED");
stmt.execute("SET FILES SPACE TRUE");
stmt.execute("CREATE TABLE DatabaseInfo ( version INTEGER NOT NULL )");
stmt.execute("INSERT INTO DatabaseInfo VALUES ( 0 )");
stmt.execute("CREATE TYPE BlockSignature AS VARBINARY(128)");
stmt.execute("CREATE TYPE Signature AS VARBINARY(64)");
stmt.execute("CREATE TYPE QoraAddress AS VARCHAR(36)");
stmt.execute("CREATE TYPE QoraPublicKey AS VARBINARY(32)");
stmt.execute("CREATE TYPE QoraAmount AS DECIMAL(19, 8)");
stmt.execute("CREATE TYPE RegisteredName AS VARCHAR(400) COLLATE SQL_TEXT_UCC");
stmt.execute("CREATE TYPE NameData AS VARCHAR(4000)");
stmt.execute("CREATE TYPE PollName AS VARCHAR(400) COLLATE SQL_TEXT_UCC");
stmt.execute("CREATE TYPE PollOption AS VARCHAR(400) COLLATE SQL_TEXT_UCC");
stmt.execute("CREATE TYPE DataHash AS VARCHAR(100)");
stmt.execute("CREATE TYPE AssetID AS BIGINT");
stmt.execute("CREATE TYPE AssetName AS VARCHAR(400) COLLATE SQL_TEXT_UCC");
stmt.execute("CREATE TYPE AssetOrderID AS VARCHAR(100)");
stmt.execute("CREATE TYPE ATName AS VARCHAR(200) COLLATE SQL_TEXT_UCC");
stmt.execute("CREATE TYPE ATType AS VARCHAR(200) COLLATE SQL_TEXT_UCC");
break;
switch (databaseVersion) {
case 0:
// create from new
stmt.execute("SET DATABASE DEFAULT TABLE TYPE CACHED");
stmt.execute("SET FILES SPACE TRUE");
stmt.execute("CREATE TABLE DatabaseInfo ( version INTEGER NOT NULL )");
stmt.execute("INSERT INTO DatabaseInfo VALUES ( 0 )");
stmt.execute("CREATE TYPE BlockSignature AS VARBINARY(128)");
stmt.execute("CREATE TYPE Signature AS VARBINARY(64)");
stmt.execute("CREATE TYPE QoraAddress AS VARCHAR(36)");
stmt.execute("CREATE TYPE QoraPublicKey AS VARBINARY(32)");
stmt.execute("CREATE TYPE QoraAmount AS DECIMAL(19, 8)");
stmt.execute("CREATE TYPE RegisteredName AS VARCHAR(400) COLLATE SQL_TEXT_UCC");
stmt.execute("CREATE TYPE NameData AS VARCHAR(4000)");
stmt.execute("CREATE TYPE PollName AS VARCHAR(400) COLLATE SQL_TEXT_UCC");
stmt.execute("CREATE TYPE PollOption AS VARCHAR(400) COLLATE SQL_TEXT_UCC");
stmt.execute("CREATE TYPE DataHash AS VARCHAR(100)");
stmt.execute("CREATE TYPE AssetID AS BIGINT");
stmt.execute("CREATE TYPE AssetName AS VARCHAR(400) COLLATE SQL_TEXT_UCC");
stmt.execute("CREATE TYPE AssetOrderID AS VARCHAR(100)");
stmt.execute("CREATE TYPE ATName AS VARCHAR(200) COLLATE SQL_TEXT_UCC");
stmt.execute("CREATE TYPE ATType AS VARCHAR(200) COLLATE SQL_TEXT_UCC");
break;
case 1:
// Blocks
stmt.execute("CREATE TABLE Blocks (signature BlockSignature PRIMARY KEY, version TINYINT NOT NULL, reference BlockSignature, "
+ "transaction_count INTEGER NOT NULL, total_fees QoraAmount NOT NULL, transactions_signature Signature NOT NULL, "
+ "height INTEGER NOT NULL, generation TIMESTAMP NOT NULL, generating_balance QoraAmount NOT NULL, "
+ "generator QoraPublicKey NOT NULL, generator_signature Signature NOT NULL, AT_data VARBINARY(20000), AT_fees QoraAmount)");
stmt.execute("CREATE INDEX BlockHeightIndex ON Blocks (height)");
stmt.execute("CREATE INDEX BlockGeneratorIndex ON Blocks (generator)");
stmt.execute("CREATE INDEX BlockReferenceIndex ON Blocks (reference)");
stmt.execute("SET TABLE Blocks NEW SPACE");
break;
case 1:
// Blocks
stmt.execute("CREATE TABLE Blocks (signature BlockSignature PRIMARY KEY, version TINYINT NOT NULL, reference BlockSignature, "
+ "transaction_count INTEGER NOT NULL, total_fees QoraAmount NOT NULL, transactions_signature Signature NOT NULL, "
+ "height INTEGER NOT NULL, generation TIMESTAMP NOT NULL, generating_balance QoraAmount NOT NULL, "
+ "generator QoraPublicKey NOT NULL, generator_signature Signature NOT NULL, AT_data VARBINARY(20000), AT_fees QoraAmount)");
stmt.execute("CREATE INDEX BlockHeightIndex ON Blocks (height)");
stmt.execute("CREATE INDEX BlockGeneratorIndex ON Blocks (generator)");
stmt.execute("CREATE INDEX BlockReferenceIndex ON Blocks (reference)");
stmt.execute("SET TABLE Blocks NEW SPACE");
break;
case 2:
// Generic transactions (null reference, creator and milestone_block for genesis transactions)
stmt.execute("CREATE TABLE Transactions (signature Signature PRIMARY KEY, reference Signature, type TINYINT NOT NULL, "
+ "creator QoraPublicKey, creation TIMESTAMP NOT NULL, fee QoraAmount NOT NULL, milestone_block BlockSignature)");
stmt.execute("CREATE INDEX TransactionTypeIndex ON Transactions (type)");
stmt.execute("CREATE INDEX TransactionCreationIndex ON Transactions (creation)");
stmt.execute("CREATE INDEX TransactionCreatorIndex ON Transactions (creator)");
stmt.execute("CREATE INDEX TransactionReferenceIndex ON Transactions (reference)");
stmt.execute("SET TABLE Transactions NEW SPACE");
case 2:
// Generic transactions (null reference, creator and milestone_block for genesis transactions)
stmt.execute("CREATE TABLE Transactions (signature Signature PRIMARY KEY, reference Signature, type TINYINT NOT NULL, "
+ "creator QoraPublicKey, creation TIMESTAMP NOT NULL, fee QoraAmount NOT NULL, milestone_block BlockSignature)");
stmt.execute("CREATE INDEX TransactionTypeIndex ON Transactions (type)");
stmt.execute("CREATE INDEX TransactionCreationIndex ON Transactions (creation)");
stmt.execute("CREATE INDEX TransactionCreatorIndex ON Transactions (creator)");
stmt.execute("CREATE INDEX TransactionReferenceIndex ON Transactions (reference)");
stmt.execute("SET TABLE Transactions NEW SPACE");
// Transaction-Block mapping ("signature" is unique as a transaction cannot be included in more than one block)
stmt.execute("CREATE TABLE BlockTransactions (block_signature BlockSignature, sequence INTEGER, transaction_signature Signature, "
+ "PRIMARY KEY (block_signature, sequence), FOREIGN KEY (transaction_signature) REFERENCES Transactions (signature) ON DELETE CASCADE, "
+ "FOREIGN KEY (block_signature) REFERENCES Blocks (signature) ON DELETE CASCADE)");
stmt.execute("SET TABLE BlockTransactions NEW SPACE");
// Transaction-Block mapping ("signature" is unique as a transaction cannot be included in more than one block)
stmt.execute("CREATE TABLE BlockTransactions (block_signature BlockSignature, sequence INTEGER, transaction_signature Signature, "
+ "PRIMARY KEY (block_signature, sequence), FOREIGN KEY (transaction_signature) REFERENCES Transactions (signature) ON DELETE CASCADE, "
+ "FOREIGN KEY (block_signature) REFERENCES Blocks (signature) ON DELETE CASCADE)");
stmt.execute("SET TABLE BlockTransactions NEW SPACE");
// Unconfirmed transactions
// Do we need this? If a transaction doesn't have a corresponding BlockTransactions record then it's unconfirmed?
stmt.execute("CREATE TABLE UnconfirmedTransactions (signature Signature PRIMARY KEY, expiry TIMESTAMP NOT NULL)");
stmt.execute("CREATE INDEX UnconfirmedTransactionExpiryIndex ON UnconfirmedTransactions (expiry)");
// Unconfirmed transactions
// Do we need this? If a transaction doesn't have a corresponding BlockTransactions record then it's unconfirmed?
stmt.execute("CREATE TABLE UnconfirmedTransactions (signature Signature PRIMARY KEY, expiry TIMESTAMP NOT NULL)");
stmt.execute("CREATE INDEX UnconfirmedTransactionExpiryIndex ON UnconfirmedTransactions (expiry)");
// Transaction recipients
stmt.execute("CREATE TABLE TransactionRecipients (signature Signature, recipient QoraAddress NOT NULL, "
+ "FOREIGN KEY (signature) REFERENCES Transactions (signature) ON DELETE CASCADE)");
stmt.execute("SET TABLE TransactionRecipients NEW SPACE");
break;
// Transaction recipients
stmt.execute("CREATE TABLE TransactionRecipients (signature Signature, recipient QoraAddress NOT NULL, "
+ "FOREIGN KEY (signature) REFERENCES Transactions (signature) ON DELETE CASCADE)");
stmt.execute("SET TABLE TransactionRecipients NEW SPACE");
break;
case 3:
// Genesis Transactions
stmt.execute("CREATE TABLE GenesisTransactions (signature Signature, recipient QoraAddress NOT NULL, "
+ "amount QoraAmount NOT NULL, PRIMARY KEY (signature), "
+ "FOREIGN KEY (signature) REFERENCES Transactions (signature) ON DELETE CASCADE)");
break;
case 3:
// Genesis Transactions
stmt.execute("CREATE TABLE GenesisTransactions (signature Signature, recipient QoraAddress NOT NULL, "
+ "amount QoraAmount NOT NULL, PRIMARY KEY (signature), "
+ "FOREIGN KEY (signature) REFERENCES Transactions (signature) ON DELETE CASCADE)");
break;
case 4:
// Payment Transactions
stmt.execute("CREATE TABLE PaymentTransactions (signature Signature, sender QoraPublicKey NOT NULL, recipient QoraAddress NOT NULL, "
+ "amount QoraAmount NOT NULL, PRIMARY KEY (signature), "
+ "FOREIGN KEY (signature) REFERENCES Transactions (signature) ON DELETE CASCADE)");
break;
case 4:
// Payment Transactions
stmt.execute("CREATE TABLE PaymentTransactions (signature Signature, sender QoraPublicKey NOT NULL, recipient QoraAddress NOT NULL, "
+ "amount QoraAmount NOT NULL, PRIMARY KEY (signature), "
+ "FOREIGN KEY (signature) REFERENCES Transactions (signature) ON DELETE CASCADE)");
break;
case 5:
// Register Name Transactions
stmt.execute("CREATE TABLE RegisterNameTransactions (signature Signature, registrant QoraPublicKey NOT NULL, name RegisteredName NOT NULL, "
+ "owner QoraAddress NOT NULL, data NameData NOT NULL, "
+ "PRIMARY KEY (signature), FOREIGN KEY (signature) REFERENCES Transactions (signature) ON DELETE CASCADE)");
break;
case 5:
// Register Name Transactions
stmt.execute("CREATE TABLE RegisterNameTransactions (signature Signature, registrant QoraPublicKey NOT NULL, name RegisteredName NOT NULL, "
+ "owner QoraAddress NOT NULL, data NameData NOT NULL, "
+ "PRIMARY KEY (signature), FOREIGN KEY (signature) REFERENCES Transactions (signature) ON DELETE CASCADE)");
break;
case 6:
// Update Name Transactions
stmt.execute("CREATE TABLE UpdateNameTransactions (signature Signature, owner QoraPublicKey NOT NULL, name RegisteredName NOT NULL, "
+ "new_owner QoraAddress NOT NULL, new_data NameData NOT NULL, "
+ "PRIMARY KEY (signature), FOREIGN KEY (signature) REFERENCES Transactions (signature) ON DELETE CASCADE)");
break;
case 6:
// Update Name Transactions
stmt.execute("CREATE TABLE UpdateNameTransactions (signature Signature, owner QoraPublicKey NOT NULL, name RegisteredName NOT NULL, "
+ "new_owner QoraAddress NOT NULL, new_data NameData NOT NULL, "
+ "PRIMARY KEY (signature), FOREIGN KEY (signature) REFERENCES Transactions (signature) ON DELETE CASCADE)");
break;
case 7:
// Sell Name Transactions
stmt.execute("CREATE TABLE SellNameTransactions (signature Signature, owner QoraPublicKey NOT NULL, name RegisteredName NOT NULL, "
+ "amount QoraAmount NOT NULL, PRIMARY KEY (signature), FOREIGN KEY (signature) REFERENCES Transactions (signature) ON DELETE CASCADE)");
break;
case 7:
// Sell Name Transactions
stmt.execute("CREATE TABLE SellNameTransactions (signature Signature, owner QoraPublicKey NOT NULL, name RegisteredName NOT NULL, "
+ "amount QoraAmount NOT NULL, PRIMARY KEY (signature), FOREIGN KEY (signature) REFERENCES Transactions (signature) ON DELETE CASCADE)");
break;
case 8:
// Cancel Sell Name Transactions
stmt.execute("CREATE TABLE CancelSellNameTransactions (signature Signature, owner QoraPublicKey NOT NULL, name RegisteredName NOT NULL, "
+ "PRIMARY KEY (signature), FOREIGN KEY (signature) REFERENCES Transactions (signature) ON DELETE CASCADE)");
break;
case 8:
// Cancel Sell Name Transactions
stmt.execute("CREATE TABLE CancelSellNameTransactions (signature Signature, owner QoraPublicKey NOT NULL, name RegisteredName NOT NULL, "
+ "PRIMARY KEY (signature), FOREIGN KEY (signature) REFERENCES Transactions (signature) ON DELETE CASCADE)");
break;
case 9:
// Buy Name Transactions
stmt.execute("CREATE TABLE BuyNameTransactions (signature Signature, buyer QoraPublicKey NOT NULL, name RegisteredName NOT NULL, "
+ "seller QoraAddress NOT NULL, amount QoraAmount NOT NULL, "
+ "PRIMARY KEY (signature), FOREIGN KEY (signature) REFERENCES Transactions (signature) ON DELETE CASCADE)");
break;
case 9:
// Buy Name Transactions
stmt.execute("CREATE TABLE BuyNameTransactions (signature Signature, buyer QoraPublicKey NOT NULL, name RegisteredName NOT NULL, "
+ "seller QoraAddress NOT NULL, amount QoraAmount NOT NULL, "
+ "PRIMARY KEY (signature), FOREIGN KEY (signature) REFERENCES Transactions (signature) ON DELETE CASCADE)");
break;
case 10:
// Create Poll Transactions
stmt.execute("CREATE TABLE CreatePollTransactions (signature Signature, creator QoraPublicKey NOT NULL, poll PollName NOT NULL, "
+ "description VARCHAR(4000) NOT NULL, "
+ "PRIMARY KEY (signature), FOREIGN KEY (signature) REFERENCES Transactions (signature) ON DELETE CASCADE)");
// Poll options. NB: option is implicitly NON NULL and UNIQUE due to being part of compound primary key
stmt.execute("CREATE TABLE CreatePollTransactionOptions (signature Signature, option PollOption, "
+ "PRIMARY KEY (signature, option), FOREIGN KEY (signature) REFERENCES CreatePollTransactions (signature) ON DELETE CASCADE)");
// For the future: add flag to polls to allow one or multiple votes per voter
break;
case 10:
// Create Poll Transactions
stmt.execute("CREATE TABLE CreatePollTransactions (signature Signature, creator QoraPublicKey NOT NULL, poll PollName NOT NULL, "
+ "description VARCHAR(4000) NOT NULL, "
+ "PRIMARY KEY (signature), FOREIGN KEY (signature) REFERENCES Transactions (signature) ON DELETE CASCADE)");
// Poll options. NB: option is implicitly NON NULL and UNIQUE due to being part of compound primary key
stmt.execute("CREATE TABLE CreatePollTransactionOptions (signature Signature, option PollOption, "
+ "PRIMARY KEY (signature, option), FOREIGN KEY (signature) REFERENCES CreatePollTransactions (signature) ON DELETE CASCADE)");
// For the future: add flag to polls to allow one or multiple votes per voter
break;
case 11:
// Vote On Poll Transactions
stmt.execute("CREATE TABLE VoteOnPollTransactions (signature Signature, voter QoraPublicKey NOT NULL, poll PollName NOT NULL, "
+ "option_index INTEGER NOT NULL, "
+ "PRIMARY KEY (signature), FOREIGN KEY (signature) REFERENCES Transactions (signature) ON DELETE CASCADE)");
break;
case 11:
// Vote On Poll Transactions
stmt.execute("CREATE TABLE VoteOnPollTransactions (signature Signature, voter QoraPublicKey NOT NULL, poll PollName NOT NULL, "
+ "option_index INTEGER NOT NULL, "
+ "PRIMARY KEY (signature), FOREIGN KEY (signature) REFERENCES Transactions (signature) ON DELETE CASCADE)");
break;
case 12:
// Arbitrary/Multi-payment Transaction Payments
stmt.execute("CREATE TABLE SharedTransactionPayments (signature Signature, recipient QoraPublicKey NOT NULL, "
+ "amount QoraAmount NOT NULL, asset_id AssetID NOT NULL, "
+ "PRIMARY KEY (signature, recipient, asset_id), FOREIGN KEY (signature) REFERENCES Transactions (signature) ON DELETE CASCADE)");
break;
case 12:
// Arbitrary/Multi-payment Transaction Payments
stmt.execute("CREATE TABLE SharedTransactionPayments (signature Signature, recipient QoraPublicKey NOT NULL, "
+ "amount QoraAmount NOT NULL, asset_id AssetID NOT NULL, "
+ "PRIMARY KEY (signature, recipient, asset_id), FOREIGN KEY (signature) REFERENCES Transactions (signature) ON DELETE CASCADE)");
break;
case 13:
// Arbitrary Transactions
stmt.execute("CREATE TABLE ArbitraryTransactions (signature Signature, creator QoraPublicKey NOT NULL, service TINYINT NOT NULL, "
+ "data_hash DataHash NOT NULL, "
+ "PRIMARY KEY (signature), FOREIGN KEY (signature) REFERENCES Transactions (signature) ON DELETE CASCADE)");
// NB: Actual data payload stored elsewhere
// For the future: data payload should be encrypted, at the very least with transaction's reference as the seed for the encryption key
break;
case 13:
// Arbitrary Transactions
stmt.execute("CREATE TABLE ArbitraryTransactions (signature Signature, creator QoraPublicKey NOT NULL, service TINYINT NOT NULL, "
+ "data_hash DataHash NOT NULL, "
+ "PRIMARY KEY (signature), FOREIGN KEY (signature) REFERENCES Transactions (signature) ON DELETE CASCADE)");
// NB: Actual data payload stored elsewhere
// For the future: data payload should be encrypted, at the very least with transaction's reference as the seed for the encryption key
break;
case 14:
// Issue Asset Transactions
stmt.execute(
"CREATE TABLE IssueAssetTransactions (signature Signature, issuer QoraPublicKey NOT NULL, owner QoraAddress NOT NULL, asset_name AssetName NOT NULL, "
+ "description VARCHAR(4000) NOT NULL, quantity BIGINT NOT NULL, is_divisible BOOLEAN NOT NULL, asset_id AssetID, "
+ "PRIMARY KEY (signature), FOREIGN KEY (signature) REFERENCES Transactions (signature) ON DELETE CASCADE)");
// For the future: maybe convert quantity from BIGINT to QoraAmount, regardless of divisibility
break;
case 14:
// Issue Asset Transactions
stmt.execute(
"CREATE TABLE IssueAssetTransactions (signature Signature, issuer QoraPublicKey NOT NULL, owner QoraAddress NOT NULL, asset_name AssetName NOT NULL, "
+ "description VARCHAR(4000) NOT NULL, quantity BIGINT NOT NULL, is_divisible BOOLEAN NOT NULL, asset_id AssetID, "
+ "PRIMARY KEY (signature), FOREIGN KEY (signature) REFERENCES Transactions (signature) ON DELETE CASCADE)");
// For the future: maybe convert quantity from BIGINT to QoraAmount, regardless of divisibility
break;
case 15:
// Transfer Asset Transactions
stmt.execute("CREATE TABLE TransferAssetTransactions (signature Signature, sender QoraPublicKey NOT NULL, recipient QoraAddress NOT NULL, "
+ "asset_id AssetID NOT NULL, amount QoraAmount NOT NULL, "
+ "PRIMARY KEY (signature), FOREIGN KEY (signature) REFERENCES Transactions (signature) ON DELETE CASCADE)");
break;
case 15:
// Transfer Asset Transactions
stmt.execute("CREATE TABLE TransferAssetTransactions (signature Signature, sender QoraPublicKey NOT NULL, recipient QoraAddress NOT NULL, "
+ "asset_id AssetID NOT NULL, amount QoraAmount NOT NULL, "
+ "PRIMARY KEY (signature), FOREIGN KEY (signature) REFERENCES Transactions (signature) ON DELETE CASCADE)");
break;
case 16:
// Create Asset Order Transactions
stmt.execute("CREATE TABLE CreateAssetOrderTransactions (signature Signature, creator QoraPublicKey NOT NULL, "
+ "have_asset_id AssetID NOT NULL, amount QoraAmount NOT NULL, want_asset_id AssetID NOT NULL, price QoraAmount NOT NULL, "
+ "PRIMARY KEY (signature), FOREIGN KEY (signature) REFERENCES Transactions (signature) ON DELETE CASCADE)");
break;
case 16:
// Create Asset Order Transactions
stmt.execute("CREATE TABLE CreateAssetOrderTransactions (signature Signature, creator QoraPublicKey NOT NULL, "
+ "have_asset_id AssetID NOT NULL, amount QoraAmount NOT NULL, want_asset_id AssetID NOT NULL, price QoraAmount NOT NULL, "
+ "PRIMARY KEY (signature), FOREIGN KEY (signature) REFERENCES Transactions (signature) ON DELETE CASCADE)");
break;
case 17:
// Cancel Asset Order Transactions
stmt.execute("CREATE TABLE CancelAssetOrderTransactions (signature Signature, creator QoraPublicKey NOT NULL, "
+ "asset_order AssetOrderID NOT NULL, "
+ "PRIMARY KEY (signature), FOREIGN KEY (signature) REFERENCES Transactions (signature) ON DELETE CASCADE)");
break;
case 17:
// Cancel Asset Order Transactions
stmt.execute("CREATE TABLE CancelAssetOrderTransactions (signature Signature, creator QoraPublicKey NOT NULL, "
+ "asset_order AssetOrderID NOT NULL, "
+ "PRIMARY KEY (signature), FOREIGN KEY (signature) REFERENCES Transactions (signature) ON DELETE CASCADE)");
break;
case 18:
// Multi-payment Transactions
stmt.execute("CREATE TABLE MultiPaymentTransactions (signature Signature, sender QoraPublicKey NOT NULL, "
+ "PRIMARY KEY (signature), FOREIGN KEY (signature) REFERENCES Transactions (signature) ON DELETE CASCADE)");
break;
case 18:
// Multi-payment Transactions
stmt.execute("CREATE TABLE MultiPaymentTransactions (signature Signature, sender QoraPublicKey NOT NULL, "
+ "PRIMARY KEY (signature), FOREIGN KEY (signature) REFERENCES Transactions (signature) ON DELETE CASCADE)");
break;
case 19:
// Deploy CIYAM AT Transactions
stmt.execute("CREATE TABLE DeployATTransactions (signature Signature, creator QoraPublicKey NOT NULL, AT_name ATName NOT NULL, "
+ "description VARCHAR(2000) NOT NULL, AT_type ATType NOT NULL, AT_tags VARCHAR(200) NOT NULL, "
+ "creation_bytes VARBINARY(100000) NOT NULL, amount QoraAmount NOT NULL, "
+ "PRIMARY KEY (signature), FOREIGN KEY (signature) REFERENCES Transactions (signature) ON DELETE CASCADE)");
break;
case 19:
// Deploy CIYAM AT Transactions
stmt.execute("CREATE TABLE DeployATTransactions (signature Signature, creator QoraPublicKey NOT NULL, AT_name ATName NOT NULL, "
+ "description VARCHAR(2000) NOT NULL, AT_type ATType NOT NULL, AT_tags VARCHAR(200) NOT NULL, "
+ "creation_bytes VARBINARY(100000) NOT NULL, amount QoraAmount NOT NULL, "
+ "PRIMARY KEY (signature), FOREIGN KEY (signature) REFERENCES Transactions (signature) ON DELETE CASCADE)");
break;
case 20:
// Message Transactions
stmt.execute(
"CREATE TABLE MessageTransactions (signature Signature, version TINYINT NOT NULL, sender QoraPublicKey NOT NULL, recipient QoraAddress NOT NULL, "
+ "is_text BOOLEAN NOT NULL, is_encrypted BOOLEAN NOT NULL, amount QoraAmount NOT NULL, asset_id AssetID NOT NULL, data VARBINARY(4000) NOT NULL, "
+ "PRIMARY KEY (signature), FOREIGN KEY (signature) REFERENCES Transactions (signature) ON DELETE CASCADE)");
break;
case 20:
// Message Transactions
stmt.execute(
"CREATE TABLE MessageTransactions (signature Signature, version TINYINT NOT NULL, sender QoraPublicKey NOT NULL, recipient QoraAddress NOT NULL, "
+ "is_text BOOLEAN NOT NULL, is_encrypted BOOLEAN NOT NULL, amount QoraAmount NOT NULL, asset_id AssetID NOT NULL, data VARBINARY(4000) NOT NULL, "
+ "PRIMARY KEY (signature), FOREIGN KEY (signature) REFERENCES Transactions (signature) ON DELETE CASCADE)");
break;
case 21:
// Assets (including QORA coin itself)
stmt.execute(
"CREATE TABLE Assets (asset_id AssetID IDENTITY, owner QoraPublicKey NOT NULL, asset_name AssetName NOT NULL, description VARCHAR(4000) NOT NULL, "
+ "quantity BIGINT NOT NULL, is_divisible BOOLEAN NOT NULL, reference Signature NOT NULL)");
stmt.execute("CREATE INDEX AssetNameIndex on Assets (asset_name)");
break;
case 21:
// Assets (including QORA coin itself)
stmt.execute(
"CREATE TABLE Assets (asset_id AssetID IDENTITY, owner QoraPublicKey NOT NULL, asset_name AssetName NOT NULL, description VARCHAR(4000) NOT NULL, "
+ "quantity BIGINT NOT NULL, is_divisible BOOLEAN NOT NULL, reference Signature NOT NULL)");
stmt.execute("CREATE INDEX AssetNameIndex on Assets (asset_name)");
break;
case 22:
// Accounts
stmt.execute("CREATE TABLE Accounts (account QoraAddress, reference Signature, PRIMARY KEY (account))");
stmt.execute(
"CREATE TABLE AccountBalances (account QoraAddress, asset_id AssetID, balance QoraAmount NOT NULL, PRIMARY KEY (account, asset_id))");
break;
case 22:
// Accounts
stmt.execute("CREATE TABLE Accounts (account QoraAddress, reference Signature, PRIMARY KEY (account))");
stmt.execute(
"CREATE TABLE AccountBalances (account QoraAddress, asset_id AssetID, balance QoraAmount NOT NULL, PRIMARY KEY (account, asset_id))");
break;
default:
// nothing to do
return false;
}
default:
// nothing to do
return false;
}
// database was updated

View File

@ -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;
}
return preparedStatement.execute();
}
/**

View File

@ -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();
}

View File

@ -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);
}
}

View File

@ -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,20 +396,18 @@ 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.setInt(1, height);
PreparedStatement preparedStatement = DB.getConnection().prepareStatement("SELECT " + DB_COLUMNS + " FROM Blocks WHERE height = ?");
preparedStatement.setInt(1, height);
try {
return new Block(DB.checkedExecute(preparedStatement));
} catch (NoDataFoundException e) {
return null;
}
try {
return new Block(DB.checkedExecute(preparedStatement));
} catch (NoDataFoundException e) {
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();
}
}

View File

@ -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);
// Add Genesis Block
GenesisBlock genesisBlock = GenesisBlock.getInstance();
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);
}
// 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();
}
/**
@ -93,13 +90,11 @@ 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"));
if (rs == null)
return 0;
ResultSet rs = DB.checkedExecute("SELECT MAX(height) FROM Blocks");
if (rs == null)
return 0;
return rs.getInt(1);
}
return rs.getInt(1);
}
/**

View File

@ -1,6 +1,5 @@
package qora.block;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.SQLException;
@ -40,15 +39,13 @@ 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.setInt(1, height);
PreparedStatement preparedStatement = DB.getConnection().prepareStatement("SELECT signature FROM Blocks WHERE height = ?");
preparedStatement.setInt(1, height);
try {
return new Block(DB.checkedExecute(preparedStatement));
} catch (NoDataFoundException e) {
return null;
}
try {
return new Block(DB.checkedExecute(preparedStatement));
} catch (NoDataFoundException e) {
return null;
}
}

View File

@ -1,6 +1,5 @@
package qora.block;
import java.sql.Connection;
import java.sql.ResultSet;
import java.sql.SQLException;
@ -44,16 +43,14 @@ 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)
throw new NoDataFoundException();
ResultSet rs = DB.checkedExecute("SELECT transaction_signature FROM BlockTransactions WHERE block_signature = ? and sequence = ?", blockSignature,
sequence);
if (rs == null)
throw new NoDataFoundException();
this.blockSignature = blockSignature;
this.sequence = sequence;
this.transactionSignature = DB.getResultSetBytes(rs.getBinaryStream(1), Transaction.SIGNATURE_LENGTH);
}
this.blockSignature = blockSignature;
this.sequence = sequence;
this.transactionSignature = DB.getResultSetBytes(rs.getBinaryStream(1), Transaction.SIGNATURE_LENGTH);
}
protected BlockTransaction(byte[] transactionSignature) throws SQLException {
@ -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();
}

View File

@ -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;

View File

@ -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
}

View File

@ -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);
}
}

View File

@ -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);
}
}

View File

@ -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);
}
}

View File

@ -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);
}
}

View File

@ -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;
}

View File

@ -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,79 +17,73 @@ 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());
// only true if blockchain is empty
// assertTrue(block.isValid(connection));
GenesisBlock block = GenesisBlock.getInstance();
assertNotNull(block);
assertTrue(block.isSignatureValid());
// only true if blockchain is empty
// assertTrue(block.isValid(connection));
List<Transaction> transactions = block.getTransactions();
assertNotNull(transactions);
List<Transaction> transactions = block.getTransactions();
assertNotNull(transactions);
for (Transaction transaction : transactions) {
assertNotNull(transaction);
assertEquals(Transaction.TransactionType.GENESIS, transaction.getType());
assertTrue(transaction.getFee().compareTo(BigDecimal.ZERO) == 0);
assertNull(transaction.getReference());
assertTrue(transaction.isSignatureValid());
assertEquals(Transaction.ValidationResult.OK, transaction.isValid(connection));
}
// Attempt to load first transaction directly from database
Transaction transaction = TransactionFactory.fromSignature(transactions.get(0).getSignature());
for (Transaction transaction : transactions) {
assertNotNull(transaction);
assertEquals(Transaction.TransactionType.GENESIS, transaction.getType());
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
Transaction transaction = TransactionFactory.fromSignature(transactions.get(0).getSignature());
assertNotNull(transaction);
assertEquals(Transaction.TransactionType.GENESIS, transaction.getType());
assertTrue(transaction.getFee().compareTo(BigDecimal.ZERO) == 0);
assertNull(transaction.getReference());
assertTrue(transaction.isSignatureValid());
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);
assertNotNull("Block 754 is required for this test", block);
assertTrue(block.isSignatureValid());
// Block 949 has lots of varied transactions
// Blocks 390 & 754 have only payment transactions
Block block = Block.fromHeight(754);
assertNotNull("Block 754 is required for this test", block);
assertTrue(block.isSignatureValid());
List<Transaction> transactions = block.getTransactions();
assertNotNull(transactions);
List<Transaction> transactions = block.getTransactions();
assertNotNull(transactions);
for (Transaction transaction : transactions) {
assertNotNull(transaction);
assertEquals(Transaction.TransactionType.PAYMENT, transaction.getType());
assertFalse(transaction.getFee().compareTo(BigDecimal.ZERO) == 0);
assertNotNull(transaction.getReference());
assertTrue(transaction.isSignatureValid());
}
// Attempt to load first transaction directly from database
Transaction transaction = TransactionFactory.fromSignature(transactions.get(0).getSignature());
for (Transaction transaction : transactions) {
assertNotNull(transaction);
assertEquals(Transaction.TransactionType.PAYMENT, transaction.getType());
assertFalse(transaction.getFee().compareTo(BigDecimal.ZERO) == 0);
assertNotNull(transaction.getReference());
assertTrue(transaction.isSignatureValid());
}
// Attempt to load first transaction directly from database
Transaction transaction = TransactionFactory.fromSignature(transactions.get(0).getSignature());
assertNotNull(transaction);
assertEquals(Transaction.TransactionType.PAYMENT, transaction.getType());
assertFalse(transaction.getFee().compareTo(BigDecimal.ZERO) == 0);
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);
assertNotNull("Block 754 is required for this test", block);
assertTrue(block.isSignatureValid());
// Block 949 has lots of varied transactions
// Blocks 390 & 754 have only payment transactions
Block block = Block.fromHeight(754);
assertNotNull("Block 754 is required for this test", block);
assertTrue(block.isSignatureValid());
byte[] bytes = block.toBytes();
byte[] bytes = block.toBytes();
assertEquals(block.getDataLength(), bytes.length);
}
assertEquals(block.getDataLength(), bytes.length);
}
}

View File

@ -21,7 +21,7 @@ public class common {
@AfterClass
public static void closeDatabase() throws SQLException {
DB.close();
DB.shutdown();
}
}

View File

@ -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)
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();
for (int i = 0; i < n_connections; ++i) {
connections[i] = DB.getConnection();
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();

View File

@ -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());
}

View File

@ -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();
}
}

View File

@ -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,19 +48,17 @@ 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);
assertNotNull("Block 754 is required for this test", block);
assertTrue(block.isSignatureValid());
// Block 949 has lots of varied transactions
// Blocks 390 & 754 have only payment transactions
Block block = Block.fromHeight(754);
assertNotNull("Block 754 is required for this test", block);
assertTrue(block.isSignatureValid());
List<Transaction> transactions = block.getTransactions();
assertNotNull(transactions);
List<Transaction> transactions = block.getTransactions();
assertNotNull(transactions);
for (Transaction transaction : transactions)
testGenericSerialization(transaction);
}
for (Transaction transaction : transactions)
testGenericSerialization(transaction);
}
@Test