diff --git a/src/database/DB.java b/src/database/DB.java index 6c5ba0fd..aa2e5449 100644 --- a/src/database/DB.java +++ b/src/database/DB.java @@ -23,11 +23,28 @@ public class DB { private static JDBCPool connectionPool; private static String connectionUrl; + /** + * Open connection pool to database using prior set connection URL. + *
+ * The connection URL must be set via {@link DB#setUrl(String)} before using this call. + * + * @throws SQLException + * @see DB#setUrl(String) + */ public static void open() throws SQLException { connectionPool = new JDBCPool(); connectionPool.setUrl(connectionUrl); } + /** + * Set the database connection URL. + *
+ * Typical example: + *
+ * {@code setUrl("jdbc:hsqldb:file:db/qora")} + * + * @param url + */ public static void setUrl(String url) { connectionUrl = url; } @@ -59,11 +76,44 @@ public class DB { c.prepareStatement("ROLLBACK").execute(); } + /** + * Shutdown database and close all connections in connection pool. + *
+ * Note: any attempts to use an existing connection after this point will fail. Also, any attempts to request a connection using {@link DB#getConnection()} + * will fail. + *
+ * After this method returns, the database can be reopened using {@link DB#open()}. + * + * @throws SQLException + */ public static void close() throws SQLException { getConnection().createStatement().execute("SHUTDOWN"); connectionPool.close(0); } + /** + * Shutdown and delete database, then rebuild it. + *
+ * See {@link DB#close()} for warnings about connections. + *
+ * Note that this only rebuilds the database schema, not the data itself. + * + * @throws SQLException + */ + public static void rebuild() throws SQLException { + // Shutdown database and close any access + DB.close(); + + // Wipe files (if any) + // TODO + + // Re-open clean database + DB.open(); + + // Apply schema updates + DatabaseUpdates.updateDatabase(); + } + /** * Convert InputStream, from ResultSet.getBinaryStream(), into byte[] of set length. * @@ -231,4 +281,24 @@ public class DB { return resultSet; } + /** + * Fetch last value of IDENTITY column after an INSERT statement. + *
+ * Performs "CALL IDENTITY()" SQL statement to retrieve last value used when INSERTing into a table that has an IDENTITY column. + *
+ * Typically used after INSERTing NULL as the IDENTIY column's value to fetch what value was actually stored by HSQLDB.
+ *
+ * @param connection
+ * @return Long
+ * @throws SQLException
+ */
+ public static Long callIdentity(Connection connection) throws SQLException {
+ PreparedStatement preparedStatement = connection.prepareStatement("CALL IDENTITY()");
+ ResultSet resultSet = DB.checkedExecute(preparedStatement);
+ if (resultSet == null)
+ return null;
+
+ return resultSet.getLong(1);
+ }
+
}
diff --git a/src/database/DatabaseUpdates.java b/src/database/DatabaseUpdates.java
new file mode 100644
index 00000000..e65ec7f2
--- /dev/null
+++ b/src/database/DatabaseUpdates.java
@@ -0,0 +1,294 @@
+package database;
+
+import java.sql.Connection;
+import java.sql.ResultSet;
+import java.sql.SQLException;
+import java.sql.Statement;
+
+public class DatabaseUpdates {
+
+ /**
+ * Apply any incremental changes to database schema.
+ *
+ * @throws SQLException
+ */
+ public static void updateDatabase() throws SQLException {
+ while (databaseUpdating())
+ incrementDatabaseVersion();
+ }
+
+ /**
+ * Increment database's schema version.
+ *
+ * @throws SQLException
+ */
+ private static void incrementDatabaseVersion() throws SQLException {
+ try (final Connection c = DB.getConnection()) {
+ Statement stmt = c.createStatement();
+ stmt.execute("UPDATE DatabaseInfo SET version = version + 1");
+ }
+ }
+
+ /**
+ * Fetch current version of database schema.
+ *
+ * @return int, 0 if no schema yet
+ * @throws SQLException
+ */
+ private static int fetchDatabaseVersion() throws SQLException {
+ int databaseVersion = 0;
+
+ try (final Connection c = DB.getConnection()) {
+ Statement stmt = c.createStatement();
+ if (stmt.execute("SELECT version FROM DatabaseInfo")) {
+ ResultSet rs = stmt.getResultSet();
+
+ if (rs.next())
+ databaseVersion = rs.getInt(1);
+ }
+ } catch (SQLException e) {
+ // empty database
+ }
+
+ return databaseVersion;
+ }
+
+ /**
+ * Incrementally update database schema, returning whether an update happened.
+ *
+ * @return true - if a schema update happened, false otherwise
+ * @throws SQLException
+ */
+ private static boolean databaseUpdating() throws SQLException {
+ int databaseVersion = fetchDatabaseVersion();
+
+ 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.
+ *
+ * Only really add "ON DELETE CASCADE" to sub-tables that store type-specific data. For example on sub-types of Transactions like
+ * PaymentTransactions. A counterexample would be adding "ON DELETE CASCADE" to Assets using Assets' "reference" as a foreign key referring to
+ * Transactions' "signature". We want to database to automatically delete complete transaction data (Transactions row and corresponding
+ * PaymentTransactions row), but leave deleting less related table rows (Assets) to the Java logic.
+ */
+
+ switch (databaseVersion) {
+ case 0:
+ // create from new
+ stmt.execute("SET DATABASE DEFAULT TABLE TYPE CACHED");
+ stmt.execute("SET FILES SPACE TRUE");
+ stmt.execute("CREATE TABLE DatabaseInfo ( version INTEGER NOT NULL )");
+ stmt.execute("INSERT INTO DatabaseInfo VALUES ( 0 )");
+ stmt.execute("CREATE 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, generating_balance QoraAmount NOT NULL, "
+ + "generator QoraPublicKey NOT NULL, generator_signature Signature NOT NULL, AT_data VARBINARY(20000), AT_fees QoraAmount)");
+ stmt.execute("CREATE INDEX BlockHeightIndex ON Blocks (height)");
+ stmt.execute("CREATE INDEX BlockGeneratorIndex ON Blocks (generator)");
+ stmt.execute("CREATE INDEX BlockReferenceIndex ON Blocks (reference)");
+ stmt.execute("SET TABLE Blocks NEW SPACE");
+ break;
+
+ case 2:
+ // Generic transactions (null reference, creator and milestone_block for genesis transactions)
+ stmt.execute("CREATE TABLE Transactions (signature Signature PRIMARY KEY, reference Signature, type TINYINT NOT NULL, "
+ + "creator QoraPublicKey, creation TIMESTAMP NOT NULL, fee QoraAmount NOT NULL, milestone_block BlockSignature)");
+ stmt.execute("CREATE INDEX TransactionTypeIndex ON Transactions (type)");
+ stmt.execute("CREATE INDEX TransactionCreationIndex ON Transactions (creation)");
+ stmt.execute("CREATE INDEX TransactionCreatorIndex ON Transactions (creator)");
+ stmt.execute("CREATE INDEX TransactionReferenceIndex ON Transactions (reference)");
+ stmt.execute("SET TABLE Transactions NEW SPACE");
+
+ // Transaction-Block mapping ("signature" is unique as a transaction cannot be included in more than one block)
+ stmt.execute("CREATE TABLE BlockTransactions (block_signature BlockSignature, sequence INTEGER, transaction_signature Signature, "
+ + "PRIMARY KEY (block_signature, sequence), FOREIGN KEY (transaction_signature) REFERENCES Transactions (signature) ON DELETE CASCADE, "
+ + "FOREIGN KEY (block_signature) REFERENCES Blocks (signature) ON DELETE CASCADE)");
+ stmt.execute("SET TABLE BlockTransactions NEW SPACE");
+
+ // Unconfirmed transactions
+ // Do we need this? If a transaction doesn't have a corresponding BlockTransactions record then it's unconfirmed?
+ stmt.execute("CREATE TABLE UnconfirmedTransactions (signature Signature PRIMARY KEY, expiry TIMESTAMP NOT NULL)");
+ stmt.execute("CREATE INDEX UnconfirmedTransactionExpiryIndex ON UnconfirmedTransactions (expiry)");
+
+ // Transaction recipients
+ stmt.execute("CREATE TABLE TransactionRecipients (signature Signature, recipient QoraAddress NOT NULL, "
+ + "FOREIGN KEY (signature) REFERENCES Transactions (signature) ON DELETE CASCADE)");
+ stmt.execute("SET TABLE TransactionRecipients NEW SPACE");
+ break;
+
+ case 3:
+ // Genesis Transactions
+ stmt.execute("CREATE TABLE GenesisTransactions (signature Signature, recipient QoraAddress NOT NULL, "
+ + "amount QoraAmount NOT NULL, PRIMARY KEY (signature), "
+ + "FOREIGN KEY (signature) REFERENCES Transactions (signature) ON DELETE CASCADE)");
+ break;
+
+ case 4:
+ // Payment Transactions
+ stmt.execute("CREATE TABLE PaymentTransactions (signature Signature, sender QoraPublicKey NOT NULL, recipient QoraAddress NOT NULL, "
+ + "amount QoraAmount NOT NULL, PRIMARY KEY (signature), "
+ + "FOREIGN KEY (signature) REFERENCES Transactions (signature) ON DELETE CASCADE)");
+ break;
+
+ case 5:
+ // Register Name Transactions
+ stmt.execute("CREATE TABLE RegisterNameTransactions (signature Signature, registrant QoraPublicKey NOT NULL, name RegisteredName NOT NULL, "
+ + "owner QoraAddress NOT NULL, data NameData NOT NULL, "
+ + "PRIMARY KEY (signature), FOREIGN KEY (signature) REFERENCES Transactions (signature) ON DELETE CASCADE)");
+ break;
+
+ case 6:
+ // Update Name Transactions
+ stmt.execute("CREATE TABLE UpdateNameTransactions (signature Signature, owner QoraPublicKey NOT NULL, name RegisteredName NOT NULL, "
+ + "new_owner QoraAddress NOT NULL, new_data NameData NOT NULL, "
+ + "PRIMARY KEY (signature), FOREIGN KEY (signature) REFERENCES Transactions (signature) ON DELETE CASCADE)");
+ break;
+
+ case 7:
+ // Sell Name Transactions
+ stmt.execute("CREATE TABLE SellNameTransactions (signature Signature, owner QoraPublicKey NOT NULL, name RegisteredName NOT NULL, "
+ + "amount QoraAmount NOT NULL, PRIMARY KEY (signature), FOREIGN KEY (signature) REFERENCES Transactions (signature) ON DELETE CASCADE)");
+ break;
+
+ case 8:
+ // Cancel Sell Name Transactions
+ stmt.execute("CREATE TABLE CancelSellNameTransactions (signature Signature, owner QoraPublicKey NOT NULL, name RegisteredName NOT NULL, "
+ + "PRIMARY KEY (signature), FOREIGN KEY (signature) REFERENCES Transactions (signature) ON DELETE CASCADE)");
+ break;
+
+ case 9:
+ // Buy Name Transactions
+ stmt.execute("CREATE TABLE BuyNameTransactions (signature Signature, buyer QoraPublicKey NOT NULL, name RegisteredName NOT NULL, "
+ + "seller QoraAddress NOT NULL, amount QoraAmount NOT NULL, "
+ + "PRIMARY KEY (signature), FOREIGN KEY (signature) REFERENCES Transactions (signature) ON DELETE CASCADE)");
+ break;
+
+ case 10:
+ // Create Poll Transactions
+ stmt.execute("CREATE TABLE CreatePollTransactions (signature Signature, creator QoraPublicKey NOT NULL, poll PollName NOT NULL, "
+ + "description VARCHAR(4000) NOT NULL, "
+ + "PRIMARY KEY (signature), FOREIGN KEY (signature) REFERENCES Transactions (signature) ON DELETE CASCADE)");
+ // Poll options. NB: option is implicitly NON NULL and UNIQUE due to being part of compound primary key
+ stmt.execute("CREATE TABLE CreatePollTransactionOptions (signature Signature, option PollOption, "
+ + "PRIMARY KEY (signature, option), FOREIGN KEY (signature) REFERENCES CreatePollTransactions (signature) ON DELETE CASCADE)");
+ // For the future: add flag to polls to allow one or multiple votes per voter
+ break;
+
+ case 11:
+ // Vote On Poll Transactions
+ stmt.execute("CREATE TABLE VoteOnPollTransactions (signature Signature, voter QoraPublicKey NOT NULL, poll PollName NOT NULL, "
+ + "option_index INTEGER NOT NULL, "
+ + "PRIMARY KEY (signature), FOREIGN KEY (signature) REFERENCES Transactions (signature) ON DELETE CASCADE)");
+ break;
+
+ case 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 13:
+ // Arbitrary Transactions
+ stmt.execute("CREATE TABLE ArbitraryTransactions (signature Signature, creator QoraPublicKey NOT NULL, service TINYINT NOT NULL, "
+ + "data_hash DataHash NOT NULL, "
+ + "PRIMARY KEY (signature), FOREIGN KEY (signature) REFERENCES Transactions (signature) ON DELETE CASCADE)");
+ // NB: Actual data payload stored elsewhere
+ // For the future: data payload should be encrypted, at the very least with transaction's reference as the seed for the encryption key
+ break;
+
+ case 14:
+ // Issue Asset Transactions
+ stmt.execute("CREATE TABLE IssueAssetTransactions (signature Signature, 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 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 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 17:
+ // Cancel Asset Order Transactions
+ stmt.execute("CREATE TABLE CancelAssetOrderTransactions (signature Signature, creator QoraPublicKey NOT NULL, "
+ + "asset_order AssetOrderID NOT NULL, "
+ + "PRIMARY KEY (signature), FOREIGN KEY (signature) REFERENCES Transactions (signature) ON DELETE CASCADE)");
+ break;
+
+ case 18:
+ // Multi-payment Transactions
+ stmt.execute("CREATE TABLE MultiPaymentTransactions (signature Signature, sender QoraPublicKey NOT NULL, "
+ + "PRIMARY KEY (signature), FOREIGN KEY (signature) REFERENCES Transactions (signature) ON DELETE CASCADE)");
+ break;
+
+ case 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;
+
+ case 21:
+ // Assets (including QORA coin itself)
+ stmt.execute(
+ "CREATE TABLE Assets (asset AssetID IDENTITY, owner QoraAddress NOT NULL, asset_name AssetName NOT NULL, description VARCHAR(4000) NOT NULL, "
+ + "quantity BIGINT NOT NULL, is_divisible BOOLEAN NOT NULL, reference Signature NOT NULL)");
+ break;
+
+ case 22:
+ // Accounts
+ stmt.execute("CREATE TABLE AccountBalances (account QoraAddress, asset AssetID, amount QoraAmount NOT NULL, PRIMARY KEY (account, asset))");
+ break;
+
+ default:
+ // nothing to do
+ return false;
+ }
+ }
+
+ // database was updated
+ return true;
+ }
+
+}
diff --git a/src/qora/account/Account.java b/src/qora/account/Account.java
index 69d81618..3bf5ccaa 100644
--- a/src/qora/account/Account.java
+++ b/src/qora/account/Account.java
@@ -1,5 +1,8 @@
package qora.account;
+import java.math.BigDecimal;
+import java.sql.Connection;
+
public class Account {
public static final int ADDRESS_LENGTH = 25;
@@ -24,4 +27,40 @@ public class Account {
return this.getAddress().equals(((Account) b).getAddress());
}
+
+ // Balance manipulations - "key" is asset ID, or 0 for QORA
+
+ public BigDecimal getBalance(long key, int confirmations) {
+ // TODO
+ return null;
+ }
+
+ public BigDecimal getUnconfirmedBalance(long key) {
+ // TODO
+ return null;
+ }
+
+ public BigDecimal getConfirmedBalance(long key) {
+ // TODO
+ return null;
+ }
+
+ public void setConfirmedBalance(Connection connection, long key, BigDecimal amount) {
+ // TODO
+ return;
+ }
+
+ // Reference manipulations
+
+ public byte[] getLastReference() {
+ // TODO
+ return null;
+ }
+
+ // pass null to remove
+ public void setLastReference(Connection connection, byte[] reference) {
+ // TODO
+ return;
+ }
+
}
diff --git a/src/qora/assets/Asset.java b/src/qora/assets/Asset.java
new file mode 100644
index 00000000..52ad82ff
--- /dev/null
+++ b/src/qora/assets/Asset.java
@@ -0,0 +1,49 @@
+package qora.assets;
+
+import java.sql.Connection;
+import java.sql.PreparedStatement;
+import java.sql.SQLException;
+
+import database.DB;
+import qora.account.PublicKeyAccount;
+
+public class Asset {
+
+ public static final long QORA = 0L;
+
+ // Properties
+ private Long key;
+ private PublicKeyAccount owner;
+ private String name;
+ private String description;
+ private long quantity;
+ private boolean isDivisible;
+ private byte[] reference;
+
+ public Asset(Long key, PublicKeyAccount owner, String name, String description, long quantity, boolean isDivisible, byte[] reference) {
+ this.key = key;
+ this.owner = owner;
+ this.name = name;
+ this.description = description;
+ this.quantity = quantity;
+ this.isDivisible = isDivisible;
+ this.reference = reference;
+ }
+
+ public Asset(PublicKeyAccount owner, String name, String description, long quantity, boolean isDivisible, byte[] reference) {
+ this(null, owner, name, description, quantity, isDivisible, reference);
+ }
+
+ // Load/Save
+
+ public void save(Connection connection) throws SQLException {
+ String sql = DB.formatInsertWithPlaceholders("Assets", "asset", "owner", "asset_name", "description", "quantity", "is_divisible", "reference");
+ PreparedStatement preparedStatement = connection.prepareStatement(sql);
+ DB.bindInsertPlaceholders(preparedStatement, this.key, this.owner.getAddress(), this.name, this.description, this.quantity, this.isDivisible,
+ this.reference);
+ preparedStatement.execute();
+
+ if (this.key == null)
+ this.key = DB.callIdentity(connection);
+ }
+}
diff --git a/src/qora/block/Block.java b/src/qora/block/Block.java
index 1877fc6e..f002b04a 100644
--- a/src/qora/block/Block.java
+++ b/src/qora/block/Block.java
@@ -7,6 +7,7 @@ import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
+import java.sql.Timestamp;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
@@ -20,6 +21,7 @@ import database.DB;
import database.NoDataFoundException;
import qora.account.PrivateKeyAccount;
import qora.account.PublicKeyAccount;
+import qora.assets.Asset;
import qora.transaction.Transaction;
import qora.transaction.TransactionFactory;
@@ -40,9 +42,9 @@ import qora.transaction.TransactionFactory;
* In scenarios (2) and (3) this will need to be set after successful processing,
* but before Block is saved into database.
*
- * GenerationSignature's data is: reference + generationTarget + generator's public key
- * TransactionSignature's data is: generationSignature + transaction signatures
- * Block signature is: generationSignature + transactionsSignature
+ * GeneratorSignature's data is: reference + generatingBalance + generator's public key
+ * TransactionSignature's data is: generatorSignature + transaction signatures
+ * Block signature is: generatorSignature + transactionsSignature
*/
public class Block {
@@ -52,7 +54,7 @@ public class Block {
// Columns when fetching from database
private static final String DB_COLUMNS = "version, reference, transaction_count, total_fees, "
- + "transactions_signature, height, generation, generation_target, generator, generation_signature, " + "AT_data, AT_fees";
+ + "transactions_signature, height, generation, generating_balance, generator, generator_signature, " + "AT_data, AT_fees";
// Database properties
protected int version;
@@ -62,9 +64,9 @@ public class Block {
protected byte[] transactionsSignature;
protected int height;
protected long timestamp;
- protected BigDecimal generationTarget;
+ protected BigDecimal generatingBalance;
protected PublicKeyAccount generator;
- protected byte[] generationSignature;
+ protected byte[] generatorSignature;
protected byte[] atBytes;
protected BigDecimal atFees;
@@ -74,17 +76,17 @@ public class Block {
// Property lengths for serialisation
protected static final int VERSION_LENGTH = 4;
protected static final int TRANSACTIONS_SIGNATURE_LENGTH = 64;
- protected static final int GENERATION_SIGNATURE_LENGTH = 64;
- protected static final int REFERENCE_LENGTH = GENERATION_SIGNATURE_LENGTH + TRANSACTIONS_SIGNATURE_LENGTH;
+ protected static final int GENERATOR_SIGNATURE_LENGTH = 64;
+ protected static final int REFERENCE_LENGTH = GENERATOR_SIGNATURE_LENGTH + TRANSACTIONS_SIGNATURE_LENGTH;
protected static final int TIMESTAMP_LENGTH = 8;
- protected static final int GENERATION_TARGET_LENGTH = 8;
+ protected static final int GENERATING_BALANCE_LENGTH = 8;
protected static final int GENERATOR_LENGTH = 32;
protected static final int TRANSACTION_COUNT_LENGTH = 8;
- protected static final int BASE_LENGTH = VERSION_LENGTH + REFERENCE_LENGTH + TIMESTAMP_LENGTH + GENERATION_TARGET_LENGTH + GENERATOR_LENGTH
- + TRANSACTIONS_SIGNATURE_LENGTH + GENERATION_SIGNATURE_LENGTH + TRANSACTION_COUNT_LENGTH;
+ protected static final int BASE_LENGTH = VERSION_LENGTH + REFERENCE_LENGTH + TIMESTAMP_LENGTH + GENERATING_BALANCE_LENGTH + GENERATOR_LENGTH
+ + TRANSACTIONS_SIGNATURE_LENGTH + GENERATOR_SIGNATURE_LENGTH + TRANSACTION_COUNT_LENGTH;
// Other length constants
- protected static final int BLOCK_SIGNATURE_LENGTH = GENERATION_SIGNATURE_LENGTH + TRANSACTIONS_SIGNATURE_LENGTH;
+ protected static final int BLOCK_SIGNATURE_LENGTH = GENERATOR_SIGNATURE_LENGTH + TRANSACTIONS_SIGNATURE_LENGTH;
public static final int MAX_BLOCK_BYTES = 1048576;
protected static final int TRANSACTION_SIZE_LENGTH = 4; // per transaction
public static final int MAX_TRANSACTION_BYTES = MAX_BLOCK_BYTES - BASE_LENGTH;
@@ -95,24 +97,25 @@ public class Block {
// 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) {
+ protected Block(int version, byte[] reference, long timestamp, BigDecimal generatingBalance, PublicKeyAccount generator, byte[] generatorSignature,
+ byte[] transactionsSignature, byte[] atBytes, BigDecimal atFees) {
this.version = version;
this.reference = reference;
this.timestamp = timestamp;
- this.generationTarget = generationTarget;
+ this.generatingBalance = generatingBalance;
this.generator = generator;
- this.generationSignature = generationSignature;
+ this.generatorSignature = generatorSignature;
this.height = 0;
this.transactionCount = 0;
this.transactions = new ArrayList
* This is handled differently as there is no private key for the genesis account and so no way to sign data.
*
@@ -280,7 +277,7 @@ public class GenesisBlock extends Block {
try {
// 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);
+ ByteArrayOutputStream bytes = new ByteArrayOutputStream(8 + 64 + GENERATING_BALANCE_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,7 +289,7 @@ public class GenesisBlock extends Block {
* will break genesis block signatures!
*/
bytes.write(Bytes.ensureCapacity(GENESIS_REFERENCE, 64, 0));
- bytes.write(Longs.toByteArray(GENESIS_GENERATION_TARGET.longValue()));
+ bytes.write(Longs.toByteArray(GENESIS_GENERATING_BALANCE.longValue()));
// NOTE: Genesis account's public key is only 8 bytes, not the usual 32.
bytes.write(GENESIS_GENERATOR.getPublicKey());
return bytes.toByteArray();
@@ -304,7 +301,7 @@ public class GenesisBlock extends Block {
@Override
public boolean isSignatureValid() {
// Validate block signature
- if (!Arrays.equals(GENESIS_GENERATION_SIGNATURE, this.generationSignature))
+ if (!Arrays.equals(GENESIS_GENERATOR_SIGNATURE, this.generatorSignature))
return false;
// Validate transactions signature
@@ -317,7 +314,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() != 0)
+ if (BlockChain.getHeight() != 0)
return false;
// Validate transactions
diff --git a/src/qora/transaction/GenesisTransaction.java b/src/qora/transaction/GenesisTransaction.java
index d32071db..b5059195 100644
--- a/src/qora/transaction/GenesisTransaction.java
+++ b/src/qora/transaction/GenesisTransaction.java
@@ -202,11 +202,18 @@ public class GenesisTransaction extends Transaction {
return ValidationResult.OK;
}
- public void process() {
+ public void process(Connection connection) throws SQLException {
// TODO
+ this.save(connection);
+
+ // SET recipient's balance
+ // this.recipient.setConfirmedBalance(this.amount, db);
+
+ // Set recipient's reference
+ // recipient.setLastReference(this.signature, db);
}
- public void orphan() {
+ public void orphan(Connection connection) {
// TODO
}
diff --git a/src/qora/transaction/PaymentTransaction.java b/src/qora/transaction/PaymentTransaction.java
index 4a99da96..80d79297 100644
--- a/src/qora/transaction/PaymentTransaction.java
+++ b/src/qora/transaction/PaymentTransaction.java
@@ -173,11 +173,11 @@ public class PaymentTransaction extends Transaction {
return ValidationResult.OK;
}
- public void process() {
+ public void process(Connection connection) {
// TODO
}
- public void orphan() {
+ public void orphan(Connection connection) {
// TODO
}
diff --git a/src/qora/transaction/Transaction.java b/src/qora/transaction/Transaction.java
index 4df98c24..d0018b3d 100644
--- a/src/qora/transaction/Transaction.java
+++ b/src/qora/transaction/Transaction.java
@@ -8,7 +8,6 @@ import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Timestamp;
-import java.time.Instant;
import java.util.Arrays;
import java.util.Map;
import static java.util.Arrays.stream;
@@ -198,7 +197,7 @@ public abstract class Transaction {
if (ourHeight == 0)
return 0;
- int blockChainHeight = BlockChain.getMaxHeight();
+ int blockChainHeight = BlockChain.getHeight();
return blockChainHeight - ourHeight + 1;
}
@@ -224,6 +223,7 @@ public abstract class Transaction {
this.type = type;
this.reference = DB.getResultSetBytes(rs.getBinaryStream(1), REFERENCE_LENGTH);
+ // Note: can't use CREATOR_LENGTH in case we encounter Genesis Account's short, 8-byte public key
this.creator = new PublicKeyAccount(DB.getResultSetBytes(rs.getBinaryStream(2)));
this.timestamp = rs.getTimestamp(3).getTime();
this.fee = rs.getBigDecimal(4).setScale(8);
@@ -234,7 +234,7 @@ public abstract class Transaction {
String sql = DB.formatInsertWithPlaceholders("Transactions", "signature", "reference", "type", "creator", "creation", "fee", "milestone_block");
PreparedStatement preparedStatement = connection.prepareStatement(sql);
DB.bindInsertPlaceholders(preparedStatement, this.signature, this.reference, this.type.value, this.creator.getPublicKey(),
- Timestamp.from(Instant.ofEpochSecond(this.timestamp)), this.fee, null);
+ new Timestamp(this.timestamp), this.fee, null);
preparedStatement.execute();
}
@@ -365,8 +365,8 @@ public abstract class Transaction {
public abstract ValidationResult isValid(Connection connection);
- public abstract void process();
+ public abstract void process(Connection connection) throws SQLException;
- public abstract void orphan();
+ public abstract void orphan(Connection connection);
}
diff --git a/src/test/blockchain.java b/src/test/blockchain.java
new file mode 100644
index 00000000..ef54f5ac
--- /dev/null
+++ b/src/test/blockchain.java
@@ -0,0 +1,16 @@
+package test;
+
+import java.sql.SQLException;
+
+import org.junit.Test;
+
+import qora.block.BlockChain;
+
+public class blockchain extends common {
+
+ @Test
+ public void testRebuild() throws SQLException {
+ BlockChain.validate();
+ }
+
+}
diff --git a/src/test/common.java b/src/test/common.java
index bfb8eff3..6f316e46 100644
--- a/src/test/common.java
+++ b/src/test/common.java
@@ -6,6 +6,7 @@ import org.junit.AfterClass;
import org.junit.BeforeClass;
import database.DB;
+import database.DatabaseUpdates;
public class common {
@@ -15,12 +16,7 @@ public class common {
DB.open();
// Create/update database schema
- try {
- updates.updateDatabase();
- } catch (SQLException e) {
- e.printStackTrace();
- throw e;
- }
+ DatabaseUpdates.updateDatabase();
}
@AfterClass
diff --git a/src/test/connections.java b/src/test/connections.java
index 9ebe21ec..7700f64c 100644
--- a/src/test/connections.java
+++ b/src/test/connections.java
@@ -40,12 +40,23 @@ public class connections extends common {
}
}
- /*
- * @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(); }
- */
+ @Test
+ public void testConnectionAfterShutdown() {
+ try {
+ DB.close();
+ } catch (SQLException e) {
+ e.printStackTrace();
+ fail();
+ }
+
+ try {
+ DB.open();
+ Connection c = DB.getConnection();
+ c.close();
+ } catch (SQLException e) {
+ e.printStackTrace();
+ fail();
+ }
+ }
}
diff --git a/src/test/load.java b/src/test/load.java
index a507577a..471d126f 100644
--- a/src/test/load.java
+++ b/src/test/load.java
@@ -16,7 +16,7 @@ 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);
+ assertTrue("Migrate old database to at least block 49778 before running this test", BlockChain.getHeight() >= 49778);
String signature58 = "1211ZPwG3hk5evWzXCZi9hMDRpwumWmkENjwWkeTCik9xA5uoYnxzF7rwR5hmHH3kG2RXo7ToCAaRc7dvnynByJt";
byte[] signature = Base58.decode(signature58);
@@ -34,7 +34,7 @@ public class load extends common {
@Test
public void testLoadFactory() throws SQLException {
- assertTrue("Migrate old database to at least block 49778 before running this test", BlockChain.getMaxHeight() >= 49778);
+ assertTrue("Migrate old database to at least block 49778 before running this test", BlockChain.getHeight() >= 49778);
String signature58 = "1211ZPwG3hk5evWzXCZi9hMDRpwumWmkENjwWkeTCik9xA5uoYnxzF7rwR5hmHH3kG2RXo7ToCAaRc7dvnynByJt";
byte[] signature = Base58.decode(signature58);
diff --git a/src/test/migrate.java b/src/test/migrate.java
index 33e4610b..6af71b3e 100644
--- a/src/test/migrate.java
+++ b/src/test/migrate.java
@@ -101,7 +101,7 @@ public class migrate extends common {
PreparedStatement blocksPStmt = c
.prepareStatement("INSERT INTO Blocks " + formatWithPlaceholders("signature", "version", "reference", "transaction_count", "total_fees",
- "transactions_signature", "height", "generation", "generation_target", "generator", "generation_signature", "AT_data", "AT_fees"));
+ "transactions_signature", "height", "generation", "generating_balance", "generator", "generator_signature", "AT_data", "AT_fees"));
PreparedStatement txPStmt = c.prepareStatement(
"INSERT INTO Transactions " + formatWithPlaceholders("signature", "reference", "type", "creator", "creation", "fee", "milestone_block"));
@@ -149,7 +149,7 @@ public class migrate extends common {
PreparedStatement blockTxPStmt = c
.prepareStatement("INSERT INTO BlockTransactions " + formatWithPlaceholders("block_signature", "sequence", "transaction_signature"));
- int height = BlockChain.getMaxHeight() + 1;
+ int height = BlockChain.getHeight() + 1;
byte[] milestone_block = null;
System.out.println("Starting migration from block height " + height);
@@ -166,8 +166,8 @@ public class migrate extends common {
DB.startTransaction(c);
// Blocks:
- // signature, version, reference, transaction_count, total_fees, transactions_signature, height, generation, generation_target, generator,
- // generation_signature
+ // signature, version, reference, transaction_count, total_fees, transactions_signature, height, generation, generating_balance, generator,
+ // generator_signature
// varchar, tinyint, varchar, int, decimal, varchar, int, timestamp, decimal, varchar, varchar
byte[] blockSignature = Base58.decode((String) json.get("signature"));
byte[] blockReference = Base58.decode((String) json.get("reference"));
@@ -597,7 +597,7 @@ public class migrate extends common {
}
c.close();
- System.out.println("Migration finished with new blockchain height " + BlockChain.getMaxHeight());
+ System.out.println("Migration finished with new blockchain height " + BlockChain.getHeight());
}
}
diff --git a/src/test/navigation.java b/src/test/navigation.java
index 710ac863..aeeac9cf 100644
--- a/src/test/navigation.java
+++ b/src/test/navigation.java
@@ -15,7 +15,7 @@ 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);
+ assertTrue("Migrate old database to at least block 49778 before running this test", BlockChain.getHeight() >= 49778);
String signature58 = "1211ZPwG3hk5evWzXCZi9hMDRpwumWmkENjwWkeTCik9xA5uoYnxzF7rwR5hmHH3kG2RXo7ToCAaRc7dvnynByJt";
byte[] signature = Base58.decode(signature58);
diff --git a/src/test/signatures.java b/src/test/signatures.java
index efb44d58..f3413b08 100644
--- a/src/test/signatures.java
+++ b/src/test/signatures.java
@@ -17,7 +17,7 @@ public class signatures extends common {
GenesisBlock block = GenesisBlock.getInstance();
- System.out.println("Generator: " + block.getGenerator().getAddress() + ", generation signature: " + Base58.encode(block.getGenerationSignature()));
+ System.out.println("Generator: " + block.getGenerator().getAddress() + ", generator signature: " + Base58.encode(block.getGeneratorSignature()));
assertEquals(expected58, Base58.encode(block.getSignature()));
}
diff --git a/src/test/updates.java b/src/test/updates.java
index f8576db1..8160e59b 100644
--- a/src/test/updates.java
+++ b/src/test/updates.java
@@ -1,271 +1,16 @@
package test;
-import static org.junit.Assert.*;
-
-import java.sql.Connection;
-import java.sql.ResultSet;
import java.sql.SQLException;
-import java.sql.Statement;
import org.junit.Test;
-import database.DB;
+import database.DatabaseUpdates;
public class updates extends common {
- public static boolean databaseUpdating() throws SQLException {
- int databaseVersion = fetchDatabaseVersion();
-
- 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
-
- 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)");
- stmt.execute("SET TABLE Blocks NEW SPACE");
- break;
-
- case 2:
- // Generic transactions (null reference, creator and milestone_block for genesis transactions)
- stmt.execute("CREATE TABLE Transactions (signature Signature PRIMARY KEY, reference Signature, type TINYINT NOT NULL, "
- + "creator QoraPublicKey, creation TIMESTAMP NOT NULL, fee QoraAmount NOT NULL, milestone_block BlockSignature)");
- stmt.execute("CREATE INDEX TransactionTypeIndex ON Transactions (type)");
- stmt.execute("CREATE INDEX TransactionCreationIndex ON Transactions (creation)");
- stmt.execute("CREATE INDEX TransactionCreatorIndex ON Transactions (creator)");
- stmt.execute("CREATE INDEX TransactionReferenceIndex ON Transactions (reference)");
- stmt.execute("SET TABLE Transactions NEW SPACE");
-
- // Transaction-Block mapping ("signature" is unique as a transaction cannot be included in more than one block)
- stmt.execute("CREATE TABLE BlockTransactions (block_signature BlockSignature, sequence INTEGER, transaction_signature Signature, "
- + "PRIMARY KEY (block_signature, sequence), FOREIGN KEY (transaction_signature) REFERENCES Transactions (signature) ON DELETE CASCADE, "
- + "FOREIGN KEY (block_signature) REFERENCES Blocks (signature) ON DELETE CASCADE)");
- stmt.execute("SET TABLE BlockTransactions NEW SPACE");
-
- // Unconfirmed transactions
- // Do we need this? If a transaction doesn't have a corresponding BlockTransactions record then it's unconfirmed?
- stmt.execute("CREATE TABLE UnconfirmedTransactions (signature Signature PRIMARY KEY, expiry TIMESTAMP NOT NULL)");
- stmt.execute("CREATE INDEX UnconfirmedTransactionExpiryIndex ON UnconfirmedTransactions (expiry)");
-
- // Transaction recipients
- stmt.execute("CREATE TABLE TransactionRecipients (signature Signature, recipient QoraAddress NOT NULL, "
- + "FOREIGN KEY (signature) REFERENCES Transactions (signature) ON DELETE CASCADE)");
- stmt.execute("SET TABLE TransactionRecipients NEW SPACE");
- break;
-
- case 3:
- // Genesis Transactions
- stmt.execute("CREATE TABLE GenesisTransactions (signature Signature, recipient QoraAddress NOT NULL, "
- + "amount QoraAmount NOT NULL, PRIMARY KEY (signature), "
- + "FOREIGN KEY (signature) REFERENCES Transactions (signature) ON DELETE CASCADE)");
- break;
-
- case 4:
- // Payment Transactions
- stmt.execute("CREATE TABLE PaymentTransactions (signature Signature, sender QoraPublicKey NOT NULL, recipient QoraAddress NOT NULL, "
- + "amount QoraAmount NOT NULL, PRIMARY KEY (signature), "
- + "FOREIGN KEY (signature) REFERENCES Transactions (signature) ON DELETE CASCADE)");
- break;
-
- case 5:
- // Register Name Transactions
- stmt.execute("CREATE TABLE RegisterNameTransactions (signature Signature, registrant QoraPublicKey NOT NULL, name RegisteredName NOT NULL, "
- + "owner QoraAddress NOT NULL, data NameData NOT NULL, "
- + "PRIMARY KEY (signature), FOREIGN KEY (signature) REFERENCES Transactions (signature) ON DELETE CASCADE)");
- break;
-
- case 6:
- // Update Name Transactions
- stmt.execute("CREATE TABLE UpdateNameTransactions (signature Signature, owner QoraPublicKey NOT NULL, name RegisteredName NOT NULL, "
- + "new_owner QoraAddress NOT NULL, new_data NameData NOT NULL, "
- + "PRIMARY KEY (signature), FOREIGN KEY (signature) REFERENCES Transactions (signature) ON DELETE CASCADE)");
- break;
-
- case 7:
- // Sell Name Transactions
- stmt.execute("CREATE TABLE SellNameTransactions (signature Signature, owner QoraPublicKey NOT NULL, name RegisteredName NOT NULL, "
- + "amount QoraAmount NOT NULL, PRIMARY KEY (signature), FOREIGN KEY (signature) REFERENCES Transactions (signature) ON DELETE CASCADE)");
- break;
-
- case 8:
- // Cancel Sell Name Transactions
- stmt.execute("CREATE TABLE CancelSellNameTransactions (signature Signature, owner QoraPublicKey NOT NULL, name RegisteredName NOT NULL, "
- + "PRIMARY KEY (signature), FOREIGN KEY (signature) REFERENCES Transactions (signature) ON DELETE CASCADE)");
- break;
-
- case 9:
- // Buy Name Transactions
- stmt.execute("CREATE TABLE BuyNameTransactions (signature Signature, buyer QoraPublicKey NOT NULL, name RegisteredName NOT NULL, "
- + "seller QoraAddress NOT NULL, amount QoraAmount NOT NULL, "
- + "PRIMARY KEY (signature), FOREIGN KEY (signature) REFERENCES Transactions (signature) ON DELETE CASCADE)");
- break;
-
- case 10:
- // Create Poll Transactions
- stmt.execute("CREATE TABLE CreatePollTransactions (signature Signature, creator QoraPublicKey NOT NULL, poll PollName NOT NULL, "
- + "description VARCHAR(4000) NOT NULL, "
- + "PRIMARY KEY (signature), FOREIGN KEY (signature) REFERENCES Transactions (signature) ON DELETE CASCADE)");
- // Poll options. NB: option is implicitly NON NULL and UNIQUE due to being part of compound primary key
- stmt.execute("CREATE TABLE CreatePollTransactionOptions (signature Signature, option PollOption, "
- + "PRIMARY KEY (signature, option), FOREIGN KEY (signature) REFERENCES CreatePollTransactions (signature) ON DELETE CASCADE)");
- // For the future: add flag to polls to allow one or multiple votes per voter
- break;
-
- case 11:
- // Vote On Poll Transactions
- stmt.execute("CREATE TABLE VoteOnPollTransactions (signature Signature, voter QoraPublicKey NOT NULL, poll PollName NOT NULL, "
- + "option_index INTEGER NOT NULL, "
- + "PRIMARY KEY (signature), FOREIGN KEY (signature) REFERENCES Transactions (signature) ON DELETE CASCADE)");
- break;
-
- case 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 13:
- // Arbitrary Transactions
- stmt.execute("CREATE TABLE ArbitraryTransactions (signature Signature, creator QoraPublicKey NOT NULL, service TINYINT NOT NULL, "
- + "data_hash DataHash NOT NULL, "
- + "PRIMARY KEY (signature), FOREIGN KEY (signature) REFERENCES Transactions (signature) ON DELETE CASCADE)");
- // NB: Actual data payload stored elsewhere
- // For the future: data payload should be encrypted, at the very least with transaction's reference as the seed for the encryption key
- break;
-
- case 14:
- // Issue Asset Transactions
- stmt.execute("CREATE TABLE IssueAssetTransactions (signature Signature, 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 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 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 17:
- // Cancel Asset Order Transactions
- stmt.execute("CREATE TABLE CancelAssetOrderTransactions (signature Signature, creator QoraPublicKey NOT NULL, "
- + "asset_order AssetOrderID NOT NULL, "
- + "PRIMARY KEY (signature), FOREIGN KEY (signature) REFERENCES Transactions (signature) ON DELETE CASCADE)");
- break;
-
- case 18:
- // Multi-payment Transactions
- stmt.execute("CREATE TABLE MultiPaymentTransactions (signature Signature, sender QoraPublicKey NOT NULL, "
- + "PRIMARY KEY (signature), FOREIGN KEY (signature) REFERENCES Transactions (signature) ON DELETE CASCADE)");
- break;
-
- case 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 static int fetchDatabaseVersion() throws SQLException {
- int databaseVersion = 0;
-
- try (final Connection c = DB.getConnection()) {
- Statement stmt = c.createStatement();
- if (stmt.execute("SELECT version FROM DatabaseInfo")) {
- ResultSet rs = stmt.getResultSet();
- assertNotNull(rs);
-
- assertTrue(rs.next());
-
- databaseVersion = rs.getInt(1);
- }
- } catch (SQLException e) {
- // empty database?
- }
-
- return databaseVersion;
- }
-
- 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 {
- updateDatabase();
- } catch (SQLException e) {
- e.printStackTrace();
- fail();
- }
+ public void testUpdates() throws SQLException {
+ DatabaseUpdates.updateDatabase();
}
}