forked from Qortal/qortal
Added GET_ACCOUNT_TRANSACTIONS message, as well as a generic TRANSACTIONS message for responses.
This commit is contained in:
parent
59119ebc3b
commit
a63fa1cce5
@ -32,6 +32,7 @@ import org.bouncycastle.jsse.provider.BouncyCastleJsseProvider;
|
||||
import org.qortal.api.ApiService;
|
||||
import org.qortal.api.DomainMapService;
|
||||
import org.qortal.api.GatewayService;
|
||||
import org.qortal.api.resource.TransactionsResource;
|
||||
import org.qortal.block.Block;
|
||||
import org.qortal.block.BlockChain;
|
||||
import org.qortal.block.BlockChain.BlockTimingByHeight;
|
||||
@ -200,6 +201,15 @@ public class Controller extends Thread {
|
||||
}
|
||||
public GetAccountBalanceMessageStats getAccountBalanceMessageStats = new GetAccountBalanceMessageStats();
|
||||
|
||||
public static class GetAccountTransactionsMessageStats {
|
||||
public AtomicLong requests = new AtomicLong();
|
||||
public AtomicLong unknownAccounts = new AtomicLong();
|
||||
|
||||
public GetAccountTransactionsMessageStats() {
|
||||
}
|
||||
}
|
||||
public GetAccountTransactionsMessageStats getAccountTransactionsMessageStats = new GetAccountTransactionsMessageStats();
|
||||
|
||||
public static class GetAccountNamesMessageStats {
|
||||
public AtomicLong requests = new AtomicLong();
|
||||
public AtomicLong unknownAccounts = new AtomicLong();
|
||||
@ -1280,6 +1290,10 @@ public class Controller extends Thread {
|
||||
onNetworkGetAccountBalanceMessage(peer, message);
|
||||
break;
|
||||
|
||||
case GET_ACCOUNT_TRANSACTIONS:
|
||||
onNetworkGetAccountTransactionsMessage(peer, message);
|
||||
break;
|
||||
|
||||
case GET_ACCOUNT_NAMES:
|
||||
onNetworkGetAccountNamesMessage(peer, message);
|
||||
break;
|
||||
@ -1610,6 +1624,50 @@ public class Controller extends Thread {
|
||||
}
|
||||
}
|
||||
|
||||
private void onNetworkGetAccountTransactionsMessage(Peer peer, Message message) {
|
||||
GetAccountTransactionsMessage getAccountTransactionsMessage = (GetAccountTransactionsMessage) message;
|
||||
String address = getAccountTransactionsMessage.getAddress();
|
||||
int limit = Math.min(getAccountTransactionsMessage.getLimit(), 100);
|
||||
int offset = getAccountTransactionsMessage.getOffset();
|
||||
this.stats.getAccountTransactionsMessageStats.requests.incrementAndGet();
|
||||
|
||||
try (final Repository repository = RepositoryManager.getRepository()) {
|
||||
List<byte[]> signatures = repository.getTransactionRepository().getSignaturesMatchingCriteria(null, null, null,
|
||||
null, null, null, address, TransactionsResource.ConfirmationStatus.CONFIRMED, limit, offset, false);
|
||||
|
||||
// Expand signatures to transactions
|
||||
List<TransactionData> transactions = new ArrayList<>(signatures.size());
|
||||
for (byte[] signature : signatures) {
|
||||
transactions.add(repository.getTransactionRepository().fromSignature(signature));
|
||||
}
|
||||
|
||||
if (transactions == null) {
|
||||
// We don't have this account
|
||||
this.stats.getAccountTransactionsMessageStats.unknownAccounts.getAndIncrement();
|
||||
|
||||
// Send valid, yet unexpected message type in response, so peer doesn't have to wait for timeout
|
||||
LOGGER.debug(() -> String.format("Sending 'account unknown' response to peer %s for GET_ACCOUNT_TRANSACTIONS request for unknown account %s", peer, address));
|
||||
|
||||
// We'll send empty block summaries message as it's very short
|
||||
Message accountUnknownMessage = new BlockSummariesMessage(Collections.emptyList());
|
||||
accountUnknownMessage.setId(message.getId());
|
||||
if (!peer.sendMessage(accountUnknownMessage))
|
||||
peer.disconnect("failed to send account-unknown response");
|
||||
return;
|
||||
}
|
||||
|
||||
TransactionsMessage transactionsMessage = new TransactionsMessage(transactions);
|
||||
transactionsMessage.setId(message.getId());
|
||||
|
||||
if (!peer.sendMessage(transactionsMessage)) {
|
||||
peer.disconnect("failed to send account transactions");
|
||||
}
|
||||
|
||||
} catch (DataException e) {
|
||||
LOGGER.error(String.format("Repository issue while send transactions for account %s %d to peer %s", address, peer), e);
|
||||
}
|
||||
}
|
||||
|
||||
private void onNetworkGetAccountNamesMessage(Peer peer, Message message) {
|
||||
GetAccountNamesMessage getAccountNamesMessage = (GetAccountNamesMessage) message;
|
||||
String address = getAccountNamesMessage.getAddress();
|
||||
|
@ -5,6 +5,7 @@ import org.apache.logging.log4j.Logger;
|
||||
import org.qortal.data.account.AccountBalanceData;
|
||||
import org.qortal.data.account.AccountData;
|
||||
import org.qortal.data.naming.NameData;
|
||||
import org.qortal.data.transaction.TransactionData;
|
||||
import org.qortal.network.Network;
|
||||
import org.qortal.network.Peer;
|
||||
import org.qortal.network.message.*;
|
||||
@ -24,6 +25,8 @@ public class LiteNode {
|
||||
|
||||
public Map<Integer, Long> pendingRequests = Collections.synchronizedMap(new HashMap<>());
|
||||
|
||||
public int MAX_TRANSACTIONS_PER_MESSAGE = 100;
|
||||
|
||||
|
||||
public LiteNode() {
|
||||
|
||||
@ -66,6 +69,37 @@ public class LiteNode {
|
||||
return accountMessage.getAccountBalanceData();
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetch list of transactions for given QORT address
|
||||
* @param address - the QORT address to query
|
||||
* @param limit - the maximum number of results to return
|
||||
* @param offset - the starting index
|
||||
* @return a list of TransactionData objects, or null if not retrieved
|
||||
*/
|
||||
public List<TransactionData> fetchAccountTransactions(String address, int limit, int offset) {
|
||||
List<TransactionData> allTransactions = new ArrayList<>();
|
||||
if (limit == 0) {
|
||||
limit = Integer.MAX_VALUE;
|
||||
}
|
||||
int batchSize = Math.min(limit, MAX_TRANSACTIONS_PER_MESSAGE);
|
||||
|
||||
while (allTransactions.size() < limit) {
|
||||
GetAccountTransactionsMessage getAccountTransactionsMessage = new GetAccountTransactionsMessage(address, batchSize, offset);
|
||||
TransactionsMessage transactionsMessage = (TransactionsMessage) this.sendMessage(getAccountTransactionsMessage, TRANSACTIONS);
|
||||
if (transactionsMessage == null) {
|
||||
// An error occurred, so give up instead of returning partial results
|
||||
return null;
|
||||
}
|
||||
allTransactions.addAll(transactionsMessage.getTransactions());
|
||||
if (transactionsMessage.getTransactions().size() < batchSize) {
|
||||
// No more transactions to fetch
|
||||
break;
|
||||
}
|
||||
offset += batchSize;
|
||||
}
|
||||
return allTransactions;
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetch list of names for given QORT address
|
||||
* @param address - the QORT address to query
|
||||
|
@ -0,0 +1,71 @@
|
||||
package org.qortal.network.message;
|
||||
|
||||
import com.google.common.primitives.Ints;
|
||||
import org.qortal.transform.Transformer;
|
||||
import org.qortal.utils.Base58;
|
||||
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.UnsupportedEncodingException;
|
||||
import java.nio.ByteBuffer;
|
||||
|
||||
public class GetAccountTransactionsMessage extends Message {
|
||||
|
||||
private static final int ADDRESS_LENGTH = Transformer.ADDRESS_LENGTH;
|
||||
|
||||
private String address;
|
||||
private int limit;
|
||||
private int offset;
|
||||
|
||||
public GetAccountTransactionsMessage(String address, int limit, int offset) {
|
||||
this(-1, address, limit, offset);
|
||||
}
|
||||
|
||||
private GetAccountTransactionsMessage(int id, String address, int limit, int offset) {
|
||||
super(id, MessageType.GET_ACCOUNT_TRANSACTIONS);
|
||||
|
||||
this.address = address;
|
||||
this.limit = limit;
|
||||
this.offset = offset;
|
||||
}
|
||||
|
||||
public String getAddress() {
|
||||
return this.address;
|
||||
}
|
||||
|
||||
public int getLimit() { return this.limit; }
|
||||
|
||||
public int getOffset() { return this.offset; }
|
||||
|
||||
public static Message fromByteBuffer(int id, ByteBuffer bytes) throws UnsupportedEncodingException {
|
||||
byte[] addressBytes = new byte[ADDRESS_LENGTH];
|
||||
bytes.get(addressBytes);
|
||||
String address = Base58.encode(addressBytes);
|
||||
|
||||
int limit = bytes.getInt();
|
||||
|
||||
int offset = bytes.getInt();
|
||||
|
||||
return new GetAccountTransactionsMessage(id, address, limit, offset);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected byte[] toData() {
|
||||
try {
|
||||
ByteArrayOutputStream bytes = new ByteArrayOutputStream();
|
||||
|
||||
// Send raw address instead of base58 encoded
|
||||
byte[] address = Base58.decode(this.address);
|
||||
bytes.write(address);
|
||||
|
||||
bytes.write(Ints.toByteArray(this.limit));
|
||||
|
||||
bytes.write(Ints.toByteArray(this.offset));
|
||||
|
||||
return bytes.toByteArray();
|
||||
} catch (IOException e) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@ -110,7 +110,10 @@ public abstract class Message {
|
||||
|
||||
NAMES(180),
|
||||
GET_ACCOUNT_NAMES(181),
|
||||
GET_NAME(182);
|
||||
GET_NAME(182),
|
||||
|
||||
TRANSACTIONS(190),
|
||||
GET_ACCOUNT_TRANSACTIONS(191);
|
||||
|
||||
public final int value;
|
||||
public final Method fromByteBufferMethod;
|
||||
|
@ -0,0 +1,77 @@
|
||||
package org.qortal.network.message;
|
||||
|
||||
import com.google.common.primitives.Ints;
|
||||
import org.qortal.data.transaction.TransactionData;
|
||||
import org.qortal.transform.TransformationException;
|
||||
import org.qortal.transform.transaction.TransactionTransformer;
|
||||
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.UnsupportedEncodingException;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
public class TransactionsMessage extends Message {
|
||||
|
||||
private List<TransactionData> transactions;
|
||||
|
||||
public TransactionsMessage(List<TransactionData> transactions) {
|
||||
this(-1, transactions);
|
||||
}
|
||||
|
||||
private TransactionsMessage(int id, List<TransactionData> transactions) {
|
||||
super(id, MessageType.TRANSACTIONS);
|
||||
|
||||
this.transactions = transactions;
|
||||
}
|
||||
|
||||
public List<TransactionData> getTransactions() {
|
||||
return this.transactions;
|
||||
}
|
||||
|
||||
public static Message fromByteBuffer(int id, ByteBuffer byteBuffer) throws UnsupportedEncodingException {
|
||||
try {
|
||||
final int transactionCount = byteBuffer.getInt();
|
||||
|
||||
List<TransactionData> transactions = new ArrayList<>();
|
||||
|
||||
for (int i = 0; i < transactionCount; ++i) {
|
||||
TransactionData transactionData = TransactionTransformer.fromByteBuffer(byteBuffer);
|
||||
transactions.add(transactionData);
|
||||
}
|
||||
|
||||
if (byteBuffer.hasRemaining()) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return new TransactionsMessage(id, transactions);
|
||||
} catch (TransformationException e) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected byte[] toData() {
|
||||
if (this.transactions == null)
|
||||
return null;
|
||||
|
||||
try {
|
||||
ByteArrayOutputStream bytes = new ByteArrayOutputStream();
|
||||
|
||||
bytes.write(Ints.toByteArray(this.transactions.size()));
|
||||
|
||||
for (int i = 0; i < this.transactions.size(); ++i) {
|
||||
TransactionData transactionData = this.transactions.get(i);
|
||||
|
||||
byte[] serializedTransactionData = TransactionTransformer.toBytes(transactionData);
|
||||
bytes.write(serializedTransactionData);
|
||||
}
|
||||
|
||||
return bytes.toByteArray();
|
||||
} catch (TransformationException | IOException e) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user