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.ApiService;
|
||||||
import org.qortal.api.DomainMapService;
|
import org.qortal.api.DomainMapService;
|
||||||
import org.qortal.api.GatewayService;
|
import org.qortal.api.GatewayService;
|
||||||
|
import org.qortal.api.resource.TransactionsResource;
|
||||||
import org.qortal.block.Block;
|
import org.qortal.block.Block;
|
||||||
import org.qortal.block.BlockChain;
|
import org.qortal.block.BlockChain;
|
||||||
import org.qortal.block.BlockChain.BlockTimingByHeight;
|
import org.qortal.block.BlockChain.BlockTimingByHeight;
|
||||||
@ -200,6 +201,15 @@ public class Controller extends Thread {
|
|||||||
}
|
}
|
||||||
public GetAccountBalanceMessageStats getAccountBalanceMessageStats = new GetAccountBalanceMessageStats();
|
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 static class GetAccountNamesMessageStats {
|
||||||
public AtomicLong requests = new AtomicLong();
|
public AtomicLong requests = new AtomicLong();
|
||||||
public AtomicLong unknownAccounts = new AtomicLong();
|
public AtomicLong unknownAccounts = new AtomicLong();
|
||||||
@ -1280,6 +1290,10 @@ public class Controller extends Thread {
|
|||||||
onNetworkGetAccountBalanceMessage(peer, message);
|
onNetworkGetAccountBalanceMessage(peer, message);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
|
case GET_ACCOUNT_TRANSACTIONS:
|
||||||
|
onNetworkGetAccountTransactionsMessage(peer, message);
|
||||||
|
break;
|
||||||
|
|
||||||
case GET_ACCOUNT_NAMES:
|
case GET_ACCOUNT_NAMES:
|
||||||
onNetworkGetAccountNamesMessage(peer, message);
|
onNetworkGetAccountNamesMessage(peer, message);
|
||||||
break;
|
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) {
|
private void onNetworkGetAccountNamesMessage(Peer peer, Message message) {
|
||||||
GetAccountNamesMessage getAccountNamesMessage = (GetAccountNamesMessage) message;
|
GetAccountNamesMessage getAccountNamesMessage = (GetAccountNamesMessage) message;
|
||||||
String address = getAccountNamesMessage.getAddress();
|
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.AccountBalanceData;
|
||||||
import org.qortal.data.account.AccountData;
|
import org.qortal.data.account.AccountData;
|
||||||
import org.qortal.data.naming.NameData;
|
import org.qortal.data.naming.NameData;
|
||||||
|
import org.qortal.data.transaction.TransactionData;
|
||||||
import org.qortal.network.Network;
|
import org.qortal.network.Network;
|
||||||
import org.qortal.network.Peer;
|
import org.qortal.network.Peer;
|
||||||
import org.qortal.network.message.*;
|
import org.qortal.network.message.*;
|
||||||
@ -24,6 +25,8 @@ public class LiteNode {
|
|||||||
|
|
||||||
public Map<Integer, Long> pendingRequests = Collections.synchronizedMap(new HashMap<>());
|
public Map<Integer, Long> pendingRequests = Collections.synchronizedMap(new HashMap<>());
|
||||||
|
|
||||||
|
public int MAX_TRANSACTIONS_PER_MESSAGE = 100;
|
||||||
|
|
||||||
|
|
||||||
public LiteNode() {
|
public LiteNode() {
|
||||||
|
|
||||||
@ -66,6 +69,37 @@ public class LiteNode {
|
|||||||
return accountMessage.getAccountBalanceData();
|
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
|
* Fetch list of names for given QORT address
|
||||||
* @param address - the QORT address to query
|
* @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),
|
NAMES(180),
|
||||||
GET_ACCOUNT_NAMES(181),
|
GET_ACCOUNT_NAMES(181),
|
||||||
GET_NAME(182);
|
GET_NAME(182),
|
||||||
|
|
||||||
|
TRANSACTIONS(190),
|
||||||
|
GET_ACCOUNT_TRANSACTIONS(191);
|
||||||
|
|
||||||
public final int value;
|
public final int value;
|
||||||
public final Method fromByteBufferMethod;
|
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