From 2c23acfa7402761fa75d2076e77ba925254906ad Mon Sep 17 00:00:00 2001 From: catbref Date: Thu, 14 Jun 2018 09:55:58 +0100 Subject: [PATCH] Fix HSQLDB*Transaction save() methods Added RepositoryManager.closeRepositoryFactory() to allow swapping out of repositories during unit testing Added some more unit tests - all pass! --- src/qora/transaction/GenesisTransaction.java | 41 ++++---- src/qora/transaction/Transaction.java | 3 +- src/repository/RepositoryFactory.java | 2 + src/repository/RepositoryManager.java | 10 +- src/repository/TransactionRepository.java | 2 +- .../hsqldb/HSQLDBRepositoryFactory.java | 21 ++++- ...SQLDBCreateOrderTransactionRepository.java | 2 - .../HSQLDBGenesisTransactionRepository.java | 2 - ...HSQLDBIssueAssetTransactionRepository.java | 2 - .../HSQLDBMessageTransactionRepository.java | 2 - .../HSQLDBPaymentTransactionRepository.java | 2 - .../HSQLDBTransactionRepository.java | 40 ++++++-- src/test/BlockTests.java | 2 +- src/test/Common.java | 3 +- src/test/GenesisTests.java | 93 +++++++++++++++++++ 15 files changed, 183 insertions(+), 44 deletions(-) create mode 100644 src/test/GenesisTests.java diff --git a/src/qora/transaction/GenesisTransaction.java b/src/qora/transaction/GenesisTransaction.java index fab24298..91fabcd8 100644 --- a/src/qora/transaction/GenesisTransaction.java +++ b/src/qora/transaction/GenesisTransaction.java @@ -7,8 +7,11 @@ import com.google.common.primitives.Bytes; import data.transaction.GenesisTransactionData; import data.transaction.TransactionData; +import qora.account.Account; import qora.account.PrivateKeyAccount; +import qora.assets.Asset; import qora.crypto.Crypto; +import repository.DataException; import repository.Repository; import transform.TransformationException; import transform.transaction.TransactionTransformer; @@ -88,31 +91,33 @@ public class GenesisTransaction extends Transaction { } @Override - public void process() { - // TODO - // this.save(); + public void process() throws DataException { + GenesisTransactionData genesisTransactionData = (GenesisTransactionData) this.transactionData; - // Set recipient's balance - // TODO - // this.recipient.setConfirmedBalance(Asset.QORA, this.amount); + // Save this transaction itself + this.repository.getTransactionRepository().save(this.transactionData); - // Set recipient's reference - // TODO - // recipient.setLastReference(this.signature); + // Update recipient's balance + Account recipient = new Account(repository, genesisTransactionData.getRecipient()); + recipient.setConfirmedBalance(Asset.QORA, genesisTransactionData.getAmount()); + + // Set recipient's starting reference + recipient.setLastReference(genesisTransactionData.getSignature()); } @Override - public void orphan() { - // TODO - // this.delete(); + public void orphan() throws DataException { + GenesisTransactionData genesisTransactionData = (GenesisTransactionData) this.transactionData; - // Reset recipient's balance - // TODO - // this.recipient.deleteBalance(Asset.QORA); + // Delete this transaction + this.repository.getTransactionRepository().delete(this.transactionData); - // Set recipient's reference - // TODO - // recipient.setLastReference(null); + // Delete recipient's balance + Account recipient = new Account(repository, genesisTransactionData.getRecipient()); + recipient.deleteBalance(Asset.QORA); + + // Delete recipient's last reference + recipient.setLastReference(null); } } diff --git a/src/qora/transaction/Transaction.java b/src/qora/transaction/Transaction.java index 5f836d2d..43610610 100644 --- a/src/qora/transaction/Transaction.java +++ b/src/qora/transaction/Transaction.java @@ -187,8 +187,9 @@ public abstract class Transaction { * Load encapsulating Block from DB, if any * * @return Block, or null if transaction is not in a Block + * @throws DataException */ - public BlockData getBlock() { + public BlockData getBlock() throws DataException { return this.repository.getTransactionRepository().toBlock(this.transactionData); } diff --git a/src/repository/RepositoryFactory.java b/src/repository/RepositoryFactory.java index d79f4768..22c2da1a 100644 --- a/src/repository/RepositoryFactory.java +++ b/src/repository/RepositoryFactory.java @@ -4,4 +4,6 @@ public interface RepositoryFactory { public Repository getRepository() throws DataException; + public void close() throws DataException; + } diff --git a/src/repository/RepositoryManager.java b/src/repository/RepositoryManager.java index 3b25308b..2ec36d2d 100644 --- a/src/repository/RepositoryManager.java +++ b/src/repository/RepositoryManager.java @@ -2,14 +2,22 @@ package repository; public abstract class RepositoryManager { - private static RepositoryFactory repositoryFactory; + private static RepositoryFactory repositoryFactory = null; public static void setRepositoryFactory(RepositoryFactory newRepositoryFactory) { repositoryFactory = newRepositoryFactory; } public static Repository getRepository() throws DataException { + if (repositoryFactory == null) + throw new DataException("No repository available"); + return repositoryFactory.getRepository(); } + public static void closeRepositoryFactory() throws DataException { + repositoryFactory.close(); + repositoryFactory = null; + } + } diff --git a/src/repository/TransactionRepository.java b/src/repository/TransactionRepository.java index 334d107d..6a75a6a9 100644 --- a/src/repository/TransactionRepository.java +++ b/src/repository/TransactionRepository.java @@ -11,7 +11,7 @@ public interface TransactionRepository { public int getHeight(TransactionData transactionData); - public BlockData toBlock(TransactionData transactionData); + public BlockData toBlock(TransactionData transactionData) throws DataException; public void save(TransactionData transactionData) throws DataException; diff --git a/src/repository/hsqldb/HSQLDBRepositoryFactory.java b/src/repository/hsqldb/HSQLDBRepositoryFactory.java index 16c08a3d..b8c2dc99 100644 --- a/src/repository/hsqldb/HSQLDBRepositoryFactory.java +++ b/src/repository/hsqldb/HSQLDBRepositoryFactory.java @@ -1,6 +1,7 @@ package repository.hsqldb; import java.sql.Connection; +import java.sql.DriverManager; import java.sql.SQLException; import org.hsqldb.jdbc.JDBCPool; @@ -18,11 +19,11 @@ public class HSQLDBRepositoryFactory implements RepositoryFactory { // one-time initialization goes in here this.connectionUrl = connectionUrl; - connectionPool = new JDBCPool(); - connectionPool.setUrl(this.connectionUrl); + this.connectionPool = new JDBCPool(); + this.connectionPool.setUrl(this.connectionUrl); // Perform DB updates? - try (final Connection connection = connectionPool.getConnection()) { + try (final Connection connection = this.connectionPool.getConnection()) { HSQLDBDatabaseUpdates.updateDatabase(connection); } catch (SQLException e) { throw new DataException("Repository initialization error", e); @@ -47,4 +48,18 @@ public class HSQLDBRepositoryFactory implements RepositoryFactory { return connection; } + 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(); + } catch (SQLException e) { + throw new DataException("Error during repository shutdown", e); + } + } + } diff --git a/src/repository/hsqldb/transaction/HSQLDBCreateOrderTransactionRepository.java b/src/repository/hsqldb/transaction/HSQLDBCreateOrderTransactionRepository.java index 8bff6dcb..0c31e94b 100644 --- a/src/repository/hsqldb/transaction/HSQLDBCreateOrderTransactionRepository.java +++ b/src/repository/hsqldb/transaction/HSQLDBCreateOrderTransactionRepository.java @@ -36,8 +36,6 @@ public class HSQLDBCreateOrderTransactionRepository extends HSQLDBTransactionRep @Override public void save(TransactionData transactionData) throws DataException { - super.save(transactionData); - CreateOrderTransactionData createOrderTransactionData = (CreateOrderTransactionData) transactionData; HSQLDBSaver saveHelper = new HSQLDBSaver("CreateAssetOrderTransactions"); diff --git a/src/repository/hsqldb/transaction/HSQLDBGenesisTransactionRepository.java b/src/repository/hsqldb/transaction/HSQLDBGenesisTransactionRepository.java index d5a22621..3f5e9e0e 100644 --- a/src/repository/hsqldb/transaction/HSQLDBGenesisTransactionRepository.java +++ b/src/repository/hsqldb/transaction/HSQLDBGenesisTransactionRepository.java @@ -33,8 +33,6 @@ public class HSQLDBGenesisTransactionRepository extends HSQLDBTransactionReposit @Override public void save(TransactionData transactionData) throws DataException { - super.save(transactionData); - GenesisTransactionData genesisTransactionData = (GenesisTransactionData) transactionData; HSQLDBSaver saveHelper = new HSQLDBSaver("GenesisTransactions"); diff --git a/src/repository/hsqldb/transaction/HSQLDBIssueAssetTransactionRepository.java b/src/repository/hsqldb/transaction/HSQLDBIssueAssetTransactionRepository.java index b39918ad..a0ecee3f 100644 --- a/src/repository/hsqldb/transaction/HSQLDBIssueAssetTransactionRepository.java +++ b/src/repository/hsqldb/transaction/HSQLDBIssueAssetTransactionRepository.java @@ -41,8 +41,6 @@ public class HSQLDBIssueAssetTransactionRepository extends HSQLDBTransactionRepo @Override public void save(TransactionData transactionData) throws DataException { - super.save(transactionData); - IssueAssetTransactionData issueAssetTransactionData = (IssueAssetTransactionData) transactionData; HSQLDBSaver saveHelper = new HSQLDBSaver("IssueAssetTransactions"); diff --git a/src/repository/hsqldb/transaction/HSQLDBMessageTransactionRepository.java b/src/repository/hsqldb/transaction/HSQLDBMessageTransactionRepository.java index 218305cc..f67c4301 100644 --- a/src/repository/hsqldb/transaction/HSQLDBMessageTransactionRepository.java +++ b/src/repository/hsqldb/transaction/HSQLDBMessageTransactionRepository.java @@ -41,8 +41,6 @@ public class HSQLDBMessageTransactionRepository extends HSQLDBTransactionReposit @Override public void save(TransactionData transactionData) throws DataException { - super.save(transactionData); - MessageTransactionData messageTransactionData = (MessageTransactionData) transactionData; HSQLDBSaver saveHelper = new HSQLDBSaver("MessageTransactions"); diff --git a/src/repository/hsqldb/transaction/HSQLDBPaymentTransactionRepository.java b/src/repository/hsqldb/transaction/HSQLDBPaymentTransactionRepository.java index fc36f5a0..d6b787b8 100644 --- a/src/repository/hsqldb/transaction/HSQLDBPaymentTransactionRepository.java +++ b/src/repository/hsqldb/transaction/HSQLDBPaymentTransactionRepository.java @@ -34,8 +34,6 @@ public class HSQLDBPaymentTransactionRepository extends HSQLDBTransactionReposit @Override public void save(TransactionData transactionData) throws DataException { - super.save(transactionData); - PaymentTransactionData paymentTransactionData = (PaymentTransactionData) transactionData; HSQLDBSaver saveHelper = new HSQLDBSaver("PaymentTransactions"); diff --git a/src/repository/hsqldb/transaction/HSQLDBTransactionRepository.java b/src/repository/hsqldb/transaction/HSQLDBTransactionRepository.java index f6ebef3d..d1dd702a 100644 --- a/src/repository/hsqldb/transaction/HSQLDBTransactionRepository.java +++ b/src/repository/hsqldb/transaction/HSQLDBTransactionRepository.java @@ -42,11 +42,11 @@ public class HSQLDBTransactionRepository implements TransactionRepository { TransactionType type = TransactionType.valueOf(rs.getInt(1)); byte[] reference = this.repository.getResultSetBytes(rs.getBinaryStream(2)); - byte[] creator = this.repository.getResultSetBytes(rs.getBinaryStream(3)); + byte[] creatorPublicKey = this.repository.getResultSetBytes(rs.getBinaryStream(3)); long timestamp = rs.getTimestamp(4).getTime(); BigDecimal fee = rs.getBigDecimal(5).setScale(8); - return this.fromBase(type, signature, reference, creator, timestamp, fee); + return this.fromBase(type, signature, reference, creatorPublicKey, timestamp, fee); } catch (SQLException e) { throw new DataException("Unable to fetch transaction from repository", e); } @@ -60,11 +60,11 @@ public class HSQLDBTransactionRepository implements TransactionRepository { TransactionType type = TransactionType.valueOf(rs.getInt(1)); byte[] signature = this.repository.getResultSetBytes(rs.getBinaryStream(2)); - byte[] creator = this.repository.getResultSetBytes(rs.getBinaryStream(3)); + byte[] creatorPublicKey = this.repository.getResultSetBytes(rs.getBinaryStream(3)); long timestamp = rs.getTimestamp(4).getTime(); BigDecimal fee = rs.getBigDecimal(5).setScale(8); - return this.fromBase(type, signature, reference, creator, timestamp, fee); + return this.fromBase(type, signature, reference, creatorPublicKey, timestamp, fee); } catch (SQLException e) { throw new DataException("Unable to fetch transaction from repository", e); } @@ -115,7 +115,7 @@ public class HSQLDBTransactionRepository implements TransactionRepository { } @Override - public BlockData toBlock(TransactionData transactionData) { + public BlockData toBlock(TransactionData transactionData) throws DataException { byte[] signature = transactionData.getSignature(); if (signature == null) return null; @@ -130,7 +130,7 @@ public class HSQLDBTransactionRepository implements TransactionRepository { return this.repository.getBlockRepository().fromSignature(blockSignature); } catch (SQLException | DataException e) { - return null; + throw new DataException("Unable to fetch transaction's block from repository", e); } } @@ -145,6 +145,32 @@ public class HSQLDBTransactionRepository implements TransactionRepository { } catch (SQLException e) { throw new DataException(e); } + + // Now call transaction-type-specific save() method + switch (transactionData.getType()) { + case GENESIS: + this.genesisTransactionRepository.save(transactionData); + break; + + case PAYMENT: + this.paymentTransactionRepository.save(transactionData); + break; + + case ISSUE_ASSET: + this.issueAssetTransactionRepository.save(transactionData); + break; + + case CREATE_ASSET_ORDER: + this.createOrderTransactionRepository.save(transactionData); + break; + + case MESSAGE: + this.messageTransactionRepository.save(transactionData); + break; + + default: + throw new DataException("Unsupported transaction type during save into repository"); + } } @Override @@ -154,7 +180,7 @@ public class HSQLDBTransactionRepository implements TransactionRepository { try { this.repository.checkedExecute("DELETE FROM Transactions WHERE signature = ?", transactionData.getSignature()); } catch (SQLException e) { - throw new DataException(e); + throw new DataException("Unable to delete transaction from repository", e); } } diff --git a/src/test/BlockTests.java b/src/test/BlockTests.java index 149ebfde..b0485c3a 100644 --- a/src/test/BlockTests.java +++ b/src/test/BlockTests.java @@ -28,7 +28,7 @@ public class BlockTests extends Common { assertNotNull(block); assertTrue(block.isSignatureValid()); // only true if blockchain is empty - // assertTrue(block.isValid(connection)); + // assertTrue(block.isValid()); List transactions = block.getTransactions(); assertNotNull(transactions); diff --git a/src/test/Common.java b/src/test/Common.java index e0fe9cec..e7d44d2a 100644 --- a/src/test/Common.java +++ b/src/test/Common.java @@ -15,13 +15,12 @@ public class Common { @BeforeClass public static void setRepository() throws DataException { RepositoryFactory repositoryFactory = new HSQLDBRepositoryFactory(connectionUrl); - RepositoryManager.setRepositoryFactory(repositoryFactory); } @AfterClass public static void closeRepository() throws DataException { - // Currently a no-op? + RepositoryManager.closeRepositoryFactory(); } } diff --git a/src/test/GenesisTests.java b/src/test/GenesisTests.java new file mode 100644 index 00000000..67758300 --- /dev/null +++ b/src/test/GenesisTests.java @@ -0,0 +1,93 @@ +package test; + +import static org.junit.Assert.*; + +import java.math.BigDecimal; +import java.util.List; + +import org.junit.AfterClass; +import org.junit.BeforeClass; +import org.junit.Test; + +import data.transaction.TransactionData; +import qora.account.Account; +import qora.assets.Asset; +import qora.block.GenesisBlock; +import qora.transaction.Transaction; +import repository.DataException; +import repository.Repository; +import repository.RepositoryFactory; +import repository.RepositoryManager; +import repository.hsqldb.HSQLDBRepositoryFactory; + +// Don't extend Common as we want an in-memory database +public class GenesisTests { + + public static final String connectionUrl = "jdbc:hsqldb:mem:db/test;create=true;close_result=true;sql.strict_exec=true;sql.enforce_names=true;sql.syntax_mys=true"; + + @BeforeClass + public static void setRepository() throws DataException { + RepositoryFactory repositoryFactory = new HSQLDBRepositoryFactory(connectionUrl); + RepositoryManager.setRepositoryFactory(repositoryFactory); + } + + @AfterClass + public static void closeRepository() throws DataException { + RepositoryManager.closeRepositoryFactory(); + } + + @Test + public void testGenesisBlockTransactions() throws DataException { + try (final Repository repository = RepositoryManager.getRepository()) { + assertEquals("Blockchain should be empty for this test", 0, repository.getBlockRepository().getBlockchainHeight()); + + GenesisBlock block = new GenesisBlock(repository); + + assertNotNull(block); + assertTrue(block.isSignatureValid()); + // Note: only true if blockchain is empty + assertTrue(block.isValid()); + + List transactions = block.getTransactions(); + assertNotNull(transactions); + + for (Transaction transaction : transactions) { + assertNotNull(transaction); + + TransactionData transactionData = transaction.getTransactionData(); + + assertEquals(Transaction.TransactionType.GENESIS, transactionData.getType()); + assertTrue(transactionData.getFee().compareTo(BigDecimal.ZERO) == 0); + assertNull(transactionData.getReference()); + assertNotNull(transactionData.getSignature()); + assertTrue(transaction.isSignatureValid()); + assertEquals(Transaction.ValidationResult.OK, transaction.isValid()); + } + + // Actually try to process genesis block onto empty blockchain + block.process(); + repository.saveChanges(); + + // Attempt to load first transaction directly from database + TransactionData transactionData = repository.getTransactionRepository().fromSignature(transactions.get(0).getTransactionData().getSignature()); + assertNotNull(transactionData); + + assertEquals(Transaction.TransactionType.GENESIS, transactionData.getType()); + assertTrue(transactionData.getFee().compareTo(BigDecimal.ZERO) == 0); + assertNull(transactionData.getReference()); + + Transaction transaction = Transaction.fromData(repository, transactionData); + assertNotNull(transaction); + + assertTrue(transaction.isSignatureValid()); + assertEquals(Transaction.ValidationResult.OK, transaction.isValid()); + + // Check known balance + Account testAccount = new Account(repository, "QegT2Ws5YjLQzEZ9YMzWsAZMBE8cAygHZN"); + BigDecimal testBalance = testAccount.getConfirmedBalance(Asset.QORA); + BigDecimal expectedBalance = new BigDecimal("12606834").setScale(8); + assertTrue(testBalance.compareTo(expectedBalance) == 0); + } + } + +}