mirror of
https://github.com/Qortal/qortal.git
synced 2025-04-16 16:15:53 +00:00
Merge branch 'DAO' into DAO
This commit is contained in:
commit
6853f0edcb
@ -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
|
@ -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 });
|
||||
}
|
||||
|
||||
}
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
@ -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;
|
||||
}
|
||||
}
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
37
src/data/transaction/GenesisTransactionData.java
Normal file
37
src/data/transaction/GenesisTransactionData.java
Normal 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;
|
||||
}
|
||||
|
||||
}
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
58
src/data/transaction/TransactionData.java
Normal file
58
src/data/transaction/TransactionData.java
Normal 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;
|
||||
}
|
||||
|
||||
}
|
@ -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() {
|
||||
|
@ -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 {
|
||||
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -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;
|
||||
|
@ -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;
|
||||
|
||||
}
|
||||
|
@ -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;
|
||||
|
||||
}
|
||||
|
@ -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;
|
||||
@ -27,30 +28,38 @@ public class HSQLDBBlockRepository implements BlockRepository
|
||||
public HSQLDBBlockRepository(HSQLDBRepository repository) {
|
||||
this.repository = repository;
|
||||
}
|
||||
|
||||
public BlockData fromSignature(byte[] signature) throws DataException
|
||||
{
|
||||
ResultSet rs;
|
||||
|
||||
public BlockData fromSignature(byte[] signature) throws DataException {
|
||||
try {
|
||||
rs = DB.checkedExecute(repository.connection, "SELECT " + BLOCK_DB_COLUMNS + " FROM Blocks WHERE signature = ?", signature);
|
||||
ResultSet rs = DB.checkedExecute(repository.connection, "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(repository.connection, "SELECT " + BLOCK_DB_COLUMNS + " FROM Blocks WHERE height = ?", height);
|
||||
ResultSet rs = DB.checkedExecute(repository.connection, "SELECT " + BLOCK_DB_COLUMNS + " FROM Blocks WHERE height = ?", height);
|
||||
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(repository.connection, "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 {
|
||||
if (rs == null)
|
||||
return null;
|
||||
|
||||
try {
|
||||
int version = rs.getInt(1);
|
||||
byte[] reference = DB.getResultSetBytes(rs.getBinaryStream(2));
|
||||
@ -65,12 +74,49 @@ public class HSQLDBBlockRepository implements BlockRepository
|
||||
byte[] atBytes = DB.getResultSetBytes(rs.getBinaryStream(11));
|
||||
BigDecimal atFees = rs.getBigDecimal(12);
|
||||
|
||||
return new Block(version, reference, transactionCount, totalFees, transactionsSignature, height, timestamp,
|
||||
generatingBalance,generatorPublicKey, generatorSignature, atBytes, atFees);
|
||||
}
|
||||
catch(SQLException e)
|
||||
{
|
||||
return new BlockData(version, reference, transactionCount, totalFees, transactionsSignature, height, timestamp, generatingBalance, generatorPublicKey,
|
||||
generatorSignature, atBytes, atFees);
|
||||
} catch(SQLException e) {
|
||||
throw new DataException("Error extracting data from result set", 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);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -4,11 +4,10 @@ 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 {
|
||||
|
||||
@ -16,19 +15,34 @@ public class HSQLDBGenesisTransactionRepository extends HSQLDBTransactionReposit
|
||||
super(repository);
|
||||
}
|
||||
|
||||
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(repository.connection, "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);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -6,11 +6,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 {
|
||||
@ -23,7 +24,7 @@ public class HSQLDBTransactionRepository implements TransactionRepository {
|
||||
genesisTransactionRepository = new HSQLDBGenesisTransactionRepository(repository);
|
||||
}
|
||||
|
||||
public Transaction fromSignature(byte[] signature) {
|
||||
public TransactionData fromSignature(byte[] signature) {
|
||||
try {
|
||||
ResultSet rs = DB.checkedExecute(repository.connection, "SELECT type, reference, creator, creation, fee FROM Transactions WHERE signature = ?", signature);
|
||||
if (rs == null)
|
||||
@ -31,7 +32,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);
|
||||
|
||||
@ -41,7 +42,7 @@ public class HSQLDBTransactionRepository implements TransactionRepository {
|
||||
}
|
||||
}
|
||||
|
||||
public Transaction fromReference(byte[] reference) {
|
||||
public TransactionData fromReference(byte[] reference) {
|
||||
try {
|
||||
ResultSet rs = DB.checkedExecute(repository.connection, "SELECT type, signature, creator, creation, fee FROM Transactions WHERE reference = ?", reference);
|
||||
if (rs == null)
|
||||
@ -49,7 +50,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);
|
||||
|
||||
@ -59,7 +60,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);
|
||||
@ -70,26 +71,27 @@ 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(repository.connection, "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;
|
||||
|
||||
@ -98,33 +100,30 @@ public class HSQLDBTransactionRepository implements TransactionRepository {
|
||||
ResultSet rs = DB.checkedExecute(repository.connection, "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(repository.connection);
|
||||
} 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 {
|
||||
|
@ -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;
|
||||
|
||||
|
284
src/transform/block/BlockTransformer.java
Normal file
284
src/transform/block/BlockTransformer.java
Normal 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);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@ -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);
|
||||
|
@ -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);
|
||||
|
@ -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 {
|
||||
|
Loading…
x
Reference in New Issue
Block a user