forked from Qortal/qortal
WORK IN PROGRESS
Still converting to repository layout. This commit is just in case my dev computer blows up and also for interim code review. Removed data.block.BlockData as an interface (with data.block.Block as implementation) for now.
This commit is contained in:
parent
6c33cfed74
commit
8220113613
@ -1,6 +1,6 @@
|
||||
package data.account;
|
||||
|
||||
public class Account {
|
||||
public class AccountData {
|
||||
|
||||
// Properties
|
||||
protected String address;
|
||||
@ -8,10 +8,10 @@ public class Account {
|
||||
|
||||
// Constructors
|
||||
|
||||
protected Account() {
|
||||
protected AccountData() {
|
||||
}
|
||||
|
||||
public Account(String address) {
|
||||
public AccountData(String address) {
|
||||
this.address = address;
|
||||
}
|
||||
|
||||
@ -33,10 +33,10 @@ public class Account {
|
||||
|
||||
@Override
|
||||
public boolean equals(Object b) {
|
||||
if (!(b instanceof Account))
|
||||
if (!(b instanceof AccountData))
|
||||
return false;
|
||||
|
||||
return this.getAddress().equals(((Account) b).getAddress());
|
||||
return this.getAddress().equals(((AccountData) b).getAddress());
|
||||
}
|
||||
|
||||
@Override
|
@ -1,9 +0,0 @@
|
||||
package data.account;
|
||||
|
||||
public final class GenesisAccount extends PublicKeyAccount {
|
||||
|
||||
public GenesisAccount() {
|
||||
super(new byte[] { 1, 1, 1, 1, 1, 1, 1, 1 });
|
||||
}
|
||||
|
||||
}
|
@ -1,27 +0,0 @@
|
||||
package data.account;
|
||||
|
||||
import qora.crypto.Crypto;
|
||||
|
||||
public class PublicKeyAccount extends Account {
|
||||
|
||||
// Properties
|
||||
protected byte[] publicKey;
|
||||
|
||||
// Constructors
|
||||
|
||||
public PublicKeyAccount(byte[] publicKey) {
|
||||
super(Crypto.toAddress(publicKey));
|
||||
|
||||
this.publicKey = publicKey;
|
||||
}
|
||||
|
||||
protected PublicKeyAccount() {
|
||||
}
|
||||
|
||||
// Getters/Setters
|
||||
|
||||
public byte[] getPublicKey() {
|
||||
return this.publicKey;
|
||||
}
|
||||
|
||||
}
|
@ -1,86 +0,0 @@
|
||||
package data.block;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
|
||||
import qora.account.PublicKeyAccount;
|
||||
|
||||
public class Block implements BlockData {
|
||||
private int version;
|
||||
private byte[] reference;
|
||||
private int transactionCount;
|
||||
private BigDecimal totalFees;
|
||||
private byte[] transactionsSignature;
|
||||
private int height;
|
||||
private long timestamp;
|
||||
private BigDecimal generatingBalance;
|
||||
private byte[] generatorPublicKey;
|
||||
private byte[] generatorSignature;
|
||||
private byte[] atBytes;
|
||||
private BigDecimal atFees;
|
||||
|
||||
public Block(int version, byte[] reference, int transactionCount, BigDecimal totalFees, byte[] transactionsSignature,
|
||||
int height, long timestamp, BigDecimal generatingBalance, byte[] generatorPublicKey, byte[] generatorSignature,
|
||||
byte[] atBytes, BigDecimal atFees)
|
||||
{
|
||||
this.version = version;
|
||||
this.reference = reference;
|
||||
this.transactionCount = transactionCount;
|
||||
this.totalFees = totalFees;
|
||||
this.transactionsSignature = transactionsSignature;
|
||||
this.height = height;
|
||||
this.timestamp = timestamp;
|
||||
this.generatingBalance = generatingBalance;
|
||||
this.generatorPublicKey = generatorPublicKey;
|
||||
this.generatorSignature = generatorSignature;
|
||||
this.atBytes = atBytes;
|
||||
this.atFees = atFees;
|
||||
}
|
||||
|
||||
public int getVersion() {
|
||||
return version;
|
||||
}
|
||||
|
||||
public byte[] getReference() {
|
||||
return reference;
|
||||
}
|
||||
|
||||
public int getTransactionCount() {
|
||||
return transactionCount;
|
||||
}
|
||||
|
||||
public BigDecimal getTotalFees() {
|
||||
return totalFees;
|
||||
}
|
||||
|
||||
public byte[] getTransactionsSignature() {
|
||||
return transactionsSignature;
|
||||
}
|
||||
|
||||
public int getHeight() {
|
||||
return height;
|
||||
}
|
||||
|
||||
public long getTimestamp() {
|
||||
return timestamp;
|
||||
}
|
||||
|
||||
public BigDecimal getGeneratingBalance() {
|
||||
return generatingBalance;
|
||||
}
|
||||
|
||||
public byte[] getGeneratorPublicKey() {
|
||||
return generatorPublicKey;
|
||||
}
|
||||
|
||||
public byte[] getGeneratorSignature() {
|
||||
return generatorSignature;
|
||||
}
|
||||
|
||||
public byte[] getAtBytes() {
|
||||
return atBytes;
|
||||
}
|
||||
|
||||
public BigDecimal getAtFees() {
|
||||
return atFees;
|
||||
}
|
||||
}
|
@ -2,17 +2,119 @@ package data.block;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
|
||||
public interface BlockData {
|
||||
public int getVersion();
|
||||
public byte[] getReference();
|
||||
public int getTransactionCount();
|
||||
public BigDecimal getTotalFees();
|
||||
public byte[] getTransactionsSignature();
|
||||
public int getHeight();
|
||||
public long getTimestamp();
|
||||
public BigDecimal getGeneratingBalance();
|
||||
public byte[] getGeneratorPublicKey();
|
||||
public byte[] getGeneratorSignature();
|
||||
public byte[] getAtBytes();
|
||||
public BigDecimal getAtFees();
|
||||
import com.google.common.primitives.Bytes;
|
||||
|
||||
public class BlockData {
|
||||
|
||||
private byte[] signature;
|
||||
private int version;
|
||||
private byte[] reference;
|
||||
private int transactionCount;
|
||||
private BigDecimal totalFees;
|
||||
private byte[] transactionsSignature;
|
||||
private int height;
|
||||
private long timestamp;
|
||||
private BigDecimal generatingBalance;
|
||||
private byte[] generatorPublicKey;
|
||||
private byte[] generatorSignature;
|
||||
private byte[] atBytes;
|
||||
private BigDecimal atFees;
|
||||
|
||||
public BlockData(int version, byte[] reference, int transactionCount, BigDecimal totalFees, byte[] transactionsSignature, int height, long timestamp,
|
||||
BigDecimal generatingBalance, byte[] generatorPublicKey, byte[] generatorSignature, byte[] atBytes, BigDecimal atFees) {
|
||||
this.version = version;
|
||||
this.reference = reference;
|
||||
this.transactionCount = transactionCount;
|
||||
this.totalFees = totalFees;
|
||||
this.transactionsSignature = transactionsSignature;
|
||||
this.height = height;
|
||||
this.timestamp = timestamp;
|
||||
this.generatingBalance = generatingBalance;
|
||||
this.generatorPublicKey = generatorPublicKey;
|
||||
this.generatorSignature = generatorSignature;
|
||||
this.atBytes = atBytes;
|
||||
this.atFees = atFees;
|
||||
|
||||
if (this.generatorSignature != null && this.transactionsSignature != null)
|
||||
this.signature = Bytes.concat(this.generatorSignature, this.transactionsSignature);
|
||||
else
|
||||
this.signature = null;
|
||||
}
|
||||
|
||||
public int getTransactionCount() {
|
||||
return this.transactionCount;
|
||||
}
|
||||
|
||||
public void setTransactionCount(int transactionCount) {
|
||||
this.transactionCount = transactionCount;
|
||||
}
|
||||
|
||||
public BigDecimal getTotalFees() {
|
||||
return this.totalFees;
|
||||
}
|
||||
|
||||
public void setTotalFees(BigDecimal totalFees) {
|
||||
this.totalFees = totalFees;
|
||||
}
|
||||
|
||||
public byte[] getTransactionsSignature() {
|
||||
return this.transactionsSignature;
|
||||
}
|
||||
|
||||
public void setTransactionsSignature(byte[] transactionsSignature) {
|
||||
this.transactionsSignature = transactionsSignature;
|
||||
}
|
||||
|
||||
public byte[] getSignature() {
|
||||
return this.signature;
|
||||
}
|
||||
|
||||
public int getVersion() {
|
||||
return this.version;
|
||||
}
|
||||
|
||||
public byte[] getReference() {
|
||||
return this.reference;
|
||||
}
|
||||
|
||||
public void setReference(byte[] reference) {
|
||||
this.reference = reference;
|
||||
}
|
||||
|
||||
public int getHeight() {
|
||||
return this.height;
|
||||
}
|
||||
|
||||
public void setHeight(int height) {
|
||||
this.height = height;
|
||||
}
|
||||
|
||||
public long getTimestamp() {
|
||||
return this.timestamp;
|
||||
}
|
||||
|
||||
public BigDecimal getGeneratingBalance() {
|
||||
return this.generatingBalance;
|
||||
}
|
||||
|
||||
public byte[] getGeneratorPublicKey() {
|
||||
return this.generatorPublicKey;
|
||||
}
|
||||
|
||||
public byte[] getGeneratorSignature() {
|
||||
return this.generatorSignature;
|
||||
}
|
||||
|
||||
public void setGeneratorSignature(byte[] generatorSignature) {
|
||||
this.generatorSignature = generatorSignature;
|
||||
}
|
||||
|
||||
public byte[] getAtBytes() {
|
||||
return this.atBytes;
|
||||
}
|
||||
|
||||
public BigDecimal getAtFees() {
|
||||
return this.atFees;
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -1,37 +0,0 @@
|
||||
package data.transaction;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
|
||||
import data.account.Account;
|
||||
import data.account.GenesisAccount;
|
||||
|
||||
public class GenesisTransaction extends Transaction {
|
||||
|
||||
// Properties
|
||||
private Account recipient;
|
||||
private BigDecimal amount;
|
||||
|
||||
// Constructors
|
||||
|
||||
public GenesisTransaction(Account recipient, BigDecimal amount, long timestamp, byte[] signature) {
|
||||
super(TransactionType.GENESIS, BigDecimal.ZERO, new GenesisAccount(), timestamp, signature);
|
||||
|
||||
this.recipient = recipient;
|
||||
this.amount = amount;
|
||||
}
|
||||
|
||||
public GenesisTransaction(Account recipient, BigDecimal amount, long timestamp) {
|
||||
this(recipient, amount, timestamp, null);
|
||||
}
|
||||
|
||||
// Getters/Setters
|
||||
|
||||
public Account getRecipient() {
|
||||
return this.recipient;
|
||||
}
|
||||
|
||||
public BigDecimal getAmount() {
|
||||
return this.amount;
|
||||
}
|
||||
|
||||
}
|
37
src/data/transaction/GenesisTransactionData.java
Normal file
37
src/data/transaction/GenesisTransactionData.java
Normal file
@ -0,0 +1,37 @@
|
||||
package data.transaction;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
|
||||
import qora.account.GenesisAccount;
|
||||
import qora.transaction.Transaction.TransactionType;
|
||||
|
||||
public class GenesisTransactionData extends TransactionData {
|
||||
|
||||
// Properties
|
||||
private String recipient;
|
||||
private BigDecimal amount;
|
||||
|
||||
// Constructors
|
||||
|
||||
public GenesisTransactionData(String recipient, BigDecimal amount, long timestamp, byte[] signature) {
|
||||
super(TransactionType.GENESIS, BigDecimal.ZERO, new GenesisAccount().getPublicKey(), timestamp, signature);
|
||||
|
||||
this.recipient = recipient;
|
||||
this.amount = amount;
|
||||
}
|
||||
|
||||
public GenesisTransactionData(String recipient, BigDecimal amount, long timestamp) {
|
||||
this(recipient, amount, timestamp, null);
|
||||
}
|
||||
|
||||
// Getters/Setters
|
||||
|
||||
public String getRecipient() {
|
||||
return this.recipient;
|
||||
}
|
||||
|
||||
public BigDecimal getAmount() {
|
||||
return this.amount;
|
||||
}
|
||||
|
||||
}
|
@ -1,83 +0,0 @@
|
||||
package data.transaction;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.util.Map;
|
||||
|
||||
import data.account.PublicKeyAccount;
|
||||
|
||||
import static java.util.Arrays.stream;
|
||||
import static java.util.stream.Collectors.toMap;
|
||||
|
||||
public abstract class Transaction {
|
||||
|
||||
// Transaction types
|
||||
// TODO Transaction types are semantic and should go into the business logic layer.
|
||||
// No need to know the meaning of the integer value in data layer
|
||||
public enum TransactionType {
|
||||
GENESIS(1), PAYMENT(2), REGISTER_NAME(3), UPDATE_NAME(4), SELL_NAME(5), CANCEL_SELL_NAME(6), BUY_NAME(7), CREATE_POLL(8), VOTE_ON_POLL(9), ARBITRARY(
|
||||
10), ISSUE_ASSET(11), TRANSFER_ASSET(12), CREATE_ASSET_ORDER(13), CANCEL_ASSET_ORDER(14), MULTIPAYMENT(15), DEPLOY_AT(16), MESSAGE(17);
|
||||
|
||||
public final int value;
|
||||
|
||||
private final static Map<Integer, TransactionType> map = stream(TransactionType.values()).collect(toMap(type -> type.value, type -> type));
|
||||
|
||||
TransactionType(int value) {
|
||||
this.value = value;
|
||||
}
|
||||
|
||||
public static TransactionType valueOf(int value) {
|
||||
return map.get(value);
|
||||
}
|
||||
}
|
||||
|
||||
// Properties shared with all transaction types
|
||||
protected TransactionType type;
|
||||
// TODO PublicKeyAccount is a separate data entity, so here should only be a key to reference it
|
||||
protected PublicKeyAccount creator;
|
||||
protected long timestamp;
|
||||
protected byte[] reference;
|
||||
protected BigDecimal fee;
|
||||
protected byte[] signature;
|
||||
|
||||
// Constructors
|
||||
|
||||
public Transaction(TransactionType type, BigDecimal fee, PublicKeyAccount creator, long timestamp, byte[] reference, byte[] signature) {
|
||||
this.fee = fee;
|
||||
this.type = type;
|
||||
this.creator = creator;
|
||||
this.timestamp = timestamp;
|
||||
this.reference = reference;
|
||||
this.signature = signature;
|
||||
}
|
||||
|
||||
public Transaction(TransactionType type, BigDecimal fee, PublicKeyAccount creator, long timestamp, byte[] reference) {
|
||||
this(type, fee, creator, timestamp, reference, null);
|
||||
}
|
||||
|
||||
// Getters/setters
|
||||
|
||||
public TransactionType getType() {
|
||||
return this.type;
|
||||
}
|
||||
|
||||
public PublicKeyAccount getCreator() {
|
||||
return this.creator;
|
||||
}
|
||||
|
||||
public long getTimestamp() {
|
||||
return this.timestamp;
|
||||
}
|
||||
|
||||
public byte[] getReference() {
|
||||
return this.reference;
|
||||
}
|
||||
|
||||
public BigDecimal getFee() {
|
||||
return this.fee;
|
||||
}
|
||||
|
||||
public byte[] getSignature() {
|
||||
return this.signature;
|
||||
}
|
||||
|
||||
}
|
58
src/data/transaction/TransactionData.java
Normal file
58
src/data/transaction/TransactionData.java
Normal file
@ -0,0 +1,58 @@
|
||||
package data.transaction;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
|
||||
import qora.transaction.Transaction.TransactionType;
|
||||
|
||||
public abstract class TransactionData {
|
||||
|
||||
// Properties shared with all transaction types
|
||||
protected TransactionType type;
|
||||
protected byte[] creatorPublicKey;
|
||||
protected long timestamp;
|
||||
protected byte[] reference;
|
||||
protected BigDecimal fee;
|
||||
protected byte[] signature;
|
||||
|
||||
// Constructors
|
||||
|
||||
public TransactionData(TransactionType type, BigDecimal fee, byte[] creatorPublicKey, long timestamp, byte[] reference, byte[] signature) {
|
||||
this.fee = fee;
|
||||
this.type = type;
|
||||
this.creatorPublicKey = creatorPublicKey;
|
||||
this.timestamp = timestamp;
|
||||
this.reference = reference;
|
||||
this.signature = signature;
|
||||
}
|
||||
|
||||
public TransactionData(TransactionType type, BigDecimal fee, byte[] creatorPublicKey, long timestamp, byte[] reference) {
|
||||
this(type, fee, creatorPublicKey, timestamp, reference, null);
|
||||
}
|
||||
|
||||
// Getters/setters
|
||||
|
||||
public TransactionType getType() {
|
||||
return this.type;
|
||||
}
|
||||
|
||||
public byte[] getCreatorPublicKey() {
|
||||
return this.creatorPublicKey;
|
||||
}
|
||||
|
||||
public long getTimestamp() {
|
||||
return this.timestamp;
|
||||
}
|
||||
|
||||
public byte[] getReference() {
|
||||
return this.reference;
|
||||
}
|
||||
|
||||
public BigDecimal getFee() {
|
||||
return this.fee;
|
||||
}
|
||||
|
||||
public byte[] getSignature() {
|
||||
return this.signature;
|
||||
}
|
||||
|
||||
}
|
@ -8,8 +8,9 @@ public class PublicKeyAccount extends Account {
|
||||
protected byte[] publicKey;
|
||||
|
||||
public PublicKeyAccount(byte[] publicKey) {
|
||||
super(Crypto.toAddress(publicKey));
|
||||
|
||||
this.publicKey = publicKey;
|
||||
this.address = Crypto.toAddress(this.publicKey);
|
||||
}
|
||||
|
||||
protected PublicKeyAccount() {
|
||||
|
@ -6,17 +6,8 @@ import java.sql.SQLException;
|
||||
import database.DB;
|
||||
import database.NoDataFoundException;
|
||||
import qora.account.Account;
|
||||
import qora.transaction.TransactionHandler;
|
||||
import repository.hsqldb.HSQLDBSaver;
|
||||
|
||||
/*
|
||||
* TODO:
|
||||
* Probably need to standardize on using assetId or assetKey for the long value, and plain "asset" for the java object.
|
||||
* Thus in the database the primary key column could be called "asset_id".
|
||||
* In the Order object, we'd pass longs to variables with names like "haveAssetId" and use getters like "getHaveAssetId"
|
||||
* which frees up other method names like "getHaveAsset" to return a java Asset object.
|
||||
*/
|
||||
|
||||
public class Asset {
|
||||
|
||||
public static final long QORA = 0L;
|
||||
@ -91,7 +82,7 @@ public class Asset {
|
||||
this.description = rs.getString(3);
|
||||
this.quantity = rs.getLong(4);
|
||||
this.isDivisible = rs.getBoolean(5);
|
||||
this.reference = DB.getResultSetBytes(rs.getBinaryStream(6), TransactionHandler.REFERENCE_LENGTH);
|
||||
this.reference = DB.getResultSetBytes(rs.getBinaryStream(6));
|
||||
}
|
||||
|
||||
public static Asset fromAssetId(long assetId) throws SQLException {
|
||||
|
@ -22,6 +22,8 @@ import com.google.common.primitives.Bytes;
|
||||
import com.google.common.primitives.Ints;
|
||||
import com.google.common.primitives.Longs;
|
||||
|
||||
import data.block.BlockData;
|
||||
import data.transaction.TransactionData;
|
||||
import database.DB;
|
||||
import database.NoDataFoundException;
|
||||
import qora.account.PrivateKeyAccount;
|
||||
@ -31,10 +33,14 @@ import qora.assets.Order;
|
||||
import qora.assets.Trade;
|
||||
import qora.transaction.CreateOrderTransaction;
|
||||
import qora.transaction.GenesisTransaction;
|
||||
import qora.transaction.TransactionHandler;
|
||||
import qora.transaction.Transaction;
|
||||
import repository.BlockRepository;
|
||||
import repository.DataException;
|
||||
import repository.RepositoryManager;
|
||||
import repository.hsqldb.HSQLDBSaver;
|
||||
import qora.transaction.TransactionHandler;
|
||||
import transform.TransformationException;
|
||||
import transform.block.BlockTransformer;
|
||||
import transform.transaction.TransactionTransformer;
|
||||
import utils.Base58;
|
||||
import utils.NTP;
|
||||
import utils.Serialization;
|
||||
@ -63,51 +69,16 @@ import utils.Serialization;
|
||||
|
||||
public class Block {
|
||||
|
||||
/**
|
||||
* Ordered list of columns when fetching a Block row from database.
|
||||
*/
|
||||
private static final String DB_COLUMNS = "version, reference, transaction_count, total_fees, "
|
||||
+ "transactions_signature, height, generation, generating_balance, generator, generator_signature, AT_data, AT_fees";
|
||||
|
||||
// Database properties
|
||||
protected int version;
|
||||
protected byte[] reference;
|
||||
protected int transactionCount;
|
||||
protected BigDecimal totalFees;
|
||||
protected byte[] transactionsSignature;
|
||||
protected int height;
|
||||
protected long timestamp;
|
||||
protected BigDecimal generatingBalance;
|
||||
protected PublicKeyAccount generator;
|
||||
protected byte[] generatorSignature;
|
||||
protected byte[] atBytes;
|
||||
protected BigDecimal atFees;
|
||||
|
||||
// Properties
|
||||
private BlockData blockData;
|
||||
private PublicKeyAccount generator;
|
||||
|
||||
// Other properties
|
||||
protected List<TransactionHandler> transactions;
|
||||
protected List<Transaction> transactions;
|
||||
protected BigDecimal cachedNextGeneratingBalance;
|
||||
|
||||
// Property lengths for serialisation
|
||||
protected static final int VERSION_LENGTH = 4;
|
||||
protected static final int TRANSACTIONS_SIGNATURE_LENGTH = 64;
|
||||
protected static final int GENERATOR_SIGNATURE_LENGTH = 64;
|
||||
protected static final int REFERENCE_LENGTH = GENERATOR_SIGNATURE_LENGTH + TRANSACTIONS_SIGNATURE_LENGTH;
|
||||
protected static final int TIMESTAMP_LENGTH = 8;
|
||||
protected static final int GENERATING_BALANCE_LENGTH = 8;
|
||||
protected static final int GENERATOR_LENGTH = 32;
|
||||
protected static final int TRANSACTION_COUNT_LENGTH = 4;
|
||||
protected static final int BASE_LENGTH = VERSION_LENGTH + REFERENCE_LENGTH + TIMESTAMP_LENGTH + GENERATING_BALANCE_LENGTH + GENERATOR_LENGTH
|
||||
+ TRANSACTIONS_SIGNATURE_LENGTH + GENERATOR_SIGNATURE_LENGTH + TRANSACTION_COUNT_LENGTH;
|
||||
|
||||
// Other length constants
|
||||
protected static final int BLOCK_SIGNATURE_LENGTH = GENERATOR_SIGNATURE_LENGTH + TRANSACTIONS_SIGNATURE_LENGTH;
|
||||
public static final int MAX_BLOCK_BYTES = 1048576;
|
||||
protected static final int TRANSACTION_SIZE_LENGTH = 4; // per transaction
|
||||
protected static final int AT_BYTES_LENGTH = 4;
|
||||
protected static final int AT_FEES_LENGTH = 8;
|
||||
protected static final int AT_LENGTH = AT_FEES_LENGTH + AT_BYTES_LENGTH;
|
||||
|
||||
// Other useful constants
|
||||
public static final int MAX_BLOCK_BYTES = 1048576;
|
||||
/**
|
||||
* Number of blocks between recalculating block's generating balance.
|
||||
*/
|
||||
@ -125,91 +96,15 @@ public class Block {
|
||||
|
||||
// Constructors
|
||||
|
||||
// For creating a new block from scratch
|
||||
public Block(int version, byte[] reference, long timestamp, BigDecimal generatingBalance, PublicKeyAccount generator, byte[] atBytes, BigDecimal atFees) {
|
||||
this.version = version;
|
||||
this.reference = reference;
|
||||
this.timestamp = timestamp;
|
||||
this.generatingBalance = generatingBalance;
|
||||
this.generator = generator;
|
||||
this.generatorSignature = null;
|
||||
this.height = 0;
|
||||
|
||||
this.transactionCount = 0;
|
||||
this.transactions = new ArrayList<TransactionHandler>();
|
||||
this.transactionsSignature = null;
|
||||
this.totalFees = BigDecimal.ZERO.setScale(8);
|
||||
|
||||
this.atBytes = atBytes;
|
||||
this.atFees = atFees;
|
||||
if (this.atFees != null)
|
||||
this.totalFees = this.totalFees.add(this.atFees);
|
||||
}
|
||||
|
||||
// For instantiating a block that was previously serialized
|
||||
protected Block(int version, byte[] reference, long timestamp, BigDecimal generatingBalance, PublicKeyAccount generator, byte[] generatorSignature,
|
||||
byte[] transactionsSignature, byte[] atBytes, BigDecimal atFees, List<TransactionHandler> transactions) {
|
||||
this(version, reference, timestamp, generatingBalance, generator, atBytes, atFees);
|
||||
|
||||
this.generatorSignature = generatorSignature;
|
||||
|
||||
this.transactionsSignature = transactionsSignature;
|
||||
this.transactionCount = transactions.size();
|
||||
this.transactions = transactions;
|
||||
|
||||
// Add transactions' fees to totalFees
|
||||
for (TransactionHandler transaction : this.transactions)
|
||||
this.totalFees = this.totalFees.add(transaction.getFee());
|
||||
public Block(BlockData blockData) {
|
||||
this.blockData = blockData;
|
||||
this.generator = new PublicKeyAccount(blockData.getGeneratorPublicKey());
|
||||
}
|
||||
|
||||
// Getters/setters
|
||||
|
||||
public int getVersion() {
|
||||
return this.version;
|
||||
}
|
||||
|
||||
public byte[] getReference() {
|
||||
return this.reference;
|
||||
}
|
||||
|
||||
public long getTimestamp() {
|
||||
return this.timestamp;
|
||||
}
|
||||
|
||||
public BigDecimal getGeneratingBalance() {
|
||||
return this.generatingBalance;
|
||||
}
|
||||
|
||||
public PublicKeyAccount getGenerator() {
|
||||
return this.generator;
|
||||
}
|
||||
|
||||
public byte[] getGeneratorSignature() {
|
||||
return this.generatorSignature;
|
||||
}
|
||||
|
||||
public byte[] getTransactionsSignature() {
|
||||
return this.transactionsSignature;
|
||||
}
|
||||
|
||||
public BigDecimal getTotalFees() {
|
||||
return this.totalFees;
|
||||
}
|
||||
|
||||
public int getTransactionCount() {
|
||||
return this.transactionCount;
|
||||
}
|
||||
|
||||
public byte[] getATBytes() {
|
||||
return this.atBytes;
|
||||
}
|
||||
|
||||
public BigDecimal getATFees() {
|
||||
return this.atFees;
|
||||
}
|
||||
|
||||
public int getHeight() {
|
||||
return this.height;
|
||||
public BlockData getBlockData() {
|
||||
return this.blockData;
|
||||
}
|
||||
|
||||
// More information
|
||||
@ -220,26 +115,10 @@ public class Block {
|
||||
* @return byte[], or null if either component signature is null.
|
||||
*/
|
||||
public byte[] getSignature() {
|
||||
if (this.generatorSignature == null || this.transactionsSignature == null)
|
||||
if (this.blockData.getGeneratorSignature() == null || this.blockData.getTransactionsSignature() == null)
|
||||
return null;
|
||||
|
||||
return Bytes.concat(this.generatorSignature, this.transactionsSignature);
|
||||
}
|
||||
|
||||
public int getDataLength() {
|
||||
int blockLength = BASE_LENGTH;
|
||||
|
||||
if (version >= 2 && this.atBytes != null)
|
||||
blockLength += AT_FEES_LENGTH + AT_BYTES_LENGTH + this.atBytes.length;
|
||||
|
||||
// Short cut for no transactions
|
||||
if (this.transactions == null || this.transactions.isEmpty())
|
||||
return blockLength;
|
||||
|
||||
for (TransactionHandler transaction : this.transactions)
|
||||
blockLength += TRANSACTION_SIZE_LENGTH + transaction.getDataLength();
|
||||
|
||||
return blockLength;
|
||||
return Bytes.concat(this.blockData.getGeneratorSignature(), this.blockData.getTransactionsSignature());
|
||||
}
|
||||
|
||||
/**
|
||||
@ -248,9 +127,9 @@ public class Block {
|
||||
* @return 1, 2 or 3
|
||||
*/
|
||||
public int getNextBlockVersion() {
|
||||
if (this.height < AT_BLOCK_HEIGHT_RELEASE)
|
||||
if (this.blockData.getHeight() < AT_BLOCK_HEIGHT_RELEASE)
|
||||
return 1;
|
||||
else if (this.timestamp < POWFIX_RELEASE_TIMESTAMP)
|
||||
else if (this.blockData.getTimestamp() < POWFIX_RELEASE_TIMESTAMP)
|
||||
return 2;
|
||||
else
|
||||
return 3;
|
||||
@ -269,8 +148,8 @@ public class Block {
|
||||
*/
|
||||
public BigDecimal getNextBlockGeneratingBalance() throws SQLException {
|
||||
// This block not at the start of an interval?
|
||||
if (this.height % BLOCK_RETARGET_INTERVAL != 0)
|
||||
return this.generatingBalance;
|
||||
if (this.blockData.getHeight() % BLOCK_RETARGET_INTERVAL != 0)
|
||||
return this.blockData.getGeneratingBalance();
|
||||
|
||||
// Return cached calculation if we have one
|
||||
if (this.cachedNextGeneratingBalance != null)
|
||||
@ -280,23 +159,29 @@ public class Block {
|
||||
|
||||
// Navigate back to first block in previous interval:
|
||||
// XXX: why can't we simply load using block height?
|
||||
Block firstBlock = this;
|
||||
for (int i = 1; firstBlock != null && i < BLOCK_RETARGET_INTERVAL; ++i)
|
||||
firstBlock = firstBlock.getParent();
|
||||
BlockRepository blockRepo = RepositoryManager.getBlockRepository();
|
||||
BlockData firstBlock = this.blockData;
|
||||
|
||||
try {
|
||||
for (int i = 1; firstBlock != null && i < BLOCK_RETARGET_INTERVAL; ++i)
|
||||
firstBlock = blockRepo.fromSignature(firstBlock.getReference());
|
||||
} catch (DataException e) {
|
||||
firstBlock = null;
|
||||
}
|
||||
|
||||
// Couldn't navigate back far enough?
|
||||
if (firstBlock == null)
|
||||
throw new IllegalStateException("Failed to calculate next block's generating balance due to lack of historic blocks");
|
||||
|
||||
// Calculate the actual time period (in ms) over previous interval's blocks.
|
||||
long previousGeneratingTime = this.timestamp - firstBlock.getTimestamp();
|
||||
long previousGeneratingTime = this.blockData.getTimestamp() - firstBlock.getTimestamp();
|
||||
|
||||
// Calculate expected forging time (in ms) for a whole interval based on this block's generating balance.
|
||||
long expectedGeneratingTime = Block.calcForgingDelay(this.generatingBalance) * BLOCK_RETARGET_INTERVAL * 1000;
|
||||
long expectedGeneratingTime = Block.calcForgingDelay(this.blockData.getGeneratingBalance()) * BLOCK_RETARGET_INTERVAL * 1000;
|
||||
|
||||
// Finally, scale generating balance such that faster than expected previous intervals produce larger generating balances.
|
||||
BigDecimal multiplier = BigDecimal.valueOf((double) expectedGeneratingTime / (double) previousGeneratingTime);
|
||||
this.cachedNextGeneratingBalance = BlockChain.minMaxBalance(this.generatingBalance.multiply(multiplier));
|
||||
this.cachedNextGeneratingBalance = BlockChain.minMaxBalance(this.blockData.getGeneratingBalance().multiply(multiplier));
|
||||
|
||||
return this.cachedNextGeneratingBalance;
|
||||
}
|
||||
@ -316,310 +201,31 @@ public class Block {
|
||||
/**
|
||||
* Return block's transactions.
|
||||
* <p>
|
||||
* If the block was loaded from DB then it's possible this method will call the DB to load the transactions if they are not already loaded.
|
||||
* If the block was loaded from repository then it's possible this method will call the repository to load the transactions if they are not already loaded.
|
||||
*
|
||||
* @return
|
||||
* @throws SQLException
|
||||
* @throws DataException
|
||||
*/
|
||||
public List<TransactionHandler> getTransactions() throws SQLException {
|
||||
public List<Transaction> getTransactions() throws DataException {
|
||||
// Already loaded?
|
||||
if (this.transactions != null)
|
||||
return this.transactions;
|
||||
|
||||
// Allocate cache for results
|
||||
this.transactions = new ArrayList<TransactionHandler>();
|
||||
List<TransactionData> transactionsData = RepositoryManager.getBlockRepository().getTransactionsFromSignature(this.blockData.getSignature());
|
||||
|
||||
ResultSet rs = DB.checkedExecute("SELECT transaction_signature FROM BlockTransactions WHERE block_signature = ?", this.getSignature());
|
||||
if (rs == null)
|
||||
return this.transactions; // No transactions in this block
|
||||
|
||||
// NB: do-while loop because DB.checkedExecute() implicitly calls ResultSet.next() for us
|
||||
do {
|
||||
byte[] transactionSignature = DB.getResultSetBytes(rs.getBinaryStream(1), TransactionHandler.SIGNATURE_LENGTH);
|
||||
this.transactions.add(TransactionFactory.fromSignature(transactionSignature));
|
||||
|
||||
// No need to update totalFees as this will be loaded via the Blocks table
|
||||
} while (rs.next());
|
||||
|
||||
// The number of transactions fetched from database should correspond with Block's transactionCount
|
||||
if (this.transactions.size() != this.transactionCount)
|
||||
throw new IllegalStateException("Block's transactions from database do not match block's transaction count");
|
||||
// The number of transactions fetched from repository should correspond with Block's transactionCount
|
||||
if (transactionsData.size() != this.blockData.getTransactionCount())
|
||||
throw new IllegalStateException("Block's transactions from repository do not match block's transaction count");
|
||||
|
||||
this.transactions = new ArrayList<Transaction>();
|
||||
|
||||
for (TransactionData transactionData : transactionsData)
|
||||
this.transactions.add(Transaction.fromData(transactionData));
|
||||
|
||||
return this.transactions;
|
||||
}
|
||||
|
||||
// Load/Save
|
||||
|
||||
protected Block(byte[] signature) throws SQLException {
|
||||
this(DB.checkedExecute("SELECT " + DB_COLUMNS + " FROM Blocks WHERE signature = ?", signature));
|
||||
}
|
||||
|
||||
protected Block(ResultSet rs) throws SQLException {
|
||||
if (rs == null)
|
||||
throw new NoDataFoundException();
|
||||
|
||||
this.version = rs.getInt(1);
|
||||
this.reference = DB.getResultSetBytes(rs.getBinaryStream(2), REFERENCE_LENGTH);
|
||||
this.transactionCount = rs.getInt(3);
|
||||
this.totalFees = rs.getBigDecimal(4);
|
||||
this.transactionsSignature = DB.getResultSetBytes(rs.getBinaryStream(5), TRANSACTIONS_SIGNATURE_LENGTH);
|
||||
this.height = rs.getInt(6);
|
||||
this.timestamp = rs.getTimestamp(7).getTime();
|
||||
this.generatingBalance = rs.getBigDecimal(8);
|
||||
// Note: can't use GENERATOR_LENGTH in case we encounter Genesis Account's short, 8-byte public key
|
||||
this.generator = new PublicKeyAccount(DB.getResultSetBytes(rs.getBinaryStream(9)));
|
||||
this.generatorSignature = DB.getResultSetBytes(rs.getBinaryStream(10), GENERATOR_SIGNATURE_LENGTH);
|
||||
this.atBytes = DB.getResultSetBytes(rs.getBinaryStream(11));
|
||||
this.atFees = rs.getBigDecimal(12);
|
||||
}
|
||||
|
||||
/**
|
||||
* Load Block from DB using block signature.
|
||||
*
|
||||
* @param signature
|
||||
* @return Block, or null if not found
|
||||
* @throws SQLException
|
||||
*/
|
||||
public static Block fromSignature(byte[] signature) throws SQLException {
|
||||
try {
|
||||
return new Block(signature);
|
||||
} catch (NoDataFoundException e) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Load Block from DB using block height
|
||||
*
|
||||
* @param height
|
||||
* @return Block, or null if not found
|
||||
* @throws SQLException
|
||||
*/
|
||||
public static Block fromHeight(int height) throws SQLException {
|
||||
PreparedStatement preparedStatement = DB.getConnection().prepareStatement("SELECT " + DB_COLUMNS + " FROM Blocks WHERE height = ?");
|
||||
preparedStatement.setInt(1, height);
|
||||
|
||||
try {
|
||||
return new Block(DB.checkedExecute(preparedStatement));
|
||||
} catch (NoDataFoundException e) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
protected void save() throws SQLException {
|
||||
HSQLDBSaver saveHelper = new HSQLDBSaver("Blocks");
|
||||
|
||||
saveHelper.bind("signature", this.getSignature()).bind("version", this.version).bind("reference", this.reference)
|
||||
.bind("transaction_count", this.transactionCount).bind("total_fees", this.totalFees).bind("transactions_signature", this.transactionsSignature)
|
||||
.bind("height", this.height).bind("generation", new Timestamp(this.timestamp)).bind("generating_balance", this.generatingBalance)
|
||||
.bind("generator", this.generator.getPublicKey()).bind("generator_signature", this.generatorSignature).bind("AT_data", this.atBytes)
|
||||
.bind("AT_fees", this.atFees);
|
||||
|
||||
saveHelper.execute();
|
||||
}
|
||||
|
||||
// Navigation
|
||||
|
||||
/**
|
||||
* Load parent Block from DB
|
||||
*
|
||||
* @return Block, or null if not found
|
||||
* @throws SQLException
|
||||
*/
|
||||
public Block getParent() throws SQLException {
|
||||
try {
|
||||
return new Block(this.reference);
|
||||
} catch (NoDataFoundException e) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Load child Block from DB
|
||||
*
|
||||
* @return Block, or null if not found
|
||||
* @throws SQLException
|
||||
*/
|
||||
public Block getChild() throws SQLException {
|
||||
byte[] blockSignature = this.getSignature();
|
||||
if (blockSignature == null)
|
||||
return null;
|
||||
|
||||
ResultSet resultSet = DB.checkedExecute("SELECT " + DB_COLUMNS + " FROM Blocks WHERE reference = ?", blockSignature);
|
||||
|
||||
try {
|
||||
return new Block(resultSet);
|
||||
} catch (NoDataFoundException e) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
// Converters
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
public JSONObject toJSON() throws SQLException {
|
||||
JSONObject json = new JSONObject();
|
||||
|
||||
json.put("version", this.version);
|
||||
json.put("timestamp", this.timestamp);
|
||||
json.put("generatingBalance", this.generatingBalance);
|
||||
json.put("generator", this.generator.getAddress());
|
||||
json.put("generatorPublicKey", Base58.encode(this.generator.getPublicKey()));
|
||||
json.put("fee", this.getTotalFees().toPlainString());
|
||||
json.put("transactionsSignature", Base58.encode(this.transactionsSignature));
|
||||
json.put("generatorSignature", Base58.encode(this.generatorSignature));
|
||||
json.put("signature", Base58.encode(this.getSignature()));
|
||||
|
||||
if (this.reference != null)
|
||||
json.put("reference", Base58.encode(this.reference));
|
||||
|
||||
json.put("height", this.getHeight());
|
||||
|
||||
// Add transaction info
|
||||
JSONArray transactionsJson = new JSONArray();
|
||||
boolean tradesHappened = false;
|
||||
|
||||
for (TransactionHandler transaction : this.getTransactions()) {
|
||||
transactionsJson.add(transaction.toJSON());
|
||||
|
||||
// If this is an asset CreateOrderTransaction then check to see if any trades happened
|
||||
if (transaction.getType() == Transaction.TransactionHandler.CREATE_ASSET_ORDER) {
|
||||
CreateOrderTransaction orderTransaction = (CreateOrderTransaction) transaction;
|
||||
Order order = orderTransaction.getOrder();
|
||||
List<Trade> trades = order.getTrades();
|
||||
|
||||
// Filter out trades with timestamps that don't match order transaction's timestamp
|
||||
trades.removeIf((Trade trade) -> trade.getTimestamp() != order.getTimestamp());
|
||||
|
||||
// Any trades left?
|
||||
if (!trades.isEmpty()) {
|
||||
tradesHappened = true;
|
||||
|
||||
// No need to check any further
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
json.put("transactions", transactionsJson);
|
||||
|
||||
// Add asset trade activity flag
|
||||
json.put("assetTrades", tradesHappened);
|
||||
|
||||
// Add CIYAM AT info (if any)
|
||||
if (atBytes != null) {
|
||||
json.put("blockATs", HashCode.fromBytes(atBytes).toString());
|
||||
json.put("atFees", this.atFees);
|
||||
}
|
||||
|
||||
return json;
|
||||
}
|
||||
|
||||
public byte[] toBytes() throws SQLException {
|
||||
try {
|
||||
ByteArrayOutputStream bytes = new ByteArrayOutputStream(getDataLength());
|
||||
bytes.write(Ints.toByteArray(this.version));
|
||||
bytes.write(Longs.toByteArray(this.timestamp));
|
||||
bytes.write(this.reference);
|
||||
// NOTE: generatingBalance serialized as long value, not as BigDecimal, for historic compatibility
|
||||
bytes.write(Longs.toByteArray(this.generatingBalance.longValue()));
|
||||
bytes.write(this.generator.getPublicKey());
|
||||
bytes.write(this.transactionsSignature);
|
||||
bytes.write(this.generatorSignature);
|
||||
|
||||
if (this.version >= 2) {
|
||||
if (this.atBytes != null) {
|
||||
bytes.write(Ints.toByteArray(this.atBytes.length));
|
||||
bytes.write(this.atBytes);
|
||||
// NOTE: atFees serialized as long value, not as BigDecimal, for historic compatibility
|
||||
bytes.write(Longs.toByteArray(this.atFees.longValue()));
|
||||
} else {
|
||||
bytes.write(Ints.toByteArray(0));
|
||||
bytes.write(Longs.toByteArray(0L));
|
||||
}
|
||||
}
|
||||
|
||||
// Transactions
|
||||
bytes.write(Ints.toByteArray(this.transactionCount));
|
||||
|
||||
for (TransactionHandler transaction : this.getTransactions()) {
|
||||
bytes.write(Ints.toByteArray(transaction.getDataLength()));
|
||||
bytes.write(transaction.toBytes());
|
||||
}
|
||||
|
||||
return bytes.toByteArray();
|
||||
} catch (IOException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
public static Block parse(byte[] data) throws TransformationException {
|
||||
if (data == null)
|
||||
return null;
|
||||
|
||||
if (data.length < BASE_LENGTH)
|
||||
throw new TransformationException("Byte data too short for Block");
|
||||
|
||||
ByteBuffer byteBuffer = ByteBuffer.wrap(data);
|
||||
|
||||
int version = byteBuffer.getInt();
|
||||
|
||||
if (version >= 2 && data.length < BASE_LENGTH + AT_LENGTH)
|
||||
throw new TransformationException("Byte data too short for V2+ Block");
|
||||
|
||||
long timestamp = byteBuffer.getLong();
|
||||
|
||||
byte[] reference = new byte[REFERENCE_LENGTH];
|
||||
byteBuffer.get(reference);
|
||||
|
||||
BigDecimal generatingBalance = BigDecimal.valueOf(byteBuffer.getLong()).setScale(8);
|
||||
PublicKeyAccount generator = Serialization.deserializePublicKey(byteBuffer);
|
||||
|
||||
byte[] transactionsSignature = new byte[TRANSACTIONS_SIGNATURE_LENGTH];
|
||||
byteBuffer.get(transactionsSignature);
|
||||
byte[] generatorSignature = new byte[GENERATOR_SIGNATURE_LENGTH];
|
||||
byteBuffer.get(generatorSignature);
|
||||
|
||||
byte[] atBytes = null;
|
||||
BigDecimal atFees = null;
|
||||
if (version >= 2) {
|
||||
int atBytesLength = byteBuffer.getInt();
|
||||
|
||||
if (atBytesLength > MAX_BLOCK_BYTES)
|
||||
throw new TransformationException("Byte data too long for Block's AT info");
|
||||
|
||||
atBytes = new byte[atBytesLength];
|
||||
byteBuffer.get(atBytes);
|
||||
|
||||
atFees = BigDecimal.valueOf(byteBuffer.getLong()).setScale(8);
|
||||
}
|
||||
|
||||
int transactionCount = byteBuffer.getInt();
|
||||
|
||||
// Parse transactions now, compared to deferred parsing in Gen1, so we can throw ParseException if need be
|
||||
List<TransactionHandler> transactions = new ArrayList<TransactionHandler>();
|
||||
for (int t = 0; t < transactionCount; ++t) {
|
||||
if (byteBuffer.remaining() < TRANSACTION_SIZE_LENGTH)
|
||||
throw new TransformationException("Byte data too short for Block Transaction length");
|
||||
|
||||
int transactionLength = byteBuffer.getInt();
|
||||
if (byteBuffer.remaining() < transactionLength)
|
||||
throw new TransformationException("Byte data too short for Block Transaction");
|
||||
if (transactionLength > MAX_BLOCK_BYTES)
|
||||
throw new TransformationException("Byte data too long for Block Transaction");
|
||||
|
||||
byte[] transactionBytes = new byte[transactionLength];
|
||||
byteBuffer.get(transactionBytes);
|
||||
|
||||
TransactionHandler transaction = TransactionHandler.parse(transactionBytes);
|
||||
transactions.add(transaction);
|
||||
}
|
||||
|
||||
if (byteBuffer.hasRemaining())
|
||||
throw new TransformationException("Excess byte data found after parsing Block");
|
||||
|
||||
return new Block(version, reference, timestamp, generatingBalance, generator, generatorSignature, transactionsSignature, atBytes, atFees, transactions);
|
||||
}
|
||||
|
||||
// Processing
|
||||
|
||||
/**
|
||||
@ -629,12 +235,12 @@ public class Block {
|
||||
* <p>
|
||||
* Requires block's {@code generator} being a {@code PrivateKeyAccount} so block's transactions signature can be recalculated.
|
||||
*
|
||||
* @param transaction
|
||||
* @param transactionData
|
||||
* @return true if transaction successfully added to block, false otherwise
|
||||
* @throws IllegalStateException
|
||||
* if block's {@code generator} is not a {@code PrivateKeyAccount}.
|
||||
*/
|
||||
public boolean addTransaction(TransactionHandler transaction) {
|
||||
public boolean addTransaction(TransactionData transactionData) {
|
||||
// Can't add to transactions if we haven't loaded existing ones yet
|
||||
if (this.transactions == null)
|
||||
throw new IllegalStateException("Attempted to add transaction to partially loaded database Block");
|
||||
@ -643,17 +249,21 @@ public class Block {
|
||||
throw new IllegalStateException("Block's generator has no private key");
|
||||
|
||||
// Check there is space in block
|
||||
if (this.getDataLength() + transaction.getDataLength() > MAX_BLOCK_BYTES)
|
||||
try {
|
||||
if (BlockTransformer.getDataLength(this.blockData) + TransactionTransformer.getDataLength(transactionData) > MAX_BLOCK_BYTES)
|
||||
return false;
|
||||
} catch (TransformationException e) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Add to block
|
||||
this.transactions.add(transaction);
|
||||
this.transactions.add(Transaction.fromData(transactionData));
|
||||
|
||||
// Update transaction count
|
||||
this.transactionCount++;
|
||||
this.blockData.setTransactionCount(this.blockData.getTransactionCount() + 1);
|
||||
|
||||
// Update totalFees
|
||||
this.totalFees.add(transaction.getFee());
|
||||
this.blockData.setTotalFees(this.blockData.getTotalFees().add(transactionData.getFee()));
|
||||
|
||||
// Update transactions signature
|
||||
calcTransactionsSignature();
|
||||
@ -668,29 +278,17 @@ public class Block {
|
||||
*
|
||||
* @throws IllegalStateException
|
||||
* if block's {@code generator} is not a {@code PrivateKeyAccount}.
|
||||
* @throws RuntimeException
|
||||
* if somehow the generator signature cannot be calculated
|
||||
*/
|
||||
public void calcGeneratorSignature() {
|
||||
if (!(this.generator instanceof PrivateKeyAccount))
|
||||
throw new IllegalStateException("Block's generator has no private key");
|
||||
|
||||
this.generatorSignature = ((PrivateKeyAccount) this.generator).sign(this.getBytesForGeneratorSignature());
|
||||
}
|
||||
|
||||
private byte[] getBytesForGeneratorSignature() {
|
||||
try {
|
||||
ByteArrayOutputStream bytes = new ByteArrayOutputStream(GENERATOR_SIGNATURE_LENGTH + GENERATING_BALANCE_LENGTH + GENERATOR_LENGTH);
|
||||
|
||||
// Only copy the generator signature from reference, which is the first 64 bytes.
|
||||
bytes.write(Arrays.copyOf(this.reference, GENERATOR_SIGNATURE_LENGTH));
|
||||
|
||||
bytes.write(Longs.toByteArray(this.generatingBalance.longValue()));
|
||||
|
||||
// We're padding here just in case the generator is the genesis account whose public key is only 8 bytes long.
|
||||
bytes.write(Bytes.ensureCapacity(this.generator.getPublicKey(), GENERATOR_LENGTH, 0));
|
||||
|
||||
return bytes.toByteArray();
|
||||
} catch (IOException e) {
|
||||
throw new RuntimeException(e);
|
||||
this.blockData.setGeneratorSignature(((PrivateKeyAccount) this.generator).sign(BlockTransformer.getBytesForGeneratorSignature(this.blockData)));
|
||||
} catch (TransformationException e) {
|
||||
throw new RuntimeException("Unable to calculate block's generator signature", e);
|
||||
}
|
||||
}
|
||||
|
||||
@ -701,41 +299,33 @@ public class Block {
|
||||
*
|
||||
* @throws IllegalStateException
|
||||
* if block's {@code generator} is not a {@code PrivateKeyAccount}.
|
||||
* @throws RuntimeException
|
||||
* if somehow the transactions signature cannot be calculated
|
||||
*/
|
||||
public void calcTransactionsSignature() {
|
||||
if (!(this.generator instanceof PrivateKeyAccount))
|
||||
throw new IllegalStateException("Block's generator has no private key");
|
||||
|
||||
this.transactionsSignature = ((PrivateKeyAccount) this.generator).sign(this.getBytesForTransactionsSignature());
|
||||
}
|
||||
|
||||
private byte[] getBytesForTransactionsSignature() {
|
||||
ByteArrayOutputStream bytes = new ByteArrayOutputStream(GENERATOR_SIGNATURE_LENGTH + this.transactionCount * TransactionHandler.SIGNATURE_LENGTH);
|
||||
|
||||
try {
|
||||
bytes.write(this.generatorSignature);
|
||||
|
||||
for (TransactionHandler transaction : this.getTransactions()) {
|
||||
if (!transaction.isSignatureValid())
|
||||
return null;
|
||||
|
||||
bytes.write(transaction.getSignature());
|
||||
}
|
||||
|
||||
return bytes.toByteArray();
|
||||
} catch (IOException | SQLException e) {
|
||||
throw new RuntimeException(e);
|
||||
this.blockData.setTransactionsSignature(((PrivateKeyAccount) this.generator).sign(BlockTransformer.getBytesForTransactionsSignature(this)));
|
||||
} catch (TransformationException e) {
|
||||
throw new RuntimeException("Unable to calculate block's transactions signature", e);
|
||||
}
|
||||
}
|
||||
|
||||
public boolean isSignatureValid() {
|
||||
// Check generator's signature first
|
||||
if (!this.generator.verify(this.generatorSignature, getBytesForGeneratorSignature()))
|
||||
return false;
|
||||
|
||||
// Check transactions signature
|
||||
if (!this.generator.verify(this.transactionsSignature, getBytesForTransactionsSignature()))
|
||||
public boolean isSignatureValid() {
|
||||
try {
|
||||
// Check generator's signature first
|
||||
if (!this.generator.verify(this.blockData.getGeneratorSignature(), BlockTransformer.getBytesForGeneratorSignature(this.blockData)))
|
||||
return false;
|
||||
|
||||
// Check transactions signature
|
||||
if (!this.generator.verify(this.blockData.getTransactionsSignature(), BlockTransformer.getBytesForTransactionsSignature(this)))
|
||||
return false;
|
||||
} catch (TransformationException e) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
@ -749,41 +339,44 @@ public class Block {
|
||||
*
|
||||
* @return true if block is valid, false otherwise.
|
||||
* @throws SQLException
|
||||
* @throws DataException
|
||||
*/
|
||||
public boolean isValid() throws SQLException {
|
||||
public boolean isValid() throws SQLException, DataException {
|
||||
// TODO
|
||||
|
||||
// Check parent blocks exists
|
||||
if (this.reference == null)
|
||||
if (this.blockData.getReference() == null)
|
||||
return false;
|
||||
|
||||
Block parentBlock = this.getParent();
|
||||
if (parentBlock == null)
|
||||
BlockData parentBlockData = RepositoryManager.getBlockRepository().fromSignature(this.blockData.getReference());
|
||||
if (parentBlockData == null)
|
||||
return false;
|
||||
|
||||
Block parentBlock = new Block(parentBlockData);
|
||||
|
||||
// Check timestamp is valid, i.e. later than parent timestamp and not in the future, within ~500ms margin
|
||||
if (this.timestamp < parentBlock.getTimestamp() || this.timestamp - BLOCK_TIMESTAMP_MARGIN > NTP.getTime())
|
||||
if (this.blockData.getTimestamp() < parentBlockData.getTimestamp() || this.blockData.getTimestamp() - BLOCK_TIMESTAMP_MARGIN > NTP.getTime())
|
||||
return false;
|
||||
|
||||
// Legacy gen1 test: check timestamp ms is the same as parent timestamp ms?
|
||||
if (this.timestamp % 1000 != parentBlock.getTimestamp() % 1000)
|
||||
if (this.blockData.getTimestamp() % 1000 != parentBlockData.getTimestamp() % 1000)
|
||||
return false;
|
||||
|
||||
// Check block version
|
||||
if (this.version != parentBlock.getNextBlockVersion())
|
||||
if (this.blockData.getVersion() != parentBlock.getNextBlockVersion())
|
||||
return false;
|
||||
if (this.version < 2 && (this.atBytes != null || this.atBytes.length > 0 || this.atFees != null || this.atFees.compareTo(BigDecimal.ZERO) > 0))
|
||||
if (this.blockData.getVersion() < 2 && (this.blockData.getAtBytes() != null || this.blockData.getAtFees() != null))
|
||||
return false;
|
||||
|
||||
// Check generating balance
|
||||
if (this.generatingBalance != parentBlock.getNextBlockGeneratingBalance())
|
||||
if (this.blockData.getGeneratingBalance() != parentBlock.getNextBlockGeneratingBalance())
|
||||
return false;
|
||||
|
||||
// Check generator's proof of stake against block's generating balance
|
||||
// TODO
|
||||
|
||||
// Check CIYAM AT
|
||||
if (this.atBytes != null && this.atBytes.length > 0) {
|
||||
if (this.blockData.getAtBytes() != null && this.blockData.getAtBytes().length > 0) {
|
||||
// TODO
|
||||
// try {
|
||||
// AT_Block atBlock = AT_Controller.validateATs(this.getBlockATs(), BlockChain.getHeight() + 1);
|
||||
@ -796,18 +389,18 @@ public class Block {
|
||||
// Check transactions
|
||||
Savepoint savepoint = DB.createSavepoint("BLOCK_TRANSACTIONS");
|
||||
try {
|
||||
for (TransactionHandler transaction : this.getTransactions()) {
|
||||
for (Transaction transaction : this.getTransactions()) {
|
||||
// GenesisTransactions are not allowed (GenesisBlock overrides isValid() to allow them)
|
||||
if (transaction instanceof GenesisTransaction)
|
||||
return false;
|
||||
|
||||
// Check timestamp and deadline
|
||||
if (transaction.getTimestamp() > this.timestamp || transaction.getDeadline() <= this.timestamp)
|
||||
if (transaction.getTransactionData().getTimestamp() > this.blockData.getTimestamp() || transaction.getDeadline() <= this.blockData.getTimestamp())
|
||||
return false;
|
||||
|
||||
// Check transaction is even valid
|
||||
// NOTE: in Gen1 there was an extra block height passed to DeployATTransaction.isValid
|
||||
if (transaction.isValid() != TransactionHandler.ValidationResult.OK)
|
||||
if (transaction.isValid() != Transaction.ValidationResult.OK)
|
||||
return false;
|
||||
|
||||
// Process transaction to make sure other transactions validate properly
|
||||
@ -818,6 +411,8 @@ public class Block {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
} catch (DataException e) {
|
||||
return false;
|
||||
} finally {
|
||||
// Revert back to savepoint
|
||||
try {
|
||||
@ -834,32 +429,32 @@ public class Block {
|
||||
return true;
|
||||
}
|
||||
|
||||
public void process() throws SQLException {
|
||||
public void process() throws DataException, SQLException {
|
||||
// Process transactions (we'll link them to this block after saving the block itself)
|
||||
List<TransactionHandler> transactions = this.getTransactions();
|
||||
for (TransactionHandler transaction : transactions)
|
||||
List<Transaction> transactions = this.getTransactions();
|
||||
for (Transaction transaction : transactions)
|
||||
transaction.process();
|
||||
|
||||
// If fees are non-zero then add fees to generator's balance
|
||||
BigDecimal blockFee = this.getTotalFees();
|
||||
BigDecimal blockFee = this.blockData.getTotalFees();
|
||||
if (blockFee.compareTo(BigDecimal.ZERO) == 1)
|
||||
this.generator.setConfirmedBalance(Asset.QORA, this.generator.getConfirmedBalance(Asset.QORA).add(blockFee));
|
||||
|
||||
// Link block into blockchain by fetching signature of highest block and setting that as our reference
|
||||
int blockchainHeight = BlockChain.getHeight();
|
||||
Block latestBlock = Block.fromHeight(blockchainHeight);
|
||||
if (latestBlock != null)
|
||||
this.reference = latestBlock.getSignature();
|
||||
BlockData latestBlockData = RepositoryManager.getBlockRepository().fromHeight(blockchainHeight);
|
||||
if (latestBlockData != null)
|
||||
this.blockData.setReference(latestBlockData.getSignature());
|
||||
|
||||
this.height = blockchainHeight + 1;
|
||||
this.save();
|
||||
this.blockData.setHeight(blockchainHeight + 1);
|
||||
RepositoryManager.getBlockRepository().save(this.blockData);
|
||||
|
||||
// Link transactions to this block, thus removing them from unconfirmed transactions list.
|
||||
for (int sequence = 0; sequence < transactions.size(); ++sequence) {
|
||||
TransactionHandler transaction = transactions.get(sequence);
|
||||
Transaction transaction = transactions.get(sequence);
|
||||
|
||||
// Link transaction to this block
|
||||
BlockTransaction blockTransaction = new BlockTransaction(this.getSignature(), sequence, transaction.getSignature());
|
||||
BlockTransaction blockTransaction = new BlockTransaction(this.getSignature(), sequence, transaction.getTransactionData().getSignature());
|
||||
blockTransaction.save();
|
||||
}
|
||||
}
|
||||
|
@ -87,14 +87,17 @@ public class BlockChain {
|
||||
* Return highest block height from DB.
|
||||
*
|
||||
* @return height, or 0 if there are no blocks in DB (not very likely).
|
||||
* @throws SQLException
|
||||
*/
|
||||
public static int getHeight() throws SQLException {
|
||||
ResultSet rs = DB.checkedExecute("SELECT MAX(height) FROM Blocks");
|
||||
if (rs == null)
|
||||
return 0;
|
||||
public static int getHeight() {
|
||||
try {
|
||||
ResultSet rs = DB.checkedExecute("SELECT MAX(height) FROM Blocks");
|
||||
if (rs == null)
|
||||
return 0;
|
||||
|
||||
return rs.getInt(1);
|
||||
return rs.getInt(1);
|
||||
} catch (SQLException e) {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -5,9 +5,8 @@ import java.sql.SQLException;
|
||||
|
||||
import database.DB;
|
||||
import database.NoDataFoundException;
|
||||
import qora.transaction.TransactionHandler;
|
||||
import qora.transaction.Transaction;
|
||||
import repository.hsqldb.HSQLDBSaver;
|
||||
import qora.transaction.TransactionHandler;
|
||||
|
||||
public class BlockTransaction {
|
||||
|
||||
@ -50,7 +49,7 @@ public class BlockTransaction {
|
||||
|
||||
this.blockSignature = blockSignature;
|
||||
this.sequence = sequence;
|
||||
this.transactionSignature = DB.getResultSetBytes(rs.getBinaryStream(1), TransactionHandler.SIGNATURE_LENGTH);
|
||||
this.transactionSignature = DB.getResultSetBytes(rs.getBinaryStream(1));
|
||||
}
|
||||
|
||||
protected BlockTransaction(byte[] transactionSignature) throws SQLException {
|
||||
@ -58,7 +57,7 @@ public class BlockTransaction {
|
||||
if (rs == null)
|
||||
throw new NoDataFoundException();
|
||||
|
||||
this.blockSignature = DB.getResultSetBytes(rs.getBinaryStream(1), Block.BLOCK_SIGNATURE_LENGTH);
|
||||
this.blockSignature = DB.getResultSetBytes(rs.getBinaryStream(1));
|
||||
this.sequence = rs.getInt(2);
|
||||
this.transactionSignature = transactionSignature;
|
||||
}
|
||||
@ -118,8 +117,10 @@ public class BlockTransaction {
|
||||
* @return Transaction, or null if not found (which should never happen)
|
||||
* @throws SQLException
|
||||
*/
|
||||
public TransactionHandler getTransaction() throws SQLException {
|
||||
return TransactionFactory.fromSignature(this.transactionSignature);
|
||||
public Transaction getTransaction() throws SQLException {
|
||||
// XXX
|
||||
// return TransactionFactory.fromSignature(this.transactionSignature);
|
||||
return null;
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -1,151 +1,21 @@
|
||||
package qora.transaction;
|
||||
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.math.BigDecimal;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.sql.ResultSet;
|
||||
import java.sql.SQLException;
|
||||
import java.util.Arrays;
|
||||
|
||||
import org.json.simple.JSONObject;
|
||||
|
||||
import com.google.common.primitives.Bytes;
|
||||
import com.google.common.primitives.Ints;
|
||||
import com.google.common.primitives.Longs;
|
||||
|
||||
import database.DB;
|
||||
import database.NoDataFoundException;
|
||||
import qora.account.Account;
|
||||
import qora.account.GenesisAccount;
|
||||
import data.transaction.GenesisTransactionData;
|
||||
import data.transaction.TransactionData;
|
||||
import qora.account.PrivateKeyAccount;
|
||||
import qora.assets.Asset;
|
||||
import qora.crypto.Crypto;
|
||||
import repository.hsqldb.HSQLDBSaver;
|
||||
import transform.TransformationException;
|
||||
import utils.Base58;
|
||||
import utils.Serialization;
|
||||
import transform.transaction.TransactionTransformer;
|
||||
|
||||
public class GenesisTransaction extends TransactionHandler {
|
||||
public class GenesisTransaction extends Transaction {
|
||||
|
||||
// Properties
|
||||
private Account recipient;
|
||||
private BigDecimal amount;
|
||||
|
||||
// Property lengths
|
||||
private static final int RECIPIENT_LENGTH = 25; // raw, not Base58-encoded
|
||||
private static final int AMOUNT_LENGTH = 8;
|
||||
// Note that Genesis transactions don't require reference, fee or signature:
|
||||
private static final int TYPELESS_LENGTH = TIMESTAMP_LENGTH + RECIPIENT_LENGTH + AMOUNT_LENGTH;
|
||||
|
||||
// Constructors
|
||||
|
||||
public GenesisTransaction(String recipient, BigDecimal amount, long timestamp) {
|
||||
super(TransactionType.GENESIS, BigDecimal.ZERO, new GenesisAccount(), timestamp, null, null);
|
||||
|
||||
this.recipient = new Account(recipient);
|
||||
this.amount = amount;
|
||||
this.signature = calcSignature();
|
||||
}
|
||||
|
||||
// Getters/Setters
|
||||
|
||||
public Account getRecipient() {
|
||||
return this.recipient;
|
||||
}
|
||||
|
||||
public BigDecimal getAmount() {
|
||||
return this.amount;
|
||||
}
|
||||
|
||||
// More information
|
||||
|
||||
@Override
|
||||
public int getDataLength() {
|
||||
return TYPE_LENGTH + TYPELESS_LENGTH;
|
||||
}
|
||||
|
||||
// Load/Save
|
||||
|
||||
/**
|
||||
* Load GenesisTransaction from DB using signature.
|
||||
*
|
||||
* @param signature
|
||||
* @throws NoDataFoundException
|
||||
* if no matching row found
|
||||
* @throws SQLException
|
||||
*/
|
||||
protected GenesisTransaction(byte[] signature) throws SQLException {
|
||||
super(TransactionType.GENESIS, signature);
|
||||
|
||||
ResultSet rs = DB.checkedExecute("SELECT recipient, amount FROM GenesisTransactions WHERE signature = ?", signature);
|
||||
if (rs == null)
|
||||
throw new NoDataFoundException();
|
||||
|
||||
this.recipient = new Account(rs.getString(1));
|
||||
this.amount = rs.getBigDecimal(2).setScale(8);
|
||||
}
|
||||
|
||||
/**
|
||||
* Load GenesisTransaction from DB using signature
|
||||
*
|
||||
* @param signature
|
||||
* @return GenesisTransaction, or null if not found
|
||||
* @throws SQLException
|
||||
*/
|
||||
public static GenesisTransaction fromSignature(byte[] signature) throws SQLException {
|
||||
try {
|
||||
return new GenesisTransaction(signature);
|
||||
} catch (NoDataFoundException e) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void save() throws SQLException {
|
||||
super.save();
|
||||
|
||||
HSQLDBSaver saveHelper = new HSQLDBSaver("GenesisTransactions");
|
||||
saveHelper.bind("signature", this.signature).bind("recipient", this.recipient.getAddress()).bind("amount", this.amount);
|
||||
saveHelper.execute();
|
||||
}
|
||||
|
||||
// Converters
|
||||
|
||||
protected static TransactionHandler parse(ByteBuffer byteBuffer) throws TransformationException {
|
||||
if (byteBuffer.remaining() < TYPELESS_LENGTH)
|
||||
throw new TransformationException("Byte data too short for GenesisTransaction");
|
||||
|
||||
long timestamp = byteBuffer.getLong();
|
||||
String recipient = Serialization.deserializeRecipient(byteBuffer);
|
||||
BigDecimal amount = Serialization.deserializeBigDecimal(byteBuffer);
|
||||
|
||||
return new GenesisTransaction(recipient, amount, timestamp);
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
@Override
|
||||
public JSONObject toJSON() throws SQLException {
|
||||
JSONObject json = getBaseJSON();
|
||||
|
||||
json.put("recipient", this.recipient.getAddress());
|
||||
json.put("amount", this.amount.toPlainString());
|
||||
|
||||
return json;
|
||||
}
|
||||
|
||||
@Override
|
||||
public byte[] toBytes() {
|
||||
try {
|
||||
ByteArrayOutputStream bytes = new ByteArrayOutputStream(getDataLength());
|
||||
bytes.write(Ints.toByteArray(this.type.value));
|
||||
bytes.write(Longs.toByteArray(this.timestamp));
|
||||
bytes.write(Base58.decode(this.recipient.getAddress()));
|
||||
bytes.write(Serialization.serializeBigDecimal(this.amount));
|
||||
return bytes.toByteArray();
|
||||
} catch (IOException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
public GenesisTransaction(TransactionData transactionData) {
|
||||
this.transactionData = transactionData;
|
||||
}
|
||||
|
||||
// Processing
|
||||
@ -174,8 +44,12 @@ public class GenesisTransaction extends TransactionHandler {
|
||||
* @return byte[]
|
||||
*/
|
||||
private byte[] calcSignature() {
|
||||
byte[] digest = Crypto.digest(toBytes());
|
||||
return Bytes.concat(digest, digest);
|
||||
try {
|
||||
byte[] digest = Crypto.digest(TransactionTransformer.toBytes(this.transactionData));
|
||||
return Bytes.concat(digest, digest);
|
||||
} catch (TransformationException e) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@ -189,42 +63,50 @@ public class GenesisTransaction extends TransactionHandler {
|
||||
*/
|
||||
@Override
|
||||
public boolean isSignatureValid() {
|
||||
return Arrays.equals(this.signature, calcSignature());
|
||||
return Arrays.equals(this.transactionData.getSignature(), this.calcSignature());
|
||||
}
|
||||
|
||||
@Override
|
||||
public ValidationResult isValid() {
|
||||
GenesisTransactionData genesisTransaction = (GenesisTransactionData) this.transactionData;
|
||||
|
||||
// Check amount is zero or positive
|
||||
if (this.amount.compareTo(BigDecimal.ZERO) == -1)
|
||||
if (genesisTransaction.getAmount().compareTo(BigDecimal.ZERO) == -1)
|
||||
return ValidationResult.NEGATIVE_AMOUNT;
|
||||
|
||||
// Check recipient address is valid
|
||||
if (!Crypto.isValidAddress(this.recipient.getAddress()))
|
||||
if (!Crypto.isValidAddress(genesisTransaction.getRecipient()))
|
||||
return ValidationResult.INVALID_ADDRESS;
|
||||
|
||||
return ValidationResult.OK;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void process() throws SQLException {
|
||||
this.save();
|
||||
public void process() {
|
||||
// TODO
|
||||
// this.save();
|
||||
|
||||
// Set recipient's balance
|
||||
this.recipient.setConfirmedBalance(Asset.QORA, this.amount);
|
||||
// TODO
|
||||
// this.recipient.setConfirmedBalance(Asset.QORA, this.amount);
|
||||
|
||||
// Set recipient's reference
|
||||
recipient.setLastReference(this.signature);
|
||||
// TODO
|
||||
// recipient.setLastReference(this.signature);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void orphan() throws SQLException {
|
||||
this.delete();
|
||||
public void orphan() {
|
||||
// TODO
|
||||
// this.delete();
|
||||
|
||||
// Reset recipient's balance
|
||||
this.recipient.deleteBalance(Asset.QORA);
|
||||
// TODO
|
||||
// this.recipient.deleteBalance(Asset.QORA);
|
||||
|
||||
// Set recipient's reference
|
||||
recipient.setLastReference(null);
|
||||
// TODO
|
||||
// recipient.setLastReference(null);
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -7,25 +7,36 @@ import java.util.Map;
|
||||
import static java.util.Arrays.stream;
|
||||
import static java.util.stream.Collectors.toMap;
|
||||
|
||||
import org.json.simple.JSONObject;
|
||||
|
||||
import data.transaction.Transaction;
|
||||
import database.DB;
|
||||
import database.NoDataFoundException;
|
||||
import data.block.BlockData;
|
||||
import data.transaction.TransactionData;
|
||||
import qora.account.PrivateKeyAccount;
|
||||
import qora.account.PublicKeyAccount;
|
||||
import qora.block.Block;
|
||||
import qora.block.BlockChain;
|
||||
import qora.block.BlockTransaction;
|
||||
import repository.Repository;
|
||||
import repository.RepositoryManager;
|
||||
import settings.Settings;
|
||||
import transform.TransformationException;
|
||||
import transform.Transformer;
|
||||
import transform.transaction.TransactionTransformer;
|
||||
import utils.Base58;
|
||||
|
||||
public abstract class TransactionHandler {
|
||||
public abstract class Transaction {
|
||||
|
||||
// Transaction types
|
||||
public enum TransactionType {
|
||||
GENESIS(1), PAYMENT(2), REGISTER_NAME(3), UPDATE_NAME(4), SELL_NAME(5), CANCEL_SELL_NAME(6), BUY_NAME(7), CREATE_POLL(8), VOTE_ON_POLL(9), ARBITRARY(
|
||||
10), ISSUE_ASSET(11), TRANSFER_ASSET(12), CREATE_ASSET_ORDER(13), CANCEL_ASSET_ORDER(14), MULTIPAYMENT(15), DEPLOY_AT(16), MESSAGE(17);
|
||||
|
||||
public final int value;
|
||||
|
||||
private final static Map<Integer, TransactionType> map = stream(TransactionType.values()).collect(toMap(type -> type.value, type -> type));
|
||||
|
||||
TransactionType(int value) {
|
||||
this.value = value;
|
||||
}
|
||||
|
||||
public static TransactionType valueOf(int value) {
|
||||
return map.get(value);
|
||||
}
|
||||
}
|
||||
|
||||
// Validation results
|
||||
public enum ValidationResult {
|
||||
@ -52,30 +63,40 @@ public abstract class TransactionHandler {
|
||||
protected static final BigDecimal maxBytePerFee = BigDecimal.valueOf(Settings.getInstance().getMaxBytePerFee());
|
||||
protected static final BigDecimal minFeePerByte = BigDecimal.ONE.divide(maxBytePerFee, MathContext.DECIMAL32);
|
||||
|
||||
private Transaction transaction;
|
||||
protected TransactionData transactionData;
|
||||
|
||||
// Constructors
|
||||
|
||||
public TransactionHandler(Transaction transaction) {
|
||||
this.transaction = transaction;
|
||||
public static Transaction fromData(TransactionData transactionData) {
|
||||
switch (transactionData.getType()) {
|
||||
case GENESIS:
|
||||
return new GenesisTransaction(transactionData);
|
||||
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
// Getters / Setters
|
||||
|
||||
public TransactionData getTransactionData() {
|
||||
return this.transactionData;
|
||||
}
|
||||
|
||||
// More information
|
||||
|
||||
|
||||
|
||||
public long getDeadline() {
|
||||
// 24 hour deadline to include transaction in a block
|
||||
return this.transaction.getTimestamp() + (24 * 60 * 60 * 1000);
|
||||
return this.transactionData.getTimestamp() + (24 * 60 * 60 * 1000);
|
||||
}
|
||||
|
||||
public boolean hasMinimumFee() {
|
||||
return this.transaction.getFee().compareTo(MINIMUM_FEE) >= 0;
|
||||
return this.transactionData.getFee().compareTo(MINIMUM_FEE) >= 0;
|
||||
}
|
||||
|
||||
public BigDecimal feePerByte() {
|
||||
try {
|
||||
return this.transaction.getFee().divide(new BigDecimal(TransactionTransformer.getDataLength(this.transaction)), MathContext.DECIMAL32);
|
||||
return this.transactionData.getFee().divide(new BigDecimal(TransactionTransformer.getDataLength(this.transactionData)), MathContext.DECIMAL32);
|
||||
} catch (TransformationException e) {
|
||||
throw new IllegalStateException("Unable to get transaction byte length?");
|
||||
}
|
||||
@ -87,7 +108,7 @@ public abstract class TransactionHandler {
|
||||
|
||||
public BigDecimal calcRecommendedFee() {
|
||||
try {
|
||||
BigDecimal recommendedFee = BigDecimal.valueOf(TransactionTransformer.getDataLength(this.transaction)).divide(maxBytePerFee, MathContext.DECIMAL32).setScale(8);
|
||||
BigDecimal recommendedFee = BigDecimal.valueOf(TransactionTransformer.getDataLength(this.transactionData)).divide(maxBytePerFee, MathContext.DECIMAL32).setScale(8);
|
||||
|
||||
// security margin
|
||||
recommendedFee = recommendedFee.add(new BigDecimal("0.0000001"));
|
||||
@ -118,7 +139,7 @@ public abstract class TransactionHandler {
|
||||
* @return height, or 0 if not in blockchain (i.e. unconfirmed)
|
||||
*/
|
||||
public int getHeight() {
|
||||
return RepositoryManager.getTransactionRepository().getHeight(this.transaction);
|
||||
return RepositoryManager.getTransactionRepository().getHeight(this.transactionData);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -132,6 +153,9 @@ public abstract class TransactionHandler {
|
||||
return 0;
|
||||
|
||||
int blockChainHeight = BlockChain.getHeight();
|
||||
if (blockChainHeight == 0)
|
||||
return 0;
|
||||
|
||||
return blockChainHeight - ourHeight + 1;
|
||||
}
|
||||
|
||||
@ -142,8 +166,8 @@ public abstract class TransactionHandler {
|
||||
*
|
||||
* @return Block, or null if transaction is not in a Block
|
||||
*/
|
||||
public Block getBlock() {
|
||||
return RepositoryManager.getTransactionRepository().toBlock(this.transaction);
|
||||
public BlockData getBlock() {
|
||||
return RepositoryManager.getTransactionRepository().toBlock(this.transactionData);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -151,8 +175,8 @@ public abstract class TransactionHandler {
|
||||
*
|
||||
* @return Transaction, or null if no parent found (which should not happen)
|
||||
*/
|
||||
public Transaction getParent() {
|
||||
byte[] reference = this.transaction.getReference();
|
||||
public TransactionData getParent() {
|
||||
byte[] reference = this.transactionData.getReference();
|
||||
if (reference == null)
|
||||
return null;
|
||||
|
||||
@ -164,8 +188,8 @@ public abstract class TransactionHandler {
|
||||
*
|
||||
* @return Transaction, or null if no child found
|
||||
*/
|
||||
public Transaction getChild() {
|
||||
byte[] signature = this.transaction.getSignature();
|
||||
public TransactionData getChild() {
|
||||
byte[] signature = this.transactionData.getSignature();
|
||||
if (signature == null)
|
||||
return null;
|
||||
|
||||
@ -181,7 +205,7 @@ public abstract class TransactionHandler {
|
||||
*/
|
||||
private byte[] toBytesLessSignature() {
|
||||
try {
|
||||
byte[] bytes = TransactionTransformer.toBytes(this.transaction);
|
||||
byte[] bytes = TransactionTransformer.toBytes(this.transactionData);
|
||||
return Arrays.copyOf(bytes, bytes.length - Transformer.SIGNATURE_LENGTH);
|
||||
} catch (TransformationException e) {
|
||||
// XXX this isn't good
|
||||
@ -196,7 +220,7 @@ public abstract class TransactionHandler {
|
||||
}
|
||||
|
||||
public boolean isSignatureValid() {
|
||||
byte[] signature = this.transaction.getSignature();
|
||||
byte[] signature = this.transactionData.getSignature();
|
||||
if (signature == null)
|
||||
return false;
|
||||
|
@ -1,8 +1,20 @@
|
||||
package repository;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import data.block.BlockData;
|
||||
import data.transaction.TransactionData;
|
||||
|
||||
public interface BlockRepository {
|
||||
BlockData fromSignature(byte[] signature) throws DataException;
|
||||
BlockData fromHeight(int height) throws DataException;
|
||||
|
||||
public BlockData fromSignature(byte[] signature) throws DataException;
|
||||
|
||||
public BlockData fromReference(byte[] reference) throws DataException;
|
||||
|
||||
public BlockData fromHeight(int height) throws DataException;
|
||||
|
||||
public List<TransactionData> getTransactionsFromSignature(byte[] signature) throws DataException;
|
||||
|
||||
public void save(BlockData blockData) throws DataException;
|
||||
|
||||
}
|
||||
|
@ -1,20 +1,20 @@
|
||||
package repository;
|
||||
|
||||
import data.transaction.Transaction;
|
||||
import qora.block.Block;
|
||||
import data.transaction.TransactionData;
|
||||
import data.block.BlockData;
|
||||
|
||||
public interface TransactionRepository {
|
||||
|
||||
public Transaction fromSignature(byte[] signature);
|
||||
public TransactionData fromSignature(byte[] signature);
|
||||
|
||||
public Transaction fromReference(byte[] reference);
|
||||
public TransactionData fromReference(byte[] reference);
|
||||
|
||||
public int getHeight(Transaction transaction);
|
||||
public int getHeight(TransactionData transaction);
|
||||
|
||||
public Block toBlock(Transaction transaction);
|
||||
public BlockData toBlock(TransactionData transaction);
|
||||
|
||||
public void save(Transaction transaction);
|
||||
public void save(TransactionData transaction) throws DataException;
|
||||
|
||||
public void delete(Transaction transaction);
|
||||
public void delete(TransactionData transaction) throws DataException;
|
||||
|
||||
}
|
||||
|
@ -1,20 +1,21 @@
|
||||
package repository.hsqldb;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.sql.PreparedStatement;
|
||||
import java.sql.ResultSet;
|
||||
import java.sql.SQLException;
|
||||
import java.sql.Timestamp;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import data.block.Block;
|
||||
import data.block.BlockData;
|
||||
import data.transaction.TransactionData;
|
||||
import database.DB;
|
||||
import qora.account.PublicKeyAccount;
|
||||
import repository.BlockRepository;
|
||||
import repository.DataException;
|
||||
import repository.RepositoryManager;
|
||||
import repository.TransactionRepository;
|
||||
|
||||
public class HSQLDBBlockRepository implements BlockRepository
|
||||
{
|
||||
public class HSQLDBBlockRepository implements BlockRepository {
|
||||
protected static final int TRANSACTIONS_SIGNATURE_LENGTH = 64;
|
||||
protected static final int GENERATOR_SIGNATURE_LENGTH = 64;
|
||||
protected static final int REFERENCE_LENGTH = GENERATOR_SIGNATURE_LENGTH + TRANSACTIONS_SIGNATURE_LENGTH;
|
||||
@ -22,43 +23,94 @@ public class HSQLDBBlockRepository implements BlockRepository
|
||||
private static final String BLOCK_DB_COLUMNS = "version, reference, transaction_count, total_fees, "
|
||||
+ "transactions_signature, height, generation, generating_balance, generator, generator_signature, AT_data, AT_fees";
|
||||
|
||||
public BlockData fromSignature(byte[] signature) throws DataException
|
||||
{
|
||||
ResultSet rs;
|
||||
public BlockData fromSignature(byte[] signature) throws DataException {
|
||||
try {
|
||||
rs = DB.checkedExecute("SELECT " + BLOCK_DB_COLUMNS + " FROM Blocks WHERE signature = ?", signature);
|
||||
ResultSet rs = DB.checkedExecute("SELECT " + BLOCK_DB_COLUMNS + " FROM Blocks WHERE signature = ?", signature);
|
||||
return getBlockFromResultSet(rs);
|
||||
} catch (SQLException e) {
|
||||
throw new DataException("Error loading data from DB", e);
|
||||
}
|
||||
return getBlockFromResultSet(rs);
|
||||
}
|
||||
|
||||
public BlockData fromHeight(int height) throws DataException
|
||||
{
|
||||
ResultSet rs;
|
||||
public BlockData fromReference(byte[] reference) throws DataException {
|
||||
try {
|
||||
rs = DB.checkedExecute("SELECT " + BLOCK_DB_COLUMNS + " FROM Blocks WHERE height = ?", height);
|
||||
ResultSet rs = DB.checkedExecute("SELECT " + BLOCK_DB_COLUMNS + " FROM Blocks WHERE reference = ?", reference);
|
||||
return getBlockFromResultSet(rs);
|
||||
} catch (SQLException e) {
|
||||
throw new DataException("Error loading data from DB", e);
|
||||
}
|
||||
}
|
||||
|
||||
public BlockData fromHeight(int height) throws DataException {
|
||||
try {
|
||||
ResultSet rs = DB.checkedExecute("SELECT " + BLOCK_DB_COLUMNS + " FROM Blocks WHERE height = ?", height);
|
||||
return getBlockFromResultSet(rs);
|
||||
} catch (SQLException e) {
|
||||
throw new DataException("Error loading data from DB", e);
|
||||
}
|
||||
return getBlockFromResultSet(rs);
|
||||
}
|
||||
|
||||
private BlockData getBlockFromResultSet(ResultSet rs) throws DataException {
|
||||
int version = rs.getInt(1);
|
||||
byte[] reference = DB.getResultSetBytes(rs.getBinaryStream(2), REFERENCE_LENGTH);
|
||||
int transactionCount = rs.getInt(3);
|
||||
BigDecimal totalFees = rs.getBigDecimal(4);
|
||||
byte[] transactionsSignature = DB.getResultSetBytes(rs.getBinaryStream(5), TRANSACTIONS_SIGNATURE_LENGTH);
|
||||
int height = rs.getInt(6);
|
||||
long timestamp = rs.getTimestamp(7).getTime();
|
||||
BigDecimal generatingBalance = rs.getBigDecimal(8);
|
||||
byte[] generatorPublicKey = DB.getResultSetBytes(rs.getBinaryStream(9));
|
||||
byte[] generatorSignature = DB.getResultSetBytes(rs.getBinaryStream(10), GENERATOR_SIGNATURE_LENGTH);
|
||||
byte[] atBytes = DB.getResultSetBytes(rs.getBinaryStream(11));
|
||||
BigDecimal atFees = rs.getBigDecimal(12);
|
||||
if (rs == null)
|
||||
return null;
|
||||
|
||||
return new Block(version, reference, transactionCount, totalFees, transactionsSignature, height, timestamp,
|
||||
generatingBalance,generatorPublicKey, generatorSignature, atBytes, atFees);
|
||||
try {
|
||||
int version = rs.getInt(1);
|
||||
byte[] reference = DB.getResultSetBytes(rs.getBinaryStream(2));
|
||||
int transactionCount = rs.getInt(3);
|
||||
BigDecimal totalFees = rs.getBigDecimal(4);
|
||||
byte[] transactionsSignature = DB.getResultSetBytes(rs.getBinaryStream(5));
|
||||
int height = rs.getInt(6);
|
||||
long timestamp = rs.getTimestamp(7).getTime();
|
||||
BigDecimal generatingBalance = rs.getBigDecimal(8);
|
||||
byte[] generatorPublicKey = DB.getResultSetBytes(rs.getBinaryStream(9));
|
||||
byte[] generatorSignature = DB.getResultSetBytes(rs.getBinaryStream(10));
|
||||
byte[] atBytes = DB.getResultSetBytes(rs.getBinaryStream(11));
|
||||
BigDecimal atFees = rs.getBigDecimal(12);
|
||||
|
||||
return new BlockData(version, reference, transactionCount, totalFees, transactionsSignature, height, timestamp, generatingBalance, generatorPublicKey,
|
||||
generatorSignature, atBytes, atFees);
|
||||
} catch (SQLException e) {
|
||||
throw new DataException(e);
|
||||
}
|
||||
}
|
||||
|
||||
public List<TransactionData> getTransactionsFromSignature(byte[] signature) throws DataException {
|
||||
List<TransactionData> transactions = new ArrayList<TransactionData>();
|
||||
|
||||
try {
|
||||
ResultSet rs = DB.checkedExecute("SELECT transaction_signature FROM BlockTransactions WHERE block_signature = ?", signature);
|
||||
if (rs == null)
|
||||
return transactions; // No transactions in this block
|
||||
|
||||
TransactionRepository transactionRepo = RepositoryManager.getTransactionRepository();
|
||||
|
||||
// NB: do-while loop because DB.checkedExecute() implicitly calls ResultSet.next() for us
|
||||
do {
|
||||
byte[] transactionSignature = DB.getResultSetBytes(rs.getBinaryStream(1));
|
||||
transactions.add(transactionRepo.fromSignature(transactionSignature));
|
||||
} while (rs.next());
|
||||
} catch (SQLException e) {
|
||||
throw new DataException(e);
|
||||
}
|
||||
|
||||
return transactions;
|
||||
}
|
||||
|
||||
public void save(BlockData blockData) throws DataException {
|
||||
HSQLDBSaver saveHelper = new HSQLDBSaver("Blocks");
|
||||
|
||||
saveHelper.bind("signature", blockData.getSignature()).bind("version", blockData.getVersion()).bind("reference", blockData.getReference())
|
||||
.bind("transaction_count", blockData.getTransactionCount()).bind("total_fees", blockData.getTotalFees()).bind("transactions_signature", blockData.getTransactionsSignature())
|
||||
.bind("height", blockData.getHeight()).bind("generation", new Timestamp(blockData.getTimestamp())).bind("generating_balance", blockData.getGeneratingBalance())
|
||||
.bind("generator", blockData.getGeneratorPublicKey()).bind("generator_signature", blockData.getGeneratorSignature()).bind("AT_data", blockData.getAtBytes())
|
||||
.bind("AT_fees", blockData.getAtFees());
|
||||
|
||||
try {
|
||||
saveHelper.execute();
|
||||
} catch (SQLException e) {
|
||||
throw new DataException("Unable to save Block into repository", e);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -4,27 +4,41 @@ import java.math.BigDecimal;
|
||||
import java.sql.ResultSet;
|
||||
import java.sql.SQLException;
|
||||
|
||||
import data.account.Account;
|
||||
import data.account.PublicKeyAccount;
|
||||
import data.transaction.GenesisTransaction;
|
||||
import data.transaction.Transaction;
|
||||
import data.transaction.GenesisTransactionData;
|
||||
import data.transaction.TransactionData;
|
||||
import database.DB;
|
||||
import repository.DataException;
|
||||
|
||||
public class HSQLDBGenesisTransactionRepository extends HSQLDBTransactionRepository {
|
||||
|
||||
Transaction fromBase(byte[] signature, byte[] reference, PublicKeyAccount creator, long timestamp, BigDecimal fee) {
|
||||
TransactionData fromBase(byte[] signature, byte[] reference, byte[] creator, long timestamp, BigDecimal fee) {
|
||||
try {
|
||||
ResultSet rs = DB.checkedExecute("SELECT recipient, amount FROM GenesisTransactions WHERE signature = ?", signature);
|
||||
if (rs == null)
|
||||
return null;
|
||||
|
||||
Account recipient = new Account(rs.getString(1));
|
||||
String recipient = rs.getString(1);
|
||||
BigDecimal amount = rs.getBigDecimal(2).setScale(8);
|
||||
|
||||
return new GenesisTransaction(recipient, amount, timestamp, signature);
|
||||
return new GenesisTransactionData(recipient, amount, timestamp, signature);
|
||||
} catch (SQLException e) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void save(TransactionData transaction) throws DataException {
|
||||
super.save(transaction);
|
||||
|
||||
GenesisTransactionData genesisTransaction = (GenesisTransactionData) transaction;
|
||||
|
||||
HSQLDBSaver saveHelper = new HSQLDBSaver("GenesisTransactions");
|
||||
saveHelper.bind("signature", genesisTransaction.getSignature()).bind("recipient", genesisTransaction.getRecipient()).bind("amount", genesisTransaction.getAmount());
|
||||
try {
|
||||
saveHelper.execute();
|
||||
} catch (SQLException e) {
|
||||
throw new DataException(e);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -5,11 +5,12 @@ import java.sql.ResultSet;
|
||||
import java.sql.SQLException;
|
||||
import java.sql.Timestamp;
|
||||
|
||||
import data.account.PublicKeyAccount;
|
||||
import data.transaction.Transaction;
|
||||
import data.transaction.Transaction.TransactionType;
|
||||
import data.block.BlockData;
|
||||
import data.transaction.TransactionData;
|
||||
import qora.transaction.Transaction.TransactionType;
|
||||
import database.DB;
|
||||
import qora.block.Block;
|
||||
import repository.DataException;
|
||||
import repository.RepositoryManager;
|
||||
import repository.TransactionRepository;
|
||||
|
||||
public class HSQLDBTransactionRepository implements TransactionRepository {
|
||||
@ -20,7 +21,7 @@ public class HSQLDBTransactionRepository implements TransactionRepository {
|
||||
genesisTransactionRepository = new HSQLDBGenesisTransactionRepository();
|
||||
}
|
||||
|
||||
public Transaction fromSignature(byte[] signature) {
|
||||
public TransactionData fromSignature(byte[] signature) {
|
||||
try {
|
||||
ResultSet rs = DB.checkedExecute("SELECT type, reference, creator, creation, fee FROM Transactions WHERE signature = ?", signature);
|
||||
if (rs == null)
|
||||
@ -28,7 +29,7 @@ public class HSQLDBTransactionRepository implements TransactionRepository {
|
||||
|
||||
TransactionType type = TransactionType.valueOf(rs.getInt(1));
|
||||
byte[] reference = DB.getResultSetBytes(rs.getBinaryStream(2));
|
||||
PublicKeyAccount creator = new PublicKeyAccount(DB.getResultSetBytes(rs.getBinaryStream(3)));
|
||||
byte[] creator = DB.getResultSetBytes(rs.getBinaryStream(3));
|
||||
long timestamp = rs.getTimestamp(4).getTime();
|
||||
BigDecimal fee = rs.getBigDecimal(5).setScale(8);
|
||||
|
||||
@ -38,7 +39,7 @@ public class HSQLDBTransactionRepository implements TransactionRepository {
|
||||
}
|
||||
}
|
||||
|
||||
public Transaction fromReference(byte[] reference) {
|
||||
public TransactionData fromReference(byte[] reference) {
|
||||
try {
|
||||
ResultSet rs = DB.checkedExecute("SELECT type, signature, creator, creation, fee FROM Transactions WHERE reference = ?", reference);
|
||||
if (rs == null)
|
||||
@ -46,7 +47,7 @@ public class HSQLDBTransactionRepository implements TransactionRepository {
|
||||
|
||||
TransactionType type = TransactionType.valueOf(rs.getInt(1));
|
||||
byte[] signature = DB.getResultSetBytes(rs.getBinaryStream(2));
|
||||
PublicKeyAccount creator = new PublicKeyAccount(DB.getResultSetBytes(rs.getBinaryStream(3)));
|
||||
byte[] creator = DB.getResultSetBytes(rs.getBinaryStream(3));
|
||||
long timestamp = rs.getTimestamp(4).getTime();
|
||||
BigDecimal fee = rs.getBigDecimal(5).setScale(8);
|
||||
|
||||
@ -56,7 +57,7 @@ public class HSQLDBTransactionRepository implements TransactionRepository {
|
||||
}
|
||||
}
|
||||
|
||||
private Transaction fromBase(TransactionType type, byte[] signature, byte[] reference, PublicKeyAccount creator, long timestamp, BigDecimal fee) {
|
||||
private TransactionData fromBase(TransactionType type, byte[] signature, byte[] reference, byte[] creator, long timestamp, BigDecimal fee) {
|
||||
switch (type) {
|
||||
case GENESIS:
|
||||
return this.genesisTransactionRepository.fromBase(signature, reference, creator, timestamp, fee);
|
||||
@ -67,26 +68,28 @@ public class HSQLDBTransactionRepository implements TransactionRepository {
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getHeight(Transaction transaction) {
|
||||
byte[] signature = transaction.getSignature();
|
||||
public int getHeight(TransactionData transactionData) {
|
||||
byte[] signature = transactionData.getSignature();
|
||||
if (signature == null)
|
||||
return 0;
|
||||
|
||||
// in one go?
|
||||
try {
|
||||
ResultSet rs = DB.checkedExecute("SELECT height from BlockTransactions JOIN Blocks ON Blocks.signature = BlockTransactions.block_signature WHERE transaction_signature = ? LIMIT 1", signature);
|
||||
ResultSet rs = DB.checkedExecute(
|
||||
"SELECT height from BlockTransactions JOIN Blocks ON Blocks.signature = BlockTransactions.block_signature WHERE transaction_signature = ? LIMIT 1",
|
||||
signature);
|
||||
if (rs == null)
|
||||
return 0;
|
||||
|
||||
|
||||
return rs.getInt(1);
|
||||
} catch (SQLException e) {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public Block toBlock(Transaction transaction) {
|
||||
byte[] signature = transaction.getSignature();
|
||||
public BlockData toBlock(TransactionData transactionData) {
|
||||
byte[] signature = transactionData.getSignature();
|
||||
if (signature == null)
|
||||
return null;
|
||||
|
||||
@ -95,37 +98,34 @@ public class HSQLDBTransactionRepository implements TransactionRepository {
|
||||
ResultSet rs = DB.checkedExecute("SELECT block_signature from BlockTransactions WHERE transaction_signature = ? LIMIT 1", signature);
|
||||
if (rs == null)
|
||||
return null;
|
||||
|
||||
|
||||
byte[] blockSignature = DB.getResultSetBytes(rs.getBinaryStream(1));
|
||||
|
||||
// TODO
|
||||
// return RepositoryManager.getBlockRepository().fromSignature(blockSignature);
|
||||
|
||||
return null;
|
||||
} catch (SQLException e) {
|
||||
|
||||
return RepositoryManager.getBlockRepository().fromSignature(blockSignature);
|
||||
} catch (SQLException | DataException e) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void save(Transaction transaction) {
|
||||
public void save(TransactionData transactionData) throws DataException {
|
||||
HSQLDBSaver saver = new HSQLDBSaver("Transactions");
|
||||
saver.bind("signature", transaction.getSignature()).bind("reference", transaction.getReference()).bind("type", transaction.getType().value)
|
||||
.bind("creator", transaction.getCreator().getPublicKey()).bind("creation", new Timestamp(transaction.getTimestamp())).bind("fee", transaction.getFee())
|
||||
saver.bind("signature", transactionData.getSignature()).bind("reference", transactionData.getReference()).bind("type", transactionData.getType().value)
|
||||
.bind("creator", transactionData.getCreatorPublicKey()).bind("creation", new Timestamp(transactionData.getTimestamp())).bind("fee", transactionData.getFee())
|
||||
.bind("milestone_block", null);
|
||||
try {
|
||||
saver.execute();
|
||||
} catch (SQLException e) {
|
||||
// XXX do what?
|
||||
throw new DataException(e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void delete(Transaction transaction) {
|
||||
public void delete(TransactionData transactionData) {
|
||||
// NOTE: The corresponding row in sub-table is deleted automatically by the database thanks to "ON DELETE CASCADE" in the sub-table's FOREIGN KEY
|
||||
// definition.
|
||||
try {
|
||||
DB.checkedExecute("DELETE FROM Transactions WHERE signature = ?", transaction.getSignature());
|
||||
DB.checkedExecute("DELETE FROM Transactions WHERE signature = ?", transactionData.getSignature());
|
||||
} catch (SQLException e) {
|
||||
// XXX do what?
|
||||
}
|
||||
|
@ -8,6 +8,7 @@ public abstract class Transformer {
|
||||
// Raw, not Base58-encoded
|
||||
public static final int ADDRESS_LENGTH = 25;
|
||||
|
||||
public static final int PUBLIC_KEY_LENGTH = 32;
|
||||
public static final int SIGNATURE_LENGTH = 64;
|
||||
public static final int TIMESTAMP_LENGTH = LONG_LENGTH;
|
||||
|
||||
|
284
src/transform/block/BlockTransformer.java
Normal file
284
src/transform/block/BlockTransformer.java
Normal file
@ -0,0 +1,284 @@
|
||||
package transform.block;
|
||||
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.math.BigDecimal;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.sql.SQLException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
|
||||
import org.json.simple.JSONArray;
|
||||
import org.json.simple.JSONObject;
|
||||
|
||||
import com.google.common.hash.HashCode;
|
||||
import com.google.common.primitives.Bytes;
|
||||
import com.google.common.primitives.Ints;
|
||||
import com.google.common.primitives.Longs;
|
||||
|
||||
import data.block.BlockData;
|
||||
import data.transaction.TransactionData;
|
||||
import qora.account.PublicKeyAccount;
|
||||
import qora.block.Block;
|
||||
import qora.transaction.Transaction;
|
||||
import repository.DataException;
|
||||
import transform.TransformationException;
|
||||
import transform.Transformer;
|
||||
import transform.transaction.TransactionTransformer;
|
||||
import utils.Base58;
|
||||
import utils.Serialization;
|
||||
|
||||
public class BlockTransformer extends Transformer {
|
||||
|
||||
private static final int VERSION_LENGTH = INT_LENGTH;
|
||||
private static final int TRANSACTIONS_SIGNATURE_LENGTH = SIGNATURE_LENGTH;
|
||||
private static final int GENERATOR_SIGNATURE_LENGTH = SIGNATURE_LENGTH;
|
||||
private static final int BLOCK_REFERENCE_LENGTH = GENERATOR_SIGNATURE_LENGTH + TRANSACTIONS_SIGNATURE_LENGTH;
|
||||
private static final int TIMESTAMP_LENGTH = LONG_LENGTH;
|
||||
private static final int GENERATING_BALANCE_LENGTH = LONG_LENGTH;
|
||||
private static final int GENERATOR_LENGTH = PUBLIC_KEY_LENGTH;
|
||||
private static final int TRANSACTION_COUNT_LENGTH = INT_LENGTH;
|
||||
|
||||
private static final int BASE_LENGTH = VERSION_LENGTH + BLOCK_REFERENCE_LENGTH + TIMESTAMP_LENGTH + GENERATING_BALANCE_LENGTH + GENERATOR_LENGTH
|
||||
+ TRANSACTIONS_SIGNATURE_LENGTH + GENERATOR_SIGNATURE_LENGTH + TRANSACTION_COUNT_LENGTH;
|
||||
|
||||
protected static final int BLOCK_SIGNATURE_LENGTH = GENERATOR_SIGNATURE_LENGTH + TRANSACTIONS_SIGNATURE_LENGTH;
|
||||
protected static final int TRANSACTION_SIZE_LENGTH = INT_LENGTH; // per transaction
|
||||
protected static final int AT_BYTES_LENGTH = INT_LENGTH;
|
||||
protected static final int AT_FEES_LENGTH = LONG_LENGTH;
|
||||
protected static final int AT_LENGTH = AT_FEES_LENGTH + AT_BYTES_LENGTH;
|
||||
|
||||
public static BlockData fromBytes(byte[] bytes) throws TransformationException {
|
||||
if (bytes == null)
|
||||
return null;
|
||||
|
||||
if (bytes.length < BASE_LENGTH)
|
||||
throw new TransformationException("Byte data too short for Block");
|
||||
|
||||
ByteBuffer byteBuffer = ByteBuffer.wrap(bytes);
|
||||
|
||||
int version = byteBuffer.getInt();
|
||||
|
||||
if (version >= 2 && bytes.length < BASE_LENGTH + AT_LENGTH)
|
||||
throw new TransformationException("Byte data too short for V2+ Block");
|
||||
|
||||
long timestamp = byteBuffer.getLong();
|
||||
|
||||
byte[] reference = new byte[BLOCK_REFERENCE_LENGTH];
|
||||
byteBuffer.get(reference);
|
||||
|
||||
BigDecimal generatingBalance = BigDecimal.valueOf(byteBuffer.getLong()).setScale(8);
|
||||
byte[] generatorPublicKey = Serialization.deserializePublicKey(byteBuffer);
|
||||
|
||||
byte[] transactionsSignature = new byte[TRANSACTIONS_SIGNATURE_LENGTH];
|
||||
byteBuffer.get(transactionsSignature);
|
||||
byte[] generatorSignature = new byte[GENERATOR_SIGNATURE_LENGTH];
|
||||
byteBuffer.get(generatorSignature);
|
||||
|
||||
byte[] atBytes = null;
|
||||
BigDecimal atFees = null;
|
||||
if (version >= 2) {
|
||||
int atBytesLength = byteBuffer.getInt();
|
||||
|
||||
if (atBytesLength > Block.MAX_BLOCK_BYTES)
|
||||
throw new TransformationException("Byte data too long for Block's AT info");
|
||||
|
||||
atBytes = new byte[atBytesLength];
|
||||
byteBuffer.get(atBytes);
|
||||
|
||||
atFees = BigDecimal.valueOf(byteBuffer.getLong()).setScale(8);
|
||||
}
|
||||
|
||||
int transactionCount = byteBuffer.getInt();
|
||||
|
||||
// Parse transactions now, compared to deferred parsing in Gen1, so we can throw ParseException if need be
|
||||
List<TransactionData> transactions = new ArrayList<TransactionData>();
|
||||
for (int t = 0; t < transactionCount; ++t) {
|
||||
if (byteBuffer.remaining() < TRANSACTION_SIZE_LENGTH)
|
||||
throw new TransformationException("Byte data too short for Block Transaction length");
|
||||
|
||||
int transactionLength = byteBuffer.getInt();
|
||||
if (byteBuffer.remaining() < transactionLength)
|
||||
throw new TransformationException("Byte data too short for Block Transaction");
|
||||
if (transactionLength > Block.MAX_BLOCK_BYTES)
|
||||
throw new TransformationException("Byte data too long for Block Transaction");
|
||||
|
||||
byte[] transactionBytes = new byte[transactionLength];
|
||||
byteBuffer.get(transactionBytes);
|
||||
|
||||
TransactionData transaction = TransactionTransformer.fromBytes(transactionBytes);
|
||||
transactions.add(transaction);
|
||||
}
|
||||
|
||||
if (byteBuffer.hasRemaining())
|
||||
throw new TransformationException("Excess byte data found after parsing Block");
|
||||
|
||||
// XXX Can't return a simple BlockData object because it doesn't support holding the transactions
|
||||
// return new BlockData(version, reference, timestamp, generatingBalance, generatorPublicKey, generatorSignature, transactionsSignature, atBytes, atFees, transactions);
|
||||
return null;
|
||||
}
|
||||
|
||||
public static int getDataLength(BlockData blockData) throws TransformationException {
|
||||
// TODO
|
||||
int blockLength = BASE_LENGTH;
|
||||
|
||||
if (blockData.getVersion() >= 2 && blockData.getAtBytes() != null)
|
||||
blockLength += AT_FEES_LENGTH + AT_BYTES_LENGTH + blockData.getAtBytes().length;
|
||||
|
||||
/*
|
||||
* XXX Where do the transactions come from? A param? Do we pass a Block instead of BlockData?
|
||||
// Short cut for no transactions
|
||||
if (block.getTransactions() == null || block.getTransactions().isEmpty())
|
||||
return blockLength;
|
||||
|
||||
for (TransactionData transaction : this.transactions)
|
||||
blockLength += TRANSACTION_SIZE_LENGTH + transaction.getDataLength();
|
||||
*/
|
||||
|
||||
return blockLength;
|
||||
}
|
||||
|
||||
public static byte[] toBytes(BlockData blockData) throws TransformationException {
|
||||
try {
|
||||
ByteArrayOutputStream bytes = new ByteArrayOutputStream(getDataLength(blockData));
|
||||
|
||||
bytes.write(Ints.toByteArray(blockData.getVersion()));
|
||||
bytes.write(Longs.toByteArray(blockData.getTimestamp()));
|
||||
bytes.write(blockData.getReference());
|
||||
// NOTE: generatingBalance serialized as long value, not as BigDecimal, for historic compatibility
|
||||
bytes.write(Longs.toByteArray(blockData.getGeneratingBalance().longValue()));
|
||||
bytes.write(blockData.getGeneratorPublicKey());
|
||||
bytes.write(blockData.getTransactionsSignature());
|
||||
bytes.write(blockData.getGeneratorSignature());
|
||||
|
||||
if (blockData.getVersion() >= 2) {
|
||||
byte[] atBytes = blockData.getAtBytes();
|
||||
|
||||
if (atBytes != null) {
|
||||
bytes.write(Ints.toByteArray(atBytes.length));
|
||||
bytes.write(atBytes);
|
||||
// NOTE: atFees serialized as long value, not as BigDecimal, for historic compatibility
|
||||
bytes.write(Longs.toByteArray(blockData.getAtFees().longValue()));
|
||||
} else {
|
||||
bytes.write(Ints.toByteArray(0));
|
||||
bytes.write(Longs.toByteArray(0L));
|
||||
}
|
||||
}
|
||||
|
||||
// Transactions
|
||||
bytes.write(Ints.toByteArray(blockData.getTransactionCount()));
|
||||
|
||||
/*
|
||||
* XXX Where do the transactions come from? A param? Do we pass a Block instead of BlockData?
|
||||
for (TransactionData transaction : blockData.getTransactions()) {
|
||||
bytes.write(Ints.toByteArray(transaction.getDataLength()));
|
||||
bytes.write(transaction.toBytes());
|
||||
}
|
||||
*/
|
||||
|
||||
return bytes.toByteArray();
|
||||
} catch (IOException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
public static JSONObject toJSON(BlockData blockData) throws TransformationException {
|
||||
JSONObject json = new JSONObject();
|
||||
|
||||
json.put("version", blockData.getVersion());
|
||||
json.put("timestamp", blockData.getTimestamp());
|
||||
json.put("generatingBalance", blockData.getGeneratingBalance());
|
||||
json.put("generator", new PublicKeyAccount(blockData.getGeneratorPublicKey()).getAddress());
|
||||
json.put("generatorPublicKey", Base58.encode(blockData.getGeneratorPublicKey()));
|
||||
json.put("fee", blockData.getTotalFees().toPlainString());
|
||||
json.put("transactionsSignature", Base58.encode(blockData.getTransactionsSignature()));
|
||||
json.put("generatorSignature", Base58.encode(blockData.getGeneratorSignature()));
|
||||
json.put("signature", Base58.encode(blockData.getSignature()));
|
||||
|
||||
if (blockData.getReference() != null)
|
||||
json.put("reference", Base58.encode(blockData.getReference()));
|
||||
|
||||
json.put("height", blockData.getHeight());
|
||||
|
||||
// Add transaction info
|
||||
JSONArray transactionsJson = new JSONArray();
|
||||
boolean tradesHappened = false;
|
||||
|
||||
/*
|
||||
* XXX Where do the transactions come from? A param? Do we pass a Block instead of BlockData?
|
||||
for (TransactionData transaction : blockData.getTransactions()) {
|
||||
transactionsJson.add(transaction.toJSON());
|
||||
|
||||
// If this is an asset CreateOrderTransaction then check to see if any trades happened
|
||||
if (transaction.getType() == Transaction.TransactionType.CREATE_ASSET_ORDER) {
|
||||
CreateOrderTransaction orderTransaction = (CreateOrderTransaction) transaction;
|
||||
Order order = orderTransaction.getOrder();
|
||||
List<Trade> trades = order.getTrades();
|
||||
|
||||
// Filter out trades with timestamps that don't match order transaction's timestamp
|
||||
trades.removeIf((Trade trade) -> trade.getTimestamp() != order.getTimestamp());
|
||||
|
||||
// Any trades left?
|
||||
if (!trades.isEmpty()) {
|
||||
tradesHappened = true;
|
||||
|
||||
// No need to check any further
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
json.put("transactions", transactionsJson);
|
||||
*/
|
||||
|
||||
// Add asset trade activity flag
|
||||
json.put("assetTrades", tradesHappened);
|
||||
|
||||
// Add CIYAM AT info (if any)
|
||||
if (blockData.getAtBytes() != null) {
|
||||
json.put("blockATs", HashCode.fromBytes(blockData.getAtBytes()).toString());
|
||||
json.put("atFees", blockData.getAtFees());
|
||||
}
|
||||
|
||||
return json;
|
||||
}
|
||||
|
||||
public static byte[] getBytesForGeneratorSignature(BlockData blockData) throws TransformationException {
|
||||
try {
|
||||
ByteArrayOutputStream bytes = new ByteArrayOutputStream(GENERATOR_SIGNATURE_LENGTH + GENERATING_BALANCE_LENGTH + GENERATOR_LENGTH);
|
||||
|
||||
// Only copy the generator signature from reference, which is the first 64 bytes.
|
||||
bytes.write(Arrays.copyOf(blockData.getReference(), GENERATOR_SIGNATURE_LENGTH));
|
||||
|
||||
bytes.write(Longs.toByteArray(blockData.getGeneratingBalance().longValue()));
|
||||
|
||||
// We're padding here just in case the generator is the genesis account whose public key is only 8 bytes long.
|
||||
bytes.write(Bytes.ensureCapacity(blockData.getGeneratorPublicKey(), GENERATOR_LENGTH, 0));
|
||||
|
||||
return bytes.toByteArray();
|
||||
} catch (IOException e) {
|
||||
throw new TransformationException(e);
|
||||
}
|
||||
}
|
||||
|
||||
public static byte[] getBytesForTransactionsSignature(Block block) throws TransformationException {
|
||||
ByteArrayOutputStream bytes = new ByteArrayOutputStream(GENERATOR_SIGNATURE_LENGTH + block.getBlockData().getTransactionCount() * TransactionTransformer.SIGNATURE_LENGTH);
|
||||
|
||||
try {
|
||||
bytes.write(block.getBlockData().getGeneratorSignature());
|
||||
|
||||
for (Transaction transaction : block.getTransactions()) {
|
||||
if (!transaction.isSignatureValid())
|
||||
return null;
|
||||
|
||||
bytes.write(transaction.getTransactionData().getSignature());
|
||||
}
|
||||
|
||||
return bytes.toByteArray();
|
||||
} catch (IOException | DataException e) {
|
||||
throw new TransformationException(e);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@ -10,9 +10,8 @@ import org.json.simple.JSONObject;
|
||||
import com.google.common.primitives.Ints;
|
||||
import com.google.common.primitives.Longs;
|
||||
|
||||
import data.transaction.Transaction;
|
||||
import data.account.Account;
|
||||
import data.transaction.GenesisTransaction;
|
||||
import data.transaction.TransactionData;
|
||||
import data.transaction.GenesisTransactionData;
|
||||
import transform.TransformationException;
|
||||
import utils.Base58;
|
||||
import utils.Serialization;
|
||||
@ -25,30 +24,30 @@ public class GenesisTransactionTransformer extends TransactionTransformer {
|
||||
// Note that Genesis transactions don't require reference, fee or signature:
|
||||
private static final int TYPELESS_LENGTH = TIMESTAMP_LENGTH + RECIPIENT_LENGTH + AMOUNT_LENGTH;
|
||||
|
||||
static Transaction fromByteBuffer(ByteBuffer byteBuffer) throws TransformationException {
|
||||
static TransactionData fromByteBuffer(ByteBuffer byteBuffer) throws TransformationException {
|
||||
if (byteBuffer.remaining() < TYPELESS_LENGTH)
|
||||
throw new TransformationException("Byte data too short for GenesisTransaction");
|
||||
|
||||
long timestamp = byteBuffer.getLong();
|
||||
Account recipient = new Account(Serialization.deserializeRecipient(byteBuffer));
|
||||
String recipient = Serialization.deserializeRecipient(byteBuffer);
|
||||
BigDecimal amount = Serialization.deserializeBigDecimal(byteBuffer);
|
||||
|
||||
return new GenesisTransaction(recipient, amount, timestamp);
|
||||
return new GenesisTransactionData(recipient, amount, timestamp);
|
||||
}
|
||||
|
||||
public static int getDataLength(Transaction baseTransaction) throws TransformationException {
|
||||
public static int getDataLength(TransactionData baseTransaction) throws TransformationException {
|
||||
return TYPE_LENGTH + TYPELESS_LENGTH;
|
||||
}
|
||||
|
||||
public static byte[] toBytes(Transaction baseTransaction) throws TransformationException {
|
||||
public static byte[] toBytes(TransactionData baseTransaction) throws TransformationException {
|
||||
try {
|
||||
GenesisTransaction transaction = (GenesisTransaction) baseTransaction;
|
||||
GenesisTransactionData transaction = (GenesisTransactionData) baseTransaction;
|
||||
|
||||
ByteArrayOutputStream bytes = new ByteArrayOutputStream();
|
||||
|
||||
bytes.write(Ints.toByteArray(transaction.getType().value));
|
||||
bytes.write(Longs.toByteArray(transaction.getTimestamp()));
|
||||
bytes.write(Base58.decode(transaction.getRecipient().getAddress()));
|
||||
bytes.write(Base58.decode(transaction.getRecipient()));
|
||||
bytes.write(Serialization.serializeBigDecimal(transaction.getAmount()));
|
||||
|
||||
return bytes.toByteArray();
|
||||
@ -58,13 +57,13 @@ public class GenesisTransactionTransformer extends TransactionTransformer {
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
public static JSONObject toJSON(Transaction baseTransaction) throws TransformationException {
|
||||
public static JSONObject toJSON(TransactionData baseTransaction) throws TransformationException {
|
||||
JSONObject json = TransactionTransformer.getBaseJSON(baseTransaction);
|
||||
|
||||
try {
|
||||
GenesisTransaction transaction = (GenesisTransaction) baseTransaction;
|
||||
GenesisTransactionData transaction = (GenesisTransactionData) baseTransaction;
|
||||
|
||||
json.put("recipient", transaction.getRecipient().getAddress());
|
||||
json.put("recipient", transaction.getRecipient());
|
||||
json.put("amount", transaction.getAmount().toPlainString());
|
||||
} catch (ClassCastException e) {
|
||||
throw new TransformationException(e);
|
||||
|
@ -4,8 +4,8 @@ import java.nio.ByteBuffer;
|
||||
|
||||
import org.json.simple.JSONObject;
|
||||
|
||||
import data.transaction.Transaction;
|
||||
import data.transaction.Transaction.TransactionType;
|
||||
import data.transaction.TransactionData;
|
||||
import qora.transaction.Transaction.TransactionType;
|
||||
import transform.TransformationException;
|
||||
import transform.Transformer;
|
||||
import utils.Base58;
|
||||
@ -14,7 +14,7 @@ public class TransactionTransformer extends Transformer {
|
||||
|
||||
protected static final int TYPE_LENGTH = INT_LENGTH;
|
||||
|
||||
public static Transaction fromBytes(byte[] bytes) throws TransformationException {
|
||||
public static TransactionData fromBytes(byte[] bytes) throws TransformationException {
|
||||
if (bytes == null)
|
||||
return null;
|
||||
|
||||
@ -36,7 +36,7 @@ public class TransactionTransformer extends Transformer {
|
||||
}
|
||||
}
|
||||
|
||||
public static int getDataLength(Transaction transaction) throws TransformationException {
|
||||
public static int getDataLength(TransactionData transaction) throws TransformationException {
|
||||
switch (transaction.getType()) {
|
||||
case GENESIS:
|
||||
return GenesisTransactionTransformer.getDataLength(transaction);
|
||||
@ -46,7 +46,7 @@ public class TransactionTransformer extends Transformer {
|
||||
}
|
||||
}
|
||||
|
||||
public static byte[] toBytes(Transaction transaction) throws TransformationException {
|
||||
public static byte[] toBytes(TransactionData transaction) throws TransformationException {
|
||||
switch (transaction.getType()) {
|
||||
case GENESIS:
|
||||
return GenesisTransactionTransformer.toBytes(transaction);
|
||||
@ -56,7 +56,7 @@ public class TransactionTransformer extends Transformer {
|
||||
}
|
||||
}
|
||||
|
||||
public static JSONObject toJSON(Transaction transaction) throws TransformationException {
|
||||
public static JSONObject toJSON(TransactionData transaction) throws TransformationException {
|
||||
switch (transaction.getType()) {
|
||||
case GENESIS:
|
||||
return GenesisTransactionTransformer.toJSON(transaction);
|
||||
@ -67,7 +67,7 @@ public class TransactionTransformer extends Transformer {
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
static JSONObject getBaseJSON(Transaction transaction) {
|
||||
static JSONObject getBaseJSON(TransactionData transaction) {
|
||||
JSONObject json = new JSONObject();
|
||||
|
||||
json.put("type", transaction.getType().value);
|
||||
|
@ -6,8 +6,8 @@ import java.math.BigInteger;
|
||||
import java.nio.ByteBuffer;
|
||||
|
||||
import qora.account.PublicKeyAccount;
|
||||
import qora.transaction.TransactionHandler;
|
||||
import transform.TransformationException;
|
||||
import transform.Transformer;
|
||||
|
||||
public class Serialization {
|
||||
|
||||
@ -31,15 +31,15 @@ public class Serialization {
|
||||
}
|
||||
|
||||
public static String deserializeRecipient(ByteBuffer byteBuffer) {
|
||||
byte[] bytes = new byte[TransactionHandler.RECIPIENT_LENGTH];
|
||||
byte[] bytes = new byte[Transformer.ADDRESS_LENGTH];
|
||||
byteBuffer.get(bytes);
|
||||
return Base58.encode(bytes);
|
||||
}
|
||||
|
||||
public static PublicKeyAccount deserializePublicKey(ByteBuffer byteBuffer) {
|
||||
byte[] bytes = new byte[TransactionHandler.CREATOR_LENGTH];
|
||||
public static byte[] deserializePublicKey(ByteBuffer byteBuffer) {
|
||||
byte[] bytes = new byte[Transformer.PUBLIC_KEY_LENGTH];
|
||||
byteBuffer.get(bytes);
|
||||
return new PublicKeyAccount(bytes);
|
||||
return bytes;
|
||||
}
|
||||
|
||||
public static String deserializeSizedString(ByteBuffer byteBuffer, int maxSize) throws TransformationException {
|
||||
|
Loading…
Reference in New Issue
Block a user