forked from Qortal/qortal
Merge branch 'master' into master
This commit is contained in:
commit
24ae771867
Binary file not shown.
@ -1 +1 @@
|
||||
1d6f5d634a2c4e570a5a8af260a51653
|
||||
ab1560171ae5c6c15b0dfa8e6cccc7f8
|
@ -1 +1 @@
|
||||
c6387380bc5db1f0a98ecbb480b17bd89b564401
|
||||
c293c9656f43b432a08053f19ec5aa0de1cd10ea
|
12
lib/org/ciyam/at/maven-metadata-local.xml
Normal file
12
lib/org/ciyam/at/maven-metadata-local.xml
Normal file
@ -0,0 +1,12 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<metadata>
|
||||
<groupId>org.ciyam</groupId>
|
||||
<artifactId>at</artifactId>
|
||||
<versioning>
|
||||
<release>1.0</release>
|
||||
<versions>
|
||||
<version>1.0</version>
|
||||
</versions>
|
||||
<lastUpdated>20181015085522</lastUpdated>
|
||||
</versioning>
|
||||
</metadata>
|
@ -7,6 +7,6 @@
|
||||
<versions>
|
||||
<version>1.0</version>
|
||||
</versions>
|
||||
<lastUpdated>20181003154752</lastUpdated>
|
||||
<lastUpdated>20181015081124</lastUpdated>
|
||||
</versioning>
|
||||
</metadata>
|
||||
|
@ -1 +1 @@
|
||||
bc81bc1f9b74a4eececd5dd8b29e47d8
|
||||
2369bf36c52580a89d5ea71a0f037a82
|
@ -1 +1 @@
|
||||
feefde4343bda4d6e13159e5c01f8b4f8963a1bc
|
||||
6bc38899b93ffce2286ae26f7af0b2d8b69db3cf
|
@ -6,6 +6,8 @@ public class ATData {
|
||||
|
||||
// Properties
|
||||
private String ATAddress;
|
||||
private String creator;
|
||||
private long creation;
|
||||
private int version;
|
||||
private byte[] codeBytes;
|
||||
private boolean isSleeping;
|
||||
@ -14,13 +16,14 @@ public class ATData {
|
||||
private boolean hadFatalError;
|
||||
private boolean isFrozen;
|
||||
private BigDecimal frozenBalance;
|
||||
private byte[] deploySignature;
|
||||
|
||||
// Constructors
|
||||
|
||||
public ATData(String ATAddress, int version, byte[] codeBytes, boolean isSleeping, Integer sleepUntilHeight, boolean isFinished, boolean hadFatalError,
|
||||
boolean isFrozen, BigDecimal frozenBalance, byte[] deploySignature) {
|
||||
public ATData(String ATAddress, String creator, long creation, int version, byte[] codeBytes, boolean isSleeping, Integer sleepUntilHeight,
|
||||
boolean isFinished, boolean hadFatalError, boolean isFrozen, BigDecimal frozenBalance) {
|
||||
this.ATAddress = ATAddress;
|
||||
this.creator = creator;
|
||||
this.creation = creation;
|
||||
this.version = version;
|
||||
this.codeBytes = codeBytes;
|
||||
this.isSleeping = isSleeping;
|
||||
@ -29,12 +32,11 @@ public class ATData {
|
||||
this.hadFatalError = hadFatalError;
|
||||
this.isFrozen = isFrozen;
|
||||
this.frozenBalance = frozenBalance;
|
||||
this.deploySignature = deploySignature;
|
||||
}
|
||||
|
||||
public ATData(String ATAddress, int version, byte[] codeBytes, boolean isSleeping, Integer sleepUntilHeight, boolean isFinished, boolean hadFatalError,
|
||||
boolean isFrozen, Long frozenBalance, byte[] deploySignature) {
|
||||
this(ATAddress, version, codeBytes, isSleeping, sleepUntilHeight, isFinished, hadFatalError, isFrozen, (BigDecimal) null, deploySignature);
|
||||
public ATData(String ATAddress, String creator, long creation, int version, byte[] codeBytes, boolean isSleeping, Integer sleepUntilHeight,
|
||||
boolean isFinished, boolean hadFatalError, boolean isFrozen, Long frozenBalance) {
|
||||
this(ATAddress, creator, creation, version, codeBytes, isSleeping, sleepUntilHeight, isFinished, hadFatalError, isFrozen, (BigDecimal) null);
|
||||
|
||||
// Convert Long frozenBalance to BigDecimal
|
||||
if (frozenBalance != null)
|
||||
@ -47,6 +49,14 @@ public class ATData {
|
||||
return this.ATAddress;
|
||||
}
|
||||
|
||||
public String getCreator() {
|
||||
return this.creator;
|
||||
}
|
||||
|
||||
public long getCreation() {
|
||||
return this.creation;
|
||||
}
|
||||
|
||||
public int getVersion() {
|
||||
return this.version;
|
||||
}
|
||||
@ -103,8 +113,4 @@ public class ATData {
|
||||
this.frozenBalance = frozenBalance;
|
||||
}
|
||||
|
||||
public byte[] getDeploySignature() {
|
||||
return this.deploySignature;
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -1,18 +1,42 @@
|
||||
package data.at;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
|
||||
public class ATStateData {
|
||||
|
||||
// Properties
|
||||
private String ATAddress;
|
||||
private int height;
|
||||
private Integer height;
|
||||
private Long creation;
|
||||
private byte[] stateData;
|
||||
private byte[] stateHash;
|
||||
private BigDecimal fees;
|
||||
|
||||
// Constructors
|
||||
|
||||
public ATStateData(String ATAddress, int height, byte[] stateData) {
|
||||
/** Create new ATStateData */
|
||||
public ATStateData(String ATAddress, Integer height, Long creation, byte[] stateData, byte[] stateHash, BigDecimal fees) {
|
||||
this.ATAddress = ATAddress;
|
||||
this.height = height;
|
||||
this.creation = creation;
|
||||
this.stateData = stateData;
|
||||
this.stateHash = stateHash;
|
||||
this.fees = fees;
|
||||
}
|
||||
|
||||
/** For recreating per-block ATStateData from repository where not all info is needed */
|
||||
public ATStateData(String ATAddress, int height, byte[] stateHash, BigDecimal fees) {
|
||||
this(ATAddress, height, null, null, stateHash, fees);
|
||||
}
|
||||
|
||||
/** For creating ATStateData from serialized bytes when we don't have all the info */
|
||||
public ATStateData(String ATAddress, byte[] stateHash) {
|
||||
this(ATAddress, null, null, null, stateHash, null);
|
||||
}
|
||||
|
||||
/** For creating ATStateData from serialized bytes when we don't have all the info */
|
||||
public ATStateData(String ATAddress, byte[] stateHash, BigDecimal fees) {
|
||||
this(ATAddress, null, null, null, stateHash, fees);
|
||||
}
|
||||
|
||||
// Getters / setters
|
||||
@ -21,12 +45,29 @@ public class ATStateData {
|
||||
return this.ATAddress;
|
||||
}
|
||||
|
||||
public int getHeight() {
|
||||
public Integer getHeight() {
|
||||
return this.height;
|
||||
}
|
||||
|
||||
// Likely to be used when block received over network is attached to blockchain
|
||||
public void setHeight(Integer height) {
|
||||
this.height = height;
|
||||
}
|
||||
|
||||
public Long getCreation() {
|
||||
return this.creation;
|
||||
}
|
||||
|
||||
public byte[] getStateData() {
|
||||
return this.stateData;
|
||||
}
|
||||
|
||||
public byte[] getStateHash() {
|
||||
return this.stateHash;
|
||||
}
|
||||
|
||||
public BigDecimal getFees() {
|
||||
return this.fees;
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -13,18 +13,18 @@ public class BlockData implements Serializable {
|
||||
private int transactionCount;
|
||||
private BigDecimal totalFees;
|
||||
private byte[] transactionsSignature;
|
||||
private int height;
|
||||
private Integer height;
|
||||
private long timestamp;
|
||||
private BigDecimal generatingBalance;
|
||||
private byte[] generatorPublicKey;
|
||||
private byte[] generatorSignature;
|
||||
private byte[] atBytes;
|
||||
private int atCount;
|
||||
private BigDecimal atFees;
|
||||
|
||||
private BlockData() {} // necessary for JAX-RS serialization
|
||||
|
||||
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) {
|
||||
public BlockData(int version, byte[] reference, int transactionCount, BigDecimal totalFees, byte[] transactionsSignature, Integer height, long timestamp,
|
||||
BigDecimal generatingBalance, byte[] generatorPublicKey, byte[] generatorSignature, int atCount, BigDecimal atFees) {
|
||||
this.version = version;
|
||||
this.reference = reference;
|
||||
this.transactionCount = transactionCount;
|
||||
@ -35,7 +35,7 @@ public class BlockData implements Serializable {
|
||||
this.generatingBalance = generatingBalance;
|
||||
this.generatorPublicKey = generatorPublicKey;
|
||||
this.generatorSignature = generatorSignature;
|
||||
this.atBytes = atBytes;
|
||||
this.atCount = atCount;
|
||||
this.atFees = atFees;
|
||||
|
||||
if (this.generatorSignature != null && this.transactionsSignature != null)
|
||||
@ -44,6 +44,26 @@ public class BlockData implements Serializable {
|
||||
this.signature = null;
|
||||
}
|
||||
|
||||
public byte[] getSignature() {
|
||||
return this.signature;
|
||||
}
|
||||
|
||||
public void setSignature(byte[] signature) {
|
||||
this.signature = signature;
|
||||
}
|
||||
|
||||
public int getVersion() {
|
||||
return this.version;
|
||||
}
|
||||
|
||||
public byte[] getReference() {
|
||||
return this.reference;
|
||||
}
|
||||
|
||||
public void setReference(byte[] reference) {
|
||||
this.reference = reference;
|
||||
}
|
||||
|
||||
public int getTransactionCount() {
|
||||
return this.transactionCount;
|
||||
}
|
||||
@ -68,31 +88,11 @@ public class BlockData implements Serializable {
|
||||
this.transactionsSignature = transactionsSignature;
|
||||
}
|
||||
|
||||
public byte[] getSignature() {
|
||||
return this.signature;
|
||||
}
|
||||
|
||||
public void setSignature(byte[] signature) {
|
||||
this.signature = signature;
|
||||
}
|
||||
|
||||
public int getVersion() {
|
||||
return this.version;
|
||||
}
|
||||
|
||||
public byte[] getReference() {
|
||||
return this.reference;
|
||||
}
|
||||
|
||||
public void setReference(byte[] reference) {
|
||||
this.reference = reference;
|
||||
}
|
||||
|
||||
public int getHeight() {
|
||||
public Integer getHeight() {
|
||||
return this.height;
|
||||
}
|
||||
|
||||
public void setHeight(int height) {
|
||||
public void setHeight(Integer height) {
|
||||
this.height = height;
|
||||
}
|
||||
|
||||
@ -116,12 +116,20 @@ public class BlockData implements Serializable {
|
||||
this.generatorSignature = generatorSignature;
|
||||
}
|
||||
|
||||
public byte[] getAtBytes() {
|
||||
return this.atBytes;
|
||||
public int getATCount() {
|
||||
return this.atCount;
|
||||
}
|
||||
|
||||
public BigDecimal getAtFees() {
|
||||
public void setATCount(int atCount) {
|
||||
this.atCount = atCount;
|
||||
}
|
||||
|
||||
public BigDecimal getATFees() {
|
||||
return this.atFees;
|
||||
}
|
||||
|
||||
public void setATFees(BigDecimal atFees) {
|
||||
this.atFees = atFees;
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -3,9 +3,9 @@ package data.block;
|
||||
public class BlockTransactionData {
|
||||
|
||||
// Properties
|
||||
protected byte[] blockSignature;
|
||||
protected int sequence;
|
||||
protected byte[] transactionSignature;
|
||||
private byte[] blockSignature;
|
||||
private int sequence;
|
||||
private byte[] transactionSignature;
|
||||
|
||||
// Constructors
|
||||
|
||||
|
@ -2,36 +2,40 @@ package data.transaction;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
|
||||
import qora.account.GenesisAccount;
|
||||
import qora.transaction.Transaction.TransactionType;
|
||||
|
||||
public class ATTransactionData extends TransactionData {
|
||||
|
||||
// Properties
|
||||
private byte[] senderPublicKey;
|
||||
private String atAddress;
|
||||
private String recipient;
|
||||
private BigDecimal amount;
|
||||
private Long assetId;
|
||||
private byte[] message;
|
||||
|
||||
// Constructors
|
||||
|
||||
public ATTransactionData(byte[] senderPublicKey, String recipient, BigDecimal amount, byte[] message, BigDecimal fee, long timestamp, byte[] reference,
|
||||
byte[] signature) {
|
||||
super(TransactionType.AT, fee, senderPublicKey, timestamp, reference, signature);
|
||||
public ATTransactionData(String atAddress, String recipient, BigDecimal amount, Long assetId, byte[] message, BigDecimal fee, long timestamp,
|
||||
byte[] reference, byte[] signature) {
|
||||
super(TransactionType.AT, fee, GenesisAccount.PUBLIC_KEY, timestamp, reference, signature);
|
||||
|
||||
this.senderPublicKey = senderPublicKey;
|
||||
this.atAddress = atAddress;
|
||||
this.recipient = recipient;
|
||||
this.amount = amount;
|
||||
this.assetId = assetId;
|
||||
this.message = message;
|
||||
}
|
||||
|
||||
public ATTransactionData(byte[] senderPublicKey, String recipient, BigDecimal amount, byte[] message, BigDecimal fee, long timestamp, byte[] reference) {
|
||||
this(senderPublicKey, recipient, amount, message, fee, timestamp, reference, null);
|
||||
public ATTransactionData(String atAddress, String recipient, BigDecimal amount, Long assetId, byte[] message, BigDecimal fee, long timestamp,
|
||||
byte[] reference) {
|
||||
this(atAddress, recipient, amount, assetId, message, fee, timestamp, reference, null);
|
||||
}
|
||||
|
||||
// Getters/Setters
|
||||
|
||||
public byte[] getSenderPublicKey() {
|
||||
return this.senderPublicKey;
|
||||
public String getATAddress() {
|
||||
return this.atAddress;
|
||||
}
|
||||
|
||||
public String getRecipient() {
|
||||
@ -42,6 +46,10 @@ public class ATTransactionData extends TransactionData {
|
||||
return this.amount;
|
||||
}
|
||||
|
||||
public Long getAssetId() {
|
||||
return this.assetId;
|
||||
}
|
||||
|
||||
public byte[] getMessage() {
|
||||
return this.message;
|
||||
}
|
||||
|
@ -28,7 +28,7 @@ public class Account {
|
||||
protected Account() {
|
||||
}
|
||||
|
||||
public Account(Repository repository, String address) throws DataException {
|
||||
public Account(Repository repository, String address) {
|
||||
this.repository = repository;
|
||||
this.accountData = new AccountData(address);
|
||||
}
|
||||
@ -55,6 +55,7 @@ public class Account {
|
||||
for (int i = 1; i < BlockChain.BLOCK_RETARGET_INTERVAL && blockData != null && blockData.getHeight() > 1; ++i) {
|
||||
Block block = new Block(this.repository, blockData);
|
||||
|
||||
// CIYAM AT transactions should be fetched from repository so no special handling needed here
|
||||
for (Transaction transaction : block.getTransactions()) {
|
||||
if (transaction.isInvolved(this)) {
|
||||
final BigDecimal amount = transaction.getAmount(this);
|
||||
@ -65,19 +66,10 @@ public class Account {
|
||||
}
|
||||
}
|
||||
|
||||
// TODO - CIYAM AT support needed
|
||||
/*
|
||||
* LinkedHashMap<Tuple2<Integer, Integer>, AT_Transaction> atTxs = db.getATTransactionMap().getATTransactions(block.getHeight(db));
|
||||
* Iterator<AT_Transaction> iter = atTxs.values().iterator(); while (iter.hasNext()) { AT_Transaction key = iter.next();
|
||||
*
|
||||
* if (key.getRecipient().equals(this.getAddress())) balance = balance.subtract(BigDecimal.valueOf(key.getAmount(), 8)); }
|
||||
*/
|
||||
|
||||
blockData = block.getParent();
|
||||
}
|
||||
|
||||
// Do not go below 0
|
||||
// XXX: How would this even be possible?
|
||||
balance = balance.max(BigDecimal.ZERO);
|
||||
|
||||
return balance;
|
||||
@ -102,19 +94,11 @@ public class Account {
|
||||
for (int i = 1; i < confirmations && blockData != null && blockData.getHeight() > 1; ++i) {
|
||||
Block block = new Block(this.repository, blockData);
|
||||
|
||||
// CIYAM AT transactions should be fetched from repository so no special handling needed here
|
||||
for (Transaction transaction : block.getTransactions())
|
||||
if (transaction.isInvolved(this))
|
||||
balance = balance.subtract(transaction.getAmount(this));
|
||||
|
||||
// TODO - CIYAM AT support
|
||||
/*
|
||||
* // Also check AT transactions for amounts received to this account LinkedHashMap<Tuple2<Integer, Integer>, AT_Transaction> atTxs =
|
||||
* db.getATTransactionMap().getATTransactions(block.getHeight(db)); Iterator<AT_Transaction> iter = atTxs.values().iterator(); while
|
||||
* (iter.hasNext()) { AT_Transaction key = iter.next();
|
||||
*
|
||||
* if (key.getRecipient().equals(this.getAddress())) balance = balance.subtract(BigDecimal.valueOf(key.getAmount(), 8)); }
|
||||
*/
|
||||
|
||||
blockData = block.getParent();
|
||||
}
|
||||
|
||||
@ -131,6 +115,9 @@ public class Account {
|
||||
}
|
||||
|
||||
public void setConfirmedBalance(long assetId, BigDecimal balance) throws DataException {
|
||||
// Can't have a balance without an account - make sure it exists!
|
||||
this.repository.getAccountRepository().create(this.accountData.getAddress());
|
||||
|
||||
AccountBalanceData accountBalanceData = new AccountBalanceData(this.accountData.getAddress(), assetId, balance);
|
||||
this.repository.getAccountRepository().save(accountBalanceData);
|
||||
|
||||
|
@ -1,13 +1,12 @@
|
||||
package qora.account;
|
||||
|
||||
import repository.DataException;
|
||||
import repository.Repository;
|
||||
|
||||
public final class GenesisAccount extends PublicKeyAccount {
|
||||
|
||||
public static final byte[] PUBLIC_KEY = new byte[] { 1, 1, 1, 1, 1, 1, 1, 1 };
|
||||
|
||||
public GenesisAccount(Repository repository) throws DataException {
|
||||
public GenesisAccount(Repository repository) {
|
||||
super(repository, PUBLIC_KEY);
|
||||
}
|
||||
|
||||
|
@ -2,14 +2,13 @@ package qora.account;
|
||||
|
||||
import qora.crypto.Crypto;
|
||||
import qora.crypto.Ed25519;
|
||||
import repository.DataException;
|
||||
import repository.Repository;
|
||||
|
||||
public class PublicKeyAccount extends Account {
|
||||
|
||||
protected byte[] publicKey;
|
||||
|
||||
public PublicKeyAccount(Repository repository, byte[] publicKey) throws DataException {
|
||||
public PublicKeyAccount(Repository repository, byte[] publicKey) {
|
||||
super(repository, Crypto.toAddress(publicKey));
|
||||
|
||||
this.publicKey = publicKey;
|
||||
|
@ -169,9 +169,9 @@ public class Order {
|
||||
BigDecimal matchedAmount = ourAmountLeft.min(theirAmountLeft);
|
||||
LOGGER.trace("matchedAmount: " + matchedAmount.toPlainString() + " " + wantAssetData.getName());
|
||||
|
||||
// If we can't buy anything then we're done
|
||||
// If we can't buy anything then try another order
|
||||
if (matchedAmount.compareTo(BigDecimal.ZERO) <= 0)
|
||||
break;
|
||||
continue;
|
||||
|
||||
// Calculate amount granularity based on both assets' divisibility
|
||||
BigDecimal increment = this.calculateAmountGranularity(haveAssetData, wantAssetData, theirOrderData);
|
||||
@ -179,9 +179,9 @@ public class Order {
|
||||
matchedAmount = matchedAmount.subtract(matchedAmount.remainder(increment));
|
||||
LOGGER.trace("matchedAmount adjusted for granularity: " + matchedAmount.toPlainString() + " " + wantAssetData.getName());
|
||||
|
||||
// If we can't buy anything then we're done
|
||||
// If we can't buy anything then try another order
|
||||
if (matchedAmount.compareTo(BigDecimal.ZERO) <= 0)
|
||||
break;
|
||||
continue;
|
||||
|
||||
// Trade can go ahead!
|
||||
|
||||
|
@ -1,10 +1,15 @@
|
||||
package qora.at;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.nio.ByteBuffer;
|
||||
|
||||
import org.ciyam.at.MachineState;
|
||||
|
||||
import data.at.ATData;
|
||||
import data.at.ATStateData;
|
||||
import data.transaction.DeployATTransactionData;
|
||||
import qora.account.PublicKeyAccount;
|
||||
import qora.crypto.Crypto;
|
||||
import repository.DataException;
|
||||
import repository.Repository;
|
||||
|
||||
@ -23,26 +28,61 @@ public class AT {
|
||||
this.atStateData = atStateData;
|
||||
}
|
||||
|
||||
/** Deploying AT */
|
||||
public AT(Repository repository, DeployATTransactionData deployATTransactionData) throws DataException {
|
||||
this.repository = repository;
|
||||
|
||||
MachineState machineState = new MachineState(deployATTransactionData.getCreationBytes());
|
||||
String atAddress = deployATTransactionData.getATAddress();
|
||||
int height = this.repository.getBlockRepository().getBlockchainHeight() + 1;
|
||||
String creator = new PublicKeyAccount(repository, deployATTransactionData.getCreatorPublicKey()).getAddress();
|
||||
long creation = deployATTransactionData.getTimestamp();
|
||||
|
||||
this.atData = new ATData(deployATTransactionData.getATAddress(), machineState.version, machineState.codeByteBuffer.array(), machineState.isSleeping,
|
||||
machineState.sleepUntilHeight, machineState.isFinished, machineState.hadFatalError, machineState.isFrozen, machineState.frozenBalance,
|
||||
deployATTransactionData.getSignature());
|
||||
byte[] creationBytes = deployATTransactionData.getCreationBytes();
|
||||
short version = (short) (creationBytes[0] | (creationBytes[1] << 8)); // Little-endian
|
||||
|
||||
String atAddress = this.atData.getATAddress();
|
||||
if (version >= 2) {
|
||||
MachineState machineState = new MachineState(deployATTransactionData.getCreationBytes());
|
||||
|
||||
int height = this.repository.getBlockRepository().getBlockchainHeight();
|
||||
byte[] stateData = machineState.toBytes();
|
||||
this.atData = new ATData(atAddress, creator, creation, machineState.version, machineState.getCodeBytes(), machineState.getIsSleeping(),
|
||||
machineState.getSleepUntilHeight(), machineState.getIsFinished(), machineState.getHadFatalError(), machineState.getIsFrozen(),
|
||||
machineState.getFrozenBalance());
|
||||
|
||||
this.atStateData = new ATStateData(atAddress, height, stateData);
|
||||
byte[] stateData = machineState.toBytes();
|
||||
byte[] stateHash = Crypto.digest(stateData);
|
||||
|
||||
this.atStateData = new ATStateData(atAddress, height, creation, stateData, stateHash, BigDecimal.ZERO.setScale(8));
|
||||
} else {
|
||||
// Legacy v1 AT in 'dead' state
|
||||
// Extract code bytes length
|
||||
ByteBuffer byteBuffer = ByteBuffer.wrap(deployATTransactionData.getCreationBytes());
|
||||
|
||||
short numCodePages = byteBuffer.get(2 + 2);
|
||||
|
||||
byteBuffer.position(6 * 2 + 8);
|
||||
int codeLen = 0;
|
||||
|
||||
if (numCodePages * 256 < 257) {
|
||||
codeLen = (int) (byteBuffer.get() & 0xff);
|
||||
} else if (numCodePages * 256 < Short.MAX_VALUE + 1) {
|
||||
codeLen = byteBuffer.getShort() & 0xffff;
|
||||
} else if (numCodePages * 256 <= Integer.MAX_VALUE) {
|
||||
codeLen = byteBuffer.getInt();
|
||||
}
|
||||
|
||||
// Extract code bytes
|
||||
byte[] codeBytes = new byte[codeLen];
|
||||
byteBuffer.get(codeBytes);
|
||||
|
||||
this.atData = new ATData(atAddress, creator, creation, 1, codeBytes, false, null, true, false, false, (Long) null);
|
||||
|
||||
this.atStateData = new ATStateData(atAddress, height, creation, null, null, BigDecimal.ZERO.setScale(8));
|
||||
}
|
||||
}
|
||||
|
||||
// Processing
|
||||
|
||||
public void deploy() throws DataException {
|
||||
this.repository.getATRepository().save(this.atData);
|
||||
this.repository.getATRepository().save(this.atStateData);
|
||||
}
|
||||
|
||||
public void undeploy() throws DataException {
|
||||
|
@ -15,6 +15,7 @@ import org.apache.logging.log4j.Logger;
|
||||
|
||||
import com.google.common.primitives.Bytes;
|
||||
|
||||
import data.at.ATStateData;
|
||||
import data.block.BlockData;
|
||||
import data.block.BlockTransactionData;
|
||||
import data.transaction.TransactionData;
|
||||
@ -25,6 +26,7 @@ import qora.assets.Asset;
|
||||
import qora.crypto.Crypto;
|
||||
import qora.transaction.GenesisTransaction;
|
||||
import qora.transaction.Transaction;
|
||||
import repository.ATRepository;
|
||||
import repository.BlockRepository;
|
||||
import repository.DataException;
|
||||
import repository.Repository;
|
||||
@ -64,6 +66,7 @@ public class Block {
|
||||
REFERENCE_MISSING(10),
|
||||
PARENT_DOES_NOT_EXIST(11),
|
||||
BLOCKCHAIN_NOT_EMPTY(12),
|
||||
PARENT_HAS_EXISTING_CHILD(13),
|
||||
TIMESTAMP_OLDER_THAN_PARENT(20),
|
||||
TIMESTAMP_IN_FUTURE(21),
|
||||
TIMESTAMP_MS_INCORRECT(22),
|
||||
@ -74,7 +77,8 @@ public class Block {
|
||||
GENESIS_TRANSACTIONS_INVALID(50),
|
||||
TRANSACTION_TIMESTAMP_INVALID(51),
|
||||
TRANSACTION_INVALID(52),
|
||||
TRANSACTION_PROCESSING_FAILED(53);
|
||||
TRANSACTION_PROCESSING_FAILED(53),
|
||||
AT_STATES_MISMATCH(61);
|
||||
|
||||
public final int value;
|
||||
|
||||
@ -97,6 +101,11 @@ public class Block {
|
||||
// Other properties
|
||||
private static final Logger LOGGER = LogManager.getLogger(Block.class);
|
||||
protected List<Transaction> transactions;
|
||||
|
||||
protected List<ATStateData> atStates;
|
||||
protected List<ATStateData> ourAtStates; // Generated locally
|
||||
protected BigDecimal ourAtFees; // Generated locally
|
||||
|
||||
protected BigDecimal cachedNextGeneratingBalance;
|
||||
|
||||
// Other useful constants
|
||||
@ -104,39 +113,92 @@ public class Block {
|
||||
|
||||
// Constructors
|
||||
|
||||
/**
|
||||
* Constructs Block-handling object without loading transactions and AT states.
|
||||
* <p>
|
||||
* Transactions and AT states are loaded on first call to getTransactions() or getATStates() respectively.
|
||||
*
|
||||
* @param repository
|
||||
* @param blockData
|
||||
* @throws DataException
|
||||
*/
|
||||
public Block(Repository repository, BlockData blockData) throws DataException {
|
||||
this.repository = repository;
|
||||
this.blockData = blockData;
|
||||
this.generator = new PublicKeyAccount(repository, blockData.getGeneratorPublicKey());
|
||||
}
|
||||
|
||||
// When receiving a block over network?
|
||||
public Block(Repository repository, BlockData blockData, List<TransactionData> transactions) throws DataException {
|
||||
/**
|
||||
* Constructs Block-handling object using passed transaction and AT states.
|
||||
* <p>
|
||||
* This constructor typically used when receiving a serialized block over the network.
|
||||
*
|
||||
* @param repository
|
||||
* @param blockData
|
||||
* @param transactions
|
||||
* @param atStates
|
||||
* @throws DataException
|
||||
*/
|
||||
public Block(Repository repository, BlockData blockData, List<TransactionData> transactions, List<ATStateData> atStates) throws DataException {
|
||||
this(repository, blockData);
|
||||
|
||||
this.transactions = new ArrayList<Transaction>();
|
||||
|
||||
BigDecimal totalFees = BigDecimal.ZERO.setScale(8);
|
||||
|
||||
// We have to sum fees too
|
||||
for (TransactionData transactionData : transactions) {
|
||||
this.transactions.add(Transaction.fromData(repository, transactionData));
|
||||
this.blockData.setTotalFees(this.blockData.getTotalFees().add(transactionData.getFee()));
|
||||
totalFees = totalFees.add(transactionData.getFee());
|
||||
}
|
||||
|
||||
this.atStates = atStates;
|
||||
for (ATStateData atState : atStates)
|
||||
totalFees = totalFees.add(atState.getFees());
|
||||
|
||||
this.blockData.setTotalFees(totalFees);
|
||||
}
|
||||
|
||||
// For creating a new block?
|
||||
public Block(Repository repository, int version, byte[] reference, long timestamp, BigDecimal generatingBalance, PrivateKeyAccount generator,
|
||||
byte[] atBytes, BigDecimal atFees) {
|
||||
/**
|
||||
* Constructs Block-handling object with basic, initial values.
|
||||
* <p>
|
||||
* This constructor typically used when generating a new block.
|
||||
* <p>
|
||||
* Note that CIYAM ATs will be executed and AT-Transactions prepended to this block, along with AT state data and fees.
|
||||
*
|
||||
* @param repository
|
||||
* @param version
|
||||
* @param reference
|
||||
* @param timestamp
|
||||
* @param generatingBalance
|
||||
* @param generator
|
||||
* @throws DataException
|
||||
*/
|
||||
public Block(Repository repository, int version, byte[] reference, long timestamp, BigDecimal generatingBalance, PrivateKeyAccount generator)
|
||||
throws DataException {
|
||||
this.repository = repository;
|
||||
this.generator = generator;
|
||||
|
||||
this.blockData = new BlockData(version, reference, 0, BigDecimal.ZERO.setScale(8), null, 0, timestamp, generatingBalance, generator.getPublicKey(),
|
||||
null, atBytes, atFees);
|
||||
int transactionCount = 0;
|
||||
byte[] transactionsSignature = null;
|
||||
Integer height = null;
|
||||
byte[] generatorSignature = null;
|
||||
|
||||
this.executeATs();
|
||||
|
||||
int atCount = this.ourAtStates.size();
|
||||
BigDecimal atFees = this.ourAtFees;
|
||||
BigDecimal totalFees = atFees;
|
||||
|
||||
this.blockData = new BlockData(version, reference, transactionCount, totalFees, transactionsSignature, height, timestamp, generatingBalance,
|
||||
generator.getPublicKey(), generatorSignature, atCount, atFees);
|
||||
|
||||
this.transactions = new ArrayList<Transaction>();
|
||||
this.atStates = this.ourAtStates;
|
||||
}
|
||||
|
||||
/** Construct a new block for use in tests */
|
||||
public Block(Repository repository, BlockData parentBlockData, PrivateKeyAccount generator, byte[] atBytes, BigDecimal atFees) throws DataException {
|
||||
public Block(Repository repository, BlockData parentBlockData, PrivateKeyAccount generator) throws DataException {
|
||||
this.repository = repository;
|
||||
this.generator = generator;
|
||||
|
||||
@ -155,11 +217,18 @@ public class Block {
|
||||
}
|
||||
|
||||
long timestamp = parentBlock.calcNextBlockTimestamp(version, generatorSignature, generator);
|
||||
int transactionCount = 0;
|
||||
BigDecimal totalFees = BigDecimal.ZERO.setScale(8);
|
||||
byte[] transactionsSignature = null;
|
||||
int height = parentBlockData.getHeight() + 1;
|
||||
int atCount = 0;
|
||||
BigDecimal atFees = BigDecimal.ZERO.setScale(8);
|
||||
|
||||
this.blockData = new BlockData(version, reference, 0, BigDecimal.ZERO.setScale(8), null, 0, timestamp, generatingBalance, generator.getPublicKey(),
|
||||
generatorSignature, atBytes, atFees);
|
||||
this.blockData = new BlockData(version, reference, transactionCount, totalFees, transactionsSignature, height, timestamp, generatingBalance,
|
||||
generator.getPublicKey(), generatorSignature, atCount, atFees);
|
||||
|
||||
this.transactions = new ArrayList<Transaction>();
|
||||
this.atStates = new ArrayList<ATStateData>();
|
||||
}
|
||||
|
||||
// Getters/setters
|
||||
@ -192,12 +261,17 @@ public class Block {
|
||||
* @return 1, 2 or 3
|
||||
*/
|
||||
public int getNextBlockVersion() {
|
||||
if (this.blockData.getHeight() == null)
|
||||
throw new IllegalStateException("Can't determine next block's version as this block has no height set");
|
||||
|
||||
if (this.blockData.getHeight() < BlockChain.getATReleaseHeight())
|
||||
return 1;
|
||||
else if (this.blockData.getTimestamp() < BlockChain.getPowFixReleaseTimestamp())
|
||||
return 2;
|
||||
else
|
||||
else if (this.blockData.getTimestamp() < BlockChain.getDeployATV2Timestamp())
|
||||
return 3;
|
||||
else
|
||||
return 4;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -212,8 +286,8 @@ public class Block {
|
||||
* @throws DataException
|
||||
*/
|
||||
public BigDecimal calcNextBlockGeneratingBalance() throws DataException {
|
||||
if (this.blockData.getHeight() == 0)
|
||||
throw new IllegalStateException("Block height is unset");
|
||||
if (this.blockData.getHeight() == null)
|
||||
throw new IllegalStateException("Can't calculate next block's generating balance as this block's height is unset");
|
||||
|
||||
// This block not at the start of an interval?
|
||||
if (this.blockData.getHeight() % BlockChain.BLOCK_RETARGET_INTERVAL != 0)
|
||||
@ -346,7 +420,7 @@ public class Block {
|
||||
/**
|
||||
* Return block's transactions.
|
||||
* <p>
|
||||
* 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.
|
||||
* If the block was loaded from repository then it's possible this method will call the repository to fetch the transactions if not done already.
|
||||
*
|
||||
* @return
|
||||
* @throws DataException
|
||||
@ -371,6 +445,37 @@ public class Block {
|
||||
return this.transactions;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return block's AT states.
|
||||
* <p>
|
||||
* If the block was loaded from repository then it's possible this method will call the repository to fetch the AT states if not done already.
|
||||
* <p>
|
||||
* <b>Note:</b> AT states fetched from repository only contain summary info, not actual data like serialized state data or AT creation timestamps!
|
||||
*
|
||||
* @return
|
||||
* @throws DataException
|
||||
*/
|
||||
public List<ATStateData> getATStates() throws DataException {
|
||||
// Already loaded?
|
||||
if (this.atStates != null)
|
||||
return this.atStates;
|
||||
|
||||
// If loading from repository, this block must have a height
|
||||
if (this.blockData.getHeight() == null)
|
||||
throw new IllegalStateException("Can't fetch block's AT states from repository without a block height");
|
||||
|
||||
// Allocate cache for results
|
||||
List<ATStateData> atStateData = this.repository.getATRepository().getBlockATStatesFromHeight(this.blockData.getHeight());
|
||||
|
||||
// The number of AT states fetched from repository should correspond with Block's atCount
|
||||
if (atStateData.size() != this.blockData.getATCount())
|
||||
throw new IllegalStateException("Block's AT states from repository do not match block's AT count");
|
||||
|
||||
this.atStates = atStateData;
|
||||
|
||||
return this.atStates;
|
||||
}
|
||||
|
||||
// Navigation
|
||||
|
||||
/**
|
||||
@ -531,8 +636,6 @@ public class Block {
|
||||
* @throws DataException
|
||||
*/
|
||||
public ValidationResult isValid() throws DataException {
|
||||
// TODO
|
||||
|
||||
// Check parent block exists
|
||||
if (this.blockData.getReference() == null)
|
||||
return ValidationResult.REFERENCE_MISSING;
|
||||
@ -543,6 +646,10 @@ public class Block {
|
||||
|
||||
Block parentBlock = new Block(this.repository, parentBlockData);
|
||||
|
||||
// Check parent doesn't already have a child block
|
||||
if (parentBlock.getChild() != null)
|
||||
return ValidationResult.PARENT_HAS_EXISTING_CHILD;
|
||||
|
||||
// Check timestamp is newer than parent timestamp
|
||||
if (this.blockData.getTimestamp() <= parentBlockData.getTimestamp())
|
||||
return ValidationResult.TIMESTAMP_OLDER_THAN_PARENT;
|
||||
@ -558,7 +665,7 @@ public class Block {
|
||||
// Check block version
|
||||
if (this.blockData.getVersion() != parentBlock.getNextBlockVersion())
|
||||
return ValidationResult.VERSION_INCORRECT;
|
||||
if (this.blockData.getVersion() < 2 && (this.blockData.getAtBytes() != null || this.blockData.getAtFees() != null))
|
||||
if (this.blockData.getVersion() < 2 && this.blockData.getATCount() != 0)
|
||||
return ValidationResult.FEATURE_NOT_YET_RELEASED;
|
||||
|
||||
// Check generating balance
|
||||
@ -583,16 +690,34 @@ public class Block {
|
||||
if (hashValue.compareTo(lowerTarget) < 0)
|
||||
return ValidationResult.GENERATOR_NOT_ACCEPTED;
|
||||
|
||||
// Process CIYAM ATs, prepending AT-Transactions to block then compare post-execution checksums
|
||||
// XXX We should pre-calculate, and cache, next block's AT-transactions after processing each block to save repeated work
|
||||
if (this.blockData.getAtBytes() != null && this.blockData.getAtBytes().length > 0) {
|
||||
// TODO
|
||||
// try {
|
||||
// AT_Block atBlock = AT_Controller.validateATs(this.getBlockATs(), BlockChain.getHeight() + 1);
|
||||
// this.atFees = atBlock.getTotalFees();
|
||||
// } catch (NoSuchAlgorithmException | AT_Exception e) {
|
||||
// return false;
|
||||
// }
|
||||
// CIYAM ATs
|
||||
if (this.blockData.getATCount() != 0) {
|
||||
// Locally generated AT states should be valid so no need to re-execute them
|
||||
if (this.ourAtStates != this.getATStates()) {
|
||||
// Otherwise, check locally generated AT states against ones received from elsewhere?
|
||||
this.executeATs();
|
||||
|
||||
if (this.ourAtStates.size() != this.blockData.getATCount())
|
||||
return ValidationResult.AT_STATES_MISMATCH;
|
||||
|
||||
if (this.ourAtFees.compareTo(this.blockData.getATFees()) != 0)
|
||||
return ValidationResult.AT_STATES_MISMATCH;
|
||||
|
||||
// Note: this.atStates fully loaded thanks to this.getATStates() call above
|
||||
for (int s = 0; s < this.atStates.size(); ++s) {
|
||||
ATStateData ourAtState = this.ourAtStates.get(s);
|
||||
ATStateData theirAtState = this.atStates.get(s);
|
||||
|
||||
if (!ourAtState.getATAddress().equals(theirAtState.getATAddress()))
|
||||
return ValidationResult.AT_STATES_MISMATCH;
|
||||
|
||||
if (!ourAtState.getStateHash().equals(theirAtState.getStateHash()))
|
||||
return ValidationResult.AT_STATES_MISMATCH;
|
||||
|
||||
if (ourAtState.getFees().compareTo(theirAtState.getFees()) != 0)
|
||||
return ValidationResult.AT_STATES_MISMATCH;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Check transactions
|
||||
@ -612,7 +737,7 @@ public class Block {
|
||||
Transaction.ValidationResult validationResult = transaction.isValid();
|
||||
if (validationResult != Transaction.ValidationResult.OK) {
|
||||
LOGGER.error("Error during transaction validation, tx " + Base58.encode(transaction.getTransactionData().getSignature()) + ": "
|
||||
+ validationResult.value);
|
||||
+ validationResult.name());
|
||||
return ValidationResult.TRANSACTION_INVALID;
|
||||
}
|
||||
|
||||
@ -643,8 +768,47 @@ public class Block {
|
||||
return ValidationResult.OK;
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute CIYAM ATs for this block.
|
||||
* <p>
|
||||
* This needs to be done locally for all blocks, regardless of origin.<br>
|
||||
* This method is called by <tt>isValid</tt>.
|
||||
* <p>
|
||||
* After calling, AT-generated transactions are prepended to the block's transactions and AT state data is generated.
|
||||
* <p>
|
||||
* This method is not needed if fetching an existing block from the repository.
|
||||
* <p>
|
||||
* Updates <tt>this.ourAtStates</tt> and <tt>this.ourAtFees</tt>.
|
||||
*
|
||||
* @see #isValid()
|
||||
*
|
||||
* @throws DataException
|
||||
*
|
||||
*/
|
||||
public void executeATs() throws DataException {
|
||||
// We're expecting a lack of AT state data at this point.
|
||||
if (this.ourAtStates != null)
|
||||
throw new IllegalStateException("Attempted to execute ATs when block's local AT state data already exists");
|
||||
|
||||
// For old v1 CIYAM ATs we blindly accept them
|
||||
if (this.blockData.getVersion() < 4) {
|
||||
this.ourAtStates = this.atStates;
|
||||
this.ourAtFees = this.blockData.getATFees();
|
||||
return;
|
||||
}
|
||||
|
||||
// Find all executable ATs, ordered by earliest creation date first
|
||||
|
||||
// Run each AT, appends AT-Transactions and corresponding AT states, to our lists
|
||||
|
||||
// Finally prepend our entire AT-Transactions/states to block's transactions/states, adjust fees, etc.
|
||||
|
||||
// Note: store locally-calculated AT states separately to this.atStates so we can compare them in isValid()
|
||||
}
|
||||
|
||||
public void process() throws DataException {
|
||||
// Process transactions (we'll link them to this block after saving the block itself)
|
||||
// AT-generated transactions are already added to our transactions so no special handling is needed here.
|
||||
List<Transaction> transactions = this.getTransactions();
|
||||
for (Transaction transaction : transactions)
|
||||
transaction.process();
|
||||
@ -654,6 +818,17 @@ public class Block {
|
||||
if (blockFee.compareTo(BigDecimal.ZERO) > 0)
|
||||
this.generator.setConfirmedBalance(Asset.QORA, this.generator.getConfirmedBalance(Asset.QORA).add(blockFee));
|
||||
|
||||
// Process AT fees and save AT states into repository
|
||||
ATRepository atRepository = this.repository.getATRepository();
|
||||
for (ATStateData atState : this.getATStates()) {
|
||||
Account atAccount = new Account(this.repository, atState.getATAddress());
|
||||
|
||||
// Subtract AT-generated fees from AT accounts
|
||||
atAccount.setConfirmedBalance(Asset.QORA, atAccount.getConfirmedBalance(Asset.QORA).subtract(atState.getFees()));
|
||||
|
||||
atRepository.save(atState);
|
||||
}
|
||||
|
||||
// Link block into blockchain by fetching signature of highest block and setting that as our reference
|
||||
int blockchainHeight = this.repository.getBlockRepository().getBlockchainHeight();
|
||||
BlockData latestBlockData = this.repository.getBlockRepository().fromHeight(blockchainHeight);
|
||||
@ -675,12 +850,8 @@ public class Block {
|
||||
}
|
||||
|
||||
public void orphan() throws DataException {
|
||||
// TODO
|
||||
|
||||
// Orphan block's CIYAM ATs
|
||||
orphanAutomatedTransactions();
|
||||
|
||||
// Orphan transactions in reverse order, and unlink them from this block
|
||||
// AT-generated transactions are already added to our transactions so no special handling is needed here.
|
||||
List<Transaction> transactions = this.getTransactions();
|
||||
for (int sequence = transactions.size() - 1; sequence >= 0; --sequence) {
|
||||
Transaction transaction = transactions.get(sequence);
|
||||
@ -696,25 +867,19 @@ public class Block {
|
||||
if (blockFee.compareTo(BigDecimal.ZERO) > 0)
|
||||
this.generator.setConfirmedBalance(Asset.QORA, this.generator.getConfirmedBalance(Asset.QORA).subtract(blockFee));
|
||||
|
||||
// Return AT fees and delete AT states from repository
|
||||
ATRepository atRepository = this.repository.getATRepository();
|
||||
for (ATStateData atState : this.getATStates()) {
|
||||
Account atAccount = new Account(this.repository, atState.getATAddress());
|
||||
|
||||
// Return AT-generated fees to AT accounts
|
||||
atAccount.setConfirmedBalance(Asset.QORA, atAccount.getConfirmedBalance(Asset.QORA).add(atState.getFees()));
|
||||
}
|
||||
// Delete ATStateData for this height
|
||||
atRepository.deleteATStates(this.blockData.getHeight());
|
||||
|
||||
// Delete block from blockchain
|
||||
this.repository.getBlockRepository().delete(this.blockData);
|
||||
}
|
||||
|
||||
public void orphanAutomatedTransactions() throws DataException {
|
||||
// TODO - CIYAM AT support
|
||||
/*
|
||||
* LinkedHashMap< Tuple2<Integer, Integer> , AT_Transaction > atTxs = DBSet.getInstance().getATTransactionMap().getATTransactions(this.getHeight(db));
|
||||
*
|
||||
* Iterator<AT_Transaction> iter = atTxs.values().iterator();
|
||||
*
|
||||
* while ( iter.hasNext() ) { AT_Transaction key = iter.next(); Long amount = key.getAmount(); if (key.getRecipientId() != null &&
|
||||
* !Arrays.equals(key.getRecipientId(), new byte[ AT_Constants.AT_ID_SIZE ]) && !key.getRecipient().equalsIgnoreCase("1") ) { Account recipient = new
|
||||
* Account( key.getRecipient() ); recipient.setConfirmedBalance( recipient.getConfirmedBalance( db ).subtract( BigDecimal.valueOf( amount, 8 ) ) , db );
|
||||
* if ( Arrays.equals(recipient.getLastReference(db),new byte[64])) { recipient.removeReference(db); } } Account sender = new Account( key.getSender()
|
||||
* ); sender.setConfirmedBalance( sender.getConfirmedBalance( db ).add( BigDecimal.valueOf( amount, 8 ) ) , db );
|
||||
*
|
||||
* }
|
||||
*/
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -34,7 +34,7 @@ public class GenesisBlock extends Block {
|
||||
|
||||
public GenesisBlock(Repository repository) throws DataException {
|
||||
super(repository, new BlockData(GENESIS_BLOCK_VERSION, GENESIS_REFERENCE, 0, BigDecimal.ZERO.setScale(8), GENESIS_TRANSACTIONS_SIGNATURE, 1,
|
||||
Settings.getInstance().getGenesisTimestamp(), GENESIS_GENERATING_BALANCE, GENESIS_GENERATOR_PUBLIC_KEY, GENESIS_GENERATOR_SIGNATURE, null, null));
|
||||
Settings.getInstance().getGenesisTimestamp(), GENESIS_GENERATING_BALANCE, GENESIS_GENERATOR_PUBLIC_KEY, GENESIS_GENERATOR_SIGNATURE, 0, BigDecimal.ZERO.setScale(8)));
|
||||
|
||||
this.transactions = new ArrayList<Transaction>();
|
||||
|
||||
|
@ -46,31 +46,31 @@ public class Payment {
|
||||
amountsByAssetId.put(Asset.QORA, fee);
|
||||
|
||||
// Check payments, and calculate amount total by assetId
|
||||
if (payments != null)
|
||||
for (PaymentData paymentData : payments) {
|
||||
// Check amount is positive
|
||||
if (paymentData.getAmount().compareTo(BigDecimal.ZERO) < 0)
|
||||
return ValidationResult.NEGATIVE_AMOUNT;
|
||||
for (PaymentData paymentData : payments) {
|
||||
// Check amount is zero or positive
|
||||
if (paymentData.getAmount().compareTo(BigDecimal.ZERO) < 0)
|
||||
return ValidationResult.NEGATIVE_AMOUNT;
|
||||
|
||||
// Optional zero-amount check
|
||||
if (!isZeroAmountValid && paymentData.getAmount().compareTo(BigDecimal.ZERO) <= 0)
|
||||
return ValidationResult.NEGATIVE_AMOUNT;
|
||||
// Optional zero-amount check
|
||||
if (!isZeroAmountValid && paymentData.getAmount().compareTo(BigDecimal.ZERO) <= 0)
|
||||
return ValidationResult.NEGATIVE_AMOUNT;
|
||||
|
||||
// Check recipient address is valid
|
||||
if (!Crypto.isValidAddress(paymentData.getRecipient()))
|
||||
return ValidationResult.INVALID_ADDRESS;
|
||||
// Check recipient address is valid
|
||||
if (!Crypto.isValidAddress(paymentData.getRecipient()))
|
||||
return ValidationResult.INVALID_ADDRESS;
|
||||
|
||||
AssetData assetData = assetRepository.fromAssetId(paymentData.getAssetId());
|
||||
// Check asset even exists
|
||||
if (assetData == null)
|
||||
return ValidationResult.ASSET_DOES_NOT_EXIST;
|
||||
AssetData assetData = assetRepository.fromAssetId(paymentData.getAssetId());
|
||||
// Check asset even exists
|
||||
if (assetData == null)
|
||||
return ValidationResult.ASSET_DOES_NOT_EXIST;
|
||||
|
||||
// Check asset amount is integer if asset is not divisible
|
||||
if (!assetData.getIsDivisible() && paymentData.getAmount().stripTrailingZeros().scale() > 0)
|
||||
return ValidationResult.INVALID_AMOUNT;
|
||||
// Check asset amount is integer if asset is not divisible
|
||||
if (!assetData.getIsDivisible() && paymentData.getAmount().stripTrailingZeros().scale() > 0)
|
||||
return ValidationResult.INVALID_AMOUNT;
|
||||
|
||||
amountsByAssetId.compute(paymentData.getAssetId(), (assetId, amount) -> amount == null ? amount : amount.add(paymentData.getAmount()));
|
||||
}
|
||||
// Set or add amount into amounts-by-asset map
|
||||
amountsByAssetId.compute(paymentData.getAssetId(), (assetId, amount) -> amount == null ? amount : amount.add(paymentData.getAmount()));
|
||||
}
|
||||
|
||||
// Check sender has enough of each asset
|
||||
Account sender = new PublicKeyAccount(this.repository, senderPublicKey);
|
||||
@ -87,7 +87,7 @@ public class Payment {
|
||||
|
||||
// Single payment forms
|
||||
public ValidationResult isValid(byte[] senderPublicKey, PaymentData paymentData, BigDecimal fee, boolean isZeroAmountValid) throws DataException {
|
||||
return isValid(senderPublicKey, Collections.singletonList(paymentData), fee);
|
||||
return isValid(senderPublicKey, Collections.singletonList(paymentData), fee, isZeroAmountValid);
|
||||
}
|
||||
|
||||
public ValidationResult isValid(byte[] senderPublicKey, PaymentData paymentData, BigDecimal fee) throws DataException {
|
||||
@ -105,22 +105,22 @@ public class Payment {
|
||||
sender.setLastReference(signature);
|
||||
|
||||
// Process all payments
|
||||
if (payments != null)
|
||||
for (PaymentData paymentData : payments) {
|
||||
Account recipient = new Account(this.repository, paymentData.getRecipient());
|
||||
long assetId = paymentData.getAssetId();
|
||||
BigDecimal amount = paymentData.getAmount();
|
||||
for (PaymentData paymentData : payments) {
|
||||
Account recipient = new Account(this.repository, paymentData.getRecipient());
|
||||
|
||||
// Update sender's balance due to amount
|
||||
sender.setConfirmedBalance(assetId, sender.getConfirmedBalance(assetId).subtract(amount));
|
||||
long assetId = paymentData.getAssetId();
|
||||
BigDecimal amount = paymentData.getAmount();
|
||||
|
||||
// Update recipient's balance
|
||||
recipient.setConfirmedBalance(assetId, recipient.getConfirmedBalance(assetId).add(amount));
|
||||
// Update sender's balance due to amount
|
||||
sender.setConfirmedBalance(assetId, sender.getConfirmedBalance(assetId).subtract(amount));
|
||||
|
||||
// For QORA amounts only: if recipient has no reference yet, then this is their starting reference
|
||||
if ((alwaysInitializeRecipientReference || assetId == Asset.QORA) && recipient.getLastReference() == null)
|
||||
recipient.setLastReference(signature);
|
||||
}
|
||||
// Update recipient's balance
|
||||
recipient.setConfirmedBalance(assetId, recipient.getConfirmedBalance(assetId).add(amount));
|
||||
|
||||
// For QORA amounts only: if recipient has no reference yet, then this is their starting reference
|
||||
if ((alwaysInitializeRecipientReference || assetId == Asset.QORA) && recipient.getLastReference() == null)
|
||||
recipient.setLastReference(signature);
|
||||
}
|
||||
}
|
||||
|
||||
public void process(byte[] senderPublicKey, PaymentData paymentData, BigDecimal fee, byte[] signature, boolean alwaysInitializeRecipientReference)
|
||||
@ -139,25 +139,24 @@ public class Payment {
|
||||
sender.setLastReference(reference);
|
||||
|
||||
// Orphan all payments
|
||||
if (payments != null)
|
||||
for (PaymentData paymentData : payments) {
|
||||
Account recipient = new Account(this.repository, paymentData.getRecipient());
|
||||
long assetId = paymentData.getAssetId();
|
||||
BigDecimal amount = paymentData.getAmount();
|
||||
for (PaymentData paymentData : payments) {
|
||||
Account recipient = new Account(this.repository, paymentData.getRecipient());
|
||||
long assetId = paymentData.getAssetId();
|
||||
BigDecimal amount = paymentData.getAmount();
|
||||
|
||||
// Update sender's balance due to amount
|
||||
sender.setConfirmedBalance(assetId, sender.getConfirmedBalance(assetId).add(amount));
|
||||
// Update sender's balance due to amount
|
||||
sender.setConfirmedBalance(assetId, sender.getConfirmedBalance(assetId).add(amount));
|
||||
|
||||
// Update recipient's balance
|
||||
recipient.setConfirmedBalance(assetId, recipient.getConfirmedBalance(assetId).subtract(amount));
|
||||
// Update recipient's balance
|
||||
recipient.setConfirmedBalance(assetId, recipient.getConfirmedBalance(assetId).subtract(amount));
|
||||
|
||||
/*
|
||||
* For QORA amounts only: If recipient's last reference is this transaction's signature, then they can't have made any transactions of their own
|
||||
* (which would have changed their last reference) thus this is their first reference so remove it.
|
||||
*/
|
||||
if ((alwaysUninitializeRecipientReference || assetId == Asset.QORA) && Arrays.equals(recipient.getLastReference(), signature))
|
||||
recipient.setLastReference(null);
|
||||
}
|
||||
/*
|
||||
* For QORA amounts only: If recipient's last reference is this transaction's signature, then they can't have made any transactions of their own
|
||||
* (which would have changed their last reference) thus this is their first reference so remove it.
|
||||
*/
|
||||
if ((alwaysUninitializeRecipientReference || assetId == Asset.QORA) && Arrays.equals(recipient.getLastReference(), signature))
|
||||
recipient.setLastReference(null);
|
||||
}
|
||||
}
|
||||
|
||||
public void orphan(byte[] senderPublicKey, PaymentData paymentData, BigDecimal fee, byte[] signature, byte[] reference,
|
||||
|
179
src/qora/transaction/ATTransaction.java
Normal file
179
src/qora/transaction/ATTransaction.java
Normal file
@ -0,0 +1,179 @@
|
||||
package qora.transaction;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
import data.assets.AssetData;
|
||||
import data.transaction.ATTransactionData;
|
||||
import data.transaction.TransactionData;
|
||||
import qora.account.Account;
|
||||
import qora.assets.Asset;
|
||||
import qora.crypto.Crypto;
|
||||
import repository.DataException;
|
||||
import repository.Repository;
|
||||
|
||||
public class ATTransaction extends Transaction {
|
||||
|
||||
// Properties
|
||||
private ATTransactionData atTransactionData;
|
||||
|
||||
// Other useful constants
|
||||
public static final int MAX_DATA_SIZE = 256;
|
||||
|
||||
// Constructors
|
||||
|
||||
public ATTransaction(Repository repository, TransactionData transactionData) {
|
||||
super(repository, transactionData);
|
||||
|
||||
this.atTransactionData = (ATTransactionData) this.transactionData;
|
||||
}
|
||||
|
||||
// More information
|
||||
|
||||
@Override
|
||||
public List<Account> getRecipientAccounts() throws DataException {
|
||||
return Collections.singletonList(new Account(this.repository, this.atTransactionData.getRecipient()));
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isInvolved(Account account) throws DataException {
|
||||
String address = account.getAddress();
|
||||
|
||||
if (address.equals(this.atTransactionData.getATAddress()))
|
||||
return true;
|
||||
|
||||
if (address.equals(this.atTransactionData.getRecipient()))
|
||||
return true;
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public BigDecimal getAmount(Account account) throws DataException {
|
||||
String address = account.getAddress();
|
||||
BigDecimal amount = BigDecimal.ZERO.setScale(8);
|
||||
String atAddress = this.atTransactionData.getATAddress();
|
||||
|
||||
if (address.equals(atAddress)) {
|
||||
amount = amount.subtract(this.atTransactionData.getFee());
|
||||
|
||||
if (this.atTransactionData.getAmount() != null && this.atTransactionData.getAssetId() == Asset.QORA)
|
||||
amount = amount.subtract(this.atTransactionData.getAmount());
|
||||
}
|
||||
|
||||
if (address.equals(this.atTransactionData.getRecipient()) && this.atTransactionData.getAmount() != null)
|
||||
amount = amount.add(this.atTransactionData.getAmount());
|
||||
|
||||
return amount;
|
||||
}
|
||||
|
||||
// Navigation
|
||||
|
||||
public Account getATAccount() throws DataException {
|
||||
return new Account(this.repository, this.atTransactionData.getATAddress());
|
||||
}
|
||||
|
||||
public Account getRecipient() throws DataException {
|
||||
return new Account(this.repository, this.atTransactionData.getRecipient());
|
||||
}
|
||||
|
||||
// Processing
|
||||
|
||||
@Override
|
||||
public ValidationResult isValid() throws DataException {
|
||||
// Check reference is correct
|
||||
Account atAccount = getATAccount();
|
||||
if (!Arrays.equals(atAccount.getLastReference(), atTransactionData.getReference()))
|
||||
return ValidationResult.INVALID_REFERENCE;
|
||||
|
||||
if (this.atTransactionData.getMessage().length > MAX_DATA_SIZE)
|
||||
return ValidationResult.INVALID_DATA_LENGTH;
|
||||
|
||||
BigDecimal amount = this.atTransactionData.getAmount();
|
||||
|
||||
// If we have no payment then we're done
|
||||
if (amount == null)
|
||||
return ValidationResult.OK;
|
||||
|
||||
// Check amount is zero or positive
|
||||
if (amount.compareTo(BigDecimal.ZERO) < 0)
|
||||
return ValidationResult.NEGATIVE_AMOUNT;
|
||||
|
||||
// Check recipient address is valid
|
||||
if (!Crypto.isValidAddress(this.atTransactionData.getRecipient()))
|
||||
return ValidationResult.INVALID_ADDRESS;
|
||||
|
||||
long assetId = this.atTransactionData.getAssetId();
|
||||
AssetData assetData = this.repository.getAssetRepository().fromAssetId(assetId);
|
||||
// Check asset even exists
|
||||
if (assetData == null)
|
||||
return ValidationResult.ASSET_DOES_NOT_EXIST;
|
||||
|
||||
// Check asset amount is integer if asset is not divisible
|
||||
if (!assetData.getIsDivisible() && amount.stripTrailingZeros().scale() > 0)
|
||||
return ValidationResult.INVALID_AMOUNT;
|
||||
|
||||
Account sender = getATAccount();
|
||||
// Check sender has enough of asset
|
||||
if (sender.getConfirmedBalance(assetId).compareTo(amount) < 0)
|
||||
return ValidationResult.NO_BALANCE;
|
||||
|
||||
return ValidationResult.OK;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void process() throws DataException {
|
||||
// Save this transaction itself
|
||||
this.repository.getTransactionRepository().save(this.transactionData);
|
||||
|
||||
if (this.atTransactionData.getAmount() != null) {
|
||||
Account sender = getATAccount();
|
||||
Account recipient = getRecipient();
|
||||
|
||||
long assetId = this.atTransactionData.getAssetId();
|
||||
BigDecimal amount = this.atTransactionData.getAmount();
|
||||
|
||||
// Update sender's balance due to amount
|
||||
sender.setConfirmedBalance(assetId, sender.getConfirmedBalance(assetId).subtract(amount));
|
||||
|
||||
// Update recipient's balance
|
||||
recipient.setConfirmedBalance(assetId, recipient.getConfirmedBalance(assetId).add(amount));
|
||||
|
||||
// For QORA amounts only: if recipient has no reference yet, then this is their starting reference
|
||||
if (assetId == Asset.QORA && recipient.getLastReference() == null)
|
||||
// In Qora1 last reference was set to 64-bytes of zero
|
||||
// In Qora2 we use AT-Transction's signature, which makes more sense
|
||||
recipient.setLastReference(this.atTransactionData.getSignature());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void orphan() throws DataException {
|
||||
// Delete this transaction
|
||||
this.repository.getTransactionRepository().delete(this.transactionData);
|
||||
|
||||
if (this.atTransactionData.getAmount() != null) {
|
||||
Account sender = getATAccount();
|
||||
Account recipient = getRecipient();
|
||||
|
||||
long assetId = this.atTransactionData.getAssetId();
|
||||
BigDecimal amount = this.atTransactionData.getAmount();
|
||||
|
||||
// Update sender's balance due to amount
|
||||
sender.setConfirmedBalance(assetId, sender.getConfirmedBalance(assetId).add(amount));
|
||||
|
||||
// Update recipient's balance
|
||||
recipient.setConfirmedBalance(assetId, recipient.getConfirmedBalance(assetId).subtract(amount));
|
||||
|
||||
/*
|
||||
* For QORA amounts only: If recipient's last reference is this transaction's signature, then they can't have made any transactions of their own
|
||||
* (which would have changed their last reference) thus this is their first reference so remove it.
|
||||
*/
|
||||
if (assetId == Asset.QORA && Arrays.equals(recipient.getLastReference(), this.atTransactionData.getSignature()))
|
||||
recipient.setLastReference(null);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@ -149,9 +149,9 @@ public class ArbitraryTransaction extends Transaction {
|
||||
// Make sure directory structure exists
|
||||
try {
|
||||
Files.createDirectories(dataPath.getParent());
|
||||
} catch (IOException e1) {
|
||||
} catch (IOException e) {
|
||||
// TODO Auto-generated catch block
|
||||
e1.printStackTrace();
|
||||
e.printStackTrace();
|
||||
}
|
||||
|
||||
// Output actual transaction data
|
||||
|
@ -75,6 +75,13 @@ public class DeployATTransaction extends Transaction {
|
||||
return amount;
|
||||
}
|
||||
|
||||
/** Returns AT version from the header bytes */
|
||||
private short getVersion() {
|
||||
byte[] creationBytes = deployATTransactionData.getCreationBytes();
|
||||
short version = (short) (creationBytes[0] | (creationBytes[1] << 8)); // Little-endian
|
||||
return version;
|
||||
}
|
||||
|
||||
/** Make sure deployATTransactionData has an ATAddress */
|
||||
private void ensureATAddress() throws DataException {
|
||||
if (this.deployATTransactionData.getATAddress() != null)
|
||||
@ -82,7 +89,7 @@ public class DeployATTransaction extends Transaction {
|
||||
|
||||
int blockHeight = this.getHeight();
|
||||
if (blockHeight == 0)
|
||||
blockHeight = this.repository.getBlockRepository().getBlockchainHeight();
|
||||
blockHeight = this.repository.getBlockRepository().getBlockchainHeight() + 1;
|
||||
|
||||
try {
|
||||
byte[] name = this.deployATTransactionData.getName().getBytes("UTF-8");
|
||||
@ -163,11 +170,8 @@ public class DeployATTransaction extends Transaction {
|
||||
if (creator.getConfirmedBalance(Asset.QORA).compareTo(minimumBalance) < 0)
|
||||
return ValidationResult.NO_BALANCE;
|
||||
|
||||
// Check creation bytes are valid (for v3+)
|
||||
byte[] creationBytes = deployATTransactionData.getCreationBytes();
|
||||
short version = (short) (creationBytes[0] | (creationBytes[1] << 8)); // Little-endian
|
||||
|
||||
if (version >= 3) {
|
||||
// Check creation bytes are valid (for v2+)
|
||||
if (this.getVersion() >= 2) {
|
||||
// Do actual validation
|
||||
} else {
|
||||
// Skip validation for old, dead ATs
|
||||
@ -194,6 +198,13 @@ public class DeployATTransaction extends Transaction {
|
||||
|
||||
// Update creator's reference
|
||||
creator.setLastReference(deployATTransactionData.getSignature());
|
||||
|
||||
// Update AT's reference, which also creates AT account
|
||||
Account atAccount = this.getATAccount();
|
||||
atAccount.setLastReference(deployATTransactionData.getSignature());
|
||||
|
||||
// Update AT's balance
|
||||
atAccount.setConfirmedBalance(Asset.QORA, deployATTransactionData.getAmount());
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -212,6 +223,9 @@ public class DeployATTransaction extends Transaction {
|
||||
|
||||
// Update creator's reference
|
||||
creator.setLastReference(deployATTransactionData.getReference());
|
||||
|
||||
// Delete AT's account
|
||||
this.repository.getAccountRepository().delete(this.deployATTransactionData.getATAddress());
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -133,12 +133,13 @@ public class GenesisTransaction extends Transaction {
|
||||
// Save this transaction itself
|
||||
this.repository.getTransactionRepository().save(this.transactionData);
|
||||
|
||||
// Update recipient's balance
|
||||
Account recipient = new Account(repository, genesisTransactionData.getRecipient());
|
||||
recipient.setConfirmedBalance(Asset.QORA, genesisTransactionData.getAmount());
|
||||
|
||||
// Set recipient's starting reference
|
||||
// Set recipient's starting reference (also creates account)
|
||||
recipient.setLastReference(genesisTransactionData.getSignature());
|
||||
|
||||
// Update recipient's balance
|
||||
recipient.setConfirmedBalance(Asset.QORA, genesisTransactionData.getAmount());
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -146,12 +147,8 @@ public class GenesisTransaction extends Transaction {
|
||||
// Delete this transaction
|
||||
this.repository.getTransactionRepository().delete(this.transactionData);
|
||||
|
||||
// Delete recipient's balance
|
||||
Account recipient = new Account(repository, genesisTransactionData.getRecipient());
|
||||
recipient.deleteBalance(Asset.QORA);
|
||||
|
||||
// Delete recipient's last reference
|
||||
recipient.setLastReference(null);
|
||||
// Delete recipient's account (and balance)
|
||||
this.repository.getAccountRepository().delete(genesisTransactionData.getRecipient());
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -85,7 +85,7 @@ public class MessageTransaction extends Transaction {
|
||||
// Processing
|
||||
|
||||
private PaymentData getPaymentData() {
|
||||
return new PaymentData(messageTransactionData.getRecipient(), Asset.QORA, messageTransactionData.getAmount());
|
||||
return new PaymentData(messageTransactionData.getRecipient(), messageTransactionData.getAssetId(), messageTransactionData.getAmount());
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -193,11 +193,14 @@ public abstract class Transaction {
|
||||
case MULTIPAYMENT:
|
||||
return new MultiPaymentTransaction(repository, transactionData);
|
||||
|
||||
case DEPLOY_AT:
|
||||
return new DeployATTransaction(repository, transactionData);
|
||||
|
||||
case MESSAGE:
|
||||
return new MessageTransaction(repository, transactionData);
|
||||
|
||||
case DEPLOY_AT:
|
||||
return new DeployATTransaction(repository, transactionData);
|
||||
case AT:
|
||||
return new ATTransaction(repository, transactionData);
|
||||
|
||||
default:
|
||||
throw new IllegalStateException("Unsupported transaction type [" + transactionData.getType().value + "] during fetch from repository");
|
||||
|
@ -1,5 +1,7 @@
|
||||
package repository;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import data.at.ATData;
|
||||
import data.at.ATStateData;
|
||||
|
||||
@ -17,8 +19,14 @@ public interface ATRepository {
|
||||
|
||||
public ATStateData getATState(String atAddress, int height) throws DataException;
|
||||
|
||||
public List<ATStateData> getBlockATStatesFromHeight(int height) throws DataException;
|
||||
|
||||
public void save(ATStateData atStateData) throws DataException;
|
||||
|
||||
/** Delete AT's state data at this height */
|
||||
public void delete(String atAddress, int height) throws DataException;
|
||||
|
||||
/** Delete state data for all ATs at this height */
|
||||
public void deleteATStates(int height) throws DataException;
|
||||
|
||||
}
|
||||
|
@ -7,10 +7,14 @@ public interface AccountRepository {
|
||||
|
||||
// General account
|
||||
|
||||
public void create(String address) throws DataException;
|
||||
|
||||
public AccountData getAccount(String address) throws DataException;
|
||||
|
||||
public void save(AccountData accountData) throws DataException;
|
||||
|
||||
public void delete(String address) throws DataException;
|
||||
|
||||
// Account balances
|
||||
|
||||
public AccountBalanceData getBalance(String address, long assetId) throws DataException;
|
||||
|
@ -3,6 +3,10 @@ package repository.hsqldb;
|
||||
import java.math.BigDecimal;
|
||||
import java.sql.ResultSet;
|
||||
import java.sql.SQLException;
|
||||
import java.sql.Timestamp;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Calendar;
|
||||
import java.util.List;
|
||||
|
||||
import data.at.ATData;
|
||||
import data.at.ATStateData;
|
||||
@ -21,30 +25,32 @@ public class HSQLDBATRepository implements ATRepository {
|
||||
|
||||
@Override
|
||||
public ATData fromATAddress(String atAddress) throws DataException {
|
||||
try (ResultSet resultSet = this.repository
|
||||
.checkedExecute("SELECT owner, asset_name, description, quantity, is_divisible, reference FROM Assets WHERE AT_address = ?", atAddress)) {
|
||||
try (ResultSet resultSet = this.repository.checkedExecute(
|
||||
"SELECT creator, creation, version, code_bytes, is_sleeping, sleep_until_height, is_finished, had_fatal_error, is_frozen, frozen_balance FROM ATs WHERE AT_address = ?",
|
||||
atAddress)) {
|
||||
if (resultSet == null)
|
||||
return null;
|
||||
|
||||
int version = resultSet.getInt(1);
|
||||
byte[] codeBytes = resultSet.getBytes(2); // Actually BLOB
|
||||
boolean isSleeping = resultSet.getBoolean(3);
|
||||
String creator = resultSet.getString(1);
|
||||
long creation = resultSet.getTimestamp(2, Calendar.getInstance(HSQLDBRepository.UTC)).getTime();
|
||||
int version = resultSet.getInt(3);
|
||||
byte[] codeBytes = resultSet.getBytes(4); // Actually BLOB
|
||||
boolean isSleeping = resultSet.getBoolean(5);
|
||||
|
||||
Integer sleepUntilHeight = resultSet.getInt(4);
|
||||
Integer sleepUntilHeight = resultSet.getInt(6);
|
||||
if (resultSet.wasNull())
|
||||
sleepUntilHeight = null;
|
||||
|
||||
boolean isFinished = resultSet.getBoolean(5);
|
||||
boolean hadFatalError = resultSet.getBoolean(6);
|
||||
boolean isFrozen = resultSet.getBoolean(7);
|
||||
boolean isFinished = resultSet.getBoolean(7);
|
||||
boolean hadFatalError = resultSet.getBoolean(8);
|
||||
boolean isFrozen = resultSet.getBoolean(9);
|
||||
|
||||
BigDecimal frozenBalance = resultSet.getBigDecimal(8);
|
||||
BigDecimal frozenBalance = resultSet.getBigDecimal(10);
|
||||
if (resultSet.wasNull())
|
||||
frozenBalance = null;
|
||||
|
||||
byte[] deploySignature = resultSet.getBytes(9);
|
||||
|
||||
return new ATData(atAddress, version, codeBytes, isSleeping, sleepUntilHeight, isFinished, hadFatalError, isFrozen, frozenBalance, deploySignature);
|
||||
return new ATData(atAddress, creator, creation, version, codeBytes, isSleeping, sleepUntilHeight, isFinished, hadFatalError, isFrozen,
|
||||
frozenBalance);
|
||||
} catch (SQLException e) {
|
||||
throw new DataException("Unable to fetch AT from repository", e);
|
||||
}
|
||||
@ -54,10 +60,10 @@ public class HSQLDBATRepository implements ATRepository {
|
||||
public void save(ATData atData) throws DataException {
|
||||
HSQLDBSaver saveHelper = new HSQLDBSaver("ATs");
|
||||
|
||||
saveHelper.bind("AT_address", atData.getATAddress()).bind("version", atData.getVersion()).bind("code_bytes", atData.getCodeBytes())
|
||||
.bind("is_sleeping", atData.getIsSleeping()).bind("sleep_until_height", atData.getSleepUntilHeight())
|
||||
.bind("is_finished", atData.getIsFinished()).bind("had_fatal_error", atData.getHadFatalError()).bind("is_frozen", atData.getIsFrozen())
|
||||
.bind("frozen_balance", atData.getFrozenBalance()).bind("deploy_signature", atData.getDeploySignature());
|
||||
saveHelper.bind("AT_address", atData.getATAddress()).bind("creator", atData.getCreator()).bind("creation", new Timestamp(atData.getCreation()))
|
||||
.bind("version", atData.getVersion()).bind("code_bytes", atData.getCodeBytes()).bind("is_sleeping", atData.getIsSleeping())
|
||||
.bind("sleep_until_height", atData.getSleepUntilHeight()).bind("is_finished", atData.getIsFinished())
|
||||
.bind("had_fatal_error", atData.getHadFatalError()).bind("is_frozen", atData.getIsFrozen()).bind("frozen_balance", atData.getFrozenBalance());
|
||||
|
||||
try {
|
||||
saveHelper.execute(this.repository);
|
||||
@ -69,7 +75,7 @@ public class HSQLDBATRepository implements ATRepository {
|
||||
@Override
|
||||
public void delete(String atAddress) throws DataException {
|
||||
try {
|
||||
this.repository.delete("ATs", "atAddress = ?", atAddress);
|
||||
this.repository.delete("ATs", "AT_address = ?", atAddress);
|
||||
// AT States also deleted via ON DELETE CASCADE
|
||||
} catch (SQLException e) {
|
||||
throw new DataException("Unable to delete AT from repository", e);
|
||||
@ -80,28 +86,63 @@ public class HSQLDBATRepository implements ATRepository {
|
||||
|
||||
@Override
|
||||
public ATStateData getATState(String atAddress, int height) throws DataException {
|
||||
try (ResultSet resultSet = this.repository.checkedExecute("SELECT state_data FROM ATStates WHERE AT_address = ? AND height = ?", atAddress, height)) {
|
||||
try (ResultSet resultSet = this.repository
|
||||
.checkedExecute("SELECT creation, state_data, state_hash, fees FROM ATStates WHERE AT_address = ? AND height = ?", atAddress, height)) {
|
||||
if (resultSet == null)
|
||||
return null;
|
||||
|
||||
byte[] stateData = resultSet.getBytes(1); // Actually BLOB
|
||||
long creation = resultSet.getTimestamp(1, Calendar.getInstance(HSQLDBRepository.UTC)).getTime();
|
||||
byte[] stateData = resultSet.getBytes(2); // Actually BLOB
|
||||
byte[] stateHash = resultSet.getBytes(3);
|
||||
BigDecimal fees = resultSet.getBigDecimal(4);
|
||||
|
||||
return new ATStateData(atAddress, height, stateData);
|
||||
return new ATStateData(atAddress, height, creation, stateData, stateHash, fees);
|
||||
} catch (SQLException e) {
|
||||
throw new DataException("Unable to fetch AT State from repository", e);
|
||||
throw new DataException("Unable to fetch AT state from repository", e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<ATStateData> getBlockATStatesFromHeight(int height) throws DataException {
|
||||
List<ATStateData> atStates = new ArrayList<ATStateData>();
|
||||
|
||||
try (ResultSet resultSet = this.repository.checkedExecute("SELECT AT_address, state_hash, fees FROM ATStates WHERE height = ? ORDER BY creation ASC",
|
||||
height)) {
|
||||
if (resultSet == null)
|
||||
return atStates; // No atStates in this block
|
||||
|
||||
// NB: do-while loop because .checkedExecute() implicitly calls ResultSet.next() for us
|
||||
do {
|
||||
String atAddress = resultSet.getString(1);
|
||||
byte[] stateHash = resultSet.getBytes(2);
|
||||
BigDecimal fees = resultSet.getBigDecimal(3);
|
||||
|
||||
ATStateData atStateData = new ATStateData(atAddress, height, stateHash, fees);
|
||||
atStates.add(atStateData);
|
||||
} while (resultSet.next());
|
||||
} catch (SQLException e) {
|
||||
throw new DataException("Unable to fetch AT states for this height from repository", e);
|
||||
}
|
||||
|
||||
return atStates;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void save(ATStateData atStateData) throws DataException {
|
||||
// We shouldn't ever save partial ATStateData
|
||||
if (atStateData.getCreation() == null || atStateData.getStateHash() == null || atStateData.getHeight() == null)
|
||||
throw new IllegalArgumentException("Refusing to save partial AT state into repository!");
|
||||
|
||||
HSQLDBSaver saveHelper = new HSQLDBSaver("ATStates");
|
||||
|
||||
saveHelper.bind("AT_address", atStateData.getATAddress()).bind("height", atStateData.getHeight()).bind("state_data", atStateData.getStateData());
|
||||
saveHelper.bind("AT_address", atStateData.getATAddress()).bind("height", atStateData.getHeight())
|
||||
.bind("creation", new Timestamp(atStateData.getCreation())).bind("state_data", atStateData.getStateData())
|
||||
.bind("state_hash", atStateData.getStateHash()).bind("fees", atStateData.getFees());
|
||||
|
||||
try {
|
||||
saveHelper.execute(this.repository);
|
||||
} catch (SQLException e) {
|
||||
throw new DataException("Unable to save AT State into repository", e);
|
||||
throw new DataException("Unable to save AT state into repository", e);
|
||||
}
|
||||
}
|
||||
|
||||
@ -110,7 +151,16 @@ public class HSQLDBATRepository implements ATRepository {
|
||||
try {
|
||||
this.repository.delete("ATStates", "AT_address = ? AND height = ?", atAddress, height);
|
||||
} catch (SQLException e) {
|
||||
throw new DataException("Unable to delete AT State from repository", e);
|
||||
throw new DataException("Unable to delete AT state from repository", e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void deleteATStates(int height) throws DataException {
|
||||
try {
|
||||
this.repository.delete("ATStates", "height = ?", height);
|
||||
} catch (SQLException e) {
|
||||
throw new DataException("Unable to delete AT states from repository", e);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -17,6 +17,21 @@ public class HSQLDBAccountRepository implements AccountRepository {
|
||||
this.repository = repository;
|
||||
}
|
||||
|
||||
// General account
|
||||
|
||||
@Override
|
||||
public void create(String address) throws DataException {
|
||||
HSQLDBSaver saveHelper = new HSQLDBSaver("Accounts");
|
||||
|
||||
saveHelper.bind("account", address);
|
||||
|
||||
try {
|
||||
saveHelper.execute(this.repository);
|
||||
} catch (SQLException e) {
|
||||
throw new DataException("Unable to create account in repository", e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public AccountData getAccount(String address) throws DataException {
|
||||
try (ResultSet resultSet = this.repository.checkedExecute("SELECT reference FROM Accounts WHERE account = ?", address)) {
|
||||
@ -32,6 +47,7 @@ public class HSQLDBAccountRepository implements AccountRepository {
|
||||
@Override
|
||||
public void save(AccountData accountData) throws DataException {
|
||||
HSQLDBSaver saveHelper = new HSQLDBSaver("Accounts");
|
||||
|
||||
saveHelper.bind("account", accountData.getAddress()).bind("reference", accountData.getReference());
|
||||
|
||||
try {
|
||||
@ -41,6 +57,19 @@ public class HSQLDBAccountRepository implements AccountRepository {
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void delete(String address) throws DataException {
|
||||
// NOTE: Account balances are deleted automatically by the database thanks to "ON DELETE CASCADE" in AccountBalances' FOREIGN KEY
|
||||
// definition.
|
||||
try {
|
||||
this.repository.delete("Accounts", "account = ?", address);
|
||||
} catch (SQLException e) {
|
||||
throw new DataException("Unable to delete account from repository", e);
|
||||
}
|
||||
}
|
||||
|
||||
// Account balances
|
||||
|
||||
@Override
|
||||
public AccountBalanceData getBalance(String address, long assetId) throws DataException {
|
||||
try (ResultSet resultSet = this.repository.checkedExecute("SELECT balance FROM AccountBalances WHERE account = ? and asset_id = ?", address, assetId)) {
|
||||
@ -58,6 +87,7 @@ public class HSQLDBAccountRepository implements AccountRepository {
|
||||
@Override
|
||||
public void save(AccountBalanceData accountBalanceData) throws DataException {
|
||||
HSQLDBSaver saveHelper = new HSQLDBSaver("AccountBalances");
|
||||
|
||||
saveHelper.bind("account", accountBalanceData.getAddress()).bind("asset_id", accountBalanceData.getAssetId()).bind("balance",
|
||||
accountBalanceData.getBalance());
|
||||
|
||||
|
@ -18,7 +18,7 @@ import repository.TransactionRepository;
|
||||
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";
|
||||
+ "transactions_signature, height, generation, generating_balance, generator, generator_signature, AT_count, AT_fees";
|
||||
|
||||
protected HSQLDBRepository repository;
|
||||
|
||||
@ -41,11 +41,11 @@ public class HSQLDBBlockRepository implements BlockRepository {
|
||||
BigDecimal generatingBalance = resultSet.getBigDecimal(8);
|
||||
byte[] generatorPublicKey = resultSet.getBytes(9);
|
||||
byte[] generatorSignature = resultSet.getBytes(10);
|
||||
byte[] atBytes = resultSet.getBytes(11);
|
||||
int atCount = resultSet.getInt(11);
|
||||
BigDecimal atFees = resultSet.getBigDecimal(12);
|
||||
|
||||
return new BlockData(version, reference, transactionCount, totalFees, transactionsSignature, height, timestamp, generatingBalance,
|
||||
generatorPublicKey, generatorSignature, atBytes, atFees);
|
||||
generatorPublicKey, generatorSignature, atCount, atFees);
|
||||
} catch (SQLException e) {
|
||||
throw new DataException("Error extracting data from result set", e);
|
||||
}
|
||||
@ -62,7 +62,7 @@ public class HSQLDBBlockRepository implements BlockRepository {
|
||||
|
||||
@Override
|
||||
public BlockData fromReference(byte[] reference) throws DataException {
|
||||
try (ResultSet resultSet = this.repository.checkedExecute("SELECT " + BLOCK_DB_COLUMNS + " FROM Blocks WHERE height = ?", reference)) {
|
||||
try (ResultSet resultSet = this.repository.checkedExecute("SELECT " + BLOCK_DB_COLUMNS + " FROM Blocks WHERE reference = ?", reference)) {
|
||||
return getBlockFromResultSet(resultSet);
|
||||
} catch (SQLException e) {
|
||||
throw new DataException("Error loading data from DB", e);
|
||||
@ -123,7 +123,7 @@ public class HSQLDBBlockRepository implements BlockRepository {
|
||||
transactions.add(transactionRepo.fromSignature(transactionSignature));
|
||||
} while (resultSet.next());
|
||||
} catch (SQLException e) {
|
||||
throw new DataException(e);
|
||||
throw new DataException("Unable to fetch block's transactions from repository", e);
|
||||
}
|
||||
|
||||
return transactions;
|
||||
@ -138,7 +138,7 @@ public class HSQLDBBlockRepository implements BlockRepository {
|
||||
.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());
|
||||
.bind("AT_count", blockData.getATCount()).bind("AT_fees", blockData.getATFees());
|
||||
|
||||
try {
|
||||
saveHelper.execute(this.repository);
|
||||
@ -159,6 +159,7 @@ public class HSQLDBBlockRepository implements BlockRepository {
|
||||
@Override
|
||||
public void save(BlockTransactionData blockTransactionData) throws DataException {
|
||||
HSQLDBSaver saveHelper = new HSQLDBSaver("BlockTransactions");
|
||||
|
||||
saveHelper.bind("block_signature", blockTransactionData.getBlockSignature()).bind("sequence", blockTransactionData.getSequence())
|
||||
.bind("transaction_signature", blockTransactionData.getTransactionSignature());
|
||||
|
||||
|
@ -85,7 +85,7 @@ public class HSQLDBDatabaseUpdates {
|
||||
stmt.execute("CREATE TYPE Signature AS VARBINARY(64)");
|
||||
stmt.execute("CREATE TYPE QoraAddress AS VARCHAR(36)");
|
||||
stmt.execute("CREATE TYPE QoraPublicKey AS VARBINARY(32)");
|
||||
stmt.execute("CREATE TYPE QoraAmount AS DECIMAL(19, 8)");
|
||||
stmt.execute("CREATE TYPE QoraAmount AS DECIMAL(27, 8)");
|
||||
stmt.execute("CREATE TYPE RegisteredName AS VARCHAR(400) COLLATE SQL_TEXT_NO_PAD");
|
||||
stmt.execute("CREATE TYPE NameData AS VARCHAR(4000)");
|
||||
stmt.execute("CREATE TYPE PollName AS VARCHAR(400) COLLATE SQL_TEXT_NO_PAD");
|
||||
@ -99,6 +99,7 @@ public class HSQLDBDatabaseUpdates {
|
||||
stmt.execute("CREATE TYPE ATType AS VARCHAR(200) COLLATE SQL_TEXT_UCC_NO_PAD");
|
||||
stmt.execute("CREATE TYPE ATCode AS BLOB(64K)"); // 16bit * 1
|
||||
stmt.execute("CREATE TYPE ATState AS BLOB(1M)"); // 16bit * 8 + 16bit * 4 + 16bit * 4
|
||||
stmt.execute("CREATE TYPE ATStateHash as VARBINARY(32)");
|
||||
stmt.execute("CREATE TYPE ATMessage AS VARBINARY(256)");
|
||||
break;
|
||||
|
||||
@ -107,7 +108,7 @@ public class HSQLDBDatabaseUpdates {
|
||||
stmt.execute("CREATE TABLE Blocks (signature BlockSignature PRIMARY KEY, version TINYINT NOT NULL, reference BlockSignature, "
|
||||
+ "transaction_count INTEGER NOT NULL, total_fees QoraAmount NOT NULL, transactions_signature Signature NOT NULL, "
|
||||
+ "height INTEGER NOT NULL, generation TIMESTAMP WITH TIME ZONE NOT NULL, generating_balance QoraAmount NOT NULL, "
|
||||
+ "generator QoraPublicKey NOT NULL, generator_signature Signature NOT NULL, AT_data VARBINARY(20000), AT_fees QoraAmount)");
|
||||
+ "generator QoraPublicKey NOT NULL, generator_signature Signature NOT NULL, AT_count INTEGER NOT NULL, AT_fees QoraAmount NOT NULL)");
|
||||
// For finding blocks by height.
|
||||
stmt.execute("CREATE INDEX BlockHeightIndex ON Blocks (height)");
|
||||
// For finding blocks by the account that generated them.
|
||||
@ -302,7 +303,7 @@ public class HSQLDBDatabaseUpdates {
|
||||
// Accounts
|
||||
stmt.execute("CREATE TABLE Accounts (account QoraAddress, reference Signature, PRIMARY KEY (account))");
|
||||
stmt.execute("CREATE TABLE AccountBalances (account QoraAddress, asset_id AssetID, balance QoraAmount NOT NULL, "
|
||||
+ "PRIMARY KEY (account, asset_id))");
|
||||
+ "PRIMARY KEY (account, asset_id), FOREIGN KEY (account) REFERENCES Accounts (account) ON DELETE CASCADE)");
|
||||
break;
|
||||
|
||||
case 23:
|
||||
@ -352,17 +353,21 @@ public class HSQLDBDatabaseUpdates {
|
||||
|
||||
case 27:
|
||||
// CIYAM Automated Transactions
|
||||
stmt.execute("CREATE TABLE ATs (AT_address QoraAddress, version INTEGER NOT NULL, code_bytes ATCode NOT NULL, "
|
||||
+ "is_sleeping BOOLEAN NOT NULL, sleep_until_height INTEGER, is_finished BOOLEAN NOT NULL, had_fatal_error BOOLEAN NOT NULL, "
|
||||
+ "is_frozen BOOLEAN NOT NULL, frozen_balance QoraAmount, deploy_signature Signature NOT NULL, PRIMARY key (AT_address))");
|
||||
// For finding executable ATs
|
||||
stmt.execute("CREATE INDEX ATIndex on ATs (is_finished, AT_address)");
|
||||
stmt.execute("CREATE TABLE ATs (AT_address QoraAddress, creator QoraAddress, creation TIMESTAMP WITH TIME ZONE, version INTEGER NOT NULL, "
|
||||
+ "code_bytes ATCode NOT NULL, is_sleeping BOOLEAN NOT NULL, sleep_until_height INTEGER, "
|
||||
+ "is_finished BOOLEAN NOT NULL, had_fatal_error BOOLEAN NOT NULL, is_frozen BOOLEAN NOT NULL, frozen_balance QoraAmount, "
|
||||
+ "PRIMARY key (AT_address))");
|
||||
// For finding executable ATs, ordered by creation timestamp
|
||||
stmt.execute("CREATE INDEX ATIndex on ATs (is_finished, creation, AT_address)");
|
||||
// AT state on a per-block basis
|
||||
stmt.execute("CREATE TABLE ATStates (AT_address QoraAddress, height INTEGER NOT NULL, state_data ATState, "
|
||||
stmt.execute("CREATE TABLE ATStates (AT_address QoraAddress, height INTEGER NOT NULL, creation TIMESTAMP WITH TIME ZONE, "
|
||||
+ "state_data ATState, state_hash ATStateHash NOT NULL, fees QoraAmount NOT NULL, "
|
||||
+ "PRIMARY KEY (AT_address, height), FOREIGN KEY (AT_address) REFERENCES ATs (AT_address) ON DELETE CASCADE)");
|
||||
// For finding per-block AT states, ordered by creation timestamp
|
||||
stmt.execute("CREATE INDEX BlockATStateIndex on ATStates (height, creation, AT_address)");
|
||||
// Generated AT Transactions
|
||||
stmt.execute(
|
||||
"CREATE TABLE ATTransactions (signature Signature, sender QoraPublicKey NOT NULL, recipient QoraAddress, amount QoraAmount NOT NULL, message ATMessage, "
|
||||
"CREATE TABLE ATTransactions (signature Signature, AT_address QoraAddress NOT NULL, recipient QoraAddress, amount QoraAmount, asset_id AssetID, message ATMessage, "
|
||||
+ "PRIMARY KEY (signature), FOREIGN KEY (signature) REFERENCES Transactions (signature) ON DELETE CASCADE)");
|
||||
break;
|
||||
|
||||
|
@ -0,0 +1,63 @@
|
||||
package repository.hsqldb.transaction;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.sql.ResultSet;
|
||||
import java.sql.SQLException;
|
||||
|
||||
import data.transaction.ATTransactionData;
|
||||
import data.transaction.TransactionData;
|
||||
import repository.DataException;
|
||||
import repository.hsqldb.HSQLDBRepository;
|
||||
import repository.hsqldb.HSQLDBSaver;
|
||||
|
||||
public class HSQLDBATTransactionRepository extends HSQLDBTransactionRepository {
|
||||
|
||||
public HSQLDBATTransactionRepository(HSQLDBRepository repository) {
|
||||
this.repository = repository;
|
||||
}
|
||||
|
||||
TransactionData fromBase(byte[] signature, byte[] reference, byte[] creatorPublicKey, long timestamp, BigDecimal fee) throws DataException {
|
||||
try (ResultSet resultSet = this.repository
|
||||
.checkedExecute("SELECT AT_address, recipient, amount, asset_id, message FROM ATTransactions WHERE signature = ?", signature)) {
|
||||
if (resultSet == null)
|
||||
return null;
|
||||
|
||||
String atAddress = resultSet.getString(1);
|
||||
String recipient = resultSet.getString(2);
|
||||
|
||||
BigDecimal amount = resultSet.getBigDecimal(3);
|
||||
if (resultSet.wasNull())
|
||||
amount = null;
|
||||
|
||||
Long assetId = resultSet.getLong(4);
|
||||
if (resultSet.wasNull())
|
||||
assetId = null;
|
||||
|
||||
byte[] message = resultSet.getBytes(5);
|
||||
if (resultSet.wasNull())
|
||||
message = null;
|
||||
|
||||
return new ATTransactionData(atAddress, recipient, amount, assetId, message, fee, timestamp, reference, signature);
|
||||
} catch (SQLException e) {
|
||||
throw new DataException("Unable to fetch AT transaction from repository", e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void save(TransactionData transactionData) throws DataException {
|
||||
ATTransactionData atTransactionData = (ATTransactionData) transactionData;
|
||||
|
||||
HSQLDBSaver saveHelper = new HSQLDBSaver("ATTransactions");
|
||||
|
||||
saveHelper.bind("signature", atTransactionData.getSignature()).bind("AT_address", atTransactionData.getATAddress())
|
||||
.bind("recipient", atTransactionData.getRecipient()).bind("amount", atTransactionData.getAmount())
|
||||
.bind("asset_id", atTransactionData.getAssetId()).bind("message", atTransactionData.getMessage());
|
||||
|
||||
try {
|
||||
saveHelper.execute(this.repository);
|
||||
} catch (SQLException e) {
|
||||
throw new DataException("Unable to save AT transaction into repository", e);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@ -37,6 +37,7 @@ public class HSQLDBTransactionRepository implements TransactionRepository {
|
||||
private HSQLDBMultiPaymentTransactionRepository multiPaymentTransactionRepository;
|
||||
private HSQLDBDeployATTransactionRepository deployATTransactionRepository;
|
||||
private HSQLDBMessageTransactionRepository messageTransactionRepository;
|
||||
private HSQLDBATTransactionRepository atTransactionRepository;
|
||||
|
||||
public HSQLDBTransactionRepository(HSQLDBRepository repository) {
|
||||
this.repository = repository;
|
||||
@ -57,6 +58,7 @@ public class HSQLDBTransactionRepository implements TransactionRepository {
|
||||
this.multiPaymentTransactionRepository = new HSQLDBMultiPaymentTransactionRepository(repository);
|
||||
this.deployATTransactionRepository = new HSQLDBDeployATTransactionRepository(repository);
|
||||
this.messageTransactionRepository = new HSQLDBMessageTransactionRepository(repository);
|
||||
this.atTransactionRepository = new HSQLDBATTransactionRepository(repository);
|
||||
}
|
||||
|
||||
protected HSQLDBTransactionRepository() {
|
||||
@ -154,18 +156,30 @@ public class HSQLDBTransactionRepository implements TransactionRepository {
|
||||
case MESSAGE:
|
||||
return this.messageTransactionRepository.fromBase(signature, reference, creatorPublicKey, timestamp, fee);
|
||||
|
||||
case AT:
|
||||
return this.atTransactionRepository.fromBase(signature, reference, creatorPublicKey, timestamp, fee);
|
||||
|
||||
default:
|
||||
throw new DataException("Unsupported transaction type [" + type.value + "] during fetch from HSQLDB repository");
|
||||
throw new DataException("Unsupported transaction type [" + type.name() + "] during fetch from HSQLDB repository");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns payments associated with a transaction's signature.
|
||||
* <p>
|
||||
* Used by various transaction types, like Payment, MultiPayment, ArbitraryTransaction.
|
||||
*
|
||||
* @param signature
|
||||
* @return list of payments, empty if none found
|
||||
* @throws DataException
|
||||
*/
|
||||
protected List<PaymentData> getPaymentsFromSignature(byte[] signature) throws DataException {
|
||||
List<PaymentData> payments = new ArrayList<PaymentData>();
|
||||
|
||||
try (ResultSet resultSet = this.repository.checkedExecute("SELECT recipient, amount, asset_id FROM SharedTransactionPayments WHERE signature = ?",
|
||||
signature)) {
|
||||
if (resultSet == null)
|
||||
return null;
|
||||
|
||||
List<PaymentData> payments = new ArrayList<PaymentData>();
|
||||
return payments;
|
||||
|
||||
// NOTE: do-while because checkedExecute() above has already called rs.next() for us
|
||||
do {
|
||||
@ -317,8 +331,12 @@ public class HSQLDBTransactionRepository implements TransactionRepository {
|
||||
this.messageTransactionRepository.save(transactionData);
|
||||
break;
|
||||
|
||||
case AT:
|
||||
this.atTransactionRepository.save(transactionData);
|
||||
break;
|
||||
|
||||
default:
|
||||
throw new DataException("Unsupported transaction type [" + transactionData.getType().value + "] during save into HSQLDB repository");
|
||||
throw new DataException("Unsupported transaction type [" + transactionData.getType().name() + "] during save into HSQLDB repository");
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -8,6 +8,7 @@ import java.util.Arrays;
|
||||
|
||||
import com.google.common.hash.HashCode;
|
||||
|
||||
import data.at.ATStateData;
|
||||
import data.block.BlockData;
|
||||
import data.block.BlockTransactionData;
|
||||
import data.transaction.DeployATTransactionData;
|
||||
@ -69,13 +70,22 @@ public class ATTests extends Common {
|
||||
long blockTimestamp = 1439997158336L;
|
||||
BigDecimal generatingBalance = BigDecimal.valueOf(1440368826L).setScale(8);
|
||||
byte[] generatorPublicKey = Base58.decode("X4s833bbtghh7gejmaBMbWqD44HrUobw93ANUuaNhFc");
|
||||
byte[] atBytes = HashCode.fromString("17950a6c62d17ff0caa545651c054a105f1c464daca443df846cc6a3d58f764b78c09cff50f0fd9ec2").asBytes();
|
||||
int atCount = 1;
|
||||
BigDecimal atFees = BigDecimal.valueOf(50.0).setScale(8);
|
||||
|
||||
BlockData blockData = new BlockData(version, blockReference, transactionCount, totalFees, transactionsSignature, height, blockTimestamp,
|
||||
generatingBalance, generatorPublicKey, generatorSignature, atBytes, atFees);
|
||||
generatingBalance, generatorPublicKey, generatorSignature, atCount, atFees);
|
||||
|
||||
repository.getBlockRepository().save(blockData);
|
||||
|
||||
byte[] atBytes = HashCode.fromString("17950a6c62d17ff0caa545651c054a105f1c464daca443df846cc6a3d58f764b78c09cff50f0fd9ec2").asBytes();
|
||||
|
||||
String atAddress = Base58.encode(Arrays.copyOfRange(atBytes, 0, 25));
|
||||
byte[] stateHash = Arrays.copyOfRange(atBytes, 25, atBytes.length);
|
||||
|
||||
ATStateData atStateData = new ATStateData(atAddress, height, timestamp, new byte[0], stateHash, atFees);
|
||||
|
||||
repository.getATRepository().save(atStateData);
|
||||
}
|
||||
|
||||
int sequence = 0;
|
||||
|
@ -36,7 +36,7 @@ public class NavigationTests extends Common {
|
||||
|
||||
System.out.println("Block " + blockData.getHeight() + ", signature: " + Base58.encode(blockData.getSignature()));
|
||||
|
||||
assertEquals(49778, blockData.getHeight());
|
||||
assertEquals((Integer) 49778, blockData.getHeight());
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -47,11 +47,7 @@ public class SignatureTests extends Common {
|
||||
PrivateKeyAccount generator = new PrivateKeyAccount(repository,
|
||||
new byte[] { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31 });
|
||||
|
||||
byte[] atBytes = null;
|
||||
|
||||
BigDecimal atFees = null;
|
||||
|
||||
Block block = new Block(repository, version, reference, timestamp, generatingBalance, generator, atBytes, atFees);
|
||||
Block block = new Block(repository, version, reference, timestamp, generatingBalance, generator);
|
||||
block.sign();
|
||||
|
||||
assertTrue(block.isSignatureValid());
|
||||
|
@ -172,7 +172,7 @@ public class TransactionTests {
|
||||
assertEquals(ValidationResult.OK, paymentTransaction.isValid());
|
||||
|
||||
// Forge new block with transaction
|
||||
Block block = new Block(repository, parentBlockData, generator, null, null);
|
||||
Block block = new Block(repository, parentBlockData, generator);
|
||||
block.addTransaction(paymentTransactionData);
|
||||
block.sign();
|
||||
|
||||
@ -233,7 +233,7 @@ public class TransactionTests {
|
||||
assertEquals(ValidationResult.OK, registerNameTransaction.isValid());
|
||||
|
||||
// Forge new block with transaction
|
||||
Block block = new Block(repository, parentBlockData, generator, null, null);
|
||||
Block block = new Block(repository, parentBlockData, generator);
|
||||
block.addTransaction(registerNameTransactionData);
|
||||
block.sign();
|
||||
|
||||
@ -289,7 +289,7 @@ public class TransactionTests {
|
||||
assertEquals(ValidationResult.OK, updateNameTransaction.isValid());
|
||||
|
||||
// Forge new block with transaction
|
||||
Block block = new Block(repository, parentBlockData, generator, null, null);
|
||||
Block block = new Block(repository, parentBlockData, generator);
|
||||
block.addTransaction(updateNameTransactionData);
|
||||
block.sign();
|
||||
|
||||
@ -334,7 +334,7 @@ public class TransactionTests {
|
||||
assertEquals(ValidationResult.OK, sellNameTransaction.isValid());
|
||||
|
||||
// Forge new block with transaction
|
||||
Block block = new Block(repository, parentBlockData, generator, null, null);
|
||||
Block block = new Block(repository, parentBlockData, generator);
|
||||
block.addTransaction(sellNameTransactionData);
|
||||
block.sign();
|
||||
|
||||
@ -385,7 +385,7 @@ public class TransactionTests {
|
||||
assertEquals(ValidationResult.OK, cancelSellNameTransaction.isValid());
|
||||
|
||||
// Forge new block with transaction
|
||||
Block block = new Block(repository, parentBlockData, generator, null, null);
|
||||
Block block = new Block(repository, parentBlockData, generator);
|
||||
block.addTransaction(cancelSellNameTransactionData);
|
||||
block.sign();
|
||||
|
||||
@ -432,7 +432,7 @@ public class TransactionTests {
|
||||
byte[] buyersReference = somePaymentTransaction.getTransactionData().getSignature();
|
||||
|
||||
// Forge new block with transaction
|
||||
Block block = new Block(repository, parentBlockData, generator, null, null);
|
||||
Block block = new Block(repository, parentBlockData, generator);
|
||||
block.addTransaction(somePaymentTransaction.getTransactionData());
|
||||
block.sign();
|
||||
|
||||
@ -451,7 +451,7 @@ public class TransactionTests {
|
||||
assertEquals(ValidationResult.OK, buyNameTransaction.isValid());
|
||||
|
||||
// Forge new block with transaction
|
||||
block = new Block(repository, parentBlockData, generator, null, null);
|
||||
block = new Block(repository, parentBlockData, generator);
|
||||
block.addTransaction(buyNameTransactionData);
|
||||
block.sign();
|
||||
|
||||
@ -504,7 +504,7 @@ public class TransactionTests {
|
||||
assertEquals(ValidationResult.OK, createPollTransaction.isValid());
|
||||
|
||||
// Forge new block with transaction
|
||||
Block block = new Block(repository, parentBlockData, generator, null, null);
|
||||
Block block = new Block(repository, parentBlockData, generator);
|
||||
block.addTransaction(createPollTransactionData);
|
||||
block.sign();
|
||||
|
||||
@ -563,7 +563,7 @@ public class TransactionTests {
|
||||
assertEquals(ValidationResult.OK, voteOnPollTransaction.isValid());
|
||||
|
||||
// Forge new block with transaction
|
||||
Block block = new Block(repository, parentBlockData, generator, null, null);
|
||||
Block block = new Block(repository, parentBlockData, generator);
|
||||
block.addTransaction(voteOnPollTransactionData);
|
||||
block.sign();
|
||||
|
||||
@ -630,7 +630,7 @@ public class TransactionTests {
|
||||
assertEquals(ValidationResult.OK, issueAssetTransaction.isValid());
|
||||
|
||||
// Forge new block with transaction
|
||||
Block block = new Block(repository, parentBlockData, generator, null, null);
|
||||
Block block = new Block(repository, parentBlockData, generator);
|
||||
block.addTransaction(issueAssetTransactionData);
|
||||
block.sign();
|
||||
|
||||
@ -720,7 +720,7 @@ public class TransactionTests {
|
||||
assertEquals(ValidationResult.OK, transferAssetTransaction.isValid());
|
||||
|
||||
// Forge new block with transaction
|
||||
Block block = new Block(repository, parentBlockData, generator, null, null);
|
||||
Block block = new Block(repository, parentBlockData, generator);
|
||||
block.addTransaction(transferAssetTransactionData);
|
||||
block.sign();
|
||||
|
||||
@ -800,7 +800,7 @@ public class TransactionTests {
|
||||
byte[] buyersReference = somePaymentTransaction.getTransactionData().getSignature();
|
||||
|
||||
// Forge new block with transaction
|
||||
Block block = new Block(repository, parentBlockData, generator, null, null);
|
||||
Block block = new Block(repository, parentBlockData, generator);
|
||||
block.addTransaction(somePaymentTransaction.getTransactionData());
|
||||
block.sign();
|
||||
|
||||
@ -824,7 +824,7 @@ public class TransactionTests {
|
||||
assertEquals(ValidationResult.OK, createOrderTransaction.isValid());
|
||||
|
||||
// Forge new block with transaction
|
||||
block = new Block(repository, parentBlockData, generator, null, null);
|
||||
block = new Block(repository, parentBlockData, generator);
|
||||
block.addTransaction(createOrderTransactionData);
|
||||
block.sign();
|
||||
|
||||
@ -905,7 +905,7 @@ public class TransactionTests {
|
||||
assertEquals(ValidationResult.OK, cancelOrderTransaction.isValid());
|
||||
|
||||
// Forge new block with transaction
|
||||
Block block = new Block(repository, parentBlockData, generator, null, null);
|
||||
Block block = new Block(repository, parentBlockData, generator);
|
||||
block.addTransaction(cancelOrderTransactionData);
|
||||
block.sign();
|
||||
|
||||
@ -980,7 +980,7 @@ public class TransactionTests {
|
||||
assertEquals(ValidationResult.OK, createOrderTransaction.isValid());
|
||||
|
||||
// Forge new block with transaction
|
||||
Block block = new Block(repository, parentBlockData, generator, null, null);
|
||||
Block block = new Block(repository, parentBlockData, generator);
|
||||
block.addTransaction(createOrderTransactionData);
|
||||
block.sign();
|
||||
|
||||
@ -1089,7 +1089,7 @@ public class TransactionTests {
|
||||
assertEquals(ValidationResult.OK, multiPaymentTransaction.isValid());
|
||||
|
||||
// Forge new block with payment transaction
|
||||
Block block = new Block(repository, parentBlockData, generator, null, null);
|
||||
Block block = new Block(repository, parentBlockData, generator);
|
||||
block.addTransaction(multiPaymentTransactionData);
|
||||
block.sign();
|
||||
|
||||
@ -1159,7 +1159,7 @@ public class TransactionTests {
|
||||
assertEquals(ValidationResult.OK, messageTransaction.isValid());
|
||||
|
||||
// Forge new block with message transaction
|
||||
Block block = new Block(repository, parentBlockData, generator, null, null);
|
||||
Block block = new Block(repository, parentBlockData, generator);
|
||||
block.addTransaction(messageTransactionData);
|
||||
block.sign();
|
||||
|
||||
|
@ -14,4 +14,7 @@ public abstract class Transformer {
|
||||
public static final int SIGNATURE_LENGTH = 64;
|
||||
public static final int TIMESTAMP_LENGTH = LONG_LENGTH;
|
||||
|
||||
public static final int MD5_LENGTH = 16;
|
||||
public static final int SHA256_LENGTH = 32;
|
||||
|
||||
}
|
||||
|
@ -17,6 +17,7 @@ import com.google.common.primitives.Ints;
|
||||
import com.google.common.primitives.Longs;
|
||||
|
||||
import data.assets.TradeData;
|
||||
import data.at.ATStateData;
|
||||
import data.block.BlockData;
|
||||
import data.transaction.TransactionData;
|
||||
import qora.account.PublicKeyAccount;
|
||||
@ -24,12 +25,13 @@ import qora.assets.Order;
|
||||
import qora.block.Block;
|
||||
import qora.transaction.CreateOrderTransaction;
|
||||
import qora.transaction.Transaction;
|
||||
import qora.transaction.Transaction.TransactionType;
|
||||
import repository.DataException;
|
||||
import transform.TransformationException;
|
||||
import transform.Transformer;
|
||||
import transform.transaction.TransactionTransformer;
|
||||
import utils.Base58;
|
||||
import utils.Pair;
|
||||
import utils.Triple;
|
||||
import utils.Serialization;
|
||||
|
||||
public class BlockTransformer extends Transformer {
|
||||
@ -52,6 +54,9 @@ public class BlockTransformer extends Transformer {
|
||||
protected static final int AT_FEES_LENGTH = LONG_LENGTH;
|
||||
protected static final int AT_LENGTH = AT_FEES_LENGTH + AT_BYTES_LENGTH;
|
||||
|
||||
protected static final int V2_AT_ENTRY_LENGTH = ADDRESS_LENGTH + MD5_LENGTH;
|
||||
protected static final int V4_AT_ENTRY_LENGTH = ADDRESS_LENGTH + SHA256_LENGTH + BIG_DECIMAL_LENGTH;
|
||||
|
||||
/**
|
||||
* Extract block data and transaction data from serialized bytes.
|
||||
*
|
||||
@ -59,7 +64,7 @@ public class BlockTransformer extends Transformer {
|
||||
* @return BlockData and a List of transactions.
|
||||
* @throws TransformationException
|
||||
*/
|
||||
public static Pair<BlockData, List<TransactionData>> fromBytes(byte[] bytes) throws TransformationException {
|
||||
public static Triple<BlockData, List<TransactionData>, List<ATStateData>> fromBytes(byte[] bytes) throws TransformationException {
|
||||
if (bytes == null)
|
||||
return null;
|
||||
|
||||
@ -88,25 +93,74 @@ public class BlockTransformer extends Transformer {
|
||||
byte[] generatorSignature = new byte[GENERATOR_SIGNATURE_LENGTH];
|
||||
byteBuffer.get(generatorSignature);
|
||||
|
||||
byte[] atBytes = null;
|
||||
BigDecimal atFees = null;
|
||||
BigDecimal totalFees = BigDecimal.ZERO.setScale(8);
|
||||
|
||||
int atCount = 0;
|
||||
BigDecimal atFees = BigDecimal.ZERO.setScale(8);
|
||||
List<ATStateData> atStates = new ArrayList<ATStateData>();
|
||||
|
||||
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);
|
||||
ByteBuffer atByteBuffer = byteBuffer.slice();
|
||||
atByteBuffer.limit(atBytesLength);
|
||||
|
||||
atFees = BigDecimal.valueOf(byteBuffer.getLong()).setScale(8);
|
||||
if (version < 4) {
|
||||
// For versions < 4, read AT-address & MD5 pairs
|
||||
if (atBytesLength % V2_AT_ENTRY_LENGTH != 0)
|
||||
throw new TransformationException("AT byte data not a multiple of version 2+ entries");
|
||||
|
||||
while (atByteBuffer.hasRemaining()) {
|
||||
byte[] atAddressBytes = new byte[ADDRESS_LENGTH];
|
||||
atByteBuffer.get(atAddressBytes);
|
||||
String atAddress = Base58.encode(atAddressBytes);
|
||||
|
||||
byte[] stateHash = new byte[MD5_LENGTH];
|
||||
atByteBuffer.get(stateHash);
|
||||
|
||||
atStates.add(new ATStateData(atAddress, stateHash));
|
||||
}
|
||||
|
||||
// Bump byteBuffer over AT states just read in slice
|
||||
byteBuffer.position(byteBuffer.position() + atBytesLength);
|
||||
|
||||
// AT fees follow in versions < 4
|
||||
atFees = Serialization.deserializeBigDecimal(byteBuffer);
|
||||
} else {
|
||||
// For block versions >= 4, read AT-address, SHA256 hash and fees
|
||||
if (atBytesLength % V4_AT_ENTRY_LENGTH != 0)
|
||||
throw new TransformationException("AT byte data not a multiple of version 4+ entries");
|
||||
|
||||
while (atByteBuffer.hasRemaining()) {
|
||||
byte[] atAddressBytes = new byte[ADDRESS_LENGTH];
|
||||
atByteBuffer.get(atAddressBytes);
|
||||
String atAddress = Base58.encode(atAddressBytes);
|
||||
|
||||
byte[] stateHash = new byte[SHA256_LENGTH];
|
||||
atByteBuffer.get(stateHash);
|
||||
|
||||
BigDecimal fees = Serialization.deserializeBigDecimal(atByteBuffer);
|
||||
// Add this AT's fees to our total
|
||||
atFees = atFees.add(fees);
|
||||
|
||||
atStates.add(new ATStateData(atAddress, stateHash, fees));
|
||||
}
|
||||
}
|
||||
|
||||
// AT count to reflect the number of states we have
|
||||
atCount = atStates.size();
|
||||
|
||||
// Add AT fees to totalFees
|
||||
totalFees = totalFees.add(atFees);
|
||||
}
|
||||
|
||||
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>();
|
||||
BigDecimal totalFees = BigDecimal.ZERO.setScale(8);
|
||||
|
||||
for (int t = 0; t < transactionCount; ++t) {
|
||||
if (byteBuffer.remaining() < TRANSACTION_SIZE_LENGTH)
|
||||
@ -126,26 +180,28 @@ public class BlockTransformer extends Transformer {
|
||||
TransactionData transactionData = TransactionTransformer.fromBytes(transactionBytes);
|
||||
transactions.add(transactionData);
|
||||
|
||||
totalFees.add(transactionData.getFee());
|
||||
totalFees = totalFees.add(transactionData.getFee());
|
||||
}
|
||||
|
||||
if (byteBuffer.hasRemaining())
|
||||
throw new TransformationException("Excess byte data found after parsing Block");
|
||||
|
||||
// XXX we don't know height!
|
||||
int height = 0;
|
||||
// We don't have a height!
|
||||
Integer height = null;
|
||||
BlockData blockData = new BlockData(version, reference, transactionCount, totalFees, transactionsSignature, height, timestamp, generatingBalance,
|
||||
generatorPublicKey, generatorSignature, atBytes, atFees);
|
||||
generatorPublicKey, generatorSignature, atCount, atFees);
|
||||
|
||||
return new Pair<BlockData, List<TransactionData>>(blockData, transactions);
|
||||
return new Triple<BlockData, List<TransactionData>, List<ATStateData>>(blockData, transactions, atStates);
|
||||
}
|
||||
|
||||
public static int getDataLength(Block block) throws TransformationException {
|
||||
BlockData blockData = block.getBlockData();
|
||||
int blockLength = BASE_LENGTH;
|
||||
|
||||
if (blockData.getVersion() >= 2 && blockData.getAtBytes() != null)
|
||||
blockLength += AT_FEES_LENGTH + AT_BYTES_LENGTH + blockData.getAtBytes().length;
|
||||
if (blockData.getVersion() >= 4)
|
||||
blockLength += AT_BYTES_LENGTH + blockData.getATCount() * V4_AT_ENTRY_LENGTH;
|
||||
else if (blockData.getVersion() >= 2)
|
||||
blockLength += AT_FEES_LENGTH + AT_BYTES_LENGTH + blockData.getATCount() * V2_AT_ENTRY_LENGTH;
|
||||
|
||||
try {
|
||||
// Short cut for no transactions
|
||||
@ -177,18 +233,29 @@ public class BlockTransformer extends Transformer {
|
||||
bytes.write(blockData.getTransactionsSignature());
|
||||
bytes.write(blockData.getGeneratorSignature());
|
||||
|
||||
if (blockData.getVersion() >= 2) {
|
||||
byte[] atBytes = blockData.getAtBytes();
|
||||
if (blockData.getVersion() >= 4) {
|
||||
int atBytesLength = blockData.getATCount() * V4_AT_ENTRY_LENGTH;
|
||||
bytes.write(Ints.toByteArray(atBytesLength));
|
||||
|
||||
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));
|
||||
for (ATStateData atStateData : block.getATStates()) {
|
||||
bytes.write(Base58.decode(atStateData.getATAddress()));
|
||||
bytes.write(atStateData.getStateHash());
|
||||
Serialization.serializeBigDecimal(bytes, atStateData.getFees());
|
||||
}
|
||||
} else if (blockData.getVersion() >= 2) {
|
||||
int atBytesLength = blockData.getATCount() * V2_AT_ENTRY_LENGTH;
|
||||
bytes.write(Ints.toByteArray(atBytesLength));
|
||||
|
||||
for (ATStateData atStateData : block.getATStates()) {
|
||||
bytes.write(Base58.decode(atStateData.getATAddress()));
|
||||
bytes.write(atStateData.getStateHash());
|
||||
}
|
||||
|
||||
if (blockData.getATFees() != null)
|
||||
// NOTE: atFees serialized as long value, not as BigDecimal, for historic compatibility
|
||||
bytes.write(Longs.toByteArray(blockData.getATFees().longValue()));
|
||||
else
|
||||
bytes.write(Longs.toByteArray(0));
|
||||
}
|
||||
|
||||
// Transactions
|
||||
@ -263,30 +330,39 @@ public class BlockTransformer extends Transformer {
|
||||
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());
|
||||
if (blockData.getATCount() > 0) {
|
||||
JSONArray atsJson = new JSONArray();
|
||||
|
||||
try {
|
||||
for (ATStateData atStateData : block.getATStates()) {
|
||||
JSONObject atJson = new JSONObject();
|
||||
|
||||
atJson.put("AT", atStateData.getATAddress());
|
||||
atJson.put("stateHash", HashCode.fromBytes(atStateData.getStateHash()).toString());
|
||||
|
||||
if (blockData.getVersion() >= 4)
|
||||
atJson.put("fees", atStateData.getFees().toPlainString());
|
||||
|
||||
atsJson.add(atJson);
|
||||
}
|
||||
} catch (DataException e) {
|
||||
throw new TransformationException("Unable to transform block into JSON", e);
|
||||
}
|
||||
|
||||
json.put("ATs", atsJson);
|
||||
|
||||
if (blockData.getVersion() >= 2)
|
||||
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);
|
||||
byte[] generatorSignature = Arrays.copyOf(blockData.getReference(), GENERATOR_SIGNATURE_LENGTH);
|
||||
PublicKeyAccount generator = new PublicKeyAccount(null, blockData.getGeneratorPublicKey());
|
||||
|
||||
// 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);
|
||||
}
|
||||
return getBytesForGeneratorSignature(generatorSignature, blockData.getGeneratingBalance(), generator);
|
||||
}
|
||||
|
||||
public static byte[] getBytesForGeneratorSignature(byte[] generatorSignature, BigDecimal generatingBalance, PublicKeyAccount generator)
|
||||
@ -308,13 +384,18 @@ public class BlockTransformer extends Transformer {
|
||||
}
|
||||
|
||||
public static byte[] getBytesForTransactionsSignature(Block block) throws TransformationException {
|
||||
ByteArrayOutputStream bytes = new ByteArrayOutputStream(
|
||||
GENERATOR_SIGNATURE_LENGTH + block.getBlockData().getTransactionCount() * TransactionTransformer.SIGNATURE_LENGTH);
|
||||
|
||||
try {
|
||||
List<Transaction> transactions = block.getTransactions();
|
||||
|
||||
ByteArrayOutputStream bytes = new ByteArrayOutputStream(GENERATOR_SIGNATURE_LENGTH + transactions.size() * TransactionTransformer.SIGNATURE_LENGTH);
|
||||
|
||||
bytes.write(block.getBlockData().getGeneratorSignature());
|
||||
|
||||
for (Transaction transaction : block.getTransactions()) {
|
||||
for (Transaction transaction : transactions) {
|
||||
// For legacy blocks, we don't include AT-Transactions
|
||||
if (block.getBlockData().getVersion() < 4 && transaction.getTransactionData().getType() == TransactionType.AT)
|
||||
continue;
|
||||
|
||||
if (!transaction.isSignatureValid())
|
||||
throw new TransformationException("Transaction signature invalid when building block's transactions signature");
|
||||
|
||||
|
92
src/transform/transaction/ATTransactionTransformer.java
Normal file
92
src/transform/transaction/ATTransactionTransformer.java
Normal file
@ -0,0 +1,92 @@
|
||||
package transform.transaction;
|
||||
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.nio.ByteBuffer;
|
||||
|
||||
import org.json.simple.JSONObject;
|
||||
|
||||
import com.google.common.primitives.Ints;
|
||||
import com.google.common.primitives.Longs;
|
||||
|
||||
import data.transaction.TransactionData;
|
||||
import data.transaction.ATTransactionData;
|
||||
import transform.TransformationException;
|
||||
import utils.Serialization;
|
||||
|
||||
public class ATTransactionTransformer extends TransactionTransformer {
|
||||
|
||||
// Property lengths
|
||||
private static final int SENDER_LENGTH = ADDRESS_LENGTH;
|
||||
private static final int RECIPIENT_LENGTH = ADDRESS_LENGTH;
|
||||
private static final int AMOUNT_LENGTH = BIG_DECIMAL_LENGTH;
|
||||
private static final int ASSET_ID_LENGTH = LONG_LENGTH;
|
||||
private static final int DATA_SIZE_LENGTH = INT_LENGTH;
|
||||
|
||||
private static final int TYPELESS_DATALESS_LENGTH = BASE_TYPELESS_LENGTH + SENDER_LENGTH + RECIPIENT_LENGTH + AMOUNT_LENGTH + ASSET_ID_LENGTH + DATA_SIZE_LENGTH;
|
||||
|
||||
static TransactionData fromByteBuffer(ByteBuffer byteBuffer) throws TransformationException {
|
||||
throw new TransformationException("Serialized AT Transactions should not exist!");
|
||||
}
|
||||
|
||||
public static int getDataLength(TransactionData transactionData) throws TransformationException {
|
||||
ATTransactionData atTransactionData = (ATTransactionData) transactionData;
|
||||
|
||||
return TYPE_LENGTH + TYPELESS_DATALESS_LENGTH + atTransactionData.getMessage().length;
|
||||
}
|
||||
|
||||
public static byte[] toBytes(TransactionData transactionData) throws TransformationException {
|
||||
try {
|
||||
ATTransactionData atTransactionData = (ATTransactionData) transactionData;
|
||||
|
||||
ByteArrayOutputStream bytes = new ByteArrayOutputStream();
|
||||
|
||||
bytes.write(Ints.toByteArray(atTransactionData.getType().value));
|
||||
bytes.write(Longs.toByteArray(atTransactionData.getTimestamp()));
|
||||
bytes.write(atTransactionData.getReference());
|
||||
|
||||
Serialization.serializeAddress(bytes, atTransactionData.getATAddress());
|
||||
|
||||
Serialization.serializeAddress(bytes, atTransactionData.getRecipient());
|
||||
|
||||
if (atTransactionData.getAssetId() != null) {
|
||||
Serialization.serializeBigDecimal(bytes, atTransactionData.getAmount());
|
||||
bytes.write(Longs.toByteArray(atTransactionData.getAssetId()));
|
||||
}
|
||||
|
||||
byte[] message = atTransactionData.getMessage();
|
||||
if (message.length > 0) {
|
||||
bytes.write(Ints.toByteArray(message.length));
|
||||
bytes.write(message);
|
||||
} else {
|
||||
bytes.write(Ints.toByteArray(0));
|
||||
}
|
||||
|
||||
Serialization.serializeBigDecimal(bytes, atTransactionData.getFee());
|
||||
|
||||
if (atTransactionData.getSignature() != null)
|
||||
bytes.write(atTransactionData.getSignature());
|
||||
|
||||
return bytes.toByteArray();
|
||||
} catch (IOException | ClassCastException e) {
|
||||
throw new TransformationException(e);
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
public static JSONObject toJSON(TransactionData transactionData) throws TransformationException {
|
||||
JSONObject json = TransactionTransformer.getBaseJSON(transactionData);
|
||||
|
||||
try {
|
||||
ATTransactionData atTransactionData = (ATTransactionData) transactionData;
|
||||
|
||||
json.put("sender", atTransactionData.getATAddress());
|
||||
|
||||
} catch (ClassCastException e) {
|
||||
throw new TransformationException(e);
|
||||
}
|
||||
|
||||
return json;
|
||||
}
|
||||
|
||||
}
|
@ -97,7 +97,9 @@ public class DeployATTransactionTransformer extends TransactionTransformer {
|
||||
|
||||
Serialization.serializeSizedString(bytes, deployATTransactionData.getTags());
|
||||
|
||||
bytes.write(deployATTransactionData.getCreationBytes());
|
||||
byte[] creationBytes = deployATTransactionData.getCreationBytes();
|
||||
bytes.write(Ints.toByteArray(creationBytes.length));
|
||||
bytes.write(creationBytes);
|
||||
|
||||
Serialization.serializeBigDecimal(bytes, deployATTransactionData.getAmount());
|
||||
|
||||
@ -146,15 +148,14 @@ public class DeployATTransactionTransformer extends TransactionTransformer {
|
||||
|
||||
// Omitted: Serialization.serializeSizedString(bytes, deployATTransactionData.getTags());
|
||||
|
||||
bytes.write(deployATTransactionData.getCreationBytes());
|
||||
byte[] creationBytes = deployATTransactionData.getCreationBytes();
|
||||
bytes.write(Ints.toByteArray(creationBytes.length));
|
||||
bytes.write(creationBytes);
|
||||
|
||||
Serialization.serializeBigDecimal(bytes, deployATTransactionData.getAmount());
|
||||
|
||||
Serialization.serializeBigDecimal(bytes, deployATTransactionData.getFee());
|
||||
|
||||
if (deployATTransactionData.getSignature() != null)
|
||||
bytes.write(deployATTransactionData.getSignature());
|
||||
|
||||
return bytes.toByteArray();
|
||||
} catch (IOException | ClassCastException e) {
|
||||
throw new TransformationException(e);
|
||||
|
@ -29,4 +29,22 @@ public class Pair<T, U> {
|
||||
return b;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (o == this)
|
||||
return true;
|
||||
|
||||
if (!(o instanceof Pair<?, ?>))
|
||||
return false;
|
||||
|
||||
Pair<?, ?> other = (Pair<?, ?>) o;
|
||||
|
||||
return this.a.equals(other.getA()) && this.b.equals(other.getB());
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return this.a.hashCode() ^ this.b.hashCode();
|
||||
}
|
||||
|
||||
}
|
||||
|
42
src/utils/Triple.java
Normal file
42
src/utils/Triple.java
Normal file
@ -0,0 +1,42 @@
|
||||
package utils;
|
||||
|
||||
public class Triple<T, U, V> {
|
||||
|
||||
private T a;
|
||||
private U b;
|
||||
private V c;
|
||||
|
||||
public Triple() {
|
||||
}
|
||||
|
||||
public Triple(T a, U b, V c) {
|
||||
this.a = a;
|
||||
this.b = b;
|
||||
this.c = c;
|
||||
}
|
||||
|
||||
public void setA(T a) {
|
||||
this.a = a;
|
||||
}
|
||||
|
||||
public T getA() {
|
||||
return a;
|
||||
}
|
||||
|
||||
public void setB(U b) {
|
||||
this.b = b;
|
||||
}
|
||||
|
||||
public U getB() {
|
||||
return b;
|
||||
}
|
||||
|
||||
public void setC(V c) {
|
||||
this.c = c;
|
||||
}
|
||||
|
||||
public V getC() {
|
||||
return c;
|
||||
}
|
||||
|
||||
}
|
@ -1,7 +1,9 @@
|
||||
import java.io.BufferedReader;
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.DataInputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.OutputStream;
|
||||
import java.math.BigDecimal;
|
||||
import java.net.InetSocketAddress;
|
||||
import java.net.Socket;
|
||||
import java.net.SocketException;
|
||||
@ -9,17 +11,32 @@ import java.net.SocketTimeoutException;
|
||||
import java.nio.BufferUnderflowException;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.charset.Charset;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.Paths;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import org.apache.logging.log4j.LogManager;
|
||||
import org.apache.logging.log4j.Logger;
|
||||
import org.json.simple.JSONArray;
|
||||
import org.json.simple.JSONObject;
|
||||
import org.json.simple.JSONValue;
|
||||
import org.json.simple.parser.ParseException;
|
||||
|
||||
import com.google.common.hash.HashCode;
|
||||
import com.google.common.primitives.Bytes;
|
||||
import com.google.common.primitives.Ints;
|
||||
|
||||
import data.at.ATData;
|
||||
import data.at.ATStateData;
|
||||
import data.block.BlockData;
|
||||
import data.transaction.ATTransactionData;
|
||||
import data.transaction.TransactionData;
|
||||
import qora.assets.Asset;
|
||||
import qora.block.Block;
|
||||
import qora.block.Block.ValidationResult;
|
||||
import qora.block.BlockChain;
|
||||
@ -29,7 +46,10 @@ import repository.Repository;
|
||||
import repository.RepositoryManager;
|
||||
import transform.TransformationException;
|
||||
import transform.block.BlockTransformer;
|
||||
import transform.transaction.ATTransactionTransformer;
|
||||
import utils.Base58;
|
||||
import utils.Pair;
|
||||
import utils.Triple;
|
||||
|
||||
public class v1feeder extends Thread {
|
||||
|
||||
@ -77,6 +97,9 @@ public class v1feeder extends Thread {
|
||||
private long lastPingTimestamp = System.currentTimeMillis();
|
||||
private List<byte[]> signatures = new ArrayList<byte[]>();
|
||||
|
||||
private static Map<Pair<String, Integer>, BigDecimal> legacyATFees;
|
||||
private static Map<Integer, List<TransactionData>> legacyATTransactions;
|
||||
|
||||
private v1feeder(String address, int port) throws InterruptedException {
|
||||
try {
|
||||
for (int i = 0; i < 10; ++i)
|
||||
@ -177,6 +200,9 @@ public class v1feeder extends Thread {
|
||||
// shove into list
|
||||
int numSignatures = byteBuffer.getInt();
|
||||
|
||||
if (numSignatures == 0)
|
||||
throw new RuntimeException("No signatures from peer - are we up to date?");
|
||||
|
||||
while (numSignatures-- > 0) {
|
||||
byte[] signature = new byte[SIGNATURE_LENGTH];
|
||||
byteBuffer.get(signature);
|
||||
@ -201,7 +227,7 @@ public class v1feeder extends Thread {
|
||||
byte[] blockBytes = new byte[byteBuffer.remaining()];
|
||||
byteBuffer.get(blockBytes);
|
||||
|
||||
Pair<BlockData, List<TransactionData>> blockInfo = null;
|
||||
Triple<BlockData, List<TransactionData>, List<ATStateData>> blockInfo = null;
|
||||
|
||||
try {
|
||||
blockInfo = BlockTransformer.fromBytes(blockBytes);
|
||||
@ -211,7 +237,26 @@ public class v1feeder extends Thread {
|
||||
}
|
||||
|
||||
try (final Repository repository = RepositoryManager.getRepository()) {
|
||||
Block block = new Block(repository, blockInfo.getA(), blockInfo.getB());
|
||||
BlockData blockData = blockInfo.getA();
|
||||
|
||||
// Adjust AT state data to include fees
|
||||
List<ATStateData> atStates = new ArrayList<ATStateData>();
|
||||
for (ATStateData atState : blockInfo.getC()) {
|
||||
BigDecimal fees = legacyATFees.get(new Pair<String, Integer>(atState.getATAddress(), claimedHeight));
|
||||
ATData atData = repository.getATRepository().fromATAddress(atState.getATAddress());
|
||||
|
||||
atStates.add(new ATStateData(atState.getATAddress(), claimedHeight, atData.getCreation(), null, atState.getStateHash(), fees));
|
||||
}
|
||||
|
||||
// AT-Transaction injection goes here!
|
||||
List<TransactionData> transactions = blockInfo.getB();
|
||||
List<TransactionData> atTransactions = legacyATTransactions.get(claimedHeight);
|
||||
if (atTransactions != null) {
|
||||
transactions.addAll(0, atTransactions);
|
||||
blockData.setTransactionCount(blockData.getTransactionCount() + atTransactions.size());
|
||||
}
|
||||
|
||||
Block block = new Block(repository, blockData, transactions, atStates);
|
||||
|
||||
if (!block.isSignatureValid()) {
|
||||
LOGGER.error("Invalid block signature");
|
||||
@ -221,8 +266,8 @@ public class v1feeder extends Thread {
|
||||
ValidationResult result = block.isValid();
|
||||
|
||||
if (result != ValidationResult.OK) {
|
||||
LOGGER.error("Invalid block, validation result code: " + result.value);
|
||||
throw new RuntimeException("Invalid block, validation result code: " + result.value);
|
||||
LOGGER.error("Invalid block, validation result: " + result.name());
|
||||
throw new RuntimeException("Invalid block, validation result: " + result.name());
|
||||
}
|
||||
|
||||
block.process();
|
||||
@ -398,12 +443,88 @@ public class v1feeder extends Thread {
|
||||
}
|
||||
}
|
||||
|
||||
private static void readLegacyATs(String legacyATPathname) {
|
||||
legacyATFees = new HashMap<Pair<String, Integer>, BigDecimal>();
|
||||
legacyATTransactions = new HashMap<Integer, List<TransactionData>>();
|
||||
|
||||
Path path = Paths.get(legacyATPathname);
|
||||
|
||||
JSONArray json = null;
|
||||
|
||||
try (BufferedReader in = Files.newBufferedReader(path)) {
|
||||
json = (JSONArray) JSONValue.parseWithException(in);
|
||||
} catch (IOException | ParseException e) {
|
||||
throw new RuntimeException("Couldn't read legacy AT JSON file");
|
||||
}
|
||||
|
||||
for (Object o : json) {
|
||||
JSONObject entry = (JSONObject) o;
|
||||
|
||||
int height = Integer.parseInt((String) entry.get("height"));
|
||||
long timestamp = (Long) entry.get("timestamp");
|
||||
|
||||
JSONArray transactionEntries = (JSONArray) entry.get("transactions");
|
||||
|
||||
List<TransactionData> transactions = new ArrayList<TransactionData>();
|
||||
|
||||
for (Object t : transactionEntries) {
|
||||
JSONObject transactionEntry = (JSONObject) t;
|
||||
|
||||
String recipient = (String) transactionEntry.get("recipient");
|
||||
String sender = (String) transactionEntry.get("sender");
|
||||
BigDecimal amount = new BigDecimal((String) transactionEntry.get("amount")).setScale(8);
|
||||
|
||||
if (recipient.equals("1111111111111111111111111")) {
|
||||
// fee
|
||||
legacyATFees.put(new Pair<String, Integer>(sender, height), amount);
|
||||
} else {
|
||||
// Actual AT Transaction
|
||||
String messageString = (String) transactionEntry.get("message");
|
||||
byte[] message = messageString.isEmpty() ? new byte[0] : HashCode.fromString(messageString).asBytes();
|
||||
int sequence = ((Long) transactionEntry.get("seq")).intValue();
|
||||
byte[] reference = Base58.decode((String) transactionEntry.get("reference"));
|
||||
|
||||
// reference is AT's deploy tx signature
|
||||
// sender's public key is genesis account
|
||||
// zero fee
|
||||
// timestamp is block's timestamp
|
||||
// signature = duplicated hash of transaction data
|
||||
|
||||
BigDecimal fee = BigDecimal.ZERO.setScale(8);
|
||||
|
||||
TransactionData transactionData = new ATTransactionData(sender, recipient, amount, Asset.QORA, message, fee, timestamp, reference);
|
||||
byte[] digest;
|
||||
try {
|
||||
digest = Crypto.digest(ATTransactionTransformer.toBytes(transactionData));
|
||||
byte[] signature = Bytes.concat(digest, digest);
|
||||
|
||||
transactionData = new ATTransactionData(sender, recipient, amount, Asset.QORA, message, fee, timestamp, reference, signature);
|
||||
} catch (TransformationException e) {
|
||||
throw new RuntimeException("Couldn't transform AT Transaction into bytes", e);
|
||||
}
|
||||
|
||||
if (sequence > transactions.size())
|
||||
transactions.add(transactionData);
|
||||
else
|
||||
transactions.add(sequence, transactionData);
|
||||
}
|
||||
}
|
||||
|
||||
if (!transactions.isEmpty())
|
||||
legacyATTransactions.put(height, transactions);
|
||||
}
|
||||
}
|
||||
|
||||
public static void main(String[] args) {
|
||||
if (args.length == 0) {
|
||||
System.err.println("usage: v1feeder v1-node-address [port]");
|
||||
if (args.length < 2 || args.length > 3) {
|
||||
System.err.println("usage: v1feeder legacy-AT-json v1-node-address [port]");
|
||||
System.err.println("example: v1feeder legacy-ATs.json 10.0.0.100 9084");
|
||||
System.exit(1);
|
||||
}
|
||||
|
||||
String legacyATPathname = args[0];
|
||||
readLegacyATs(legacyATPathname);
|
||||
|
||||
try {
|
||||
test.Common.setRepository();
|
||||
} catch (DataException e) {
|
||||
@ -419,8 +540,8 @@ public class v1feeder extends Thread {
|
||||
}
|
||||
|
||||
// connect to v1 node
|
||||
String address = args[0];
|
||||
int port = args.length > 1 ? Integer.valueOf(args[1]) : DEFAULT_PORT;
|
||||
String address = args[1];
|
||||
int port = args.length > 2 ? Integer.valueOf(args[2]) : DEFAULT_PORT;
|
||||
|
||||
try {
|
||||
new v1feeder(address, port).join();
|
||||
|
Loading…
x
Reference in New Issue
Block a user