More work on Blocks, refactor to using public key in DB, etc.

Added brokenmd160.java as command-line support for producing broken MD160 digests.

Transactions, and sub-classes, now use/store public key instead of Qora address.
(Qora address can be derived from public key and they take up about the same space in DB).

Loads more JavaDoc for lovely mouseover help in Eclipse IDE.

Crypto.verify() and Crypto.sign() moved into PublicKeyAccount and PrivateKeyAccount
as appropriate.

Fleshed out Block, added BlockTransactions support.

Added TODO comments as Eclipse helpfully lists these for later implementation.

Made loading-from-DB-constructors protected/private and also throw NoDataFoundException
if unable to load from DB. Public methods can call respective constructors, catch the above
exception and return null if they like. Load-from-DB-constructors are to allow sub-classes
to load some data from sub-tables and super-class to load from another table.
(See PaymentTransaction/Transaction for example). Using public methods allows similar argument
lists but with different names,
e.g. DBObject.fromSignature(Connection, byte[]) and DBObject.fromReference(Connection, byte[])

Saving into DB maybe still a bit untidy. Looking for a way to close-couple column names with
place-holder bind Objects.
Less of:
connection.prepareStatement("INSERT INTO table (column) VALUES (?)")
DB.bindInsertPlaceholders(PreparedStatement, Object...);
More like:
DB.insertUpdate(String tableName, SomeMagicCloseCoupledPairs...)
called like:
DB.insertUpdate("Cats", {"name", "Tiddles"}, {"age", 3});
This commit is contained in:
catbref 2018-05-17 17:39:55 +01:00
parent 7fa82b07e9
commit b90a486039
20 changed files with 699 additions and 182 deletions

21
src/brokenmd160.java Normal file
View File

@ -0,0 +1,21 @@
import com.google.common.hash.HashCode;
import qora.crypto.BrokenMD160;
@SuppressWarnings("deprecation")
public class brokenmd160 {
public static void main(String args[]) {
if (args.length == 0) {
System.err.println("usage: broken-md160 <hex>\noutputs: hex");
System.exit(1);
}
byte[] raw = HashCode.fromString(args[0]).asBytes();
BrokenMD160 brokenMD160 = new BrokenMD160();
byte[] digest = brokenMD160.digest(raw);
System.out.println(HashCode.fromBytes(digest).toString());
}
}

View File

