WORK IN PROGRESS

Still converting to repository layout.
This commit is just in case my dev computer blows up and also for interim code review.

Removed data.block.BlockData as an interface (with data.block.Block as implementation) for now.
This commit is contained in:
catbref 2018-06-11 12:09:16 +01:00
parent 6c33cfed74
commit 8220113613
26 changed files with 888 additions and 1074 deletions

View File

@ -1,6 +1,6 @@
package data.account;
public class Account {
public class AccountData {
// Properties
protected String address;
@ -8,10 +8,10 @@ public class Account {
// Constructors
protected Account() {
protected AccountData() {
}
public Account(String address) {
public AccountData(String address) {
this.address = address;
}
@ -33,10 +33,10 @@ public class Account {
@Override
public boolean equals(Object b) {
if (!(b instanceof Account))
if (!(b instanceof AccountData))
return false;
return this.getAddress().equals(((Account) b).getAddress());
return this.getAddress().equals(((AccountData) b).getAddress());
}
@Override

View File

@ -1,9 +0,0 @@
package data.account;
public final class GenesisAccount extends PublicKeyAccount {
public GenesisAccount() {
super(new byte[] { 1, 1, 1, 1, 1, 1, 1, 1 });
}
}

View File

@ -1,27 +0,0 @@
package data.account;
import qora.crypto.Crypto;
public class PublicKeyAccount extends Account {
// Properties
protected byte[] publicKey;
// Constructors
public PublicKeyAccount(byte[] publicKey) {
super(Crypto.toAddress(publicKey));
this.publicKey = publicKey;
}
protected PublicKeyAccount() {
}
// Getters/Setters
public byte[] getPublicKey() {
return this.publicKey;
}
}

View File

@ -1,86 +0,0 @@
package data.block;
import java.math.BigDecimal;
import qora.account.PublicKeyAccount;
public class Block implements BlockData {
private int version;
private byte[] reference;
private int transactionCount;
private BigDecimal totalFees;
private byte[] transactionsSignature;
private int height;
private long timestamp;
private BigDecimal generatingBalance;
private byte[] generatorPublicKey;
private byte[] generatorSignature;
private byte[] atBytes;
private BigDecimal atFees;
public Block(int version, byte[] reference, int transactionCount, BigDecimal totalFees, byte[] transactionsSignature,
int height, long timestamp, BigDecimal generatingBalance, byte[] generatorPublicKey, byte[] generatorSignature,
byte[] atBytes, BigDecimal atFees)
{
this.version = version;
this.reference = reference;
this.transactionCount = transactionCount;
this.totalFees = totalFees;
this.transactionsSignature = transactionsSignature;
this.height = height;
this.timestamp = timestamp;
this.generatingBalance = generatingBalance;
this.generatorPublicKey = generatorPublicKey;
this.generatorSignature = generatorSignature;
this.atBytes = atBytes;
this.atFees = atFees;
}
public int getVersion() {
return version;
}
public byte[] getReference() {
return reference;
}
public int getTransactionCount() {
return transactionCount;
}
public BigDecimal getTotalFees() {
return totalFees;
}
public byte[] getTransactionsSignature() {
return transactionsSignature;
}
public int getHeight() {
return height;
}
public long getTimestamp() {
return timestamp;
}
public BigDecimal getGeneratingBalance() {
return generatingBalance;
}
public byte[] getGeneratorPublicKey() {
return generatorPublicKey;
}
public byte[] getGeneratorSignature() {
return generatorSignature;
}
public byte[] getAtBytes() {
return atBytes;
}
public BigDecimal getAtFees() {
return atFees;
}
}

View File

@ -2,17 +2,119 @@ package data.block;
import java.math.BigDecimal;
public interface BlockData {
public int getVersion();
public byte[] getReference();
public int getTransactionCount();
public BigDecimal getTotalFees();
public byte[] getTransactionsSignature();
public int getHeight();
public long getTimestamp();
public BigDecimal getGeneratingBalance();
public byte[] getGeneratorPublicKey();
public byte[] getGeneratorSignature();
public byte[] getAtBytes();
public BigDecimal getAtFees();
import com.google.common.primitives.Bytes;
public class BlockData {
private byte[] signature;
private int version;
private byte[] reference;
private int transactionCount;
private BigDecimal totalFees;
private byte[] transactionsSignature;
private int height;
private long timestamp;
private BigDecimal generatingBalance;
private byte[] generatorPublicKey;
private byte[] generatorSignature;
private byte[] atBytes;
private BigDecimal atFees;
public BlockData(int version, byte[] reference, int transactionCount, BigDecimal totalFees, byte[] transactionsSignature, int height, long timestamp,
BigDecimal generatingBalance, byte[] generatorPublicKey, byte[] generatorSignature, byte[] atBytes, BigDecimal atFees) {
this.version = version;
this.reference = reference;
this.transactionCount = transactionCount;
this.totalFees = totalFees;
this.transactionsSignature = transactionsSignature;
this.height = height;
this.timestamp = timestamp;
this.generatingBalance = generatingBalance;
this.generatorPublicKey = generatorPublicKey;
this.generatorSignature = generatorSignature;
this.atBytes = atBytes;
this.atFees = atFees;
if (this.generatorSignature != null && this.transactionsSignature != null)
this.signature = Bytes.concat(this.generatorSignature, this.transactionsSignature);
else
this.signature = null;
}
public int getTransactionCount() {
return this.transactionCount;
}
public void setTransactionCount(int transactionCount) {
this.transactionCount = transactionCount;
}
public BigDecimal getTotalFees() {
return this.totalFees;
}
public void setTotalFees(BigDecimal totalFees) {
this.totalFees = totalFees;
}
public byte[] getTransactionsSignature() {
return this.transactionsSignature;
}
public void setTransactionsSignature(byte[] transactionsSignature) {
this.transactionsSignature = transactionsSignature;
}
public byte[] getSignature() {
return this.signature;
}
public int getVersion() {
return this.version;
}
public byte[] getReference() {
return this.reference;
}
public void setReference(byte[] reference) {
this.reference = reference;
}
public int getHeight() {
return this.height;
}
public void setHeight(int height) {
this.height = height;
}
public long getTimestamp() {
return this.timestamp;
}
public BigDecimal getGeneratingBalance() {
return this.generatingBalance;
}
public byte[] getGeneratorPublicKey() {
return this.generatorPublicKey;
}
public byte[] getGeneratorSignature() {
return this.generatorSignature;
}
public void setGeneratorSignature(byte[] generatorSignature) {
this.generatorSignature = generatorSignature;
}
public byte[] getAtBytes() {
return this.atBytes;
}
public BigDecimal getAtFees() {
return this.atFees;
}
}

View File

@ -1,37 +0,0 @@
package data.transaction;
import java.math.BigDecimal;
import data.account.Account;
import data.account.GenesisAccount;
public class GenesisTransaction extends Transaction {
// Properties
private Account recipient;
private BigDecimal amount;
// Constructors
public GenesisTransaction(Account recipient, BigDecimal amount, long timestamp, byte[] signature) {
super(TransactionType.GENESIS, BigDecimal.ZERO, new GenesisAccount(), timestamp, signature);
this.recipient = recipient;
this.amount = amount;
}
public GenesisTransaction(Account recipient, BigDecimal amount, long timestamp) {
this(recipient, amount, timestamp, null);
}
// Getters/Setters
public Account getRecipient() {
return this.recipient;
}
public BigDecimal getAmount() {
return this.amount;
}
}

View File

@ -0,0 +1,37 @@
package data.transaction;
import java.math.BigDecimal;
import qora.account.GenesisAccount;
import qora.transaction.Transaction.TransactionType;
public class GenesisTransactionData extends TransactionData {
// Properties
private String recipient;
private BigDecimal amount;
// Constructors
public GenesisTransactionData(String recipient, BigDecimal amount, long timestamp, byte[] signature) {
super(TransactionType.GENESIS, BigDecimal.ZERO, new GenesisAccount().getPublicKey(), timestamp, signature);
this.recipient = recipient;
this.amount = amount;
}
public GenesisTransactionData(String recipient, BigDecimal amount, long timestamp) {
this(recipient, amount, timestamp, null);
}
// Getters/Setters
public String getRecipient() {
return this.recipient;
}
public BigDecimal getAmount() {
return this.amount;
}
}

View File

@ -1,83 +0,0 @@
package data.transaction;
import java.math.BigDecimal;
import java.util.Map;
import data.account.PublicKeyAccount;
import static java.util.Arrays.stream;
import static java.util.stream.Collectors.toMap;
public abstract class Transaction {
// Transaction types
// TODO Transaction types are semantic and should go into the business logic layer.
// No need to know the meaning of the integer value in data layer
public enum TransactionType {
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;
private final static Map<Integer, TransactionType> map = stream(TransactionType.values()).collect(toMap(type -> type.value, type -> type));
TransactionType(int value) {
this.value = value;
}
public static TransactionType valueOf(int value) {
return map.get(value);
}
}
// Properties shared with all transaction types
protected TransactionType type;
// TODO PublicKeyAccount is a separate data entity, so here should only be a key to reference it
protected PublicKeyAccount creator;
protected long timestamp;
protected byte[] reference;
protected BigDecimal fee;
protected byte[] signature;
// Constructors
public Transaction(TransactionType type, BigDecimal fee, PublicKeyAccount creator, long timestamp, byte[] reference, byte[] signature) {
this.fee = fee;
this.type = type;
this.creator = creator;
this.timestamp = timestamp;
this.reference = reference;
this.signature = signature;
}
public Transaction(TransactionType type, BigDecimal fee, PublicKeyAccount creator, long timestamp, byte[] reference) {
this(type, fee, creator, timestamp, reference, null);
}
// Getters/setters
public TransactionType getType() {
return this.type;
}
public PublicKeyAccount getCreator() {
return this.creator;
}
public long getTimestamp() {
return this.timestamp;
}
public byte[] getReference() {
return this.reference;
}
public BigDecimal getFee() {
return this.fee;
}
public byte[] getSignature() {
return this.signature;
}
}

View File

@ -0,0 +1,58 @@
package data.transaction;
import java.math.BigDecimal;
import qora.transaction.Transaction.TransactionType;
public abstract class TransactionData {
// Properties shared with all transaction types
protected TransactionType type;
protected byte[] creatorPublicKey;
protected long timestamp;
protected byte[] reference;
protected BigDecimal fee;
protected byte[] signature;
// Constructors
public TransactionData(TransactionType type, BigDecimal fee, byte[] creatorPublicKey, long timestamp, byte[] reference, byte[] signature) {
this.fee = fee;
this.type = type;
this.creatorPublicKey = creatorPublicKey;
this.timestamp = timestamp;
this.reference = reference;
this.signature = signature;
}
public TransactionData(TransactionType type, BigDecimal fee, byte[] creatorPublicKey, long timestamp, byte[] reference) {
this(type, fee, creatorPublicKey, timestamp, reference, null);
}
// Getters/setters
public TransactionType getType() {
return this.type;
}
public byte[] getCreatorPublicKey() {
return this.creatorPublicKey;
}
public long getTimestamp() {
return this.timestamp;
}
public byte[] getReference() {
return this.reference;
}
public BigDecimal getFee() {
return this.fee;
}
public byte[] getSignature() {
return this.signature;
}
}

View File

@ -8,8 +8,9 @@ public class PublicKeyAccount extends Account {
protected byte[] publicKey;
public PublicKeyAccount(byte[] publicKey) {
super(Crypto.toAddress(publicKey));
this.publicKey = publicKey;
this.address = Crypto.toAddress(this.publicKey);
}
protected PublicKeyAccount() {

View File

@ -6,17 +6,8 @@ import java.sql.SQLException;
import database.DB;
import database.NoDataFoundException;
import qora.account.Account;
import qora.transaction.TransactionHandler;
import repository.hsqldb.HSQLDBSaver;
/*
* TODO:
* Probably need to standardize on using assetId or assetKey for the long value, and plain "asset" for the java object.
* Thus in the database the primary key column could be called "asset_id".
* In the Order object, we'd pass longs to variables with names like "haveAssetId" and use getters like "getHaveAssetId"
* which frees up other method names like "getHaveAsset" to return a java Asset object.
*/
public class Asset {
public static final long QORA = 0L;
@ -91,7 +82,7 @@ public class Asset {
this.description = rs.getString(3);
this.quantity = rs.getLong(4);
this.isDivisible = rs.getBoolean(5);
this.reference = DB.getResultSetBytes(rs.getBinaryStream(6), TransactionHandler.REFERENCE_LENGTH);
this.reference = DB.getResultSetBytes(rs.getBinaryStream(6));
}
public static Asset fromAssetId(long assetId) throws SQLException {

View File

@ -22,6 +22,8 @@ import com.google.common.primitives.Bytes;
import com.google.common.primitives.Ints;
import com.google.common.primitives.Longs;
import data.block.BlockData;
import data.transaction.TransactionData;
import database.DB;
import database.NoDataFoundException;
import qora.account.PrivateKeyAccount;
@ -31,10 +33,14 @@ import qora.assets.Order;
import qora.assets.Trade;
import qora.transaction.CreateOrderTransaction;
import qora.transaction.GenesisTransaction;
import qora.transaction.TransactionHandler;
import qora.transaction.Transaction;
import repository.BlockRepository;
import repository.DataException;
import repository.RepositoryManager;
import repository.hsqldb.HSQLDBSaver;
import qora.transaction.TransactionHandler;
import transform.TransformationException;
import transform.block.BlockTransformer;
import transform.transaction.TransactionTransformer;
import utils.Base58;
import utils.NTP;
import utils.Serialization;
@ -63,51 +69,16 @@ import utils.Serialization;
public class Block {
/**
* Ordered list of columns when fetching a Block row from database.
*/
private static final String DB_COLUMNS = "version, reference, transaction_count, total_fees, "
+ "transactions_signature, height, generation, generating_balance, generator, generator_signature, AT_data, AT_fees";
// Database properties
protected int version;
protected byte[] reference;
protected int transactionCount;
protected BigDecimal totalFees;
protected byte[] transactionsSignature;
protected int height;
protected long timestamp;
protected BigDecimal generatingBalance;
protected PublicKeyAccount generator;
protected byte[] generatorSignature;
protected byte[] atBytes;
protected BigDecimal atFees;
// Properties
private BlockData blockData;
private PublicKeyAccount generator;
// Other properties
protected List<TransactionHandler> transactions;
protected List<Transaction> transactions;
protected BigDecimal cachedNextGeneratingBalance;
// Property lengths for serialisation
protected static final int VERSION_LENGTH = 4;
protected static final int TRANSACTIONS_SIGNATURE_LENGTH = 64;
protected static final int GENERATOR_SIGNATURE_LENGTH = 64;
protected static final int REFERENCE_LENGTH = GENERATOR_SIGNATURE_LENGTH + TRANSACTIONS_SIGNATURE_LENGTH;
protected static final int TIMESTAMP_LENGTH = 8;
protected static final int GENERATING_BALANCE_LENGTH = 8;
protected static final int GENERATOR_LENGTH = 32;
protected static final int TRANSACTION_COUNT_LENGTH = 4;
protected static final int BASE_LENGTH = VERSION_LENGTH + REFERENCE_LENGTH + TIMESTAMP_LENGTH + GENERATING_BALANCE_LENGTH + GENERATOR_LENGTH
+ TRANSACTIONS_SIGNATURE_LENGTH + GENERATOR_SIGNATURE_LENGTH + TRANSACTION_COUNT_LENGTH;
// Other length constants
protected static final int BLOCK_SIGNATURE_LENGTH = GENERATOR_SIGNATURE_LENGTH + TRANSACTIONS_SIGNATURE_LENGTH;
public static final int MAX_BLOCK_BYTES = 1048576;
protected static final int TRANSACTION_SIZE_LENGTH = 4; // per transaction
protected static final int AT_BYTES_LENGTH = 4;
protected static final int AT_FEES_LENGTH = 8;
protected static final int AT_LENGTH = AT_FEES_LENGTH + AT_BYTES_LENGTH;
// Other useful constants
public static final int MAX_BLOCK_BYTES = 1048576;
/**
* Number of blocks between recalculating block's generating balance.
*/
@ -125,91 +96,15 @@ public class Block {
// Constructors
// For creating a new block from scratch
public Block(int version, byte[] reference, long timestamp, BigDecimal generatingBalance, PublicKeyAccount generator, byte[] atBytes, BigDecimal atFees) {
this.version = version;
this.reference = reference;
this.timestamp = timestamp;
this.generatingBalance = generatingBalance;
this.generator = generator;
this.generatorSignature = null;
this.height = 0;
this.transactionCount = 0;
this.transactions = new ArrayList<TransactionHandler>();
this.transactionsSignature = null;
this.totalFees = BigDecimal.ZERO.setScale(8);
this.atBytes = atBytes;
this.atFees = atFees;
if (this.atFees != null)
this.totalFees = this.totalFees.add(this.atFees);
}
// For instantiating a block that was previously serialized
protected Block(int version, byte[] reference, long timestamp, BigDecimal generatingBalance, PublicKeyAccount generator, byte[] generatorSignature,
byte[] transactionsSignature, byte[] atBytes, BigDecimal atFees, List<TransactionHandler> transactions) {
this(version, reference, timestamp, generatingBalance, generator, atBytes, atFees);
this.generatorSignature = generatorSignature;
this.transactionsSignature = transactionsSignature;
this.transactionCount = transactions.size();
this.transactions = transactions;
// Add transactions' fees to totalFees
for (TransactionHandler transaction : this.transactions)
this.totalFees = this.totalFees.add(transaction.getFee());
public Block(BlockData blockData) {
this.blockData = blockData;
this.generator = new PublicKeyAccount(blockData.getGeneratorPublicKey());
}
// Getters/setters
public int getVersion() {
return this.version;
}
public byte[] getReference() {
return this.reference;
}
public long getTimestamp() {
return this.timestamp;
}
public BigDecimal getGeneratingBalance() {
return this.generatingBalance;
}
public PublicKeyAccount getGenerator() {
return this.generator;
}
public byte[] getGeneratorSignature() {
return this.generatorSignature;
}
public byte[] getTransactionsSignature() {
return this.transactionsSignature;
}
public BigDecimal getTotalFees() {
return this.totalFees;
}
public int getTransactionCount() {
return this.transactionCount;
}
public byte[] getATBytes() {
return this.atBytes;
}
public BigDecimal getATFees() {
return this.atFees;
}
public int getHeight() {
return this.height;
public BlockData getBlockData() {
return this.blockData;
}
// More information
@ -220,26 +115,10 @@ public class Block {
* @return byte[], or null if either component signature is null.
*/
public byte[] getSignature() {
if (this.generatorSignature == null || this.transactionsSignature == null)
if (this.blockData.getGeneratorSignature() == null || this.blockData.getTransactionsSignature() == null)
return null;
return Bytes.concat(this.generatorSignature, this.transactionsSignature);
}
public int getDataLength() {
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 (TransactionHandler transaction : this.transactions)
blockLength += TRANSACTION_SIZE_LENGTH + transaction.getDataLength();
return blockLength;
return Bytes.concat(this.blockData.getGeneratorSignature(), this.blockData.getTransactionsSignature());
}
/**
@ -248,9 +127,9 @@ public class Block {
* @return 1, 2 or 3
*/
public int getNextBlockVersion() {
if (this.height < AT_BLOCK_HEIGHT_RELEASE)
if (this.blockData.getHeight() < AT_BLOCK_HEIGHT_RELEASE)
return 1;
else if (this.timestamp < POWFIX_RELEASE_TIMESTAMP)
else if (this.blockData.getTimestamp() < POWFIX_RELEASE_TIMESTAMP)
return 2;
else
return 3;
@ -269,8 +148,8 @@ public class Block {
*/
public BigDecimal getNextBlockGeneratingBalance() throws SQLException {
// This block not at the start of an interval?
if (this.height % BLOCK_RETARGET_INTERVAL != 0)
return this.generatingBalance;
if (this.blockData.getHeight() % BLOCK_RETARGET_INTERVAL != 0)
return this.blockData.getGeneratingBalance();
// Return cached calculation if we have one
if (this.cachedNextGeneratingBalance != null)
@ -280,23 +159,29 @@ public class Block {
// Navigate back to first block in previous interval:
// XXX: why can't we simply load using block height?
Block firstBlock = this;
for (int i = 1; firstBlock != null && i < BLOCK_RETARGET_INTERVAL; ++i)
firstBlock = firstBlock.getParent();
BlockRepository blockRepo = RepositoryManager.getBlockRepository();
BlockData firstBlock = this.blockData;
try {
for (int i = 1; firstBlock != null && i < BLOCK_RETARGET_INTERVAL; ++i)
firstBlock = blockRepo.fromSignature(firstBlock.getReference());
} catch (DataException e) {
firstBlock = null;
}
// Couldn't navigate back far enough?
if (firstBlock == null)
throw new IllegalStateException("Failed to calculate next block's generating balance due to lack of historic blocks");
// Calculate the actual time period (in ms) over previous interval's blocks.
long previousGeneratingTime = this.timestamp - firstBlock.getTimestamp();
long previousGeneratingTime = this.blockData.getTimestamp() - firstBlock.getTimestamp();
// Calculate expected forging time (in ms) for a whole interval based on this block's generating balance.
long expectedGeneratingTime = Block.calcForgingDelay(this.generatingBalance) * BLOCK_RETARGET_INTERVAL * 1000;
long expectedGeneratingTime = Block.calcForgingDelay(this.blockData.getGeneratingBalance()) * BLOCK_RETARGET_INTERVAL * 1000;
// Finally, scale generating balance such that faster than expected previous intervals produce larger generating balances.
BigDecimal multiplier = BigDecimal.valueOf((double) expectedGeneratingTime / (double) previousGeneratingTime);
this.cachedNextGeneratingBalance = BlockChain.minMaxBalance(this.generatingBalance.multiply(multiplier));
this.cachedNextGeneratingBalance = BlockChain.minMaxBalance(this.blockData.getGeneratingBalance().multiply(multiplier));
return this.cachedNextGeneratingBalance;
}
@ -316,310 +201,31 @@ public class Block {
/**
* Return block's transactions.
* <p>
* If the block was loaded from DB then it's possible this method will call the DB to load the transactions if they are not already loaded.
* If the block was loaded from repository then it's possible this method will call the repository to load the transactions if they are not already loaded.
*
* @return
* @throws SQLException
* @throws DataException
*/
public List<TransactionHandler> getTransactions() throws SQLException {
public List<Transaction> getTransactions() throws DataException {
// Already loaded?
if (this.transactions != null)
return this.transactions;
// Allocate cache for results
this.transactions = new ArrayList<TransactionHandler>();
List<TransactionData> transactionsData = RepositoryManager.getBlockRepository().getTransactionsFromSignature(this.blockData.getSignature());
ResultSet rs = DB.checkedExecute("SELECT transaction_signature FROM BlockTransactions WHERE block_signature = ?", this.getSignature());
if (rs == null)
return this.transactions; // No transactions in this block
// NB: do-while loop because DB.checkedExecute() implicitly calls ResultSet.next() for us
do {
byte[] transactionSignature = DB.getResultSetBytes(rs.getBinaryStream(1), TransactionHandler.SIGNATURE_LENGTH);
this.transactions.add(TransactionFactory.fromSignature(transactionSignature));
// No need to update totalFees as this will be loaded via the Blocks table
} while (rs.next());
// The number of transactions fetched from database should correspond with Block's transactionCount
if (this.transactions.size() != this.transactionCount)
throw new IllegalStateException("Block's transactions from database do not match block's transaction count");
// The number of transactions fetched from repository should correspond with Block's transactionCount
if (transactionsData.size() != this.blockData.getTransactionCount())
throw new IllegalStateException("Block's transactions from repository do not match block's transaction count");
this.transactions = new ArrayList<Transaction>();
for (TransactionData transactionData : transactionsData)
this.transactions.add(Transaction.fromData(transactionData));
return this.transactions;
}
// Load/Save
protected Block(byte[] signature) throws SQLException {
this(DB.checkedExecute("SELECT " + DB_COLUMNS + " FROM Blocks WHERE signature = ?", signature));
}
protected Block(ResultSet rs) throws SQLException {
if (rs == null)
throw new NoDataFoundException();
this.version = rs.getInt(1);
this.reference = DB.getResultSetBytes(rs.getBinaryStream(2), REFERENCE_LENGTH);
this.transactionCount = rs.getInt(3);
this.totalFees = rs.getBigDecimal(4);
this.transactionsSignature = DB.getResultSetBytes(rs.getBinaryStream(5), TRANSACTIONS_SIGNATURE_LENGTH);
this.height = rs.getInt(6);
this.timestamp = rs.getTimestamp(7).getTime();
this.generatingBalance = rs.getBigDecimal(8);
// Note: can't use GENERATOR_LENGTH in case we encounter Genesis Account's short, 8-byte public key
this.generator = new PublicKeyAccount(DB.getResultSetBytes(rs.getBinaryStream(9)));
this.generatorSignature = DB.getResultSetBytes(rs.getBinaryStream(10), GENERATOR_SIGNATURE_LENGTH);
this.atBytes = DB.getResultSetBytes(rs.getBinaryStream(11));
this.atFees = rs.getBigDecimal(12);
}
/**
* Load Block from DB using block signature.
*
* @param signature
* @return Block, or null if not found
* @throws SQLException
*/
public static Block fromSignature(byte[] signature) throws SQLException {
try {
return new Block(signature);
} catch (NoDataFoundException e) {
return null;
}
}
/**
* Load Block from DB using block height
*
* @param height
* @return Block, or null if not found
* @throws SQLException
*/
public static Block fromHeight(int height) throws SQLException {
PreparedStatement preparedStatement = DB.getConnection().prepareStatement("SELECT " + DB_COLUMNS + " FROM Blocks WHERE height = ?");
preparedStatement.setInt(1, height);
try {
return new Block(DB.checkedExecute(preparedStatement));
} catch (NoDataFoundException e) {
return null;
}
}
protected void save() throws SQLException {
HSQLDBSaver saveHelper = new HSQLDBSaver("Blocks");
saveHelper.bind("signature", this.getSignature()).bind("version", this.version).bind("reference", this.reference)
.bind("transaction_count", this.transactionCount).bind("total_fees", this.totalFees).bind("transactions_signature", this.transactionsSignature)
.bind("height", this.height).bind("generation", new Timestamp(this.timestamp)).bind("generating_balance", this.generatingBalance)
.bind("generator", this.generator.getPublicKey()).bind("generator_signature", this.generatorSignature).bind("AT_data", this.atBytes)
.bind("AT_fees", this.atFees);
saveHelper.execute();
}
// Navigation
/**
* Load parent Block from DB
*
* @return Block, or null if not found
* @throws SQLException
*/
public Block getParent() throws SQLException {
try {
return new Block(this.reference);
} catch (NoDataFoundException e) {
return null;
}
}
/**
* Load child Block from DB
*
* @return Block, or null if not found
* @throws SQLException
*/
public Block getChild() throws SQLException {
byte[] blockSignature = this.getSignature();
if (blockSignature == null)
return null;
ResultSet resultSet = DB.checkedExecute("SELECT " + DB_COLUMNS + " FROM Blocks WHERE reference = ?", blockSignature);
try {
return new Block(resultSet);
} catch (NoDataFoundException e) {
return null;
}
}
// Converters
@SuppressWarnings("unchecked")
public JSONObject toJSON() throws SQLException {
JSONObject json = new JSONObject();
json.put("version", this.version);
json.put("timestamp", this.timestamp);
json.put("generatingBalance", this.generatingBalance);
json.put("generator", this.generator.getAddress());
json.put("generatorPublicKey", Base58.encode(this.generator.getPublicKey()));
json.put("fee", this.getTotalFees().toPlainString());
json.put("transactionsSignature", Base58.encode(this.transactionsSignature));
json.put("generatorSignature", Base58.encode(this.generatorSignature));
json.put("signature", Base58.encode(this.getSignature()));
if (this.reference != null)
json.put("reference", Base58.encode(this.reference));
json.put("height", this.getHeight());
// Add transaction info
JSONArray transactionsJson = new JSONArray();
boolean tradesHappened = false;
for (TransactionHandler transaction : this.getTransactions()) {
transactionsJson.add(transaction.toJSON());
// If this is an asset CreateOrderTransaction then check to see if any trades happened
if (transaction.getType() == Transaction.TransactionHandler.CREATE_ASSET_ORDER) {
CreateOrderTransaction orderTransaction = (CreateOrderTransaction) transaction;
Order order = orderTransaction.getOrder();
List<Trade> trades = order.getTrades();
// Filter out trades with timestamps that don't match order transaction's timestamp
trades.removeIf((Trade trade) -> trade.getTimestamp() != order.getTimestamp());
// Any trades left?
if (!trades.isEmpty()) {
tradesHappened = true;
// No need to check any further
break;
}
}
}
json.put("transactions", transactionsJson);
// Add asset trade activity flag
json.put("assetTrades", tradesHappened);
// Add CIYAM AT info (if any)
if (atBytes != null) {
json.put("blockATs", HashCode.fromBytes(atBytes).toString());
json.put("atFees", this.atFees);
}
return json;
}
public byte[] toBytes() throws SQLException {
try {
ByteArrayOutputStream bytes = new ByteArrayOutputStream(getDataLength());
bytes.write(Ints.toByteArray(this.version));
bytes.write(Longs.toByteArray(this.timestamp));
bytes.write(this.reference);
// NOTE: generatingBalance serialized as long value, not as BigDecimal, for historic compatibility
bytes.write(Longs.toByteArray(this.generatingBalance.longValue()));
bytes.write(this.generator.getPublicKey());
bytes.write(this.transactionsSignature);
bytes.write(this.generatorSignature);
if (this.version >= 2) {
if (this.atBytes != null) {
bytes.write(Ints.toByteArray(this.atBytes.length));
bytes.write(this.atBytes);
// NOTE: atFees serialized as long value, not as BigDecimal, for historic compatibility
bytes.write(Longs.toByteArray(this.atFees.longValue()));
} else {
bytes.write(Ints.toByteArray(0));
bytes.write(Longs.toByteArray(0L));
}
}
// Transactions
bytes.write(Ints.toByteArray(this.transactionCount));
for (TransactionHandler transaction : this.getTransactions()) {
bytes.write(Ints.toByteArray(transaction.getDataLength()));
bytes.write(transaction.toBytes());
}
return bytes.toByteArray();
} catch (IOException e) {
throw new RuntimeException(e);
}
}
public static Block parse(byte[] data) throws TransformationException {
if (data == null)
return null;
if (data.length < BASE_LENGTH)
throw new TransformationException("Byte data too short for Block");
ByteBuffer byteBuffer = ByteBuffer.wrap(data);
int version = byteBuffer.getInt();
if (version >= 2 && data.length < BASE_LENGTH + AT_LENGTH)
throw new TransformationException("Byte data too short for V2+ Block");
long timestamp = byteBuffer.getLong();
byte[] reference = new byte[REFERENCE_LENGTH];
byteBuffer.get(reference);
BigDecimal generatingBalance = BigDecimal.valueOf(byteBuffer.getLong()).setScale(8);
PublicKeyAccount generator = Serialization.deserializePublicKey(byteBuffer);
byte[] transactionsSignature = new byte[TRANSACTIONS_SIGNATURE_LENGTH];
byteBuffer.get(transactionsSignature);
byte[] generatorSignature = new byte[GENERATOR_SIGNATURE_LENGTH];
byteBuffer.get(generatorSignature);
byte[] atBytes = null;
BigDecimal atFees = null;
if (version >= 2) {
int atBytesLength = byteBuffer.getInt();
if (atBytesLength > MAX_BLOCK_BYTES)
throw new TransformationException("Byte data too long for Block's AT info");
atBytes = new byte[atBytesLength];
byteBuffer.get(atBytes);
atFees = BigDecimal.valueOf(byteBuffer.getLong()).setScale(8);
}
int transactionCount = byteBuffer.getInt();
// Parse transactions now, compared to deferred parsing in Gen1, so we can throw ParseException if need be
List<TransactionHandler> transactions = new ArrayList<TransactionHandler>();
for (int t = 0; t < transactionCount; ++t) {
if (byteBuffer.remaining() < TRANSACTION_SIZE_LENGTH)
throw new TransformationException("Byte data too short for Block Transaction length");
int transactionLength = byteBuffer.getInt();
if (byteBuffer.remaining() < transactionLength)
throw new TransformationException("Byte data too short for Block Transaction");
if (transactionLength > MAX_BLOCK_BYTES)
throw new TransformationException("Byte data too long for Block Transaction");
byte[] transactionBytes = new byte[transactionLength];
byteBuffer.get(transactionBytes);
TransactionHandler transaction = TransactionHandler.parse(transactionBytes);
transactions.add(transaction);
}
if (byteBuffer.hasRemaining())
throw new TransformationException("Excess byte data found after parsing Block");
return new Block(version, reference, timestamp, generatingBalance, generator, generatorSignature, transactionsSignature, atBytes, atFees, transactions);
}
// Processing
/**
@ -629,12 +235,12 @@ public class Block {
* <p>
* Requires block's {@code generator} being a {@code PrivateKeyAccount} so block's transactions signature can be recalculated.
*
* @param transaction
* @param transactionData
* @return true if transaction successfully added to block, false otherwise
* @throws IllegalStateException
* if block's {@code generator} is not a {@code PrivateKeyAccount}.
*/
public boolean addTransaction(TransactionHandler transaction) {
public boolean addTransaction(TransactionData transactionData) {
// Can't add to transactions if we haven't loaded existing ones yet
if (this.transactions == null)
throw new IllegalStateException("Attempted to add transaction to partially loaded database Block");
@ -643,17 +249,21 @@ public class Block {
throw new IllegalStateException("Block's generator has no private key");
// Check there is space in block
if (this.getDataLength() + transaction.getDataLength() > MAX_BLOCK_BYTES)
try {
if (BlockTransformer.getDataLength(this.blockData) + TransactionTransformer.getDataLength(transactionData) > MAX_BLOCK_BYTES)
return false;
} catch (TransformationException e) {
return false;
}
// Add to block
this.transactions.add(transaction);
this.transactions.add(Transaction.fromData(transactionData));
// Update transaction count
this.transactionCount++;
this.blockData.setTransactionCount(this.blockData.getTransactionCount() + 1);
// Update totalFees
this.totalFees.add(transaction.getFee());
this.blockData.setTotalFees(this.blockData.getTotalFees().add(transactionData.getFee()));
// Update transactions signature
calcTransactionsSignature();
@ -668,29 +278,17 @@ public class Block {
*
* @throws IllegalStateException
* if block's {@code generator} is not a {@code PrivateKeyAccount}.
* @throws RuntimeException
* if somehow the generator signature cannot be calculated
*/
public void calcGeneratorSignature() {
if (!(this.generator instanceof PrivateKeyAccount))
throw new IllegalStateException("Block's generator has no private key");
this.generatorSignature = ((PrivateKeyAccount) this.generator).sign(this.getBytesForGeneratorSignature());
}
private byte[] getBytesForGeneratorSignature() {
try {
ByteArrayOutputStream bytes = new ByteArrayOutputStream(GENERATOR_SIGNATURE_LENGTH + GENERATING_BALANCE_LENGTH + GENERATOR_LENGTH);
// Only copy the generator signature from reference, which is the first 64 bytes.
bytes.write(Arrays.copyOf(this.reference, GENERATOR_SIGNATURE_LENGTH));
bytes.write(Longs.toByteArray(this.generatingBalance.longValue()));
// We're padding here just in case the generator is the genesis account whose public key is only 8 bytes long.
bytes.write(Bytes.ensureCapacity(this.generator.getPublicKey(), GENERATOR_LENGTH, 0));
return bytes.toByteArray();
} catch (IOException e) {
throw new RuntimeException(e);
this.blockData.setGeneratorSignature(((PrivateKeyAccount) this.generator).sign(BlockTransformer.getBytesForGeneratorSignature(this.blockData)));
} catch (TransformationException e) {
throw new RuntimeException("Unable to calculate block's generator signature", e);
}
}
@ -701,41 +299,33 @@ public class Block {
*
* @throws IllegalStateException
* if block's {@code generator} is not a {@code PrivateKeyAccount}.
* @throws RuntimeException
* if somehow the transactions signature cannot be calculated
*/
public void calcTransactionsSignature() {
if (!(this.generator instanceof PrivateKeyAccount))
throw new IllegalStateException("Block's generator has no private key");
this.transactionsSignature = ((PrivateKeyAccount) this.generator).sign(this.getBytesForTransactionsSignature());
}
private byte[] getBytesForTransactionsSignature() {
ByteArrayOutputStream bytes = new ByteArrayOutputStream(GENERATOR_SIGNATURE_LENGTH + this.transactionCount * TransactionHandler.SIGNATURE_LENGTH);
try {
bytes.write(this.generatorSignature);
for (TransactionHandler transaction : this.getTransactions()) {
if (!transaction.isSignatureValid())
return null;
bytes.write(transaction.getSignature());
}
return bytes.toByteArray();
} catch (IOException | SQLException e) {
throw new RuntimeException(e);
this.blockData.setTransactionsSignature(((PrivateKeyAccount) this.generator).sign(BlockTransformer.getBytesForTransactionsSignature(this)));
} catch (TransformationException e) {
throw new RuntimeException("Unable to calculate block's transactions signature", e);
}
}
public boolean isSignatureValid() {
// Check generator's signature first
if (!this.generator.verify(this.generatorSignature, getBytesForGeneratorSignature()))
return false;
// Check transactions signature
if (!this.generator.verify(this.transactionsSignature, getBytesForTransactionsSignature()))
public boolean isSignatureValid() {
try {
// Check generator's signature first
if (!this.generator.verify(this.blockData.getGeneratorSignature(), BlockTransformer.getBytesForGeneratorSignature(this.blockData)))
return false;
// Check transactions signature
if (!this.generator.verify(this.blockData.getTransactionsSignature(), BlockTransformer.getBytesForTransactionsSignature(this)))
return false;
} catch (TransformationException e) {
return false;
}
return true;
}
@ -749,41 +339,44 @@ public class Block {
*
* @return true if block is valid, false otherwise.
* @throws SQLException
* @throws DataException
*/
public boolean isValid() throws SQLException {
public boolean isValid() throws SQLException, DataException {
// TODO
// Check parent blocks exists
if (this.reference == null)
if (this.blockData.getReference() == null)
return false;
Block parentBlock = this.getParent();
if (parentBlock == null)
BlockData parentBlockData = RepositoryManager.getBlockRepository().fromSignature(this.blockData.getReference());
if (parentBlockData == null)
return false;
Block parentBlock = new Block(parentBlockData);
// Check timestamp is valid, i.e. later than parent timestamp and not in the future, within ~500ms margin
if (this.timestamp < parentBlock.getTimestamp() || this.timestamp - BLOCK_TIMESTAMP_MARGIN > NTP.getTime())
if (this.blockData.getTimestamp() < parentBlockData.getTimestamp() || this.blockData.getTimestamp() - BLOCK_TIMESTAMP_MARGIN > NTP.getTime())
return false;
// Legacy gen1 test: check timestamp ms is the same as parent timestamp ms?
if (this.timestamp % 1000 != parentBlock.getTimestamp() % 1000)
if (this.blockData.getTimestamp() % 1000 != parentBlockData.getTimestamp() % 1000)
return false;
// Check block version
if (this.version != parentBlock.getNextBlockVersion())
if (this.blockData.getVersion() != parentBlock.getNextBlockVersion())
return false;
if (this.version < 2 && (this.atBytes != null || this.atBytes.length > 0 || this.atFees != null || this.atFees.compareTo(BigDecimal.ZERO) > 0))
if (this.blockData.getVersion() < 2 && (this.blockData.getAtBytes() != null || this.blockData.getAtFees() != null))
return false;
// Check generating balance
if (this.generatingBalance != parentBlock.getNextBlockGeneratingBalance())
if (this.blockData.getGeneratingBalance() != parentBlock.getNextBlockGeneratingBalance())
return false;
// Check generator's proof of stake against block's generating balance
// TODO
// Check CIYAM AT
if (this.atBytes != null && this.atBytes.length > 0) {
if (this.blockData.getAtBytes() != null && this.blockData.getAtBytes().length > 0) {
// TODO
// try {
// AT_Block atBlock = AT_Controller.validateATs(this.getBlockATs(), BlockChain.getHeight() + 1);
@ -796,18 +389,18 @@ public class Block {
// Check transactions
Savepoint savepoint = DB.createSavepoint("BLOCK_TRANSACTIONS");
try {
for (TransactionHandler transaction : this.getTransactions()) {
for (Transaction transaction : this.getTransactions()) {
// GenesisTransactions are not allowed (GenesisBlock overrides isValid() to allow them)
if (transaction instanceof GenesisTransaction)
return false;
// Check timestamp and deadline
if (transaction.getTimestamp() > this.timestamp || transaction.getDeadline() <= this.timestamp)
if (transaction.getTransactionData().getTimestamp() > this.blockData.getTimestamp() || transaction.getDeadline() <= this.blockData.getTimestamp())
return false;
// Check transaction is even valid
// NOTE: in Gen1 there was an extra block height passed to DeployATTransaction.isValid
if (transaction.isValid() != TransactionHandler.ValidationResult.OK)
if (transaction.isValid() != Transaction.ValidationResult.OK)
return false;
// Process transaction to make sure other transactions validate properly
@ -818,6 +411,8 @@ public class Block {
return false;
}
}
} catch (DataException e) {
return false;
} finally {
// Revert back to savepoint
try {
@ -834,32 +429,32 @@ public class Block {
return true;
}
public void process() throws SQLException {
public void process() throws DataException, SQLException {
// Process transactions (we'll link them to this block after saving the block itself)
List<TransactionHandler> transactions = this.getTransactions();
for (TransactionHandler transaction : transactions)
List<Transaction> transactions = this.getTransactions();
for (Transaction transaction : transactions)
transaction.process();
// If fees are non-zero then add fees to generator's balance
BigDecimal blockFee = this.getTotalFees();
BigDecimal blockFee = this.blockData.getTotalFees();
if (blockFee.compareTo(BigDecimal.ZERO) == 1)
this.generator.setConfirmedBalance(Asset.QORA, this.generator.getConfirmedBalance(Asset.QORA).add(blockFee));
// Link block into blockchain by fetching signature of highest block and setting that as our reference
int blockchainHeight = BlockChain.getHeight();
Block latestBlock = Block.fromHeight(blockchainHeight);
if (latestBlock != null)
this.reference = latestBlock.getSignature();
BlockData latestBlockData = RepositoryManager.getBlockRepository().fromHeight(blockchainHeight);
if (latestBlockData != null)
this.blockData.setReference(latestBlockData.getSignature());
this.height = blockchainHeight + 1;
this.save();
this.blockData.setHeight(blockchainHeight + 1);
RepositoryManager.getBlockRepository().save(this.blockData);
// Link transactions to this block, thus removing them from unconfirmed transactions list.
for (int sequence = 0; sequence < transactions.size(); ++sequence) {
TransactionHandler transaction = transactions.get(sequence);
Transaction transaction = transactions.get(sequence);
// Link transaction to this block
BlockTransaction blockTransaction = new BlockTransaction(this.getSignature(), sequence, transaction.getSignature());
BlockTransaction blockTransaction = new BlockTransaction(this.getSignature(), sequence, transaction.getTransactionData().getSignature());
blockTransaction.save();
}
}

View File

@ -87,14 +87,17 @@ public class BlockChain {
* Return highest block height from DB.
*
* @return height, or 0 if there are no blocks in DB (not very likely).
* @throws SQLException
*/
public static int getHeight() throws SQLException {
ResultSet rs = DB.checkedExecute("SELECT MAX(height) FROM Blocks");
if (rs == null)
return 0;
public static int getHeight() {
try {
ResultSet rs = DB.checkedExecute("SELECT MAX(height) FROM Blocks");
if (rs == null)
return 0;
return rs.getInt(1);
return rs.getInt(1);
} catch (SQLException e) {
return 0;
}
}
/**

View File

@ -5,9 +5,8 @@ import java.sql.SQLException;
import database.DB;
import database.NoDataFoundException;
import qora.transaction.TransactionHandler;
import qora.transaction.Transaction;
import repository.hsqldb.HSQLDBSaver;
import qora.transaction.TransactionHandler;
public class BlockTransaction {
@ -50,7 +49,7 @@ public class BlockTransaction {
this.blockSignature = blockSignature;
this.sequence = sequence;
this.transactionSignature = DB.getResultSetBytes(rs.getBinaryStream(1), TransactionHandler.SIGNATURE_LENGTH);
this.transactionSignature = DB.getResultSetBytes(rs.getBinaryStream(1));
}
protected BlockTransaction(byte[] transactionSignature) throws SQLException {
@ -58,7 +57,7 @@ public class BlockTransaction {
if (rs == null)
throw new NoDataFoundException();
this.blockSignature = DB.getResultSetBytes(rs.getBinaryStream(1), Block.BLOCK_SIGNATURE_LENGTH);
this.blockSignature = DB.getResultSetBytes(rs.getBinaryStream(1));
this.sequence = rs.getInt(2);
this.transactionSignature = transactionSignature;
}
@ -118,8 +117,10 @@ public class BlockTransaction {
* @return Transaction, or null if not found (which should never happen)
* @throws SQLException
*/
public TransactionHandler getTransaction() throws SQLException {
return TransactionFactory.fromSignature(this.transactionSignature);
public Transaction getTransaction() throws SQLException {
// XXX
// return TransactionFactory.fromSignature(this.transactionSignature);
return null;
}
}

View File

@ -1,151 +1,21 @@
package qora.transaction;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.math.BigDecimal;
import java.nio.ByteBuffer;
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 data.transaction.GenesisTransactionData;
import data.transaction.TransactionData;
import qora.account.PrivateKeyAccount;
import qora.assets.Asset;
import qora.crypto.Crypto;
import repository.hsqldb.HSQLDBSaver;
import transform.TransformationException;
import utils.Base58;
import utils.Serialization;
import transform.transaction.TransactionTransformer;
public class GenesisTransaction extends TransactionHandler {
public class GenesisTransaction extends Transaction {
// Properties
private Account recipient;
private BigDecimal amount;
// Property lengths
private static final int RECIPIENT_LENGTH = 25; // raw, not Base58-encoded
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.GENESIS, BigDecimal.ZERO, new GenesisAccount(), timestamp, null, 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
@Override
public int getDataLength() {
return TYPE_LENGTH + TYPELESS_LENGTH;
}
// Load/Save
/**
* Load GenesisTransaction from DB using signature.
*
* @param signature
* @throws NoDataFoundException
* if no matching row found
* @throws SQLException
*/
protected GenesisTransaction(byte[] signature) throws SQLException {
super(TransactionType.GENESIS, signature);
ResultSet rs = DB.checkedExecute("SELECT recipient, amount FROM GenesisTransactions WHERE signature = ?", signature);
if (rs == null)
throw new NoDataFoundException();
this.recipient = new Account(rs.getString(1));
this.amount = rs.getBigDecimal(2).setScale(8);
}
/**
* Load GenesisTransaction from DB using signature
*
* @param signature
* @return GenesisTransaction, or null if not found
* @throws SQLException
*/
public static GenesisTransaction fromSignature(byte[] signature) throws SQLException {
try {
return new GenesisTransaction(signature);
} catch (NoDataFoundException e) {
return null;
}
}
@Override
public void save() throws SQLException {
super.save();
HSQLDBSaver saveHelper = new HSQLDBSaver("GenesisTransactions");
saveHelper.bind("signature", this.signature).bind("recipient", this.recipient.getAddress()).bind("amount", this.amount);
saveHelper.execute();
}
// Converters
protected static TransactionHandler parse(ByteBuffer byteBuffer) throws TransformationException {
if (byteBuffer.remaining() < TYPELESS_LENGTH)
throw new TransformationException("Byte data too short for GenesisTransaction");
long timestamp = byteBuffer.getLong();
String recipient = Serialization.deserializeRecipient(byteBuffer);
BigDecimal amount = Serialization.deserializeBigDecimal(byteBuffer);
return new GenesisTransaction(recipient, amount, timestamp);
}
@SuppressWarnings("unchecked")
@Override
public JSONObject toJSON() throws SQLException {
JSONObject json = getBaseJSON();
json.put("recipient", this.recipient.getAddress());
json.put("amount", this.amount.toPlainString());
return json;
}
@Override
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);
}
public GenesisTransaction(TransactionData transactionData) {
this.transactionData = transactionData;
}
// Processing
@ -174,8 +44,12 @@ public class GenesisTransaction extends TransactionHandler {
* @return byte[]
*/
private byte[] calcSignature() {
byte[] digest = Crypto.digest(toBytes());
return Bytes.concat(digest, digest);
try {
byte[] digest = Crypto.digest(TransactionTransformer.toBytes(this.transactionData));
return Bytes.concat(digest, digest);
} catch (TransformationException e) {
return null;
}
}
/**
@ -189,42 +63,50 @@ public class GenesisTransaction extends TransactionHandler {
*/
@Override
public boolean isSignatureValid() {
return Arrays.equals(this.signature, calcSignature());
return Arrays.equals(this.transactionData.getSignature(), this.calcSignature());
}
@Override
public ValidationResult isValid() {
GenesisTransactionData genesisTransaction = (GenesisTransactionData) this.transactionData;
// Check amount is zero or positive
if (this.amount.compareTo(BigDecimal.ZERO) == -1)
if (genesisTransaction.getAmount().compareTo(BigDecimal.ZERO) == -1)
return ValidationResult.NEGATIVE_AMOUNT;
// Check recipient address is valid
if (!Crypto.isValidAddress(this.recipient.getAddress()))
if (!Crypto.isValidAddress(genesisTransaction.getRecipient()))
return ValidationResult.INVALID_ADDRESS;
return ValidationResult.OK;
}
@Override
public void process() throws SQLException {
this.save();
public void process() {
// TODO
// this.save();
// Set recipient's balance
this.recipient.setConfirmedBalance(Asset.QORA, this.amount);
// TODO
// this.recipient.setConfirmedBalance(Asset.QORA, this.amount);
// Set recipient's reference
recipient.setLastReference(this.signature);
// TODO
// recipient.setLastReference(this.signature);
}
@Override
public void orphan() throws SQLException {
this.delete();
public void orphan() {
// TODO
// this.delete();
// Reset recipient's balance
this.recipient.deleteBalance(Asset.QORA);
// TODO
// this.recipient.deleteBalance(Asset.QORA);
// Set recipient's reference
recipient.setLastReference(null);
// TODO
// recipient.setLastReference(null);
}
}

View File

@ -7,25 +7,36 @@ import java.util.Map;
import static java.util.Arrays.stream;
import static java.util.stream.Collectors.toMap;
import org.json.simple.JSONObject;
import data.transaction.Transaction;
import database.DB;
import database.NoDataFoundException;
import data.block.BlockData;
import data.transaction.TransactionData;
import qora.account.PrivateKeyAccount;
import qora.account.PublicKeyAccount;
import qora.block.Block;
import qora.block.BlockChain;
import qora.block.BlockTransaction;
import repository.Repository;
import repository.RepositoryManager;
import settings.Settings;
import transform.TransformationException;
import transform.Transformer;
import transform.transaction.TransactionTransformer;
import utils.Base58;
public abstract class TransactionHandler {
public abstract class Transaction {
// Transaction types
public enum TransactionType {
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;
private final static Map<Integer, TransactionType> map = stream(TransactionType.values()).collect(toMap(type -> type.value, type -> type));
TransactionType(int value) {
this.value = value;
}
public static TransactionType valueOf(int value) {
return map.get(value);
}
}
// Validation results
public enum ValidationResult {
@ -52,30 +63,40 @@ public abstract class TransactionHandler {
protected static final BigDecimal maxBytePerFee = BigDecimal.valueOf(Settings.getInstance().getMaxBytePerFee());
protected static final BigDecimal minFeePerByte = BigDecimal.ONE.divide(maxBytePerFee, MathContext.DECIMAL32);
private Transaction transaction;
protected TransactionData transactionData;
// Constructors
public TransactionHandler(Transaction transaction) {
this.transaction = transaction;
public static Transaction fromData(TransactionData transactionData) {
switch (transactionData.getType()) {
case GENESIS:
return new GenesisTransaction(transactionData);
default:
return null;
}
}
// Getters / Setters
public TransactionData getTransactionData() {
return this.transactionData;
}
// More information
public long getDeadline() {
// 24 hour deadline to include transaction in a block
return this.transaction.getTimestamp() + (24 * 60 * 60 * 1000);
return this.transactionData.getTimestamp() + (24 * 60 * 60 * 1000);
}
public boolean hasMinimumFee() {
return this.transaction.getFee().compareTo(MINIMUM_FEE) >= 0;
return this.transactionData.getFee().compareTo(MINIMUM_FEE) >= 0;
}
public BigDecimal feePerByte() {
try {
return this.transaction.getFee().divide(new BigDecimal(TransactionTransformer.getDataLength(this.transaction)), MathContext.DECIMAL32);
return this.transactionData.getFee().divide(new BigDecimal(TransactionTransformer.getDataLength(this.transactionData)), MathContext.DECIMAL32);
} catch (TransformationException e) {
throw new IllegalStateException("Unable to get transaction byte length?");
}
@ -87,7 +108,7 @@ public abstract class TransactionHandler {
public BigDecimal calcRecommendedFee() {
try {
BigDecimal recommendedFee = BigDecimal.valueOf(TransactionTransformer.getDataLength(this.transaction)).divide(maxBytePerFee, MathContext.DECIMAL32).setScale(8);
BigDecimal recommendedFee = BigDecimal.valueOf(TransactionTransformer.getDataLength(this.transactionData)).divide(maxBytePerFee, MathContext.DECIMAL32).setScale(8);
// security margin
recommendedFee = recommendedFee.add(new BigDecimal("0.0000001"));
@ -118,7 +139,7 @@ public abstract class TransactionHandler {
* @return height, or 0 if not in blockchain (i.e. unconfirmed)
*/
public int getHeight() {
return RepositoryManager.getTransactionRepository().getHeight(this.transaction);
return RepositoryManager.getTransactionRepository().getHeight(this.transactionData);
}
/**
@ -132,6 +153,9 @@ public abstract class TransactionHandler {
return 0;
int blockChainHeight = BlockChain.getHeight();
if (blockChainHeight == 0)
return 0;
return blockChainHeight - ourHeight + 1;
}
@ -142,8 +166,8 @@ public abstract class TransactionHandler {
*
* @return Block, or null if transaction is not in a Block
*/
public Block getBlock() {
return RepositoryManager.getTransactionRepository().toBlock(this.transaction);
public BlockData getBlock() {
return RepositoryManager.getTransactionRepository().toBlock(this.transactionData);
}
/**
@ -151,8 +175,8 @@ public abstract class TransactionHandler {
*
* @return Transaction, or null if no parent found (which should not happen)
*/
public Transaction getParent() {
byte[] reference = this.transaction.getReference();
public TransactionData getParent() {
byte[] reference = this.transactionData.getReference();
if (reference == null)
return null;
@ -164,8 +188,8 @@ public abstract class TransactionHandler {
*
* @return Transaction, or null if no child found
*/
public Transaction getChild() {
byte[] signature = this.transaction.getSignature();
public TransactionData getChild() {
byte[] signature = this.transactionData.getSignature();
if (signature == null)
return null;
@ -181,7 +205,7 @@ public abstract class TransactionHandler {
*/
private byte[] toBytesLessSignature() {
try {
byte[] bytes = TransactionTransformer.toBytes(this.transaction);
byte[] bytes = TransactionTransformer.toBytes(this.transactionData);
return Arrays.copyOf(bytes, bytes.length - Transformer.SIGNATURE_LENGTH);
} catch (TransformationException e) {
// XXX this isn't good
@ -196,7 +220,7 @@ public abstract class TransactionHandler {
}
public boolean isSignatureValid() {
byte[] signature = this.transaction.getSignature();
byte[] signature = this.transactionData.getSignature();
if (signature == null)
return false;

View File

@ -1,8 +1,20 @@
package repository;
import java.util.List;
import data.block.BlockData;
import data.transaction.TransactionData;
public interface BlockRepository {
BlockData fromSignature(byte[] signature) throws DataException;
BlockData fromHeight(int height) throws DataException;
public BlockData fromSignature(byte[] signature) throws DataException;
public BlockData fromReference(byte[] reference) throws DataException;
public BlockData fromHeight(int height) throws DataException;
public List<TransactionData> getTransactionsFromSignature(byte[] signature) throws DataException;
public void save(BlockData blockData) throws DataException;
}

View File

@ -1,20 +1,20 @@
package repository;
import data.transaction.Transaction;
import qora.block.Block;
import data.transaction.TransactionData;
import data.block.BlockData;
public interface TransactionRepository {
public Transaction fromSignature(byte[] signature);
public TransactionData fromSignature(byte[] signature);
public Transaction fromReference(byte[] reference);
public TransactionData fromReference(byte[] reference);
public int getHeight(Transaction transaction);
public int getHeight(TransactionData transaction);
public Block toBlock(Transaction transaction);
public BlockData toBlock(TransactionData transaction);
public void save(Transaction transaction);
public void save(TransactionData transaction) throws DataException;
public void delete(Transaction transaction);
public void delete(TransactionData transaction) throws DataException;
}

View File

@ -1,20 +1,21 @@
package repository.hsqldb;
import java.math.BigDecimal;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Timestamp;
import java.util.ArrayList;
import java.util.List;
import data.block.Block;
import data.block.BlockData;
import data.transaction.TransactionData;
import database.DB;
import qora.account.PublicKeyAccount;
import repository.BlockRepository;
import repository.DataException;
import repository.RepositoryManager;
import repository.TransactionRepository;
public class HSQLDBBlockRepository implements BlockRepository
{
public class HSQLDBBlockRepository implements BlockRepository {
protected static final int TRANSACTIONS_SIGNATURE_LENGTH = 64;
protected static final int GENERATOR_SIGNATURE_LENGTH = 64;
protected static final int REFERENCE_LENGTH = GENERATOR_SIGNATURE_LENGTH + TRANSACTIONS_SIGNATURE_LENGTH;
@ -22,43 +23,94 @@ public class HSQLDBBlockRepository implements BlockRepository
private static final String BLOCK_DB_COLUMNS = "version, reference, transaction_count, total_fees, "
+ "transactions_signature, height, generation, generating_balance, generator, generator_signature, AT_data, AT_fees";
public BlockData fromSignature(byte[] signature) throws DataException
{
ResultSet rs;
public BlockData fromSignature(byte[] signature) throws DataException {
try {
rs = DB.checkedExecute("SELECT " + BLOCK_DB_COLUMNS + " FROM Blocks WHERE signature = ?", signature);
ResultSet rs = DB.checkedExecute("SELECT " + BLOCK_DB_COLUMNS + " FROM Blocks WHERE signature = ?", signature);
return getBlockFromResultSet(rs);
} catch (SQLException e) {
throw new DataException("Error loading data from DB", e);
}
return getBlockFromResultSet(rs);
}
public BlockData fromHeight(int height) throws DataException
{
ResultSet rs;
public BlockData fromReference(byte[] reference) throws DataException {
try {
rs = DB.checkedExecute("SELECT " + BLOCK_DB_COLUMNS + " FROM Blocks WHERE height = ?", height);
ResultSet rs = DB.checkedExecute("SELECT " + BLOCK_DB_COLUMNS + " FROM Blocks WHERE reference = ?", reference);
return getBlockFromResultSet(rs);
} catch (SQLException e) {
throw new DataException("Error loading data from DB", e);
}
}
public BlockData fromHeight(int height) throws DataException {
try {
ResultSet rs = DB.checkedExecute("SELECT " + BLOCK_DB_COLUMNS + " FROM Blocks WHERE height = ?", height);
return getBlockFromResultSet(rs);
} catch (SQLException e) {
throw new DataException("Error loading data from DB", e);
}
return getBlockFromResultSet(rs);
}
private BlockData getBlockFromResultSet(ResultSet rs) throws DataException {
int version = rs.getInt(1);
byte[] reference = DB.getResultSetBytes(rs.getBinaryStream(2), REFERENCE_LENGTH);
int transactionCount = rs.getInt(3);
BigDecimal totalFees = rs.getBigDecimal(4);
byte[] transactionsSignature = DB.getResultSetBytes(rs.getBinaryStream(5), TRANSACTIONS_SIGNATURE_LENGTH);
int height = rs.getInt(6);
long timestamp = rs.getTimestamp(7).getTime();
BigDecimal generatingBalance = rs.getBigDecimal(8);
byte[] generatorPublicKey = DB.getResultSetBytes(rs.getBinaryStream(9));
byte[] generatorSignature = DB.getResultSetBytes(rs.getBinaryStream(10), GENERATOR_SIGNATURE_LENGTH);
byte[] atBytes = DB.getResultSetBytes(rs.getBinaryStream(11));
BigDecimal atFees = rs.getBigDecimal(12);
if (rs == null)
return null;
return new Block(version, reference, transactionCount, totalFees, transactionsSignature, height, timestamp,
generatingBalance,generatorPublicKey, generatorSignature, atBytes, atFees);
try {
int version = rs.getInt(1);
byte[] reference = DB.getResultSetBytes(rs.getBinaryStream(2));
int transactionCount = rs.getInt(3);
BigDecimal totalFees = rs.getBigDecimal(4);
byte[] transactionsSignature = DB.getResultSetBytes(rs.getBinaryStream(5));
int height = rs.getInt(6);
long timestamp = rs.getTimestamp(7).getTime();
BigDecimal generatingBalance = rs.getBigDecimal(8);
byte[] generatorPublicKey = DB.getResultSetBytes(rs.getBinaryStream(9));
byte[] generatorSignature = DB.getResultSetBytes(rs.getBinaryStream(10));
byte[] atBytes = DB.getResultSetBytes(rs.getBinaryStream(11));
BigDecimal atFees = rs.getBigDecimal(12);
return new BlockData(version, reference, transactionCount, totalFees, transactionsSignature, height, timestamp, generatingBalance, generatorPublicKey,
generatorSignature, atBytes, atFees);
} catch (SQLException e) {
throw new DataException(e);
}
}
public List<TransactionData> getTransactionsFromSignature(byte[] signature) throws DataException {
List<TransactionData> transactions = new ArrayList<TransactionData>();
try {
ResultSet rs = DB.checkedExecute("SELECT transaction_signature FROM BlockTransactions WHERE block_signature = ?", signature);
if (rs == null)
return transactions; // No transactions in this block
TransactionRepository transactionRepo = RepositoryManager.getTransactionRepository();
// NB: do-while loop because DB.checkedExecute() implicitly calls ResultSet.next() for us
do {
byte[] transactionSignature = DB.getResultSetBytes(rs.getBinaryStream(1));
transactions.add(transactionRepo.fromSignature(transactionSignature));
} while (rs.next());
} catch (SQLException e) {
throw new DataException(e);
}
return transactions;
}
public void save(BlockData blockData) throws DataException {
HSQLDBSaver saveHelper = new HSQLDBSaver("Blocks");
saveHelper.bind("signature", blockData.getSignature()).bind("version", blockData.getVersion()).bind("reference", blockData.getReference())
.bind("transaction_count", blockData.getTransactionCount()).bind("total_fees", blockData.getTotalFees()).bind("transactions_signature", blockData.getTransactionsSignature())
.bind("height", blockData.getHeight()).bind("generation", new Timestamp(blockData.getTimestamp())).bind("generating_balance", blockData.getGeneratingBalance())
.bind("generator", blockData.getGeneratorPublicKey()).bind("generator_signature", blockData.getGeneratorSignature()).bind("AT_data", blockData.getAtBytes())
.bind("AT_fees", blockData.getAtFees());
try {
saveHelper.execute();
} catch (SQLException e) {
throw new DataException("Unable to save Block into repository", e);
}
}
}

View File

@ -4,27 +4,41 @@ import java.math.BigDecimal;
import java.sql.ResultSet;
import java.sql.SQLException;
import data.account.Account;
import data.account.PublicKeyAccount;
import data.transaction.GenesisTransaction;
import data.transaction.Transaction;
import data.transaction.GenesisTransactionData;
import data.transaction.TransactionData;
import database.DB;
import repository.DataException;
public class HSQLDBGenesisTransactionRepository extends HSQLDBTransactionRepository {
Transaction fromBase(byte[] signature, byte[] reference, PublicKeyAccount creator, long timestamp, BigDecimal fee) {
TransactionData fromBase(byte[] signature, byte[] reference, byte[] creator, long timestamp, BigDecimal fee) {
try {
ResultSet rs = DB.checkedExecute("SELECT recipient, amount FROM GenesisTransactions WHERE signature = ?", signature);
if (rs == null)
return null;
Account recipient = new Account(rs.getString(1));
String recipient = rs.getString(1);
BigDecimal amount = rs.getBigDecimal(2).setScale(8);
return new GenesisTransaction(recipient, amount, timestamp, signature);
return new GenesisTransactionData(recipient, amount, timestamp, signature);
} catch (SQLException e) {
return null;
}
}
@Override
public void save(TransactionData transaction) throws DataException {
super.save(transaction);
GenesisTransactionData genesisTransaction = (GenesisTransactionData) transaction;
HSQLDBSaver saveHelper = new HSQLDBSaver("GenesisTransactions");
saveHelper.bind("signature", genesisTransaction.getSignature()).bind("recipient", genesisTransaction.getRecipient()).bind("amount", genesisTransaction.getAmount());
try {
saveHelper.execute();
} catch (SQLException e) {
throw new DataException(e);
}
}
}

View File

@ -5,11 +5,12 @@ import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Timestamp;
import data.account.PublicKeyAccount;
import data.transaction.Transaction;
import data.transaction.Transaction.TransactionType;
import data.block.BlockData;
import data.transaction.TransactionData;
import qora.transaction.Transaction.TransactionType;
import database.DB;
import qora.block.Block;
import repository.DataException;
import repository.RepositoryManager;
import repository.TransactionRepository;
public class HSQLDBTransactionRepository implements TransactionRepository {
@ -20,7 +21,7 @@ public class HSQLDBTransactionRepository implements TransactionRepository {
genesisTransactionRepository = new HSQLDBGenesisTransactionRepository();
}
public Transaction fromSignature(byte[] signature) {
public TransactionData fromSignature(byte[] signature) {
try {
ResultSet rs = DB.checkedExecute("SELECT type, reference, creator, creation, fee FROM Transactions WHERE signature = ?", signature);
if (rs == null)
@ -28,7 +29,7 @@ public class HSQLDBTransactionRepository implements TransactionRepository {
TransactionType type = TransactionType.valueOf(rs.getInt(1));
byte[] reference = DB.getResultSetBytes(rs.getBinaryStream(2));
PublicKeyAccount creator = new PublicKeyAccount(DB.getResultSetBytes(rs.getBinaryStream(3)));
byte[] creator = DB.getResultSetBytes(rs.getBinaryStream(3));
long timestamp = rs.getTimestamp(4).getTime();
BigDecimal fee = rs.getBigDecimal(5).setScale(8);
@ -38,7 +39,7 @@ public class HSQLDBTransactionRepository implements TransactionRepository {
}
}
public Transaction fromReference(byte[] reference) {
public TransactionData fromReference(byte[] reference) {
try {
ResultSet rs = DB.checkedExecute("SELECT type, signature, creator, creation, fee FROM Transactions WHERE reference = ?", reference);
if (rs == null)
@ -46,7 +47,7 @@ public class HSQLDBTransactionRepository implements TransactionRepository {
TransactionType type = TransactionType.valueOf(rs.getInt(1));
byte[] signature = DB.getResultSetBytes(rs.getBinaryStream(2));
PublicKeyAccount creator = new PublicKeyAccount(DB.getResultSetBytes(rs.getBinaryStream(3)));
byte[] creator = DB.getResultSetBytes(rs.getBinaryStream(3));
long timestamp = rs.getTimestamp(4).getTime();
BigDecimal fee = rs.getBigDecimal(5).setScale(8);
@ -56,7 +57,7 @@ public class HSQLDBTransactionRepository implements TransactionRepository {
}
}
private Transaction fromBase(TransactionType type, byte[] signature, byte[] reference, PublicKeyAccount creator, long timestamp, BigDecimal fee) {
private TransactionData fromBase(TransactionType type, byte[] signature, byte[] reference, byte[] creator, long timestamp, BigDecimal fee) {
switch (type) {
case GENESIS:
return this.genesisTransactionRepository.fromBase(signature, reference, creator, timestamp, fee);
@ -67,26 +68,28 @@ public class HSQLDBTransactionRepository implements TransactionRepository {
}
@Override
public int getHeight(Transaction transaction) {
byte[] signature = transaction.getSignature();
public int getHeight(TransactionData transactionData) {
byte[] signature = transactionData.getSignature();
if (signature == null)
return 0;
// in one go?
try {
ResultSet rs = DB.checkedExecute("SELECT height from BlockTransactions JOIN Blocks ON Blocks.signature = BlockTransactions.block_signature WHERE transaction_signature = ? LIMIT 1", signature);
ResultSet rs = DB.checkedExecute(
"SELECT height from BlockTransactions JOIN Blocks ON Blocks.signature = BlockTransactions.block_signature WHERE transaction_signature = ? LIMIT 1",
signature);
if (rs == null)
return 0;
return rs.getInt(1);
} catch (SQLException e) {
return 0;
}
}
@Override
public Block toBlock(Transaction transaction) {
byte[] signature = transaction.getSignature();
public BlockData toBlock(TransactionData transactionData) {
byte[] signature = transactionData.getSignature();
if (signature == null)
return null;
@ -95,37 +98,34 @@ public class HSQLDBTransactionRepository implements TransactionRepository {
ResultSet rs = DB.checkedExecute("SELECT block_signature from BlockTransactions WHERE transaction_signature = ? LIMIT 1", signature);
if (rs == null)
return null;
byte[] blockSignature = DB.getResultSetBytes(rs.getBinaryStream(1));
// TODO
// return RepositoryManager.getBlockRepository().fromSignature(blockSignature);
return null;
} catch (SQLException e) {
return RepositoryManager.getBlockRepository().fromSignature(blockSignature);
} catch (SQLException | DataException e) {
return null;
}
}
@Override
public void save(Transaction transaction) {
public void save(TransactionData transactionData) throws DataException {
HSQLDBSaver saver = new HSQLDBSaver("Transactions");
saver.bind("signature", transaction.getSignature()).bind("reference", transaction.getReference()).bind("type", transaction.getType().value)
.bind("creator", transaction.getCreator().getPublicKey()).bind("creation", new Timestamp(transaction.getTimestamp())).bind("fee", transaction.getFee())
saver.bind("signature", transactionData.getSignature()).bind("reference", transactionData.getReference()).bind("type", transactionData.getType().value)
.bind("creator", transactionData.getCreatorPublicKey()).bind("creation", new Timestamp(transactionData.getTimestamp())).bind("fee", transactionData.getFee())
.bind("milestone_block", null);
try {
saver.execute();
} catch (SQLException e) {
// XXX do what?
throw new DataException(e);
}
}
@Override
public void delete(Transaction transaction) {
public void delete(TransactionData transactionData) {
// NOTE: The corresponding row in sub-table is deleted automatically by the database thanks to "ON DELETE CASCADE" in the sub-table's FOREIGN KEY
// definition.
try {
DB.checkedExecute("DELETE FROM Transactions WHERE signature = ?", transaction.getSignature());
DB.checkedExecute("DELETE FROM Transactions WHERE signature = ?", transactionData.getSignature());
} catch (SQLException e) {
// XXX do what?
}

View File

@ -8,6 +8,7 @@ public abstract class Transformer {
// Raw, not Base58-encoded
public static final int ADDRESS_LENGTH = 25;
public static final int PUBLIC_KEY_LENGTH = 32;
public static final int SIGNATURE_LENGTH = 64;
public static final int TIMESTAMP_LENGTH = LONG_LENGTH;

View File

@ -0,0 +1,284 @@
package transform.block;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.math.BigDecimal;
import java.nio.ByteBuffer;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import org.json.simple.JSONArray;
import org.json.simple.JSONObject;
import com.google.common.hash.HashCode;
import com.google.common.primitives.Bytes;
import com.google.common.primitives.Ints;
import com.google.common.primitives.Longs;
import data.block.BlockData;
import data.transaction.TransactionData;
import qora.account.PublicKeyAccount;
import qora.block.Block;
import qora.transaction.Transaction;
import repository.DataException;
import transform.TransformationException;
import transform.Transformer;
import transform.transaction.TransactionTransformer;
import utils.Base58;
import utils.Serialization;
public class BlockTransformer extends Transformer {
private static final int VERSION_LENGTH = INT_LENGTH;
private static final int TRANSACTIONS_SIGNATURE_LENGTH = SIGNATURE_LENGTH;
private static final int GENERATOR_SIGNATURE_LENGTH = SIGNATURE_LENGTH;
private static final int BLOCK_REFERENCE_LENGTH = GENERATOR_SIGNATURE_LENGTH + TRANSACTIONS_SIGNATURE_LENGTH;
private static final int TIMESTAMP_LENGTH = LONG_LENGTH;
private static final int GENERATING_BALANCE_LENGTH = LONG_LENGTH;
private static final int GENERATOR_LENGTH = PUBLIC_KEY_LENGTH;
private static final int TRANSACTION_COUNT_LENGTH = INT_LENGTH;
private static final int BASE_LENGTH = VERSION_LENGTH + BLOCK_REFERENCE_LENGTH + TIMESTAMP_LENGTH + GENERATING_BALANCE_LENGTH + GENERATOR_LENGTH
+ TRANSACTIONS_SIGNATURE_LENGTH + GENERATOR_SIGNATURE_LENGTH + TRANSACTION_COUNT_LENGTH;
protected static final int BLOCK_SIGNATURE_LENGTH = GENERATOR_SIGNATURE_LENGTH + TRANSACTIONS_SIGNATURE_LENGTH;
protected static final int TRANSACTION_SIZE_LENGTH = INT_LENGTH; // per transaction
protected static final int AT_BYTES_LENGTH = INT_LENGTH;
protected static final int AT_FEES_LENGTH = LONG_LENGTH;
protected static final int AT_LENGTH = AT_FEES_LENGTH + AT_BYTES_LENGTH;
public static BlockData fromBytes(byte[] bytes) throws TransformationException {
if (bytes == null)
return null;
if (bytes.length < BASE_LENGTH)
throw new TransformationException("Byte data too short for Block");
ByteBuffer byteBuffer = ByteBuffer.wrap(bytes);
int version = byteBuffer.getInt();
if (version >= 2 && bytes.length < BASE_LENGTH + AT_LENGTH)
throw new TransformationException("Byte data too short for V2+ Block");
long timestamp = byteBuffer.getLong();
byte[] reference = new byte[BLOCK_REFERENCE_LENGTH];
byteBuffer.get(reference);
BigDecimal generatingBalance = BigDecimal.valueOf(byteBuffer.getLong()).setScale(8);
byte[] generatorPublicKey = Serialization.deserializePublicKey(byteBuffer);
byte[] transactionsSignature = new byte[TRANSACTIONS_SIGNATURE_LENGTH];
byteBuffer.get(transactionsSignature);
byte[] generatorSignature = new byte[GENERATOR_SIGNATURE_LENGTH];
byteBuffer.get(generatorSignature);
byte[] atBytes = null;
BigDecimal atFees = null;
if (version >= 2) {
int atBytesLength = byteBuffer.getInt();
if (atBytesLength > Block.MAX_BLOCK_BYTES)
throw new TransformationException("Byte data too long for Block's AT info");
atBytes = new byte[atBytesLength];
byteBuffer.get(atBytes);
atFees = BigDecimal.valueOf(byteBuffer.getLong()).setScale(8);
}
int transactionCount = byteBuffer.getInt();
// Parse transactions now, compared to deferred parsing in Gen1, so we can throw ParseException if need be
List<TransactionData> transactions = new ArrayList<TransactionData>();
for (int t = 0; t < transactionCount; ++t) {
if (byteBuffer.remaining() < TRANSACTION_SIZE_LENGTH)
throw new TransformationException("Byte data too short for Block Transaction length");
int transactionLength = byteBuffer.getInt();
if (byteBuffer.remaining() < transactionLength)
throw new TransformationException("Byte data too short for Block Transaction");
if (transactionLength > Block.MAX_BLOCK_BYTES)
throw new TransformationException("Byte data too long for Block Transaction");
byte[] transactionBytes = new byte[transactionLength];
byteBuffer.get(transactionBytes);
TransactionData transaction = TransactionTransformer.fromBytes(transactionBytes);
transactions.add(transaction);
}
if (byteBuffer.hasRemaining())
throw new TransformationException("Excess byte data found after parsing Block");
// XXX Can't return a simple BlockData object because it doesn't support holding the transactions
// return new BlockData(version, reference, timestamp, generatingBalance, generatorPublicKey, generatorSignature, transactionsSignature, atBytes, atFees, transactions);
return null;
}
public static int getDataLength(BlockData blockData) throws TransformationException {
// TODO
int blockLength = BASE_LENGTH;
if (blockData.getVersion() >= 2 && blockData.getAtBytes() != null)
blockLength += AT_FEES_LENGTH + AT_BYTES_LENGTH + blockData.getAtBytes().length;
/*
* XXX Where do the transactions come from? A param? Do we pass a Block instead of BlockData?
// Short cut for no transactions
if (block.getTransactions() == null || block.getTransactions().isEmpty())
return blockLength;
for (TransactionData transaction : this.transactions)
blockLength += TRANSACTION_SIZE_LENGTH + transaction.getDataLength();
*/
return blockLength;
}
public static byte[] toBytes(BlockData blockData) throws TransformationException {
try {
ByteArrayOutputStream bytes = new ByteArrayOutputStream(getDataLength(blockData));
bytes.write(Ints.toByteArray(blockData.getVersion()));
bytes.write(Longs.toByteArray(blockData.getTimestamp()));
bytes.write(blockData.getReference());
// NOTE: generatingBalance serialized as long value, not as BigDecimal, for historic compatibility
bytes.write(Longs.toByteArray(blockData.getGeneratingBalance().longValue()));
bytes.write(blockData.getGeneratorPublicKey());
bytes.write(blockData.getTransactionsSignature());
bytes.write(blockData.getGeneratorSignature());
if (blockData.getVersion() >= 2) {
byte[] atBytes = blockData.getAtBytes();
if (atBytes != null) {
bytes.write(Ints.toByteArray(atBytes.length));
bytes.write(atBytes);
// NOTE: atFees serialized as long value, not as BigDecimal, for historic compatibility
bytes.write(Longs.toByteArray(blockData.getAtFees().longValue()));
} else {
bytes.write(Ints.toByteArray(0));
bytes.write(Longs.toByteArray(0L));
}
}
// Transactions
bytes.write(Ints.toByteArray(blockData.getTransactionCount()));
/*
* XXX Where do the transactions come from? A param? Do we pass a Block instead of BlockData?
for (TransactionData transaction : blockData.getTransactions()) {
bytes.write(Ints.toByteArray(transaction.getDataLength()));
bytes.write(transaction.toBytes());
}
*/
return bytes.toByteArray();
} catch (IOException e) {
throw new RuntimeException(e);
}
}
@SuppressWarnings("unchecked")
public static JSONObject toJSON(BlockData blockData) throws TransformationException {
JSONObject json = new JSONObject();
json.put("version", blockData.getVersion());
json.put("timestamp", blockData.getTimestamp());
json.put("generatingBalance", blockData.getGeneratingBalance());
json.put("generator", new PublicKeyAccount(blockData.getGeneratorPublicKey()).getAddress());
json.put("generatorPublicKey", Base58.encode(blockData.getGeneratorPublicKey()));
json.put("fee", blockData.getTotalFees().toPlainString());
json.put("transactionsSignature", Base58.encode(blockData.getTransactionsSignature()));
json.put("generatorSignature", Base58.encode(blockData.getGeneratorSignature()));
json.put("signature", Base58.encode(blockData.getSignature()));
if (blockData.getReference() != null)
json.put("reference", Base58.encode(blockData.getReference()));
json.put("height", blockData.getHeight());
// Add transaction info
JSONArray transactionsJson = new JSONArray();
boolean tradesHappened = false;
/*
* XXX Where do the transactions come from? A param? Do we pass a Block instead of BlockData?
for (TransactionData transaction : blockData.getTransactions()) {
transactionsJson.add(transaction.toJSON());
// If this is an asset CreateOrderTransaction then check to see if any trades happened
if (transaction.getType() == Transaction.TransactionType.CREATE_ASSET_ORDER) {
CreateOrderTransaction orderTransaction = (CreateOrderTransaction) transaction;
Order order = orderTransaction.getOrder();
List<Trade> trades = order.getTrades();
// Filter out trades with timestamps that don't match order transaction's timestamp
trades.removeIf((Trade trade) -> trade.getTimestamp() != order.getTimestamp());
// Any trades left?
if (!trades.isEmpty()) {
tradesHappened = true;
// No need to check any further
break;
}
}
}
json.put("transactions", transactionsJson);
*/
// Add asset trade activity flag
json.put("assetTrades", tradesHappened);
// Add CIYAM AT info (if any)
if (blockData.getAtBytes() != null) {
json.put("blockATs", HashCode.fromBytes(blockData.getAtBytes()).toString());
json.put("atFees", blockData.getAtFees());
}
return json;
}
public static byte[] getBytesForGeneratorSignature(BlockData blockData) throws TransformationException {
try {
ByteArrayOutputStream bytes = new ByteArrayOutputStream(GENERATOR_SIGNATURE_LENGTH + GENERATING_BALANCE_LENGTH + GENERATOR_LENGTH);
// Only copy the generator signature from reference, which is the first 64 bytes.
bytes.write(Arrays.copyOf(blockData.getReference(), GENERATOR_SIGNATURE_LENGTH));
bytes.write(Longs.toByteArray(blockData.getGeneratingBalance().longValue()));
// We're padding here just in case the generator is the genesis account whose public key is only 8 bytes long.
bytes.write(Bytes.ensureCapacity(blockData.getGeneratorPublicKey(), GENERATOR_LENGTH, 0));
return bytes.toByteArray();
} catch (IOException e) {
throw new TransformationException(e);
}
}
public static byte[] getBytesForTransactionsSignature(Block block) throws TransformationException {
ByteArrayOutputStream bytes = new ByteArrayOutputStream(GENERATOR_SIGNATURE_LENGTH + block.getBlockData().getTransactionCount() * TransactionTransformer.SIGNATURE_LENGTH);
try {
bytes.write(block.getBlockData().getGeneratorSignature());
for (Transaction transaction : block.getTransactions()) {
if (!transaction.isSignatureValid())
return null;
bytes.write(transaction.getTransactionData().getSignature());
}
return bytes.toByteArray();
} catch (IOException | DataException e) {
throw new TransformationException(e);
}
}
}

View File

@ -10,9 +10,8 @@ import org.json.simple.JSONObject;
import com.google.common.primitives.Ints;
import com.google.common.primitives.Longs;
import data.transaction.Transaction;
import data.account.Account;
import data.transaction.GenesisTransaction;
import data.transaction.TransactionData;
import data.transaction.GenesisTransactionData;
import transform.TransformationException;
import utils.Base58;
import utils.Serialization;
@ -25,30 +24,30 @@ public class GenesisTransactionTransformer extends TransactionTransformer {
// Note that Genesis transactions don't require reference, fee or signature:
private static final int TYPELESS_LENGTH = TIMESTAMP_LENGTH + RECIPIENT_LENGTH + AMOUNT_LENGTH;
static Transaction fromByteBuffer(ByteBuffer byteBuffer) throws TransformationException {
static TransactionData fromByteBuffer(ByteBuffer byteBuffer) throws TransformationException {
if (byteBuffer.remaining() < TYPELESS_LENGTH)
throw new TransformationException("Byte data too short for GenesisTransaction");
long timestamp = byteBuffer.getLong();
Account recipient = new Account(Serialization.deserializeRecipient(byteBuffer));
String recipient = Serialization.deserializeRecipient(byteBuffer);
BigDecimal amount = Serialization.deserializeBigDecimal(byteBuffer);
return new GenesisTransaction(recipient, amount, timestamp);
return new GenesisTransactionData(recipient, amount, timestamp);
}
public static int getDataLength(Transaction baseTransaction) throws TransformationException {
public static int getDataLength(TransactionData baseTransaction) throws TransformationException {
return TYPE_LENGTH + TYPELESS_LENGTH;
}
public static byte[] toBytes(Transaction baseTransaction) throws TransformationException {
public static byte[] toBytes(TransactionData baseTransaction) throws TransformationException {
try {
GenesisTransaction transaction = (GenesisTransaction) baseTransaction;
GenesisTransactionData transaction = (GenesisTransactionData) baseTransaction;
ByteArrayOutputStream bytes = new ByteArrayOutputStream();
bytes.write(Ints.toByteArray(transaction.getType().value));
bytes.write(Longs.toByteArray(transaction.getTimestamp()));
bytes.write(Base58.decode(transaction.getRecipient().getAddress()));
bytes.write(Base58.decode(transaction.getRecipient()));
bytes.write(Serialization.serializeBigDecimal(transaction.getAmount()));
return bytes.toByteArray();
@ -58,13 +57,13 @@ public class GenesisTransactionTransformer extends TransactionTransformer {
}
@SuppressWarnings("unchecked")
public static JSONObject toJSON(Transaction baseTransaction) throws TransformationException {
public static JSONObject toJSON(TransactionData baseTransaction) throws TransformationException {
JSONObject json = TransactionTransformer.getBaseJSON(baseTransaction);
try {
GenesisTransaction transaction = (GenesisTransaction) baseTransaction;
GenesisTransactionData transaction = (GenesisTransactionData) baseTransaction;
json.put("recipient", transaction.getRecipient().getAddress());
json.put("recipient", transaction.getRecipient());
json.put("amount", transaction.getAmount().toPlainString());
} catch (ClassCastException e) {
throw new TransformationException(e);

View File

@ -4,8 +4,8 @@ import java.nio.ByteBuffer;
import org.json.simple.JSONObject;
import data.transaction.Transaction;
import data.transaction.Transaction.TransactionType;
import data.transaction.TransactionData;
import qora.transaction.Transaction.TransactionType;
import transform.TransformationException;
import transform.Transformer;
import utils.Base58;
@ -14,7 +14,7 @@ public class TransactionTransformer extends Transformer {
protected static final int TYPE_LENGTH = INT_LENGTH;
public static Transaction fromBytes(byte[] bytes) throws TransformationException {
public static TransactionData fromBytes(byte[] bytes) throws TransformationException {
if (bytes == null)
return null;
@ -36,7 +36,7 @@ public class TransactionTransformer extends Transformer {
}
}
public static int getDataLength(Transaction transaction) throws TransformationException {
public static int getDataLength(TransactionData transaction) throws TransformationException {
switch (transaction.getType()) {
case GENESIS:
return GenesisTransactionTransformer.getDataLength(transaction);
@ -46,7 +46,7 @@ public class TransactionTransformer extends Transformer {
}
}
public static byte[] toBytes(Transaction transaction) throws TransformationException {
public static byte[] toBytes(TransactionData transaction) throws TransformationException {
switch (transaction.getType()) {
case GENESIS:
return GenesisTransactionTransformer.toBytes(transaction);
@ -56,7 +56,7 @@ public class TransactionTransformer extends Transformer {
}
}
public static JSONObject toJSON(Transaction transaction) throws TransformationException {
public static JSONObject toJSON(TransactionData transaction) throws TransformationException {
switch (transaction.getType()) {
case GENESIS:
return GenesisTransactionTransformer.toJSON(transaction);
@ -67,7 +67,7 @@ public class TransactionTransformer extends Transformer {
}
@SuppressWarnings("unchecked")
static JSONObject getBaseJSON(Transaction transaction) {
static JSONObject getBaseJSON(TransactionData transaction) {
JSONObject json = new JSONObject();
json.put("type", transaction.getType().value);

View File

@ -6,8 +6,8 @@ import java.math.BigInteger;
import java.nio.ByteBuffer;
import qora.account.PublicKeyAccount;
import qora.transaction.TransactionHandler;
import transform.TransformationException;
import transform.Transformer;
public class Serialization {
@ -31,15 +31,15 @@ public class Serialization {
}
public static String deserializeRecipient(ByteBuffer byteBuffer) {
byte[] bytes = new byte[TransactionHandler.RECIPIENT_LENGTH];
byte[] bytes = new byte[Transformer.ADDRESS_LENGTH];
byteBuffer.get(bytes);
return Base58.encode(bytes);
}
public static PublicKeyAccount deserializePublicKey(ByteBuffer byteBuffer) {
byte[] bytes = new byte[TransactionHandler.CREATOR_LENGTH];
public static byte[] deserializePublicKey(ByteBuffer byteBuffer) {
byte[] bytes = new byte[Transformer.PUBLIC_KEY_LENGTH];
byteBuffer.get(bytes);
return new PublicKeyAccount(bytes);
return bytes;
}
public static String deserializeSizedString(ByteBuffer byteBuffer, int maxSize) throws TransformationException {