qortal/src/qora/transaction/MessageTransaction.java
catbref ad250e57c8 Fix issues with payments, asset trades and voting.
Some payments don't always initialize the recipient's
last reference, depending on whether the asset is QORA or not and
what type of transaction is being processed.
Calls to Payment.process/orphan now take boolean to indicate
whether recipient's last reference should always be initialized
regardless of asset.
("initialized" here means setting an initial last reference for
an account if the current value is null/empty/missing)

When matching orders to produce trades, it isn't enough to only
sort orders with best price first. Orders with the same price also
need to be further sorted by earliest order first. (This was
implicitly done in Qora v1). Added additional ORDER BY sub-clause
to achieve this and improved the corresponding table index too.

Converted a lot of logging from simplistic System.out/err.println
to Apache log4j2. Added several "trace"-level logging statements
to aid debugging which can be activated given appropriate
configuration in log4j2.properties.

With voting, detection of previous votes was broken during orphan
as previousOptionIndex was set to 0 instead of null when fetching
a VoteOnPollTransaction from the HSQLDB repository. This was due
to lack of null-checking - now fixed.
Corresponding changes made in IssueAsset and Message
transaction HSQLDB repository classes.

v1feeder now syncs up to block 99055. Block 99056 contains DeployAT.
Orphaning back to block 1 and then resync works without issue too.
2018-08-08 17:02:41 +01:00

138 lines
4.7 KiB
Java

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.MessageTransactionData;
import data.transaction.TransactionData;
import qora.account.Account;
import qora.account.PublicKeyAccount;
import qora.assets.Asset;
import qora.block.BlockChain;
import qora.payment.Payment;
import repository.DataException;
import repository.Repository;
public class MessageTransaction extends Transaction {
// Properties
private MessageTransactionData messageTransactionData;
// Other useful constants
public static final int MAX_DATA_SIZE = 4000;
// Constructors
public MessageTransaction(Repository repository, TransactionData transactionData) {
super(repository, transactionData);
this.messageTransactionData = (MessageTransactionData) this.transactionData;
}
// More information
@Override
public List<Account> getRecipientAccounts() throws DataException {
return Collections.singletonList(new Account(this.repository, messageTransactionData.getRecipient()));
}
@Override
public boolean isInvolved(Account account) throws DataException {
String address = account.getAddress();
if (address.equals(this.getSender().getAddress()))
return true;
if (address.equals(messageTransactionData.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.transactionData.getFee());
// We're only interested in QORA
if (messageTransactionData.getAssetId() == Asset.QORA) {
if (address.equals(messageTransactionData.getRecipient()))
amount = amount.add(messageTransactionData.getAmount());
else if (address.equals(senderAddress))
amount = amount.subtract(messageTransactionData.getAmount());
}
return amount;
}
// Navigation
public Account getSender() throws DataException {
return new PublicKeyAccount(this.repository, this.messageTransactionData.getSenderPublicKey());
}
public Account getRecipient() throws DataException {
return new Account(this.repository, this.messageTransactionData.getRecipient());
}
// Processing
private PaymentData getPaymentData() {
return new PaymentData(messageTransactionData.getRecipient(), Asset.QORA, messageTransactionData.getAmount());
}
@Override
public ValidationResult isValid() throws DataException {
// Are message transactions even allowed at this point?
if (messageTransactionData.getVersion() != MessageTransaction.getVersionByTimestamp(messageTransactionData.getTimestamp()))
return ValidationResult.NOT_YET_RELEASED;
if (this.repository.getBlockRepository().getBlockchainHeight() < BlockChain.getMessageReleaseHeight())
return ValidationResult.NOT_YET_RELEASED;
// Check data length
if (messageTransactionData.getData().length < 1 || messageTransactionData.getData().length > MAX_DATA_SIZE)
return ValidationResult.INVALID_DATA_LENGTH;
// Check reference is correct
Account sender = getSender();
if (!Arrays.equals(sender.getLastReference(), messageTransactionData.getReference()))
return ValidationResult.INVALID_REFERENCE;
// Zero-amount payments (i.e. message-only) only valid for versions later than 1
boolean isZeroAmountValid = messageTransactionData.getVersion() > 1;
// Wrap and delegate final payment checks to Payment class
return new Payment(this.repository).isValid(messageTransactionData.getSenderPublicKey(), getPaymentData(), messageTransactionData.getFee(),
isZeroAmountValid);
}
@Override
public void process() throws DataException {
// Save this transaction itself
this.repository.getTransactionRepository().save(this.transactionData);
// Wrap and delegate payment processing to Payment class. Only update recipient's last reference if transferring QORA.
new Payment(this.repository).process(messageTransactionData.getSenderPublicKey(), getPaymentData(), messageTransactionData.getFee(),
messageTransactionData.getSignature(), false);
}
@Override
public void orphan() throws DataException {
// Delete this transaction itself
this.repository.getTransactionRepository().delete(this.transactionData);
// Wrap and delegate payment processing to Payment class. Only revert recipient's last reference if transferring QORA.
new Payment(this.repository).orphan(messageTransactionData.getSenderPublicKey(), getPaymentData(), messageTransactionData.getFee(),
messageTransactionData.getSignature(), messageTransactionData.getReference(), false);
}
}