diff --git a/src/database/DB.java b/src/database/DB.java
index ca2ffdf7..6c5ba0fd 100644
--- a/src/database/DB.java
+++ b/src/database/DB.java
@@ -10,6 +10,8 @@ import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.Arrays;
+import org.hsqldb.jdbc.JDBCPool;
+
import com.google.common.primitives.Bytes;
/**
@@ -18,6 +20,33 @@ import com.google.common.primitives.Bytes;
*/
public class DB {
+ private static JDBCPool connectionPool;
+ private static String connectionUrl;
+
+ public static void open() throws SQLException {
+ connectionPool = new JDBCPool();
+ connectionPool.setUrl(connectionUrl);
+ }
+
+ public static void setUrl(String url) {
+ connectionUrl = url;
+ }
+
+ /**
+ * Return an on-demand Connection from connection pool.
+ *
+ * Mostly used in database-read scenarios whereas database-write scenarios, especially multi-statement transactions, are likely to pass around a Connection
+ * object.
+ *
+ * By default HSQLDB will wait up to 30 seconds for a pooled connection to become free.
+ *
+ * @return Connection
+ * @throws SQLException
+ */
+ public static Connection getConnection() throws SQLException {
+ return connectionPool.getConnection();
+ }
+
public static void startTransaction(Connection c) throws SQLException {
c.prepareStatement("START TRANSACTION").execute();
}
@@ -30,6 +59,11 @@ public class DB {
c.prepareStatement("ROLLBACK").execute();
}
+ public static void close() throws SQLException {
+ getConnection().createStatement().execute("SHUTDOWN");
+ connectionPool.close(0);
+ }
+
/**
* Convert InputStream, from ResultSet.getBinaryStream(), into byte[] of set length.
*
@@ -71,6 +105,9 @@ public class DB {
try {
byte[] buffer = new byte[BYTE_BUFFER_LENGTH];
int length = inputStream.read(buffer);
+ if (length == -1)
+ break;
+
result = Bytes.concat(result, Arrays.copyOf(buffer, length));
} catch (IOException e) {
// No more bytes
@@ -117,6 +154,8 @@ public class DB {
* Note that each object is bound to two place-holders based on this SQL syntax:
*
* INSERT INTO table (column, ...) VALUES (?, ...) ON DUPLICATE KEY UPDATE column=?, ...
+ *
+ * Requires that mySQL SQL syntax support is enabled during connection.
*
* @param preparedStatement
* @param objects
@@ -145,27 +184,28 @@ public class DB {
*
* Typically used to fetch Blocks or Transactions using signature or reference.
*
- * @param connection
* @param sql
* @param bytes
* @return ResultSet, or null if no matching rows found
* @throws SQLException
*/
- public static ResultSet executeUsingBytes(Connection connection, String sql, byte[] bytes) throws SQLException {
- PreparedStatement preparedStatement = connection.prepareStatement(sql);
- preparedStatement.setBinaryStream(1, new ByteArrayInputStream(bytes));
+ public static ResultSet executeUsingBytes(String sql, byte[] bytes) throws SQLException {
+ try (final Connection connection = DB.getConnection()) {
+ PreparedStatement preparedStatement = connection.prepareStatement(sql);
+ preparedStatement.setBinaryStream(1, new ByteArrayInputStream(bytes));
- if (!preparedStatement.execute())
- throw new SQLException("Fetching from database produced no results");
+ if (!preparedStatement.execute())
+ throw new SQLException("Fetching from database produced no results");
- ResultSet rs = preparedStatement.getResultSet();
- if (rs == null)
- throw new SQLException("Fetching results from database produced no ResultSet");
+ ResultSet rs = preparedStatement.getResultSet();
+ if (rs == null)
+ throw new SQLException("Fetching results from database produced no ResultSet");
- if (!rs.next())
- return null;
+ if (!rs.next())
+ return null;
- return rs;
+ return rs;
+ }
}
/**
diff --git a/src/qora/block/Block.java b/src/qora/block/Block.java
index 3230f63f..1877fc6e 100644
--- a/src/qora/block/Block.java
+++ b/src/qora/block/Block.java
@@ -1,16 +1,20 @@
package qora.block;
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
import java.math.BigDecimal;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.ArrayList;
+import java.util.Arrays;
import java.util.List;
import org.json.simple.JSONObject;
import com.google.common.primitives.Bytes;
+import com.google.common.primitives.Longs;
import database.DB;
import database.NoDataFoundException;
@@ -89,6 +93,9 @@ public class Block {
protected static final int AT_LENGTH = AT_FEES_LENGTH + AT_BYTES_LENGTH;
// Constructors
+
+ // For creating a new block from scratch or instantiating one that was previously serialized
+ // XXX shouldn't transactionsSignature be passed in here?
protected Block(int version, byte[] reference, long timestamp, BigDecimal generationTarget, PublicKeyAccount generator, byte[] generationSignature,
byte[] atBytes, BigDecimal atFees) {
this.version = version;
@@ -100,7 +107,7 @@ public class Block {
this.height = 0;
this.transactionCount = 0;
- this.transactions = null;
+ this.transactions = new ArrayList();
this.transactionsSignature = null;
this.totalFees = null;
@@ -183,20 +190,15 @@ public class Block {
return blockLength;
}
- public List getTransactions() {
- return this.transactions;
- }
-
/**
- * Return block's transactions from DB (or cache).
+ * Return block's transactions.
*
- * If block's transactions have already been loaded from DB then the cached copied is returned instead.
+ * If the block was loaded from DB then it's possible this method will call the DB to load the transactions if they are not already loaded.
*
- * @param connection
* @return
* @throws SQLException
*/
- public List getTransactions(Connection connection) throws SQLException {
+ public List getTransactions() throws SQLException {
// Already loaded?
if (this.transactions != null)
return this.transactions;
@@ -204,16 +206,14 @@ public class Block {
// Allocate cache for results
this.transactions = new ArrayList();
- // Load from DB
- ResultSet rs = DB.executeUsingBytes(connection, "SELECT transaction_signature FROM BlockTransactions WHERE block_signature = ?", this.getSignature());
+ ResultSet rs = DB.executeUsingBytes("SELECT transaction_signature FROM BlockTransactions WHERE block_signature = ?", this.getSignature());
if (rs == null)
return this.transactions; // No transactions in this block
- // Use each row's signature to load, and cache, Transactions
// NB: do-while loop because DB.executeUsingBytes() implicitly calls ResultSet.next() for us
do {
byte[] transactionSignature = DB.getResultSetBytes(rs.getBinaryStream(1), Transaction.SIGNATURE_LENGTH);
- this.transactions.add(TransactionFactory.fromSignature(connection, transactionSignature));
+ this.transactions.add(TransactionFactory.fromSignature(transactionSignature));
} while (rs.next());
return this.transactions;
@@ -221,8 +221,8 @@ public class Block {
// Load/Save
- protected Block(Connection connection, byte[] signature) throws SQLException {
- this(DB.executeUsingBytes(connection, "SELECT " + DB_COLUMNS + " FROM Blocks WHERE signature = ?", signature));
+ protected Block(byte[] signature) throws SQLException {
+ this(DB.executeUsingBytes("SELECT " + DB_COLUMNS + " FROM Blocks WHERE signature = ?", signature));
}
protected Block(ResultSet rs) throws SQLException {
@@ -246,14 +246,13 @@ public class Block {
/**
* Load Block from DB using block signature.
*
- * @param connection
* @param signature
* @return Block, or null if not found
* @throws SQLException
*/
- public static Block fromSignature(Connection connection, byte[] signature) throws SQLException {
+ public static Block fromSignature(byte[] signature) throws SQLException {
try {
- return new Block(connection, signature);
+ return new Block(signature);
} catch (NoDataFoundException e) {
return null;
}
@@ -262,19 +261,20 @@ public class Block {
/**
* Load Block from DB using block height
*
- * @param connection
* @param height
* @return Block, or null if not found
* @throws SQLException
*/
- public static Block fromHeight(Connection connection, int height) throws SQLException {
- PreparedStatement preparedStatement = connection.prepareStatement("SELECT signature FROM Blocks WHERE height = ?");
- preparedStatement.setInt(1, height);
+ public static Block fromHeight(int height) throws SQLException {
+ try (final Connection connection = DB.getConnection()) {
+ PreparedStatement preparedStatement = connection.prepareStatement("SELECT " + DB_COLUMNS + " FROM Blocks WHERE height = ?");
+ preparedStatement.setInt(1, height);
- try {
- return new Block(DB.checkedExecute(preparedStatement));
- } catch (NoDataFoundException e) {
- return null;
+ try {
+ return new Block(DB.checkedExecute(preparedStatement));
+ } catch (NoDataFoundException e) {
+ return null;
+ }
}
}
@@ -295,13 +295,12 @@ public class Block {
/**
* Load parent Block from DB
*
- * @param connection
* @return Block, or null if not found
* @throws SQLException
*/
- public Block getParent(Connection connection) throws SQLException {
+ public Block getParent() throws SQLException {
try {
- return new Block(connection, this.reference);
+ return new Block(this.reference);
} catch (NoDataFoundException e) {
return null;
}
@@ -310,16 +309,15 @@ public class Block {
/**
* Load child Block from DB
*
- * @param connection
* @return Block, or null if not found
* @throws SQLException
*/
- public Block getChild(Connection connection) throws SQLException {
+ public Block getChild() throws SQLException {
byte[] blockSignature = this.getSignature();
if (blockSignature == null)
return null;
- ResultSet resultSet = DB.executeUsingBytes(connection, "SELECT " + DB_COLUMNS + " FROM Blocks WHERE reference = ?", blockSignature);
+ ResultSet resultSet = DB.executeUsingBytes("SELECT " + DB_COLUMNS + " FROM Blocks WHERE reference = ?", blockSignature);
try {
return new Block(resultSet);
@@ -356,9 +354,44 @@ public class Block {
return null;
}
- public boolean isSignatureValid(PublicKeyAccount signer) {
- // TODO
- return false;
+ private byte[] getBytesForSignature() {
+ try {
+ ByteArrayOutputStream bytes = new ByteArrayOutputStream(REFERENCE_LENGTH + GENERATION_TARGET_LENGTH + GENERATOR_LENGTH);
+ // Only copy the generator signature from reference, which is the first 64 bytes.
+ bytes.write(Arrays.copyOf(this.reference, GENERATION_SIGNATURE_LENGTH));
+ bytes.write(Longs.toByteArray(this.generationTarget.longValue()));
+ // We're padding here just in case the generator is the genesis account whose public key is only 8 bytes long.
+ bytes.write(Bytes.ensureCapacity(this.generator.getPublicKey(), GENERATOR_LENGTH, 0));
+ return bytes.toByteArray();
+ } catch (IOException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ public boolean isSignatureValid() {
+ // Check generator's signature first
+ if (!this.generator.verify(this.generationSignature, getBytesForSignature()))
+ return false;
+
+ // Check transactions signature
+ ByteArrayOutputStream bytes = new ByteArrayOutputStream(GENERATION_SIGNATURE_LENGTH + this.transactionCount * Transaction.SIGNATURE_LENGTH);
+ try {
+ bytes.write(this.generationSignature);
+
+ for (Transaction transaction : this.getTransactions()) {
+ if (!transaction.isSignatureValid())
+ return false;
+
+ bytes.write(transaction.getSignature());
+ }
+ } catch (IOException | SQLException e) {
+ throw new RuntimeException(e);
+ }
+
+ if (!this.generator.verify(this.transactionsSignature, bytes.toByteArray()))
+ return false;
+
+ return true;
}
public boolean isValid(Connection connection) throws SQLException {
diff --git a/src/qora/block/BlockChain.java b/src/qora/block/BlockChain.java
index d0810409..3a1cc3b6 100644
--- a/src/qora/block/BlockChain.java
+++ b/src/qora/block/BlockChain.java
@@ -15,13 +15,12 @@ public class BlockChain {
/**
* Return block height from DB using signature.
*
- * @param connection
* @param signature
* @return height, or 0 if block not found.
* @throws SQLException
*/
- public static int getBlockHeightFromSignature(Connection connection, byte[] signature) throws SQLException {
- ResultSet rs = DB.executeUsingBytes(connection, "SELECT height FROM Blocks WHERE signature = ?", signature);
+ public static int getBlockHeightFromSignature(byte[] signature) throws SQLException {
+ ResultSet rs = DB.executeUsingBytes("SELECT height FROM Blocks WHERE signature = ?", signature);
if (rs == null)
return 0;
@@ -31,16 +30,17 @@ public class BlockChain {
/**
* Return highest block height from DB.
*
- * @param connection
* @return height, or 0 if there are no blocks in DB (not very likely).
* @throws SQLException
*/
- public static int getMaxHeight(Connection connection) throws SQLException {
- ResultSet rs = DB.checkedExecute(connection.prepareStatement("SELECT MAX(height) FROM Blocks"));
- if (rs == null)
- return 0;
+ public static int getMaxHeight() throws SQLException {
+ try (final Connection connection = DB.getConnection()) {
+ ResultSet rs = DB.checkedExecute(connection.prepareStatement("SELECT MAX(height) FROM Blocks"));
+ if (rs == null)
+ return 0;
- return rs.getInt(1);
+ return rs.getInt(1);
+ }
}
}
diff --git a/src/qora/block/BlockFactory.java b/src/qora/block/BlockFactory.java
index 07f37c44..69716a5a 100644
--- a/src/qora/block/BlockFactory.java
+++ b/src/qora/block/BlockFactory.java
@@ -12,13 +12,12 @@ public class BlockFactory {
/**
* Load Block from DB using block signature.
*
- * @param connection
* @param signature
* @return ? extends Block, or null if not found
* @throws SQLException
*/
- public static Block fromSignature(Connection connection, byte[] signature) throws SQLException {
- Block block = Block.fromSignature(connection, signature);
+ public static Block fromSignature(byte[] signature) throws SQLException {
+ Block block = Block.fromSignature(signature);
if (block == null)
return null;
@@ -33,22 +32,23 @@ public class BlockFactory {
/**
* Load Block from DB using block height
*
- * @param connection
* @param height
* @return ? extends Block, or null if not found
* @throws SQLException
*/
- public static Block fromHeight(Connection connection, int height) throws SQLException {
+ public static Block fromHeight(int height) throws SQLException {
if (height == 1)
return GenesisBlock.getInstance();
- PreparedStatement preparedStatement = connection.prepareStatement("SELECT signature FROM Blocks WHERE height = ?");
- preparedStatement.setInt(1, height);
+ try (final Connection connection = DB.getConnection()) {
+ PreparedStatement preparedStatement = connection.prepareStatement("SELECT signature FROM Blocks WHERE height = ?");
+ preparedStatement.setInt(1, height);
- try {
- return new Block(DB.checkedExecute(preparedStatement));
- } catch (NoDataFoundException e) {
- return null;
+ try {
+ return new Block(DB.checkedExecute(preparedStatement));
+ } catch (NoDataFoundException e) {
+ return null;
+ }
}
}
diff --git a/src/qora/block/BlockTransaction.java b/src/qora/block/BlockTransaction.java
index f632ceab..f8a4f613 100644
--- a/src/qora/block/BlockTransaction.java
+++ b/src/qora/block/BlockTransaction.java
@@ -47,25 +47,26 @@ public class BlockTransaction {
// Load/Save
- protected BlockTransaction(Connection connection, byte[] blockSignature, int sequence) throws SQLException {
- // Can't use DB.executeUsingBytes() here as we need two placeholders
- PreparedStatement preparedStatement = connection
- .prepareStatement("SELECT transaction_signature FROM BlockTransactions WHERE block_signature = ? and sequence = ?");
- preparedStatement.setBinaryStream(1, new ByteArrayInputStream(blockSignature));
- preparedStatement.setInt(2, sequence);
+ protected BlockTransaction(byte[] blockSignature, int sequence) throws SQLException {
+ try (final Connection connection = DB.getConnection()) {
+ // Can't use DB.executeUsingBytes() here as we need two placeholders
+ PreparedStatement preparedStatement = connection
+ .prepareStatement("SELECT transaction_signature FROM BlockTransactions WHERE block_signature = ? and sequence = ?");
+ preparedStatement.setBinaryStream(1, new ByteArrayInputStream(blockSignature));
+ preparedStatement.setInt(2, sequence);
- ResultSet rs = DB.checkedExecute(preparedStatement);
- if (rs == null)
- throw new NoDataFoundException();
+ ResultSet rs = DB.checkedExecute(preparedStatement);
+ if (rs == null)
+ throw new NoDataFoundException();
- this.blockSignature = blockSignature;
- this.sequence = sequence;
- this.transactionSignature = DB.getResultSetBytes(rs.getBinaryStream(1), Transaction.SIGNATURE_LENGTH);
+ this.blockSignature = blockSignature;
+ this.sequence = sequence;
+ this.transactionSignature = DB.getResultSetBytes(rs.getBinaryStream(1), Transaction.SIGNATURE_LENGTH);
+ }
}
- protected BlockTransaction(Connection connection, byte[] transactionSignature) throws SQLException {
- ResultSet rs = DB.executeUsingBytes(connection, "SELECT block_signature, sequence FROM BlockTransactions WHERE transaction_signature = ?",
- transactionSignature);
+ protected BlockTransaction(byte[] transactionSignature) throws SQLException {
+ ResultSet rs = DB.executeUsingBytes("SELECT block_signature, sequence FROM BlockTransactions WHERE transaction_signature = ?", transactionSignature);
if (rs == null)
throw new NoDataFoundException();
@@ -77,15 +78,14 @@ public class BlockTransaction {
/**
* Load BlockTransaction from DB using block signature and tx-in-block sequence.
*
- * @param connection
* @param blockSignature
* @param sequence
* @return BlockTransaction, or null if not found
* @throws SQLException
*/
- public static BlockTransaction fromBlockSignature(Connection connection, byte[] blockSignature, int sequence) throws SQLException {
+ public static BlockTransaction fromBlockSignature(byte[] blockSignature, int sequence) throws SQLException {
try {
- return new BlockTransaction(connection, blockSignature, sequence);
+ return new BlockTransaction(blockSignature, sequence);
} catch (NoDataFoundException e) {
return null;
}
@@ -94,14 +94,13 @@ public class BlockTransaction {
/**
* Load BlockTransaction from DB using transaction signature.
*
- * @param connection
* @param transactionSignature
* @return BlockTransaction, or null if not found
* @throws SQLException
*/
- public static BlockTransaction fromTransactionSignature(Connection connection, byte[] transactionSignature) throws SQLException {
+ public static BlockTransaction fromTransactionSignature(byte[] transactionSignature) throws SQLException {
try {
- return new BlockTransaction(connection, transactionSignature);
+ return new BlockTransaction(transactionSignature);
} catch (NoDataFoundException e) {
return null;
}
@@ -119,23 +118,21 @@ public class BlockTransaction {
/**
* Load corresponding Block from DB.
*
- * @param connection
* @return Block, or null if not found (which should never happen)
* @throws SQLException
*/
- public Block getBlock(Connection connection) throws SQLException {
- return Block.fromSignature(connection, this.blockSignature);
+ public Block getBlock() throws SQLException {
+ return Block.fromSignature(this.blockSignature);
}
/**
* Load corresponding Transaction from DB.
*
- * @param connection
* @return Transaction, or null if not found (which should never happen)
* @throws SQLException
*/
- public Transaction getTransaction(Connection connection) throws SQLException {
- return TransactionFactory.fromSignature(connection, this.transactionSignature);
+ public Transaction getTransaction() throws SQLException {
+ return TransactionFactory.fromSignature(this.transactionSignature);
}
// Converters
diff --git a/src/qora/block/GenesisBlock.java b/src/qora/block/GenesisBlock.java
index 2a736935..07aa4c27 100644
--- a/src/qora/block/GenesisBlock.java
+++ b/src/qora/block/GenesisBlock.java
@@ -14,7 +14,6 @@ import com.google.common.primitives.Longs;
import qora.account.GenesisAccount;
import qora.account.PrivateKeyAccount;
-import qora.account.PublicKeyAccount;
import qora.crypto.Crypto;
import qora.transaction.GenesisTransaction;
import qora.transaction.Transaction;
@@ -33,10 +32,11 @@ public class GenesisBlock extends Block {
// Constructors
protected GenesisBlock() {
- super(GENESIS_BLOCK_VERSION, GENESIS_REFERENCE, GENESIS_TIMESTAMP, GENESIS_GENERATION_TARGET, GENESIS_GENERATOR, GENESIS_GENERATION_SIGNATURE, null, null);
+ super(GENESIS_BLOCK_VERSION, GENESIS_REFERENCE, GENESIS_TIMESTAMP, GENESIS_GENERATION_TARGET, GENESIS_GENERATOR, GENESIS_GENERATION_SIGNATURE, null,
+ null);
this.height = 1;
-
+
this.transactions = new ArrayList();
// Genesis transactions
@@ -174,7 +174,7 @@ public class GenesisBlock extends Block {
addGenesisTransaction("QgcphUTiVHHfHg8e1LVgg5jujVES7ZDUTr", "115031531");
addGenesisTransaction("QbQk9s4j4EAxAguBhmqA8mdtTct3qGnsrx", "138348733.2");
addGenesisTransaction("QT79PhvBwE6vFzfZ4oh5wdKVsEazZuVJFy", "6360421.343");
-
+
this.transactionsSignature = GENESIS_TRANSACTIONS_SIGNATURE;
}
@@ -197,7 +197,7 @@ public class GenesisBlock extends Block {
public static boolean isGenesisBlock(Block block) {
if (block.height != 1)
return false;
-
+
// Validate block signature
if (!Arrays.equals(GENESIS_GENERATION_SIGNATURE, block.generationSignature))
return false;
@@ -211,8 +211,8 @@ public class GenesisBlock extends Block {
// Load/Save
- protected GenesisBlock(Connection connection, byte[] signature) throws SQLException {
- super(connection, signature);
+ protected GenesisBlock(byte[] signature) throws SQLException {
+ super(signature);
}
protected GenesisBlock(ResultSet rs) throws SQLException {
@@ -226,12 +226,11 @@ public class GenesisBlock extends Block {
*
* As the genesis block is the first block, this always returns null.
*
- * @param connection
* @return null
* @throws SQLException
*/
@Override
- public Block getParent(Connection connection) throws SQLException {
+ public Block getParent() throws SQLException {
return null;
}
@@ -248,7 +247,7 @@ public class GenesisBlock extends Block {
private void addGenesisTransaction(String recipient, String amount) {
this.transactions.add(new GenesisTransaction(recipient, new BigDecimal(amount).setScale(8), this.timestamp));
}
-
+
/**
* Refuse to calculate genesis block signature!
*
@@ -279,7 +278,9 @@ public class GenesisBlock extends Block {
private static byte[] getBytesForSignature() {
try {
- ByteArrayOutputStream bytes = new ByteArrayOutputStream(VERSION_LENGTH + REFERENCE_LENGTH + GENERATION_TARGET_LENGTH + GENERATOR_LENGTH);
+ // Passing expected size to ByteArrayOutputStream avoids reallocation when adding more bytes than default 32.
+ // See below for explanation of some of the values used to calculated expected size.
+ ByteArrayOutputStream bytes = new ByteArrayOutputStream(8 + 64 + GENERATION_TARGET_LENGTH + GENERATOR_LENGTH);
/*
* NOTE: Historic code had genesis block using Longs.toByteArray() compared to standard block's Ints.toByteArray. The subsequent
* Bytes.ensureCapacity(versionBytes, 0, 4) did not truncate versionBytes back to 4 bytes either. This means 8 bytes were used even though
@@ -292,6 +293,7 @@ public class GenesisBlock extends Block {
*/
bytes.write(Bytes.ensureCapacity(GENESIS_REFERENCE, 64, 0));
bytes.write(Longs.toByteArray(GENESIS_GENERATION_TARGET.longValue()));
+ // NOTE: Genesis account's public key is only 8 bytes, not the usual 32.
bytes.write(GENESIS_GENERATOR.getPublicKey());
return bytes.toByteArray();
} catch (IOException e) {
@@ -300,7 +302,7 @@ public class GenesisBlock extends Block {
}
@Override
- public boolean isSignatureValid(PublicKeyAccount signer) {
+ public boolean isSignatureValid() {
// Validate block signature
if (!Arrays.equals(GENESIS_GENERATION_SIGNATURE, this.generationSignature))
return false;
@@ -315,7 +317,7 @@ public class GenesisBlock extends Block {
@Override
public boolean isValid(Connection connection) throws SQLException {
// Check there is no other block in DB
- if (BlockChain.getMaxHeight(connection) != 0)
+ if (BlockChain.getMaxHeight() != 0)
return false;
// Validate transactions
diff --git a/src/qora/crypto/Crypto.java b/src/qora/crypto/Crypto.java
index 74558e96..678fac0b 100644
--- a/src/qora/crypto/Crypto.java
+++ b/src/qora/crypto/Crypto.java
@@ -80,9 +80,9 @@ public class Crypto {
case ADDRESS_VERSION:
case AT_ADDRESS_VERSION:
byte[] addressWithoutChecksum = Arrays.copyOf(addressBytes, addressBytes.length - 4);
- byte[] passedChecksum = Arrays.copyOfRange(addressWithoutChecksum, addressBytes.length - 4, addressBytes.length);
+ byte[] passedChecksum = Arrays.copyOfRange(addressBytes, addressBytes.length - 4, addressBytes.length);
- byte[] generatedChecksum = doubleDigest(addressWithoutChecksum);
+ byte[] generatedChecksum = Arrays.copyOf(doubleDigest(addressWithoutChecksum), 4);
return Arrays.equals(passedChecksum, generatedChecksum);
default:
diff --git a/src/qora/transaction/GenesisTransaction.java b/src/qora/transaction/GenesisTransaction.java
index efbe8e39..6b573d2b 100644
--- a/src/qora/transaction/GenesisTransaction.java
+++ b/src/qora/transaction/GenesisTransaction.java
@@ -20,7 +20,6 @@ import database.NoDataFoundException;
import qora.account.Account;
import qora.account.GenesisAccount;
import qora.account.PrivateKeyAccount;
-import qora.account.PublicKeyAccount;
import qora.crypto.Crypto;
import utils.Base58;
import utils.Serialization;
@@ -40,7 +39,7 @@ public class GenesisTransaction extends Transaction {
// Constructors
public GenesisTransaction(String recipient, BigDecimal amount, long timestamp) {
- super(TransactionType.PAYMENT, BigDecimal.ZERO, new GenesisAccount(), timestamp, new byte[0], null);
+ super(TransactionType.GENESIS, BigDecimal.ZERO, new GenesisAccount(), timestamp, null, null);
this.recipient = new Account(recipient);
this.amount = amount;
@@ -68,34 +67,32 @@ public class GenesisTransaction extends Transaction {
/**
* Load GenesisTransaction from DB using signature.
*
- * @param connection
* @param signature
* @throws NoDataFoundException
* if no matching row found
* @throws SQLException
*/
- protected GenesisTransaction(Connection connection, byte[] signature) throws SQLException {
- super(connection, TransactionType.GENESIS, signature);
+ protected GenesisTransaction(byte[] signature) throws SQLException {
+ super(TransactionType.GENESIS, signature);
- ResultSet rs = DB.executeUsingBytes(connection, "SELECT recipient, amount FROM GenesisTransactions WHERE signature = ?", signature);
+ ResultSet rs = DB.executeUsingBytes("SELECT recipient, amount FROM GenesisTransactions WHERE signature = ?", signature);
if (rs == null)
throw new NoDataFoundException();
- this.recipient = new Account(rs.getString(2));
- this.amount = rs.getBigDecimal(3).setScale(8);
+ this.recipient = new Account(rs.getString(1));
+ this.amount = rs.getBigDecimal(2).setScale(8);
}
/**
* Load GenesisTransaction from DB using signature
*
- * @param connection
* @param signature
- * @return PaymentTransaction, or null if not found
+ * @return GenesisTransaction, or null if not found
* @throws SQLException
*/
- public static GenesisTransaction fromSignature(Connection connection, byte[] signature) throws SQLException {
+ public static GenesisTransaction fromSignature(byte[] signature) throws SQLException {
try {
- return new GenesisTransaction(connection, signature);
+ return new GenesisTransaction(signature);
} catch (NoDataFoundException e) {
return null;
}
@@ -120,7 +117,7 @@ public class GenesisTransaction extends Transaction {
@SuppressWarnings("unchecked")
@Override
- public JSONObject toJSON() {
+ public JSONObject toJSON() throws SQLException {
JSONObject json = getBaseJSON();
json.put("recipient", this.recipient.getAddress());
@@ -173,7 +170,7 @@ public class GenesisTransaction extends Transaction {
}
/**
- * Check validity of genesis transction signature.
+ * Check validity of genesis transaction signature.
*
* This is handled differently as there is no private key for the genesis account and so no way to sign/verify data.
*
@@ -182,7 +179,7 @@ public class GenesisTransaction extends Transaction {
* @return boolean
*/
@Override
- public boolean isSignatureValid(PublicKeyAccount signer) {
+ public boolean isSignatureValid() {
return Arrays.equals(this.signature, calcSignature());
}
diff --git a/src/qora/transaction/PaymentTransaction.java b/src/qora/transaction/PaymentTransaction.java
index 763e4bf7..b8144008 100644
--- a/src/qora/transaction/PaymentTransaction.java
+++ b/src/qora/transaction/PaymentTransaction.java
@@ -74,16 +74,15 @@ public class PaymentTransaction extends Transaction {
/**
* Load PaymentTransaction from DB using signature.
*
- * @param connection
* @param signature
* @throws NoDataFoundException
* if no matching row found
* @throws SQLException
*/
- protected PaymentTransaction(Connection connection, byte[] signature) throws SQLException {
- super(connection, TransactionType.PAYMENT, signature);
+ protected PaymentTransaction(byte[] signature) throws SQLException {
+ super(TransactionType.PAYMENT, signature);
- ResultSet rs = DB.executeUsingBytes(connection, "SELECT sender, recipient, amount FROM PaymentTransactions WHERE signature = ?", signature);
+ ResultSet rs = DB.executeUsingBytes("SELECT sender, recipient, amount FROM PaymentTransactions WHERE signature = ?", signature);
if (rs == null)
throw new NoDataFoundException();
@@ -95,14 +94,13 @@ public class PaymentTransaction extends Transaction {
/**
* Load PaymentTransaction from DB using signature
*
- * @param connection
* @param signature
* @return PaymentTransaction, or null if not found
* @throws SQLException
*/
- public static PaymentTransaction fromSignature(Connection connection, byte[] signature) throws SQLException {
+ public static PaymentTransaction fromSignature(byte[] signature) throws SQLException {
try {
- return new PaymentTransaction(connection, signature);
+ return new PaymentTransaction(signature);
} catch (NoDataFoundException e) {
return null;
}
@@ -114,7 +112,7 @@ public class PaymentTransaction extends Transaction {
String sql = DB.formatInsertWithPlaceholders("PaymentTransactions", "signature", "sender", "recipient", "amount");
PreparedStatement preparedStatement = connection.prepareStatement(sql);
- DB.bindInsertPlaceholders(preparedStatement, this.signature, this.sender.getPublicKey(), this.recipient, this.amount);
+ DB.bindInsertPlaceholders(preparedStatement, this.signature, this.sender.getPublicKey(), this.recipient.getAddress(), this.amount);
preparedStatement.execute();
}
@@ -127,7 +125,7 @@ public class PaymentTransaction extends Transaction {
@SuppressWarnings("unchecked")
@Override
- public JSONObject toJSON() {
+ public JSONObject toJSON() throws SQLException {
JSONObject json = getBaseJSON();
json.put("sender", this.sender.getAddress());
diff --git a/src/qora/transaction/Transaction.java b/src/qora/transaction/Transaction.java
index 1e556583..2902e72e 100644
--- a/src/qora/transaction/Transaction.java
+++ b/src/qora/transaction/Transaction.java
@@ -171,34 +171,32 @@ public abstract class Transaction {
/**
* Get block height for this transaction in the blockchain.
*
- * @param connection
* @return height, or 0 if not in blockchain (i.e. unconfirmed)
* @throws SQLException
*/
- public int getHeight(Connection connection) throws SQLException {
+ public int getHeight() throws SQLException {
if (this.signature == null)
return 0;
- BlockTransaction blockTx = BlockTransaction.fromTransactionSignature(connection, this.signature);
+ BlockTransaction blockTx = BlockTransaction.fromTransactionSignature(this.signature);
if (blockTx == null)
return 0;
- return BlockChain.getBlockHeightFromSignature(connection, blockTx.getBlockSignature());
+ return BlockChain.getBlockHeightFromSignature(blockTx.getBlockSignature());
}
/**
* Get number of confirmations for this transaction.
*
- * @param connection
* @return confirmation count, or 0 if not in blockchain (i.e. unconfirmed)
* @throws SQLException
*/
- public int getConfirmations(Connection connection) throws SQLException {
- int ourHeight = this.getHeight(connection);
+ public int getConfirmations() throws SQLException {
+ int ourHeight = this.getHeight();
if (ourHeight == 0)
return 0;
- int blockChainHeight = BlockChain.getMaxHeight(connection);
+ int blockChainHeight = BlockChain.getMaxHeight();
return blockChainHeight - ourHeight + 1;
}
@@ -211,21 +209,20 @@ public abstract class Transaction {
*
* Note that the transaction type is not checked against the DB's value.
*
- * @param connection
* @param type
* @param signature
* @throws NoDataFoundException
* if no matching row found
* @throws SQLException
*/
- protected Transaction(Connection connection, TransactionType type, byte[] signature) throws SQLException {
- ResultSet rs = DB.executeUsingBytes(connection, "SELECT reference, creator, creation, fee FROM Transactions WHERE signature = ?", signature);
+ protected Transaction(TransactionType type, byte[] signature) throws SQLException {
+ ResultSet rs = DB.executeUsingBytes("SELECT reference, creator, creation, fee FROM Transactions WHERE signature = ?", signature);
if (rs == null)
throw new NoDataFoundException();
this.type = type;
this.reference = DB.getResultSetBytes(rs.getBinaryStream(1), REFERENCE_LENGTH);
- this.creator = new PublicKeyAccount(DB.getResultSetBytes(rs.getBinaryStream(2), CREATOR_LENGTH));
+ this.creator = new PublicKeyAccount(DB.getResultSetBytes(rs.getBinaryStream(2)));
this.timestamp = rs.getTimestamp(3).getTime();
this.fee = rs.getBigDecimal(4).setScale(8);
this.signature = signature;
@@ -244,88 +241,67 @@ public abstract class Transaction {
/**
* Load encapsulating Block from DB, if any
*
- * @param connection
* @return Block, or null if transaction is not in a Block
* @throws SQLException
*/
- public Block getBlock(Connection connection) throws SQLException {
+ public Block getBlock() throws SQLException {
if (this.signature == null)
return null;
- BlockTransaction blockTx = BlockTransaction.fromTransactionSignature(connection, this.signature);
+ BlockTransaction blockTx = BlockTransaction.fromTransactionSignature(this.signature);
if (blockTx == null)
return null;
- return Block.fromSignature(connection, blockTx.getBlockSignature());
+ return Block.fromSignature(blockTx.getBlockSignature());
}
/**
* Load parent Transaction from DB via this transaction's reference.
*
- * @param connection
* @return Transaction, or null if no parent found (which should not happen)
* @throws SQLException
*/
- public Transaction getParent(Connection connection) throws SQLException {
+ public Transaction getParent() throws SQLException {
if (this.reference == null)
return null;
- return TransactionFactory.fromSignature(connection, this.reference);
+ return TransactionFactory.fromSignature(this.reference);
}
/**
* Load child Transaction from DB, if any.
*
- * @param connection
* @return Transaction, or null if no child found
* @throws SQLException
*/
- public Transaction getChild(Connection connection) throws SQLException {
+ public Transaction getChild() throws SQLException {
if (this.signature == null)
return null;
- return TransactionFactory.fromReference(connection, this.signature);
+ return TransactionFactory.fromReference(this.signature);
}
// Converters
- public abstract JSONObject toJSON();
+ public abstract JSONObject toJSON() throws SQLException;
/**
* Produce JSON representation of common/base Transaction info.
- *
- * To include info on number of confirmations, a Connection object is required. See {@link Transaction#getBaseJSON(Connection)}
*
* @return JSONObject
+ * @throws SQLException
*/
@SuppressWarnings("unchecked")
- protected JSONObject getBaseJSON() {
+ protected JSONObject getBaseJSON() throws SQLException {
JSONObject json = new JSONObject();
json.put("type", this.type.value);
json.put("fee", this.fee.toPlainString());
json.put("timestamp", this.timestamp);
- json.put("reference", Base58.encode(this.reference));
+ if (this.reference != null)
+ json.put("reference", Base58.encode(this.reference));
json.put("signature", Base58.encode(this.signature));
-
- return json;
- }
-
- /**
- * Produce JSON representation of common/base Transaction info, including number of confirmations.
- *
- * Requires SQL Connection object to determine number of confirmations.
- *
- * @param connection
- * @return JSONObject
- * @throws SQLException
- * @see Transaction#getBaseJSON()
- */
- @SuppressWarnings("unchecked")
- protected JSONObject getBaseJSON(Connection connection) throws SQLException {
- JSONObject json = this.getBaseJSON();
-
- json.put("confirmations", this.getConfirmations(connection));
+ json.put("confirmations", this.getConfirmations());
return json;
}
@@ -353,11 +329,11 @@ public abstract class Transaction {
return signer.sign(this.toBytesLessSignature());
}
- public boolean isSignatureValid(PublicKeyAccount signer) {
+ public boolean isSignatureValid() {
if (this.signature == null)
return false;
- return signer.verify(this.signature, this.toBytesLessSignature());
+ return this.creator.verify(this.signature, this.toBytesLessSignature());
}
public abstract ValidationResult isValid(Connection connection);
diff --git a/src/qora/transaction/TransactionFactory.java b/src/qora/transaction/TransactionFactory.java
index 9f43cf70..d2c0d5fb 100644
--- a/src/qora/transaction/TransactionFactory.java
+++ b/src/qora/transaction/TransactionFactory.java
@@ -1,6 +1,5 @@
package qora.transaction;
-import java.sql.Connection;
import java.sql.ResultSet;
import java.sql.SQLException;
@@ -12,30 +11,28 @@ public class TransactionFactory {
/**
* Load Transaction from DB using signature.
*
- * @param connection
* @param signature
* @return ? extends Transaction, or null if not found
* @throws SQLException
*/
- public static Transaction fromSignature(Connection connection, byte[] signature) throws SQLException {
- ResultSet resultSet = DB.executeUsingBytes(connection, "SELECT type, signature FROM Transactions WHERE signature = ?", signature);
- return fromResultSet(connection, resultSet);
+ public static Transaction fromSignature(byte[] signature) throws SQLException {
+ ResultSet resultSet = DB.executeUsingBytes("SELECT type, signature FROM Transactions WHERE signature = ?", signature);
+ return fromResultSet(resultSet);
}
/**
* Load Transaction from DB using reference.
*
- * @param connection
* @param reference
* @return ? extends Transaction, or null if not found
* @throws SQLException
*/
- public static Transaction fromReference(Connection connection, byte[] reference) throws SQLException {
- ResultSet resultSet = DB.executeUsingBytes(connection, "SELECT type, signature FROM Transactions WHERE reference = ?", reference);
- return fromResultSet(connection, resultSet);
+ public static Transaction fromReference(byte[] reference) throws SQLException {
+ ResultSet resultSet = DB.executeUsingBytes("SELECT type, signature FROM Transactions WHERE reference = ?", reference);
+ return fromResultSet(resultSet);
}
- private static Transaction fromResultSet(Connection connection, ResultSet resultSet) throws SQLException {
+ private static Transaction fromResultSet(ResultSet resultSet) throws SQLException {
if (resultSet == null)
return null;
@@ -47,10 +44,10 @@ public class TransactionFactory {
switch (type) {
case GENESIS:
- return GenesisTransaction.fromSignature(connection, signature);
+ return GenesisTransaction.fromSignature(signature);
case PAYMENT:
- return PaymentTransaction.fromSignature(connection, signature);
+ return PaymentTransaction.fromSignature(signature);
default:
return null;
diff --git a/src/test/blocks.java b/src/test/blocks.java
new file mode 100644
index 00000000..44aee7c7
--- /dev/null
+++ b/src/test/blocks.java
@@ -0,0 +1,84 @@
+package test;
+
+import static org.junit.Assert.*;
+
+import java.math.BigDecimal;
+import java.sql.Connection;
+import java.sql.SQLException;
+import java.util.List;
+
+import org.junit.Test;
+
+import database.DB;
+import qora.block.Block;
+import qora.block.GenesisBlock;
+import qora.transaction.Transaction;
+import qora.transaction.TransactionFactory;
+
+public class blocks extends common {
+
+ @Test
+ public void testGenesisBlockTransactions() throws SQLException {
+ try (final Connection connection = DB.getConnection()) {
+ GenesisBlock block = GenesisBlock.getInstance();
+ assertNotNull(block);
+ assertTrue(block.isSignatureValid());
+ // only true if blockchain is empty
+ // assertTrue(block.isValid(connection));
+
+ List transactions = block.getTransactions();
+ assertNotNull(transactions);
+
+ for (Transaction transaction : transactions) {
+ assertNotNull(transaction);
+ assertEquals(Transaction.TransactionType.GENESIS, transaction.getType());
+ assertTrue(transaction.getFee().compareTo(BigDecimal.ZERO) == 0);
+ assertNull(transaction.getReference());
+ assertTrue(transaction.isSignatureValid());
+ assertEquals(Transaction.ValidationResult.OK, transaction.isValid(connection));
+ }
+
+ // Attempt to load first transaction directly from database
+ Transaction transaction = TransactionFactory.fromSignature(transactions.get(0).getSignature());
+ assertNotNull(transaction);
+ assertEquals(Transaction.TransactionType.GENESIS, transaction.getType());
+ assertTrue(transaction.getFee().compareTo(BigDecimal.ZERO) == 0);
+ assertNull(transaction.getReference());
+ assertTrue(transaction.isSignatureValid());
+ assertEquals(Transaction.ValidationResult.OK, transaction.isValid(connection));
+ }
+ }
+
+ @Test
+ public void testBlockPaymentTransactions() throws SQLException {
+ try (final Connection connection = DB.getConnection()) {
+ // Block 949 has lots of varied transactions
+ // Blocks 390 & 754 have only payment transactions
+ Block block = Block.fromHeight(754);
+ assertNotNull("Block 754 is required for this test", block);
+ assertTrue(block.isSignatureValid());
+
+ List transactions = block.getTransactions();
+ assertNotNull(transactions);
+
+ for (Transaction transaction : transactions) {
+ assertNotNull(transaction);
+ assertEquals(Transaction.TransactionType.PAYMENT, transaction.getType());
+ assertFalse(transaction.getFee().compareTo(BigDecimal.ZERO) == 0);
+ assertNotNull(transaction.getReference());
+ assertTrue(transaction.isSignatureValid());
+ assertEquals(Transaction.ValidationResult.OK, transaction.isValid(connection));
+ }
+
+ // Attempt to load first transaction directly from database
+ Transaction transaction = TransactionFactory.fromSignature(transactions.get(0).getSignature());
+ assertNotNull(transaction);
+ assertEquals(Transaction.TransactionType.PAYMENT, transaction.getType());
+ assertFalse(transaction.getFee().compareTo(BigDecimal.ZERO) == 0);
+ assertNotNull(transaction.getReference());
+ assertTrue(transaction.isSignatureValid());
+ assertEquals(Transaction.ValidationResult.OK, transaction.isValid(connection));
+ }
+ }
+
+}
diff --git a/src/test/common.java b/src/test/common.java
index 885fae0a..bfb8eff3 100644
--- a/src/test/common.java
+++ b/src/test/common.java
@@ -1,21 +1,31 @@
package test;
-import static org.junit.Assert.fail;
-
-import java.sql.Connection;
-import java.sql.DriverManager;
import java.sql.SQLException;
+import org.junit.AfterClass;
+import org.junit.BeforeClass;
+
+import database.DB;
+
public class common {
- public static Connection getConnection() {
+ @BeforeClass
+ public static void setConnection() throws SQLException {
+ DB.setUrl("jdbc:hsqldb:file:db/test;create=true;close_result=true;sql.strict_exec=true;sql.enforce_names=true;sql.syntax_mys=true");
+ DB.open();
+
+ // Create/update database schema
try {
- return DriverManager.getConnection("jdbc:hsqldb:file:db/test;create=true;close_result=true;sql.strict_exec=true;sql.enforce_names=true;sql.syntax_mys=true", "SA", "");
+ updates.updateDatabase();
} catch (SQLException e) {
e.printStackTrace();
- fail();
- return null;
+ throw e;
}
}
+ @AfterClass
+ public static void closeDatabase() throws SQLException {
+ DB.close();
+ }
+
}
diff --git a/src/test/connections.java b/src/test/connections.java
index 31851e34..9ebe21ec 100644
--- a/src/test/connections.java
+++ b/src/test/connections.java
@@ -3,18 +3,18 @@ package test;
import static org.junit.Assert.*;
import java.sql.Connection;
-import java.sql.DriverManager;
import java.sql.SQLException;
-import java.sql.Statement;
import org.junit.Test;
-public class connections {
+import database.DB;
+
+public class connections extends common {
@Test
public void testConnection() {
try {
- Connection c = DriverManager.getConnection("jdbc:hsqldb:file:db/test", "SA", "");
+ Connection c = DB.getConnection();
c.close();
} catch (SQLException e) {
e.printStackTrace();
@@ -24,53 +24,28 @@ public class connections {
@Test
public void testSimultaneousConnections() {
+ int n_connections = 5;
+ Connection[] connections = new Connection[n_connections];
+
try {
- Connection c1 = DriverManager.getConnection("jdbc:hsqldb:file:db/test", "SA", "");
- Connection c2 = DriverManager.getConnection("jdbc:hsqldb:file:db/test", "SA", "");
- c1.close();
- c2.close();
+ for (int i = 0; i < n_connections; ++i)
+ connections[i] = DB.getConnection();
+
+ // Close in same order as opening
+ for (int i = 0; i < n_connections; ++i)
+ connections[i].close();
} catch (SQLException e) {
e.printStackTrace();
fail();
}
}
- @Test
- public void testExistOnlyConnection() {
- try {
- Connection c = DriverManager.getConnection("jdbc:hsqldb:file:db/test;ifexists=true", "SA", "");
- c.close();
- } catch (SQLException e) {
- e.printStackTrace();
- fail();
- }
- }
-
- @Test
- public void testConnectionAfterShutdown() {
- try {
- Connection c = DriverManager.getConnection("jdbc:hsqldb:file:db/test", "SA", "");
- Statement s = c.createStatement();
- s.execute("SHUTDOWN COMPACT");
- c.close();
-
- c = DriverManager.getConnection("jdbc:hsqldb:file:db/test", "SA", "");
- c.close();
- } catch (SQLException e) {
- e.printStackTrace();
- fail();
- }
- }
-
- @Test
- public void testComplexConnection() {
- try {
- Connection c = DriverManager.getConnection("jdbc:hsqldb:file:db/test;create=false;close_result=true;sql.strict_exec=true;sql.enforce_names=true", "SA", "");
- c.close();
- } catch (SQLException e) {
- e.printStackTrace();
- fail();
- }
- }
+ /*
+ * @Test public void testConnectionAfterShutdown() { try { DB.close(); } catch (SQLException e) { e.printStackTrace(); fail(); }
+ *
+ * try { Connection c = DB.getConnection(); c.close(); } catch (SQLException e) { // good return; }
+ *
+ * fail(); }
+ */
}
diff --git a/src/test/crypto.java b/src/test/crypto.java
index 0897ec3c..33746dd0 100644
--- a/src/test/crypto.java
+++ b/src/test/crypto.java
@@ -15,24 +15,24 @@ public class crypto {
byte[] input = HashCode.fromString("00").asBytes();
byte[] digest = Crypto.digest(input);
byte[] expected = HashCode.fromString("6e340b9cffb37a989ca544e6bb780a2c78901d3fb33738768511a30617afa01d").asBytes();
-
+
assertArrayEquals(digest, expected);
}
-
+
@Test
public void testCryptoDoubleDigest() {
byte[] input = HashCode.fromString("00").asBytes();
byte[] digest = Crypto.doubleDigest(input);
byte[] expected = HashCode.fromString("1406e05881e299367766d313e26c05564ec91bf721d31726bd6e46e60689539a").asBytes();
-
+
assertArrayEquals(digest, expected);
}
-
+
@Test
public void testCryptoQoraAddress() {
byte[] publicKey = HashCode.fromString("775ada64a48a30b3bfc4f1db16bca512d4088704975a62bde78781ce0cba90d6").asBytes();
String expected = "QUD9y7NZqTtNwvSAUfewd7zKUGoVivVnTW";
-
+
assertEquals(expected, Crypto.toAddress(publicKey));
}
diff --git a/src/test/load.java b/src/test/load.java
index 154ac21f..a507577a 100644
--- a/src/test/load.java
+++ b/src/test/load.java
@@ -2,43 +2,28 @@ package test;
import static org.junit.Assert.*;
-import java.sql.Connection;
import java.sql.SQLException;
-import org.junit.After;
-import org.junit.Before;
import org.junit.Test;
+import qora.block.BlockChain;
import qora.transaction.PaymentTransaction;
import qora.transaction.Transaction;
import qora.transaction.TransactionFactory;
import utils.Base58;
-public class load {
-
- private static Connection connection;
-
- @Before
- public void connect() throws SQLException {
- connection = common.getConnection();
- }
-
- @After
- public void disconnect() {
- try {
- connection.createStatement().execute("SHUTDOWN");
- } catch (SQLException e) {
- fail();
- }
- }
+public class load extends common {
@Test
public void testLoadPaymentTransaction() throws SQLException {
+ assertTrue("Migrate old database to at least block 49778 before running this test", BlockChain.getMaxHeight() >= 49778);
+
String signature58 = "1211ZPwG3hk5evWzXCZi9hMDRpwumWmkENjwWkeTCik9xA5uoYnxzF7rwR5hmHH3kG2RXo7ToCAaRc7dvnynByJt";
byte[] signature = Base58.decode(signature58);
- PaymentTransaction paymentTransaction = PaymentTransaction.fromSignature(connection, signature);
+ PaymentTransaction paymentTransaction = PaymentTransaction.fromSignature(signature);
+ assertNotNull(paymentTransaction);
assertEquals(paymentTransaction.getSender().getAddress(), "QXwu8924WdgPoRmtiWQBUMF6eedmp1Hu2E");
assertEquals(paymentTransaction.getCreator().getAddress(), "QXwu8924WdgPoRmtiWQBUMF6eedmp1Hu2E");
assertEquals(paymentTransaction.getRecipient().getAddress(), "QZsv8vbJ6QfrBNba4LMp5UtHhAzhrxvVUU");
@@ -49,17 +34,19 @@ public class load {
@Test
public void testLoadFactory() throws SQLException {
+ assertTrue("Migrate old database to at least block 49778 before running this test", BlockChain.getMaxHeight() >= 49778);
+
String signature58 = "1211ZPwG3hk5evWzXCZi9hMDRpwumWmkENjwWkeTCik9xA5uoYnxzF7rwR5hmHH3kG2RXo7ToCAaRc7dvnynByJt";
byte[] signature = Base58.decode(signature58);
while (true) {
- Transaction transaction = TransactionFactory.fromSignature(connection, signature);
+ Transaction transaction = TransactionFactory.fromSignature(signature);
if (transaction == null)
break;
PaymentTransaction payment = (PaymentTransaction) transaction;
- System.out.println("Transaction " + Base58.encode(payment.getSignature()) + ": " + payment.getAmount().toString() + " QORA from "
- + payment.getSender().getAddress() + " to " + payment.getRecipient());
+ System.out
+ .println(payment.getSender().getAddress() + " sent " + payment.getAmount().toString() + " QORA to " + payment.getRecipient().getAddress());
signature = payment.getReference();
}
@@ -70,11 +57,11 @@ public class load {
String signature58 = "1111222233334444";
byte[] signature = Base58.decode(signature58);
- PaymentTransaction payment = PaymentTransaction.fromSignature(connection, signature);
+ PaymentTransaction payment = PaymentTransaction.fromSignature(signature);
if (payment != null) {
- System.out.println("Transaction " + Base58.encode(payment.getSignature()) + ": " + payment.getAmount().toString() + " QORA from "
- + payment.getSender().getAddress() + " to " + payment.getRecipient());
+ System.out
+ .println(payment.getSender().getAddress() + " sent " + payment.getAmount().toString() + " QORA to " + payment.getRecipient().getAddress());
fail();
}
}
diff --git a/src/test/migrate.java b/src/test/migrate.java
index be5017db..33e4610b 100644
--- a/src/test/migrate.java
+++ b/src/test/migrate.java
@@ -13,7 +13,6 @@ import java.nio.charset.Charset;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.SQLException;
-import java.sql.Statement;
import java.sql.Timestamp;
import java.util.ArrayList;
import java.util.Arrays;
@@ -25,42 +24,22 @@ import org.json.simple.JSONArray;
import org.json.simple.JSONObject;
import org.json.simple.JSONValue;
import org.json.simple.parser.ParseException;
-import org.junit.After;
-import org.junit.Before;
import org.junit.Test;
import com.google.common.hash.HashCode;
import com.google.common.io.CharStreams;
+import database.DB;
+import qora.block.BlockChain;
import utils.Base58;
-public class migrate {
+public class migrate extends common {
private static final String GENESIS_ADDRESS = "QfGMeDQQUQePMpAmfLBJzgqyrM35RWxHGD";
private static final byte[] GENESIS_PUBLICKEY = new byte[] { 1, 1, 1, 1, 1, 1, 1, 1 };
- private static Connection c;
- private static PreparedStatement startTransactionPStmt;
- private static PreparedStatement commitPStmt;
-
private static Map publicKeyByAddress = new HashMap();
- @Before
- public void connect() throws SQLException {
- c = common.getConnection();
- startTransactionPStmt = c.prepareStatement("START TRANSACTION");
- commitPStmt = c.prepareStatement("COMMIT");
- }
-
- @After
- public void disconnect() {
- try {
- c.createStatement().execute("SHUTDOWN");
- } catch (SQLException e) {
- fail();
- }
- }
-
public Object fetchBlockJSON(int height) throws IOException {
InputStream is;
@@ -111,14 +90,6 @@ public class migrate {
return output.toString();
}
- public void startTransaction() throws SQLException {
- startTransactionPStmt.execute();
- }
-
- public void commit() throws SQLException {
- commitPStmt.execute();
- }
-
@Test
public void testMigration() throws SQLException, IOException {
// Genesis public key
@@ -126,8 +97,7 @@ public class migrate {
// Some other public keys for addresses that have never created a transaction
publicKeyByAddress.put("QcDLhirHkSbR4TLYeShLzHw61B8UGTFusk", Base58.decode("HP58uWRBae654ze6ysmdyGv3qaDrr9BEk6cHv4WuiF7d"));
- Statement stmt = c.createStatement();
- stmt.execute("DELETE FROM Blocks");
+ Connection c = DB.getConnection();
PreparedStatement blocksPStmt = c
.prepareStatement("INSERT INTO Blocks " + formatWithPlaceholders("signature", "version", "reference", "transaction_count", "total_fees",
@@ -179,8 +149,10 @@ public class migrate {
PreparedStatement blockTxPStmt = c
.prepareStatement("INSERT INTO BlockTransactions " + formatWithPlaceholders("block_signature", "sequence", "transaction_signature"));
- int height = 1;
+ int height = BlockChain.getMaxHeight() + 1;
byte[] milestone_block = null;
+ System.out.println("Starting migration from block height " + height);
+
while (true) {
JSONObject json = (JSONObject) fetchBlockJSON(height);
if (json == null)
@@ -191,7 +163,7 @@ public class migrate {
JSONArray transactions = (JSONArray) json.get("transactions");
- startTransaction();
+ DB.startTransaction(c);
// Blocks:
// signature, version, reference, transaction_count, total_fees, transactions_signature, height, generation, generation_target, generator,
@@ -246,7 +218,7 @@ public class migrate {
if (txReference != null)
txPStmt.setBinaryStream(2, new ByteArrayInputStream(txReference));
else if (height == 1 && type == 1)
- txPStmt.setNull(2, java.sql.Types.VARCHAR); // genesis transactions only
+ txPStmt.setNull(2, java.sql.Types.VARBINARY); // genesis transactions only
else
fail();
@@ -255,7 +227,7 @@ public class migrate {
// Determine transaction "creator" from specific transaction info
switch (type) {
case 1: // genesis
- txPStmt.setNull(4, java.sql.Types.VARCHAR); // genesis transactions only
+ txPStmt.setBinaryStream(4, new ByteArrayInputStream(GENESIS_PUBLICKEY)); // genesis transactions only
break;
case 2: // payment
@@ -299,7 +271,7 @@ public class migrate {
if (milestone_block != null)
txPStmt.setBinaryStream(7, new ByteArrayInputStream(milestone_block));
else if (height == 1 && type == 1)
- txPStmt.setNull(7, java.sql.Types.VARCHAR); // genesis transactions only
+ txPStmt.setNull(7, java.sql.Types.VARBINARY); // genesis transactions only
else
fail();
@@ -614,7 +586,7 @@ public class migrate {
blockTxPStmt.execute();
blockTxPStmt.clearParameters();
- commit();
+ DB.commit(c);
}
// new milestone block every 500 blocks?
@@ -623,6 +595,9 @@ public class migrate {
++height;
}
+
+ c.close();
+ System.out.println("Migration finished with new blockchain height " + BlockChain.getMaxHeight());
}
}
diff --git a/src/test/navigation.java b/src/test/navigation.java
index b6bae133..710ac863 100644
--- a/src/test/navigation.java
+++ b/src/test/navigation.java
@@ -2,49 +2,31 @@ package test;
import static org.junit.Assert.*;
-import java.sql.Connection;
import java.sql.SQLException;
-import org.junit.After;
-import org.junit.Before;
import org.junit.Test;
import qora.block.Block;
+import qora.block.BlockChain;
import qora.transaction.PaymentTransaction;
import utils.Base58;
-public class navigation {
-
- private static Connection connection;
-
- @Before
- public void connect() throws SQLException {
- connection = common.getConnection();
- }
-
- @After
- public void disconnect() {
- try {
- connection.createStatement().execute("SHUTDOWN");
- } catch (SQLException e) {
- fail();
- }
- }
+public class navigation extends common {
@Test
public void testNavigateFromTransactionToBlock() throws SQLException {
+ assertTrue("Migrate old database to at least block 49778 before running this test", BlockChain.getMaxHeight() >= 49778);
+
String signature58 = "1211ZPwG3hk5evWzXCZi9hMDRpwumWmkENjwWkeTCik9xA5uoYnxzF7rwR5hmHH3kG2RXo7ToCAaRc7dvnynByJt";
byte[] signature = Base58.decode(signature58);
System.out.println("Navigating to Block from transaction " + signature58);
- PaymentTransaction paymentTransaction = PaymentTransaction.fromSignature(connection, signature);
+ PaymentTransaction paymentTransaction = PaymentTransaction.fromSignature(signature);
+ assertNotNull("Payment transaction not loaded from database", paymentTransaction);
- assertNotNull(paymentTransaction);
-
- Block block = paymentTransaction.getBlock(connection);
-
- assertNotNull(block);
+ Block block = paymentTransaction.getBlock();
+ assertNotNull("Block 49778 not loaded from database", block);
System.out.println("Block " + block.getHeight() + ", signature: " + Base58.encode(block.getSignature()));
diff --git a/src/test/save.java b/src/test/save.java
index f2506a20..b24b1f7b 100644
--- a/src/test/save.java
+++ b/src/test/save.java
@@ -1,40 +1,18 @@
package test;
-import static org.junit.Assert.*;
-
import java.math.BigDecimal;
import java.sql.Connection;
import java.sql.SQLException;
-import java.sql.Statement;
import java.time.Instant;
-import org.junit.After;
-import org.junit.Before;
import org.junit.Test;
+import database.DB;
import qora.account.PublicKeyAccount;
import qora.transaction.PaymentTransaction;
import utils.Base58;
-public class save {
-
- private static Connection connection;
-
- @Before
- public void connect() throws SQLException {
- connection = common.getConnection();
- Statement stmt = connection.createStatement();
- stmt.execute("SET DATABASE SQL SYNTAX MYS TRUE");
- }
-
- @After
- public void disconnect() {
- try {
- connection.createStatement().execute("SHUTDOWN");
- } catch (SQLException e) {
- fail();
- }
- }
+public class save extends common {
@Test
public void testSavePaymentTransaction() throws SQLException {
@@ -47,7 +25,9 @@ public class save {
PaymentTransaction paymentTransaction = new PaymentTransaction(sender, "Qrecipient", BigDecimal.valueOf(12345L), BigDecimal.ONE,
Instant.now().getEpochSecond(), reference, signature);
- paymentTransaction.save(connection);
+ try (final Connection connection = DB.getConnection()) {
+ paymentTransaction.save(connection);
+ }
}
}
diff --git a/src/test/signatures.java b/src/test/signatures.java
index 8b7365d1..efb44d58 100644
--- a/src/test/signatures.java
+++ b/src/test/signatures.java
@@ -2,33 +2,14 @@ package test;
import static org.junit.Assert.*;
-import java.sql.Connection;
import java.sql.SQLException;
-import org.junit.After;
-import org.junit.Before;
import org.junit.Test;
import qora.block.GenesisBlock;
import utils.Base58;
-public class signatures {
-
- private static Connection connection;
-
- @Before
- public void connect() throws SQLException {
- connection = common.getConnection();
- }
-
- @After
- public void disconnect() {
- try {
- connection.createStatement().execute("SHUTDOWN");
- } catch (SQLException e) {
- fail();
- }
- }
+public class signatures extends common {
@Test
public void testGenesisBlockSignature() throws SQLException {
diff --git a/src/test/updates.java b/src/test/updates.java
index a12c1045..f8576db1 100644
--- a/src/test/updates.java
+++ b/src/test/updates.java
@@ -7,236 +7,229 @@ import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
-import org.junit.After;
-import org.junit.Before;
import org.junit.Test;
-public class updates {
+import database.DB;
- private static Connection c;
+public class updates extends common {
- @Before
- public void connect() throws SQLException {
- c = common.getConnection();
- Statement stmt = c.createStatement();
- stmt.execute("SET DATABASE DEFAULT TABLE TYPE CACHED");
- }
-
- @After
- public void disconnect() {
- try {
- c.createStatement().execute("SHUTDOWN");
- } catch (SQLException e) {
- fail();
- }
- }
-
- public boolean databaseUpdating() throws SQLException {
+ public static boolean databaseUpdating() throws SQLException {
int databaseVersion = fetchDatabaseVersion();
- Statement stmt = c.createStatement();
+ try (final Connection c = DB.getConnection()) {
+ Statement stmt = c.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
+ // 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
- switch (databaseVersion) {
- case 0:
- // create from new
- stmt.execute("CREATE TABLE DatabaseInfo ( version INTEGER NOT NULL )");
- stmt.execute("INSERT INTO DatabaseInfo VALUES ( 0 )");
- stmt.execute("CREATE DOMAIN BlockSignature AS VARBINARY(128)");
- stmt.execute("CREATE DOMAIN Signature AS VARBINARY(64)");
- stmt.execute("CREATE DOMAIN QoraAddress AS VARCHAR(36)");
- stmt.execute("CREATE DOMAIN QoraPublicKey AS VARBINARY(32)");
- stmt.execute("CREATE DOMAIN QoraAmount AS DECIMAL(19, 8)");
- stmt.execute("CREATE DOMAIN RegisteredName AS VARCHAR(400) COLLATE SQL_TEXT_UCC");
- stmt.execute("CREATE DOMAIN NameData AS VARCHAR(4000)");
- stmt.execute("CREATE DOMAIN PollName AS VARCHAR(400) COLLATE SQL_TEXT_UCC");
- stmt.execute("CREATE DOMAIN PollOption AS VARCHAR(400) COLLATE SQL_TEXT_UCC");
- stmt.execute("CREATE DOMAIN DataHash AS VARCHAR(100)");
- stmt.execute("CREATE DOMAIN AssetID AS BIGINT");
- stmt.execute("CREATE DOMAIN AssetName AS VARCHAR(400) COLLATE SQL_TEXT_UCC");
- stmt.execute("CREATE DOMAIN AssetOrderID AS VARCHAR(100)");
- stmt.execute("CREATE DOMAIN ATName AS VARCHAR(200) COLLATE SQL_TEXT_UCC");
- stmt.execute("CREATE DOMAIN ATType AS VARCHAR(200) COLLATE SQL_TEXT_UCC");
- break;
+ switch (databaseVersion) {
+ case 0:
+ // create from new
+ stmt.execute("SET DATABASE DEFAULT TABLE TYPE CACHED");
+ stmt.execute("SET FILES SPACE TRUE");
+ stmt.execute("CREATE TABLE DatabaseInfo ( version INTEGER NOT NULL )");
+ stmt.execute("INSERT INTO DatabaseInfo VALUES ( 0 )");
+ stmt.execute("CREATE DOMAIN BlockSignature AS VARBINARY(128)");
+ stmt.execute("CREATE DOMAIN Signature AS VARBINARY(64)");
+ stmt.execute("CREATE DOMAIN QoraAddress AS VARCHAR(36)");
+ stmt.execute("CREATE DOMAIN QoraPublicKey AS VARBINARY(32)");
+ stmt.execute("CREATE DOMAIN QoraAmount AS DECIMAL(19, 8)");
+ stmt.execute("CREATE DOMAIN RegisteredName AS VARCHAR(400) COLLATE SQL_TEXT_UCC");
+ stmt.execute("CREATE DOMAIN NameData AS VARCHAR(4000)");
+ stmt.execute("CREATE DOMAIN PollName AS VARCHAR(400) COLLATE SQL_TEXT_UCC");
+ stmt.execute("CREATE DOMAIN PollOption AS VARCHAR(400) COLLATE SQL_TEXT_UCC");
+ stmt.execute("CREATE DOMAIN DataHash AS VARCHAR(100)");
+ stmt.execute("CREATE DOMAIN AssetID AS BIGINT");
+ stmt.execute("CREATE DOMAIN AssetName AS VARCHAR(400) COLLATE SQL_TEXT_UCC");
+ stmt.execute("CREATE DOMAIN AssetOrderID AS VARCHAR(100)");
+ stmt.execute("CREATE DOMAIN ATName AS VARCHAR(200) COLLATE SQL_TEXT_UCC");
+ stmt.execute("CREATE DOMAIN ATType AS VARCHAR(200) COLLATE SQL_TEXT_UCC");
+ break;
- case 1:
- // Blocks
- stmt.execute("CREATE TABLE Blocks (signature BlockSignature PRIMARY KEY, version TINYINT NOT NULL, reference BlockSignature, "
- + "transaction_count INTEGER NOT NULL, total_fees QoraAmount NOT NULL, transactions_signature Signature NOT NULL, "
- + "height INTEGER NOT NULL, generation TIMESTAMP NOT NULL, generation_target QoraAmount NOT NULL, "
- + "generator QoraPublicKey NOT NULL, generation_signature Signature NOT NULL, AT_data VARBINARY(20000), AT_fees QoraAmount)");
- stmt.execute("CREATE INDEX BlockHeightIndex ON Blocks (height)");
- stmt.execute("CREATE INDEX BlockGeneratorIndex ON Blocks (generator)");
- stmt.execute("CREATE INDEX BlockReferenceIndex ON Blocks (reference)");
- 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, generation_target QoraAmount NOT NULL, "
+ + "generator QoraPublicKey NOT NULL, generation_signature Signature NOT NULL, AT_data VARBINARY(20000), AT_fees QoraAmount)");
+ stmt.execute("CREATE INDEX BlockHeightIndex ON Blocks (height)");
+ stmt.execute("CREATE INDEX BlockGeneratorIndex ON Blocks (generator)");
+ stmt.execute("CREATE INDEX BlockReferenceIndex ON Blocks (reference)");
+ stmt.execute("SET TABLE Blocks NEW SPACE");
+ break;
- case 2:
- // Generic transactions (null reference, creator and milestone_block for genesis transactions)
- stmt.execute("CREATE TABLE Transactions (signature Signature PRIMARY KEY, reference Signature, type TINYINT NOT NULL, "
- + "creator QoraPublicKey, creation TIMESTAMP NOT NULL, fee QoraAmount NOT NULL, milestone_block BlockSignature)");
- stmt.execute("CREATE INDEX TransactionTypeIndex ON Transactions (type)");
- stmt.execute("CREATE INDEX TransactionCreationIndex ON Transactions (creation)");
- stmt.execute("CREATE INDEX TransactionCreatorIndex ON Transactions (creator)");
- stmt.execute("CREATE INDEX TransactionReferenceIndex ON Transactions (reference)");
- // 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)");
- // Unconfirmed transactions
- // Do we need this? If a transaction doesn't have a corresponding BlockTransactions record then it's unconfirmed?
- stmt.execute("CREATE TABLE UnconfirmedTransactions (signature Signature PRIMARY KEY, expiry TIMESTAMP NOT NULL)");
- stmt.execute("CREATE INDEX UnconfirmedTransactionExpiryIndex ON UnconfirmedTransactions (expiry)");
- // Transaction recipients
- stmt.execute("CREATE TABLE TransactionRecipients (signature Signature, recipient QoraAddress NOT NULL, "
- + "FOREIGN KEY (signature) REFERENCES Transactions (signature) ON DELETE CASCADE)");
- break;
+ case 2:
+ // Generic transactions (null reference, creator and milestone_block for genesis transactions)
+ stmt.execute("CREATE TABLE Transactions (signature Signature PRIMARY KEY, reference Signature, type TINYINT NOT NULL, "
+ + "creator QoraPublicKey, creation TIMESTAMP NOT NULL, fee QoraAmount NOT NULL, milestone_block BlockSignature)");
+ stmt.execute("CREATE INDEX TransactionTypeIndex ON Transactions (type)");
+ stmt.execute("CREATE INDEX TransactionCreationIndex ON Transactions (creation)");
+ stmt.execute("CREATE INDEX TransactionCreatorIndex ON Transactions (creator)");
+ stmt.execute("CREATE INDEX TransactionReferenceIndex ON Transactions (reference)");
+ stmt.execute("SET TABLE Transactions NEW SPACE");
- case 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;
+ // Transaction-Block mapping ("signature" is unique as a transaction cannot be included in more than one block)
+ stmt.execute("CREATE TABLE BlockTransactions (block_signature BlockSignature, sequence INTEGER, transaction_signature Signature, "
+ + "PRIMARY KEY (block_signature, sequence), FOREIGN KEY (transaction_signature) REFERENCES Transactions (signature) ON DELETE CASCADE, "
+ + "FOREIGN KEY (block_signature) REFERENCES Blocks (signature) ON DELETE CASCADE)");
+ stmt.execute("SET TABLE BlockTransactions NEW SPACE");
- 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;
+ // Unconfirmed transactions
+ // Do we need this? If a transaction doesn't have a corresponding BlockTransactions record then it's unconfirmed?
+ stmt.execute("CREATE TABLE UnconfirmedTransactions (signature Signature PRIMARY KEY, expiry TIMESTAMP NOT NULL)");
+ stmt.execute("CREATE INDEX UnconfirmedTransactionExpiryIndex ON UnconfirmedTransactions (expiry)");
- 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;
+ // Transaction recipients
+ stmt.execute("CREATE TABLE TransactionRecipients (signature Signature, recipient QoraAddress NOT NULL, "
+ + "FOREIGN KEY (signature) REFERENCES Transactions (signature) ON DELETE CASCADE)");
+ stmt.execute("SET TABLE TransactionRecipients NEW SPACE");
+ break;
- case 6:
- // Update Name Transactions
- stmt.execute("CREATE TABLE UpdateNameTransactions (signature Signature, owner QoraPublicKey NOT NULL, name RegisteredName NOT NULL, "
- + "new_owner QoraAddress NOT NULL, new_data NameData NOT NULL, "
- + "PRIMARY KEY (signature), FOREIGN KEY (signature) REFERENCES Transactions (signature) ON DELETE CASCADE)");
- break;
+ case 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 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 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 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 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 9:
- // Buy Name Transactions
- stmt.execute("CREATE TABLE BuyNameTransactions (signature Signature, buyer QoraPublicKey NOT NULL, name RegisteredName NOT NULL, "
- + "seller QoraAddress NOT NULL, amount QoraAmount NOT NULL, "
- + "PRIMARY KEY (signature), FOREIGN KEY (signature) REFERENCES Transactions (signature) ON DELETE CASCADE)");
- break;
+ case 6:
+ // Update Name Transactions
+ stmt.execute("CREATE TABLE UpdateNameTransactions (signature Signature, owner QoraPublicKey NOT NULL, name RegisteredName NOT NULL, "
+ + "new_owner QoraAddress NOT NULL, new_data NameData NOT NULL, "
+ + "PRIMARY KEY (signature), FOREIGN KEY (signature) REFERENCES Transactions (signature) ON DELETE CASCADE)");
+ break;
- case 10:
- // Create Poll Transactions
- stmt.execute("CREATE TABLE CreatePollTransactions (signature Signature, creator QoraPublicKey NOT NULL, poll PollName NOT NULL, "
- + "description VARCHAR(4000) NOT NULL, "
- + "PRIMARY KEY (signature), FOREIGN KEY (signature) REFERENCES Transactions (signature) ON DELETE CASCADE)");
- // Poll options. NB: option is implicitly NON NULL and UNIQUE due to being part of compound primary key
- stmt.execute("CREATE TABLE CreatePollTransactionOptions (signature Signature, option PollOption, "
- + "PRIMARY KEY (signature, option), FOREIGN KEY (signature) REFERENCES CreatePollTransactions (signature) ON DELETE CASCADE)");
- // For the future: add flag to polls to allow one or multiple votes per voter
- break;
+ case 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 11:
- // Vote On Poll Transactions
- stmt.execute("CREATE TABLE VoteOnPollTransactions (signature Signature, voter QoraPublicKey NOT NULL, poll PollName NOT NULL, "
- + "option_index INTEGER NOT NULL, "
- + "PRIMARY KEY (signature), FOREIGN KEY (signature) REFERENCES Transactions (signature) ON DELETE CASCADE)");
- break;
+ case 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 12:
- // Arbitrary/Multi-payment Transaction Payments
- stmt.execute("CREATE TABLE SharedTransactionPayments (signature Signature, recipient QoraPublicKey NOT NULL, "
- + "amount QoraAmount NOT NULL, asset AssetID NOT NULL, "
- + "PRIMARY KEY (signature, recipient, asset), FOREIGN KEY (signature) REFERENCES Transactions (signature) ON DELETE CASCADE)");
- break;
+ case 9:
+ // Buy Name Transactions
+ stmt.execute("CREATE TABLE BuyNameTransactions (signature Signature, buyer QoraPublicKey NOT NULL, name RegisteredName NOT NULL, "
+ + "seller QoraAddress NOT NULL, amount QoraAmount NOT NULL, "
+ + "PRIMARY KEY (signature), FOREIGN KEY (signature) REFERENCES Transactions (signature) ON DELETE CASCADE)");
+ break;
- case 13:
- // Arbitrary Transactions
- stmt.execute("CREATE TABLE ArbitraryTransactions (signature Signature, creator QoraPublicKey NOT NULL, service TINYINT NOT NULL, "
- + "data_hash DataHash NOT NULL, "
- + "PRIMARY KEY (signature), FOREIGN KEY (signature) REFERENCES Transactions (signature) ON DELETE CASCADE)");
- // NB: Actual data payload stored elsewhere
- // For the future: data payload should be encrypted, at the very least with transaction's reference as the seed for the encryption key
- break;
+ case 10:
+ // Create Poll Transactions
+ stmt.execute("CREATE TABLE CreatePollTransactions (signature Signature, creator QoraPublicKey NOT NULL, poll PollName NOT NULL, "
+ + "description VARCHAR(4000) NOT NULL, "
+ + "PRIMARY KEY (signature), FOREIGN KEY (signature) REFERENCES Transactions (signature) ON DELETE CASCADE)");
+ // Poll options. NB: option is implicitly NON NULL and UNIQUE due to being part of compound primary key
+ stmt.execute("CREATE TABLE CreatePollTransactionOptions (signature Signature, option PollOption, "
+ + "PRIMARY KEY (signature, option), FOREIGN KEY (signature) REFERENCES CreatePollTransactions (signature) ON DELETE CASCADE)");
+ // For the future: add flag to polls to allow one or multiple votes per voter
+ break;
- case 14:
- // Issue Asset Transactions
- stmt.execute("CREATE TABLE IssueAssetTransactions (signature Signature, creator QoraPublicKey NOT NULL, asset_name AssetName NOT NULL, "
- + "description VARCHAR(4000) NOT NULL, quantity BIGINT NOT NULL, is_divisible BOOLEAN NOT NULL, "
- + "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 11:
+ // Vote On Poll Transactions
+ stmt.execute("CREATE TABLE VoteOnPollTransactions (signature Signature, voter QoraPublicKey NOT NULL, poll PollName NOT NULL, "
+ + "option_index INTEGER NOT NULL, "
+ + "PRIMARY KEY (signature), FOREIGN KEY (signature) REFERENCES Transactions (signature) ON DELETE CASCADE)");
+ break;
- case 15:
- // Transfer Asset Transactions
- stmt.execute("CREATE TABLE TransferAssetTransactions (signature Signature, sender QoraPublicKey NOT NULL, recipient QoraAddress NOT NULL, "
- + "asset AssetID NOT NULL, amount QoraAmount NOT NULL, "
- + "PRIMARY KEY (signature), FOREIGN KEY (signature) REFERENCES Transactions (signature) ON DELETE CASCADE)");
- break;
+ case 12:
+ // Arbitrary/Multi-payment Transaction Payments
+ stmt.execute("CREATE TABLE SharedTransactionPayments (signature Signature, recipient QoraPublicKey NOT NULL, "
+ + "amount QoraAmount NOT NULL, asset AssetID NOT NULL, "
+ + "PRIMARY KEY (signature, recipient, asset), 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 AssetID NOT NULL, have_amount QoraAmount NOT NULL, want_asset AssetID NOT NULL, want_amount QoraAmount NOT NULL, "
- + "PRIMARY KEY (signature), FOREIGN KEY (signature) REFERENCES Transactions (signature) ON DELETE CASCADE)");
- break;
+ case 13:
+ // Arbitrary Transactions
+ stmt.execute("CREATE TABLE ArbitraryTransactions (signature Signature, creator QoraPublicKey NOT NULL, service TINYINT NOT NULL, "
+ + "data_hash DataHash NOT NULL, "
+ + "PRIMARY KEY (signature), FOREIGN KEY (signature) REFERENCES Transactions (signature) ON DELETE CASCADE)");
+ // NB: Actual data payload stored elsewhere
+ // For the future: data payload should be encrypted, at the very least with transaction's reference as the seed for the encryption key
+ break;
- case 17:
- // Cancel Asset Order Transactions
- stmt.execute("CREATE TABLE CancelAssetOrderTransactions (signature Signature, creator QoraPublicKey NOT NULL, "
- + "asset_order AssetOrderID NOT NULL, "
- + "PRIMARY KEY (signature), FOREIGN KEY (signature) REFERENCES Transactions (signature) ON DELETE CASCADE)");
- break;
+ case 14:
+ // Issue Asset Transactions
+ stmt.execute("CREATE TABLE IssueAssetTransactions (signature Signature, creator QoraPublicKey NOT NULL, asset_name AssetName NOT NULL, "
+ + "description VARCHAR(4000) NOT NULL, quantity BIGINT NOT NULL, is_divisible BOOLEAN NOT NULL, "
+ + "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 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 15:
+ // Transfer Asset Transactions
+ stmt.execute("CREATE TABLE TransferAssetTransactions (signature Signature, sender QoraPublicKey NOT NULL, recipient QoraAddress NOT NULL, "
+ + "asset AssetID 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 16:
+ // Create Asset Order Transactions
+ stmt.execute("CREATE TABLE CreateAssetOrderTransactions (signature Signature, creator QoraPublicKey NOT NULL, "
+ + "have_asset AssetID NOT NULL, have_amount QoraAmount NOT NULL, want_asset AssetID NOT NULL, want_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, sender QoraPublicKey NOT NULL, recipient QoraAddress NOT NULL, "
- + "is_text BOOLEAN NOT NULL, is_encrypted BOOLEAN NOT NULL, amount QoraAmount NOT NULL, asset AssetID NOT NULL, data VARBINARY(4000) NOT NULL, "
- + "PRIMARY KEY (signature), FOREIGN KEY (signature) REFERENCES Transactions (signature) ON DELETE CASCADE)");
- break;
+ case 17:
+ // Cancel Asset Order Transactions
+ stmt.execute("CREATE TABLE CancelAssetOrderTransactions (signature Signature, creator QoraPublicKey NOT NULL, "
+ + "asset_order AssetOrderID NOT NULL, "
+ + "PRIMARY KEY (signature), FOREIGN KEY (signature) REFERENCES Transactions (signature) ON DELETE CASCADE)");
+ break;
- default:
- // nothing to do
- return false;
+ 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 20:
+ // Message Transactions
+ stmt.execute("CREATE TABLE MessageTransactions (signature Signature, sender QoraPublicKey NOT NULL, recipient QoraAddress NOT NULL, "
+ + "is_text BOOLEAN NOT NULL, is_encrypted BOOLEAN NOT NULL, amount QoraAmount NOT NULL, asset AssetID NOT NULL, data VARBINARY(4000) NOT NULL, "
+ + "PRIMARY KEY (signature), FOREIGN KEY (signature) REFERENCES Transactions (signature) ON DELETE CASCADE)");
+ break;
+
+ default:
+ // nothing to do
+ return false;
+ }
}
// database was updated
return true;
}
- public int fetchDatabaseVersion() throws SQLException {
+ public static int fetchDatabaseVersion() throws SQLException {
int databaseVersion = 0;
- try {
+ try (final Connection c = DB.getConnection()) {
Statement stmt = c.createStatement();
if (stmt.execute("SELECT version FROM DatabaseInfo")) {
ResultSet rs = stmt.getResultSet();
@@ -253,16 +246,22 @@ public class updates {
return databaseVersion;
}
- public void incrementDatabaseVersion() throws SQLException {
- Statement stmt = c.createStatement();
- assertFalse(stmt.execute("UPDATE DatabaseInfo SET version = version + 1"));
+ public static void incrementDatabaseVersion() throws SQLException {
+ try (final Connection c = DB.getConnection()) {
+ Statement stmt = c.createStatement();
+ assertFalse(stmt.execute("UPDATE DatabaseInfo SET version = version + 1"));
+ }
+ }
+
+ public static void updateDatabase() throws SQLException {
+ while (databaseUpdating())
+ incrementDatabaseVersion();
}
@Test
public void testUpdates() {
try {
- while (databaseUpdating())
- incrementDatabaseVersion();
+ updateDatabase();
} catch (SQLException e) {
e.printStackTrace();
fail();