forked from Qortal-Forker/qortal
ATData no longer needs deploySignature as a link back to DeployATTransaction, but does need creator and creation [timestamp]. "creation" is critical for ordering ATs when creating/validating blocks. Similar changes to ATStateData, adding creation, stateHash (for quicker comparison with blocks received over the network), and fees incurred by running AT on that block. Also added more explicit constructors for different scenarios. BlockData upgraded from simplistic "atBytes" to use ATStateData (above) which has details on ATs run for that block, fees incurred, and a hash of the AT's state. atCount added to keep track of how many ATs ran. ATTransactions essentially reuse the GenesisAccount's publickey as creator/sender as they're brought into existence by the Qora code rather than an end user. ATTransactionData updated to reflect this and the AT's address used as a "sender" field. Account tidied up with respect to CIYAM ATs and setConfirmedBalance ensures there is a corresponding record in Accounts (DB table). Account, and subclasses, don't need "throws DataException" on constructor any more. Fixed bug in Asset Order matching where the matching engine would give up after first potential match instead of trying others. Lots more work on CIYAM AT, albeit mainly blind importing of old v1 ATs from static JSON file as they're all dead and new v2 implementation is not backwards compatible. More work on Blocks, mostly AT stuff, but also fork-based corruption prevention using fix from Qora v1. Payment-related transactions (multipayment, etc.) always expect/use non-null (albeit maybe empty) list of PaymentData when validating, processing or orphaning. Mainly a change in HSQLDBTransactionRepository.getPayments() Payment.isValid(byte[], PaymentData, BigDecimal, boolean isZeroAmountValid) didn't pass on isZeroAmountValid to called method - whoops! Lots of work on ATTransactions themselves. MessageTransactions incorrectly assumed the optional payment was always in Qora. Now fixed to use the transaction's provided assetId. Mass of fixes/additions to HSQLDBATRepository, especially fixing incorrect reference to Assets DB table! In HSQLDBDatabaseUpdates, bump QoraAmount type from DECIMAL(19,8) to DECIMAL(27,8) to allow for huge asset quantities. You WILL have to rebuild your database!
180 lines
5.8 KiB
Java
180 lines
5.8 KiB
Java
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);
|
|
}
|
|
}
|
|
|
|
}
|