forked from Qortal/qortal
More work on transactions and blocks
PaymentTransaction now uses Account for recipient internally but maybe should extend that type change to constructor args. GenesisBlock added also with signature test. More javadocs.
This commit is contained in:
parent
b90a486039
commit
71f9d5c0f0
@ -12,6 +12,10 @@ import java.util.Arrays;
|
||||
|
||||
import com.google.common.primitives.Bytes;
|
||||
|
||||
/**
|
||||
* Helper methods for common database actions.
|
||||
*
|
||||
*/
|
||||
public class DB {
|
||||
|
||||
public static void startTransaction(Connection c) throws SQLException {
|
||||
@ -122,6 +126,8 @@ public class DB {
|
||||
for (int i = 0; i < objects.length; ++i) {
|
||||
Object object = objects[i];
|
||||
|
||||
// Special treatment for BigDecimals so that they retain their "scale",
|
||||
// which would otherwise be assumed as 0.
|
||||
if (object instanceof BigDecimal) {
|
||||
preparedStatement.setBigDecimal(i + 1, (BigDecimal) object);
|
||||
preparedStatement.setBigDecimal(i + objects.length + 1, (BigDecimal) object);
|
||||
@ -133,10 +139,11 @@ public class DB {
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute SQL using byte[] as 1st placeholder
|
||||
* Execute SQL using byte[] as 1st placeholder.
|
||||
* <p>
|
||||
* <b>Note: calls ResultSet.next()</b> therefore returned ResultSet is already pointing to first row.
|
||||
* <p>
|
||||
* Typically used to fetch Blocks or Transactions using signature or reference.
|
||||
* <p>
|
||||
*
|
||||
* @param connection
|
||||
* @param sql
|
||||
@ -162,7 +169,9 @@ public class DB {
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute PreparedStatement and return ResultSet with but added checking
|
||||
* Execute PreparedStatement and return ResultSet with but added checking.
|
||||
* <p>
|
||||
* <b>Note: calls ResultSet.next()</b> therefore returned ResultSet is already pointing to first row.
|
||||
*
|
||||
* @param preparedStatement
|
||||
* @return ResultSet, or null if there are no found rows
|
||||
@ -181,4 +190,5 @@ public class DB {
|
||||
|
||||
return resultSet;
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -1,9 +1,11 @@
|
||||
package qora.account;
|
||||
|
||||
import com.google.common.primitives.Bytes;
|
||||
|
||||
public final class GenesisAccount extends PublicKeyAccount {
|
||||
|
||||
public GenesisAccount() {
|
||||
super(new byte[] { 1, 1, 1, 1, 1, 1, 1, 1 });
|
||||
super(Bytes.ensureCapacity(new byte[] { 1, 1, 1, 1, 1, 1, 1, 1 }, 32, 0));
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -1,6 +1,5 @@
|
||||
package qora.block;
|
||||
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.math.BigDecimal;
|
||||
import java.sql.Connection;
|
||||
import java.sql.PreparedStatement;
|
||||
@ -70,12 +69,12 @@ public class Block {
|
||||
|
||||
// Property lengths for serialisation
|
||||
protected static final int VERSION_LENGTH = 4;
|
||||
protected static final int REFERENCE_LENGTH = 64;
|
||||
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 TIMESTAMP_LENGTH = 8;
|
||||
protected static final int GENERATION_TARGET_LENGTH = 8;
|
||||
protected static final int GENERATOR_LENGTH = 32;
|
||||
protected static final int TRANSACTIONS_SIGNATURE_LENGTH = 64;
|
||||
protected static final int GENERATION_SIGNATURE_LENGTH = 64;
|
||||
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;
|
||||
@ -188,27 +187,34 @@ public class Block {
|
||||
return this.transactions;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return block's transactions from DB (or cache).
|
||||
* <p>
|
||||
* If block's transactions have already been loaded from DB then the cached copied is returned instead.
|
||||
*
|
||||
* @param connection
|
||||
* @return
|
||||
* @throws SQLException
|
||||
*/
|
||||
public List<Transaction> getTransactions(Connection connection) throws SQLException {
|
||||
// Already loaded?
|
||||
if (this.transactions != null)
|
||||
return this.transactions;
|
||||
|
||||
// Load from DB
|
||||
// Allocate cache for results
|
||||
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();
|
||||
// Load from DB
|
||||
ResultSet rs = DB.executeUsingBytes(connection, "SELECT transaction_signature FROM BlockTransactions WHERE block_signature = ?", this.getSignature());
|
||||
if (rs == null)
|
||||
throw new SQLException("Fetching results from database produced no ResultSet");
|
||||
return this.transactions; // No transactions in this block
|
||||
|
||||
while (rs.next()) {
|
||||
// Use each row's signature to load, and cache, Transactions
|
||||
// NB: do-while loop because DB.executeUsingBytes() implicitly calls ResultSet.next() for us
|
||||
do {
|
||||
byte[] transactionSignature = DB.getResultSetBytes(rs.getBinaryStream(1), Transaction.SIGNATURE_LENGTH);
|
||||
this.transactions.add(TransactionFactory.fromSignature(connection, transactionSignature));
|
||||
}
|
||||
} while (rs.next());
|
||||
|
||||
return this.transactions;
|
||||
}
|
||||
@ -355,9 +361,9 @@ public class Block {
|
||||
return false;
|
||||
}
|
||||
|
||||
public int isValid() {
|
||||
public boolean isValid(Connection connection) throws SQLException {
|
||||
// TODO
|
||||
return VALIDATE_OK;
|
||||
return false;
|
||||
}
|
||||
|
||||
public void process() {
|
||||
|
46
src/qora/block/BlockChain.java
Normal file
46
src/qora/block/BlockChain.java
Normal file
@ -0,0 +1,46 @@
|
||||
package qora.block;
|
||||
|
||||
import java.sql.Connection;
|
||||
import java.sql.ResultSet;
|
||||
import java.sql.SQLException;
|
||||
|
||||
import database.DB;
|
||||
|
||||
/**
|
||||
* Class representing the blockchain as a whole.
|
||||
*
|
||||
*/
|
||||
public class BlockChain {
|
||||
|
||||
/**
|
||||
* Return block height from DB using signature.
|
||||
*
|
||||
* @param connection
|
||||
* @param signature
|
||||
* @return height, or 0 if block not found.
|
||||
* @throws SQLException
|
||||
*/
|
||||
public static int getBlockHeightFromSignature(Connection connection, byte[] signature) throws SQLException {
|
||||
ResultSet rs = DB.executeUsingBytes(connection, "SELECT height FROM Blocks WHERE signature = ?", signature);
|
||||
if (rs == null)
|
||||
return 0;
|
||||
|
||||
return rs.getInt(1);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return highest block height from DB.
|
||||
*
|
||||
* @param connection
|
||||
* @return height, or 0 if there are no blocks in DB (not very likely).
|
||||
* @throws SQLException
|
||||
*/
|
||||
public static int getMaxHeight(Connection connection) throws SQLException {
|
||||
ResultSet rs = DB.checkedExecute(connection.prepareStatement("SELECT MAX(height) FROM Blocks"));
|
||||
if (rs == null)
|
||||
return 0;
|
||||
|
||||
return rs.getInt(1);
|
||||
}
|
||||
|
||||
}
|
61
src/qora/block/BlockFactory.java
Normal file
61
src/qora/block/BlockFactory.java
Normal file
@ -0,0 +1,61 @@
|
||||
package qora.block;
|
||||
|
||||
import java.sql.Connection;
|
||||
import java.sql.PreparedStatement;
|
||||
import java.sql.SQLException;
|
||||
|
||||
import database.DB;
|
||||
import database.NoDataFoundException;
|
||||
|
||||
public class BlockFactory {
|
||||
|
||||
/**
|
||||
* Load Block from DB using block signature.
|
||||
*
|
||||
* @param connection
|
||||
* @param signature
|
||||
* @return ? extends Block, or null if not found
|
||||
* @throws SQLException
|
||||
*/
|
||||
public static Block fromSignature(Connection connection, byte[] signature) throws SQLException {
|
||||
Block block = Block.fromSignature(connection, signature);
|
||||
if (block == null)
|
||||
return null;
|
||||
|
||||
// Can we promote to a GenesisBlock?
|
||||
if (GenesisBlock.isGenesisBlock(block))
|
||||
return GenesisBlock.getInstance();
|
||||
|
||||
// Standard block
|
||||
return block;
|
||||
}
|
||||
|
||||
/**
|
||||
* Load Block from DB using block height
|
||||
*
|
||||
* @param connection
|
||||
* @param height
|
||||
* @return ? extends Block, or null if not found
|
||||
* @throws SQLException
|
||||
*/
|
||||
public static Block fromHeight(Connection connection, int height) throws SQLException {
|
||||
if (height == 1)
|
||||
return GenesisBlock.getInstance();
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
// Navigation
|
||||
|
||||
// Converters
|
||||
|
||||
// Processing
|
||||
|
||||
}
|
329
src/qora/block/GenesisBlock.java
Normal file
329
src/qora/block/GenesisBlock.java
Normal file
@ -0,0 +1,329 @@
|
||||
package qora.block;
|
||||
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.math.BigDecimal;
|
||||
import java.sql.Connection;
|
||||
import java.sql.ResultSet;
|
||||
import java.sql.SQLException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
|
||||
import com.google.common.primitives.Bytes;
|
||||
import com.google.common.primitives.Longs;
|
||||
|
||||
import qora.account.GenesisAccount;
|
||||
import qora.account.PrivateKeyAccount;
|
||||
import qora.account.PublicKeyAccount;
|
||||
import qora.crypto.Crypto;
|
||||
import qora.transaction.GenesisTransaction;
|
||||
import qora.transaction.Transaction;
|
||||
|
||||
public class GenesisBlock extends Block {
|
||||
|
||||
private static GenesisBlock instance;
|
||||
|
||||
private static final int GENESIS_BLOCK_VERSION = 1;
|
||||
private static final byte[] GENESIS_REFERENCE = new byte[] { 1, 1, 1, 1, 1, 1, 1, 1 }; // NOTE: Neither 64 nor 128 bytes!
|
||||
private static final BigDecimal GENESIS_GENERATION_TARGET = BigDecimal.valueOf(10_000_000L).setScale(8);
|
||||
private static final GenesisAccount GENESIS_GENERATOR = new GenesisAccount();
|
||||
private static final long GENESIS_TIMESTAMP = 1400247274336L; // QORA RELEASE: Fri May 16 13:34:34.336 2014 UTC
|
||||
private static final byte[] GENESIS_GENERATION_SIGNATURE = calcSignature();
|
||||
private static final byte[] GENESIS_TRANSACTIONS_SIGNATURE = calcSignature();
|
||||
|
||||
// Constructors
|
||||
protected GenesisBlock() {
|
||||
super(GENESIS_BLOCK_VERSION, GENESIS_REFERENCE, GENESIS_TIMESTAMP, GENESIS_GENERATION_TARGET, GENESIS_GENERATOR, GENESIS_GENERATION_SIGNATURE, null, null);
|
||||
|
||||
this.height = 1;
|
||||
|
||||
this.transactions = new ArrayList<Transaction>();
|
||||
|
||||
// Genesis transactions
|
||||
addGenesisTransaction("QUD9y7NZqTtNwvSAUfewd7zKUGoVivVnTW", "7032468.191");
|
||||
addGenesisTransaction("QVafvKkE5bZTkq8PcXvdaxwuLNN2DGCwYk", "1716146.084");
|
||||
addGenesisTransaction("QV42QQP7frYWqsVq536g7zSk97fUpf2ZSN", "5241707.06");
|
||||
addGenesisTransaction("QgkLTm5GkepJpgr53nAgUyYRsvmyHpb2zT", "854964.0816");
|
||||
addGenesisTransaction("Qc8kN338XQULMBuUa6mTqL5tipvELDhzeZ", "769467.6734");
|
||||
addGenesisTransaction("QQ81BA75jZcpjQBLZE1qcHrXV8ARC1DEec", "85496408.16");
|
||||
addGenesisTransaction("QeoSe4DscWX4AFkNBCdm4WS1V7QkUFSQLP", "854968.3564");
|
||||
addGenesisTransaction("Qdfu3Eh21ZVHNDY1xyNaFqTTEYscSmfSsm", "85496408.16");
|
||||
addGenesisTransaction("QeDSr4abXKRg9j5hTN3TK9UGuH3umThZ42", "4445813.224");
|
||||
addGenesisTransaction("QQKDuo1txYB9E2xim79YVR6SQ1ZbJtJtFX", "47023024.49");
|
||||
addGenesisTransaction("QLeaeGr4CDA95FmeMtFh8okJRMLoq8Cge5", "170992816.3");
|
||||
addGenesisTransaction("QSwN5oa8ZHWJmc6FeAJ8Xr1SHaEuSahw1J", "3419856.326");
|
||||
addGenesisTransaction("QWnoGd4a7iXqQmNEpUtCb1x7nWgcya8QbE", "17056533.43");
|
||||
addGenesisTransaction("QbJqhsJjcy3vkzsJ1kHvgn26pQF3sZEypc", "42705455.87");
|
||||
addGenesisTransaction("QiBhBcseKzaDnHKyqEJs8z1Xx2rSb9XhBr", "141069073.5");
|
||||
addGenesisTransaction("QTwYwxBhzivFEWY5yfzyz1pqhJ8XCroKwv", "85496408.16");
|
||||
addGenesisTransaction("QfikxUU15Dy1oxbcDNEcLeU5cHvbrceq3A", "17099281.63");
|
||||
addGenesisTransaction("QhdqBmKZeQ3Hg1XUuR5nKtAkw47tuoRi2q", "12824461.22");
|
||||
addGenesisTransaction("QaVNyTqsTHA6JWMcqntcJf1u9c3qid76xH", "128244612.2");
|
||||
addGenesisTransaction("QYaDa7bmgo5L9qkcfJKjhPPrQkvGjEoc7y", "85496408.16");
|
||||
addGenesisTransaction("QQPddvWaYf4pbCyVHEVoyfC72EiaAv4JhT", "25648922.45");
|
||||
addGenesisTransaction("QSQpTNtTZMwaDuNq56Jz73KHWXaey9JrT1", "26341443.35");
|
||||
addGenesisTransaction("QVjcFWE6TnGePGJEtbNc1thwD2sgHBLvUV", "42940528.25");
|
||||
addGenesisTransaction("Qga93mWNqTuJYx6o33vjUpFH7Cn4sxLyoG", "2564892.245");
|
||||
addGenesisTransaction("QXyHKyQPJnb4ejyTkvS26x9sjWnhTTJ1Uc", "10259568.98");
|
||||
addGenesisTransaction("QLurSSgJvW7WXHDFSobgfakgqXxjoZzwUH", "85496408.16");
|
||||
addGenesisTransaction("QadxfiARcsmwzE93wqq82yXFi3ykM2qdtS", "79118376.11");
|
||||
addGenesisTransaction("QRHhhtz3Cv9RPKB1QBBfkRmRfpXx8vkRa5", "22435418.54");
|
||||
addGenesisTransaction("Qh8UnEs55n8jcnBaBwVtrTGkFFFBDyrMqH", "128757590.7");
|
||||
addGenesisTransaction("QhF7Fu3f54CTYA7zBQ223NQEssi2yAbAcx", "258481290.8");
|
||||
addGenesisTransaction("QPk9VB6tigoifrUYQrw4arBNk7i8HEgsDD", "128244612.2");
|
||||
addGenesisTransaction("QXWJnEPsdtaLQAEutJFR4ySiMUJCWDzZJX", "85496408.16");
|
||||
addGenesisTransaction("QVFs42gM4Cixf4Y5vDFvKKxRAamUPMCAVq", "85496408.16");
|
||||
addGenesisTransaction("Qec5ueWc4rcBrty47GZfFSqvLymxvcycFm", "129091026.7");
|
||||
addGenesisTransaction("QfYiztbDz1Nb9EMhgHidLycvuPN8HEcHEj", "128244612.2");
|
||||
addGenesisTransaction("QPdWsZtaZcAKqk2HWVhEVbws4qG5KUTXmg", "179285967.9");
|
||||
addGenesisTransaction("QVkNs5NcwQpsrCXpWzuMXkMehJr5mkvLVy", "8558190.456");
|
||||
addGenesisTransaction("Qg19DzyEfyZANx6JLy4GrSGF5LuZ2MLqyZ", "42748204.08");
|
||||
addGenesisTransaction("Qf3A8L5WJNHt1xZxmayrTp2d5owzdkcxM6", "50519827.58");
|
||||
addGenesisTransaction("QeKR4W6qkFJGF7Hmu7rSUzTSQiqJzZLXdt", "10216820.77");
|
||||
addGenesisTransaction("QWg7T5i3uBY3xeBLFTLYYruR15Ln11vwo4", "170992816.3");
|
||||
addGenesisTransaction("QUYdM5fHECPZxKQQAmoxoQa2aWg8TZYfPw", "85496408.16");
|
||||
addGenesisTransaction("QjhfEZCgrjUbnLRnWqWxzyYqKQpjjxkuA8", "86665653.61");
|
||||
addGenesisTransaction("QMA53u3wrzDoxC57CWUJePNdR8FoqinqUS", "85496408.16");
|
||||
addGenesisTransaction("QSuCp6mB5zNNeJKD62aq2hR9h84ks1WhHf", "161588211.4");
|
||||
addGenesisTransaction("QS2tCUk7GQefg4zGewwrumxSPmN6fgA7Xc", "170992816.3");
|
||||
addGenesisTransaction("Qcn6FZRxAgp3japtvjgUkBY6KPfbPZMZtM", "170992816.3");
|
||||
addGenesisTransaction("QZrmXZkRmjV2GwMt72Rr1ZqHJjv8raDk5J", "17099281.63");
|
||||
addGenesisTransaction("QeZzwGDfAHa132jb6r4rQHbgJstLuT8QJ3", "255875360.3");
|
||||
addGenesisTransaction("Qj3L139sMMuFvvjKQDwRnoSgKUnoMhDQs5", "76946767.34");
|
||||
addGenesisTransaction("QWJvpvbFRZHu7LRbY5MjzvrMBgzJNFYjCX", "178251461.4");
|
||||
addGenesisTransaction("QRyECqW54ywKVt4kZTEXyRY17aaFUaxzc4", "8772355.539");
|
||||
addGenesisTransaction("QgpH3K3ArkQTg15xjKqGq3BRgE3aNH9Q2P", "46766535.26");
|
||||
addGenesisTransaction("QVZ6pxi8e3K3S44zLbnrLSLwSoYT8CWbwV", "233172022.2");
|
||||
addGenesisTransaction("QNbA69dbnmwqJHLQeS9v63hSLZXXGkmtC6", "46626632.05");
|
||||
addGenesisTransaction("QgzudSKbcLUeQUhFngotVswDSkbU42dSMr", "83786479.99");
|
||||
addGenesisTransaction("QfkQ2UzKMBGPwj8Sm31SArjtXoka1ubU3i", "116345066.7");
|
||||
addGenesisTransaction("QgxHHNwawZeTmQ3i5d9enchi4T9VmzNZ5k", "155448014.8");
|
||||
addGenesisTransaction("QMNugJWNsLuV4Qmbzdf8r8RMEdXk5PNM69", "155448014.8");
|
||||
addGenesisTransaction("QVhWuJkCjStNMV4U8PtNM9Qz4PvLAEtVSj", "101041209.6");
|
||||
addGenesisTransaction("QXjNcckFG9gTr9YbiA3RrRhn3mPJ9zyR4G", "3108960.297");
|
||||
addGenesisTransaction("QThnuBadmExtxk81vhFKimSzbPaPcuPAdm", "155448014.8");
|
||||
addGenesisTransaction("QRc6sQthLHjfkmm2BUhu74g33XtkDoB7JP", "77773983.95");
|
||||
addGenesisTransaction("QcDLhirHkSbR4TLYeShLzHw61B8UGTFusk", "23317202.22");
|
||||
addGenesisTransaction("QXRnsXE6srHEf2abGh4eogs2mRsmNiuw6V", "5440680.519");
|
||||
addGenesisTransaction("QRJmEswbDw4x1kwsLyxtMS9533fv5cDvQV", "3886200.371");
|
||||
addGenesisTransaction("Qg43mCzWmFVwhVfx34g6shXnSU7U7amJNx", "6217920.593");
|
||||
addGenesisTransaction("QQ9PveFTW64yUcXEE6AxhokWCwhmn8F2TD", "8549640.816");
|
||||
addGenesisTransaction("QQaxJuTkW5XXn4DhhRekXpdXaWcsxEfCNG", "3886200.371");
|
||||
addGenesisTransaction("QifWFqW8XWL5mcNxtdr5z1LVC7XUu9tNSK", "3116732.697");
|
||||
addGenesisTransaction("QavhBKRN4vuyzHNNqcWxjcohRAJNTdTmh4", "154670774.8");
|
||||
addGenesisTransaction("QMQyR3Hybof8WpQsXPxh19AZFCj4Z4mmke", "77724007.42");
|
||||
addGenesisTransaction("QbT3GGjp1esTXtowVk2XCtBsKoRB8mkP61", "77724007.42");
|
||||
addGenesisTransaction("QT13tVMZEtbrgJEsBBcTtnyqGveC7mtqAb", "23317202.22");
|
||||
addGenesisTransaction("QegT2Ws5YjLQzEZ9YMzWsAZMBE8cAygHZN", "12606834");
|
||||
addGenesisTransaction("QXoKRBJiJGKwvdA3jkmoUhkM7y6vuMp2pn", "65117173.41");
|
||||
addGenesisTransaction("QY6SpdBzUev9ziqkmyaxESZSbdKwqGdedn", "89382608.53");
|
||||
addGenesisTransaction("QeMxyt1nEE7tbFbioc87xhiKb4szx5DsjY", "15544801.48");
|
||||
addGenesisTransaction("QcTp3THGZvJ42f2mWsQrawGvgBoSHgHZyk", "39639243.78");
|
||||
addGenesisTransaction("QjSH91mTDN6TeV1naAcfwPhmRogufV4n1u", "23317202.22");
|
||||
addGenesisTransaction("QiFLELeLm2TFWsnknzje51wMdt3Srkjz8g", "1554480.148");
|
||||
addGenesisTransaction("QhxtJ3vvhsvVU9x2j5n2R3TXzutfLMUvBR", "23317202.22");
|
||||
addGenesisTransaction("QUtUSNQfqexZZkaZ2s9LcpqjnTezPTnuAx", "15544801.48");
|
||||
addGenesisTransaction("Qg6sPLxNMYxjEDGLLaFkkWx6ip3py5fLEt", "777240.0742");
|
||||
addGenesisTransaction("QeLixskYbdkiAHmhBVMa2Pdi85YPFqw3Ed", "38862003.71");
|
||||
addGenesisTransaction("Qary17o9qvZ2fifiVC8tF5zoBJm79n18zA", "3893972.772");
|
||||
addGenesisTransaction("QLvCWDGwzwpR29XgiThMGDX2vxyFW5rFHB", "8790585.239");
|
||||
addGenesisTransaction("Qgc77fSoAoUSVJfq62GxxTin6dBtU7Y6Hb", "194310018.5");
|
||||
addGenesisTransaction("QPmPKjwPLCuRei6abuhMtMocxAEeSuLVcv", "23317202.22");
|
||||
addGenesisTransaction("QcGfZePUN7JHs9WEEkJhXGzALy4JybiS3N", "194224522.1");
|
||||
addGenesisTransaction("QSeXGwk7eQjR8j7bndJST19qWtM2qnqL1u", "38862003.71");
|
||||
addGenesisTransaction("QU9i68h71nKTg4gwc5yJHzNRQdQEswP7Kn", "139592317.3");
|
||||
addGenesisTransaction("QdKrZGCkwXSSeXJhVA1idDXsA4VFtrjPHN", "15544801.48");
|
||||
addGenesisTransaction("QiYJ2B797xFpWYFu4XWivhGhyPXLU7S5Mr", "77724007.42");
|
||||
addGenesisTransaction("QWxqtsNXUWSjYns2wdngh4WBSWQzLoQHvx", "232613963.9");
|
||||
addGenesisTransaction("QTAGfu4FpTZ1bnvnd17YPtB3zabxfWKNeM", "101041209.6");
|
||||
addGenesisTransaction("QPtRxchgRdwdnoZRwhiAoa77AvVPNSRcQk", "114254290.9");
|
||||
addGenesisTransaction("QMcfoVc9Jat2pMFLHcuPEPnY6p6uBK6Dk7", "77724007.42");
|
||||
addGenesisTransaction("Qi84KosdwSWHZX3qz4WcMgqYGutBmj14dd", "15544801.48");
|
||||
addGenesisTransaction("QjAtcHsgig2tvdGr5tR4oGmRarhuojrAK1", "2883560.675");
|
||||
addGenesisTransaction("QPJPNLP2NMHu5athB7ydezdTA6zviCV378", "6373368.608");
|
||||
addGenesisTransaction("QfVLpmLbuUnA1JEe9FmeUAzihoBvqYDp8B", "15544801.48");
|
||||
addGenesisTransaction("QVVFdy6VLFqAFCb6XSBJLLZybiKgfgDDZV", "10725913.02");
|
||||
addGenesisTransaction("QVFXyeG1xpAR8Xg3u7oAmW8unueGAfeaKi", "31221733.78");
|
||||
addGenesisTransaction("QdtQtrM1h3TLtwAGCNyoTrW5HyiPRLhrPq", "138426457.2");
|
||||
addGenesisTransaction("QMukUMr84Mi2niz6rdhEJMkKJBve7uuRfe", "116586011.1");
|
||||
addGenesisTransaction("QZR8c7dmfwqGPujebFH1miQToJZ4JQfU1X", "217938116.8");
|
||||
addGenesisTransaction("QVV5Uu8eCxufTrBtquDKA96d7Kk8S4V7yX", "40091961.25");
|
||||
addGenesisTransaction("QY9YdgfTEUFvQ2UJszGS63qkwdENkW1PQ5", "154670774.8");
|
||||
addGenesisTransaction("QNgiswyhVyNJG4UMzvoSf29wDvGZqqs7WG", "11658601.11");
|
||||
addGenesisTransaction("QabjgFiY34oihNkUcy9hpFjQdCaypCShMe", "54406805.19");
|
||||
addGenesisTransaction("QionidPRekdshCTRL3c7idWWRAqGYcKaFN", "7772400.742");
|
||||
addGenesisTransaction("QcJdBJiVgiNBNg6ZwZAiEfYDMi5ZTQaYAa", "81386689.86");
|
||||
addGenesisTransaction("QNc8XMpPwM1HESwB7kqw8HoQ5sK2miZ2un", "190423818.2");
|
||||
addGenesisTransaction("QUP1SeaNw7CvCnmDp5ai3onWYwThS4GEpu", "3886200.371");
|
||||
addGenesisTransaction("QinToqEztNN1TsLdQEuzTHh7vUrEo6JTU2", "102440241.8");
|
||||
addGenesisTransaction("QcLJYLV4RD4GmPcoNnh7dQrWeYgiiPiqFQ", "32644083.11");
|
||||
addGenesisTransaction("QdYdYGYfgmMX4jQNWMZqLr81R3HdnuiKkv", "76169527.27");
|
||||
addGenesisTransaction("Qi62mUW5zfJhgRL8FRmCpjSCCnSKtf76S6", "76169527.27");
|
||||
addGenesisTransaction("QgFkxqQGkLW6CD95N2zTnT1PPqb9nxWp6b", "76169527.27");
|
||||
addGenesisTransaction("QfNUBudYsrrq27YqiHGLUg6BtG52W1W1ci", "15544801.48");
|
||||
addGenesisTransaction("QPSFoexnGoMH7EPdg72dM7SvqA7d4M2cu7", "37307523.56");
|
||||
addGenesisTransaction("QQxt5WMvoJ2TNScAzcoxHXPnLTeQ43nQ7N", "21995894.1");
|
||||
addGenesisTransaction("QicpACxck2oDYpzP8iWRQYD4oirCtvjok9", "93268808.9");
|
||||
addGenesisTransaction("QVTJkdQkTGgqEED9kAsp4BZbYNJqWfhgGw", "153909079.5");
|
||||
addGenesisTransaction("QQL5vCkhpXnP9F4wqNiBQsNaCocmRcDSUY", "15512934.64");
|
||||
addGenesisTransaction("QSvEex3p2LaZCVBaCyL8MpYsEpHLwed17r", "155448014.8");
|
||||
addGenesisTransaction("Qb3Xv96GucQpBG8n96QVFgcs2xXsEWW4CE", "38862003.71");
|
||||
addGenesisTransaction("QdRua9MqXufALpQFDeYiQDYk3EBGdwGXSx", "230303229.1");
|
||||
addGenesisTransaction("Qh16Umei91JqiHEVWV8AC6ED9aBqbDYuph", "231073474");
|
||||
addGenesisTransaction("QMu6HXfZCnwaNmyFjjhWTYAUW7k1x7PoVr", "231073474");
|
||||
addGenesisTransaction("QgcphUTiVHHfHg8e1LVgg5jujVES7ZDUTr", "115031531");
|
||||
addGenesisTransaction("QbQk9s4j4EAxAguBhmqA8mdtTct3qGnsrx", "138348733.2");
|
||||
addGenesisTransaction("QT79PhvBwE6vFzfZ4oh5wdKVsEazZuVJFy", "6360421.343");
|
||||
|
||||
this.transactionsSignature = GENESIS_TRANSACTIONS_SIGNATURE;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return cached GenesisBlock to save constructing one from scratch.
|
||||
*
|
||||
* @return GenesisBlock
|
||||
*/
|
||||
public static GenesisBlock getInstance() {
|
||||
if (instance == null)
|
||||
instance = new GenesisBlock();
|
||||
|
||||
return instance;
|
||||
}
|
||||
|
||||
// Getters/setters
|
||||
|
||||
// More information
|
||||
|
||||
public static boolean isGenesisBlock(Block block) {
|
||||
if (block.height != 1)
|
||||
return false;
|
||||
|
||||
// Validate block signature
|
||||
if (!Arrays.equals(GENESIS_GENERATION_SIGNATURE, block.generationSignature))
|
||||
return false;
|
||||
|
||||
// Validate transactions signature
|
||||
if (!Arrays.equals(GENESIS_TRANSACTIONS_SIGNATURE, block.transactionsSignature))
|
||||
return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
// Load/Save
|
||||
|
||||
protected GenesisBlock(Connection connection, byte[] signature) throws SQLException {
|
||||
super(connection, signature);
|
||||
}
|
||||
|
||||
protected GenesisBlock(ResultSet rs) throws SQLException {
|
||||
super(rs);
|
||||
}
|
||||
|
||||
// Navigation
|
||||
|
||||
/**
|
||||
* Refuse to load parent of GenesisBlock from DB!
|
||||
* <p>
|
||||
* As the genesis block is the first block, this always returns null.
|
||||
*
|
||||
* @param connection
|
||||
* @return null
|
||||
* @throws SQLException
|
||||
*/
|
||||
@Override
|
||||
public Block getParent(Connection connection) throws SQLException {
|
||||
return null;
|
||||
}
|
||||
|
||||
// Converters
|
||||
|
||||
// Processing
|
||||
|
||||
@Override
|
||||
public boolean addTransaction(Transaction transaction) {
|
||||
// The genesis block has a fixed set of transactions so always refuse.
|
||||
return false;
|
||||
}
|
||||
|
||||
private void addGenesisTransaction(String recipient, String amount) {
|
||||
this.transactions.add(new GenesisTransaction(recipient, new BigDecimal(amount).setScale(8), this.timestamp));
|
||||
}
|
||||
|
||||
/**
|
||||
* Refuse to calculate genesis block signature!
|
||||
* <p>
|
||||
* This is not possible as there is no private key for the genesis account and so no way to sign data.
|
||||
* <p>
|
||||
* <b>Always throws IllegalStateException.</b>
|
||||
*
|
||||
* @throws IllegalStateException
|
||||
*/
|
||||
@Override
|
||||
public byte[] calcSignature(PrivateKeyAccount signer) {
|
||||
throw new IllegalStateException("There is no private key for genesis transactions");
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate genesis block generation/transactions signature.
|
||||
* <p>
|
||||
* This is handled differently as there is no private key for the genesis account and so no way to sign data.
|
||||
* <p>
|
||||
* Instead we return the SHA-256 digest of the block, duplicated so that the returned byte[] is the same length as normal block signatures.
|
||||
*
|
||||
* @return byte[]
|
||||
*/
|
||||
private static byte[] calcSignature() {
|
||||
byte[] digest = Crypto.digest(getBytesForSignature());
|
||||
return Bytes.concat(digest, digest);
|
||||
}
|
||||
|
||||
private static byte[] getBytesForSignature() {
|
||||
try {
|
||||
ByteArrayOutputStream bytes = new ByteArrayOutputStream(VERSION_LENGTH + REFERENCE_LENGTH + GENERATION_TARGET_LENGTH + GENERATOR_LENGTH);
|
||||
/*
|
||||
* NOTE: Historic code had genesis block using Longs.toByteArray() compared to standard block's Ints.toByteArray. The subsequent
|
||||
* Bytes.ensureCapacity(versionBytes, 0, 4) did not truncate versionBytes back to 4 bytes either. This means 8 bytes were used even though
|
||||
* VERSION_LENGTH is set to 4. Correcting this historic bug will break genesis block signatures!
|
||||
*/
|
||||
bytes.write(Longs.toByteArray(GENESIS_BLOCK_VERSION));
|
||||
/*
|
||||
* NOTE: Historic code had the reference expanded to only 64 bytes whereas standard block references are 128 bytes. Correcting this historic bug
|
||||
* will break genesis block signatures!
|
||||
*/
|
||||
bytes.write(Bytes.ensureCapacity(GENESIS_REFERENCE, 64, 0));
|
||||
bytes.write(Longs.toByteArray(GENESIS_GENERATION_TARGET.longValue()));
|
||||
bytes.write(GENESIS_GENERATOR.getPublicKey());
|
||||
return bytes.toByteArray();
|
||||
} catch (IOException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isSignatureValid(PublicKeyAccount signer) {
|
||||
// Validate block signature
|
||||
if (!Arrays.equals(GENESIS_GENERATION_SIGNATURE, this.generationSignature))
|
||||
return false;
|
||||
|
||||
// Validate transactions signature
|
||||
if (!Arrays.equals(GENESIS_TRANSACTIONS_SIGNATURE, this.transactionsSignature))
|
||||
return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isValid(Connection connection) throws SQLException {
|
||||
// Check there is no other block in DB
|
||||
if (BlockChain.getMaxHeight(connection) != 0)
|
||||
return false;
|
||||
|
||||
// Validate transactions
|
||||
for (Transaction transaction : this.getTransactions())
|
||||
if (transaction.isValid(connection) != Transaction.ValidationResult.OK)
|
||||
return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
}
|
209
src/qora/transaction/GenesisTransaction.java
Normal file
209
src/qora/transaction/GenesisTransaction.java
Normal file
@ -0,0 +1,209 @@
|
||||
package qora.transaction;
|
||||
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.math.BigDecimal;
|
||||
import java.sql.Connection;
|
||||
import java.sql.PreparedStatement;
|
||||
import java.sql.ResultSet;
|
||||
import java.sql.SQLException;
|
||||
import java.util.Arrays;
|
||||
|
||||
import org.json.simple.JSONObject;
|
||||
|
||||
import com.google.common.primitives.Bytes;
|
||||
import com.google.common.primitives.Ints;
|
||||
import com.google.common.primitives.Longs;
|
||||
|
||||
import database.DB;
|
||||
import database.NoDataFoundException;
|
||||
import qora.account.Account;
|
||||
import qora.account.GenesisAccount;
|
||||
import qora.account.PrivateKeyAccount;
|
||||
import qora.account.PublicKeyAccount;
|
||||
import qora.crypto.Crypto;
|
||||
import utils.Base58;
|
||||
import utils.Serialization;
|
||||
|
||||
public class GenesisTransaction extends Transaction {
|
||||
|
||||
// Properties
|
||||
private Account recipient;
|
||||
private BigDecimal amount;
|
||||
|
||||
// Property lengths
|
||||
private static final int RECIPIENT_LENGTH = 32;
|
||||
private static final int AMOUNT_LENGTH = 8;
|
||||
// Note that Genesis transactions don't require reference, fee or signature:
|
||||
private static final int TYPELESS_LENGTH = TIMESTAMP_LENGTH + RECIPIENT_LENGTH + AMOUNT_LENGTH;
|
||||
|
||||
// Constructors
|
||||
|
||||
public GenesisTransaction(String recipient, BigDecimal amount, long timestamp) {
|
||||
super(TransactionType.PAYMENT, BigDecimal.ZERO, new GenesisAccount(), timestamp, new byte[0], null);
|
||||
|
||||
this.recipient = new Account(recipient);
|
||||
this.amount = amount;
|
||||
this.signature = calcSignature();
|
||||
}
|
||||
|
||||
// Getters/Setters
|
||||
|
||||
public Account getRecipient() {
|
||||
return this.recipient;
|
||||
}
|
||||
|
||||
public BigDecimal getAmount() {
|
||||
return this.amount;
|
||||
}
|
||||
|
||||
// More information
|
||||
|
||||
public int getDataLength() {
|
||||
return TYPE_LENGTH + TYPELESS_LENGTH;
|
||||
}
|
||||
|
||||
// Load/Save
|
||||
|
||||
/**
|
||||
* Load GenesisTransaction from DB using signature.
|
||||
*
|
||||
* @param connection
|
||||
* @param signature
|
||||
* @throws NoDataFoundException
|
||||
* if no matching row found
|
||||
* @throws SQLException
|
||||
*/
|
||||
protected GenesisTransaction(Connection connection, byte[] signature) throws SQLException {
|
||||
super(connection, TransactionType.GENESIS, signature);
|
||||
|
||||
ResultSet rs = DB.executeUsingBytes(connection, "SELECT recipient, amount FROM GenesisTransactions WHERE signature = ?", signature);
|
||||
if (rs == null)
|
||||
throw new NoDataFoundException();
|
||||
|
||||
this.recipient = new Account(rs.getString(2));
|
||||
this.amount = rs.getBigDecimal(3).setScale(8);
|
||||
}
|
||||
|
||||
/**
|
||||
* Load GenesisTransaction from DB using signature
|
||||
*
|
||||
* @param connection
|
||||
* @param signature
|
||||
* @return PaymentTransaction, or null if not found
|
||||
* @throws SQLException
|
||||
*/
|
||||
public static GenesisTransaction fromSignature(Connection connection, byte[] signature) throws SQLException {
|
||||
try {
|
||||
return new GenesisTransaction(connection, signature);
|
||||
} catch (NoDataFoundException e) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void save(Connection connection) throws SQLException {
|
||||
super.save(connection);
|
||||
|
||||
String sql = DB.formatInsertWithPlaceholders("GenesisTransactions", "signature", "recipient", "amount");
|
||||
PreparedStatement preparedStatement = connection.prepareStatement(sql);
|
||||
DB.bindInsertPlaceholders(preparedStatement, this.signature, this.recipient.getAddress(), this.amount);
|
||||
preparedStatement.execute();
|
||||
}
|
||||
|
||||
// Converters
|
||||
|
||||
public static Transaction parse(byte[] data) throws Exception {
|
||||
// TODO
|
||||
return null;
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
@Override
|
||||
public JSONObject toJSON() {
|
||||
JSONObject json = getBaseJSON();
|
||||
|
||||
json.put("recipient", this.recipient.getAddress());
|
||||
json.put("amount", this.amount.toPlainString());
|
||||
|
||||
return json;
|
||||
}
|
||||
|
||||
public byte[] toBytes() {
|
||||
try {
|
||||
ByteArrayOutputStream bytes = new ByteArrayOutputStream(getDataLength());
|
||||
bytes.write(Ints.toByteArray(this.type.value));
|
||||
bytes.write(Longs.toByteArray(this.timestamp));
|
||||
bytes.write(Base58.decode(this.recipient.getAddress()));
|
||||
bytes.write(Serialization.serializeBigDecimal(this.amount));
|
||||
return bytes.toByteArray();
|
||||
} catch (IOException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
// Processing
|
||||
|
||||
/**
|
||||
* Refuse to calculate genesis transaction signature!
|
||||
* <p>
|
||||
* This is not possible as there is no private key for the genesis account and so no way to sign data.
|
||||
* <p>
|
||||
* <b>Always throws IllegalStateException.</b>
|
||||
*
|
||||
* @throws IllegalStateException
|
||||
*/
|
||||
@Override
|
||||
public byte[] calcSignature(PrivateKeyAccount signer) {
|
||||
throw new IllegalStateException("There is no private key for genesis transactions");
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate genesis transaction signature.
|
||||
* <p>
|
||||
* This is handled differently as there is no private key for the genesis account and so no way to sign data.
|
||||
* <p>
|
||||
* Instead we return the SHA-256 digest of the transaction, duplicated so that the returned byte[] is the same length as normal transaction signatures.
|
||||
*
|
||||
* @return byte[]
|
||||
*/
|
||||
private byte[] calcSignature() {
|
||||
byte[] digest = Crypto.digest(toBytes());
|
||||
return Bytes.concat(digest, digest);
|
||||
}
|
||||
|
||||
/**
|
||||
* Check validity of genesis transction signature.
|
||||
* <p>
|
||||
* This is handled differently as there is no private key for the genesis account and so no way to sign/verify data.
|
||||
* <p>
|
||||
* Instead we compared our signature with one generated by {@link GenesisTransaction#calcSignature()}.
|
||||
*
|
||||
* @return boolean
|
||||
*/
|
||||
@Override
|
||||
public boolean isSignatureValid(PublicKeyAccount signer) {
|
||||
return Arrays.equals(this.signature, calcSignature());
|
||||
}
|
||||
|
||||
public ValidationResult isValid(Connection connection) {
|
||||
// Check amount is zero or positive
|
||||
if (this.amount.compareTo(BigDecimal.ZERO) == -1)
|
||||
return ValidationResult.NEGATIVE_AMOUNT;
|
||||
|
||||
// Check recipient address is valid
|
||||
if (!Crypto.isValidAddress(this.recipient.getAddress()))
|
||||
return ValidationResult.INVALID_ADDRESS;
|
||||
|
||||
return ValidationResult.OK;
|
||||
}
|
||||
|
||||
public void process() {
|
||||
// TODO
|
||||
}
|
||||
|
||||
public void orphan() {
|
||||
// TODO
|
||||
}
|
||||
|
||||
}
|
@ -1,5 +1,7 @@
|
||||
package qora.transaction;
|
||||
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.math.BigDecimal;
|
||||
import java.sql.Connection;
|
||||
import java.sql.PreparedStatement;
|
||||
@ -8,15 +10,22 @@ import java.sql.SQLException;
|
||||
|
||||
import org.json.simple.JSONObject;
|
||||
|
||||
import com.google.common.hash.HashCode;
|
||||
import com.google.common.primitives.Ints;
|
||||
import com.google.common.primitives.Longs;
|
||||
|
||||
import database.DB;
|
||||
import database.NoDataFoundException;
|
||||
import qora.account.Account;
|
||||
import qora.account.PublicKeyAccount;
|
||||
import utils.Base58;
|
||||
import utils.Serialization;
|
||||
|
||||
public class PaymentTransaction extends Transaction {
|
||||
|
||||
// Properties
|
||||
private PublicKeyAccount sender;
|
||||
private String recipient;
|
||||
private Account recipient;
|
||||
private BigDecimal amount;
|
||||
|
||||
// Property lengths
|
||||
@ -29,10 +38,10 @@ public class PaymentTransaction extends Transaction {
|
||||
|
||||
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.recipient = recipient;
|
||||
this.recipient = new Account(recipient);
|
||||
this.amount = amount;
|
||||
}
|
||||
|
||||
@ -46,7 +55,7 @@ public class PaymentTransaction extends Transaction {
|
||||
return this.sender;
|
||||
}
|
||||
|
||||
public String getRecipient() {
|
||||
public Account getRecipient() {
|
||||
return this.recipient;
|
||||
}
|
||||
|
||||
@ -72,14 +81,14 @@ public class PaymentTransaction extends Transaction {
|
||||
* @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);
|
||||
if (rs == null)
|
||||
throw new NoDataFoundException();
|
||||
|
||||
this.sender = new PublicKeyAccount(DB.getResultSetBytes(rs.getBinaryStream(1), CREATOR_LENGTH));
|
||||
this.recipient = rs.getString(2);
|
||||
this.recipient = new Account(rs.getString(2));
|
||||
this.amount = rs.getBigDecimal(3).setScale(8);
|
||||
}
|
||||
|
||||
@ -116,22 +125,41 @@ public class PaymentTransaction extends Transaction {
|
||||
return null;
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
@Override
|
||||
public JSONObject toJSON() {
|
||||
// TODO
|
||||
return null;
|
||||
JSONObject json = getBaseJSON();
|
||||
|
||||
json.put("sender", this.sender.getAddress());
|
||||
json.put("senderPublicKey", HashCode.fromBytes(this.sender.getPublicKey()).toString());
|
||||
json.put("recipient", this.recipient.getAddress());
|
||||
json.put("amount", this.amount.toPlainString());
|
||||
|
||||
return json;
|
||||
}
|
||||
|
||||
public byte[] toBytes() {
|
||||
// TODO
|
||||
return new byte[0];
|
||||
try {
|
||||
ByteArrayOutputStream bytes = new ByteArrayOutputStream(getDataLength());
|
||||
bytes.write(Ints.toByteArray(this.type.value));
|
||||
bytes.write(Longs.toByteArray(this.timestamp));
|
||||
bytes.write(this.reference);
|
||||
bytes.write(this.sender.getPublicKey());
|
||||
bytes.write(Base58.decode(this.recipient.getAddress()));
|
||||
bytes.write(Serialization.serializeBigDecimal(this.amount));
|
||||
bytes.write(Serialization.serializeBigDecimal(this.fee));
|
||||
bytes.write(this.signature);
|
||||
return bytes.toByteArray();
|
||||
} catch (IOException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
// Processing
|
||||
|
||||
public int isValid() {
|
||||
public ValidationResult isValid(Connection connection) {
|
||||
// TODO
|
||||
return VALIDATE_OK;
|
||||
return ValidationResult.OK;
|
||||
}
|
||||
|
||||
public void process() {
|
||||
|
@ -8,6 +8,7 @@ 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;
|
||||
import static java.util.stream.Collectors.toMap;
|
||||
@ -19,14 +20,18 @@ import database.NoDataFoundException;
|
||||
import qora.account.PrivateKeyAccount;
|
||||
import qora.account.PublicKeyAccount;
|
||||
import qora.block.Block;
|
||||
import qora.block.BlockChain;
|
||||
import qora.block.BlockTransaction;
|
||||
import settings.Settings;
|
||||
|
||||
import utils.Base58;
|
||||
|
||||
public abstract class Transaction {
|
||||
|
||||
// Transaction types
|
||||
public enum TransactionType {
|
||||
Genesis(1), Payment(2);
|
||||
GENESIS(1), PAYMENT(2), REGISTER_NAME(3), UPDATE_NAME(4), SELL_NAME(5), CANCEL_SELL_NAME(6), BUY_NAME(7), CREATE_POLL(8), VOTE_ON_POLL(9), ARBITRARY(
|
||||
10), ISSUE_ASSET(11), TRANSFER_ASSET(12), CREATE_ASSET_ORDER(13), CANCEL_ASSET_ORDER(14), MULTIPAYMENT(15), DEPLOY_AT(16), MESSAGE(17);
|
||||
|
||||
public final int value;
|
||||
|
||||
@ -42,7 +47,21 @@ public abstract class Transaction {
|
||||
}
|
||||
|
||||
// Validation results
|
||||
public static final int VALIDATE_OK = 1;
|
||||
public enum ValidationResult {
|
||||
OK(1), INVALID_ADDRESS(2), NEGATIVE_AMOUNT(3);
|
||||
|
||||
public final int value;
|
||||
|
||||
private final static Map<Integer, ValidationResult> map = stream(ValidationResult.values()).collect(toMap(result -> result.value, result -> result));
|
||||
|
||||
ValidationResult(int value) {
|
||||
this.value = value;
|
||||
}
|
||||
|
||||
public static ValidationResult valueOf(int value) {
|
||||
return map.get(value);
|
||||
}
|
||||
}
|
||||
|
||||
// Minimum fee
|
||||
public static final BigDecimal MINIMUM_FEE = BigDecimal.ONE;
|
||||
@ -149,6 +168,40 @@ public abstract class Transaction {
|
||||
return recommendedFee.setScale(8);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get block height for this transaction in the blockchain.
|
||||
*
|
||||
* @param connection
|
||||
* @return height, or 0 if not in blockchain (i.e. unconfirmed)
|
||||
* @throws SQLException
|
||||
*/
|
||||
public int getHeight(Connection connection) throws SQLException {
|
||||
if (this.signature == null)
|
||||
return 0;
|
||||
|
||||
BlockTransaction blockTx = BlockTransaction.fromTransactionSignature(connection, this.signature);
|
||||
if (blockTx == null)
|
||||
return 0;
|
||||
|
||||
return BlockChain.getBlockHeightFromSignature(connection, blockTx.getBlockSignature());
|
||||
}
|
||||
|
||||
/**
|
||||
* Get number of confirmations for this transaction.
|
||||
*
|
||||
* @param connection
|
||||
* @return confirmation count, or 0 if not in blockchain (i.e. unconfirmed)
|
||||
* @throws SQLException
|
||||
*/
|
||||
public int getConfirmations(Connection connection) throws SQLException {
|
||||
int ourHeight = this.getHeight(connection);
|
||||
if (ourHeight == 0)
|
||||
return 0;
|
||||
|
||||
int blockChainHeight = BlockChain.getMaxHeight(connection);
|
||||
return blockChainHeight - ourHeight + 1;
|
||||
}
|
||||
|
||||
// Load/Save
|
||||
|
||||
// Typically called by sub-class' load-from-DB constructors
|
||||
@ -238,24 +291,76 @@ public abstract class Transaction {
|
||||
|
||||
public abstract JSONObject toJSON();
|
||||
|
||||
/**
|
||||
* Produce JSON representation of common/base Transaction info.
|
||||
* <p>
|
||||
* To include info on number of confirmations, a Connection object is required. See {@link Transaction#getBaseJSON(Connection)}
|
||||
*
|
||||
* @return JSONObject
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
protected JSONObject getBaseJSON() {
|
||||
JSONObject json = new JSONObject();
|
||||
|
||||
json.put("type", this.type.value);
|
||||
json.put("fee", this.fee.toPlainString());
|
||||
json.put("timestamp", this.timestamp);
|
||||
json.put("reference", Base58.encode(this.reference));
|
||||
json.put("signature", Base58.encode(this.signature));
|
||||
|
||||
return json;
|
||||
}
|
||||
|
||||
/**
|
||||
* Produce JSON representation of common/base Transaction info, including number of confirmations.
|
||||
* <p>
|
||||
* Requires SQL Connection object to determine number of confirmations.
|
||||
*
|
||||
* @param connection
|
||||
* @return JSONObject
|
||||
* @throws SQLException
|
||||
* @see Transaction#getBaseJSON()
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
protected JSONObject getBaseJSON(Connection connection) throws SQLException {
|
||||
JSONObject json = this.getBaseJSON();
|
||||
|
||||
json.put("confirmations", this.getConfirmations(connection));
|
||||
|
||||
return json;
|
||||
}
|
||||
|
||||
/**
|
||||
* Serialize transaction as byte[], including signature.
|
||||
*
|
||||
* @return byte[]
|
||||
*/
|
||||
public abstract byte[] toBytes();
|
||||
|
||||
/**
|
||||
* Serialize transaction as byte[], stripping off trailing signature.
|
||||
*
|
||||
* @return byte[]
|
||||
*/
|
||||
private byte[] toBytesLessSignature() {
|
||||
byte[] bytes = this.toBytes();
|
||||
return Arrays.copyOf(bytes, bytes.length - SIGNATURE_LENGTH);
|
||||
}
|
||||
|
||||
// Processing
|
||||
|
||||
public byte[] calcSignature(PrivateKeyAccount signer) {
|
||||
byte[] bytes = this.toBytes();
|
||||
|
||||
return signer.sign(bytes);
|
||||
return signer.sign(this.toBytesLessSignature());
|
||||
}
|
||||
|
||||
public boolean isSignatureValid(PublicKeyAccount signer) {
|
||||
if (this.signature == null)
|
||||
return false;
|
||||
|
||||
return signer.verify(this.signature, this.toBytes());
|
||||
return signer.verify(this.signature, this.toBytesLessSignature());
|
||||
}
|
||||
|
||||
public abstract int isValid();
|
||||
public abstract ValidationResult isValid(Connection connection);
|
||||
|
||||
public abstract void process();
|
||||
|
||||
|
@ -46,11 +46,10 @@ public class TransactionFactory {
|
||||
byte[] signature = DB.getResultSetBytes(resultSet.getBinaryStream(2), Transaction.SIGNATURE_LENGTH);
|
||||
|
||||
switch (type) {
|
||||
case Genesis:
|
||||
// return new GenesisTransaction(connection, signature);
|
||||
return null;
|
||||
case GENESIS:
|
||||
return GenesisTransaction.fromSignature(connection, signature);
|
||||
|
||||
case Payment:
|
||||
case PAYMENT:
|
||||
return PaymentTransaction.fromSignature(connection, signature);
|
||||
|
||||
default:
|
||||
|
@ -41,7 +41,7 @@ public class load {
|
||||
|
||||
assertEquals(paymentTransaction.getSender().getAddress(), "QXwu8924WdgPoRmtiWQBUMF6eedmp1Hu2E");
|
||||
assertEquals(paymentTransaction.getCreator().getAddress(), "QXwu8924WdgPoRmtiWQBUMF6eedmp1Hu2E");
|
||||
assertEquals(paymentTransaction.getRecipient(), "QZsv8vbJ6QfrBNba4LMp5UtHhAzhrxvVUU");
|
||||
assertEquals(paymentTransaction.getRecipient().getAddress(), "QZsv8vbJ6QfrBNba4LMp5UtHhAzhrxvVUU");
|
||||
assertEquals(paymentTransaction.getTimestamp(), 1416209264000L);
|
||||
assertEquals(Base58.encode(paymentTransaction.getReference()),
|
||||
"31dC6kHHBeG5vYb8LMaZDjLEmhc9kQB2VUApVd8xWncSRiXu7yMejdprjYFMP2rUnzZxWd4KJhkq6LsV7rQvU1kY");
|
||||
|
44
src/test/signatures.java
Normal file
44
src/test/signatures.java
Normal file
@ -0,0 +1,44 @@
|
||||
package test;
|
||||
|
||||
import static org.junit.Assert.*;
|
||||
|
||||
import java.sql.Connection;
|
||||
import java.sql.SQLException;
|
||||
|
||||
import org.junit.After;
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
|
||||
import qora.block.GenesisBlock;
|
||||
import utils.Base58;
|
||||
|
||||
public class signatures {
|
||||
|
||||
private static Connection connection;
|
||||
|
||||
@Before
|
||||
public void connect() throws SQLException {
|
||||
connection = common.getConnection();
|
||||
}
|
||||
|
||||
@After
|
||||
public void disconnect() {
|
||||
try {
|
||||
connection.createStatement().execute("SHUTDOWN");
|
||||
} catch (SQLException e) {
|
||||
fail();
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGenesisBlockSignature() throws SQLException {
|
||||
String expected58 = "6pHMBFif7jXFG654joT8GPaymau1fMtaxacRyqSrnAwQMQDvqRuLpHpfFyqX4gWVvj4pF1mwQhFgqWAvjVvPJUjmBZQvL751dM9cEcQBTaUcxtNLuWZCVUAtbnWN9f7FsLppHhkPbxwpoodL3UJYRGt3EZrG17mhv1RJbmq8j6rr7Mk";
|
||||
|
||||
GenesisBlock block = GenesisBlock.getInstance();
|
||||
|
||||
System.out.println("Generator: " + block.getGenerator().getAddress() + ", generation signature: " + Base58.encode(block.getGenerationSignature()));
|
||||
|
||||
assertEquals(expected58, Base58.encode(block.getSignature()));
|
||||
}
|
||||
|
||||
}
|
20
src/utils/Serialization.java
Normal file
20
src/utils/Serialization.java
Normal file
@ -0,0 +1,20 @@
|
||||
package utils;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
|
||||
public class Serialization {
|
||||
|
||||
/**
|
||||
* Convert BigDecimal, unscaled, to byte[] then prepend with zero bytes to fixed length of 8.
|
||||
*
|
||||
* @param amount
|
||||
* @return byte[8]
|
||||
*/
|
||||
public static byte[] serializeBigDecimal(BigDecimal amount) {
|
||||
byte[] amountBytes = amount.unscaledValue().toByteArray();
|
||||
byte[] output = new byte[8];
|
||||
System.arraycopy(amountBytes, 0, output, 8 - amountBytes.length, amountBytes.length);
|
||||
return output;
|
||||
}
|
||||
|
||||
}
|
Loading…
Reference in New Issue
Block a user