diff --git a/src/database/DB.java b/src/database/DB.java index 5e388284..e5508305 100644 --- a/src/database/DB.java +++ b/src/database/DB.java @@ -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 local = new ThreadLocal() { + @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. - *

- * 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. *

* 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. *

- * See {@link DB#close()} for warnings about connections. + * See {@link DB#shutdown()} for warnings about connections. *

* 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. - *

- * Typically for use within an ongoing SQL Transaction. - *

- * Note: calls ResultSet.next() 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 { *

* 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. - *

- * Typically for use within an ongoing SQL Transaction. - *

- * {@code whereClause} is SQL "WHERE" clause containing "?" placeholders suitable for use with PreparedStatements. - *

- * Example call: - *

- * {@code Connection connection = DB.getConnection();}
- * {@code String manufacturer = "Lamborghini";}
- * {@code int maxMileage = 100_000;}
- * {@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) diff --git a/src/database/DatabaseUpdates.java b/src/database/DatabaseUpdates.java index 202035d3..d14ff6f3 100644 --- a/src/database/DatabaseUpdates.java +++ b/src/database/DatabaseUpdates.java @@ -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 diff --git a/src/database/SaveHelper.java b/src/database/SaveHelper.java index bb159a71..5aae6390 100644 --- a/src/database/SaveHelper.java +++ b/src/database/SaveHelper.java @@ -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; *

* Columns, and corresponding values, are bound via close-coupled pairs in a chain thus: *

- * {@code SaveHelper helper = new SaveHelper(connection, "TableName"); }
+ * {@code SaveHelper helper = new SaveHelper("TableName"); }
* {@code helper.bind("column_name", someColumnValue).bind("column2", columnValue2); }
* {@code helper.execute(); }
* */ public class SaveHelper { - private Connection connection; private String table; private List columns = new ArrayList(); @@ -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. - *

- * 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(); } /** diff --git a/src/qora/account/Account.java b/src/qora/account/Account.java index 7a37afc7..b65ff629 100644 --- a/src/qora/account/Account.java +++ b/src/qora/account/Account.java @@ -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. - *

- * 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(); } diff --git a/src/qora/assets/Asset.java b/src/qora/assets/Asset.java index 808455cc..30341807 100644 --- a/src/qora/assets/Asset.java +++ b/src/qora/assets/Asset.java @@ -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); - } - } diff --git a/src/qora/block/Block.java b/src/qora/block/Block.java index 3d0f3aa8..508b7180 100644 --- a/src/qora/block/Block.java +++ b/src/qora/block/Block.java @@ -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. *

* Performs various tests like checking for parent block, correct block timestamp, version, generating balance, etc. *

* 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 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(); } } diff --git a/src/qora/block/BlockChain.java b/src/qora/block/BlockChain.java index eb1eb365..7ef9e233 100644 --- a/src/qora/block/BlockChain.java +++ b/src/qora/block/BlockChain.java @@ -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); } /** diff --git a/src/qora/block/BlockFactory.java b/src/qora/block/BlockFactory.java index 69716a5a..96e5d172 100644 --- a/src/qora/block/BlockFactory.java +++ b/src/qora/block/BlockFactory.java @@ -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; } } diff --git a/src/qora/block/BlockTransaction.java b/src/qora/block/BlockTransaction.java index b9f737ce..38d7d28d 100644 --- a/src/qora/block/BlockTransaction.java +++ b/src/qora/block/BlockTransaction.java @@ -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(); } diff --git a/src/qora/block/GenesisBlock.java b/src/qora/block/GenesisBlock.java index a5e5769d..9d512093 100644 --- a/src/qora/block/GenesisBlock.java +++ b/src/qora/block/GenesisBlock.java @@ -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; diff --git a/src/qora/transaction/CreateOrderTransaction.java b/src/qora/transaction/CreateOrderTransaction.java index d4bc6ef4..82e3c2a7 100644 --- a/src/qora/transaction/CreateOrderTransaction.java +++ b/src/qora/transaction/CreateOrderTransaction.java @@ -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 } diff --git a/src/qora/transaction/GenesisTransaction.java b/src/qora/transaction/GenesisTransaction.java index 2f8fed3b..53b25acc 100644 --- a/src/qora/transaction/GenesisTransaction.java +++ b/src/qora/transaction/GenesisTransaction.java @@ -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); } } diff --git a/src/qora/transaction/IssueAssetTransaction.java b/src/qora/transaction/IssueAssetTransaction.java index f15baa34..58aa2956 100644 --- a/src/qora/transaction/IssueAssetTransaction.java +++ b/src/qora/transaction/IssueAssetTransaction.java @@ -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); } } diff --git a/src/qora/transaction/MessageTransaction.java b/src/qora/transaction/MessageTransaction.java index 61c0d2a6..52cc78d7 100644 --- a/src/qora/transaction/MessageTransaction.java +++ b/src/qora/transaction/MessageTransaction.java @@ -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); } } diff --git a/src/qora/transaction/PaymentTransaction.java b/src/qora/transaction/PaymentTransaction.java index a16ec76d..be7f06d7 100644 --- a/src/qora/transaction/PaymentTransaction.java +++ b/src/qora/transaction/PaymentTransaction.java @@ -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); } } diff --git a/src/qora/transaction/Transaction.java b/src/qora/transaction/Transaction.java index c90fd6fd..9e2c5d26 100644 --- a/src/qora/transaction/Transaction.java +++ b/src/qora/transaction/Transaction.java @@ -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. *

- * Checks if transaction can have {@link Transaction#process(Connection)} called. + * Checks if transaction can have {@link Transaction#process()} called. *

- * 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()}. *

* 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. *

* Processes transaction, updating balances, references, assets, etc. as appropriate. *

- * 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. *

* Undoes transaction, updating balances, references, assets, etc. as appropriate. *

- * 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; } diff --git a/src/test/blocks.java b/src/test/blocks.java index 5b658143..2c9d6b9d 100644 --- a/src/test/blocks.java +++ b/src/test/blocks.java @@ -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 transactions = block.getTransactions(); - assertNotNull(transactions); + List 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 transactions = block.getTransactions(); - assertNotNull(transactions); + List 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); } } diff --git a/src/test/common.java b/src/test/common.java index 6f316e46..282a669f 100644 --- a/src/test/common.java +++ b/src/test/common.java @@ -21,7 +21,7 @@ public class common { @AfterClass public static void closeDatabase() throws SQLException { - DB.close(); + DB.shutdown(); } } diff --git a/src/test/connections.java b/src/test/connections.java index 7700f64c..e5d067a1 100644 --- a/src/test/connections.java +++ b/src/test/connections.java @@ -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(); diff --git a/src/test/migrate.java b/src/test/migrate.java index 76bfdcb0..3a8062bc 100644 --- a/src/test/migrate.java +++ b/src/test/migrate.java @@ -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()); } diff --git a/src/test/save.java b/src/test/save.java index b24b1f7b..f488bb3e 100644 --- a/src/test/save.java +++ b/src/test/save.java @@ -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(); } } diff --git a/src/test/transactions.java b/src/test/transactions.java index ec08209a..9b4fba7a 100644 --- a/src/test/transactions.java +++ b/src/test/transactions.java @@ -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 transactions = block.getTransactions(); - assertNotNull(transactions); + List transactions = block.getTransactions(); + assertNotNull(transactions); - for (Transaction transaction : transactions) - testGenericSerialization(transaction); - } + for (Transaction transaction : transactions) + testGenericSerialization(transaction); } @Test