mirror of
https://github.com/Qortal/qortal.git
synced 2025-03-26 23:44:34 +00:00
More work on CIYAM AT support.
ATs can create AT-Transactions which contain payments (of any asset) and/or messages. Legacy Qora1 DeployATTransactions create AT records in the repository but set to "finished" so that they never execute. More repository support for ATs. In HSQLDB, create a new TYPE called ATStateHash which is used to verify the same AT outcome on a per-block basis. Added Accounts.account as a foreign key to AccountBalances with ON DELETE CASCADE. ATStates now include state_hash and fees on a per-block basis. ATTransactions now include asset_id. When transforming DeployATTransactions, don't include any signature when collating bytes for signing!
This commit is contained in:
parent
e9d8b3e6e3
commit
46eee3cbce
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>
|
<versions>
|
||||||
<version>1.0</version>
|
<version>1.0</version>
|
||||||
</versions>
|
</versions>
|
||||||
<lastUpdated>20181003154752</lastUpdated>
|
<lastUpdated>20181015081124</lastUpdated>
|
||||||
</versioning>
|
</versioning>
|
||||||
</metadata>
|
</metadata>
|
||||||
|
@ -1 +1 @@
|
|||||||
bc81bc1f9b74a4eececd5dd8b29e47d8
|
2369bf36c52580a89d5ea71a0f037a82
|
@ -1 +1 @@
|
|||||||
feefde4343bda4d6e13159e5c01f8b4f8963a1bc
|
6bc38899b93ffce2286ae26f7af0b2d8b69db3cf
|
@ -10,22 +10,25 @@ public class ATTransactionData extends TransactionData {
|
|||||||
private byte[] senderPublicKey;
|
private byte[] senderPublicKey;
|
||||||
private String recipient;
|
private String recipient;
|
||||||
private BigDecimal amount;
|
private BigDecimal amount;
|
||||||
|
private Long assetId;
|
||||||
private byte[] message;
|
private byte[] message;
|
||||||
|
|
||||||
// Constructors
|
// Constructors
|
||||||
|
|
||||||
public ATTransactionData(byte[] senderPublicKey, String recipient, BigDecimal amount, byte[] message, BigDecimal fee, long timestamp, byte[] reference,
|
public ATTransactionData(byte[] senderPublicKey, String recipient, BigDecimal amount, Long assetId, byte[] message, BigDecimal fee, long timestamp,
|
||||||
byte[] signature) {
|
byte[] reference, byte[] signature) {
|
||||||
super(TransactionType.AT, fee, senderPublicKey, timestamp, reference, signature);
|
super(TransactionType.AT, fee, senderPublicKey, timestamp, reference, signature);
|
||||||
|
|
||||||
this.senderPublicKey = senderPublicKey;
|
this.senderPublicKey = senderPublicKey;
|
||||||
this.recipient = recipient;
|
this.recipient = recipient;
|
||||||
this.amount = amount;
|
this.amount = amount;
|
||||||
|
this.assetId = assetId;
|
||||||
this.message = message;
|
this.message = message;
|
||||||
}
|
}
|
||||||
|
|
||||||
public ATTransactionData(byte[] senderPublicKey, String recipient, BigDecimal amount, byte[] message, BigDecimal fee, long timestamp, byte[] reference) {
|
public ATTransactionData(byte[] senderPublicKey, String recipient, BigDecimal amount, Long assetId, byte[] message, BigDecimal fee, long timestamp,
|
||||||
this(senderPublicKey, recipient, amount, message, fee, timestamp, reference, null);
|
byte[] reference) {
|
||||||
|
this(senderPublicKey, recipient, amount, assetId, message, fee, timestamp, reference, null);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Getters/Setters
|
// Getters/Setters
|
||||||
@ -42,6 +45,10 @@ public class ATTransactionData extends TransactionData {
|
|||||||
return this.amount;
|
return this.amount;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public Long getAssetId() {
|
||||||
|
return this.assetId;
|
||||||
|
}
|
||||||
|
|
||||||
public byte[] getMessage() {
|
public byte[] getMessage() {
|
||||||
return this.message;
|
return this.message;
|
||||||
}
|
}
|
||||||
|
@ -1,5 +1,7 @@
|
|||||||
package qora.at;
|
package qora.at;
|
||||||
|
|
||||||
|
import java.nio.ByteBuffer;
|
||||||
|
|
||||||
import org.ciyam.at.MachineState;
|
import org.ciyam.at.MachineState;
|
||||||
|
|
||||||
import data.at.ATData;
|
import data.at.ATData;
|
||||||
@ -26,20 +28,51 @@ public class AT {
|
|||||||
public AT(Repository repository, DeployATTransactionData deployATTransactionData) throws DataException {
|
public AT(Repository repository, DeployATTransactionData deployATTransactionData) throws DataException {
|
||||||
this.repository = repository;
|
this.repository = repository;
|
||||||
|
|
||||||
MachineState machineState = new MachineState(deployATTransactionData.getCreationBytes());
|
String atAddress = deployATTransactionData.getATAddress();
|
||||||
|
|
||||||
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());
|
|
||||||
|
|
||||||
String atAddress = this.atData.getATAddress();
|
|
||||||
|
|
||||||
int height = this.repository.getBlockRepository().getBlockchainHeight();
|
int height = this.repository.getBlockRepository().getBlockchainHeight();
|
||||||
byte[] stateData = machineState.toBytes();
|
|
||||||
|
|
||||||
this.atStateData = new ATStateData(atAddress, height, stateData);
|
byte[] creationBytes = deployATTransactionData.getCreationBytes();
|
||||||
|
short version = (short) (creationBytes[0] | (creationBytes[1] << 8)); // Little-endian
|
||||||
|
|
||||||
|
if (version >= 2) {
|
||||||
|
MachineState machineState = new MachineState(deployATTransactionData.getCreationBytes());
|
||||||
|
|
||||||
|
this.atData = new ATData(atAddress, machineState.version, machineState.getCodeBytes(), machineState.getIsSleeping(),
|
||||||
|
machineState.getSleepUntilHeight(), machineState.getIsFinished(), machineState.getHadFatalError(), machineState.getIsFrozen(),
|
||||||
|
machineState.getFrozenBalance(), deployATTransactionData.getSignature());
|
||||||
|
|
||||||
|
this.atStateData = new ATStateData(atAddress, height, machineState.toBytes());
|
||||||
|
} 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(deployATTransactionData.getATAddress(), 1, codeBytes, false, null, true, false, false, (Long) null,
|
||||||
|
deployATTransactionData.getSignature());
|
||||||
|
|
||||||
|
this.atStateData = new ATStateData(deployATTransactionData.getATAddress(), height, null);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Processing
|
||||||
|
|
||||||
public void deploy() throws DataException {
|
public void deploy() throws DataException {
|
||||||
this.repository.getATRepository().save(this.atData);
|
this.repository.getATRepository().save(this.atData);
|
||||||
this.repository.getATRepository().save(this.atStateData);
|
this.repository.getATRepository().save(this.atStateData);
|
||||||
|
@ -612,7 +612,7 @@ public class Block {
|
|||||||
Transaction.ValidationResult validationResult = transaction.isValid();
|
Transaction.ValidationResult validationResult = transaction.isValid();
|
||||||
if (validationResult != Transaction.ValidationResult.OK) {
|
if (validationResult != Transaction.ValidationResult.OK) {
|
||||||
LOGGER.error("Error during transaction validation, tx " + Base58.encode(transaction.getTransactionData().getSignature()) + ": "
|
LOGGER.error("Error during transaction validation, tx " + Base58.encode(transaction.getTransactionData().getSignature()) + ": "
|
||||||
+ validationResult.value);
|
+ validationResult.name());
|
||||||
return ValidationResult.TRANSACTION_INVALID;
|
return ValidationResult.TRANSACTION_INVALID;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
128
src/qora/transaction/ATTransaction.java
Normal file
128
src/qora/transaction/ATTransaction.java
Normal file
@ -0,0 +1,128 @@
|
|||||||
|
package qora.transaction;
|
||||||
|
|
||||||
|
import java.math.BigDecimal;
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import data.PaymentData;
|
||||||
|
import data.transaction.ATTransactionData;
|
||||||
|
import data.transaction.TransactionData;
|
||||||
|
import qora.account.Account;
|
||||||
|
import qora.account.PublicKeyAccount;
|
||||||
|
import qora.assets.Asset;
|
||||||
|
import qora.payment.Payment;
|
||||||
|
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, atTransactionData.getRecipient()));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isInvolved(Account account) throws DataException {
|
||||||
|
String address = account.getAddress();
|
||||||
|
|
||||||
|
if (address.equals(this.getSender().getAddress()))
|
||||||
|
return true;
|
||||||
|
|
||||||
|
if (address.equals(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 senderAddress = this.getSender().getAddress();
|
||||||
|
|
||||||
|
if (address.equals(senderAddress)) {
|
||||||
|
amount = amount.subtract(this.atTransactionData.getFee());
|
||||||
|
|
||||||
|
if (atTransactionData.getAmount() != null && atTransactionData.getAssetId() == Asset.QORA)
|
||||||
|
amount = amount.subtract(atTransactionData.getAmount());
|
||||||
|
}
|
||||||
|
|
||||||
|
if (address.equals(atTransactionData.getRecipient()) && atTransactionData.getAmount() != null)
|
||||||
|
amount = amount.add(atTransactionData.getAmount());
|
||||||
|
|
||||||
|
return amount;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Navigation
|
||||||
|
|
||||||
|
public Account getSender() throws DataException {
|
||||||
|
return new PublicKeyAccount(this.repository, this.atTransactionData.getSenderPublicKey());
|
||||||
|
}
|
||||||
|
|
||||||
|
// Processing
|
||||||
|
|
||||||
|
private PaymentData getPaymentData() {
|
||||||
|
if (atTransactionData.getAmount() == null)
|
||||||
|
return null;
|
||||||
|
|
||||||
|
return new PaymentData(atTransactionData.getRecipient(), atTransactionData.getAssetId(), atTransactionData.getAmount());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ValidationResult isValid() throws DataException {
|
||||||
|
// Check reference is correct
|
||||||
|
Account sender = getSender();
|
||||||
|
if (!Arrays.equals(sender.getLastReference(), atTransactionData.getReference()))
|
||||||
|
return ValidationResult.INVALID_REFERENCE;
|
||||||
|
|
||||||
|
if (this.atTransactionData.getMessage().length > MAX_DATA_SIZE)
|
||||||
|
return ValidationResult.INVALID_DATA_LENGTH;
|
||||||
|
|
||||||
|
// If we have no payment then we're done
|
||||||
|
if (this.atTransactionData.getAmount() == null)
|
||||||
|
return ValidationResult.OK;
|
||||||
|
|
||||||
|
// Wrap and delegate final payment checks to Payment class
|
||||||
|
return new Payment(this.repository).isValid(atTransactionData.getSenderPublicKey(), getPaymentData(), atTransactionData.getFee());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void process() throws DataException {
|
||||||
|
// Save this transaction itself
|
||||||
|
this.repository.getTransactionRepository().save(this.transactionData);
|
||||||
|
|
||||||
|
if (this.atTransactionData.getAmount() != null)
|
||||||
|
// Wrap and delegate payment processing to Payment class. Only update recipient's last reference if transferring QORA.
|
||||||
|
new Payment(this.repository).process(atTransactionData.getSenderPublicKey(), getPaymentData(), atTransactionData.getFee(),
|
||||||
|
atTransactionData.getSignature(), false);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void orphan() throws DataException {
|
||||||
|
// Delete this transaction
|
||||||
|
this.repository.getTransactionRepository().delete(this.transactionData);
|
||||||
|
|
||||||
|
if (this.atTransactionData.getAmount() != null)
|
||||||
|
// Wrap and delegate payment processing to Payment class. Only revert recipient's last reference if transferring QORA.
|
||||||
|
new Payment(this.repository).orphan(atTransactionData.getSenderPublicKey(), getPaymentData(), atTransactionData.getFee(),
|
||||||
|
atTransactionData.getSignature(), atTransactionData.getReference(), false);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -75,6 +75,13 @@ public class DeployATTransaction extends Transaction {
|
|||||||
return amount;
|
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 */
|
/** Make sure deployATTransactionData has an ATAddress */
|
||||||
private void ensureATAddress() throws DataException {
|
private void ensureATAddress() throws DataException {
|
||||||
if (this.deployATTransactionData.getATAddress() != null)
|
if (this.deployATTransactionData.getATAddress() != null)
|
||||||
@ -82,7 +89,7 @@ public class DeployATTransaction extends Transaction {
|
|||||||
|
|
||||||
int blockHeight = this.getHeight();
|
int blockHeight = this.getHeight();
|
||||||
if (blockHeight == 0)
|
if (blockHeight == 0)
|
||||||
blockHeight = this.repository.getBlockRepository().getBlockchainHeight();
|
blockHeight = this.repository.getBlockRepository().getBlockchainHeight() + 1;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
byte[] name = this.deployATTransactionData.getName().getBytes("UTF-8");
|
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)
|
if (creator.getConfirmedBalance(Asset.QORA).compareTo(minimumBalance) < 0)
|
||||||
return ValidationResult.NO_BALANCE;
|
return ValidationResult.NO_BALANCE;
|
||||||
|
|
||||||
// Check creation bytes are valid (for v3+)
|
// Check creation bytes are valid (for v2+)
|
||||||
byte[] creationBytes = deployATTransactionData.getCreationBytes();
|
if (this.getVersion() >= 2) {
|
||||||
short version = (short) (creationBytes[0] | (creationBytes[1] << 8)); // Little-endian
|
|
||||||
|
|
||||||
if (version >= 3) {
|
|
||||||
// Do actual validation
|
// Do actual validation
|
||||||
} else {
|
} else {
|
||||||
// Skip validation for old, dead ATs
|
// Skip validation for old, dead ATs
|
||||||
@ -194,6 +198,14 @@ public class DeployATTransaction extends Transaction {
|
|||||||
|
|
||||||
// Update creator's reference
|
// Update creator's reference
|
||||||
creator.setLastReference(deployATTransactionData.getSignature());
|
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
|
@Override
|
||||||
@ -212,6 +224,9 @@ public class DeployATTransaction extends Transaction {
|
|||||||
|
|
||||||
// Update creator's reference
|
// Update creator's reference
|
||||||
creator.setLastReference(deployATTransactionData.getReference());
|
creator.setLastReference(deployATTransactionData.getReference());
|
||||||
|
|
||||||
|
// Delete AT's account
|
||||||
|
this.repository.getAccountRepository().delete(this.deployATTransactionData.getATAddress());
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -193,11 +193,14 @@ public abstract class Transaction {
|
|||||||
case MULTIPAYMENT:
|
case MULTIPAYMENT:
|
||||||
return new MultiPaymentTransaction(repository, transactionData);
|
return new MultiPaymentTransaction(repository, transactionData);
|
||||||
|
|
||||||
|
case DEPLOY_AT:
|
||||||
|
return new DeployATTransaction(repository, transactionData);
|
||||||
|
|
||||||
case MESSAGE:
|
case MESSAGE:
|
||||||
return new MessageTransaction(repository, transactionData);
|
return new MessageTransaction(repository, transactionData);
|
||||||
|
|
||||||
case DEPLOY_AT:
|
case AT:
|
||||||
return new DeployATTransaction(repository, transactionData);
|
return new ATTransaction(repository, transactionData);
|
||||||
|
|
||||||
default:
|
default:
|
||||||
throw new IllegalStateException("Unsupported transaction type [" + transactionData.getType().value + "] during fetch from repository");
|
throw new IllegalStateException("Unsupported transaction type [" + transactionData.getType().value + "] during fetch from repository");
|
||||||
|
@ -11,6 +11,8 @@ public interface AccountRepository {
|
|||||||
|
|
||||||
public void save(AccountData accountData) throws DataException;
|
public void save(AccountData accountData) throws DataException;
|
||||||
|
|
||||||
|
public void delete(String address) throws DataException;
|
||||||
|
|
||||||
// Account balances
|
// Account balances
|
||||||
|
|
||||||
public AccountBalanceData getBalance(String address, long assetId) throws DataException;
|
public AccountBalanceData getBalance(String address, long assetId) throws DataException;
|
||||||
|
@ -69,7 +69,7 @@ public class HSQLDBATRepository implements ATRepository {
|
|||||||
@Override
|
@Override
|
||||||
public void delete(String atAddress) throws DataException {
|
public void delete(String atAddress) throws DataException {
|
||||||
try {
|
try {
|
||||||
this.repository.delete("ATs", "atAddress = ?", atAddress);
|
this.repository.delete("ATs", "AT_address = ?", atAddress);
|
||||||
// AT States also deleted via ON DELETE CASCADE
|
// AT States also deleted via ON DELETE CASCADE
|
||||||
} catch (SQLException e) {
|
} catch (SQLException e) {
|
||||||
throw new DataException("Unable to delete AT from repository", e);
|
throw new DataException("Unable to delete AT from repository", e);
|
||||||
|
@ -17,6 +17,8 @@ public class HSQLDBAccountRepository implements AccountRepository {
|
|||||||
this.repository = repository;
|
this.repository = repository;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// General account
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public AccountData getAccount(String address) throws DataException {
|
public AccountData getAccount(String address) throws DataException {
|
||||||
try (ResultSet resultSet = this.repository.checkedExecute("SELECT reference FROM Accounts WHERE account = ?", address)) {
|
try (ResultSet resultSet = this.repository.checkedExecute("SELECT reference FROM Accounts WHERE account = ?", address)) {
|
||||||
@ -41,6 +43,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
|
@Override
|
||||||
public AccountBalanceData getBalance(String address, long assetId) throws DataException {
|
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)) {
|
try (ResultSet resultSet = this.repository.checkedExecute("SELECT balance FROM AccountBalances WHERE account = ? and asset_id = ?", address, assetId)) {
|
||||||
|
@ -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 ATType AS VARCHAR(200) COLLATE SQL_TEXT_UCC_NO_PAD");
|
||||||
stmt.execute("CREATE TYPE ATCode AS BLOB(64K)"); // 16bit * 1
|
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 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)");
|
stmt.execute("CREATE TYPE ATMessage AS VARBINARY(256)");
|
||||||
break;
|
break;
|
||||||
|
|
||||||
@ -302,7 +303,7 @@ public class HSQLDBDatabaseUpdates {
|
|||||||
// Accounts
|
// Accounts
|
||||||
stmt.execute("CREATE TABLE Accounts (account QoraAddress, reference Signature, PRIMARY KEY (account))");
|
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, "
|
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;
|
break;
|
||||||
|
|
||||||
case 23:
|
case 23:
|
||||||
@ -358,11 +359,12 @@ public class HSQLDBDatabaseUpdates {
|
|||||||
// For finding executable ATs
|
// For finding executable ATs
|
||||||
stmt.execute("CREATE INDEX ATIndex on ATs (is_finished, AT_address)");
|
stmt.execute("CREATE INDEX ATIndex on ATs (is_finished, AT_address)");
|
||||||
// AT state on a per-block basis
|
// AT state on a per-block basis
|
||||||
stmt.execute("CREATE TABLE ATStates (AT_address QoraAddress, height INTEGER NOT NULL, state_data ATState, "
|
stmt.execute(
|
||||||
+ "PRIMARY KEY (AT_address, height), FOREIGN KEY (AT_address) REFERENCES ATs (AT_address) ON DELETE CASCADE)");
|
"CREATE TABLE ATStates (AT_address QoraAddress, height INTEGER NOT NULL, 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)");
|
||||||
// Generated AT Transactions
|
// Generated AT Transactions
|
||||||
stmt.execute(
|
stmt.execute(
|
||||||
"CREATE TABLE ATTransactions (signature Signature, sender QoraPublicKey NOT NULL, recipient QoraAddress, amount QoraAmount NOT NULL, message ATMessage, "
|
"CREATE TABLE ATTransactions (signature Signature, sender QoraPublicKey NOT NULL, recipient QoraAddress, amount QoraAmount, asset_id AssetID, message ATMessage, "
|
||||||
+ "PRIMARY KEY (signature), FOREIGN KEY (signature) REFERENCES Transactions (signature) ON DELETE CASCADE)");
|
+ "PRIMARY KEY (signature), FOREIGN KEY (signature) REFERENCES Transactions (signature) ON DELETE CASCADE)");
|
||||||
break;
|
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 sender, recipient, amount, asset_id, message FROM ATTransactions WHERE signature = ?",
|
||||||
|
signature)) {
|
||||||
|
if (resultSet == null)
|
||||||
|
return null;
|
||||||
|
|
||||||
|
byte[] senderPublicKey = resultSet.getBytes(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(senderPublicKey, 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("sender", atTransactionData.getSenderPublicKey())
|
||||||
|
.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 HSQLDBMultiPaymentTransactionRepository multiPaymentTransactionRepository;
|
||||||
private HSQLDBDeployATTransactionRepository deployATTransactionRepository;
|
private HSQLDBDeployATTransactionRepository deployATTransactionRepository;
|
||||||
private HSQLDBMessageTransactionRepository messageTransactionRepository;
|
private HSQLDBMessageTransactionRepository messageTransactionRepository;
|
||||||
|
private HSQLDBATTransactionRepository atTransactionRepository;
|
||||||
|
|
||||||
public HSQLDBTransactionRepository(HSQLDBRepository repository) {
|
public HSQLDBTransactionRepository(HSQLDBRepository repository) {
|
||||||
this.repository = repository;
|
this.repository = repository;
|
||||||
@ -57,6 +58,7 @@ public class HSQLDBTransactionRepository implements TransactionRepository {
|
|||||||
this.multiPaymentTransactionRepository = new HSQLDBMultiPaymentTransactionRepository(repository);
|
this.multiPaymentTransactionRepository = new HSQLDBMultiPaymentTransactionRepository(repository);
|
||||||
this.deployATTransactionRepository = new HSQLDBDeployATTransactionRepository(repository);
|
this.deployATTransactionRepository = new HSQLDBDeployATTransactionRepository(repository);
|
||||||
this.messageTransactionRepository = new HSQLDBMessageTransactionRepository(repository);
|
this.messageTransactionRepository = new HSQLDBMessageTransactionRepository(repository);
|
||||||
|
this.atTransactionRepository = new HSQLDBATTransactionRepository(repository);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected HSQLDBTransactionRepository() {
|
protected HSQLDBTransactionRepository() {
|
||||||
@ -154,8 +156,11 @@ public class HSQLDBTransactionRepository implements TransactionRepository {
|
|||||||
case MESSAGE:
|
case MESSAGE:
|
||||||
return this.messageTransactionRepository.fromBase(signature, reference, creatorPublicKey, timestamp, fee);
|
return this.messageTransactionRepository.fromBase(signature, reference, creatorPublicKey, timestamp, fee);
|
||||||
|
|
||||||
|
case AT:
|
||||||
|
return this.atTransactionRepository.fromBase(signature, reference, creatorPublicKey, timestamp, fee);
|
||||||
|
|
||||||
default:
|
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");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -317,8 +322,12 @@ public class HSQLDBTransactionRepository implements TransactionRepository {
|
|||||||
this.messageTransactionRepository.save(transactionData);
|
this.messageTransactionRepository.save(transactionData);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
|
case AT:
|
||||||
|
this.atTransactionRepository.save(transactionData);
|
||||||
|
break;
|
||||||
|
|
||||||
default:
|
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");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -97,7 +97,9 @@ public class DeployATTransactionTransformer extends TransactionTransformer {
|
|||||||
|
|
||||||
Serialization.serializeSizedString(bytes, deployATTransactionData.getTags());
|
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.getAmount());
|
||||||
|
|
||||||
@ -146,15 +148,14 @@ public class DeployATTransactionTransformer extends TransactionTransformer {
|
|||||||
|
|
||||||
// Omitted: Serialization.serializeSizedString(bytes, deployATTransactionData.getTags());
|
// 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.getAmount());
|
||||||
|
|
||||||
Serialization.serializeBigDecimal(bytes, deployATTransactionData.getFee());
|
Serialization.serializeBigDecimal(bytes, deployATTransactionData.getFee());
|
||||||
|
|
||||||
if (deployATTransactionData.getSignature() != null)
|
|
||||||
bytes.write(deployATTransactionData.getSignature());
|
|
||||||
|
|
||||||
return bytes.toByteArray();
|
return bytes.toByteArray();
|
||||||
} catch (IOException | ClassCastException e) {
|
} catch (IOException | ClassCastException e) {
|
||||||
throw new TransformationException(e);
|
throw new TransformationException(e);
|
||||||
|
@ -221,8 +221,8 @@ public class v1feeder extends Thread {
|
|||||||
ValidationResult result = block.isValid();
|
ValidationResult result = block.isValid();
|
||||||
|
|
||||||
if (result != ValidationResult.OK) {
|
if (result != ValidationResult.OK) {
|
||||||
LOGGER.error("Invalid block, validation result code: " + result.value);
|
LOGGER.error("Invalid block, validation result: " + result.name());
|
||||||
throw new RuntimeException("Invalid block, validation result code: " + result.value);
|
throw new RuntimeException("Invalid block, validation result: " + result.name());
|
||||||
}
|
}
|
||||||
|
|
||||||
block.process();
|
block.process();
|
||||||
|
Loading…
x
Reference in New Issue
Block a user