Fix HSQLDB*Transaction save() methods

Added RepositoryManager.closeRepositoryFactory() to allow swapping out of repositories during unit testing

Added some more unit tests - all pass!
This commit is contained in:
catbref 2018-06-14 09:55:58 +01:00
parent 4c18c7c5bc
commit 2c23acfa74
15 changed files with 183 additions and 44 deletions

View File

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

View File

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

View File

@ -4,4 +4,6 @@ public interface RepositoryFactory {
public Repository getRepository() throws DataException;
public void close() throws DataException;
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -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<Transaction> transactions = block.getTransactions();
assertNotNull(transactions);

View File

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

View File

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