diff --git a/.classpath b/.classpath index 77e8068a..dc543d95 100644 --- a/.classpath +++ b/.classpath @@ -17,5 +17,6 @@ + diff --git a/lib/hsqldb-r5836.jar b/lib/hsqldb-r5836.jar new file mode 100644 index 00000000..72721edf Binary files /dev/null and b/lib/hsqldb-r5836.jar differ diff --git a/pom.xml b/pom.xml index 078e0af6..bdf7f1b1 100644 --- a/pom.xml +++ b/pom.xml @@ -17,11 +17,6 @@ - - org.hsqldb - hsqldb - 2.4.0 - com.googlecode.json-simple json-simple diff --git a/src/data/transaction/CancelOrderTransactionData.java b/src/data/transaction/CancelOrderTransactionData.java index b4dff9bc..126116e7 100644 --- a/src/data/transaction/CancelOrderTransactionData.java +++ b/src/data/transaction/CancelOrderTransactionData.java @@ -7,7 +7,6 @@ import qora.transaction.Transaction; public class CancelOrderTransactionData extends TransactionData { // Properties - private byte[] creatorPublicKey; private byte[] orderId; // Constructors @@ -15,7 +14,6 @@ public class CancelOrderTransactionData extends TransactionData { public CancelOrderTransactionData(byte[] creatorPublicKey, byte[] orderId, BigDecimal fee, long timestamp, byte[] reference, byte[] signature) { super(Transaction.TransactionType.CANCEL_ASSET_ORDER, fee, creatorPublicKey, timestamp, reference, signature); - this.creatorPublicKey = creatorPublicKey; this.orderId = orderId; } @@ -25,10 +23,6 @@ public class CancelOrderTransactionData extends TransactionData { // Getters/Setters - public byte[] getCreatorPublicKey() { - return this.creatorPublicKey; - } - public byte[] getOrderId() { return this.orderId; } diff --git a/src/data/transaction/CreateOrderTransactionData.java b/src/data/transaction/CreateOrderTransactionData.java index c157c8ba..95bd2cfe 100644 --- a/src/data/transaction/CreateOrderTransactionData.java +++ b/src/data/transaction/CreateOrderTransactionData.java @@ -7,7 +7,6 @@ import qora.transaction.Transaction.TransactionType; public class CreateOrderTransactionData extends TransactionData { // Properties - private byte[] creatorPublicKey; private long haveAssetId; private long wantAssetId; private BigDecimal amount; @@ -19,7 +18,6 @@ public class CreateOrderTransactionData extends TransactionData { long timestamp, byte[] reference, byte[] signature) { super(TransactionType.CREATE_ASSET_ORDER, fee, creatorPublicKey, timestamp, reference, signature); - this.creatorPublicKey = creatorPublicKey; this.haveAssetId = haveAssetId; this.wantAssetId = wantAssetId; this.amount = amount; @@ -33,10 +31,6 @@ public class CreateOrderTransactionData extends TransactionData { // Getters/Setters - public byte[] getCreatorPublicKey() { - return this.creatorPublicKey; - } - public long getHaveAssetId() { return this.haveAssetId; } diff --git a/src/data/transaction/CreatePollTransactionData.java b/src/data/transaction/CreatePollTransactionData.java index 4d6cd111..78fd0862 100644 --- a/src/data/transaction/CreatePollTransactionData.java +++ b/src/data/transaction/CreatePollTransactionData.java @@ -9,7 +9,6 @@ import qora.transaction.Transaction; public class CreatePollTransactionData extends TransactionData { // Properties - private byte[] creatorPublicKey; private String owner; private String pollName; private String description; @@ -21,7 +20,6 @@ public class CreatePollTransactionData extends TransactionData { BigDecimal fee, long timestamp, byte[] reference, byte[] signature) { super(Transaction.TransactionType.CREATE_POLL, fee, creatorPublicKey, timestamp, reference, signature); - this.creatorPublicKey = creatorPublicKey; this.owner = owner; this.pollName = pollName; this.description = description; @@ -35,10 +33,6 @@ public class CreatePollTransactionData extends TransactionData { // Getters/setters - public byte[] getCreatorPublicKey() { - return this.creatorPublicKey; - } - public String getOwner() { return this.owner; } diff --git a/src/migrate.java b/src/migrate.java index 2583153e..76aa541f 100644 --- a/src/migrate.java +++ b/src/migrate.java @@ -44,21 +44,14 @@ public class migrate { private static Map publicKeyByAddress = new HashMap(); public static Object fetchBlockJSON(int height) throws IOException { - InputStream is; - - try { - is = new URL("http://localhost:9085/blocks/byheight/" + height).openStream(); + try (InputStream is = new URL("http://localhost:9085/blocks/byheight/" + height).openStream(); + InputStreamReader isr = new InputStreamReader(is, Charset.forName("UTF-8")); + BufferedReader reader = new BufferedReader(isr)) { + return JSONValue.parseWithException(reader); } catch (IOException e) { return null; - } - - try { - BufferedReader reader = new BufferedReader(new InputStreamReader(is, Charset.forName("UTF-8"))); - return JSONValue.parseWithException(reader); } catch (ParseException e) { return null; - } finally { - is.close(); } } @@ -69,8 +62,8 @@ public class migrate { InputStream is = new URL("http://localhost:9085/addresses/publickey/" + address).openStream(); - try { - String publicKey58 = CharStreams.toString(new InputStreamReader(is, Charset.forName("UTF-8"))); + try (InputStreamReader isr = new InputStreamReader(is, Charset.forName("UTF-8"))) { + String publicKey58 = CharStreams.toString(isr); byte[] publicKey = Base58.decode(publicKey58); publicKeyByAddress.put(address, publicKey); @@ -81,12 +74,12 @@ public class migrate { } public static void savePublicKeys(Connection connection) throws SQLException { - PreparedStatement pStmt = connection.prepareStatement("INSERT IGNORE INTO Test_public_keys VALUES (?, ?)"); - - for (Entry entry : publicKeyByAddress.entrySet()) { - pStmt.setString(1, entry.getKey()); - pStmt.setBytes(2, entry.getValue()); - pStmt.execute(); + try (PreparedStatement pStmt = connection.prepareStatement("INSERT IGNORE INTO Test_public_keys VALUES (?, ?)")) { + for (Entry entry : publicKeyByAddress.entrySet()) { + pStmt.setString(1, entry.getKey()); + pStmt.setBytes(2, entry.getValue()); + pStmt.execute(); + } } } @@ -103,6 +96,7 @@ public class migrate { return output.toString(); } + @SuppressWarnings("resource") public static void main(String args[]) throws SQLException, DataException, IOException { // Genesis public key publicKeyByAddress.put(GENESIS_ADDRESS, GENESIS_PUBLICKEY); @@ -132,14 +126,14 @@ public class migrate { .prepareStatement("INSERT INTO PaymentTransactions " + formatWithPlaceholders("signature", "sender", "recipient", "amount")); PreparedStatement registerNamePStmt = c .prepareStatement("INSERT INTO RegisterNameTransactions " + formatWithPlaceholders("signature", "registrant", "name", "owner", "data")); - PreparedStatement updateNamePStmt = c - .prepareStatement("INSERT INTO UpdateNameTransactions " + formatWithPlaceholders("signature", "owner", "name", "new_owner", "new_data", "name_reference")); + PreparedStatement updateNamePStmt = c.prepareStatement( + "INSERT INTO UpdateNameTransactions " + formatWithPlaceholders("signature", "owner", "name", "new_owner", "new_data", "name_reference")); PreparedStatement sellNamePStmt = c .prepareStatement("INSERT INTO SellNameTransactions " + formatWithPlaceholders("signature", "owner", "name", "amount")); PreparedStatement cancelSellNamePStmt = c .prepareStatement("INSERT INTO CancelSellNameTransactions " + formatWithPlaceholders("signature", "owner", "name")); - PreparedStatement buyNamePStmt = c - .prepareStatement("INSERT INTO BuyNameTransactions " + formatWithPlaceholders("signature", "buyer", "name", "seller", "amount", "name_reference")); + PreparedStatement buyNamePStmt = c.prepareStatement( + "INSERT INTO BuyNameTransactions " + formatWithPlaceholders("signature", "buyer", "name", "seller", "amount", "name_reference")); PreparedStatement createPollPStmt = c .prepareStatement("INSERT INTO CreatePollTransactions " + formatWithPlaceholders("signature", "creator", "owner", "poll_name", "description")); PreparedStatement createPollOptionPStmt = c diff --git a/src/qora/assets/Order.java b/src/qora/assets/Order.java index 23c98004..aa173628 100644 --- a/src/qora/assets/Order.java +++ b/src/qora/assets/Order.java @@ -163,7 +163,7 @@ public class Order { // Trade can go ahead! - // Calculate the total cost to us based on their price + // Calculate the total cost to us, in have-asset, based on their price BigDecimal tradePrice = matchedAmount.multiply(theirOrderData.getPrice()).setScale(8); // Construct trade @@ -174,7 +174,7 @@ public class Order { trade.process(); // Update our order in terms of fulfilment, etc. but do not save into repository as that's handled by Trade above - this.orderData.setFulfilled(this.orderData.getFulfilled().add(matchedAmount)); + this.orderData.setFulfilled(this.orderData.getFulfilled().add(tradePrice)); // Continue on to process other open orders in case we still have amount left to match } diff --git a/src/qora/block/BlockChain.java b/src/qora/block/BlockChain.java index f94d8043..3a2b4074 100644 --- a/src/qora/block/BlockChain.java +++ b/src/qora/block/BlockChain.java @@ -38,9 +38,11 @@ public class BlockChain { private static final long ASSETS_RELEASE_TIMESTAMP = 0L; // From Qora epoch private static final long VOTING_RELEASE_TIMESTAMP = 1403715600000L; // 2014-06-25T17:00:00+00:00 private static final long ARBITRARY_RELEASE_TIMESTAMP = 1405702800000L; // 2014-07-18T17:00:00+00:00 + private static final long CREATE_POLL_V2_TIMESTAMP = 1552500000000L; // 2019-03-13T18:00:00+00:00 // Future Qora v2 CREATE POLL transactions private static final long ISSUE_ASSET_V2_TIMESTAMP = 1552500000000L; // 2019-03-13T18:00:00+00:00 // Future Qora v2 ISSUE ASSET transactions private static final long CREATE_ORDER_V2_TIMESTAMP = 1552500000000L; // 2019-03-13T18:00:00+00:00 // Future Qora v2 CREATE ORDER transactions + private static final long ARBITRARY_TRANSACTION_V2_TIMESTAMP = 1552500000000L; // 2019-03-13T18:00:00+00:00 // Future Qora v2 ARBITRARY transactions /** * Some sort start-up/initialization/checking method. @@ -164,4 +166,11 @@ public class BlockChain { return CREATE_ORDER_V2_TIMESTAMP; } + public static long getArbitraryTransactionV2Timestamp() { + if (Settings.getInstance().isTestNet()) + return 0; + + return ARBITRARY_TRANSACTION_V2_TIMESTAMP; + } + } diff --git a/src/qora/transaction/ArbitraryTransaction.java b/src/qora/transaction/ArbitraryTransaction.java index 6ca023f3..8fa87ebd 100644 --- a/src/qora/transaction/ArbitraryTransaction.java +++ b/src/qora/transaction/ArbitraryTransaction.java @@ -44,6 +44,7 @@ public class ArbitraryTransaction extends Transaction { // More information + @Override public List getRecipientAccounts() throws DataException { List recipients = new ArrayList(); @@ -54,6 +55,7 @@ public class ArbitraryTransaction extends Transaction { return recipients; } + @Override public boolean isInvolved(Account account) throws DataException { String address = account.getAddress(); @@ -68,6 +70,7 @@ public class ArbitraryTransaction extends Transaction { return false; } + @Override public BigDecimal getAmount(Account account) throws DataException { String address = account.getAddress(); BigDecimal amount = BigDecimal.ZERO.setScale(8); diff --git a/src/qora/transaction/CancelOrderTransaction.java b/src/qora/transaction/CancelOrderTransaction.java index 12012756..3b7708a5 100644 --- a/src/qora/transaction/CancelOrderTransaction.java +++ b/src/qora/transaction/CancelOrderTransaction.java @@ -32,14 +32,17 @@ public class CancelOrderTransaction extends Transaction { // More information + @Override public List getRecipientAccounts() { return new ArrayList(); } + @Override public boolean isInvolved(Account account) throws DataException { return account.getAddress().equals(this.getCreator().getAddress()); } + @Override public BigDecimal getAmount(Account account) throws DataException { BigDecimal amount = BigDecimal.ZERO.setScale(8); @@ -51,6 +54,7 @@ public class CancelOrderTransaction extends Transaction { // Navigation + @Override public Account getCreator() throws DataException { return new PublicKeyAccount(this.repository, cancelOrderTransactionData.getCreatorPublicKey()); } diff --git a/src/qora/transaction/CreateOrderTransaction.java b/src/qora/transaction/CreateOrderTransaction.java index 38a1a63c..4a02d9d8 100644 --- a/src/qora/transaction/CreateOrderTransaction.java +++ b/src/qora/transaction/CreateOrderTransaction.java @@ -33,14 +33,17 @@ public class CreateOrderTransaction extends Transaction { // More information + @Override public List getRecipientAccounts() { return new ArrayList(); } + @Override public boolean isInvolved(Account account) throws DataException { return account.getAddress().equals(this.getCreator().getAddress()); } + @Override public BigDecimal getAmount(Account account) throws DataException { BigDecimal amount = BigDecimal.ZERO.setScale(8); @@ -52,6 +55,7 @@ public class CreateOrderTransaction extends Transaction { // Navigation + @Override public Account getCreator() throws DataException { return new PublicKeyAccount(this.repository, createOrderTransactionData.getCreatorPublicKey()); } diff --git a/src/qora/transaction/CreatePollTransaction.java b/src/qora/transaction/CreatePollTransaction.java index 0d009fd1..6658c3ba 100644 --- a/src/qora/transaction/CreatePollTransaction.java +++ b/src/qora/transaction/CreatePollTransaction.java @@ -35,10 +35,12 @@ public class CreatePollTransaction extends Transaction { // More information + @Override public List getRecipientAccounts() throws DataException { return Collections.singletonList(getOwner()); } + @Override public boolean isInvolved(Account account) throws DataException { String address = account.getAddress(); @@ -51,6 +53,7 @@ public class CreatePollTransaction extends Transaction { return false; } + @Override public BigDecimal getAmount(Account account) throws DataException { String address = account.getAddress(); BigDecimal amount = BigDecimal.ZERO.setScale(8); @@ -63,6 +66,7 @@ public class CreatePollTransaction extends Transaction { // Navigation + @Override public Account getCreator() throws DataException { return new PublicKeyAccount(this.repository, this.createPollTransactionData.getCreatorPublicKey()); } diff --git a/src/qora/transaction/GenesisTransaction.java b/src/qora/transaction/GenesisTransaction.java index 1e1f55fa..ed2330b8 100644 --- a/src/qora/transaction/GenesisTransaction.java +++ b/src/qora/transaction/GenesisTransaction.java @@ -36,10 +36,12 @@ public class GenesisTransaction extends Transaction { // More information + @Override public List getRecipientAccounts() throws DataException { return Collections.singletonList(new Account(this.repository, genesisTransactionData.getRecipient())); } + @Override public boolean isInvolved(Account account) throws DataException { String address = account.getAddress(); @@ -52,6 +54,7 @@ public class GenesisTransaction extends Transaction { return false; } + @Override public BigDecimal getAmount(Account account) throws DataException { String address = account.getAddress(); BigDecimal amount = BigDecimal.ZERO.setScale(8); diff --git a/src/qora/transaction/IssueAssetTransaction.java b/src/qora/transaction/IssueAssetTransaction.java index 95d296aa..d3954df6 100644 --- a/src/qora/transaction/IssueAssetTransaction.java +++ b/src/qora/transaction/IssueAssetTransaction.java @@ -36,10 +36,12 @@ public class IssueAssetTransaction extends Transaction { // More information + @Override public List getRecipientAccounts() throws DataException { return Collections.singletonList(getOwner()); } + @Override public boolean isInvolved(Account account) throws DataException { String address = account.getAddress(); @@ -52,6 +54,7 @@ public class IssueAssetTransaction extends Transaction { return false; } + @Override public BigDecimal getAmount(Account account) throws DataException { String address = account.getAddress(); BigDecimal amount = BigDecimal.ZERO.setScale(8); @@ -117,8 +120,9 @@ public class IssueAssetTransaction extends Transaction { return ValidationResult.NO_BALANCE; // XXX: Surely we want to check the asset name isn't already taken? This check is not present in gen1. - if (this.repository.getAssetRepository().assetExists(issueAssetTransactionData.getAssetName())) - return ValidationResult.ASSET_ALREADY_EXISTS; + if (issueAssetTransactionData.getTimestamp() >= BlockChain.getIssueAssetV2Timestamp()) + if (this.repository.getAssetRepository().assetExists(issueAssetTransactionData.getAssetName())) + return ValidationResult.ASSET_ALREADY_EXISTS; return ValidationResult.OK; } diff --git a/src/qora/transaction/MessageTransaction.java b/src/qora/transaction/MessageTransaction.java index 3ea62f65..a23b933c 100644 --- a/src/qora/transaction/MessageTransaction.java +++ b/src/qora/transaction/MessageTransaction.java @@ -34,10 +34,12 @@ public class MessageTransaction extends Transaction { // More information + @Override public List getRecipientAccounts() throws DataException { return Collections.singletonList(new Account(this.repository, messageTransactionData.getRecipient())); } + @Override public boolean isInvolved(Account account) throws DataException { String address = account.getAddress(); @@ -50,6 +52,7 @@ public class MessageTransaction extends Transaction { return false; } + @Override public BigDecimal getAmount(Account account) throws DataException { String address = account.getAddress(); BigDecimal amount = BigDecimal.ZERO.setScale(8); diff --git a/src/qora/transaction/MultiPaymentTransaction.java b/src/qora/transaction/MultiPaymentTransaction.java index 8ca52343..321f4822 100644 --- a/src/qora/transaction/MultiPaymentTransaction.java +++ b/src/qora/transaction/MultiPaymentTransaction.java @@ -34,6 +34,7 @@ public class MultiPaymentTransaction extends Transaction { // More information + @Override public List getRecipientAccounts() throws DataException { List recipients = new ArrayList(); @@ -43,6 +44,7 @@ public class MultiPaymentTransaction extends Transaction { return recipients; } + @Override public boolean isInvolved(Account account) throws DataException { String address = account.getAddress(); @@ -56,6 +58,7 @@ public class MultiPaymentTransaction extends Transaction { return false; } + @Override public BigDecimal getAmount(Account account) throws DataException { String address = account.getAddress(); BigDecimal amount = BigDecimal.ZERO.setScale(8); diff --git a/src/qora/transaction/PaymentTransaction.java b/src/qora/transaction/PaymentTransaction.java index 015985d8..6e1ec8d6 100644 --- a/src/qora/transaction/PaymentTransaction.java +++ b/src/qora/transaction/PaymentTransaction.java @@ -30,10 +30,12 @@ public class PaymentTransaction extends Transaction { // More information + @Override public List getRecipientAccounts() throws DataException { return Collections.singletonList(new Account(this.repository, paymentTransactionData.getRecipient())); } + @Override public boolean isInvolved(Account account) throws DataException { String address = account.getAddress(); @@ -46,6 +48,7 @@ public class PaymentTransaction extends Transaction { return false; } + @Override public BigDecimal getAmount(Account account) throws DataException { String address = account.getAddress(); BigDecimal amount = BigDecimal.ZERO.setScale(8); diff --git a/src/qora/transaction/TransferAssetTransaction.java b/src/qora/transaction/TransferAssetTransaction.java index 3409ffbc..17a8ed7b 100644 --- a/src/qora/transaction/TransferAssetTransaction.java +++ b/src/qora/transaction/TransferAssetTransaction.java @@ -31,10 +31,12 @@ public class TransferAssetTransaction extends Transaction { // More information + @Override public List getRecipientAccounts() throws DataException { return Collections.singletonList(new Account(this.repository, transferAssetTransactionData.getRecipient())); } + @Override public boolean isInvolved(Account account) throws DataException { String address = account.getAddress(); @@ -47,6 +49,7 @@ public class TransferAssetTransaction extends Transaction { return false; } + @Override public BigDecimal getAmount(Account account) throws DataException { String address = account.getAddress(); BigDecimal amount = BigDecimal.ZERO.setScale(8); diff --git a/src/repository/Repository.java b/src/repository/Repository.java index b9af67cd..fa969947 100644 --- a/src/repository/Repository.java +++ b/src/repository/Repository.java @@ -18,6 +18,7 @@ public interface Repository extends AutoCloseable { public void discardChanges() throws DataException; + @Override public void close() throws DataException; public void rebuild() throws DataException; diff --git a/src/repository/hsqldb/HSQLDBAccountRepository.java b/src/repository/hsqldb/HSQLDBAccountRepository.java index b75ca7f1..1e2c669e 100644 --- a/src/repository/hsqldb/HSQLDBAccountRepository.java +++ b/src/repository/hsqldb/HSQLDBAccountRepository.java @@ -17,9 +17,9 @@ public class HSQLDBAccountRepository implements AccountRepository { this.repository = repository; } + @Override public AccountData getAccount(String address) throws DataException { - try { - ResultSet resultSet = this.repository.checkedExecute("SELECT reference FROM Accounts WHERE account = ?", address); + try (ResultSet resultSet = this.repository.checkedExecute("SELECT reference FROM Accounts WHERE account = ?", address)) { if (resultSet == null) return null; @@ -29,6 +29,7 @@ public class HSQLDBAccountRepository implements AccountRepository { } } + @Override public void save(AccountData accountData) throws DataException { HSQLDBSaver saveHelper = new HSQLDBSaver("Accounts"); saveHelper.bind("account", accountData.getAddress()).bind("reference", accountData.getReference()); @@ -40,9 +41,9 @@ public class HSQLDBAccountRepository implements AccountRepository { } } + @Override public AccountBalanceData getBalance(String address, long assetId) throws DataException { - try { - ResultSet resultSet = this.repository.checkedExecute("SELECT balance FROM AccountBalances WHERE account = ? and asset_id = ?", address, assetId); + try (ResultSet resultSet = this.repository.checkedExecute("SELECT balance FROM AccountBalances WHERE account = ? and asset_id = ?", address, assetId)) { if (resultSet == null) return null; @@ -54,6 +55,7 @@ public class HSQLDBAccountRepository implements AccountRepository { } } + @Override public void save(AccountBalanceData accountBalanceData) throws DataException { HSQLDBSaver saveHelper = new HSQLDBSaver("AccountBalances"); saveHelper.bind("account", accountBalanceData.getAddress()).bind("asset_id", accountBalanceData.getAssetId()).bind("balance", @@ -66,6 +68,7 @@ public class HSQLDBAccountRepository implements AccountRepository { } } + @Override public void delete(String address, long assetId) throws DataException { try { this.repository.delete("AccountBalances", "account = ? and asset_id = ?", address, assetId); diff --git a/src/repository/hsqldb/HSQLDBAssetRepository.java b/src/repository/hsqldb/HSQLDBAssetRepository.java index 51b37be0..79b2eb7e 100644 --- a/src/repository/hsqldb/HSQLDBAssetRepository.java +++ b/src/repository/hsqldb/HSQLDBAssetRepository.java @@ -5,6 +5,7 @@ import java.sql.ResultSet; import java.sql.SQLException; import java.sql.Timestamp; import java.util.ArrayList; +import java.util.Calendar; import java.util.List; import data.assets.AssetData; @@ -23,10 +24,10 @@ public class HSQLDBAssetRepository implements AssetRepository { // Assets + @Override public AssetData fromAssetId(long assetId) throws DataException { - try { - ResultSet resultSet = this.repository - .checkedExecute("SELECT owner, asset_name, description, quantity, is_divisible, reference FROM Assets WHERE asset_id = ?", assetId); + try (ResultSet resultSet = this.repository + .checkedExecute("SELECT owner, asset_name, description, quantity, is_divisible, reference FROM Assets WHERE asset_id = ?", assetId)) { if (resultSet == null) return null; @@ -43,10 +44,10 @@ public class HSQLDBAssetRepository implements AssetRepository { } } + @Override public AssetData fromAssetName(String assetName) throws DataException { - try { - ResultSet resultSet = this.repository - .checkedExecute("SELECT owner, asset_id, description, quantity, is_divisible, reference FROM Assets WHERE asset_name = ?", assetName); + try (ResultSet resultSet = this.repository + .checkedExecute("SELECT owner, asset_id, description, quantity, is_divisible, reference FROM Assets WHERE asset_name = ?", assetName)) { if (resultSet == null) return null; @@ -63,6 +64,7 @@ public class HSQLDBAssetRepository implements AssetRepository { } } + @Override public boolean assetExists(long assetId) throws DataException { try { return this.repository.exists("Assets", "asset_id = ?", assetId); @@ -71,6 +73,7 @@ public class HSQLDBAssetRepository implements AssetRepository { } } + @Override public boolean assetExists(String assetName) throws DataException { try { return this.repository.exists("Assets", "asset_name = ?", assetName); @@ -79,6 +82,7 @@ public class HSQLDBAssetRepository implements AssetRepository { } } + @Override public void save(AssetData assetData) throws DataException { HSQLDBSaver saveHelper = new HSQLDBSaver("Assets"); @@ -89,13 +93,21 @@ public class HSQLDBAssetRepository implements AssetRepository { try { saveHelper.execute(this.repository); - if (assetData.getAssetId() == null) - assetData.setAssetId(this.repository.callIdentity()); + if (assetData.getAssetId() == null) { + // Fetch new assetId + try (ResultSet resultSet = this.repository.checkedExecute("SELECT asset_id FROM Assets WHERE reference = ?", assetData.getReference())) { + if (resultSet == null) + throw new DataException("Unable to fetch new asset ID from repository"); + + assetData.setAssetId(resultSet.getLong(1)); + } + } } catch (SQLException e) { throw new DataException("Unable to save asset into repository", e); } } + @Override public void delete(long assetId) throws DataException { try { this.repository.delete("Assets", "asset_id = ?", assetId); @@ -106,11 +118,11 @@ public class HSQLDBAssetRepository implements AssetRepository { // Orders + @Override public OrderData fromOrderId(byte[] orderId) throws DataException { - try { - ResultSet resultSet = this.repository.checkedExecute( - "SELECT creator, have_asset_id, want_asset_id, amount, fulfilled, price, ordered, is_closed, is_fulfilled FROM AssetOrders WHERE asset_order_id = ?", - orderId); + try (ResultSet resultSet = this.repository.checkedExecute( + "SELECT creator, have_asset_id, want_asset_id, amount, fulfilled, price, ordered, is_closed, is_fulfilled FROM AssetOrders WHERE asset_order_id = ?", + orderId)) { if (resultSet == null) return null; @@ -120,7 +132,7 @@ public class HSQLDBAssetRepository implements AssetRepository { BigDecimal amount = resultSet.getBigDecimal(4); BigDecimal fulfilled = resultSet.getBigDecimal(5); BigDecimal price = resultSet.getBigDecimal(6); - long timestamp = resultSet.getTimestamp(7).getTime(); + long timestamp = resultSet.getTimestamp(7, Calendar.getInstance(HSQLDBRepository.UTC)).getTime(); boolean isClosed = resultSet.getBoolean(8); boolean isFulfilled = resultSet.getBoolean(9); @@ -130,14 +142,14 @@ public class HSQLDBAssetRepository implements AssetRepository { } } + @Override public List getOpenOrders(long haveAssetId, long wantAssetId) throws DataException { List orders = new ArrayList(); - try { - ResultSet resultSet = this.repository.checkedExecute( - "SELECT creator, asset_order_id, amount, fulfilled, price, ordered FROM AssetOrders " - + "WHERE have_asset_id = ? AND want_asset_id = ? AND is_closed = FALSE AND is_fulfilled = FALSE ORDER BY price ASC", - haveAssetId, wantAssetId); + try (ResultSet resultSet = this.repository.checkedExecute( + "SELECT creator, asset_order_id, amount, fulfilled, price, ordered FROM AssetOrders " + + "WHERE have_asset_id = ? AND want_asset_id = ? AND is_closed = FALSE AND is_fulfilled = FALSE ORDER BY price ASC", + haveAssetId, wantAssetId)) { if (resultSet == null) return orders; @@ -147,7 +159,7 @@ public class HSQLDBAssetRepository implements AssetRepository { BigDecimal amount = resultSet.getBigDecimal(3); BigDecimal fulfilled = resultSet.getBigDecimal(4); BigDecimal price = resultSet.getBigDecimal(5); - long timestamp = resultSet.getTimestamp(6).getTime(); + long timestamp = resultSet.getTimestamp(6, Calendar.getInstance(HSQLDBRepository.UTC)).getTime(); boolean isClosed = false; boolean isFulfilled = false; @@ -162,6 +174,7 @@ public class HSQLDBAssetRepository implements AssetRepository { } } + @Override public void save(OrderData orderData) throws DataException { HSQLDBSaver saveHelper = new HSQLDBSaver("AssetOrders"); @@ -177,6 +190,7 @@ public class HSQLDBAssetRepository implements AssetRepository { } } + @Override public void delete(byte[] orderId) throws DataException { try { this.repository.delete("AssetOrders", "asset_order_id = ?", orderId); @@ -187,12 +201,12 @@ public class HSQLDBAssetRepository implements AssetRepository { // Trades + @Override public List getOrdersTrades(byte[] initiatingOrderId) throws DataException { List trades = new ArrayList(); - try { - ResultSet resultSet = this.repository.checkedExecute("SELECT target_order_id, amount, price, traded FROM AssetTrades WHERE initiating_order_id = ?", - initiatingOrderId); + try (ResultSet resultSet = this.repository + .checkedExecute("SELECT target_order_id, amount, price, traded FROM AssetTrades WHERE initiating_order_id = ?", initiatingOrderId)) { if (resultSet == null) return trades; @@ -200,7 +214,7 @@ public class HSQLDBAssetRepository implements AssetRepository { byte[] targetOrderId = resultSet.getBytes(1); BigDecimal amount = resultSet.getBigDecimal(2); BigDecimal price = resultSet.getBigDecimal(3); - long timestamp = resultSet.getTimestamp(4).getTime(); + long timestamp = resultSet.getTimestamp(4, Calendar.getInstance(HSQLDBRepository.UTC)).getTime(); TradeData trade = new TradeData(initiatingOrderId, targetOrderId, amount, price, timestamp); trades.add(trade); @@ -212,6 +226,7 @@ public class HSQLDBAssetRepository implements AssetRepository { } } + @Override public void save(TradeData tradeData) throws DataException { HSQLDBSaver saveHelper = new HSQLDBSaver("AssetTrades"); @@ -225,6 +240,7 @@ public class HSQLDBAssetRepository implements AssetRepository { } } + @Override public void delete(TradeData tradeData) throws DataException { try { this.repository.delete("AssetTrades", "initiating_order_id = ? AND target_order_id = ? AND amount = ? AND price = ?", tradeData.getInitiator(), diff --git a/src/repository/hsqldb/HSQLDBBlockRepository.java b/src/repository/hsqldb/HSQLDBBlockRepository.java index 7fdccc69..6b17aa30 100644 --- a/src/repository/hsqldb/HSQLDBBlockRepository.java +++ b/src/repository/hsqldb/HSQLDBBlockRepository.java @@ -5,6 +5,7 @@ import java.sql.ResultSet; import java.sql.SQLException; import java.sql.Timestamp; import java.util.ArrayList; +import java.util.Calendar; import java.util.List; import data.block.BlockData; @@ -25,23 +26,23 @@ public class HSQLDBBlockRepository implements BlockRepository { this.repository = repository; } - private BlockData getBlockFromResultSet(ResultSet rs) throws DataException { - if (rs == null) + private BlockData getBlockFromResultSet(ResultSet resultSet) throws DataException { + if (resultSet == null) return null; try { - int version = rs.getInt(1); - byte[] reference = rs.getBytes(2); - int transactionCount = rs.getInt(3); - BigDecimal totalFees = rs.getBigDecimal(4); - byte[] transactionsSignature = rs.getBytes(5); - int height = rs.getInt(6); - long timestamp = rs.getTimestamp(7).getTime(); - BigDecimal generatingBalance = rs.getBigDecimal(8); - byte[] generatorPublicKey = rs.getBytes(9); - byte[] generatorSignature = rs.getBytes(10); - byte[] atBytes = rs.getBytes(11); - BigDecimal atFees = rs.getBigDecimal(12); + int version = resultSet.getInt(1); + byte[] reference = resultSet.getBytes(2); + int transactionCount = resultSet.getInt(3); + BigDecimal totalFees = resultSet.getBigDecimal(4); + byte[] transactionsSignature = resultSet.getBytes(5); + int height = resultSet.getInt(6); + long timestamp = resultSet.getTimestamp(7, Calendar.getInstance(HSQLDBRepository.UTC)).getTime(); + BigDecimal generatingBalance = resultSet.getBigDecimal(8); + byte[] generatorPublicKey = resultSet.getBytes(9); + byte[] generatorSignature = resultSet.getBytes(10); + byte[] atBytes = resultSet.getBytes(11); + BigDecimal atFees = resultSet.getBigDecimal(12); return new BlockData(version, reference, transactionCount, totalFees, transactionsSignature, height, timestamp, generatingBalance, generatorPublicKey, generatorSignature, atBytes, atFees); @@ -50,76 +51,77 @@ public class HSQLDBBlockRepository implements BlockRepository { } } + @Override public BlockData fromSignature(byte[] signature) throws DataException { - try { - ResultSet rs = this.repository.checkedExecute("SELECT " + BLOCK_DB_COLUMNS + " FROM Blocks WHERE signature = ?", signature); - return getBlockFromResultSet(rs); + try (ResultSet resultSet = this.repository.checkedExecute("SELECT " + BLOCK_DB_COLUMNS + " FROM Blocks WHERE signature = ?", signature)) { + return getBlockFromResultSet(resultSet); } catch (SQLException e) { throw new DataException("Error loading data from DB", e); } } + @Override public BlockData fromReference(byte[] reference) throws DataException { - try { - ResultSet rs = this.repository.checkedExecute("SELECT " + BLOCK_DB_COLUMNS + " FROM Blocks WHERE height = ?", reference); - return getBlockFromResultSet(rs); + try (ResultSet resultSet = this.repository.checkedExecute("SELECT " + BLOCK_DB_COLUMNS + " FROM Blocks WHERE height = ?", reference)) { + return getBlockFromResultSet(resultSet); } catch (SQLException e) { throw new DataException("Error loading data from DB", e); } } + @Override public BlockData fromHeight(int height) throws DataException { - try { - ResultSet rs = this.repository.checkedExecute("SELECT " + BLOCK_DB_COLUMNS + " FROM Blocks WHERE height = ?", height); - return getBlockFromResultSet(rs); + try (ResultSet resultSet = this.repository.checkedExecute("SELECT " + BLOCK_DB_COLUMNS + " FROM Blocks WHERE height = ?", height)) { + return getBlockFromResultSet(resultSet); } catch (SQLException e) { throw new DataException("Error loading data from DB", e); } } + @Override public int getHeightFromSignature(byte[] signature) throws DataException { - try { - ResultSet rs = this.repository.checkedExecute("SELECT height FROM Blocks WHERE signature = ?", signature); - if (rs == null) + try (ResultSet resultSet = this.repository.checkedExecute("SELECT height FROM Blocks WHERE signature = ?", signature)) { + if (resultSet == null) return 0; - return rs.getInt(1); + return resultSet.getInt(1); } catch (SQLException e) { throw new DataException("Error obtaining block height from repository", e); } } + @Override public int getBlockchainHeight() throws DataException { - try { - ResultSet rs = this.repository.checkedExecute("SELECT MAX(height) FROM Blocks"); - if (rs == null) + try (ResultSet resultSet = this.repository.checkedExecute("SELECT MAX(height) FROM Blocks")) { + if (resultSet == null) return 0; - return rs.getInt(1); + return resultSet.getInt(1); } catch (SQLException e) { throw new DataException("Error obtaining blockchain height from repository", e); } } + @Override public BlockData getLastBlock() throws DataException { return fromHeight(getBlockchainHeight()); } + @Override public List getTransactionsFromSignature(byte[] signature) throws DataException { List transactions = new ArrayList(); - try { - ResultSet rs = this.repository.checkedExecute("SELECT transaction_signature FROM BlockTransactions WHERE block_signature = ?", signature); - if (rs == null) + try (ResultSet resultSet = this.repository.checkedExecute("SELECT transaction_signature FROM BlockTransactions WHERE block_signature = ?", signature)) { + if (resultSet == null) return transactions; // No transactions in this block TransactionRepository transactionRepo = this.repository.getTransactionRepository(); // NB: do-while loop because .checkedExecute() implicitly calls ResultSet.next() for us do { - byte[] transactionSignature = rs.getBytes(1); + byte[] transactionSignature = resultSet.getBytes(1); transactions.add(transactionRepo.fromSignature(transactionSignature)); - } while (rs.next()); + } while (resultSet.next()); } catch (SQLException e) { throw new DataException(e); } @@ -127,6 +129,7 @@ public class HSQLDBBlockRepository implements BlockRepository { return transactions; } + @Override public void save(BlockData blockData) throws DataException { HSQLDBSaver saveHelper = new HSQLDBSaver("Blocks"); @@ -144,6 +147,7 @@ public class HSQLDBBlockRepository implements BlockRepository { } } + @Override public void delete(BlockData blockData) throws DataException { try { this.repository.delete("Blocks", "signature = ?", blockData.getSignature()); @@ -152,6 +156,7 @@ public class HSQLDBBlockRepository implements BlockRepository { } } + @Override public void save(BlockTransactionData blockTransactionData) throws DataException { HSQLDBSaver saveHelper = new HSQLDBSaver("BlockTransactions"); saveHelper.bind("block_signature", blockTransactionData.getBlockSignature()).bind("sequence", blockTransactionData.getSequence()) @@ -164,6 +169,7 @@ public class HSQLDBBlockRepository implements BlockRepository { } } + @Override public void delete(BlockTransactionData blockTransactionData) throws DataException { try { this.repository.delete("BlockTransactions", "block_signature = ? AND sequence = ? AND transaction_signature = ?", diff --git a/src/repository/hsqldb/HSQLDBDatabaseUpdates.java b/src/repository/hsqldb/HSQLDBDatabaseUpdates.java index d645fb81..4bb79da5 100644 --- a/src/repository/hsqldb/HSQLDBDatabaseUpdates.java +++ b/src/repository/hsqldb/HSQLDBDatabaseUpdates.java @@ -36,14 +36,12 @@ public class HSQLDBDatabaseUpdates { private static int fetchDatabaseVersion(Connection connection) throws SQLException { int databaseVersion = 0; - try { - Statement stmt = connection.createStatement(); - if (stmt.execute("SELECT version FROM DatabaseInfo")) { - ResultSet rs = stmt.getResultSet(); - - if (rs.next()) - databaseVersion = rs.getInt(1); - } + try (Statement stmt = connection.createStatement()) { + if (stmt.execute("SELECT version FROM DatabaseInfo")) + try (ResultSet resultSet = stmt.getResultSet()) { + if (resultSet.next()) + databaseVersion = resultSet.getInt(1); + } } catch (SQLException e) { // empty database } @@ -60,292 +58,298 @@ public class HSQLDBDatabaseUpdates { private static boolean databaseUpdating(Connection connection) throws SQLException { int databaseVersion = fetchDatabaseVersion(connection); - Statement stmt = connection.createStatement(); + try (Statement stmt = connection.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 DATABASE COLLATION SQL_TEXT NO PAD"); - stmt.execute("CREATE COLLATION SQL_TEXT_UCC_NO_PAD FOR SQL_TEXT FROM SQL_TEXT_UCC NO PAD"); - stmt.execute("CREATE COLLATION SQL_TEXT_NO_PAD FOR SQL_TEXT FROM SQL_TEXT NO PAD"); - 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_NO_PAD"); - stmt.execute("CREATE TYPE NameData AS VARCHAR(4000)"); - stmt.execute("CREATE TYPE PollName AS VARCHAR(400) COLLATE SQL_TEXT_NO_PAD"); - stmt.execute("CREATE TYPE PollOption AS VARCHAR(400) COLLATE SQL_TEXT_UCC_NO_PAD"); - stmt.execute("CREATE TYPE PollOptionIndex AS INTEGER"); - stmt.execute("CREATE TYPE DataHash AS VARBINARY(32)"); - stmt.execute("CREATE TYPE AssetID AS BIGINT"); - stmt.execute("CREATE TYPE AssetName AS VARCHAR(400) COLLATE SQL_TEXT_NO_PAD"); - stmt.execute("CREATE TYPE AssetOrderID AS VARBINARY(64)"); - stmt.execute("CREATE TYPE ATName AS VARCHAR(200) COLLATE SQL_TEXT_UCC_NO_PAD"); - stmt.execute("CREATE TYPE ATType AS VARCHAR(200) COLLATE SQL_TEXT_UCC_NO_PAD"); - break; + switch (databaseVersion) { + case 0: + // create from new + stmt.execute("SET DATABASE DEFAULT TABLE TYPE CACHED"); + stmt.execute("SET DATABASE COLLATION SQL_TEXT NO PAD"); + stmt.execute("CREATE COLLATION SQL_TEXT_UCC_NO_PAD FOR SQL_TEXT FROM SQL_TEXT_UCC NO PAD"); + stmt.execute("CREATE COLLATION SQL_TEXT_NO_PAD FOR SQL_TEXT FROM SQL_TEXT NO PAD"); + 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_NO_PAD"); + stmt.execute("CREATE TYPE NameData AS VARCHAR(4000)"); + stmt.execute("CREATE TYPE PollName AS VARCHAR(400) COLLATE SQL_TEXT_NO_PAD"); + stmt.execute("CREATE TYPE PollOption AS VARCHAR(400) COLLATE SQL_TEXT_UCC_NO_PAD"); + stmt.execute("CREATE TYPE PollOptionIndex AS INTEGER"); + stmt.execute("CREATE TYPE DataHash AS VARBINARY(32)"); + stmt.execute("CREATE TYPE AssetID AS BIGINT"); + stmt.execute("CREATE TYPE AssetName AS VARCHAR(400) COLLATE SQL_TEXT_NO_PAD"); + stmt.execute("CREATE TYPE AssetOrderID AS VARBINARY(64)"); + stmt.execute("CREATE TYPE ATName AS VARCHAR(200) COLLATE SQL_TEXT_UCC_NO_PAD"); + stmt.execute("CREATE TYPE ATType AS VARCHAR(200) COLLATE SQL_TEXT_UCC_NO_PAD"); + 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)"); - // For finding blocks by height. - stmt.execute("CREATE INDEX BlockHeightIndex ON Blocks (height)"); - // For finding blocks by the account that generated them. - stmt.execute("CREATE INDEX BlockGeneratorIndex ON Blocks (generator)"); - // For finding blocks by reference, e.g. child blocks. - stmt.execute("CREATE INDEX BlockReferenceIndex ON Blocks (reference)"); - // Use a separate table space as this table will be very large. - 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 WITH TIME ZONE NOT NULL, generating_balance QoraAmount NOT NULL, " + + "generator QoraPublicKey NOT NULL, generator_signature Signature NOT NULL, AT_data VARBINARY(20000), AT_fees QoraAmount)"); + // For finding blocks by height. + stmt.execute("CREATE INDEX BlockHeightIndex ON Blocks (height)"); + // For finding blocks by the account that generated them. + stmt.execute("CREATE INDEX BlockGeneratorIndex ON Blocks (generator)"); + // For finding blocks by reference, e.g. child blocks. + stmt.execute("CREATE INDEX BlockReferenceIndex ON Blocks (reference)"); + // Use a separate table space as this table will be very large. + 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)"); - // For finding transactions by transaction type. - stmt.execute("CREATE INDEX TransactionTypeIndex ON Transactions (type)"); - // For finding transactions using timestamp. - stmt.execute("CREATE INDEX TransactionCreationIndex ON Transactions (creation)"); - // For when a user wants to lookup ALL transactions they have created, regardless of type. - stmt.execute("CREATE INDEX TransactionCreatorIndex ON Transactions (creator)"); - // For finding transactions by reference, e.g. child transactions. - stmt.execute("CREATE INDEX TransactionReferenceIndex ON Transactions (reference)"); - // Use a separate table space as this table will be very large. - 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 WITH TIME ZONE NOT NULL, fee QoraAmount NOT NULL, milestone_block BlockSignature)"); + // For finding transactions by transaction type. + stmt.execute("CREATE INDEX TransactionTypeIndex ON Transactions (type)"); + // For finding transactions using timestamp. + stmt.execute("CREATE INDEX TransactionCreationIndex ON Transactions (creation)"); + // For when a user wants to lookup ALL transactions they have created, regardless of type. + stmt.execute("CREATE INDEX TransactionCreatorIndex ON Transactions (creator)"); + // For finding transactions by reference, e.g. child transactions. + stmt.execute("CREATE INDEX TransactionReferenceIndex ON Transactions (reference)"); + // Use a separate table space as this table will be very large. + 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)"); - // Use a separate table space as this table will be very large. - 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)"); + // Use a separate table space as this table will be very large. + stmt.execute("SET TABLE BlockTransactions NEW SPACE"); - // Unconfirmed transactions - // XXX 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 + // XXX 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 WITH TIME ZONE 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)"); - // Use a separate table space as this table will be very large. - 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)"); + // Use a separate table space as this table will be very large. + 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, name_reference Signature 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, name_reference Signature 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, name_reference Signature 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, name_reference Signature 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, owner QoraAddress NOT NULL, " - + "poll_name 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_index TINYINT NOT NULL, option_name PollOption, " - + "PRIMARY KEY (signature, option_index), 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, owner QoraAddress NOT NULL, " + + "poll_name 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_index TINYINT NOT NULL, option_name PollOption, " + + "PRIMARY KEY (signature, option_index), 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_name PollName NOT NULL, " - + "option_index PollOptionIndex NOT NULL, previous_option_index PollOptionIndex, " - + "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_name PollName NOT NULL, " + + "option_index PollOptionIndex NOT NULL, previous_option_index PollOptionIndex, " + + "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 QoraAddress 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 QoraAddress 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, sender QoraPublicKey NOT NULL, version TINYINT 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, sender QoraPublicKey NOT NULL, version TINYINT 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_id 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_id 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 QoraAddress 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)"); - // For when a user wants to lookup an asset by name - 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, owner QoraAddress 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, PRIMARY KEY (asset_id))"); + // We need a corresponding trigger to make sure new asset_id values are assigned sequentially + stmt.execute( + "CREATE TRIGGER Asset_ID_Trigger BEFORE INSERT ON Assets REFERENCING NEW ROW AS new_row FOR EACH ROW WHEN (new_row.asset_id IS NULL) " + + "SET new_row.asset_id = (SELECT IFNULL(MAX(asset_id) + 1, 0) FROM Assets)"); + // For when a user wants to lookup an asset by name + 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; - case 23: - // Asset Orders - stmt.execute( - "CREATE TABLE AssetOrders (asset_order_id AssetOrderID, creator QoraPublicKey NOT NULL, have_asset_id AssetID NOT NULL, want_asset_id AssetID NOT NULL, " - + "amount QoraAmount NOT NULL, fulfilled QoraAmount NOT NULL, price QoraAmount NOT NULL, " - + "ordered TIMESTAMP NOT NULL, is_closed BOOLEAN NOT NULL, is_fulfilled BOOLEAN NOT NULL, " + "PRIMARY KEY (asset_order_id))"); - // For quick matching of orders. is_closed are is_fulfilled included so inactive orders can be filtered out. - stmt.execute("CREATE INDEX AssetOrderMatchingIndex on AssetOrders (have_asset_id, want_asset_id, is_closed, is_fulfilled)"); - // For when a user wants to look up their current/historic orders. is_closed included so user can filter by active/inactive orders. - stmt.execute("CREATE INDEX AssetOrderCreatorIndex on AssetOrders (creator, is_closed)"); - break; + case 23: + // Asset Orders + stmt.execute( + "CREATE TABLE AssetOrders (asset_order_id AssetOrderID, creator QoraPublicKey NOT NULL, have_asset_id AssetID NOT NULL, want_asset_id AssetID NOT NULL, " + + "amount QoraAmount NOT NULL, fulfilled QoraAmount NOT NULL, price QoraAmount NOT NULL, " + + "ordered TIMESTAMP WITH TIME ZONE NOT NULL, is_closed BOOLEAN NOT NULL, is_fulfilled BOOLEAN NOT NULL, " + + "PRIMARY KEY (asset_order_id))"); + // For quick matching of orders. is_closed are is_fulfilled included so inactive orders can be filtered out. + stmt.execute("CREATE INDEX AssetOrderMatchingIndex on AssetOrders (have_asset_id, want_asset_id, is_closed, is_fulfilled)"); + // For when a user wants to look up their current/historic orders. is_closed included so user can filter by active/inactive orders. + stmt.execute("CREATE INDEX AssetOrderCreatorIndex on AssetOrders (creator, is_closed)"); + break; - case 24: - // Asset Trades - stmt.execute("CREATE TABLE AssetTrades (initiating_order_id AssetOrderId NOT NULL, target_order_id AssetOrderId NOT NULL, " - + "amount QoraAmount NOT NULL, price QoraAmount NOT NULL, traded TIMESTAMP NOT NULL)"); - // For looking up historic trades based on orders - stmt.execute("CREATE INDEX AssetTradeBuyOrderIndex on AssetTrades (initiating_order_id, traded)"); - stmt.execute("CREATE INDEX AssetTradeSellOrderIndex on AssetTrades (target_order_id, traded)"); - break; + case 24: + // Asset Trades + stmt.execute("CREATE TABLE AssetTrades (initiating_order_id AssetOrderId NOT NULL, target_order_id AssetOrderId NOT NULL, " + + "amount QoraAmount NOT NULL, price QoraAmount NOT NULL, traded TIMESTAMP WITH TIME ZONE NOT NULL)"); + // For looking up historic trades based on orders + stmt.execute("CREATE INDEX AssetTradeBuyOrderIndex on AssetTrades (initiating_order_id, traded)"); + stmt.execute("CREATE INDEX AssetTradeSellOrderIndex on AssetTrades (target_order_id, traded)"); + break; - case 25: - // Polls/Voting - stmt.execute( - "CREATE TABLE Polls (poll_name PollName, description VARCHAR(4000) NOT NULL, creator QoraPublicKey NOT NULL, owner QoraAddress NOT NULL, " - + "published TIMESTAMP NOT NULL, " + "PRIMARY KEY (poll_name))"); - // Various options available on a poll - stmt.execute("CREATE TABLE PollOptions (poll_name PollName, option_index TINYINT NOT NULL, option_name PollOption, " - + "PRIMARY KEY (poll_name, option_index), FOREIGN KEY (poll_name) REFERENCES Polls (poll_name) ON DELETE CASCADE)"); - // Actual votes cast on a poll by voting users. NOTE: only one vote per user supported at this time. - stmt.execute("CREATE TABLE PollVotes (poll_name PollName, voter QoraPublicKey, option_index PollOptionIndex NOT NULL, " - + "PRIMARY KEY (poll_name, voter), FOREIGN KEY (poll_name) REFERENCES Polls (poll_name) ON DELETE CASCADE)"); - // For when a user wants to lookup poll they own - stmt.execute("CREATE INDEX PollOwnerIndex on Polls (owner)"); - break; + case 25: + // Polls/Voting + stmt.execute( + "CREATE TABLE Polls (poll_name PollName, description VARCHAR(4000) NOT NULL, creator QoraPublicKey NOT NULL, owner QoraAddress NOT NULL, " + + "published TIMESTAMP WITH TIME ZONE NOT NULL, " + "PRIMARY KEY (poll_name))"); + // Various options available on a poll + stmt.execute("CREATE TABLE PollOptions (poll_name PollName, option_index TINYINT NOT NULL, option_name PollOption, " + + "PRIMARY KEY (poll_name, option_index), FOREIGN KEY (poll_name) REFERENCES Polls (poll_name) ON DELETE CASCADE)"); + // Actual votes cast on a poll by voting users. NOTE: only one vote per user supported at this time. + stmt.execute("CREATE TABLE PollVotes (poll_name PollName, voter QoraPublicKey, option_index PollOptionIndex NOT NULL, " + + "PRIMARY KEY (poll_name, voter), FOREIGN KEY (poll_name) REFERENCES Polls (poll_name) ON DELETE CASCADE)"); + // For when a user wants to lookup poll they own + stmt.execute("CREATE INDEX PollOwnerIndex on Polls (owner)"); + break; - case 26: - // Registered Names - stmt.execute( - "CREATE TABLE Names (name RegisteredName, data VARCHAR(4000) NOT NULL, registrant QoraPublicKey NOT NULL, owner QoraAddress NOT NULL, " - + "registered TIMESTAMP NOT NULL, updated TIMESTAMP, reference Signature, is_for_sale BOOLEAN NOT NULL, sale_price QoraAmount, " - + "PRIMARY KEY (name))"); - break; + case 26: + // Registered Names + stmt.execute( + "CREATE TABLE Names (name RegisteredName, data VARCHAR(4000) NOT NULL, registrant QoraPublicKey NOT NULL, owner QoraAddress NOT NULL, " + + "registered TIMESTAMP WITH TIME ZONE NOT NULL, updated TIMESTAMP WITH TIME ZONE, reference Signature, is_for_sale BOOLEAN NOT NULL, sale_price QoraAmount, " + + "PRIMARY KEY (name))"); + break; - default: - // nothing to do - return false; + default: + // nothing to do + return false; + } } // database was updated diff --git a/src/repository/hsqldb/HSQLDBNameRepository.java b/src/repository/hsqldb/HSQLDBNameRepository.java index 6737dfd4..6b642ceb 100644 --- a/src/repository/hsqldb/HSQLDBNameRepository.java +++ b/src/repository/hsqldb/HSQLDBNameRepository.java @@ -4,6 +4,7 @@ import java.math.BigDecimal; import java.sql.ResultSet; import java.sql.SQLException; import java.sql.Timestamp; +import java.util.Calendar; import data.naming.NameData; import repository.NameRepository; @@ -19,19 +20,18 @@ public class HSQLDBNameRepository implements NameRepository { @Override public NameData fromName(String name) throws DataException { - try { - ResultSet resultSet = this.repository - .checkedExecute("SELECT registrant, owner, data, registered, updated, reference, is_for_sale, sale_price FROM Names WHERE name = ?", name); + try (ResultSet resultSet = this.repository + .checkedExecute("SELECT registrant, owner, data, registered, updated, reference, is_for_sale, sale_price FROM Names WHERE name = ?", name)) { if (resultSet == null) return null; byte[] registrantPublicKey = resultSet.getBytes(1); String owner = resultSet.getString(2); String data = resultSet.getString(3); - long registered = resultSet.getTimestamp(4).getTime(); + long registered = resultSet.getTimestamp(4, Calendar.getInstance(HSQLDBRepository.UTC)).getTime(); // Special handling for possibly-NULL "updated" column - Timestamp updatedTimestamp = resultSet.getTimestamp(5); + Timestamp updatedTimestamp = resultSet.getTimestamp(5, Calendar.getInstance(HSQLDBRepository.UTC)); Long updated = updatedTimestamp == null ? null : updatedTimestamp.getTime(); byte[] reference = resultSet.getBytes(6); diff --git a/src/repository/hsqldb/HSQLDBRepository.java b/src/repository/hsqldb/HSQLDBRepository.java index ebceafdb..e4c8be7c 100644 --- a/src/repository/hsqldb/HSQLDBRepository.java +++ b/src/repository/hsqldb/HSQLDBRepository.java @@ -6,6 +6,7 @@ import java.sql.PreparedStatement; import java.sql.ResultSet; import java.sql.SQLException; import java.sql.Statement; +import java.util.TimeZone; import repository.AccountRepository; import repository.AssetRepository; @@ -19,6 +20,8 @@ import repository.hsqldb.transaction.HSQLDBTransactionRepository; public class HSQLDBRepository implements Repository { + public static final TimeZone UTC = TimeZone.getTimeZone("UTC"); + protected Connection connection; // NB: no visibility modifier so only callable from within same package @@ -74,24 +77,22 @@ public class HSQLDBRepository implements Repository { } } - // TODO prevent leaking of connections if .close() is not called before garbage collection of the repository. - // Maybe use PhantomReference to call .close() on connection after repository destruction? @Override public void close() throws DataException { - try { + try (Statement stmt = this.connection.createStatement()) { // Diagnostic check for uncommitted changes - Statement stmt = this.connection.createStatement(); if (!stmt.execute("SELECT transaction, transaction_size FROM information_schema.system_sessions")) // TRANSACTION_SIZE() broken? throw new DataException("Unable to check repository status during close"); - ResultSet rs = stmt.getResultSet(); - if (rs == null || !rs.next()) - throw new DataException("Unable to check repository status during close"); + try (ResultSet resultSet = stmt.getResultSet()) { + if (resultSet == null || !resultSet.next()) + System.out.println("Unable to check repository status during close"); - boolean inTransaction = rs.getBoolean(1); - int transactionCount = rs.getInt(2); - if (inTransaction && transactionCount != 0) - System.out.println("Uncommitted changes (" + transactionCount + ") during repository close"); + boolean inTransaction = resultSet.getBoolean(1); + int transactionCount = resultSet.getInt(2); + if (inTransaction && transactionCount != 0) + System.out.println("Uncommitted changes (" + transactionCount + ") during repository close"); + } // give connection back to the pool this.connection.close(); @@ -115,9 +116,12 @@ public class HSQLDBRepository implements Repository { * @return ResultSet, or null if there are no found rows * @throws SQLException */ + @SuppressWarnings("resource") public ResultSet checkedExecute(String sql, Object... objects) throws SQLException { PreparedStatement preparedStatement = this.connection.prepareStatement(sql); - + // Close the PreparedStatement when the ResultSet is closed otherwise there's a potential resource leak. + // We can't use try-with-resources here as closing the PreparedStatement on return would also prematurely close the ResultSet. + preparedStatement.closeOnCompletion(); return this.checkedExecuteResultSet(preparedStatement, objects); } @@ -198,12 +202,13 @@ public class HSQLDBRepository implements Repository { * @throws SQLException */ public Long callIdentity() throws SQLException { - PreparedStatement preparedStatement = this.connection.prepareStatement("CALL IDENTITY()"); - ResultSet resultSet = this.checkedExecuteResultSet(preparedStatement); - if (resultSet == null) - return null; + try (PreparedStatement preparedStatement = this.connection.prepareStatement("CALL IDENTITY()"); + ResultSet resultSet = this.checkedExecuteResultSet(preparedStatement)) { + if (resultSet == null) + return null; - return resultSet.getLong(1); + return resultSet.getLong(1); + } } /** @@ -224,12 +229,13 @@ public class HSQLDBRepository implements Repository { * @throws SQLException */ public boolean exists(String tableName, String whereClause, Object... objects) throws SQLException { - PreparedStatement preparedStatement = this.connection.prepareStatement("SELECT TRUE FROM " + tableName + " WHERE " + whereClause + " LIMIT 1"); - ResultSet resultSet = this.checkedExecuteResultSet(preparedStatement, objects); - if (resultSet == null) - return false; + try (PreparedStatement preparedStatement = this.connection.prepareStatement("SELECT TRUE FROM " + tableName + " WHERE " + whereClause + " LIMIT 1"); + ResultSet resultSet = this.checkedExecuteResultSet(preparedStatement, objects)) { + if (resultSet == null) + return false; - return true; + return true; + } } /** @@ -241,8 +247,9 @@ public class HSQLDBRepository implements Repository { * @throws SQLException */ public void delete(String tableName, String whereClause, Object... objects) throws SQLException { - PreparedStatement preparedStatement = this.connection.prepareStatement("DELETE FROM " + tableName + " WHERE " + whereClause); - this.checkedExecuteUpdateCount(preparedStatement, objects); + try (PreparedStatement preparedStatement = this.connection.prepareStatement("DELETE FROM " + tableName + " WHERE " + whereClause)) { + this.checkedExecuteUpdateCount(preparedStatement, objects); + } } } diff --git a/src/repository/hsqldb/HSQLDBRepositoryFactory.java b/src/repository/hsqldb/HSQLDBRepositoryFactory.java index b8c2dc99..b5e368bc 100644 --- a/src/repository/hsqldb/HSQLDBRepositoryFactory.java +++ b/src/repository/hsqldb/HSQLDBRepositoryFactory.java @@ -3,6 +3,7 @@ package repository.hsqldb; import java.sql.Connection; import java.sql.DriverManager; import java.sql.SQLException; +import java.util.Properties; import org.hsqldb.jdbc.JDBCPool; @@ -22,6 +23,14 @@ public class HSQLDBRepositoryFactory implements RepositoryFactory { this.connectionPool = new JDBCPool(); this.connectionPool.setUrl(this.connectionUrl); + Properties properties = new Properties(); + properties.setProperty("close_result", "true"); // Auto-close old ResultSet if Statement creates new ResultSet + properties.setProperty("sql.strict_exec", "true"); // No multi-SQL execute() or DDL/DML executeQuery() + properties.setProperty("sql.enforce_names", "true"); // SQL keywords cannot be used as DB object names, e.g. table names + properties.setProperty("sql.syntax_mys", "true"); // Required for our use of INSERT ... ON DUPLICATE KEY UPDATE ... syntax + properties.setProperty("sql.pad_space", "false"); // Do not pad strings to same length before comparison + this.connectionPool.setProperties(properties); + // Perform DB updates? try (final Connection connection = this.connectionPool.getConnection()) { HSQLDBDatabaseUpdates.updateDatabase(connection); @@ -30,6 +39,7 @@ public class HSQLDBRepositoryFactory implements RepositoryFactory { } } + @Override public Repository getRepository() throws DataException { try { return new HSQLDBRepository(this.getConnection()); @@ -41,22 +51,23 @@ public class HSQLDBRepositoryFactory implements RepositoryFactory { private Connection getConnection() throws SQLException { Connection connection = this.connectionPool.getConnection(); - // start transaction + // Set transaction level connection.setTransactionIsolation(Connection.TRANSACTION_SERIALIZABLE); connection.setAutoCommit(false); return connection; } + @Override public void close() throws DataException { try { // Close all existing connections immediately this.connectionPool.close(0); // Now that all connections are closed, create a dedicated connection to shut down repository - Connection connection = DriverManager.getConnection(this.connectionUrl); - connection.createStatement().execute("SHUTDOWN"); - connection.close(); + try (Connection connection = DriverManager.getConnection(this.connectionUrl)) { + connection.createStatement().execute("SHUTDOWN"); + } } catch (SQLException e) { throw new DataException("Error during repository shutdown", e); } diff --git a/src/repository/hsqldb/HSQLDBSaver.java b/src/repository/hsqldb/HSQLDBSaver.java index 6c81be29..8b05767a 100644 --- a/src/repository/hsqldb/HSQLDBSaver.java +++ b/src/repository/hsqldb/HSQLDBSaver.java @@ -3,8 +3,10 @@ package repository.hsqldb; import java.math.BigDecimal; import java.sql.PreparedStatement; import java.sql.SQLException; +import java.sql.Timestamp; import java.util.ArrayList; import java.util.Arrays; +import java.util.Calendar; import java.util.List; /** @@ -56,11 +58,11 @@ public class HSQLDBSaver { */ public boolean execute(HSQLDBRepository repository) throws SQLException { String sql = this.formatInsertWithPlaceholders(); - PreparedStatement preparedStatement = repository.connection.prepareStatement(sql); + try (PreparedStatement preparedStatement = repository.connection.prepareStatement(sql)) { + this.bindValues(preparedStatement); - this.bindValues(preparedStatement); - - return preparedStatement.execute(); + return preparedStatement.execute(); + } } /** @@ -107,11 +109,15 @@ public class HSQLDBSaver { for (int i = 0; i < this.objects.size(); ++i) { Object object = this.objects.get(i); - // Special treatment for BigDecimals so that they retain their "scale", - // which would otherwise be assumed as 0. if (object instanceof BigDecimal) { + // Special treatment for BigDecimals so that they retain their "scale", + // which would otherwise be assumed as 0. preparedStatement.setBigDecimal(i + 1, (BigDecimal) object); preparedStatement.setBigDecimal(i + this.objects.size() + 1, (BigDecimal) object); + } else if (object instanceof Timestamp) { + // Special treatment for Timestamps so that they are stored as UTC + preparedStatement.setTimestamp(i + 1, (Timestamp) object, Calendar.getInstance(HSQLDBRepository.UTC)); + preparedStatement.setTimestamp(i + this.objects.size() + 1, (Timestamp) object, Calendar.getInstance(HSQLDBRepository.UTC)); } else { preparedStatement.setObject(i + 1, object); preparedStatement.setObject(i + this.objects.size() + 1, object); diff --git a/src/repository/hsqldb/HSQLDBVotingRepository.java b/src/repository/hsqldb/HSQLDBVotingRepository.java index de929878..cedb1c62 100644 --- a/src/repository/hsqldb/HSQLDBVotingRepository.java +++ b/src/repository/hsqldb/HSQLDBVotingRepository.java @@ -4,6 +4,7 @@ import java.sql.ResultSet; import java.sql.SQLException; import java.sql.Timestamp; import java.util.ArrayList; +import java.util.Calendar; import java.util.List; import data.voting.PollData; @@ -22,36 +23,39 @@ public class HSQLDBVotingRepository implements VotingRepository { // Polls + @Override public PollData fromPollName(String pollName) throws DataException { - try { - ResultSet resultSet = this.repository.checkedExecute("SELECT description, creator, owner, published FROM Polls WHERE poll_name = ?", pollName); + try (ResultSet resultSet = this.repository.checkedExecute("SELECT description, creator, owner, published FROM Polls WHERE poll_name = ?", pollName)) { if (resultSet == null) return null; String description = resultSet.getString(1); byte[] creatorPublicKey = resultSet.getBytes(2); String owner = resultSet.getString(3); - long published = resultSet.getTimestamp(4).getTime(); + long published = resultSet.getTimestamp(4, Calendar.getInstance(HSQLDBRepository.UTC)).getTime(); - resultSet = this.repository.checkedExecute("SELECT option_name FROM PollOptions where poll_name = ? ORDER BY option_index ASC", pollName); - if (resultSet == null) - return null; + try (ResultSet optionsResultSet = this.repository + .checkedExecute("SELECT option_name FROM PollOptions where poll_name = ? ORDER BY option_index ASC", pollName)) { + if (optionsResultSet == null) + return null; - List pollOptions = new ArrayList(); + List pollOptions = new ArrayList(); - // NOTE: do-while because checkedExecute() above has already called rs.next() for us - do { - String optionName = resultSet.getString(1); + // NOTE: do-while because checkedExecute() above has already called rs.next() for us + do { + String optionName = optionsResultSet.getString(1); - pollOptions.add(new PollOptionData(optionName)); - } while (resultSet.next()); + pollOptions.add(new PollOptionData(optionName)); + } while (optionsResultSet.next()); - return new PollData(creatorPublicKey, owner, pollName, description, pollOptions, published); + return new PollData(creatorPublicKey, owner, pollName, description, pollOptions, published); + } } catch (SQLException e) { throw new DataException("Unable to fetch poll from repository", e); } } + @Override public boolean pollExists(String pollName) throws DataException { try { return this.repository.exists("Polls", "poll_name = ?", pollName); @@ -60,6 +64,7 @@ public class HSQLDBVotingRepository implements VotingRepository { } } + @Override public void save(PollData pollData) throws DataException { HSQLDBSaver saveHelper = new HSQLDBSaver("Polls"); @@ -89,6 +94,7 @@ public class HSQLDBVotingRepository implements VotingRepository { } } + @Override public void delete(String pollName) throws DataException { // NOTE: The corresponding rows in PollOptions are deleted automatically by the database thanks to "ON DELETE CASCADE" in the PollOptions' FOREIGN KEY // definition. @@ -101,11 +107,11 @@ public class HSQLDBVotingRepository implements VotingRepository { // Votes + @Override public List getVotes(String pollName) throws DataException { List votes = new ArrayList(); - try { - ResultSet resultSet = this.repository.checkedExecute("SELECT voter, option_index FROM PollVotes WHERE poll_name = ?", pollName); + try (ResultSet resultSet = this.repository.checkedExecute("SELECT voter, option_index FROM PollVotes WHERE poll_name = ?", pollName)) { if (resultSet == null) return votes; @@ -123,10 +129,10 @@ public class HSQLDBVotingRepository implements VotingRepository { } } + @Override public VoteOnPollData getVote(String pollName, byte[] voterPublicKey) throws DataException { - try { - ResultSet resultSet = this.repository.checkedExecute("SELECT option_index FROM PollVotes WHERE poll_name = ? AND voter = ?", pollName, - voterPublicKey); + try (ResultSet resultSet = this.repository.checkedExecute("SELECT option_index FROM PollVotes WHERE poll_name = ? AND voter = ?", pollName, + voterPublicKey)) { if (resultSet == null) return null; @@ -138,6 +144,7 @@ public class HSQLDBVotingRepository implements VotingRepository { } } + @Override public void save(VoteOnPollData voteOnPollData) throws DataException { HSQLDBSaver saveHelper = new HSQLDBSaver("PollVotes"); @@ -151,6 +158,7 @@ public class HSQLDBVotingRepository implements VotingRepository { } } + @Override public void delete(String pollName, byte[] voterPublicKey) throws DataException { try { this.repository.delete("PollVotes", "poll_name = ? AND voter = ?", pollName, voterPublicKey); diff --git a/src/repository/hsqldb/transaction/HSQLDBArbitraryTransactionRepository.java b/src/repository/hsqldb/transaction/HSQLDBArbitraryTransactionRepository.java index f6067e77..eabb1559 100644 --- a/src/repository/hsqldb/transaction/HSQLDBArbitraryTransactionRepository.java +++ b/src/repository/hsqldb/transaction/HSQLDBArbitraryTransactionRepository.java @@ -20,16 +20,15 @@ public class HSQLDBArbitraryTransactionRepository extends HSQLDBTransactionRepos } TransactionData fromBase(byte[] signature, byte[] reference, byte[] creatorPublicKey, long timestamp, BigDecimal fee) throws DataException { - try { - ResultSet rs = this.repository.checkedExecute("SELECT sender, version, service, data_hash from ArbitraryTransactions WHERE signature = ?", - signature); - if (rs == null) + try (ResultSet resultSet = this.repository.checkedExecute("SELECT sender, version, service, data_hash from ArbitraryTransactions WHERE signature = ?", + signature)) { + if (resultSet == null) return null; - byte[] senderPublicKey = rs.getBytes(1); - int version = rs.getInt(2); - int service = rs.getInt(3); - byte[] dataHash = rs.getBytes(4); + byte[] senderPublicKey = resultSet.getBytes(1); + int version = resultSet.getInt(2); + int service = resultSet.getInt(3); + byte[] dataHash = resultSet.getBytes(4); List payments = this.getPaymentsFromSignature(signature); @@ -51,7 +50,8 @@ public class HSQLDBArbitraryTransactionRepository extends HSQLDBTransactionRepos HSQLDBSaver saveHelper = new HSQLDBSaver("ArbitraryTransactions"); saveHelper.bind("signature", arbitraryTransactionData.getSignature()).bind("sender", arbitraryTransactionData.getSenderPublicKey()) - .bind("version", arbitraryTransactionData.getVersion()).bind("service", arbitraryTransactionData.getService()).bind("data_hash", arbitraryTransactionData.getData()); + .bind("version", arbitraryTransactionData.getVersion()).bind("service", arbitraryTransactionData.getService()) + .bind("data_hash", arbitraryTransactionData.getData()); try { saveHelper.execute(this.repository); diff --git a/src/repository/hsqldb/transaction/HSQLDBBuyNameTransactionRepository.java b/src/repository/hsqldb/transaction/HSQLDBBuyNameTransactionRepository.java index 1daef638..e2393d9d 100644 --- a/src/repository/hsqldb/transaction/HSQLDBBuyNameTransactionRepository.java +++ b/src/repository/hsqldb/transaction/HSQLDBBuyNameTransactionRepository.java @@ -17,16 +17,15 @@ public class HSQLDBBuyNameTransactionRepository extends HSQLDBTransactionReposit } TransactionData fromBase(byte[] signature, byte[] reference, byte[] buyerPublicKey, long timestamp, BigDecimal fee) throws DataException { - try { - ResultSet rs = this.repository.checkedExecute("SELECT name, amount, seller, name_reference FROM BuyNameTransactions WHERE signature = ?", - signature); - if (rs == null) + try (ResultSet resultSet = this.repository.checkedExecute("SELECT name, amount, seller, name_reference FROM BuyNameTransactions WHERE signature = ?", + signature)) { + if (resultSet == null) return null; - String name = rs.getString(1); - BigDecimal amount = rs.getBigDecimal(2); - String seller = rs.getString(3); - byte[] nameReference = rs.getBytes(4); + String name = resultSet.getString(1); + BigDecimal amount = resultSet.getBigDecimal(2); + String seller = resultSet.getString(3); + byte[] nameReference = resultSet.getBytes(4); return new BuyNameTransactionData(buyerPublicKey, name, amount, seller, nameReference, fee, timestamp, reference, signature); } catch (SQLException e) { diff --git a/src/repository/hsqldb/transaction/HSQLDBCancelOrderTransactionRepository.java b/src/repository/hsqldb/transaction/HSQLDBCancelOrderTransactionRepository.java index 47f5aa6f..45ffcab2 100644 --- a/src/repository/hsqldb/transaction/HSQLDBCancelOrderTransactionRepository.java +++ b/src/repository/hsqldb/transaction/HSQLDBCancelOrderTransactionRepository.java @@ -17,12 +17,11 @@ public class HSQLDBCancelOrderTransactionRepository extends HSQLDBTransactionRep } TransactionData fromBase(byte[] signature, byte[] reference, byte[] creatorPublicKey, long timestamp, BigDecimal fee) throws DataException { - try { - ResultSet rs = this.repository.checkedExecute("SELECT asset_order_id FROM CancelAssetOrderTransactions WHERE signature = ?", signature); - if (rs == null) + try (ResultSet resultSet = this.repository.checkedExecute("SELECT asset_order_id FROM CancelAssetOrderTransactions WHERE signature = ?", signature)) { + if (resultSet == null) return null; - byte[] assetOrderId = rs.getBytes(1); + byte[] assetOrderId = resultSet.getBytes(1); return new CancelOrderTransactionData(creatorPublicKey, assetOrderId, fee, timestamp, reference, signature); } catch (SQLException e) { diff --git a/src/repository/hsqldb/transaction/HSQLDBCancelSellNameTransactionRepository.java b/src/repository/hsqldb/transaction/HSQLDBCancelSellNameTransactionRepository.java index e8d09223..b54641a2 100644 --- a/src/repository/hsqldb/transaction/HSQLDBCancelSellNameTransactionRepository.java +++ b/src/repository/hsqldb/transaction/HSQLDBCancelSellNameTransactionRepository.java @@ -17,12 +17,11 @@ public class HSQLDBCancelSellNameTransactionRepository extends HSQLDBTransaction } TransactionData fromBase(byte[] signature, byte[] reference, byte[] ownerPublicKey, long timestamp, BigDecimal fee) throws DataException { - try { - ResultSet rs = this.repository.checkedExecute("SELECT name FROM CancelSellNameTransactions WHERE signature = ?", signature); - if (rs == null) + try (ResultSet resultSet = this.repository.checkedExecute("SELECT name FROM CancelSellNameTransactions WHERE signature = ?", signature)) { + if (resultSet == null) return null; - String name = rs.getString(1); + String name = resultSet.getString(1); return new CancelSellNameTransactionData(ownerPublicKey, name, fee, timestamp, reference, signature); } catch (SQLException e) { diff --git a/src/repository/hsqldb/transaction/HSQLDBCreateOrderTransactionRepository.java b/src/repository/hsqldb/transaction/HSQLDBCreateOrderTransactionRepository.java index 952b020f..06bd37d6 100644 --- a/src/repository/hsqldb/transaction/HSQLDBCreateOrderTransactionRepository.java +++ b/src/repository/hsqldb/transaction/HSQLDBCreateOrderTransactionRepository.java @@ -17,16 +17,15 @@ public class HSQLDBCreateOrderTransactionRepository extends HSQLDBTransactionRep } TransactionData fromBase(byte[] signature, byte[] reference, byte[] creatorPublicKey, long timestamp, BigDecimal fee) throws DataException { - try { - ResultSet rs = this.repository - .checkedExecute("SELECT have_asset_id, amount, want_asset_id, price FROM CreateAssetOrderTransactions WHERE signature = ?", signature); - if (rs == null) + try (ResultSet resultSet = this.repository + .checkedExecute("SELECT have_asset_id, amount, want_asset_id, price FROM CreateAssetOrderTransactions WHERE signature = ?", signature)) { + if (resultSet == null) return null; - long haveAssetId = rs.getLong(1); - BigDecimal amount = rs.getBigDecimal(2); - long wantAssetId = rs.getLong(3); - BigDecimal price = rs.getBigDecimal(4); + long haveAssetId = resultSet.getLong(1); + BigDecimal amount = resultSet.getBigDecimal(2); + long wantAssetId = resultSet.getLong(3); + BigDecimal price = resultSet.getBigDecimal(4); return new CreateOrderTransactionData(creatorPublicKey, haveAssetId, wantAssetId, amount, price, fee, timestamp, reference, signature); } catch (SQLException e) { diff --git a/src/repository/hsqldb/transaction/HSQLDBCreatePollTransactionRepository.java b/src/repository/hsqldb/transaction/HSQLDBCreatePollTransactionRepository.java index 7086fe46..08d8dc4e 100644 --- a/src/repository/hsqldb/transaction/HSQLDBCreatePollTransactionRepository.java +++ b/src/repository/hsqldb/transaction/HSQLDBCreatePollTransactionRepository.java @@ -20,30 +20,31 @@ public class HSQLDBCreatePollTransactionRepository extends HSQLDBTransactionRepo } TransactionData fromBase(byte[] signature, byte[] reference, byte[] creatorPublicKey, long timestamp, BigDecimal fee) throws DataException { - try { - ResultSet rs = this.repository.checkedExecute("SELECT owner, poll_name, description FROM CreatePollTransactions WHERE signature = ?", signature); - if (rs == null) + try (ResultSet resultSet = this.repository.checkedExecute("SELECT owner, poll_name, description FROM CreatePollTransactions WHERE signature = ?", + signature)) { + if (resultSet == null) return null; - String owner = rs.getString(1); - String pollName = rs.getString(2); - String description = rs.getString(3); + String owner = resultSet.getString(1); + String pollName = resultSet.getString(2); + String description = resultSet.getString(3); - rs = this.repository.checkedExecute("SELECT option_name FROM CreatePollTransactionOptions where signature = ? ORDER BY option_index ASC", - signature); - if (rs == null) - return null; + try (ResultSet optionsResultSet = this.repository + .checkedExecute("SELECT option_name FROM CreatePollTransactionOptions where signature = ? ORDER BY option_index ASC", signature)) { + if (optionsResultSet == null) + return null; - List pollOptions = new ArrayList(); + List pollOptions = new ArrayList(); - // NOTE: do-while because checkedExecute() above has already called rs.next() for us - do { - String optionName = rs.getString(1); + // NOTE: do-while because checkedExecute() above has already called rs.next() for us + do { + String optionName = optionsResultSet.getString(1); - pollOptions.add(new PollOptionData(optionName)); - } while (rs.next()); + pollOptions.add(new PollOptionData(optionName)); + } while (optionsResultSet.next()); - return new CreatePollTransactionData(creatorPublicKey, owner, pollName, description, pollOptions, fee, timestamp, reference, signature); + return new CreatePollTransactionData(creatorPublicKey, owner, pollName, description, pollOptions, fee, timestamp, reference, signature); + } } catch (SQLException e) { throw new DataException("Unable to fetch create poll transaction from repository", e); } diff --git a/src/repository/hsqldb/transaction/HSQLDBGenesisTransactionRepository.java b/src/repository/hsqldb/transaction/HSQLDBGenesisTransactionRepository.java index 3f5e9e0e..f285f02a 100644 --- a/src/repository/hsqldb/transaction/HSQLDBGenesisTransactionRepository.java +++ b/src/repository/hsqldb/transaction/HSQLDBGenesisTransactionRepository.java @@ -17,13 +17,12 @@ public class HSQLDBGenesisTransactionRepository extends HSQLDBTransactionReposit } TransactionData fromBase(byte[] signature, byte[] reference, byte[] creatorPublicKey, long timestamp, BigDecimal fee) throws DataException { - try { - ResultSet rs = this.repository.checkedExecute("SELECT recipient, amount FROM GenesisTransactions WHERE signature = ?", signature); - if (rs == null) + try (ResultSet resultSet = this.repository.checkedExecute("SELECT recipient, amount FROM GenesisTransactions WHERE signature = ?", signature)) { + if (resultSet == null) return null; - String recipient = rs.getString(1); - BigDecimal amount = rs.getBigDecimal(2).setScale(8); + String recipient = resultSet.getString(1); + BigDecimal amount = resultSet.getBigDecimal(2).setScale(8); return new GenesisTransactionData(recipient, amount, timestamp, signature); } catch (SQLException e) { diff --git a/src/repository/hsqldb/transaction/HSQLDBIssueAssetTransactionRepository.java b/src/repository/hsqldb/transaction/HSQLDBIssueAssetTransactionRepository.java index 90e37f7f..ad417a95 100644 --- a/src/repository/hsqldb/transaction/HSQLDBIssueAssetTransactionRepository.java +++ b/src/repository/hsqldb/transaction/HSQLDBIssueAssetTransactionRepository.java @@ -17,20 +17,18 @@ public class HSQLDBIssueAssetTransactionRepository extends HSQLDBTransactionRepo } TransactionData fromBase(byte[] signature, byte[] reference, byte[] creatorPublicKey, long timestamp, BigDecimal fee) throws DataException { - try { - ResultSet rs = this.repository.checkedExecute( - "SELECT issuer, owner, asset_name, description, quantity, is_divisible, asset_id FROM IssueAssetTransactions WHERE signature = ?", - signature); - if (rs == null) + try (ResultSet resultSet = this.repository.checkedExecute( + "SELECT issuer, owner, asset_name, description, quantity, is_divisible, asset_id FROM IssueAssetTransactions WHERE signature = ?", signature)) { + if (resultSet == null) return null; - byte[] issuerPublicKey = rs.getBytes(1); - String owner = rs.getString(2); - String assetName = rs.getString(3); - String description = rs.getString(4); - long quantity = rs.getLong(5); - boolean isDivisible = rs.getBoolean(6); - Long assetId = rs.getLong(7); + byte[] issuerPublicKey = resultSet.getBytes(1); + String owner = resultSet.getString(2); + String assetName = resultSet.getString(3); + String description = resultSet.getString(4); + long quantity = resultSet.getLong(5); + boolean isDivisible = resultSet.getBoolean(6); + Long assetId = resultSet.getLong(7); return new IssueAssetTransactionData(assetId, issuerPublicKey, owner, assetName, description, quantity, isDivisible, fee, timestamp, reference, signature); diff --git a/src/repository/hsqldb/transaction/HSQLDBMessageTransactionRepository.java b/src/repository/hsqldb/transaction/HSQLDBMessageTransactionRepository.java index aa9ee082..7bfd964b 100644 --- a/src/repository/hsqldb/transaction/HSQLDBMessageTransactionRepository.java +++ b/src/repository/hsqldb/transaction/HSQLDBMessageTransactionRepository.java @@ -17,20 +17,19 @@ public class HSQLDBMessageTransactionRepository extends HSQLDBTransactionReposit } TransactionData fromBase(byte[] signature, byte[] reference, byte[] creatorPublicKey, long timestamp, BigDecimal fee) throws DataException { - try { - ResultSet rs = this.repository.checkedExecute( - "SELECT version, sender, recipient, is_text, is_encrypted, amount, asset_id, data FROM MessageTransactions WHERE signature = ?", signature); - if (rs == null) + try (ResultSet resultSet = this.repository.checkedExecute( + "SELECT version, sender, recipient, is_text, is_encrypted, amount, asset_id, data FROM MessageTransactions WHERE signature = ?", signature)) { + if (resultSet == null) return null; - int version = rs.getInt(1); - byte[] senderPublicKey = rs.getBytes(2); - String recipient = rs.getString(3); - boolean isText = rs.getBoolean(4); - boolean isEncrypted = rs.getBoolean(5); - BigDecimal amount = rs.getBigDecimal(6); - Long assetId = rs.getLong(7); - byte[] data = rs.getBytes(8); + int version = resultSet.getInt(1); + byte[] senderPublicKey = resultSet.getBytes(2); + String recipient = resultSet.getString(3); + boolean isText = resultSet.getBoolean(4); + boolean isEncrypted = resultSet.getBoolean(5); + BigDecimal amount = resultSet.getBigDecimal(6); + Long assetId = resultSet.getLong(7); + byte[] data = resultSet.getBytes(8); return new MessageTransactionData(version, senderPublicKey, recipient, assetId, amount, data, isText, isEncrypted, fee, timestamp, reference, signature); diff --git a/src/repository/hsqldb/transaction/HSQLDBMultiPaymentTransactionRepository.java b/src/repository/hsqldb/transaction/HSQLDBMultiPaymentTransactionRepository.java index 539cda3c..9db37966 100644 --- a/src/repository/hsqldb/transaction/HSQLDBMultiPaymentTransactionRepository.java +++ b/src/repository/hsqldb/transaction/HSQLDBMultiPaymentTransactionRepository.java @@ -19,12 +19,11 @@ public class HSQLDBMultiPaymentTransactionRepository extends HSQLDBTransactionRe } TransactionData fromBase(byte[] signature, byte[] reference, byte[] creatorPublicKey, long timestamp, BigDecimal fee) throws DataException { - try { - ResultSet rs = this.repository.checkedExecute("SELECT sender from MultiPaymentTransactions WHERE signature = ?", signature); - if (rs == null) + try (ResultSet resultSet = this.repository.checkedExecute("SELECT sender from MultiPaymentTransactions WHERE signature = ?", signature)) { + if (resultSet == null) return null; - byte[] senderPublicKey = rs.getBytes(1); + byte[] senderPublicKey = resultSet.getBytes(1); List payments = this.getPaymentsFromSignature(signature); diff --git a/src/repository/hsqldb/transaction/HSQLDBPaymentTransactionRepository.java b/src/repository/hsqldb/transaction/HSQLDBPaymentTransactionRepository.java index e5222a21..059684f8 100644 --- a/src/repository/hsqldb/transaction/HSQLDBPaymentTransactionRepository.java +++ b/src/repository/hsqldb/transaction/HSQLDBPaymentTransactionRepository.java @@ -17,14 +17,13 @@ public class HSQLDBPaymentTransactionRepository extends HSQLDBTransactionReposit } TransactionData fromBase(byte[] signature, byte[] reference, byte[] creatorPublicKey, long timestamp, BigDecimal fee) throws DataException { - try { - ResultSet rs = this.repository.checkedExecute("SELECT sender, recipient, amount FROM PaymentTransactions WHERE signature = ?", signature); - if (rs == null) + try (ResultSet resultSet = this.repository.checkedExecute("SELECT sender, recipient, amount FROM PaymentTransactions WHERE signature = ?", signature)) { + if (resultSet == null) return null; - byte[] senderPublicKey = rs.getBytes(1); - String recipient = rs.getString(2); - BigDecimal amount = rs.getBigDecimal(3); + byte[] senderPublicKey = resultSet.getBytes(1); + String recipient = resultSet.getString(2); + BigDecimal amount = resultSet.getBigDecimal(3); return new PaymentTransactionData(senderPublicKey, recipient, amount, fee, timestamp, reference, signature); } catch (SQLException e) { diff --git a/src/repository/hsqldb/transaction/HSQLDBRegisterNameTransactionRepository.java b/src/repository/hsqldb/transaction/HSQLDBRegisterNameTransactionRepository.java index 3c11b0b3..86c54c9c 100644 --- a/src/repository/hsqldb/transaction/HSQLDBRegisterNameTransactionRepository.java +++ b/src/repository/hsqldb/transaction/HSQLDBRegisterNameTransactionRepository.java @@ -17,14 +17,13 @@ public class HSQLDBRegisterNameTransactionRepository extends HSQLDBTransactionRe } TransactionData fromBase(byte[] signature, byte[] reference, byte[] registrantPublicKey, long timestamp, BigDecimal fee) throws DataException { - try { - ResultSet rs = this.repository.checkedExecute("SELECT owner, name, data FROM RegisterNameTransactions WHERE signature = ?", signature); - if (rs == null) + try (ResultSet resultSet = this.repository.checkedExecute("SELECT owner, name, data FROM RegisterNameTransactions WHERE signature = ?", signature)) { + if (resultSet == null) return null; - String owner = rs.getString(1); - String name = rs.getString(2); - String data = rs.getString(3); + String owner = resultSet.getString(1); + String name = resultSet.getString(2); + String data = resultSet.getString(3); return new RegisterNameTransactionData(registrantPublicKey, owner, name, data, fee, timestamp, reference, signature); } catch (SQLException e) { diff --git a/src/repository/hsqldb/transaction/HSQLDBSellNameTransactionRepository.java b/src/repository/hsqldb/transaction/HSQLDBSellNameTransactionRepository.java index 7669f0da..f6628d47 100644 --- a/src/repository/hsqldb/transaction/HSQLDBSellNameTransactionRepository.java +++ b/src/repository/hsqldb/transaction/HSQLDBSellNameTransactionRepository.java @@ -17,13 +17,12 @@ public class HSQLDBSellNameTransactionRepository extends HSQLDBTransactionReposi } TransactionData fromBase(byte[] signature, byte[] reference, byte[] ownerPublicKey, long timestamp, BigDecimal fee) throws DataException { - try { - ResultSet rs = this.repository.checkedExecute("SELECT name, amount FROM SellNameTransactions WHERE signature = ?", signature); - if (rs == null) + try (ResultSet resultSet = this.repository.checkedExecute("SELECT name, amount FROM SellNameTransactions WHERE signature = ?", signature)) { + if (resultSet == null) return null; - String name = rs.getString(1); - BigDecimal amount = rs.getBigDecimal(2); + String name = resultSet.getString(1); + BigDecimal amount = resultSet.getBigDecimal(2); return new SellNameTransactionData(ownerPublicKey, name, amount, fee, timestamp, reference, signature); } catch (SQLException e) { diff --git a/src/repository/hsqldb/transaction/HSQLDBTransactionRepository.java b/src/repository/hsqldb/transaction/HSQLDBTransactionRepository.java index 29f891f2..f6d7fc59 100644 --- a/src/repository/hsqldb/transaction/HSQLDBTransactionRepository.java +++ b/src/repository/hsqldb/transaction/HSQLDBTransactionRepository.java @@ -5,6 +5,7 @@ import java.sql.ResultSet; import java.sql.SQLException; import java.sql.Timestamp; import java.util.ArrayList; +import java.util.Calendar; import java.util.List; import data.PaymentData; @@ -59,17 +60,18 @@ public class HSQLDBTransactionRepository implements TransactionRepository { protected HSQLDBTransactionRepository() { } + @Override public TransactionData fromSignature(byte[] signature) throws DataException { - try { - ResultSet rs = this.repository.checkedExecute("SELECT type, reference, creator, creation, fee FROM Transactions WHERE signature = ?", signature); - if (rs == null) + try (ResultSet resultSet = this.repository.checkedExecute("SELECT type, reference, creator, creation, fee FROM Transactions WHERE signature = ?", + signature)) { + if (resultSet == null) return null; - TransactionType type = TransactionType.valueOf(rs.getInt(1)); - byte[] reference = rs.getBytes(2); - byte[] creatorPublicKey = rs.getBytes(3); - long timestamp = rs.getTimestamp(4).getTime(); - BigDecimal fee = rs.getBigDecimal(5).setScale(8); + TransactionType type = TransactionType.valueOf(resultSet.getInt(1)); + byte[] reference = resultSet.getBytes(2); + byte[] creatorPublicKey = resultSet.getBytes(3); + long timestamp = resultSet.getTimestamp(4, Calendar.getInstance(HSQLDBRepository.UTC)).getTime(); + BigDecimal fee = resultSet.getBigDecimal(5).setScale(8); return this.fromBase(type, signature, reference, creatorPublicKey, timestamp, fee); } catch (SQLException e) { @@ -77,17 +79,18 @@ public class HSQLDBTransactionRepository implements TransactionRepository { } } + @Override public TransactionData fromReference(byte[] reference) throws DataException { - try { - ResultSet rs = this.repository.checkedExecute("SELECT type, signature, creator, creation, fee FROM Transactions WHERE reference = ?", reference); - if (rs == null) + try (ResultSet resultSet = this.repository.checkedExecute("SELECT type, signature, creator, creation, fee FROM Transactions WHERE reference = ?", + reference)) { + if (resultSet == null) return null; - TransactionType type = TransactionType.valueOf(rs.getInt(1)); - byte[] signature = rs.getBytes(2); - byte[] creatorPublicKey = rs.getBytes(3); - long timestamp = rs.getTimestamp(4).getTime(); - BigDecimal fee = rs.getBigDecimal(5).setScale(8); + TransactionType type = TransactionType.valueOf(resultSet.getInt(1)); + byte[] signature = resultSet.getBytes(2); + byte[] creatorPublicKey = resultSet.getBytes(3); + long timestamp = resultSet.getTimestamp(4, Calendar.getInstance(HSQLDBRepository.UTC)).getTime(); + BigDecimal fee = resultSet.getBigDecimal(5).setScale(8); return this.fromBase(type, signature, reference, creatorPublicKey, timestamp, fee); } catch (SQLException e) { @@ -152,21 +155,21 @@ public class HSQLDBTransactionRepository implements TransactionRepository { } protected List getPaymentsFromSignature(byte[] signature) throws DataException { - try { - ResultSet rs = this.repository.checkedExecute("SELECT recipient, amount, asset_id FROM SharedTransactionPayments WHERE signature = ?", signature); - if (rs == null) + try (ResultSet resultSet = this.repository.checkedExecute("SELECT recipient, amount, asset_id FROM SharedTransactionPayments WHERE signature = ?", + signature)) { + if (resultSet == null) return null; List payments = new ArrayList(); // NOTE: do-while because checkedExecute() above has already called rs.next() for us do { - String recipient = rs.getString(1); - BigDecimal amount = rs.getBigDecimal(2); - long assetId = rs.getLong(3); + String recipient = resultSet.getString(1); + BigDecimal amount = resultSet.getBigDecimal(2); + long assetId = resultSet.getLong(3); payments.add(new PaymentData(recipient, assetId, amount)); - } while (rs.next()); + } while (resultSet.next()); return payments; } catch (SQLException e) { @@ -194,16 +197,15 @@ public class HSQLDBTransactionRepository implements TransactionRepository { if (signature == null) return 0; - // in one go? - try { - ResultSet rs = this.repository.checkedExecute( - "SELECT height from BlockTransactions JOIN Blocks ON Blocks.signature = BlockTransactions.block_signature WHERE transaction_signature = ? LIMIT 1", - signature); + // Fetch height using join via block's transactions + try (ResultSet resultSet = this.repository.checkedExecute( + "SELECT height from BlockTransactions JOIN Blocks ON Blocks.signature = BlockTransactions.block_signature WHERE transaction_signature = ? LIMIT 1", + signature)) { - if (rs == null) + if (resultSet == null) return 0; - return rs.getInt(1); + return resultSet.getInt(1); } catch (SQLException e) { throw new DataException("Unable to fetch transaction's height from repository", e); } @@ -215,12 +217,12 @@ public class HSQLDBTransactionRepository implements TransactionRepository { return null; // Fetch block signature (if any) - try { - ResultSet rs = this.repository.checkedExecute("SELECT block_signature from BlockTransactions WHERE transaction_signature = ? LIMIT 1", signature); - if (rs == null) + try (ResultSet resultSet = this.repository.checkedExecute("SELECT block_signature from BlockTransactions WHERE transaction_signature = ? LIMIT 1", + signature)) { + if (resultSet == null) return null; - byte[] blockSignature = rs.getBytes(1); + byte[] blockSignature = resultSet.getBytes(1); return this.repository.getBlockRepository().fromSignature(blockSignature); } catch (SQLException | DataException e) { diff --git a/src/repository/hsqldb/transaction/HSQLDBTransferAssetTransactionRepository.java b/src/repository/hsqldb/transaction/HSQLDBTransferAssetTransactionRepository.java index 5272155d..6c0224b4 100644 --- a/src/repository/hsqldb/transaction/HSQLDBTransferAssetTransactionRepository.java +++ b/src/repository/hsqldb/transaction/HSQLDBTransferAssetTransactionRepository.java @@ -17,16 +17,15 @@ public class HSQLDBTransferAssetTransactionRepository extends HSQLDBTransactionR } TransactionData fromBase(byte[] signature, byte[] reference, byte[] creatorPublicKey, long timestamp, BigDecimal fee) throws DataException { - try { - ResultSet rs = this.repository.checkedExecute("SELECT sender, recipient, asset_id, amount FROM TransferAssetTransactions WHERE signature = ?", - signature); - if (rs == null) + try (ResultSet resultSet = this.repository + .checkedExecute("SELECT sender, recipient, asset_id, amount FROM TransferAssetTransactions WHERE signature = ?", signature)) { + if (resultSet == null) return null; - byte[] senderPublicKey = rs.getBytes(1); - String recipient = rs.getString(2); - long assetId = rs.getLong(3); - BigDecimal amount = rs.getBigDecimal(4); + byte[] senderPublicKey = resultSet.getBytes(1); + String recipient = resultSet.getString(2); + long assetId = resultSet.getLong(3); + BigDecimal amount = resultSet.getBigDecimal(4); return new TransferAssetTransactionData(senderPublicKey, recipient, amount, assetId, fee, timestamp, reference, signature); } catch (SQLException e) { diff --git a/src/repository/hsqldb/transaction/HSQLDBUpdateNameTransactionRepository.java b/src/repository/hsqldb/transaction/HSQLDBUpdateNameTransactionRepository.java index 11f0cb9f..dfafa804 100644 --- a/src/repository/hsqldb/transaction/HSQLDBUpdateNameTransactionRepository.java +++ b/src/repository/hsqldb/transaction/HSQLDBUpdateNameTransactionRepository.java @@ -17,15 +17,15 @@ public class HSQLDBUpdateNameTransactionRepository extends HSQLDBTransactionRepo } TransactionData fromBase(byte[] signature, byte[] reference, byte[] ownerPublicKey, long timestamp, BigDecimal fee) throws DataException { - try { - ResultSet rs = this.repository.checkedExecute("SELECT new_owner, name, new_data, name_reference FROM UpdateNameTransactions WHERE signature = ?", signature); - if (rs == null) + try (ResultSet resultSet = this.repository + .checkedExecute("SELECT new_owner, name, new_data, name_reference FROM UpdateNameTransactions WHERE signature = ?", signature)) { + if (resultSet == null) return null; - String newOwner = rs.getString(1); - String name = rs.getString(2); - String newData = rs.getString(3); - byte[] nameReference = rs.getBytes(4); + String newOwner = resultSet.getString(1); + String name = resultSet.getString(2); + String newData = resultSet.getString(3); + byte[] nameReference = resultSet.getBytes(4); return new UpdateNameTransactionData(ownerPublicKey, newOwner, name, newData, nameReference, fee, timestamp, reference, signature); } catch (SQLException e) { diff --git a/src/repository/hsqldb/transaction/HSQLDBVoteOnPollTransactionRepository.java b/src/repository/hsqldb/transaction/HSQLDBVoteOnPollTransactionRepository.java index aedf1ffc..baeb5125 100644 --- a/src/repository/hsqldb/transaction/HSQLDBVoteOnPollTransactionRepository.java +++ b/src/repository/hsqldb/transaction/HSQLDBVoteOnPollTransactionRepository.java @@ -17,15 +17,14 @@ public class HSQLDBVoteOnPollTransactionRepository extends HSQLDBTransactionRepo } TransactionData fromBase(byte[] signature, byte[] reference, byte[] creatorPublicKey, long timestamp, BigDecimal fee) throws DataException { - try { - ResultSet rs = this.repository - .checkedExecute("SELECT poll_name, option_index, previous_option_index FROM VoteOnPollTransactions WHERE signature = ?", signature); - if (rs == null) + try (ResultSet resultSet = this.repository + .checkedExecute("SELECT poll_name, option_index, previous_option_index FROM VoteOnPollTransactions WHERE signature = ?", signature)) { + if (resultSet == null) return null; - String pollName = rs.getString(1); - int optionIndex = rs.getInt(2); - Integer previousOptionIndex = rs.getInt(3); + String pollName = resultSet.getString(1); + int optionIndex = resultSet.getInt(2); + Integer previousOptionIndex = resultSet.getInt(3); return new VoteOnPollTransactionData(creatorPublicKey, pollName, optionIndex, previousOptionIndex, fee, timestamp, reference, signature); } catch (SQLException e) { diff --git a/src/test/Common.java b/src/test/Common.java index fc55c4a3..e3734218 100644 --- a/src/test/Common.java +++ b/src/test/Common.java @@ -10,7 +10,8 @@ import repository.hsqldb.HSQLDBRepositoryFactory; public class Common { - public static final String connectionUrl = "jdbc:hsqldb:file:db/test;create=true;close_result=true;sql.strict_exec=true;sql.enforce_names=true;sql.syntax_mys=true;sql.pad_space=false"; + // public static final String connectionUrl = "jdbc:hsqldb:file:db/test;create=true;close_result=true;sql.strict_exec=true;sql.enforce_names=true;sql.syntax_mys=true;sql.pad_space=false"; + public static final String connectionUrl = "jdbc:hsqldb:file:db/test;create=true"; @BeforeClass public static void setRepository() throws DataException { diff --git a/src/test/RepositoryTests.java b/src/test/RepositoryTests.java index 5cc3e3c1..e2df05ec 100644 --- a/src/test/RepositoryTests.java +++ b/src/test/RepositoryTests.java @@ -35,15 +35,16 @@ public class RepositoryTests extends Common { @Test public void testAccessAfterClose() throws DataException { - Repository repository = RepositoryManager.getRepository(); - assertNotNull(repository); + try (Repository repository = RepositoryManager.getRepository()) { + assertNotNull(repository); - repository.close(); + repository.close(); - try { - repository.discardChanges(); - fail(); - } catch (NullPointerException | DataException e) { + try { + repository.discardChanges(); + fail(); + } catch (NullPointerException | DataException e) { + } } } diff --git a/src/transform/PaymentTransformer.java b/src/transform/PaymentTransformer.java index b664cd43..ef3da0a2 100644 --- a/src/transform/PaymentTransformer.java +++ b/src/transform/PaymentTransformer.java @@ -18,17 +18,16 @@ public class PaymentTransformer extends Transformer { // Property lengths private static final int RECIPIENT_LENGTH = ADDRESS_LENGTH; private static final int ASSET_ID_LENGTH = LONG_LENGTH; - private static final int AMOUNT_LENGTH = BIG_DECIMAL_LENGTH; + private static final int AMOUNT_LENGTH = 12; private static final int TOTAL_LENGTH = RECIPIENT_LENGTH + ASSET_ID_LENGTH + AMOUNT_LENGTH; public static PaymentData fromByteBuffer(ByteBuffer byteBuffer) throws TransformationException { - if (byteBuffer.remaining() < TOTAL_LENGTH) - throw new TransformationException("Byte data too short for payment information"); - String recipient = Serialization.deserializeAddress(byteBuffer); + long assetId = byteBuffer.getLong(); - BigDecimal amount = Serialization.deserializeBigDecimal(byteBuffer); + + BigDecimal amount = Serialization.deserializeBigDecimal(byteBuffer, AMOUNT_LENGTH); return new PaymentData(recipient, assetId, amount); } @@ -42,8 +41,10 @@ public class PaymentTransformer extends Transformer { ByteArrayOutputStream bytes = new ByteArrayOutputStream(); Serialization.serializeAddress(bytes, paymentData.getRecipient()); + bytes.write(Longs.toByteArray(paymentData.getAssetId())); - Serialization.serializeBigDecimal(bytes, paymentData.getAmount()); + + Serialization.serializeBigDecimal(bytes, paymentData.getAmount(), AMOUNT_LENGTH); return bytes.toByteArray(); } catch (IOException | ClassCastException e) { diff --git a/src/transform/block/BlockTransformer.java b/src/transform/block/BlockTransformer.java index e4587da5..0542948a 100644 --- a/src/transform/block/BlockTransformer.java +++ b/src/transform/block/BlockTransformer.java @@ -79,10 +79,12 @@ public class BlockTransformer extends Transformer { byteBuffer.get(reference); BigDecimal generatingBalance = BigDecimal.valueOf(byteBuffer.getLong()).setScale(8); + byte[] generatorPublicKey = Serialization.deserializePublicKey(byteBuffer); byte[] transactionsSignature = new byte[TRANSACTIONS_SIGNATURE_LENGTH]; byteBuffer.get(transactionsSignature); + byte[] generatorSignature = new byte[GENERATOR_SIGNATURE_LENGTH]; byteBuffer.get(generatorSignature); @@ -105,13 +107,16 @@ public class BlockTransformer extends Transformer { // Parse transactions now, compared to deferred parsing in Gen1, so we can throw ParseException if need be. List transactions = new ArrayList(); BigDecimal totalFees = BigDecimal.ZERO.setScale(8); + for (int t = 0; t < transactionCount; ++t) { if (byteBuffer.remaining() < TRANSACTION_SIZE_LENGTH) throw new TransformationException("Byte data too short for Block Transaction length"); int transactionLength = byteBuffer.getInt(); + if (byteBuffer.remaining() < transactionLength) throw new TransformationException("Byte data too short for Block Transaction"); + if (transactionLength > Block.MAX_BLOCK_BYTES) throw new TransformationException("Byte data too long for Block Transaction"); @@ -293,7 +298,7 @@ public class BlockTransformer extends Transformer { for (Transaction transaction : block.getTransactions()) { if (!transaction.isSignatureValid()) - return null; + throw new TransformationException("Transaction signature invalid when building block's transactions signature"); bytes.write(transaction.getTransactionData().getSignature()); } diff --git a/src/transform/transaction/ArbitraryTransactionTransformer.java b/src/transform/transaction/ArbitraryTransactionTransformer.java index 1f46dac5..5d408a34 100644 --- a/src/transform/transaction/ArbitraryTransactionTransformer.java +++ b/src/transform/transaction/ArbitraryTransactionTransformer.java @@ -5,6 +5,7 @@ import java.io.IOException; import java.math.BigDecimal; import java.nio.ByteBuffer; import java.util.ArrayList; +import java.util.Arrays; import java.util.List; import org.json.simple.JSONArray; @@ -16,6 +17,7 @@ import com.google.common.primitives.Longs; import data.transaction.TransactionData; import qora.account.PublicKeyAccount; +import qora.block.BlockChain; import qora.transaction.ArbitraryTransaction; import data.PaymentData; import data.transaction.ArbitraryTransactionData; @@ -120,6 +122,34 @@ public class ArbitraryTransactionTransformer extends TransactionTransformer { } } + /** + * In Qora v1, the bytes used for verification are really mangled so we need to test for v1-ness and adjust the bytes accordingly. + * + * @param transactionData + * @return byte[] + * @throws TransformationException + */ + public static byte[] toBytesForSigningImpl(TransactionData transactionData) throws TransformationException { + ArbitraryTransactionData arbitraryTransactionData = (ArbitraryTransactionData) transactionData; + byte[] bytes = TransactionTransformer.toBytesForSigningImpl(transactionData); + + if (arbitraryTransactionData.getVersion() == 1 || transactionData.getTimestamp() >= BlockChain.getArbitraryTransactionV2Timestamp()) + return bytes; + + // Special v1 version + + // In v1, a coding error means that all bytes prior to final payment entry are lost! + // If there are no payments then we can skip mangling + if (arbitraryTransactionData.getPayments().size() == 0) + return bytes; + + // So we're left with: final payment entry, service, data size, data, fee + int v1Length = PaymentTransformer.getDataLength() + SERVICE_LENGTH + DATA_SIZE_LENGTH + arbitraryTransactionData.getData().length + FEE_LENGTH; + int v1Start = bytes.length - v1Length; + + return Arrays.copyOfRange(bytes, v1Start, bytes.length); + } + @SuppressWarnings("unchecked") public static JSONObject toJSON(TransactionData transactionData) throws TransformationException { JSONObject json = TransactionTransformer.getBaseJSON(transactionData); diff --git a/src/transform/transaction/CreatePollTransactionTransformer.java b/src/transform/transaction/CreatePollTransactionTransformer.java index de18d78a..c29ad508 100644 --- a/src/transform/transaction/CreatePollTransactionTransformer.java +++ b/src/transform/transaction/CreatePollTransactionTransformer.java @@ -155,7 +155,7 @@ public class CreatePollTransactionTransformer extends TransactionTransformer { // Special v1 version // Replace transaction type with incorrect Register Name value - System.arraycopy(Ints.toByteArray(TransactionType.REGISTER_NAME.value), 0, bytes, 0, TransactionTransformer.INT_LENGTH); + System.arraycopy(Ints.toByteArray(TransactionType.REGISTER_NAME.value), 0, bytes, 0, INT_LENGTH); System.out.println(HashCode.fromBytes(bytes).toString()); diff --git a/src/transform/transaction/IssueAssetTransactionTransformer.java b/src/transform/transaction/IssueAssetTransactionTransformer.java index dd4bf959..15b873e6 100644 --- a/src/transform/transaction/IssueAssetTransactionTransformer.java +++ b/src/transform/transaction/IssueAssetTransactionTransformer.java @@ -131,8 +131,8 @@ public class IssueAssetTransactionTransformer extends TransactionTransformer { // Special v1 version // Zero duplicate signature/reference - int start = bytes.length - TransactionTransformer.SIGNATURE_LENGTH - TransactionTransformer.BIG_DECIMAL_LENGTH; - int end = start + TransactionTransformer.SIGNATURE_LENGTH; + int start = bytes.length - SIGNATURE_LENGTH - BIG_DECIMAL_LENGTH; + int end = start + SIGNATURE_LENGTH; Arrays.fill(bytes, start, end, (byte) 0); return bytes; diff --git a/src/transform/transaction/MultiPaymentTransactionTransformer.java b/src/transform/transaction/MultiPaymentTransactionTransformer.java index a143ab31..470b1866 100644 --- a/src/transform/transaction/MultiPaymentTransactionTransformer.java +++ b/src/transform/transaction/MultiPaymentTransactionTransformer.java @@ -5,6 +5,7 @@ import java.io.IOException; import java.math.BigDecimal; import java.nio.ByteBuffer; import java.util.ArrayList; +import java.util.Arrays; import java.util.List; import org.json.simple.JSONArray; @@ -16,6 +17,7 @@ import com.google.common.primitives.Longs; import data.transaction.TransactionData; import qora.account.PublicKeyAccount; +import qora.block.BlockChain; import data.PaymentData; import data.transaction.MultiPaymentTransactionData; import transform.PaymentTransformer; @@ -87,6 +89,29 @@ public class MultiPaymentTransactionTransformer extends TransactionTransformer { } } + /** + * In Qora v1, the bytes used for verification are really mangled so we need to test for v1-ness and adjust the bytes accordingly. + * + * @param transactionData + * @return byte[] + * @throws TransformationException + */ + public static byte[] toBytesForSigningImpl(TransactionData transactionData) throws TransformationException { + byte[] bytes = TransactionTransformer.toBytesForSigningImpl(transactionData); + + if (transactionData.getTimestamp() >= BlockChain.getIssueAssetV2Timestamp()) + return bytes; + + // Special v1 version + + // In v1, a coding error means that all bytes prior to final payment entry are lost! + // So we're left with: final payment entry and fee. Signature has already been stripped + int v1Length = PaymentTransformer.getDataLength() + FEE_LENGTH; + int v1Start = bytes.length - v1Length; + + return Arrays.copyOfRange(bytes, v1Start, bytes.length); + } + @SuppressWarnings("unchecked") public static JSONObject toJSON(TransactionData transactionData) throws TransformationException { JSONObject json = TransactionTransformer.getBaseJSON(transactionData); diff --git a/src/v1feeder.java b/src/v1feeder.java index 6c6b0d63..d8c05e45 100644 --- a/src/v1feeder.java +++ b/src/v1feeder.java @@ -36,27 +36,27 @@ public class v1feeder extends Thread { private static final int DEFAULT_PORT = 9084; private static final int MAGIC_LENGTH = 4; - private static final int TYPE_LENGTH = 4; + // private static final int TYPE_LENGTH = 4; private static final int HAS_ID_LENGTH = 1; - private static final int ID_LENGTH = 4; - private static final int DATA_SIZE_LENGTH = 4; + // private static final int ID_LENGTH = 4; + // private static final int DATA_SIZE_LENGTH = 4; private static final int CHECKSUM_LENGTH = 4; private static final int SIGNATURE_LENGTH = 128; private static final byte[] MAINNET_MAGIC = { 0x12, 0x34, 0x56, 0x78 }; - private static final int GET_PEERS_TYPE = 1; - private static final int PEERS_TYPE = 2; + // private static final int GET_PEERS_TYPE = 1; + // private static final int PEERS_TYPE = 2; private static final int HEIGHT_TYPE = 3; private static final int GET_SIGNATURES_TYPE = 4; private static final int SIGNATURES_TYPE = 5; private static final int GET_BLOCK_TYPE = 6; private static final int BLOCK_TYPE = 7; - private static final int TRANSACTION_TYPE = 8; + // private static final int TRANSACTION_TYPE = 8; private static final int PING_TYPE = 9; private static final int VERSION_TYPE = 10; - private static final int FIND_MYSELF_TYPE = 11; + // private static final int FIND_MYSELF_TYPE = 11; private Socket socket; private OutputStream out; @@ -237,7 +237,7 @@ public class v1feeder extends Thread { break; case VERSION_TYPE: - long timestamp = byteBuffer.getLong(); + @SuppressWarnings("unused") long timestamp = byteBuffer.getLong(); int versionLength = byteBuffer.getInt(); byte[] versionBytes = new byte[versionLength]; byteBuffer.get(versionBytes); @@ -299,6 +299,7 @@ public class v1feeder extends Thread { return newBufferEnd; } + @Override public void run() { try { DataInputStream in = new DataInputStream(socket.getInputStream());