Improved AT PUT_TX_AFTER_TIMESTAMP_INTO_A function

Previous version fetched all the blocks from previous 'timestamp'
to current height, checking each transaction. (very slow)

New implementation leverages repository to do the heavy lifting.

Could potentially benefit from some DB indexes in the future?

Added unit test to cover.
This commit is contained in:
catbref
2020-07-17 11:46:39 +01:00
parent ca8eabc425
commit 21d7a4eed1
4 changed files with 346 additions and 52 deletions

View File

@@ -17,7 +17,6 @@ import org.qortal.account.Account;
import org.qortal.account.NullAccount;
import org.qortal.account.PublicKeyAccount;
import org.qortal.asset.Asset;
import org.qortal.block.Block;
import org.qortal.block.BlockChain;
import org.qortal.block.BlockChain.CiyamAtSettings;
import org.qortal.crypto.Crypto;
@@ -30,11 +29,10 @@ import org.qortal.data.transaction.MessageTransactionData;
import org.qortal.data.transaction.PaymentTransactionData;
import org.qortal.data.transaction.TransactionData;
import org.qortal.group.Group;
import org.qortal.repository.BlockRepository;
import org.qortal.repository.ATRepository;
import org.qortal.repository.DataException;
import org.qortal.repository.Repository;
import org.qortal.transaction.AtTransaction;
import org.qortal.transaction.Transaction;
import org.qortal.transaction.Transaction.TransactionType;
import org.qortal.utils.Base58;
import org.qortal.utils.BitTwiddling;
@@ -150,59 +148,27 @@ public class QortalATAPI extends API {
int height = timestamp.blockHeight;
int sequence = timestamp.transactionSequence + 1;
BlockRepository blockRepository = this.getRepository().getBlockRepository();
ATRepository.NextTransactionInfo nextTransactionInfo;
try {
int currentHeight = blockRepository.getBlockchainHeight();
List<Transaction> blockTransactions = null;
while (height <= currentHeight) {
if (blockTransactions == null) {
BlockData blockData = blockRepository.fromHeight(height);
if (blockData == null)
throw new DataException("Unable to fetch block " + height + " from repository?");
Block block = new Block(this.getRepository(), blockData);
blockTransactions = block.getTransactions();
}
// No more transactions in this block? Try next block
if (sequence >= blockTransactions.size()) {
++height;
sequence = 0;
blockTransactions = null;
continue;
}
Transaction transaction = blockTransactions.get(sequence);
// Transaction needs to be sent to specified recipient
List<String> recipientAddresses = transaction.getRecipientAddresses();
if (recipientAddresses.contains(atAddress)) {
// Found a transaction
this.setA1(state, new Timestamp(height, timestamp.blockchainId, sequence).longValue());
// Copy transaction's partial signature into the other three A fields for future verification that it's the same transaction
byte[] signature = transaction.getTransactionData().getSignature();
this.setA2(state, BitTwiddling.longFromBEBytes(signature, 8));
this.setA3(state, BitTwiddling.longFromBEBytes(signature, 16));
this.setA4(state, BitTwiddling.longFromBEBytes(signature, 24));
return;
}
// Transaction wasn't for us - keep going
++sequence;
}
// No more transactions - zero A and exit
this.zeroA(state);
nextTransactionInfo = this.getRepository().getATRepository().findNextTransaction(atAddress, height, sequence);
} catch (DataException e) {
throw new RuntimeException("AT API unable to fetch next transaction?", e);
}
if (nextTransactionInfo == null) {
// No more transactions for AT at this time - zero A and exit
this.zeroA(state);
return;
}
// Found a transaction
this.setA1(state, new Timestamp(nextTransactionInfo.height, timestamp.blockchainId, nextTransactionInfo.sequence).longValue());
// Copy transaction's partial signature into the other three A fields for future verification that it's the same transaction
this.setA2(state, BitTwiddling.longFromBEBytes(nextTransactionInfo.signature, 8));
this.setA3(state, BitTwiddling.longFromBEBytes(nextTransactionInfo.signature, 16));
this.setA4(state, BitTwiddling.longFromBEBytes(nextTransactionInfo.signature, 24));
}
@Override

View File

@@ -88,4 +88,28 @@ public interface ATRepository {
/** Delete state data for all ATs at this height */
public void deleteATStates(int height) throws DataException;
// Finding transactions for ATs to process
static class NextTransactionInfo {
public final int height;
public final int sequence;
public final byte[] signature;
public NextTransactionInfo(int height, int sequence, byte[] signature) {
this.height = height;
this.sequence = sequence;
this.signature = signature;
}
}
/**
* Find next transaction for AT to process.
* <p>
* @param recipient AT address
* @param height starting height
* @param sequence starting sequence
* @return next transaction info, or null if none found
*/
public NextTransactionInfo findNextTransaction(String recipient, int height, int sequence) throws DataException;
}

View File

@@ -341,4 +341,40 @@ public class HSQLDBATRepository implements ATRepository {
}
}
// Finding transactions for ATs to process
public NextTransactionInfo findNextTransaction(String recipient, int height, int sequence) throws DataException {
// We only need to search for a subset of transaction types: MESSAGE, PAYMENT or AT
String sql = "SELECT height, sequence, Transactions.signature "
+ "FROM ("
+ "SELECT signature FROM PaymentTransactions WHERE recipient = ? "
+ "UNION "
+ "SELECT signature FROM MessageTransactions WHERE recipient = ? "
+ "UNION "
+ "SELECT signature FROM ATTransactions WHERE recipient = ?"
+ ") AS Transactions "
+ "JOIN BlockTransactions ON BlockTransactions.transaction_signature = Transactions.signature "
+ "JOIN Blocks ON Blocks.signature = BlockTransactions.block_signature "
+ "WHERE (height > ? OR (height = ? AND sequence > ?)) "
+ "ORDER BY height ASC, sequence ASC "
+ "LIMIT 1";
Object[] bindParams = new Object[] { recipient, recipient, recipient, height, height, sequence };
try (ResultSet resultSet = this.repository.checkedExecute(sql, bindParams)) {
if (resultSet == null)
return null;
int nextHeight = resultSet.getInt(1);
int nextSequence = resultSet.getInt(2);
byte[] nextSignature = resultSet.getBytes(3);
return new NextTransactionInfo(nextHeight, nextSequence, nextSignature);
} catch (SQLException e) {
throw new DataException("Unable to find next transaction to AT from repository", e);
}
}
}