mirror of
https://github.com/Qortal/qortal.git
synced 2025-04-23 19:37:51 +00:00
Merge branch 'DAO' into DAO
This commit is contained in:
commit
6853f0edcb
@ -1,6 +1,6 @@
|
|||||||
package data.account;
|
package data.account;
|
||||||
|
|
||||||
public class Account {
|
public class AccountData {
|
||||||
|
|
||||||
// Properties
|
// Properties
|
||||||
protected String address;
|
protected String address;
|
||||||
@ -8,10 +8,10 @@ public class Account {
|
|||||||
|
|
||||||
// Constructors
|
// Constructors
|
||||||
|
|
||||||
protected Account() {
|
protected AccountData() {
|
||||||
}
|
}
|
||||||
|
|
||||||
public Account(String address) {
|
public AccountData(String address) {
|
||||||
this.address = address;
|
this.address = address;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -33,10 +33,10 @@ public class Account {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean equals(Object b) {
|
public boolean equals(Object b) {
|
||||||
if (!(b instanceof Account))
|
if (!(b instanceof AccountData))
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
return this.getAddress().equals(((Account) b).getAddress());
|
return this.getAddress().equals(((AccountData) b).getAddress());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@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;
|
import java.math.BigDecimal;
|
||||||
|
|
||||||
public interface BlockData {
|
import com.google.common.primitives.Bytes;
|
||||||
public int getVersion();
|
|
||||||
public byte[] getReference();
|
public class BlockData {
|
||||||
public int getTransactionCount();
|
|
||||||
public BigDecimal getTotalFees();
|
private byte[] signature;
|
||||||
public byte[] getTransactionsSignature();
|
private int version;
|
||||||
public int getHeight();
|
private byte[] reference;
|
||||||
public long getTimestamp();
|
private int transactionCount;
|
||||||
public BigDecimal getGeneratingBalance();
|
private BigDecimal totalFees;
|
||||||
public byte[] getGeneratorPublicKey();
|
private byte[] transactionsSignature;
|
||||||
public byte[] getGeneratorSignature();
|
private int height;
|
||||||
public byte[] getAtBytes();
|
private long timestamp;
|
||||||
public BigDecimal getAtFees();
|
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;
|
protected byte[] publicKey;
|
||||||
|
|
||||||
public PublicKeyAccount(byte[] publicKey) {
|
public PublicKeyAccount(byte[] publicKey) {
|
||||||
|
super(Crypto.toAddress(publicKey));
|
||||||
|
|
||||||
this.publicKey = publicKey;
|
this.publicKey = publicKey;
|
||||||
this.address = Crypto.toAddress(this.publicKey);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
protected PublicKeyAccount() {
|
protected PublicKeyAccount() {
|
||||||
|
@ -6,17 +6,8 @@ import java.sql.SQLException;
|
|||||||
import database.DB;
|
import database.DB;
|
||||||
import database.NoDataFoundException;
|
import database.NoDataFoundException;
|
||||||
import qora.account.Account;
|
import qora.account.Account;
|
||||||
import qora.transaction.TransactionHandler;
|
|
||||||
import repository.hsqldb.HSQLDBSaver;
|
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 class Asset {
|
||||||
|
|
||||||
public static final long QORA = 0L;
|
public static final long QORA = 0L;
|
||||||
@ -91,7 +82,7 @@ public class Asset {
|
|||||||
this.description = rs.getString(3);
|
this.description = rs.getString(3);
|
||||||
this.quantity = rs.getLong(4);
|
this.quantity = rs.getLong(4);
|
||||||
this.isDivisible = rs.getBoolean(5);
|
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 {
|
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.Ints;
|
||||||
import com.google.common.primitives.Longs;
|
import com.google.common.primitives.Longs;
|
||||||
|
|
||||||
|
import data.block.BlockData;
|
||||||
|
import data.transaction.TransactionData;
|
||||||
import database.DB;
|
import database.DB;
|
||||||
import database.NoDataFoundException;
|
import database.NoDataFoundException;
|
||||||
import qora.account.PrivateKeyAccount;
|
import qora.account.PrivateKeyAccount;
|
||||||
@ -31,10 +33,14 @@ import qora.assets.Order;
|
|||||||
import qora.assets.Trade;
|
import qora.assets.Trade;
|
||||||
import qora.transaction.CreateOrderTransaction;
|
import qora.transaction.CreateOrderTransaction;
|
||||||
import qora.transaction.GenesisTransaction;
|
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 repository.hsqldb.HSQLDBSaver;
|
||||||
import qora.transaction.TransactionHandler;
|
|
||||||
import transform.TransformationException;
|
import transform.TransformationException;
|
||||||
|
import transform.block.BlockTransformer;
|
||||||
|
import transform.transaction.TransactionTransformer;
|
||||||
import utils.Base58;
|
import utils.Base58;
|
||||||
import utils.NTP;
|
import utils.NTP;
|
||||||
import utils.Serialization;
|
import utils.Serialization;
|
||||||
@ -63,51 +69,16 @@ import utils.Serialization;
|
|||||||
|
|
||||||
public class Block {
|
public class Block {
|
||||||
|
|
||||||
/**
|
// Properties
|
||||||
* Ordered list of columns when fetching a Block row from database.
|
private BlockData blockData;
|
||||||
*/
|
private PublicKeyAccount generator;
|
||||||
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;
|
|
||||||
|
|
||||||
// Other properties
|
// Other properties
|
||||||
protected List<TransactionHandler> transactions;
|
protected List<Transaction> transactions;
|
||||||
protected BigDecimal cachedNextGeneratingBalance;
|
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
|
// Other useful constants
|
||||||
|
public static final int MAX_BLOCK_BYTES = 1048576;
|
||||||
/**
|
/**
|
||||||
* Number of blocks between recalculating block's generating balance.
|
* Number of blocks between recalculating block's generating balance.
|
||||||
*/
|
*/
|
||||||
@ -125,91 +96,15 @@ public class Block {
|
|||||||
|
|
||||||
// Constructors
|
// Constructors
|
||||||
|
|
||||||
// For creating a new block from scratch
|
public Block(BlockData blockData) {
|
||||||
public Block(int version, byte[] reference, long timestamp, BigDecimal generatingBalance, PublicKeyAccount generator, byte[] atBytes, BigDecimal atFees) {
|
this.blockData = blockData;
|
||||||
this.version = version;
|
this.generator = new PublicKeyAccount(blockData.getGeneratorPublicKey());
|
||||||
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());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Getters/setters
|
// Getters/setters
|
||||||
|
|
||||||
public int getVersion() {
|
public BlockData getBlockData() {
|
||||||
return this.version;
|
return this.blockData;
|
||||||
}
|
|
||||||
|
|
||||||
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;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// More information
|
// More information
|
||||||
@ -220,26 +115,10 @@ public class Block {
|
|||||||
* @return byte[], or null if either component signature is null.
|
* @return byte[], or null if either component signature is null.
|
||||||
*/
|
*/
|
||||||
public byte[] getSignature() {
|
public byte[] getSignature() {
|
||||||
if (this.generatorSignature == null || this.transactionsSignature == null)
|
if (this.blockData.getGeneratorSignature() == null || this.blockData.getTransactionsSignature() == null)
|
||||||
return null;
|
return null;
|
||||||
|
|
||||||
return Bytes.concat(this.generatorSignature, this.transactionsSignature);
|
return Bytes.concat(this.blockData.getGeneratorSignature(), this.blockData.getTransactionsSignature());
|
||||||
}
|
|
||||||
|
|
||||||
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;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -248,9 +127,9 @@ public class Block {
|
|||||||
* @return 1, 2 or 3
|
* @return 1, 2 or 3
|
||||||
*/
|
*/
|
||||||
public int getNextBlockVersion() {
|
public int getNextBlockVersion() {
|
||||||
if (this.height < AT_BLOCK_HEIGHT_RELEASE)
|
if (this.blockData.getHeight() < AT_BLOCK_HEIGHT_RELEASE)
|
||||||
return 1;
|
return 1;
|
||||||
else if (this.timestamp < POWFIX_RELEASE_TIMESTAMP)
|
else if (this.blockData.getTimestamp() < POWFIX_RELEASE_TIMESTAMP)
|
||||||
return 2;
|
return 2;
|
||||||
else
|
else
|
||||||
return 3;
|
return 3;
|
||||||
@ -269,8 +148,8 @@ public class Block {
|
|||||||
*/
|
*/
|
||||||
public BigDecimal getNextBlockGeneratingBalance() throws SQLException {
|
public BigDecimal getNextBlockGeneratingBalance() throws SQLException {
|
||||||
// This block not at the start of an interval?
|
// This block not at the start of an interval?
|
||||||
if (this.height % BLOCK_RETARGET_INTERVAL != 0)
|
if (this.blockData.getHeight() % BLOCK_RETARGET_INTERVAL != 0)
|
||||||
return this.generatingBalance;
|
return this.blockData.getGeneratingBalance();
|
||||||
|
|
||||||
// Return cached calculation if we have one
|
// Return cached calculation if we have one
|
||||||
if (this.cachedNextGeneratingBalance != null)
|
if (this.cachedNextGeneratingBalance != null)
|
||||||
@ -280,23 +159,29 @@ public class Block {
|
|||||||
|
|
||||||
// Navigate back to first block in previous interval:
|
// Navigate back to first block in previous interval:
|
||||||
// XXX: why can't we simply load using block height?
|
// XXX: why can't we simply load using block height?
|
||||||
Block firstBlock = this;
|
BlockRepository blockRepo = RepositoryManager.getBlockRepository();
|
||||||
for (int i = 1; firstBlock != null && i < BLOCK_RETARGET_INTERVAL; ++i)
|
BlockData firstBlock = this.blockData;
|
||||||
firstBlock = firstBlock.getParent();
|
|
||||||
|
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?
|
// Couldn't navigate back far enough?
|
||||||
if (firstBlock == null)
|
if (firstBlock == null)
|
||||||
throw new IllegalStateException("Failed to calculate next block's generating balance due to lack of historic blocks");
|
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.
|
// 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.
|
// 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.
|
// Finally, scale generating balance such that faster than expected previous intervals produce larger generating balances.
|
||||||
BigDecimal multiplier = BigDecimal.valueOf((double) expectedGeneratingTime / (double) previousGeneratingTime);
|
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;
|
return this.cachedNextGeneratingBalance;
|
||||||
}
|
}
|
||||||
@ -316,310 +201,31 @@ public class Block {
|
|||||||
/**
|
/**
|
||||||
* Return block's transactions.
|
* Return block's transactions.
|
||||||
* <p>
|
* <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
|
* @return
|
||||||
* @throws SQLException
|
* @throws DataException
|
||||||
*/
|
*/
|
||||||
public List<TransactionHandler> getTransactions() throws SQLException {
|
public List<Transaction> getTransactions() throws DataException {
|
||||||
// Already loaded?
|
// Already loaded?
|
||||||
if (this.transactions != null)
|
if (this.transactions != null)
|
||||||
return this.transactions;
|
return this.transactions;
|
||||||
|
|
||||||
// Allocate cache for results
|
// 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());
|
// The number of transactions fetched from repository should correspond with Block's transactionCount
|
||||||
if (rs == null)
|
if (transactionsData.size() != this.blockData.getTransactionCount())
|
||||||
return this.transactions; // No transactions in this block
|
throw new IllegalStateException("Block's transactions from repository do not match block's transaction count");
|
||||||
|
|
||||||
// NB: do-while loop because DB.checkedExecute() implicitly calls ResultSet.next() for us
|
this.transactions = new ArrayList<Transaction>();
|
||||||
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
|
for (TransactionData transactionData : transactionsData)
|
||||||
} while (rs.next());
|
this.transactions.add(Transaction.fromData(transactionData));
|
||||||
|
|
||||||
// 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");
|
|
||||||
|
|
||||||
return this.transactions;
|
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
|
// Processing
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -629,12 +235,12 @@ public class Block {
|
|||||||
* <p>
|
* <p>
|
||||||
* Requires block's {@code generator} being a {@code PrivateKeyAccount} so block's transactions signature can be recalculated.
|
* 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
|
* @return true if transaction successfully added to block, false otherwise
|
||||||
* @throws IllegalStateException
|
* @throws IllegalStateException
|
||||||
* if block's {@code generator} is not a {@code PrivateKeyAccount}.
|
* 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
|
// Can't add to transactions if we haven't loaded existing ones yet
|
||||||
if (this.transactions == null)
|
if (this.transactions == null)
|
||||||
throw new IllegalStateException("Attempted to add transaction to partially loaded database Block");
|
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");
|
throw new IllegalStateException("Block's generator has no private key");
|
||||||
|
|
||||||
// Check there is space in block
|
// 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;
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
// Add to block
|
// Add to block
|
||||||
this.transactions.add(transaction);
|
this.transactions.add(Transaction.fromData(transactionData));
|
||||||
|
|
||||||
// Update transaction count
|
// Update transaction count
|
||||||
this.transactionCount++;
|
this.blockData.setTransactionCount(this.blockData.getTransactionCount() + 1);
|
||||||
|
|
||||||
// Update totalFees
|
// Update totalFees
|
||||||
this.totalFees.add(transaction.getFee());
|
this.blockData.setTotalFees(this.blockData.getTotalFees().add(transactionData.getFee()));
|
||||||
|
|
||||||
// Update transactions signature
|
// Update transactions signature
|
||||||
calcTransactionsSignature();
|
calcTransactionsSignature();
|
||||||
@ -668,29 +278,17 @@ public class Block {
|
|||||||
*
|
*
|
||||||
* @throws IllegalStateException
|
* @throws IllegalStateException
|
||||||
* if block's {@code generator} is not a {@code PrivateKeyAccount}.
|
* if block's {@code generator} is not a {@code PrivateKeyAccount}.
|
||||||
|
* @throws RuntimeException
|
||||||
|
* if somehow the generator signature cannot be calculated
|
||||||
*/
|
*/
|
||||||
public void calcGeneratorSignature() {
|
public void calcGeneratorSignature() {
|
||||||
if (!(this.generator instanceof PrivateKeyAccount))
|
if (!(this.generator instanceof PrivateKeyAccount))
|
||||||
throw new IllegalStateException("Block's generator has no private key");
|
throw new IllegalStateException("Block's generator has no private key");
|
||||||
|
|
||||||
this.generatorSignature = ((PrivateKeyAccount) this.generator).sign(this.getBytesForGeneratorSignature());
|
|
||||||
}
|
|
||||||
|
|
||||||
private byte[] getBytesForGeneratorSignature() {
|
|
||||||
try {
|
try {
|
||||||
ByteArrayOutputStream bytes = new ByteArrayOutputStream(GENERATOR_SIGNATURE_LENGTH + GENERATING_BALANCE_LENGTH + GENERATOR_LENGTH);
|
this.blockData.setGeneratorSignature(((PrivateKeyAccount) this.generator).sign(BlockTransformer.getBytesForGeneratorSignature(this.blockData)));
|
||||||
|
} catch (TransformationException e) {
|
||||||
// Only copy the generator signature from reference, which is the first 64 bytes.
|
throw new RuntimeException("Unable to calculate block's generator signature", e);
|
||||||
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);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -701,41 +299,33 @@ public class Block {
|
|||||||
*
|
*
|
||||||
* @throws IllegalStateException
|
* @throws IllegalStateException
|
||||||
* if block's {@code generator} is not a {@code PrivateKeyAccount}.
|
* if block's {@code generator} is not a {@code PrivateKeyAccount}.
|
||||||
|
* @throws RuntimeException
|
||||||
|
* if somehow the transactions signature cannot be calculated
|
||||||
*/
|
*/
|
||||||
public void calcTransactionsSignature() {
|
public void calcTransactionsSignature() {
|
||||||
if (!(this.generator instanceof PrivateKeyAccount))
|
if (!(this.generator instanceof PrivateKeyAccount))
|
||||||
throw new IllegalStateException("Block's generator has no private key");
|
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 {
|
try {
|
||||||
bytes.write(this.generatorSignature);
|
this.blockData.setTransactionsSignature(((PrivateKeyAccount) this.generator).sign(BlockTransformer.getBytesForTransactionsSignature(this)));
|
||||||
|
} catch (TransformationException e) {
|
||||||
for (TransactionHandler transaction : this.getTransactions()) {
|
throw new RuntimeException("Unable to calculate block's transactions signature", e);
|
||||||
if (!transaction.isSignatureValid())
|
|
||||||
return null;
|
|
||||||
|
|
||||||
bytes.write(transaction.getSignature());
|
|
||||||
}
|
|
||||||
|
|
||||||
return bytes.toByteArray();
|
|
||||||
} catch (IOException | SQLException e) {
|
|
||||||
throw new RuntimeException(e);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean isSignatureValid() {
|
|
||||||
// Check generator's signature first
|
|
||||||
if (!this.generator.verify(this.generatorSignature, getBytesForGeneratorSignature()))
|
|
||||||
return false;
|
|
||||||
|
|
||||||
// Check transactions signature
|
public boolean isSignatureValid() {
|
||||||
if (!this.generator.verify(this.transactionsSignature, getBytesForTransactionsSignature()))
|
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 false;
|
||||||
|
}
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
@ -749,41 +339,44 @@ public class Block {
|
|||||||
*
|
*
|
||||||
* @return true if block is valid, false otherwise.
|
* @return true if block is valid, false otherwise.
|
||||||
* @throws SQLException
|
* @throws SQLException
|
||||||
|
* @throws DataException
|
||||||
*/
|
*/
|
||||||
public boolean isValid() throws SQLException {
|
public boolean isValid() throws SQLException, DataException {
|
||||||
// TODO
|
// TODO
|
||||||
|
|
||||||
// Check parent blocks exists
|
// Check parent blocks exists
|
||||||
if (this.reference == null)
|
if (this.blockData.getReference() == null)
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
Block parentBlock = this.getParent();
|
BlockData parentBlockData = RepositoryManager.getBlockRepository().fromSignature(this.blockData.getReference());
|
||||||
if (parentBlock == null)
|
if (parentBlockData == null)
|
||||||
return false;
|
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
|
// 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;
|
return false;
|
||||||
|
|
||||||
// Legacy gen1 test: check timestamp ms is the same as parent timestamp ms?
|
// 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;
|
return false;
|
||||||
|
|
||||||
// Check block version
|
// Check block version
|
||||||
if (this.version != parentBlock.getNextBlockVersion())
|
if (this.blockData.getVersion() != parentBlock.getNextBlockVersion())
|
||||||
return false;
|
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;
|
return false;
|
||||||
|
|
||||||
// Check generating balance
|
// Check generating balance
|
||||||
if (this.generatingBalance != parentBlock.getNextBlockGeneratingBalance())
|
if (this.blockData.getGeneratingBalance() != parentBlock.getNextBlockGeneratingBalance())
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
// Check generator's proof of stake against block's generating balance
|
// Check generator's proof of stake against block's generating balance
|
||||||
// TODO
|
// TODO
|
||||||
|
|
||||||
// Check CIYAM AT
|
// Check CIYAM AT
|
||||||
if (this.atBytes != null && this.atBytes.length > 0) {
|
if (this.blockData.getAtBytes() != null && this.blockData.getAtBytes().length > 0) {
|
||||||
// TODO
|
// TODO
|
||||||
// try {
|
// try {
|
||||||
// AT_Block atBlock = AT_Controller.validateATs(this.getBlockATs(), BlockChain.getHeight() + 1);
|
// AT_Block atBlock = AT_Controller.validateATs(this.getBlockATs(), BlockChain.getHeight() + 1);
|
||||||
@ -796,18 +389,18 @@ public class Block {
|
|||||||
// Check transactions
|
// Check transactions
|
||||||
Savepoint savepoint = DB.createSavepoint("BLOCK_TRANSACTIONS");
|
Savepoint savepoint = DB.createSavepoint("BLOCK_TRANSACTIONS");
|
||||||
try {
|
try {
|
||||||
for (TransactionHandler transaction : this.getTransactions()) {
|
for (Transaction transaction : this.getTransactions()) {
|
||||||
// GenesisTransactions are not allowed (GenesisBlock overrides isValid() to allow them)
|
// GenesisTransactions are not allowed (GenesisBlock overrides isValid() to allow them)
|
||||||
if (transaction instanceof GenesisTransaction)
|
if (transaction instanceof GenesisTransaction)
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
// Check timestamp and deadline
|
// 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;
|
return false;
|
||||||
|
|
||||||
// Check transaction is even valid
|
// Check transaction is even valid
|
||||||
// NOTE: in Gen1 there was an extra block height passed to DeployATTransaction.isValid
|
// 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;
|
return false;
|
||||||
|
|
||||||
// Process transaction to make sure other transactions validate properly
|
// Process transaction to make sure other transactions validate properly
|
||||||
@ -818,6 +411,8 @@ public class Block {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
} catch (DataException e) {
|
||||||
|
return false;
|
||||||
} finally {
|
} finally {
|
||||||
// Revert back to savepoint
|
// Revert back to savepoint
|
||||||
try {
|
try {
|
||||||
@ -834,32 +429,32 @@ public class Block {
|
|||||||
return true;
|
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)
|
// Process transactions (we'll link them to this block after saving the block itself)
|
||||||
List<TransactionHandler> transactions = this.getTransactions();
|
List<Transaction> transactions = this.getTransactions();
|
||||||
for (TransactionHandler transaction : transactions)
|
for (Transaction transaction : transactions)
|
||||||
transaction.process();
|
transaction.process();
|
||||||
|
|
||||||
// If fees are non-zero then add fees to generator's balance
|
// 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)
|
if (blockFee.compareTo(BigDecimal.ZERO) == 1)
|
||||||
this.generator.setConfirmedBalance(Asset.QORA, this.generator.getConfirmedBalance(Asset.QORA).add(blockFee));
|
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
|
// Link block into blockchain by fetching signature of highest block and setting that as our reference
|
||||||
int blockchainHeight = BlockChain.getHeight();
|
int blockchainHeight = BlockChain.getHeight();
|
||||||
Block latestBlock = Block.fromHeight(blockchainHeight);
|
BlockData latestBlockData = RepositoryManager.getBlockRepository().fromHeight(blockchainHeight);
|
||||||
if (latestBlock != null)
|
if (latestBlockData != null)
|
||||||
this.reference = latestBlock.getSignature();
|
this.blockData.setReference(latestBlockData.getSignature());
|
||||||
|
|
||||||
this.height = blockchainHeight + 1;
|
this.blockData.setHeight(blockchainHeight + 1);
|
||||||
this.save();
|
RepositoryManager.getBlockRepository().save(this.blockData);
|
||||||
|
|
||||||
// Link transactions to this block, thus removing them from unconfirmed transactions list.
|
// Link transactions to this block, thus removing them from unconfirmed transactions list.
|
||||||
for (int sequence = 0; sequence < transactions.size(); ++sequence) {
|
for (int sequence = 0; sequence < transactions.size(); ++sequence) {
|
||||||
TransactionHandler transaction = transactions.get(sequence);
|
Transaction transaction = transactions.get(sequence);
|
||||||
|
|
||||||
// Link transaction to this block
|
// 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();
|
blockTransaction.save();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -87,14 +87,17 @@ public class BlockChain {
|
|||||||
* Return highest block height from DB.
|
* Return highest block height from DB.
|
||||||
*
|
*
|
||||||
* @return height, or 0 if there are no blocks in DB (not very likely).
|
* @return height, or 0 if there are no blocks in DB (not very likely).
|
||||||
* @throws SQLException
|
|
||||||
*/
|
*/
|
||||||
public static int getHeight() throws SQLException {
|
public static int getHeight() {
|
||||||
ResultSet rs = DB.checkedExecute("SELECT MAX(height) FROM Blocks");
|
try {
|
||||||
if (rs == null)
|
ResultSet rs = DB.checkedExecute("SELECT MAX(height) FROM Blocks");
|
||||||
return 0;
|
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.DB;
|
||||||
import database.NoDataFoundException;
|
import database.NoDataFoundException;
|
||||||
import qora.transaction.TransactionHandler;
|
import qora.transaction.Transaction;
|
||||||
import repository.hsqldb.HSQLDBSaver;
|
import repository.hsqldb.HSQLDBSaver;
|
||||||
import qora.transaction.TransactionHandler;
|
|
||||||
|
|
||||||
public class BlockTransaction {
|
public class BlockTransaction {
|
||||||
|
|
||||||
@ -50,7 +49,7 @@ public class BlockTransaction {
|
|||||||
|
|
||||||
this.blockSignature = blockSignature;
|
this.blockSignature = blockSignature;
|
||||||
this.sequence = sequence;
|
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 {
|
protected BlockTransaction(byte[] transactionSignature) throws SQLException {
|
||||||
@ -58,7 +57,7 @@ public class BlockTransaction {
|
|||||||
if (rs == null)
|
if (rs == null)
|
||||||
throw new NoDataFoundException();
|
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.sequence = rs.getInt(2);
|
||||||
this.transactionSignature = transactionSignature;
|
this.transactionSignature = transactionSignature;
|
||||||
}
|
}
|
||||||
@ -118,8 +117,10 @@ public class BlockTransaction {
|
|||||||
* @return Transaction, or null if not found (which should never happen)
|
* @return Transaction, or null if not found (which should never happen)
|
||||||
* @throws SQLException
|
* @throws SQLException
|
||||||
*/
|
*/
|
||||||
public TransactionHandler getTransaction() throws SQLException {
|
public Transaction getTransaction() throws SQLException {
|
||||||
return TransactionFactory.fromSignature(this.transactionSignature);
|
// XXX
|
||||||
|
// return TransactionFactory.fromSignature(this.transactionSignature);
|
||||||
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -1,151 +1,21 @@
|
|||||||
package qora.transaction;
|
package qora.transaction;
|
||||||
|
|
||||||
import java.io.ByteArrayOutputStream;
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.math.BigDecimal;
|
import java.math.BigDecimal;
|
||||||
import java.nio.ByteBuffer;
|
|
||||||
import java.sql.ResultSet;
|
|
||||||
import java.sql.SQLException;
|
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
|
|
||||||
import org.json.simple.JSONObject;
|
|
||||||
|
|
||||||
import com.google.common.primitives.Bytes;
|
import com.google.common.primitives.Bytes;
|
||||||
import com.google.common.primitives.Ints;
|
|
||||||
import com.google.common.primitives.Longs;
|
|
||||||
|
|
||||||
import database.DB;
|
import data.transaction.GenesisTransactionData;
|
||||||
import database.NoDataFoundException;
|
import data.transaction.TransactionData;
|
||||||
import qora.account.Account;
|
|
||||||
import qora.account.GenesisAccount;
|
|
||||||
import qora.account.PrivateKeyAccount;
|
import qora.account.PrivateKeyAccount;
|
||||||
import qora.assets.Asset;
|
|
||||||
import qora.crypto.Crypto;
|
import qora.crypto.Crypto;
|
||||||
import repository.hsqldb.HSQLDBSaver;
|
|
||||||
import transform.TransformationException;
|
import transform.TransformationException;
|
||||||
import utils.Base58;
|
import transform.transaction.TransactionTransformer;
|
||||||
import utils.Serialization;
|
|
||||||
|
|
||||||
public class GenesisTransaction extends TransactionHandler {
|
public class GenesisTransaction extends Transaction {
|
||||||
|
|
||||||
// Properties
|
public GenesisTransaction(TransactionData transactionData) {
|
||||||
private Account recipient;
|
this.transactionData = transactionData;
|
||||||
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);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Processing
|
// Processing
|
||||||
@ -174,8 +44,12 @@ public class GenesisTransaction extends TransactionHandler {
|
|||||||
* @return byte[]
|
* @return byte[]
|
||||||
*/
|
*/
|
||||||
private byte[] calcSignature() {
|
private byte[] calcSignature() {
|
||||||
byte[] digest = Crypto.digest(toBytes());
|
try {
|
||||||
return Bytes.concat(digest, digest);
|
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
|
@Override
|
||||||
public boolean isSignatureValid() {
|
public boolean isSignatureValid() {
|
||||||
return Arrays.equals(this.signature, calcSignature());
|
return Arrays.equals(this.transactionData.getSignature(), this.calcSignature());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public ValidationResult isValid() {
|
public ValidationResult isValid() {
|
||||||
|
GenesisTransactionData genesisTransaction = (GenesisTransactionData) this.transactionData;
|
||||||
|
|
||||||
// Check amount is zero or positive
|
// Check amount is zero or positive
|
||||||
if (this.amount.compareTo(BigDecimal.ZERO) == -1)
|
if (genesisTransaction.getAmount().compareTo(BigDecimal.ZERO) == -1)
|
||||||
return ValidationResult.NEGATIVE_AMOUNT;
|
return ValidationResult.NEGATIVE_AMOUNT;
|
||||||
|
|
||||||
// Check recipient address is valid
|
// Check recipient address is valid
|
||||||
if (!Crypto.isValidAddress(this.recipient.getAddress()))
|
if (!Crypto.isValidAddress(genesisTransaction.getRecipient()))
|
||||||
return ValidationResult.INVALID_ADDRESS;
|
return ValidationResult.INVALID_ADDRESS;
|
||||||
|
|
||||||
return ValidationResult.OK;
|
return ValidationResult.OK;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void process() throws SQLException {
|
public void process() {
|
||||||
this.save();
|
// TODO
|
||||||
|
// this.save();
|
||||||
|
|
||||||
// Set recipient's balance
|
// Set recipient's balance
|
||||||
this.recipient.setConfirmedBalance(Asset.QORA, this.amount);
|
// TODO
|
||||||
|
// this.recipient.setConfirmedBalance(Asset.QORA, this.amount);
|
||||||
|
|
||||||
// Set recipient's reference
|
// Set recipient's reference
|
||||||
recipient.setLastReference(this.signature);
|
// TODO
|
||||||
|
// recipient.setLastReference(this.signature);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void orphan() throws SQLException {
|
public void orphan() {
|
||||||
this.delete();
|
// TODO
|
||||||
|
// this.delete();
|
||||||
|
|
||||||
// Reset recipient's balance
|
// Reset recipient's balance
|
||||||
this.recipient.deleteBalance(Asset.QORA);
|
// TODO
|
||||||
|
// this.recipient.deleteBalance(Asset.QORA);
|
||||||
|
|
||||||
// Set recipient's reference
|
// 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.Arrays.stream;
|
||||||
import static java.util.stream.Collectors.toMap;
|
import static java.util.stream.Collectors.toMap;
|
||||||
|
|
||||||
import org.json.simple.JSONObject;
|
import data.block.BlockData;
|
||||||
|
import data.transaction.TransactionData;
|
||||||
import data.transaction.Transaction;
|
|
||||||
import database.DB;
|
|
||||||
import database.NoDataFoundException;
|
|
||||||
import qora.account.PrivateKeyAccount;
|
import qora.account.PrivateKeyAccount;
|
||||||
import qora.account.PublicKeyAccount;
|
|
||||||
import qora.block.Block;
|
import qora.block.Block;
|
||||||
import qora.block.BlockChain;
|
import qora.block.BlockChain;
|
||||||
import qora.block.BlockTransaction;
|
|
||||||
import repository.Repository;
|
|
||||||
import repository.RepositoryManager;
|
import repository.RepositoryManager;
|
||||||
import settings.Settings;
|
import settings.Settings;
|
||||||
import transform.TransformationException;
|
import transform.TransformationException;
|
||||||
import transform.Transformer;
|
import transform.Transformer;
|
||||||
import transform.transaction.TransactionTransformer;
|
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
|
// Validation results
|
||||||
public enum ValidationResult {
|
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 maxBytePerFee = BigDecimal.valueOf(Settings.getInstance().getMaxBytePerFee());
|
||||||
protected static final BigDecimal minFeePerByte = BigDecimal.ONE.divide(maxBytePerFee, MathContext.DECIMAL32);
|
protected static final BigDecimal minFeePerByte = BigDecimal.ONE.divide(maxBytePerFee, MathContext.DECIMAL32);
|
||||||
|
|
||||||
private Transaction transaction;
|
protected TransactionData transactionData;
|
||||||
|
|
||||||
// Constructors
|
// Constructors
|
||||||
|
|
||||||
public TransactionHandler(Transaction transaction) {
|
public static Transaction fromData(TransactionData transactionData) {
|
||||||
this.transaction = transaction;
|
switch (transactionData.getType()) {
|
||||||
|
case GENESIS:
|
||||||
|
return new GenesisTransaction(transactionData);
|
||||||
|
|
||||||
|
default:
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Getters / Setters
|
||||||
|
|
||||||
|
public TransactionData getTransactionData() {
|
||||||
|
return this.transactionData;
|
||||||
}
|
}
|
||||||
|
|
||||||
// More information
|
// More information
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
public long getDeadline() {
|
public long getDeadline() {
|
||||||
// 24 hour deadline to include transaction in a block
|
// 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() {
|
public boolean hasMinimumFee() {
|
||||||
return this.transaction.getFee().compareTo(MINIMUM_FEE) >= 0;
|
return this.transactionData.getFee().compareTo(MINIMUM_FEE) >= 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
public BigDecimal feePerByte() {
|
public BigDecimal feePerByte() {
|
||||||
try {
|
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) {
|
} catch (TransformationException e) {
|
||||||
throw new IllegalStateException("Unable to get transaction byte length?");
|
throw new IllegalStateException("Unable to get transaction byte length?");
|
||||||
}
|
}
|
||||||
@ -87,7 +108,7 @@ public abstract class TransactionHandler {
|
|||||||
|
|
||||||
public BigDecimal calcRecommendedFee() {
|
public BigDecimal calcRecommendedFee() {
|
||||||
try {
|
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
|
// security margin
|
||||||
recommendedFee = recommendedFee.add(new BigDecimal("0.0000001"));
|
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)
|
* @return height, or 0 if not in blockchain (i.e. unconfirmed)
|
||||||
*/
|
*/
|
||||||
public int getHeight() {
|
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;
|
return 0;
|
||||||
|
|
||||||
int blockChainHeight = BlockChain.getHeight();
|
int blockChainHeight = BlockChain.getHeight();
|
||||||
|
if (blockChainHeight == 0)
|
||||||
|
return 0;
|
||||||
|
|
||||||
return blockChainHeight - ourHeight + 1;
|
return blockChainHeight - ourHeight + 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -142,8 +166,8 @@ public abstract class TransactionHandler {
|
|||||||
*
|
*
|
||||||
* @return Block, or null if transaction is not in a Block
|
* @return Block, or null if transaction is not in a Block
|
||||||
*/
|
*/
|
||||||
public Block getBlock() {
|
public BlockData getBlock() {
|
||||||
return RepositoryManager.getTransactionRepository().toBlock(this.transaction);
|
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)
|
* @return Transaction, or null if no parent found (which should not happen)
|
||||||
*/
|
*/
|
||||||
public Transaction getParent() {
|
public TransactionData getParent() {
|
||||||
byte[] reference = this.transaction.getReference();
|
byte[] reference = this.transactionData.getReference();
|
||||||
if (reference == null)
|
if (reference == null)
|
||||||
return null;
|
return null;
|
||||||
|
|
||||||
@ -164,8 +188,8 @@ public abstract class TransactionHandler {
|
|||||||
*
|
*
|
||||||
* @return Transaction, or null if no child found
|
* @return Transaction, or null if no child found
|
||||||
*/
|
*/
|
||||||
public Transaction getChild() {
|
public TransactionData getChild() {
|
||||||
byte[] signature = this.transaction.getSignature();
|
byte[] signature = this.transactionData.getSignature();
|
||||||
if (signature == null)
|
if (signature == null)
|
||||||
return null;
|
return null;
|
||||||
|
|
||||||
@ -181,7 +205,7 @@ public abstract class TransactionHandler {
|
|||||||
*/
|
*/
|
||||||
private byte[] toBytesLessSignature() {
|
private byte[] toBytesLessSignature() {
|
||||||
try {
|
try {
|
||||||
byte[] bytes = TransactionTransformer.toBytes(this.transaction);
|
byte[] bytes = TransactionTransformer.toBytes(this.transactionData);
|
||||||
return Arrays.copyOf(bytes, bytes.length - Transformer.SIGNATURE_LENGTH);
|
return Arrays.copyOf(bytes, bytes.length - Transformer.SIGNATURE_LENGTH);
|
||||||
} catch (TransformationException e) {
|
} catch (TransformationException e) {
|
||||||
// XXX this isn't good
|
// XXX this isn't good
|
||||||
@ -196,7 +220,7 @@ public abstract class TransactionHandler {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public boolean isSignatureValid() {
|
public boolean isSignatureValid() {
|
||||||
byte[] signature = this.transaction.getSignature();
|
byte[] signature = this.transactionData.getSignature();
|
||||||
if (signature == null)
|
if (signature == null)
|
||||||
return false;
|
return false;
|
||||||
|
|
@ -1,8 +1,20 @@
|
|||||||
package repository;
|
package repository;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
import data.block.BlockData;
|
import data.block.BlockData;
|
||||||
|
import data.transaction.TransactionData;
|
||||||
|
|
||||||
public interface BlockRepository {
|
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;
|
package repository;
|
||||||
|
|
||||||
import data.transaction.Transaction;
|
import data.transaction.TransactionData;
|
||||||
import qora.block.Block;
|
import data.block.BlockData;
|
||||||
|
|
||||||
public interface TransactionRepository {
|
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;
|
package repository.hsqldb;
|
||||||
|
|
||||||
import java.math.BigDecimal;
|
import java.math.BigDecimal;
|
||||||
import java.sql.PreparedStatement;
|
|
||||||
import java.sql.ResultSet;
|
import java.sql.ResultSet;
|
||||||
import java.sql.SQLException;
|
import java.sql.SQLException;
|
||||||
import java.sql.Timestamp;
|
import java.sql.Timestamp;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
import data.block.Block;
|
|
||||||
import data.block.BlockData;
|
import data.block.BlockData;
|
||||||
|
import data.transaction.TransactionData;
|
||||||
import database.DB;
|
import database.DB;
|
||||||
import qora.account.PublicKeyAccount;
|
|
||||||
import repository.BlockRepository;
|
import repository.BlockRepository;
|
||||||
import repository.DataException;
|
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 TRANSACTIONS_SIGNATURE_LENGTH = 64;
|
||||||
protected static final int GENERATOR_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 REFERENCE_LENGTH = GENERATOR_SIGNATURE_LENGTH + TRANSACTIONS_SIGNATURE_LENGTH;
|
||||||
@ -28,29 +29,37 @@ public class HSQLDBBlockRepository implements BlockRepository
|
|||||||
this.repository = repository;
|
this.repository = repository;
|
||||||
}
|
}
|
||||||
|
|
||||||
public BlockData fromSignature(byte[] signature) throws DataException
|
public BlockData fromSignature(byte[] signature) throws DataException {
|
||||||
{
|
|
||||||
ResultSet rs;
|
|
||||||
try {
|
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) {
|
} catch (SQLException e) {
|
||||||
throw new DataException("Error loading data from DB", e);
|
throw new DataException("Error loading data from DB", e);
|
||||||
}
|
}
|
||||||
return getBlockFromResultSet(rs);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public BlockData fromHeight(int height) throws DataException
|
public BlockData fromReference(byte[] reference) throws DataException {
|
||||||
{
|
|
||||||
ResultSet rs;
|
|
||||||
try {
|
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) {
|
} catch (SQLException e) {
|
||||||
throw new DataException("Error loading data from DB", e);
|
throw new DataException("Error loading data from DB", e);
|
||||||
}
|
}
|
||||||
return getBlockFromResultSet(rs);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private BlockData getBlockFromResultSet(ResultSet rs) throws DataException {
|
private BlockData getBlockFromResultSet(ResultSet rs) throws DataException {
|
||||||
|
if (rs == null)
|
||||||
|
return null;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
int version = rs.getInt(1);
|
int version = rs.getInt(1);
|
||||||
byte[] reference = DB.getResultSetBytes(rs.getBinaryStream(2));
|
byte[] reference = DB.getResultSetBytes(rs.getBinaryStream(2));
|
||||||
@ -65,12 +74,49 @@ public class HSQLDBBlockRepository implements BlockRepository
|
|||||||
byte[] atBytes = DB.getResultSetBytes(rs.getBinaryStream(11));
|
byte[] atBytes = DB.getResultSetBytes(rs.getBinaryStream(11));
|
||||||
BigDecimal atFees = rs.getBigDecimal(12);
|
BigDecimal atFees = rs.getBigDecimal(12);
|
||||||
|
|
||||||
return new Block(version, reference, transactionCount, totalFees, transactionsSignature, height, timestamp,
|
return new BlockData(version, reference, transactionCount, totalFees, transactionsSignature, height, timestamp, generatingBalance, generatorPublicKey,
|
||||||
generatingBalance,generatorPublicKey, generatorSignature, atBytes, atFees);
|
generatorSignature, atBytes, atFees);
|
||||||
}
|
} catch(SQLException e) {
|
||||||
catch(SQLException e)
|
|
||||||
{
|
|
||||||
throw new DataException("Error extracting data from result set", 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.ResultSet;
|
||||||
import java.sql.SQLException;
|
import java.sql.SQLException;
|
||||||
|
|
||||||
import data.account.Account;
|
import data.transaction.GenesisTransactionData;
|
||||||
import data.account.PublicKeyAccount;
|
import data.transaction.TransactionData;
|
||||||
import data.transaction.GenesisTransaction;
|
|
||||||
import data.transaction.Transaction;
|
|
||||||
import database.DB;
|
import database.DB;
|
||||||
|
import repository.DataException;
|
||||||
|
|
||||||
public class HSQLDBGenesisTransactionRepository extends HSQLDBTransactionRepository {
|
public class HSQLDBGenesisTransactionRepository extends HSQLDBTransactionRepository {
|
||||||
|
|
||||||
@ -16,19 +15,34 @@ public class HSQLDBGenesisTransactionRepository extends HSQLDBTransactionReposit
|
|||||||
super(repository);
|
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 {
|
try {
|
||||||
ResultSet rs = DB.checkedExecute(repository.connection, "SELECT recipient, amount FROM GenesisTransactions WHERE signature = ?", signature);
|
ResultSet rs = DB.checkedExecute(repository.connection, "SELECT recipient, amount FROM GenesisTransactions WHERE signature = ?", signature);
|
||||||
if (rs == null)
|
if (rs == null)
|
||||||
return null;
|
return null;
|
||||||
|
|
||||||
Account recipient = new Account(rs.getString(1));
|
String recipient = rs.getString(1);
|
||||||
BigDecimal amount = rs.getBigDecimal(2).setScale(8);
|
BigDecimal amount = rs.getBigDecimal(2).setScale(8);
|
||||||
|
|
||||||
return new GenesisTransaction(recipient, amount, timestamp, signature);
|
return new GenesisTransactionData(recipient, amount, timestamp, signature);
|
||||||
} catch (SQLException e) {
|
} catch (SQLException e) {
|
||||||
return null;
|
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.SQLException;
|
||||||
import java.sql.Timestamp;
|
import java.sql.Timestamp;
|
||||||
|
|
||||||
import data.account.PublicKeyAccount;
|
import data.block.BlockData;
|
||||||
import data.transaction.Transaction;
|
import data.transaction.TransactionData;
|
||||||
import data.transaction.Transaction.TransactionType;
|
import qora.transaction.Transaction.TransactionType;
|
||||||
import database.DB;
|
import database.DB;
|
||||||
import qora.block.Block;
|
import repository.DataException;
|
||||||
|
import repository.RepositoryManager;
|
||||||
import repository.TransactionRepository;
|
import repository.TransactionRepository;
|
||||||
|
|
||||||
public class HSQLDBTransactionRepository implements TransactionRepository {
|
public class HSQLDBTransactionRepository implements TransactionRepository {
|
||||||
@ -23,7 +24,7 @@ public class HSQLDBTransactionRepository implements TransactionRepository {
|
|||||||
genesisTransactionRepository = new HSQLDBGenesisTransactionRepository(repository);
|
genesisTransactionRepository = new HSQLDBGenesisTransactionRepository(repository);
|
||||||
}
|
}
|
||||||
|
|
||||||
public Transaction fromSignature(byte[] signature) {
|
public TransactionData fromSignature(byte[] signature) {
|
||||||
try {
|
try {
|
||||||
ResultSet rs = DB.checkedExecute(repository.connection, "SELECT type, reference, creator, creation, fee FROM Transactions WHERE signature = ?", signature);
|
ResultSet rs = DB.checkedExecute(repository.connection, "SELECT type, reference, creator, creation, fee FROM Transactions WHERE signature = ?", signature);
|
||||||
if (rs == null)
|
if (rs == null)
|
||||||
@ -31,7 +32,7 @@ public class HSQLDBTransactionRepository implements TransactionRepository {
|
|||||||
|
|
||||||
TransactionType type = TransactionType.valueOf(rs.getInt(1));
|
TransactionType type = TransactionType.valueOf(rs.getInt(1));
|
||||||
byte[] reference = DB.getResultSetBytes(rs.getBinaryStream(2));
|
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();
|
long timestamp = rs.getTimestamp(4).getTime();
|
||||||
BigDecimal fee = rs.getBigDecimal(5).setScale(8);
|
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 {
|
try {
|
||||||
ResultSet rs = DB.checkedExecute(repository.connection, "SELECT type, signature, creator, creation, fee FROM Transactions WHERE reference = ?", reference);
|
ResultSet rs = DB.checkedExecute(repository.connection, "SELECT type, signature, creator, creation, fee FROM Transactions WHERE reference = ?", reference);
|
||||||
if (rs == null)
|
if (rs == null)
|
||||||
@ -49,7 +50,7 @@ public class HSQLDBTransactionRepository implements TransactionRepository {
|
|||||||
|
|
||||||
TransactionType type = TransactionType.valueOf(rs.getInt(1));
|
TransactionType type = TransactionType.valueOf(rs.getInt(1));
|
||||||
byte[] signature = DB.getResultSetBytes(rs.getBinaryStream(2));
|
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();
|
long timestamp = rs.getTimestamp(4).getTime();
|
||||||
BigDecimal fee = rs.getBigDecimal(5).setScale(8);
|
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) {
|
switch (type) {
|
||||||
case GENESIS:
|
case GENESIS:
|
||||||
return this.genesisTransactionRepository.fromBase(signature, reference, creator, timestamp, fee);
|
return this.genesisTransactionRepository.fromBase(signature, reference, creator, timestamp, fee);
|
||||||
@ -70,14 +71,15 @@ public class HSQLDBTransactionRepository implements TransactionRepository {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public int getHeight(Transaction transaction) {
|
public int getHeight(TransactionData transactionData) {
|
||||||
byte[] signature = transaction.getSignature();
|
byte[] signature = transactionData.getSignature();
|
||||||
if (signature == null)
|
if (signature == null)
|
||||||
return 0;
|
return 0;
|
||||||
|
|
||||||
// in one go?
|
// in one go?
|
||||||
try {
|
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);
|
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)
|
if (rs == null)
|
||||||
return 0;
|
return 0;
|
||||||
|
|
||||||
@ -88,8 +90,8 @@ public class HSQLDBTransactionRepository implements TransactionRepository {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Block toBlock(Transaction transaction) {
|
public BlockData toBlock(TransactionData transactionData) {
|
||||||
byte[] signature = transaction.getSignature();
|
byte[] signature = transactionData.getSignature();
|
||||||
if (signature == null)
|
if (signature == null)
|
||||||
return null;
|
return null;
|
||||||
|
|
||||||
@ -101,30 +103,27 @@ public class HSQLDBTransactionRepository implements TransactionRepository {
|
|||||||
|
|
||||||
byte[] blockSignature = DB.getResultSetBytes(rs.getBinaryStream(1));
|
byte[] blockSignature = DB.getResultSetBytes(rs.getBinaryStream(1));
|
||||||
|
|
||||||
// TODO
|
return RepositoryManager.getBlockRepository().fromSignature(blockSignature);
|
||||||
// return RepositoryManager.getBlockRepository().fromSignature(blockSignature);
|
} catch (SQLException | DataException e) {
|
||||||
|
|
||||||
return null;
|
|
||||||
} catch (SQLException e) {
|
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void save(Transaction transaction) {
|
public void save(TransactionData transactionData) throws DataException {
|
||||||
HSQLDBSaver saver = new HSQLDBSaver("Transactions");
|
HSQLDBSaver saver = new HSQLDBSaver("Transactions");
|
||||||
saver.bind("signature", transaction.getSignature()).bind("reference", transaction.getReference()).bind("type", transaction.getType().value)
|
saver.bind("signature", transactionData.getSignature()).bind("reference", transactionData.getReference()).bind("type", transactionData.getType().value)
|
||||||
.bind("creator", transaction.getCreator().getPublicKey()).bind("creation", new Timestamp(transaction.getTimestamp())).bind("fee", transaction.getFee())
|
.bind("creator", transactionData.getCreatorPublicKey()).bind("creation", new Timestamp(transactionData.getTimestamp())).bind("fee", transactionData.getFee())
|
||||||
.bind("milestone_block", null);
|
.bind("milestone_block", null);
|
||||||
try {
|
try {
|
||||||
saver.execute(repository.connection);
|
saver.execute(repository.connection);
|
||||||
} catch (SQLException e) {
|
} catch (SQLException e) {
|
||||||
// XXX do what?
|
throw new DataException(e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@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
|
// 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.
|
// definition.
|
||||||
try {
|
try {
|
||||||
|
@ -8,6 +8,7 @@ public abstract class Transformer {
|
|||||||
// Raw, not Base58-encoded
|
// Raw, not Base58-encoded
|
||||||
public static final int ADDRESS_LENGTH = 25;
|
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 SIGNATURE_LENGTH = 64;
|
||||||
public static final int TIMESTAMP_LENGTH = LONG_LENGTH;
|
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.Ints;
|
||||||
import com.google.common.primitives.Longs;
|
import com.google.common.primitives.Longs;
|
||||||
|
|
||||||
import data.transaction.Transaction;
|
import data.transaction.TransactionData;
|
||||||
import data.account.Account;
|
import data.transaction.GenesisTransactionData;
|
||||||
import data.transaction.GenesisTransaction;
|
|
||||||
import transform.TransformationException;
|
import transform.TransformationException;
|
||||||
import utils.Base58;
|
import utils.Base58;
|
||||||
import utils.Serialization;
|
import utils.Serialization;
|
||||||
@ -25,30 +24,30 @@ public class GenesisTransactionTransformer extends TransactionTransformer {
|
|||||||
// Note that Genesis transactions don't require reference, fee or signature:
|
// Note that Genesis transactions don't require reference, fee or signature:
|
||||||
private static final int TYPELESS_LENGTH = TIMESTAMP_LENGTH + RECIPIENT_LENGTH + AMOUNT_LENGTH;
|
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)
|
if (byteBuffer.remaining() < TYPELESS_LENGTH)
|
||||||
throw new TransformationException("Byte data too short for GenesisTransaction");
|
throw new TransformationException("Byte data too short for GenesisTransaction");
|
||||||
|
|
||||||
long timestamp = byteBuffer.getLong();
|
long timestamp = byteBuffer.getLong();
|
||||||
Account recipient = new Account(Serialization.deserializeRecipient(byteBuffer));
|
String recipient = Serialization.deserializeRecipient(byteBuffer);
|
||||||
BigDecimal amount = Serialization.deserializeBigDecimal(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;
|
return TYPE_LENGTH + TYPELESS_LENGTH;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static byte[] toBytes(Transaction baseTransaction) throws TransformationException {
|
public static byte[] toBytes(TransactionData baseTransaction) throws TransformationException {
|
||||||
try {
|
try {
|
||||||
GenesisTransaction transaction = (GenesisTransaction) baseTransaction;
|
GenesisTransactionData transaction = (GenesisTransactionData) baseTransaction;
|
||||||
|
|
||||||
ByteArrayOutputStream bytes = new ByteArrayOutputStream();
|
ByteArrayOutputStream bytes = new ByteArrayOutputStream();
|
||||||
|
|
||||||
bytes.write(Ints.toByteArray(transaction.getType().value));
|
bytes.write(Ints.toByteArray(transaction.getType().value));
|
||||||
bytes.write(Longs.toByteArray(transaction.getTimestamp()));
|
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()));
|
bytes.write(Serialization.serializeBigDecimal(transaction.getAmount()));
|
||||||
|
|
||||||
return bytes.toByteArray();
|
return bytes.toByteArray();
|
||||||
@ -58,13 +57,13 @@ public class GenesisTransactionTransformer extends TransactionTransformer {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@SuppressWarnings("unchecked")
|
@SuppressWarnings("unchecked")
|
||||||
public static JSONObject toJSON(Transaction baseTransaction) throws TransformationException {
|
public static JSONObject toJSON(TransactionData baseTransaction) throws TransformationException {
|
||||||
JSONObject json = TransactionTransformer.getBaseJSON(baseTransaction);
|
JSONObject json = TransactionTransformer.getBaseJSON(baseTransaction);
|
||||||
|
|
||||||
try {
|
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());
|
json.put("amount", transaction.getAmount().toPlainString());
|
||||||
} catch (ClassCastException e) {
|
} catch (ClassCastException e) {
|
||||||
throw new TransformationException(e);
|
throw new TransformationException(e);
|
||||||
|
@ -4,8 +4,8 @@ import java.nio.ByteBuffer;
|
|||||||
|
|
||||||
import org.json.simple.JSONObject;
|
import org.json.simple.JSONObject;
|
||||||
|
|
||||||
import data.transaction.Transaction;
|
import data.transaction.TransactionData;
|
||||||
import data.transaction.Transaction.TransactionType;
|
import qora.transaction.Transaction.TransactionType;
|
||||||
import transform.TransformationException;
|
import transform.TransformationException;
|
||||||
import transform.Transformer;
|
import transform.Transformer;
|
||||||
import utils.Base58;
|
import utils.Base58;
|
||||||
@ -14,7 +14,7 @@ public class TransactionTransformer extends Transformer {
|
|||||||
|
|
||||||
protected static final int TYPE_LENGTH = INT_LENGTH;
|
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)
|
if (bytes == null)
|
||||||
return 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()) {
|
switch (transaction.getType()) {
|
||||||
case GENESIS:
|
case GENESIS:
|
||||||
return GenesisTransactionTransformer.getDataLength(transaction);
|
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()) {
|
switch (transaction.getType()) {
|
||||||
case GENESIS:
|
case GENESIS:
|
||||||
return GenesisTransactionTransformer.toBytes(transaction);
|
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()) {
|
switch (transaction.getType()) {
|
||||||
case GENESIS:
|
case GENESIS:
|
||||||
return GenesisTransactionTransformer.toJSON(transaction);
|
return GenesisTransactionTransformer.toJSON(transaction);
|
||||||
@ -67,7 +67,7 @@ public class TransactionTransformer extends Transformer {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@SuppressWarnings("unchecked")
|
@SuppressWarnings("unchecked")
|
||||||
static JSONObject getBaseJSON(Transaction transaction) {
|
static JSONObject getBaseJSON(TransactionData transaction) {
|
||||||
JSONObject json = new JSONObject();
|
JSONObject json = new JSONObject();
|
||||||
|
|
||||||
json.put("type", transaction.getType().value);
|
json.put("type", transaction.getType().value);
|
||||||
|
@ -6,8 +6,8 @@ import java.math.BigInteger;
|
|||||||
import java.nio.ByteBuffer;
|
import java.nio.ByteBuffer;
|
||||||
|
|
||||||
import qora.account.PublicKeyAccount;
|
import qora.account.PublicKeyAccount;
|
||||||
import qora.transaction.TransactionHandler;
|
|
||||||
import transform.TransformationException;
|
import transform.TransformationException;
|
||||||
|
import transform.Transformer;
|
||||||
|
|
||||||
public class Serialization {
|
public class Serialization {
|
||||||
|
|
||||||
@ -31,15 +31,15 @@ public class Serialization {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public static String deserializeRecipient(ByteBuffer byteBuffer) {
|
public static String deserializeRecipient(ByteBuffer byteBuffer) {
|
||||||
byte[] bytes = new byte[TransactionHandler.RECIPIENT_LENGTH];
|
byte[] bytes = new byte[Transformer.ADDRESS_LENGTH];
|
||||||
byteBuffer.get(bytes);
|
byteBuffer.get(bytes);
|
||||||
return Base58.encode(bytes);
|
return Base58.encode(bytes);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static PublicKeyAccount deserializePublicKey(ByteBuffer byteBuffer) {
|
public static byte[] deserializePublicKey(ByteBuffer byteBuffer) {
|
||||||
byte[] bytes = new byte[TransactionHandler.CREATOR_LENGTH];
|
byte[] bytes = new byte[Transformer.PUBLIC_KEY_LENGTH];
|
||||||
byteBuffer.get(bytes);
|
byteBuffer.get(bytes);
|
||||||
return new PublicKeyAccount(bytes);
|
return bytes;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static String deserializeSizedString(ByteBuffer byteBuffer, int maxSize) throws TransformationException {
|
public static String deserializeSizedString(ByteBuffer byteBuffer, int maxSize) throws TransformationException {
|
||||||
|
Loading…
x
Reference in New Issue
Block a user