@ -26,6 +26,13 @@ public class DB {
c.prepareStatement("ROLLBACK").execute(); c.prepareStatement("ROLLBACK").execute();
} }
/**
* Convert InputStream, from ResultSet.getBinaryStream(), into byte[] of set length.
*
* @param inputStream
* @param length
* @return byte[length]
*/
public static byte[] getResultSetBytes(InputStream inputStream, int length) { public static byte[] getResultSetBytes(InputStream inputStream, int length) {
if (inputStream == null) if (inputStream == null)
return null; return null;
@ -42,6 +49,12 @@ public class DB {
return null; return null;
} }
/**
* Convert InputStream, from ResultSet.getBinaryStream(), into byte[] of unknown length.
*
* @param inputStream
* @return byte[]
*/
public static byte[] getResultSetBytes(InputStream inputStream) { public static byte[] getResultSetBytes(InputStream inputStream) {
final int BYTE_BUFFER_LENGTH = 1024; final int BYTE_BUFFER_LENGTH = 1024;
@ -64,6 +77,19 @@ public class DB {
return result; return result;
} }
/**
* Format table and column names into an INSERT INTO ... SQL statement.
* <p>
* Full form is:
* <p>
* INSERT INTO <I>table</I> (<I>column</I>, ...) VALUES (?, ...) ON DUPLICATE KEY UPDATE <I>column</I>=?, ...
* <p>
* Note that HSQLDB needs to put into mySQL compatibility mode first via "SET DATABASE SQL SYNTAX MYS TRUE".
*
* @param table
* @param columns
* @return String
*/
public static String formatInsertWithPlaceholders(String table, String... columns) { public static String formatInsertWithPlaceholders(String table, String... columns) {
String[] placeholders = new String[columns.length]; String[] placeholders = new String[columns.length];
Arrays.setAll(placeholders, (int i) -> "?"); Arrays.setAll(placeholders, (int i) -> "?");
@ -81,9 +107,18 @@ public class DB {
return output.toString(); return output.toString();
} }
/**
* Binds Objects to PreparedStatement based on INSERT INTO ... ON DUPLICATE KEY UPDATE ...
* <p>
* Note that each object is bound to <b>two</b> place-holders based on this SQL syntax:
* <p>
* INSERT INTO <I>table</I> (<I>column</I>, ...) VALUES (<b>?</b>, ...) ON DUPLICATE KEY UPDATE <I>column</I>=<b>?</b>, ...
*
* @param preparedStatement
* @param objects
* @throws SQLException
*/
public static void bindInsertPlaceholders(PreparedStatement preparedStatement, Object... objects) throws SQLException { public static void bindInsertPlaceholders(PreparedStatement preparedStatement, Object... objects) throws SQLException {
// We need to bind two sets of placeholders based on this syntax:
// INSERT INTO table (column, ... ) VALUES (?, ...) ON DUPLICATE KEY UPDATE SET column=?, ...
for (int i = 0; i < objects.length; ++i) { for (int i = 0; i < objects.length; ++i) {
Object object = objects[i]; Object object = objects[i];
@ -126,4 +161,24 @@ public class DB {
return rs; return rs;
} }
/**
* Execute PreparedStatement and return ResultSet with but added checking
*
* @param preparedStatement
* @return ResultSet, or null if there are no found rows
* @throws SQLException
*/
public static ResultSet checkedExecute(PreparedStatement preparedStatement) throws SQLException {
if (!preparedStatement.execute())
throw new SQLException("Fetching from database produced no results");
ResultSet resultSet = preparedStatement.getResultSet();
if (resultSet == null)
throw new SQLException("Fetching results from database produced no ResultSet");
if (!resultSet.next())
return null;
return resultSet;
}
} }

View File

@ -2,6 +2,10 @@ package database;
import java.sql.SQLException; import java.sql.SQLException;
/**
* Exception for use in DB-backed constructors to indicate no matching data found.
*
*/
@SuppressWarnings("serial") @SuppressWarnings("serial")
public class NoDataFoundException extends SQLException { public class NoDataFoundException extends SQLException {

View File

@ -17,14 +17,6 @@ public class Account {
return address; return address;
} }
// TOSTRING
@Override
public int hashCode() {
return this.getAddress().hashCode();
}
// EQUALS
@Override @Override
public boolean equals(Object b) { public boolean equals(Object b) {
if (!(b instanceof Account)) if (!(b instanceof Account))

View File

@ -1,6 +1,7 @@
package qora.account; package qora.account;
import qora.crypto.Crypto; import qora.crypto.Crypto;
import qora.crypto.Ed25519;
import utils.Pair; import utils.Pair;
public class PrivateKeyAccount extends PublicKeyAccount { public class PrivateKeyAccount extends PublicKeyAccount {
@ -10,7 +11,7 @@ public class PrivateKeyAccount extends PublicKeyAccount {
public PrivateKeyAccount(byte[] seed) { public PrivateKeyAccount(byte[] seed) {
this.seed = seed; this.seed = seed;
this.keyPair = Crypto.createKeyPair(seed); this.keyPair = Ed25519.createKeyPair(seed);
this.publicKey = keyPair.getB(); this.publicKey = keyPair.getB();
this.address = Crypto.toAddress(this.publicKey); this.address = Crypto.toAddress(this.publicKey);
} }
@ -27,4 +28,12 @@ public class PrivateKeyAccount extends PublicKeyAccount {
return this.keyPair; return this.keyPair;
} }
public byte[] sign(byte[] message) {
try {
return Ed25519.sign(this.keyPair, message);
} catch (Exception e) {
return null;
}
}
} }

View File

@ -1,6 +1,7 @@
package qora.account; package qora.account;
import qora.crypto.Crypto; import qora.crypto.Crypto;
import qora.crypto.Ed25519;
public class PublicKeyAccount extends Account { public class PublicKeyAccount extends Account {
@ -18,4 +19,12 @@ public class PublicKeyAccount extends Account {
return publicKey; return publicKey;
} }
public boolean verify(byte[] signature, byte[] message) {
try {
return Ed25519.verify(signature, message, this.publicKey);
} catch (Exception e) {
return false;
}
}
} }

View File

@ -1,20 +1,24 @@
package qora.block; package qora.block;
import java.io.ByteArrayInputStream;
import java.math.BigDecimal; import java.math.BigDecimal;
import java.sql.Connection; import java.sql.Connection;
import java.sql.PreparedStatement; import java.sql.PreparedStatement;
import java.sql.ResultSet; import java.sql.ResultSet;
import java.sql.SQLException; import java.sql.SQLException;
import java.util.ArrayList;
import java.util.List;
import org.json.simple.JSONObject; import org.json.simple.JSONObject;
import com.google.common.primitives.Bytes; import com.google.common.primitives.Bytes;
import database.DB; import database.DB;
import database.NoDataFoundException;
import qora.account.PrivateKeyAccount; import qora.account.PrivateKeyAccount;
import qora.account.PublicKeyAccount; import qora.account.PublicKeyAccount;
import qora.crypto.Crypto;
import qora.transaction.Transaction; import qora.transaction.Transaction;
import qora.transaction.TransactionFactory;
/* /*
* Typical use-case scenarios: * Typical use-case scenarios:
@ -43,7 +47,11 @@ public class Block {
// Validation results // Validation results
public static final int VALIDATE_OK = 1; public static final int VALIDATE_OK = 1;
// Database properties shared with all block types // 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";
// Database properties
protected int version; protected int version;
protected byte[] reference; protected byte[] reference;
protected int transactionCount; protected int transactionCount;
@ -52,11 +60,14 @@ public class Block {
protected int height; protected int height;
protected long timestamp; protected long timestamp;
protected BigDecimal generationTarget; protected BigDecimal generationTarget;
protected String generator; protected PublicKeyAccount generator;
protected byte[] generationSignature; protected byte[] generationSignature;
protected byte[] atBytes; protected byte[] atBytes;
protected BigDecimal atFees; protected BigDecimal atFees;
// Other properties
protected List<Transaction> transactions;
// Property lengths for serialisation // Property lengths for serialisation
protected static final int VERSION_LENGTH = 4; protected static final int VERSION_LENGTH = 4;
protected static final int REFERENCE_LENGTH = 64; protected static final int REFERENCE_LENGTH = 64;
@ -70,24 +81,27 @@ public class Block {
+ TRANSACTIONS_SIGNATURE_LENGTH + GENERATION_SIGNATURE_LENGTH + TRANSACTION_COUNT_LENGTH; + TRANSACTIONS_SIGNATURE_LENGTH + GENERATION_SIGNATURE_LENGTH + TRANSACTION_COUNT_LENGTH;
// Other length constants // Other length constants
protected static final int BLOCK_SIGNATURE_LENGTH = GENERATION_SIGNATURE_LENGTH + TRANSACTIONS_SIGNATURE_LENGTH;
public static final int MAX_BLOCK_BYTES = 1048576; public static final int MAX_BLOCK_BYTES = 1048576;
protected static final int TRANSACTION_SIZE_LENGTH = 4; protected static final int TRANSACTION_SIZE_LENGTH = 4; // per transaction
public static final int MAX_TRANSACTION_BYTES = MAX_BLOCK_BYTES - BASE_LENGTH - TRANSACTION_SIZE_LENGTH; public static final int MAX_TRANSACTION_BYTES = MAX_BLOCK_BYTES - BASE_LENGTH;
protected static final int AT_BYTES_LENGTH = 4; protected static final int AT_BYTES_LENGTH = 4;
protected static final int AT_FEES_LENGTH = 8; protected static final int AT_FEES_LENGTH = 8;
protected static final int AT_LENGTH = AT_FEES_LENGTH + AT_BYTES_LENGTH; protected static final int AT_LENGTH = AT_FEES_LENGTH + AT_BYTES_LENGTH;
// Constructors // Constructors
protected Block(int version, byte[] reference, long timestamp, BigDecimal generationTarget, String generator, byte[] generationSignature, byte[] atBytes, protected Block(int version, byte[] reference, long timestamp, BigDecimal generationTarget, PublicKeyAccount generator, byte[] generationSignature,
BigDecimal atFees) { byte[] atBytes, BigDecimal atFees) {
this.version = version; this.version = version;
this.reference = reference; this.reference = reference;
this.timestamp = timestamp; this.timestamp = timestamp;
this.generationTarget = generationTarget; this.generationTarget = generationTarget;
this.generator = generator; this.generator = generator;
this.generationSignature = generationSignature; this.generationSignature = generationSignature;
this.height = 0;
this.transactionCount = 0; this.transactionCount = 0;
this.transactions = null;
this.transactionsSignature = null; this.transactionsSignature = null;
this.totalFees = null; this.totalFees = null;
@ -113,7 +127,7 @@ public class Block {
return this.generationTarget; return this.generationTarget;
} }
public String getGenerator() { public PublicKeyAccount getGenerator() {
return this.generator; return this.generator;
} }
@ -141,6 +155,10 @@ public class Block {
return this.atFees; return this.atFees;
} }
public int getHeight() {
return this.height;
}
// More information // More information
public byte[] getSignature() { public byte[] getSignature() {
@ -151,17 +169,59 @@ public class Block {
} }
public int getDataLength() { public int getDataLength() {
return 0; int blockLength = BASE_LENGTH;
if (version >= 2 && this.atBytes != null)
blockLength += AT_FEES_LENGTH + AT_BYTES_LENGTH + this.atBytes.length;
// Short cut for no transactions
if (this.transactions == null || this.transactions.isEmpty())
return blockLength;
for (Transaction transaction : this.transactions)
blockLength += TRANSACTION_SIZE_LENGTH + transaction.getDataLength();
return blockLength;
}
public List<Transaction> getTransactions() {
return this.transactions;
}
public List<Transaction> getTransactions(Connection connection) throws SQLException {
// Already loaded?
if (this.transactions != null)
return this.transactions;
// Load from DB
this.transactions = new ArrayList<Transaction>();
PreparedStatement preparedStatement = connection.prepareStatement("SELECT transaction_signature FROM BlockTransactions WHERE block_signature = ?");
preparedStatement.setBinaryStream(1, new ByteArrayInputStream(this.getSignature()));
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");
while (rs.next()) {
byte[] transactionSignature = DB.getResultSetBytes(rs.getBinaryStream(1), Transaction.SIGNATURE_LENGTH);
this.transactions.add(TransactionFactory.fromSignature(connection, transactionSignature));
}
return this.transactions;
} }
// Load/Save // Load/Save
protected Block(Connection connection, byte[] signature) throws SQLException { protected Block(Connection connection, byte[] signature) throws SQLException {
ResultSet rs = DB.executeUsingBytes(connection, this(DB.executeUsingBytes(connection, "SELECT " + DB_COLUMNS + " FROM Blocks WHERE signature = ?", signature));
"SELECT version, reference, transaction_count, total_fees, " }
+ "transactions_signature, height, generation, generation_target, generator, generation_signature, "
+ "AT_data, AT_fees FROM Blocks WHERE signature = ?", protected Block(ResultSet rs) throws SQLException {
signature); if (rs == null)
throw new NoDataFoundException();
this.version = rs.getInt(1); this.version = rs.getInt(1);
this.reference = DB.getResultSetBytes(rs.getBinaryStream(2), REFERENCE_LENGTH); this.reference = DB.getResultSetBytes(rs.getBinaryStream(2), REFERENCE_LENGTH);
@ -171,18 +231,53 @@ public class Block {
this.height = rs.getInt(6); this.height = rs.getInt(6);
this.timestamp = rs.getTimestamp(7).getTime(); this.timestamp = rs.getTimestamp(7).getTime();
this.generationTarget = rs.getBigDecimal(8); this.generationTarget = rs.getBigDecimal(8);
this.generator = rs.getString(9); this.generator = new PublicKeyAccount(DB.getResultSetBytes(rs.getBinaryStream(9), GENERATOR_LENGTH));
this.generationSignature = DB.getResultSetBytes(rs.getBinaryStream(10), GENERATION_SIGNATURE_LENGTH); this.generationSignature = DB.getResultSetBytes(rs.getBinaryStream(10), GENERATION_SIGNATURE_LENGTH);
this.atBytes = DB.getResultSetBytes(rs.getBinaryStream(11)); this.atBytes = DB.getResultSetBytes(rs.getBinaryStream(11));
this.atFees = rs.getBigDecimal(12); this.atFees = rs.getBigDecimal(12);
} }
/**
* 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 {
try {
return new Block(connection, signature);
} catch (NoDataFoundException e) {
return null;
}
}
/**
* 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);
try {
return new Block(DB.checkedExecute(preparedStatement));
} catch (NoDataFoundException e) {
return null;
}
}
protected void save(Connection connection) throws SQLException { protected void save(Connection connection) throws SQLException {
String sql = DB.formatInsertWithPlaceholders("Blocks", "version", "reference", "transaction_count", "total_fees", "transactions_signature", "height", String sql = DB.formatInsertWithPlaceholders("Blocks", "version", "reference", "transaction_count", "total_fees", "transactions_signature", "height",
"generation", "generation_target", "generator", "generation_signature", "AT_data", "AT_fees"); "generation", "generation_target", "generator", "generation_signature", "AT_data", "AT_fees");
PreparedStatement preparedStatement = connection.prepareStatement(sql); PreparedStatement preparedStatement = connection.prepareStatement(sql);
DB.bindInsertPlaceholders(preparedStatement, this.version, this.reference, this.transactionCount, this.totalFees, this.transactionsSignature, DB.bindInsertPlaceholders(preparedStatement, this.version, this.reference, this.transactionCount, this.totalFees, this.transactionsSignature,
this.height, this.timestamp, this.generationTarget, this.generator, this.generationSignature, this.atBytes, this.atFees); this.height, this.timestamp, this.generationTarget, this.generator.getPublicKey(), this.generationSignature, this.atBytes, this.atFees);
preparedStatement.execute(); preparedStatement.execute();
// Save transactions // Save transactions
@ -191,19 +286,58 @@ public class Block {
// Navigation // Navigation
/**
* Load parent Block from DB
*
* @param connection
* @return Block, or null if not found
* @throws SQLException
*/
public Block getParent(Connection connection) throws SQLException {
try {
return new Block(connection, this.reference);
} catch (NoDataFoundException e) {
return null;
}
}
/**
* Load child Block from DB
*
* @param connection
* @return Block, or null if not found
* @throws SQLException
*/
public Block getChild(Connection connection) throws SQLException {
byte[] blockSignature = this.getSignature();
if (blockSignature == null)
return null;
ResultSet resultSet = DB.executeUsingBytes(connection, "SELECT " + DB_COLUMNS + " FROM Blocks WHERE reference = ?", blockSignature);
try {
return new Block(resultSet);
} catch (NoDataFoundException e) {
return null;
}
}
// Converters // Converters
public JSONObject toJSON() { public JSONObject toJSON() {
// TODO
return null; return null;
} }
public byte[] toBytes() { public byte[] toBytes() {
// TODO
return null; return null;
} }
// Processing // Processing
public boolean addTransaction(Transaction transaction) { public boolean addTransaction(Transaction transaction) {
// TODO
// Check there is space in block // Check there is space in block
// Add to block // Add to block
// Update transaction count // Update transaction count
@ -212,23 +346,26 @@ public class Block {
} }
public byte[] calcSignature(PrivateKeyAccount signer) { public byte[] calcSignature(PrivateKeyAccount signer) {
byte[] bytes = this.toBytes(); // TODO
return null;
return Crypto.sign(signer, bytes);
} }
public boolean isSignatureValid(PublicKeyAccount signer) { public boolean isSignatureValid(PublicKeyAccount signer) {
// TODO
return false; return false;
} }
public int isValid() { public int isValid() {
// TODO
return VALIDATE_OK; return VALIDATE_OK;
} }
public void process() { public void process() {
// TODO
} }
public void orphan() { public void orphan() {
// TODO
} }
} }

View File

@ -0,0 +1,150 @@
package qora.block;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.io.ByteArrayInputStream;
import org.json.simple.JSONObject;
import database.DB;
import database.NoDataFoundException;
import qora.transaction.Transaction;
import qora.transaction.TransactionFactory;
public class BlockTransaction {
// Database properties shared with all transaction types
protected byte[] blockSignature;
protected int sequence;
protected byte[] transactionSignature;
// Constructors
protected BlockTransaction(byte[] blockSignature, int sequence, byte[] transactionSignature) {
this.blockSignature = blockSignature;
this.sequence = sequence;
this.transactionSignature = transactionSignature;
}
// Getters/setters
public byte[] getBlockSignature() {
return this.blockSignature;
}
public int getSequence() {
return this.sequence;
}
public byte[] getTransactionSignature() {
return this.transactionSignature;
}
// More information
// 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);
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);
}
protected BlockTransaction(Connection connection, byte[] transactionSignature) throws SQLException {
ResultSet rs = DB.executeUsingBytes(connection, "SELECT block_signature, sequence FROM BlockTransactions WHERE transaction_signature = ?",
transactionSignature);
if (rs == null)
throw new NoDataFoundException();
this.blockSignature = DB.getResultSetBytes(rs.getBinaryStream(1), Block.BLOCK_SIGNATURE_LENGTH);
this.sequence = rs.getInt(2);
this.transactionSignature = transactionSignature;
}
/**
* 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 {
try {
return new BlockTransaction(connection, blockSignature, sequence);
} catch (NoDataFoundException e) {
return null;
}
}
/**
* 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 {
try {
return new BlockTransaction(connection, transactionSignature);
} catch (NoDataFoundException e) {
return null;
}
}
protected void save(Connection connection) throws SQLException {
String sql = DB.formatInsertWithPlaceholders("BlockTransactions", "block_signature", "sequence", "transaction_signature");
PreparedStatement preparedStatement = connection.prepareStatement(sql);
DB.bindInsertPlaceholders(preparedStatement, this.blockSignature, this.sequence, this.transactionSignature);
preparedStatement.execute();
}
// Navigation
/**
* 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);
}
/**
* 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);
}
// Converters
public JSONObject toJSON() {
// TODO
return null;
}
// Processing
}

View File

@ -1,9 +1,9 @@
package qora.crypto; package qora.crypto;
/** /**
* BROKEN RIPEMD160 * <b>BROKEN RIPEMD160</b>
* <p> * <p>
* DO NOT USE in future code as this implementation is BROKEN and returns incorrect digests for some inputs. * <b>DO NOT USE in future code</b> as this implementation is BROKEN and returns incorrect digests for some inputs.
* <p> * <p>
* It is only "grand-fathered" here to produce correct QORA addresses. * It is only "grand-fathered" here to produce correct QORA addresses.
*/ */

View File

@ -4,18 +4,11 @@ import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException; import java.security.NoSuchAlgorithmException;
import java.util.Arrays; import java.util.Arrays;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import qora.account.Account; import qora.account.Account;
import qora.account.PrivateKeyAccount;
import utils.Base58; import utils.Base58;
import utils.Pair;
public class Crypto { public class Crypto {
private static final Logger LOGGER = LogManager.getLogger(Crypto.class);
public static final byte ADDRESS_VERSION = 58; public static final byte ADDRESS_VERSION = 58;
public static final byte AT_ADDRESS_VERSION = 23; public static final byte AT_ADDRESS_VERSION = 23;
@ -34,16 +27,6 @@ public class Crypto {
return digest(digest(input)); return digest(digest(input));
} }
public static Pair<byte[], byte[]> createKeyPair(byte[] seed) {
try {
// Generate private and public key pair
return Ed25519.createKeyPair(seed);
} catch (Exception e) {
LOGGER.error(e.getMessage(), e);
return null;
}
}
@SuppressWarnings("deprecation") @SuppressWarnings("deprecation")
private static String toAddress(byte addressVersion, byte[] input) { private static String toAddress(byte addressVersion, byte[] input) {
// SHA2-256 input to create new data and of known size // SHA2-256 input to create new data and of known size
@ -80,7 +63,7 @@ public class Crypto {
public static boolean isValidAddress(String address) { public static boolean isValidAddress(String address) {
byte[] addressBytes; byte[] addressBytes;
try { try {
// Attempt Base58 decoding // Attempt Base58 decoding
addressBytes = Base58.decode(address); addressBytes = Base58.decode(address);
@ -98,33 +81,13 @@ public class Crypto {
case AT_ADDRESS_VERSION: case AT_ADDRESS_VERSION:
byte[] addressWithoutChecksum = Arrays.copyOf(addressBytes, addressBytes.length - 4); byte[] addressWithoutChecksum = Arrays.copyOf(addressBytes, addressBytes.length - 4);
byte[] passedChecksum = Arrays.copyOfRange(addressWithoutChecksum, addressBytes.length - 4, addressBytes.length); byte[] passedChecksum = Arrays.copyOfRange(addressWithoutChecksum, addressBytes.length - 4, addressBytes.length);
byte[] generatedChecksum = doubleDigest(addressWithoutChecksum); byte[] generatedChecksum = doubleDigest(addressWithoutChecksum);
return Arrays.equals(passedChecksum, generatedChecksum); return Arrays.equals(passedChecksum, generatedChecksum);
default: default:
return false; return false;
} }
} }
public static byte[] sign(PrivateKeyAccount account, byte[] message) {
try {
// GET SIGNATURE
return Ed25519.sign(account.getKeyPair(), message);
} catch (Exception e) {
LOGGER.error(e.getMessage(),e);
return new byte[64];
}
}
public static boolean verify(byte[] publicKey, byte[] signature, byte[] message) {
try {
// VERIFY SIGNATURE
return Ed25519.verify(signature, message, publicKey);
} catch (Exception e) {
LOGGER.error(e.getMessage(),e);
return false;
}
}
} }

View File

@ -10,12 +10,12 @@ import org.json.simple.JSONObject;
import database.DB; import database.DB;
import database.NoDataFoundException; import database.NoDataFoundException;
import qora.account.PublicKeyAccount;
public class PaymentTransaction extends Transaction { public class PaymentTransaction extends Transaction {
// Properties // Properties
// private PublicKeyAccount sender; private PublicKeyAccount sender;
private String sender;
private String recipient; private String recipient;
private BigDecimal amount; private BigDecimal amount;
@ -27,7 +27,8 @@ public class PaymentTransaction extends Transaction {
// Constructors // Constructors
public PaymentTransaction(String sender, String recipient, BigDecimal amount, BigDecimal fee, long timestamp, byte[] reference, byte[] signature) { public PaymentTransaction(PublicKeyAccount sender, String recipient, BigDecimal amount, BigDecimal fee, long timestamp, byte[] reference,
byte[] signature) {
super(TransactionType.Payment, fee, sender, timestamp, reference, signature); super(TransactionType.Payment, fee, sender, timestamp, reference, signature);
this.sender = sender; this.sender = sender;
@ -35,13 +36,13 @@ public class PaymentTransaction extends Transaction {
this.amount = amount; this.amount = amount;
} }
public PaymentTransaction(String sender, String recipient, BigDecimal amount, BigDecimal fee, long timestamp, byte[] reference) { public PaymentTransaction(PublicKeyAccount sender, String recipient, BigDecimal amount, BigDecimal fee, long timestamp, byte[] reference) {
this(sender, recipient, amount, fee, timestamp, reference, null); this(sender, recipient, amount, fee, timestamp, reference, null);
} }
// Getters/Setters // Getters/Setters
public String getSender() { public PublicKeyAccount getSender() {
return this.sender; return this.sender;
} }
@ -61,25 +62,50 @@ public class PaymentTransaction extends Transaction {
// Load/Save // Load/Save
public PaymentTransaction(Connection connection, byte[] signature) throws SQLException { /**
* 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); super(connection, TransactionType.Payment, signature);
ResultSet rs = DB.executeUsingBytes(connection, "SELECT sender, recipient, amount FROM PaymentTransactions WHERE signature = ?", signature); ResultSet rs = DB.executeUsingBytes(connection, "SELECT sender, recipient, amount FROM PaymentTransactions WHERE signature = ?", signature);
if (rs == null) if (rs == null)
throw new NoDataFoundException(); throw new NoDataFoundException();
this.sender = rs.getString(1); this.sender = new PublicKeyAccount(DB.getResultSetBytes(rs.getBinaryStream(1), CREATOR_LENGTH));
this.recipient = rs.getString(2); this.recipient = rs.getString(2);
this.amount = rs.getBigDecimal(3).setScale(8); this.amount = rs.getBigDecimal(3).setScale(8);
} }
/**
* 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 {
try {
return new PaymentTransaction(connection, signature);
} catch (NoDataFoundException e) {
return null;
}
}
@Override @Override
public void save(Connection connection) throws SQLException { public void save(Connection connection) throws SQLException {
super.save(connection); super.save(connection);
String sql = DB.formatInsertWithPlaceholders("PaymentTransactions", "signature", "sender", "recipient", "amount"); String sql = DB.formatInsertWithPlaceholders("PaymentTransactions", "signature", "sender", "recipient", "amount");
PreparedStatement preparedStatement = connection.prepareStatement(sql); PreparedStatement preparedStatement = connection.prepareStatement(sql);
DB.bindInsertPlaceholders(preparedStatement, this.signature, this.sender, this.recipient, this.amount); DB.bindInsertPlaceholders(preparedStatement, this.signature, this.sender.getPublicKey(), this.recipient, this.amount);
preparedStatement.execute(); preparedStatement.execute();
} }
@ -109,9 +135,11 @@ public class PaymentTransaction extends Transaction {
} }
public void process() { public void process() {
// TODO
} }
public void orphan() { public void orphan() {
// TODO
} }
} }

View File

@ -18,7 +18,8 @@ import database.DB;
import database.NoDataFoundException; import database.NoDataFoundException;
import qora.account.PrivateKeyAccount; import qora.account.PrivateKeyAccount;
import qora.account.PublicKeyAccount; import qora.account.PublicKeyAccount;
import qora.crypto.Crypto; import qora.block.Block;
import qora.block.BlockTransaction;
import settings.Settings; import settings.Settings;
public abstract class Transaction { public abstract class Transaction {
@ -52,26 +53,28 @@ public abstract class Transaction {
// Database properties shared with all transaction types // Database properties shared with all transaction types
protected TransactionType type; protected TransactionType type;
protected String creator; protected PublicKeyAccount creator;
protected long timestamp; protected long timestamp;
protected byte[] reference; protected byte[] reference;
protected BigDecimal fee; protected BigDecimal fee;
protected byte[] signature; protected byte[] signature;
// Derived/cached properties // Derived/cached properties
// maybe: protected PublicKeyAccount creatorAccount;
// Property lengths for serialisation
// Property lengths
protected static final int TYPE_LENGTH = 4; protected static final int TYPE_LENGTH = 4;
protected static final int TIMESTAMP_LENGTH = 8; protected static final int TIMESTAMP_LENGTH = 8;
protected static final int REFERENCE_LENGTH = 64; protected static final int REFERENCE_LENGTH = 64;
protected static final int FEE_LENGTH = 8; protected static final int FEE_LENGTH = 8;
protected static final int SIGNATURE_LENGTH = 64; public static final int SIGNATURE_LENGTH = 64;
protected static final int BASE_TYPELESS_LENGTH = TIMESTAMP_LENGTH + REFERENCE_LENGTH + FEE_LENGTH + SIGNATURE_LENGTH; protected static final int BASE_TYPELESS_LENGTH = TIMESTAMP_LENGTH + REFERENCE_LENGTH + FEE_LENGTH + SIGNATURE_LENGTH;
// Other length constants
protected static final int CREATOR_LENGTH = 32;
// Constructors // Constructors
protected Transaction(TransactionType type, BigDecimal fee, String creator, long timestamp, byte[] reference, byte[] signature) { protected Transaction(TransactionType type, BigDecimal fee, PublicKeyAccount creator, long timestamp, byte[] reference, byte[] signature) {
this.fee = fee; this.fee = fee;
this.type = type; this.type = type;
this.creator = creator; this.creator = creator;
@ -80,7 +83,7 @@ public abstract class Transaction {
this.signature = signature; this.signature = signature;
} }
protected Transaction(TransactionType type, BigDecimal fee, String creator, long timestamp, byte[] reference) { protected Transaction(TransactionType type, BigDecimal fee, PublicKeyAccount creator, long timestamp, byte[] reference) {
this(type, fee, creator, timestamp, reference, null); this(type, fee, creator, timestamp, reference, null);
} }
@ -90,7 +93,7 @@ public abstract class Transaction {
return this.type; return this.type;
} }
public String getCreator() { public PublicKeyAccount getCreator() {
return this.creator; return this.creator;
} }
@ -148,6 +151,20 @@ public abstract class Transaction {
// Load/Save // Load/Save
// Typically called by sub-class' load-from-DB constructors
/**
* Load base Transaction from DB using signature.
* <p>
* Note that the transaction type is <b>not</b> 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 { 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); ResultSet rs = DB.executeUsingBytes(connection, "SELECT reference, creator, creation, fee FROM Transactions WHERE signature = ?", signature);
if (rs == null) if (rs == null)
@ -155,7 +172,7 @@ public abstract class Transaction {
this.type = type; this.type = type;
this.reference = DB.getResultSetBytes(rs.getBinaryStream(1), REFERENCE_LENGTH); this.reference = DB.getResultSetBytes(rs.getBinaryStream(1), REFERENCE_LENGTH);
this.creator = rs.getString(2); this.creator = new PublicKeyAccount(DB.getResultSetBytes(rs.getBinaryStream(2), CREATOR_LENGTH));
this.timestamp = rs.getTimestamp(3).getTime(); this.timestamp = rs.getTimestamp(3).getTime();
this.fee = rs.getBigDecimal(4).setScale(8); this.fee = rs.getBigDecimal(4).setScale(8);
this.signature = signature; this.signature = signature;
@ -164,32 +181,57 @@ public abstract class Transaction {
protected void save(Connection connection) throws SQLException { protected void save(Connection connection) throws SQLException {
String sql = DB.formatInsertWithPlaceholders("Transactions", "signature", "reference", "type", "creator", "creation", "fee", "milestone_block"); String sql = DB.formatInsertWithPlaceholders("Transactions", "signature", "reference", "type", "creator", "creation", "fee", "milestone_block");
PreparedStatement preparedStatement = connection.prepareStatement(sql); PreparedStatement preparedStatement = connection.prepareStatement(sql);
DB.bindInsertPlaceholders(preparedStatement, this.signature, this.reference, this.type.value, this.creator, DB.bindInsertPlaceholders(preparedStatement, this.signature, this.reference, this.type.value, this.creator.getPublicKey(),
Timestamp.from(Instant.ofEpochSecond(this.timestamp)), this.fee, null); Timestamp.from(Instant.ofEpochSecond(this.timestamp)), this.fee, null);
preparedStatement.execute(); preparedStatement.execute();
} }
// Navigation // Navigation
/* /**
* public Block getBlock() { BlockTransaction blockTx = BlockTransaction.fromTransactionSignature(this.signature); if (blockTx == null) return null; * Load encapsulating Block from DB, if any
*
* return Block.fromSignature(blockTx.getSignature()); }
* *
* @param connection
* @return Block, or null if transaction is not in a Block
* @throws SQLException
*/
public Block getBlock(Connection connection) throws SQLException {
if (this.signature == null)
return null;
BlockTransaction blockTx = BlockTransaction.fromTransactionSignature(connection, this.signature);
if (blockTx == null)
return null;
return Block.fromSignature(connection, 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(Connection connection) throws SQLException {
if (this.reference == null) if (this.reference == null)
return null; return null;
return TransactionFactory.fromSignature(connection, this.reference); return TransactionFactory.fromSignature(connection, 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(Connection connection) throws SQLException {
if (this.signature == null) if (this.signature == null)
return null; return null;
return TransactionFactory.fromReference(connection, this.signature); return TransactionFactory.fromReference(connection, this.signature);
} }
// Converters // Converters
@ -203,14 +245,14 @@ public abstract class Transaction {
public byte[] calcSignature(PrivateKeyAccount signer) { public byte[] calcSignature(PrivateKeyAccount signer) {
byte[] bytes = this.toBytes(); byte[] bytes = this.toBytes();
return Crypto.sign(signer, bytes); return signer.sign(bytes);
} }
public boolean isSignatureValid(PublicKeyAccount signer) { public boolean isSignatureValid(PublicKeyAccount signer) {
if (this.signature == null) if (this.signature == null)
return false; return false;
return Crypto.verify(signer.getPublicKey(), this.signature, this.toBytes()); return signer.verify(this.signature, this.toBytes());
} }
public abstract int isValid(); public abstract int isValid();

View File

@ -9,11 +9,27 @@ import qora.transaction.Transaction.TransactionType;
public class TransactionFactory { 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 { public static Transaction fromSignature(Connection connection, byte[] signature) throws SQLException {
ResultSet resultSet = DB.executeUsingBytes(connection, "SELECT type, signature FROM Transactions WHERE signature = ?", signature); ResultSet resultSet = DB.executeUsingBytes(connection, "SELECT type, signature FROM Transactions WHERE signature = ?", signature);
return fromResultSet(connection, resultSet); return fromResultSet(connection, 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 { public static Transaction fromReference(Connection connection, byte[] reference) throws SQLException {
ResultSet resultSet = DB.executeUsingBytes(connection, "SELECT type, signature FROM Transactions WHERE reference = ?", reference); ResultSet resultSet = DB.executeUsingBytes(connection, "SELECT type, signature FROM Transactions WHERE reference = ?", reference);
return fromResultSet(connection, resultSet); return fromResultSet(connection, resultSet);
@ -35,7 +51,7 @@ public class TransactionFactory {
return null; return null;
case Payment: case Payment:
return new PaymentTransaction(connection, signature); return PaymentTransaction.fromSignature(connection, signature);
default: default:
return null; return null;

View File

@ -5,7 +5,7 @@ public class Settings {
public static Settings getInstance() { public static Settings getInstance() {
return new Settings(); return new Settings();
} }
public int getMaxBytePerFee() { public int getMaxBytePerFee() {
return 1024; return 1024;
} }

View File

@ -35,5 +35,5 @@ public class crypto {
assertEquals(expected, Crypto.toAddress(publicKey)); assertEquals(expected, Crypto.toAddress(publicKey));
} }
} }

View File

@ -36,16 +36,17 @@ public class load {
public void testLoadPaymentTransaction() throws SQLException { public void testLoadPaymentTransaction() throws SQLException {
String signature58 = "1211ZPwG3hk5evWzXCZi9hMDRpwumWmkENjwWkeTCik9xA5uoYnxzF7rwR5hmHH3kG2RXo7ToCAaRc7dvnynByJt"; String signature58 = "1211ZPwG3hk5evWzXCZi9hMDRpwumWmkENjwWkeTCik9xA5uoYnxzF7rwR5hmHH3kG2RXo7ToCAaRc7dvnynByJt";
byte[] signature = Base58.decode(signature58); byte[] signature = Base58.decode(signature58);
PaymentTransaction paymentTransaction = new PaymentTransaction(connection, signature);
assertEquals(paymentTransaction.getSender(), "QXwu8924WdgPoRmtiWQBUMF6eedmp1Hu2E"); PaymentTransaction paymentTransaction = PaymentTransaction.fromSignature(connection, signature);
assertEquals(paymentTransaction.getCreator(), "QXwu8924WdgPoRmtiWQBUMF6eedmp1Hu2E");
assertEquals(paymentTransaction.getSender().getAddress(), "QXwu8924WdgPoRmtiWQBUMF6eedmp1Hu2E");
assertEquals(paymentTransaction.getCreator().getAddress(), "QXwu8924WdgPoRmtiWQBUMF6eedmp1Hu2E");
assertEquals(paymentTransaction.getRecipient(), "QZsv8vbJ6QfrBNba4LMp5UtHhAzhrxvVUU"); assertEquals(paymentTransaction.getRecipient(), "QZsv8vbJ6QfrBNba4LMp5UtHhAzhrxvVUU");
assertEquals(paymentTransaction.getTimestamp(), 1416209264000L); assertEquals(paymentTransaction.getTimestamp(), 1416209264000L);
assertEquals(Base58.encode(paymentTransaction.getReference()), "31dC6kHHBeG5vYb8LMaZDjLEmhc9kQB2VUApVd8xWncSRiXu7yMejdprjYFMP2rUnzZxWd4KJhkq6LsV7rQvU1kY"); assertEquals(Base58.encode(paymentTransaction.getReference()),
"31dC6kHHBeG5vYb8LMaZDjLEmhc9kQB2VUApVd8xWncSRiXu7yMejdprjYFMP2rUnzZxWd4KJhkq6LsV7rQvU1kY");
} }
@Test @Test
public void testLoadFactory() throws SQLException { public void testLoadFactory() throws SQLException {
String signature58 = "1211ZPwG3hk5evWzXCZi9hMDRpwumWmkENjwWkeTCik9xA5uoYnxzF7rwR5hmHH3kG2RXo7ToCAaRc7dvnynByJt"; String signature58 = "1211ZPwG3hk5evWzXCZi9hMDRpwumWmkENjwWkeTCik9xA5uoYnxzF7rwR5hmHH3kG2RXo7ToCAaRc7dvnynByJt";
@ -55,28 +56,27 @@ public class load {
Transaction transaction = TransactionFactory.fromSignature(connection, signature); Transaction transaction = TransactionFactory.fromSignature(connection, signature);
if (transaction == null) if (transaction == null)
break; break;
PaymentTransaction payment = (PaymentTransaction) transaction; PaymentTransaction payment = (PaymentTransaction) transaction;
System.out.println("Transaction " + Base58.encode(payment.getSignature()) + ": " + payment.getAmount().toString() + " QORA from " + payment.getSender() + " to " + payment.getRecipient()); System.out.println("Transaction " + Base58.encode(payment.getSignature()) + ": " + payment.getAmount().toString() + " QORA from "
+ payment.getSender().getAddress() + " to " + payment.getRecipient());
signature = payment.getReference(); signature = payment.getReference();
} }
} }
@Test @Test
public void testLoadNonexistentTransaction() throws SQLException { public void testLoadNonexistentTransaction() throws SQLException {
String signature58 = "1111222233334444"; String signature58 = "1111222233334444";
byte[] signature = Base58.decode(signature58); byte[] signature = Base58.decode(signature58);
try {
PaymentTransaction payment = new PaymentTransaction(connection, signature);
System.out.println("Transaction " + Base58.encode(payment.getSignature()) + ": " + payment.getAmount().toString() + " QORA from " + payment.getSender() + " to " + payment.getRecipient());
} catch (SQLException e) {
System.out.println(e.getMessage());
return;
}
fail(); PaymentTransaction payment = PaymentTransaction.fromSignature(connection, signature);
if (payment != null) {
System.out.println("Transaction " + Base58.encode(payment.getSignature()) + ": " + payment.getAmount().toString() + " QORA from "
+ payment.getSender().getAddress() + " to " + payment.getRecipient());
fail();
}
} }
} }

View File

@ -7,7 +7,6 @@ import java.io.ByteArrayInputStream;
import java.io.IOException; import java.io.IOException;
import java.io.InputStream; import java.io.InputStream;
import java.io.InputStreamReader; import java.io.InputStreamReader;
import java.io.UnsupportedEncodingException;
import java.math.BigDecimal; import java.math.BigDecimal;
import java.net.URL; import java.net.URL;
import java.nio.charset.Charset; import java.nio.charset.Charset;
@ -18,7 +17,9 @@ import java.sql.Statement;
import java.sql.Timestamp; import java.sql.Timestamp;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Arrays; import java.util.Arrays;
import java.util.HashMap;
import java.util.List; import java.util.List;
import java.util.Map;
import org.json.simple.JSONArray; import org.json.simple.JSONArray;
import org.json.simple.JSONObject; import org.json.simple.JSONObject;
@ -29,15 +30,21 @@ import org.junit.Before;
import org.junit.Test; import org.junit.Test;
import com.google.common.hash.HashCode; import com.google.common.hash.HashCode;
import com.google.common.io.CharStreams;
import utils.Base58; import utils.Base58;
public class migrate { public class migrate {
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 Connection c;
private static PreparedStatement startTransactionPStmt; private static PreparedStatement startTransactionPStmt;
private static PreparedStatement commitPStmt; private static PreparedStatement commitPStmt;
private static Map<String, byte[]> publicKeyByAddress = new HashMap<String, byte[]>();
@Before @Before
public void connect() throws SQLException { public void connect() throws SQLException {
c = common.getConnection(); c = common.getConnection();
@ -54,8 +61,9 @@ public class migrate {
} }
} }
public Object fetchBlockJSON(int height) { public Object fetchBlockJSON(int height) throws IOException {
InputStream is; InputStream is;
try { try {
is = new URL("http://localhost:9085/blocks/byheight/" + height).openStream(); is = new URL("http://localhost:9085/blocks/byheight/" + height).openStream();
} catch (IOException e) { } catch (IOException e) {
@ -65,13 +73,28 @@ public class migrate {
try { try {
BufferedReader reader = new BufferedReader(new InputStreamReader(is, Charset.forName("UTF-8"))); BufferedReader reader = new BufferedReader(new InputStreamReader(is, Charset.forName("UTF-8")));
return JSONValue.parseWithException(reader); return JSONValue.parseWithException(reader);
} catch (IOException | ParseException e) { } catch (ParseException e) {
return null; return null;
} finally { } finally {
try { is.close();
is.close(); }
} catch (IOException e) { }
}
public byte[] addressToPublicKey(String address) throws IOException {
byte[] cachedPublicKey = publicKeyByAddress.get(address);
if (cachedPublicKey != null)
return cachedPublicKey;
InputStream is = new URL("http://localhost:9085/addresses/publickey/" + address).openStream();
try {
String publicKey58 = CharStreams.toString(new InputStreamReader(is, Charset.forName("UTF-8")));
byte[] publicKey = Base58.decode(publicKey58);
publicKeyByAddress.put(address, publicKey);
return publicKey;
} finally {
is.close();
} }
} }
@ -97,7 +120,12 @@ public class migrate {
} }
@Test @Test
public void testMigration() throws SQLException, UnsupportedEncodingException { public void testMigration() throws SQLException, IOException {
// Genesis public key
publicKeyByAddress.put(GENESIS_ADDRESS, GENESIS_PUBLICKEY);
// Some other public keys for addresses that have never created a transaction
publicKeyByAddress.put("QcDLhirHkSbR4TLYeShLzHw61B8UGTFusk", Base58.decode("HP58uWRBae654ze6ysmdyGv3qaDrr9BEk6cHv4WuiF7d"));
Statement stmt = c.createStatement(); Statement stmt = c.createStatement();
stmt.execute("DELETE FROM Blocks"); stmt.execute("DELETE FROM Blocks");
@ -148,7 +176,8 @@ public class migrate {
PreparedStatement sharedPaymentPStmt = c PreparedStatement sharedPaymentPStmt = c
.prepareStatement("INSERT INTO SharedTransactionPayments " + formatWithPlaceholders("signature", "recipient", "amount", "asset")); .prepareStatement("INSERT INTO SharedTransactionPayments " + formatWithPlaceholders("signature", "recipient", "amount", "asset"));
PreparedStatement blockTxPStmt = c.prepareStatement("INSERT INTO BlockTransactions " + formatWithPlaceholders("block", "sequence", "signature")); PreparedStatement blockTxPStmt = c
.prepareStatement("INSERT INTO BlockTransactions " + formatWithPlaceholders("block_signature", "sequence", "transaction_signature"));
int height = 1; int height = 1;
byte[] milestone_block = null; byte[] milestone_block = null;
@ -158,7 +187,7 @@ public class migrate {
break; break;
if (height % 1000 == 0) if (height % 1000 == 0)
System.out.println("Height: " + height); System.out.println("Height: " + height + ", public key map size: " + publicKeyByAddress.size());
JSONArray transactions = (JSONArray) json.get("transactions"); JSONArray transactions = (JSONArray) json.get("transactions");
@ -173,6 +202,8 @@ public class migrate {
byte[] blockTransactionsSignature = Base58.decode((String) json.get("transactionsSignature")); byte[] blockTransactionsSignature = Base58.decode((String) json.get("transactionsSignature"));
byte[] blockGeneratorSignature = Base58.decode((String) json.get("generatorSignature")); byte[] blockGeneratorSignature = Base58.decode((String) json.get("generatorSignature"));
byte[] generatorPublicKey = addressToPublicKey((String) json.get("generator"));
blocksPStmt.setBinaryStream(1, new ByteArrayInputStream(blockSignature)); blocksPStmt.setBinaryStream(1, new ByteArrayInputStream(blockSignature));
blocksPStmt.setInt(2, ((Long) json.get("version")).intValue()); blocksPStmt.setInt(2, ((Long) json.get("version")).intValue());
blocksPStmt.setBinaryStream(3, new ByteArrayInputStream(blockReference)); blocksPStmt.setBinaryStream(3, new ByteArrayInputStream(blockReference));
@ -182,7 +213,7 @@ public class migrate {
blocksPStmt.setInt(7, height); blocksPStmt.setInt(7, height);
blocksPStmt.setTimestamp(8, new Timestamp((Long) json.get("timestamp"))); blocksPStmt.setTimestamp(8, new Timestamp((Long) json.get("timestamp")));
blocksPStmt.setBigDecimal(9, BigDecimal.valueOf((Long) json.get("generatingBalance"))); blocksPStmt.setBigDecimal(9, BigDecimal.valueOf((Long) json.get("generatingBalance")));
blocksPStmt.setString(10, (String) json.get("generator")); blocksPStmt.setBinaryStream(10, new ByteArrayInputStream(generatorPublicKey));
blocksPStmt.setBinaryStream(11, new ByteArrayInputStream(blockGeneratorSignature)); blocksPStmt.setBinaryStream(11, new ByteArrayInputStream(blockGeneratorSignature));
String blockATs = (String) json.get("blockATs"); String blockATs = (String) json.get("blockATs");
@ -221,6 +252,7 @@ public class migrate {
txPStmt.setInt(3, type); txPStmt.setInt(3, type);
// Determine transaction "creator" from specific transaction info
switch (type) { switch (type) {
case 1: // genesis case 1: // genesis
txPStmt.setNull(4, java.sql.Types.VARCHAR); // genesis transactions only txPStmt.setNull(4, java.sql.Types.VARCHAR); // genesis transactions only
@ -229,21 +261,21 @@ public class migrate {
case 2: // payment case 2: // payment
case 12: // transfer asset case 12: // transfer asset
case 15: // multi-payment case 15: // multi-payment
txPStmt.setString(4, (String) transaction.get("sender")); txPStmt.setBinaryStream(4, new ByteArrayInputStream(addressToPublicKey((String) transaction.get("sender"))));
break; break;
case 3: // register name case 3: // register name
txPStmt.setString(4, (String) transaction.get("registrant")); txPStmt.setBinaryStream(4, new ByteArrayInputStream(addressToPublicKey((String) transaction.get("registrant"))));
break; break;
case 4: // update name case 4: // update name
case 5: // sell name case 5: // sell name
case 6: // cancel sell name case 6: // cancel sell name
txPStmt.setString(4, (String) transaction.get("owner")); txPStmt.setBinaryStream(4, new ByteArrayInputStream(addressToPublicKey((String) transaction.get("owner"))));
break; break;
case 7: // buy name case 7: // buy name
txPStmt.setString(4, (String) transaction.get("buyer")); txPStmt.setBinaryStream(4, new ByteArrayInputStream(addressToPublicKey((String) transaction.get("buyer"))));
break; break;
case 8: // create poll case 8: // create poll
@ -254,7 +286,7 @@ public class migrate {
case 14: // cancel asset order case 14: // cancel asset order
case 16: // deploy CIYAM AT case 16: // deploy CIYAM AT
case 17: // message case 17: // message
txPStmt.setString(4, (String) transaction.get("creator")); txPStmt.setBinaryStream(4, new ByteArrayInputStream(addressToPublicKey((String) transaction.get("creator"))));
break; break;
default: default:
@ -344,7 +376,7 @@ public class migrate {
case 2: // payment case 2: // payment
paymentPStmt.setBinaryStream(1, new ByteArrayInputStream(txSignature)); paymentPStmt.setBinaryStream(1, new ByteArrayInputStream(txSignature));
paymentPStmt.setString(2, (String) transaction.get("sender")); paymentPStmt.setBinaryStream(2, new ByteArrayInputStream(addressToPublicKey((String) transaction.get("sender"))));
paymentPStmt.setString(3, recipients.get(0)); paymentPStmt.setString(3, recipients.get(0));
paymentPStmt.setBigDecimal(4, BigDecimal.valueOf(Double.valueOf((String) transaction.get("amount")).doubleValue())); paymentPStmt.setBigDecimal(4, BigDecimal.valueOf(Double.valueOf((String) transaction.get("amount")).doubleValue()));
@ -354,7 +386,7 @@ public class migrate {
case 3: // register name case 3: // register name
registerNamePStmt.setBinaryStream(1, new ByteArrayInputStream(txSignature)); registerNamePStmt.setBinaryStream(1, new ByteArrayInputStream(txSignature));
registerNamePStmt.setString(2, (String) transaction.get("registrant")); registerNamePStmt.setBinaryStream(2, new ByteArrayInputStream(addressToPublicKey((String) transaction.get("registrant"))));
registerNamePStmt.setString(3, (String) transaction.get("name")); registerNamePStmt.setString(3, (String) transaction.get("name"));
registerNamePStmt.setString(4, (String) transaction.get("owner")); registerNamePStmt.setString(4, (String) transaction.get("owner"));
registerNamePStmt.setString(5, (String) transaction.get("value")); registerNamePStmt.setString(5, (String) transaction.get("value"));
@ -365,7 +397,7 @@ public class migrate {
case 4: // update name case 4: // update name
updateNamePStmt.setBinaryStream(1, new ByteArrayInputStream(txSignature)); updateNamePStmt.setBinaryStream(1, new ByteArrayInputStream(txSignature));
updateNamePStmt.setString(2, (String) transaction.get("owner")); updateNamePStmt.setBinaryStream(2, new ByteArrayInputStream(addressToPublicKey((String) transaction.get("owner"))));
updateNamePStmt.setString(3, (String) transaction.get("name")); updateNamePStmt.setString(3, (String) transaction.get("name"));
updateNamePStmt.setString(4, (String) transaction.get("newOwner")); updateNamePStmt.setString(4, (String) transaction.get("newOwner"));
updateNamePStmt.setString(5, (String) transaction.get("newValue")); updateNamePStmt.setString(5, (String) transaction.get("newValue"));
@ -376,7 +408,7 @@ public class migrate {
case 5: // sell name case 5: // sell name
sellNamePStmt.setBinaryStream(1, new ByteArrayInputStream(txSignature)); sellNamePStmt.setBinaryStream(1, new ByteArrayInputStream(txSignature));
sellNamePStmt.setString(2, (String) transaction.get("owner")); sellNamePStmt.setBinaryStream(2, new ByteArrayInputStream(addressToPublicKey((String) transaction.get("owner"))));
sellNamePStmt.setString(3, (String) transaction.get("name")); sellNamePStmt.setString(3, (String) transaction.get("name"));
sellNamePStmt.setBigDecimal(4, BigDecimal.valueOf(Double.valueOf((String) transaction.get("amount")).doubleValue())); sellNamePStmt.setBigDecimal(4, BigDecimal.valueOf(Double.valueOf((String) transaction.get("amount")).doubleValue()));
@ -386,7 +418,7 @@ public class migrate {
case 6: // cancel sell name case 6: // cancel sell name
cancelSellNamePStmt.setBinaryStream(1, new ByteArrayInputStream(txSignature)); cancelSellNamePStmt.setBinaryStream(1, new ByteArrayInputStream(txSignature));
cancelSellNamePStmt.setString(2, (String) transaction.get("owner")); cancelSellNamePStmt.setBinaryStream(2, new ByteArrayInputStream(addressToPublicKey((String) transaction.get("owner"))));
cancelSellNamePStmt.setString(3, (String) transaction.get("name")); cancelSellNamePStmt.setString(3, (String) transaction.get("name"));
cancelSellNamePStmt.execute(); cancelSellNamePStmt.execute();
@ -395,7 +427,7 @@ public class migrate {
case 7: // buy name case 7: // buy name
buyNamePStmt.setBinaryStream(1, new ByteArrayInputStream(txSignature)); buyNamePStmt.setBinaryStream(1, new ByteArrayInputStream(txSignature));
buyNamePStmt.setString(2, (String) transaction.get("buyer")); buyNamePStmt.setBinaryStream(2, new ByteArrayInputStream(addressToPublicKey((String) transaction.get("buyer"))));
buyNamePStmt.setString(3, (String) transaction.get("name")); buyNamePStmt.setString(3, (String) transaction.get("name"));
buyNamePStmt.setString(4, (String) transaction.get("seller")); buyNamePStmt.setString(4, (String) transaction.get("seller"));
buyNamePStmt.setBigDecimal(5, BigDecimal.valueOf(Double.valueOf((String) transaction.get("amount")).doubleValue())); buyNamePStmt.setBigDecimal(5, BigDecimal.valueOf(Double.valueOf((String) transaction.get("amount")).doubleValue()));
@ -406,7 +438,7 @@ public class migrate {
case 8: // create poll case 8: // create poll
createPollPStmt.setBinaryStream(1, new ByteArrayInputStream(txSignature)); createPollPStmt.setBinaryStream(1, new ByteArrayInputStream(txSignature));
createPollPStmt.setString(2, (String) transaction.get("creator")); createPollPStmt.setBinaryStream(2, new ByteArrayInputStream(addressToPublicKey((String) transaction.get("creator"))));
createPollPStmt.setString(3, (String) transaction.get("name")); createPollPStmt.setString(3, (String) transaction.get("name"));
createPollPStmt.setString(4, (String) transaction.get("description")); createPollPStmt.setString(4, (String) transaction.get("description"));
@ -426,7 +458,7 @@ public class migrate {
case 9: // vote on poll case 9: // vote on poll
voteOnPollPStmt.setBinaryStream(1, new ByteArrayInputStream(txSignature)); voteOnPollPStmt.setBinaryStream(1, new ByteArrayInputStream(txSignature));
voteOnPollPStmt.setString(2, (String) transaction.get("creator")); voteOnPollPStmt.setBinaryStream(2, new ByteArrayInputStream(addressToPublicKey((String) transaction.get("creator"))));
voteOnPollPStmt.setString(3, (String) transaction.get("poll")); voteOnPollPStmt.setString(3, (String) transaction.get("poll"));
voteOnPollPStmt.setInt(4, ((Long) transaction.get("option")).intValue()); voteOnPollPStmt.setInt(4, ((Long) transaction.get("option")).intValue());
@ -436,7 +468,7 @@ public class migrate {
case 10: // arbitrary transactions case 10: // arbitrary transactions
arbitraryPStmt.setBinaryStream(1, new ByteArrayInputStream(txSignature)); arbitraryPStmt.setBinaryStream(1, new ByteArrayInputStream(txSignature));
arbitraryPStmt.setString(2, (String) transaction.get("creator")); arbitraryPStmt.setBinaryStream(2, new ByteArrayInputStream(addressToPublicKey((String) transaction.get("creator"))));
arbitraryPStmt.setInt(3, ((Long) transaction.get("service")).intValue()); arbitraryPStmt.setInt(3, ((Long) transaction.get("service")).intValue());
arbitraryPStmt.setString(4, "TODO"); arbitraryPStmt.setString(4, "TODO");
@ -459,7 +491,7 @@ public class migrate {
case 11: // issue asset case 11: // issue asset
issueAssetPStmt.setBinaryStream(1, new ByteArrayInputStream(txSignature)); issueAssetPStmt.setBinaryStream(1, new ByteArrayInputStream(txSignature));
issueAssetPStmt.setString(2, (String) transaction.get("creator")); issueAssetPStmt.setBinaryStream(2, new ByteArrayInputStream(addressToPublicKey((String) transaction.get("creator"))));
issueAssetPStmt.setString(3, (String) transaction.get("name")); issueAssetPStmt.setString(3, (String) transaction.get("name"));
issueAssetPStmt.setString(4, (String) transaction.get("description")); issueAssetPStmt.setString(4, (String) transaction.get("description"));
issueAssetPStmt.setBigDecimal(5, BigDecimal.valueOf(((Long) transaction.get("quantity")).longValue())); issueAssetPStmt.setBigDecimal(5, BigDecimal.valueOf(((Long) transaction.get("quantity")).longValue()));
@ -471,7 +503,7 @@ public class migrate {
case 12: // transfer asset case 12: // transfer asset
transferAssetPStmt.setBinaryStream(1, new ByteArrayInputStream(txSignature)); transferAssetPStmt.setBinaryStream(1, new ByteArrayInputStream(txSignature));
transferAssetPStmt.setString(2, (String) transaction.get("sender")); transferAssetPStmt.setBinaryStream(2, new ByteArrayInputStream(addressToPublicKey((String) transaction.get("sender"))));
transferAssetPStmt.setString(3, (String) transaction.get("recipient")); transferAssetPStmt.setString(3, (String) transaction.get("recipient"));
transferAssetPStmt.setLong(4, ((Long) transaction.get("asset")).longValue()); transferAssetPStmt.setLong(4, ((Long) transaction.get("asset")).longValue());
transferAssetPStmt.setBigDecimal(5, BigDecimal.valueOf(Double.valueOf((String) transaction.get("amount")).doubleValue())); transferAssetPStmt.setBigDecimal(5, BigDecimal.valueOf(Double.valueOf((String) transaction.get("amount")).doubleValue()));
@ -482,7 +514,7 @@ public class migrate {
case 13: // create asset order case 13: // create asset order
createAssetOrderPStmt.setBinaryStream(1, new ByteArrayInputStream(txSignature)); createAssetOrderPStmt.setBinaryStream(1, new ByteArrayInputStream(txSignature));
createAssetOrderPStmt.setString(2, (String) transaction.get("creator")); createAssetOrderPStmt.setBinaryStream(2, new ByteArrayInputStream(addressToPublicKey((String) transaction.get("creator"))));
JSONObject assetOrder = (JSONObject) transaction.get("order"); JSONObject assetOrder = (JSONObject) transaction.get("order");
createAssetOrderPStmt.setLong(3, ((Long) assetOrder.get("have")).longValue()); createAssetOrderPStmt.setLong(3, ((Long) assetOrder.get("have")).longValue());
@ -496,7 +528,7 @@ public class migrate {
case 14: // cancel asset order case 14: // cancel asset order
cancelAssetOrderPStmt.setBinaryStream(1, new ByteArrayInputStream(txSignature)); cancelAssetOrderPStmt.setBinaryStream(1, new ByteArrayInputStream(txSignature));
cancelAssetOrderPStmt.setString(2, (String) transaction.get("creator")); cancelAssetOrderPStmt.setBinaryStream(2, new ByteArrayInputStream(addressToPublicKey((String) transaction.get("creator"))));
cancelAssetOrderPStmt.setString(3, (String) transaction.get("order")); cancelAssetOrderPStmt.setString(3, (String) transaction.get("order"));
cancelAssetOrderPStmt.execute(); cancelAssetOrderPStmt.execute();
@ -505,7 +537,7 @@ public class migrate {
case 15: // multi-payment case 15: // multi-payment
multiPaymentPStmt.setBinaryStream(1, new ByteArrayInputStream(txSignature)); multiPaymentPStmt.setBinaryStream(1, new ByteArrayInputStream(txSignature));
multiPaymentPStmt.setString(2, (String) transaction.get("sender")); multiPaymentPStmt.setBinaryStream(2, new ByteArrayInputStream(addressToPublicKey((String) transaction.get("sender"))));
multiPaymentPStmt.execute(); multiPaymentPStmt.execute();
multiPaymentPStmt.clearParameters(); multiPaymentPStmt.clearParameters();
@ -528,7 +560,7 @@ public class migrate {
InputStream creationBytesStream = new ByteArrayInputStream(creationBytes.asBytes()); InputStream creationBytesStream = new ByteArrayInputStream(creationBytes.asBytes());
deployATPStmt.setBinaryStream(1, new ByteArrayInputStream(txSignature)); deployATPStmt.setBinaryStream(1, new ByteArrayInputStream(txSignature));
deployATPStmt.setString(2, (String) transaction.get("creator")); deployATPStmt.setBinaryStream(2, new ByteArrayInputStream(addressToPublicKey((String) transaction.get("creator"))));
deployATPStmt.setString(3, (String) transaction.get("name")); deployATPStmt.setString(3, (String) transaction.get("name"));
deployATPStmt.setString(4, (String) transaction.get("description")); deployATPStmt.setString(4, (String) transaction.get("description"));
deployATPStmt.setString(5, (String) transaction.get("atType")); deployATPStmt.setString(5, (String) transaction.get("atType"));
@ -554,7 +586,7 @@ public class migrate {
} }
messagePStmt.setBinaryStream(1, new ByteArrayInputStream(txSignature)); messagePStmt.setBinaryStream(1, new ByteArrayInputStream(txSignature));
messagePStmt.setString(2, (String) transaction.get("creator")); messagePStmt.setBinaryStream(2, new ByteArrayInputStream(addressToPublicKey((String) transaction.get("creator"))));
messagePStmt.setString(3, (String) transaction.get("recipient")); messagePStmt.setString(3, (String) transaction.get("recipient"));
messagePStmt.setBoolean(4, isText); messagePStmt.setBoolean(4, isText);
messagePStmt.setBoolean(5, isEncrypted); messagePStmt.setBoolean(5, isEncrypted);

54
src/test/navigation.java Normal file
View File

@ -0,0 +1,54 @@
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.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();
}
}
@Test
public void testNavigateFromTransactionToBlock() throws SQLException {
String signature58 = "1211ZPwG3hk5evWzXCZi9hMDRpwumWmkENjwWkeTCik9xA5uoYnxzF7rwR5hmHH3kG2RXo7ToCAaRc7dvnynByJt";
byte[] signature = Base58.decode(signature58);
System.out.println("Navigating to Block from transaction " + signature58);
PaymentTransaction paymentTransaction = PaymentTransaction.fromSignature(connection, signature);
assertNotNull(paymentTransaction);
Block block = paymentTransaction.getBlock(connection);
assertNotNull(block);
System.out.println("Block " + block.getHeight() + ", signature: " + Base58.encode(block.getSignature()));
assertEquals(49778, block.getHeight());
}
}

View File

@ -12,6 +12,7 @@ import org.junit.After;
import org.junit.Before; import org.junit.Before;
import org.junit.Test; import org.junit.Test;
import qora.account.PublicKeyAccount;
import qora.transaction.PaymentTransaction; import qora.transaction.PaymentTransaction;
import utils.Base58; import utils.Base58;
@ -41,8 +42,9 @@ public class save {
byte[] reference = Base58.decode(reference58); byte[] reference = Base58.decode(reference58);
String signature58 = "ssss"; String signature58 = "ssss";
byte[] signature = Base58.decode(signature58); byte[] signature = Base58.decode(signature58);
PublicKeyAccount sender = new PublicKeyAccount("Qsender".getBytes());
PaymentTransaction paymentTransaction = new PaymentTransaction("Qsender", "Qrecipient", BigDecimal.valueOf(12345L), BigDecimal.ONE, PaymentTransaction paymentTransaction = new PaymentTransaction(sender, "Qrecipient", BigDecimal.valueOf(12345L), BigDecimal.ONE,
Instant.now().getEpochSecond(), reference, signature); Instant.now().getEpochSecond(), reference, signature);
paymentTransaction.save(connection); paymentTransaction.save(connection);

View File

@ -47,6 +47,7 @@ public class updates {
stmt.execute("CREATE DOMAIN BlockSignature AS VARBINARY(128)"); stmt.execute("CREATE DOMAIN BlockSignature AS VARBINARY(128)");
stmt.execute("CREATE DOMAIN Signature AS VARBINARY(64)"); stmt.execute("CREATE DOMAIN Signature AS VARBINARY(64)");
stmt.execute("CREATE DOMAIN QoraAddress AS VARCHAR(36)"); 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 QoraAmount AS DECIMAL(19, 8)");
stmt.execute("CREATE DOMAIN RegisteredName AS VARCHAR(400) COLLATE SQL_TEXT_UCC"); stmt.execute("CREATE DOMAIN RegisteredName AS VARCHAR(400) COLLATE SQL_TEXT_UCC");
stmt.execute("CREATE DOMAIN NameData AS VARCHAR(4000)"); stmt.execute("CREATE DOMAIN NameData AS VARCHAR(4000)");
@ -65,21 +66,24 @@ public class updates {
stmt.execute("CREATE TABLE Blocks (signature BlockSignature PRIMARY KEY, version TINYINT NOT NULL, reference BlockSignature, " 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, " + "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, " + "height INTEGER NOT NULL, generation TIMESTAMP NOT NULL, generation_target QoraAmount NOT NULL, "
+ "generator QoraAddress NOT NULL, generation_signature Signature NOT NULL, AT_data VARBINARY(20000), AT_fees QoraAmount)"); + "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 BlockHeightIndex ON Blocks (height)");
stmt.execute("CREATE INDEX BlockGeneratorIndex ON Blocks (generator)"); stmt.execute("CREATE INDEX BlockGeneratorIndex ON Blocks (generator)");
stmt.execute("CREATE INDEX BlockReferenceIndex ON Blocks (reference)");
break; break;
case 2: case 2:
// Generic transactions (null reference, creator and milestone_block for genesis transactions) // 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, " stmt.execute("CREATE TABLE Transactions (signature Signature PRIMARY KEY, reference Signature, type TINYINT NOT NULL, "
+ "creator QoraAddress, creation TIMESTAMP NOT NULL, fee QoraAmount NOT NULL, milestone_block BlockSignature)"); + "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 TransactionTypeIndex ON Transactions (type)");
stmt.execute("CREATE INDEX TransactionCreationIndex ON Transactions (creation)"); 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) // Transaction-Block mapping ("signature" is unique as a transaction cannot be included in more than one block)
stmt.execute("CREATE TABLE BlockTransactions (block BlockSignature, sequence INTEGER, signature Signature, " stmt.execute("CREATE TABLE BlockTransactions (block_signature BlockSignature, sequence INTEGER, transaction_signature Signature, "
+ "PRIMARY KEY (block, sequence), FOREIGN KEY (signature) REFERENCES Transactions (signature) ON DELETE CASCADE, " + "PRIMARY KEY (block_signature, sequence), FOREIGN KEY (transaction_signature) REFERENCES Transactions (signature) ON DELETE CASCADE, "
+ "FOREIGN KEY (block) REFERENCES Blocks (signature) ON DELETE CASCADE)"); + "FOREIGN KEY (block_signature) REFERENCES Blocks (signature) ON DELETE CASCADE)");
// Unconfirmed transactions // Unconfirmed transactions
// Do we need this? If a transaction doesn't have a corresponding BlockTransactions record then it's unconfirmed? // 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 TABLE UnconfirmedTransactions (signature Signature PRIMARY KEY, expiry TIMESTAMP NOT NULL)");
@ -98,47 +102,47 @@ public class updates {
case 4: case 4:
// Payment Transactions // Payment Transactions
stmt.execute("CREATE TABLE PaymentTransactions (signature Signature, sender QoraAddress NOT NULL, recipient QoraAddress NOT NULL, " stmt.execute("CREATE TABLE PaymentTransactions (signature Signature, sender QoraPublicKey NOT NULL, recipient QoraAddress NOT NULL, "
+ "amount QoraAmount NOT NULL, PRIMARY KEY (signature), " + "amount QoraAmount NOT NULL, PRIMARY KEY (signature), "
+ "FOREIGN KEY (signature) REFERENCES Transactions (signature) ON DELETE CASCADE)"); + "FOREIGN KEY (signature) REFERENCES Transactions (signature) ON DELETE CASCADE)");
break; break;
case 5: case 5:
// Register Name Transactions // Register Name Transactions
stmt.execute("CREATE TABLE RegisterNameTransactions (signature Signature, registrant QoraAddress NOT NULL, name RegisteredName NOT NULL, " stmt.execute("CREATE TABLE RegisterNameTransactions (signature Signature, registrant QoraPublicKey NOT NULL, name RegisteredName NOT NULL, "
+ "owner QoraAddress NOT NULL, data NameData NOT NULL, " + "owner QoraAddress NOT NULL, data NameData NOT NULL, "
+ "PRIMARY KEY (signature), FOREIGN KEY (signature) REFERENCES Transactions (signature) ON DELETE CASCADE)"); + "PRIMARY KEY (signature), FOREIGN KEY (signature) REFERENCES Transactions (signature) ON DELETE CASCADE)");
break; break;
case 6: case 6:
// Update Name Transactions // Update Name Transactions
stmt.execute("CREATE TABLE UpdateNameTransactions (signature Signature, owner QoraAddress NOT NULL, name RegisteredName NOT NULL, " 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, " + "new_owner QoraAddress NOT NULL, new_data NameData NOT NULL, "
+ "PRIMARY KEY (signature), FOREIGN KEY (signature) REFERENCES Transactions (signature) ON DELETE CASCADE)"); + "PRIMARY KEY (signature), FOREIGN KEY (signature) REFERENCES Transactions (signature) ON DELETE CASCADE)");
break; break;
case 7: case 7:
// Sell Name Transactions // Sell Name Transactions
stmt.execute("CREATE TABLE SellNameTransactions (signature Signature, owner QoraAddress NOT NULL, name RegisteredName NOT NULL, " 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)"); + "amount QoraAmount NOT NULL, PRIMARY KEY (signature), FOREIGN KEY (signature) REFERENCES Transactions (signature) ON DELETE CASCADE)");
break; break;
case 8: case 8:
// Cancel Sell Name Transactions // Cancel Sell Name Transactions
stmt.execute("CREATE TABLE CancelSellNameTransactions (signature Signature, owner QoraAddress NOT NULL, name RegisteredName NOT NULL, " 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)"); + "PRIMARY KEY (signature), FOREIGN KEY (signature) REFERENCES Transactions (signature) ON DELETE CASCADE)");
break; break;
case 9: case 9:
// Buy Name Transactions // Buy Name Transactions
stmt.execute("CREATE TABLE BuyNameTransactions (signature Signature, buyer QoraAddress NOT NULL, name RegisteredName NOT NULL, " stmt.execute("CREATE TABLE BuyNameTransactions (signature Signature, buyer QoraPublicKey NOT NULL, name RegisteredName NOT NULL, "
+ "seller QoraAddress NOT NULL, amount QoraAmount NOT NULL, " + "seller QoraAddress NOT NULL, amount QoraAmount NOT NULL, "
+ "PRIMARY KEY (signature), FOREIGN KEY (signature) REFERENCES Transactions (signature) ON DELETE CASCADE)"); + "PRIMARY KEY (signature), FOREIGN KEY (signature) REFERENCES Transactions (signature) ON DELETE CASCADE)");
break; break;
case 10: case 10:
// Create Poll Transactions // Create Poll Transactions
stmt.execute("CREATE TABLE CreatePollTransactions (signature Signature, creator QoraAddress NOT NULL, poll PollName NOT NULL, " stmt.execute("CREATE TABLE CreatePollTransactions (signature Signature, creator QoraPublicKey NOT NULL, poll PollName NOT NULL, "
+ "description VARCHAR(4000) NOT NULL, " + "description VARCHAR(4000) NOT NULL, "
+ "PRIMARY KEY (signature), FOREIGN KEY (signature) REFERENCES Transactions (signature) ON DELETE CASCADE)"); + "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 // Poll options. NB: option is implicitly NON NULL and UNIQUE due to being part of compound primary key
@ -149,21 +153,21 @@ public class updates {
case 11: case 11:
// Vote On Poll Transactions // Vote On Poll Transactions
stmt.execute("CREATE TABLE VoteOnPollTransactions (signature Signature, voter QoraAddress NOT NULL, poll PollName NOT NULL, " stmt.execute("CREATE TABLE VoteOnPollTransactions (signature Signature, voter QoraPublicKey NOT NULL, poll PollName NOT NULL, "
+ "option_index INTEGER NOT NULL, " + "option_index INTEGER NOT NULL, "
+ "PRIMARY KEY (signature), FOREIGN KEY (signature) REFERENCES Transactions (signature) ON DELETE CASCADE)"); + "PRIMARY KEY (signature), FOREIGN KEY (signature) REFERENCES Transactions (signature) ON DELETE CASCADE)");
break; break;
case 12: case 12:
// Arbitrary/Multi-payment Transaction Payments // Arbitrary/Multi-payment Transaction Payments
stmt.execute("CREATE TABLE SharedTransactionPayments (signature Signature, recipient QoraAddress NOT NULL, " stmt.execute("CREATE TABLE SharedTransactionPayments (signature Signature, recipient QoraPublicKey NOT NULL, "
+ "amount QoraAmount NOT NULL, asset AssetID NOT NULL, " + "amount QoraAmount NOT NULL, asset AssetID NOT NULL, "
+ "PRIMARY KEY (signature, recipient, asset), FOREIGN KEY (signature) REFERENCES Transactions (signature) ON DELETE CASCADE)"); + "PRIMARY KEY (signature, recipient, asset), FOREIGN KEY (signature) REFERENCES Transactions (signature) ON DELETE CASCADE)");
break; break;
case 13: case 13:
// Arbitrary Transactions // Arbitrary Transactions
stmt.execute("CREATE TABLE ArbitraryTransactions (signature Signature, creator QoraAddress NOT NULL, service TINYINT NOT NULL, " stmt.execute("CREATE TABLE ArbitraryTransactions (signature Signature, creator QoraPublicKey NOT NULL, service TINYINT NOT NULL, "
+ "data_hash DataHash NOT NULL, " + "data_hash DataHash NOT NULL, "
+ "PRIMARY KEY (signature), FOREIGN KEY (signature) REFERENCES Transactions (signature) ON DELETE CASCADE)"); + "PRIMARY KEY (signature), FOREIGN KEY (signature) REFERENCES Transactions (signature) ON DELETE CASCADE)");
// NB: Actual data payload stored elsewhere // NB: Actual data payload stored elsewhere
@ -172,7 +176,7 @@ public class updates {
case 14: case 14:
// Issue Asset Transactions // Issue Asset Transactions
stmt.execute("CREATE TABLE IssueAssetTransactions (signature Signature, creator QoraAddress NOT NULL, asset_name AssetName NOT NULL, " 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, " + "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)"); + "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 // For the future: maybe convert quantity from BIGINT to QoraAmount, regardless of divisibility
@ -180,35 +184,34 @@ public class updates {
case 15: case 15:
// Transfer Asset Transactions // Transfer Asset Transactions
stmt.execute("CREATE TABLE TransferAssetTransactions (signature Signature, sender QoraAddress NOT NULL, recipient QoraAddress NOT NULL, " stmt.execute("CREATE TABLE TransferAssetTransactions (signature Signature, sender QoraPublicKey NOT NULL, recipient QoraAddress NOT NULL, "
+ "asset AssetID NOT NULL, amount QoraAmount NOT NULL, " + "asset AssetID NOT NULL, amount QoraAmount NOT NULL, "
+ "PRIMARY KEY (signature), FOREIGN KEY (signature) REFERENCES Transactions (signature) ON DELETE CASCADE)"); + "PRIMARY KEY (signature), FOREIGN KEY (signature) REFERENCES Transactions (signature) ON DELETE CASCADE)");
break; break;
case 16: case 16:
// Create Asset Order Transactions // Create Asset Order Transactions
stmt.execute("CREATE TABLE CreateAssetOrderTransactions (signature Signature, creator QoraAddress NOT NULL, " stmt.execute("CREATE TABLE CreateAssetOrderTransactions (signature Signature, creator QoraPublicKey NOT NULL, "
+ "have_asset AssetID NOT NULL, have_amount QoraAmount NOT NULL, " + "have_asset AssetID NOT NULL, have_amount QoraAmount NOT NULL, want_asset AssetID NOT NULL, want_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)"); + "PRIMARY KEY (signature), FOREIGN KEY (signature) REFERENCES Transactions (signature) ON DELETE CASCADE)");
break; break;
case 17: case 17:
// Cancel Asset Order Transactions // Cancel Asset Order Transactions
stmt.execute("CREATE TABLE CancelAssetOrderTransactions (signature Signature, creator QoraAddress NOT NULL, " stmt.execute("CREATE TABLE CancelAssetOrderTransactions (signature Signature, creator QoraPublicKey NOT NULL, "
+ "asset_order AssetOrderID NOT NULL, " + "asset_order AssetOrderID NOT NULL, "
+ "PRIMARY KEY (signature), FOREIGN KEY (signature) REFERENCES Transactions (signature) ON DELETE CASCADE)"); + "PRIMARY KEY (signature), FOREIGN KEY (signature) REFERENCES Transactions (signature) ON DELETE CASCADE)");
break; break;
case 18: case 18:
// Multi-payment Transactions // Multi-payment Transactions
stmt.execute("CREATE TABLE MultiPaymentTransactions (signature Signature, sender QoraAddress NOT NULL, " stmt.execute("CREATE TABLE MultiPaymentTransactions (signature Signature, sender QoraPublicKey NOT NULL, "
+ "PRIMARY KEY (signature), FOREIGN KEY (signature) REFERENCES Transactions (signature) ON DELETE CASCADE)"); + "PRIMARY KEY (signature), FOREIGN KEY (signature) REFERENCES Transactions (signature) ON DELETE CASCADE)");
break; break;
case 19: case 19:
// Deploy CIYAM AT Transactions // Deploy CIYAM AT Transactions
stmt.execute("CREATE TABLE DeployATTransactions (signature Signature, creator QoraAddress NOT NULL, AT_name ATName NOT NULL, " 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, " + "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, " + "creation_bytes VARBINARY(100000) NOT NULL, amount QoraAmount NOT NULL, "
+ "PRIMARY KEY (signature), FOREIGN KEY (signature) REFERENCES Transactions (signature) ON DELETE CASCADE)"); + "PRIMARY KEY (signature), FOREIGN KEY (signature) REFERENCES Transactions (signature) ON DELETE CASCADE)");
@ -216,7 +219,7 @@ public class updates {
case 20: case 20:
// Message Transactions // Message Transactions
stmt.execute("CREATE TABLE MessageTransactions (signature Signature, sender QoraAddress NOT NULL, recipient QoraAddress NOT NULL, " 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, " + "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)"); + "PRIMARY KEY (signature), FOREIGN KEY (signature) REFERENCES Transactions (signature) ON DELETE CASCADE)");
break; break;