mirror of
https://github.com/Qortal/qortal.git
synced 2025-04-01 17:55:54 +00:00
Update LiteNode.java
* Added exception handling * Enhance peer filtering and random selection method * Improve logging * Enhance fetch methods * Replaced HashMap with ConcurrentHashMap for thread safety in pendingRequests
This commit is contained in:
parent
8ffb0625a1
commit
5b024efc0a
@ -12,178 +12,152 @@ import org.qortal.network.message.*;
|
|||||||
|
|
||||||
import java.security.SecureRandom;
|
import java.security.SecureRandom;
|
||||||
import java.util.*;
|
import java.util.*;
|
||||||
|
import java.util.concurrent.ConcurrentHashMap;
|
||||||
|
|
||||||
import static org.qortal.network.message.MessageType.*;
|
import static org.qortal.network.message.MessageType.*;
|
||||||
|
|
||||||
public class LiteNode {
|
public class LiteNode {
|
||||||
|
|
||||||
private static final Logger LOGGER = LogManager.getLogger(LiteNode.class);
|
private static final Logger LOGGER = LogManager.getLogger(LiteNode.class);
|
||||||
|
private static final int MAX_TRANSACTIONS_PER_MESSAGE = 100;
|
||||||
|
|
||||||
private static LiteNode instance;
|
private static LiteNode instance;
|
||||||
|
|
||||||
|
private final Map<Integer, Long> pendingRequests = new ConcurrentHashMap<>();
|
||||||
|
|
||||||
public Map<Integer, Long> pendingRequests = Collections.synchronizedMap(new HashMap<>());
|
private LiteNode() {
|
||||||
|
|
||||||
public int MAX_TRANSACTIONS_PER_MESSAGE = 100;
|
|
||||||
|
|
||||||
|
|
||||||
public LiteNode() {
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public static synchronized LiteNode getInstance() {
|
public static synchronized LiteNode getInstance() {
|
||||||
if (instance == null) {
|
if (instance == null) {
|
||||||
instance = new LiteNode();
|
instance = new LiteNode();
|
||||||
}
|
}
|
||||||
|
|
||||||
return instance;
|
return instance;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Fetch account data from peers for given QORT address
|
|
||||||
* @param address - the QORT address to query
|
|
||||||
* @return accountData - the account data for this address, or null if not retrieved
|
|
||||||
*/
|
|
||||||
public AccountData fetchAccountData(String address) {
|
public AccountData fetchAccountData(String address) {
|
||||||
GetAccountMessage getAccountMessage = new GetAccountMessage(address);
|
LOGGER.debug("Fetching account data for address: {}", address);
|
||||||
AccountMessage accountMessage = (AccountMessage) this.sendMessage(getAccountMessage, ACCOUNT);
|
try {
|
||||||
if (accountMessage == null) {
|
GetAccountMessage getAccountMessage = new GetAccountMessage(address);
|
||||||
|
AccountMessage accountMessage = (AccountMessage) this.sendMessage(getAccountMessage, ACCOUNT);
|
||||||
|
return accountMessage != null ? accountMessage.getAccountData() : null;
|
||||||
|
} catch (Exception e) {
|
||||||
|
LOGGER.error("Failed to fetch account data for address: {}", address, e);
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
return accountMessage.getAccountData();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Fetch account balance data from peers for given QORT address and asset ID
|
|
||||||
* @param address - the QORT address to query
|
|
||||||
* @return balance - the balance for this address and assetId, or null if not retrieved
|
|
||||||
*/
|
|
||||||
public AccountBalanceData fetchAccountBalance(String address, long assetId) {
|
public AccountBalanceData fetchAccountBalance(String address, long assetId) {
|
||||||
GetAccountBalanceMessage getAccountMessage = new GetAccountBalanceMessage(address, assetId);
|
LOGGER.debug("Fetching account balance for address: {}, assetId: {}", address, assetId);
|
||||||
AccountBalanceMessage accountMessage = (AccountBalanceMessage) this.sendMessage(getAccountMessage, ACCOUNT_BALANCE);
|
try {
|
||||||
if (accountMessage == null) {
|
GetAccountBalanceMessage getAccountMessage = new GetAccountBalanceMessage(address, assetId);
|
||||||
|
AccountBalanceMessage accountMessage = (AccountBalanceMessage) this.sendMessage(getAccountMessage, ACCOUNT_BALANCE);
|
||||||
|
return accountMessage != null ? accountMessage.getAccountBalanceData() : null;
|
||||||
|
} catch (Exception e) {
|
||||||
|
LOGGER.error("Failed to fetch account balance for address: {}, assetId: {}", address, assetId, e);
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
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) {
|
public List<TransactionData> fetchAccountTransactions(String address, int limit, int offset) {
|
||||||
|
LOGGER.debug("Fetching transactions for address: {}, limit: {}, offset: {}", address, limit, offset);
|
||||||
List<TransactionData> allTransactions = new ArrayList<>();
|
List<TransactionData> allTransactions = new ArrayList<>();
|
||||||
if (limit == 0) {
|
limit = (limit == 0) ? Integer.MAX_VALUE : limit;
|
||||||
limit = Integer.MAX_VALUE;
|
|
||||||
}
|
|
||||||
int batchSize = Math.min(limit, MAX_TRANSACTIONS_PER_MESSAGE);
|
|
||||||
|
|
||||||
while (allTransactions.size() < limit) {
|
try {
|
||||||
GetAccountTransactionsMessage getAccountTransactionsMessage = new GetAccountTransactionsMessage(address, batchSize, offset);
|
int batchSize = Math.min(limit, MAX_TRANSACTIONS_PER_MESSAGE);
|
||||||
TransactionsMessage transactionsMessage = (TransactionsMessage) this.sendMessage(getAccountTransactionsMessage, TRANSACTIONS);
|
while (allTransactions.size() < limit) {
|
||||||
if (transactionsMessage == null) {
|
GetAccountTransactionsMessage getAccountTransactionsMessage =
|
||||||
// An error occurred, so give up instead of returning partial results
|
new GetAccountTransactionsMessage(address, batchSize, offset);
|
||||||
return null;
|
TransactionsMessage transactionsMessage =
|
||||||
|
(TransactionsMessage) this.sendMessage(getAccountTransactionsMessage, TRANSACTIONS);
|
||||||
|
|
||||||
|
if (transactionsMessage == null || transactionsMessage.getTransactions() == null) {
|
||||||
|
LOGGER.warn("No transactions received for address: {}", address);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
allTransactions.addAll(transactionsMessage.getTransactions());
|
||||||
|
|
||||||
|
if (transactionsMessage.getTransactions().size() < batchSize) {
|
||||||
|
break; // No more transactions to fetch
|
||||||
|
}
|
||||||
|
|
||||||
|
offset += batchSize;
|
||||||
}
|
}
|
||||||
allTransactions.addAll(transactionsMessage.getTransactions());
|
} catch (Exception e) {
|
||||||
if (transactionsMessage.getTransactions().size() < batchSize) {
|
LOGGER.error("Failed to fetch transactions for address: {}", address, e);
|
||||||
// No more transactions to fetch
|
return null;
|
||||||
break;
|
|
||||||
}
|
|
||||||
offset += batchSize;
|
|
||||||
}
|
}
|
||||||
return allTransactions;
|
return allTransactions;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Fetch list of names for given QORT address
|
|
||||||
* @param address - the QORT address to query
|
|
||||||
* @return a list of NameData objects, or null if not retrieved
|
|
||||||
*/
|
|
||||||
public List<NameData> fetchAccountNames(String address) {
|
public List<NameData> fetchAccountNames(String address) {
|
||||||
GetAccountNamesMessage getAccountNamesMessage = new GetAccountNamesMessage(address);
|
LOGGER.debug("Fetching account names for address: {}", address);
|
||||||
NamesMessage namesMessage = (NamesMessage) this.sendMessage(getAccountNamesMessage, NAMES);
|
try {
|
||||||
if (namesMessage == null) {
|
GetAccountNamesMessage getAccountNamesMessage = new GetAccountNamesMessage(address);
|
||||||
|
NamesMessage namesMessage = (NamesMessage) this.sendMessage(getAccountNamesMessage, NAMES);
|
||||||
|
return namesMessage != null ? namesMessage.getNameDataList() : null;
|
||||||
|
} catch (Exception e) {
|
||||||
|
LOGGER.error("Failed to fetch account names for address: {}", address, e);
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
return namesMessage.getNameDataList();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Fetch info about a registered name
|
|
||||||
* @param name - the name to query
|
|
||||||
* @return a NameData object, or null if not retrieved
|
|
||||||
*/
|
|
||||||
public NameData fetchNameData(String name) {
|
public NameData fetchNameData(String name) {
|
||||||
GetNameMessage getNameMessage = new GetNameMessage(name);
|
LOGGER.debug("Fetching name data for name: {}", name);
|
||||||
NamesMessage namesMessage = (NamesMessage) this.sendMessage(getNameMessage, NAMES);
|
try {
|
||||||
if (namesMessage == null) {
|
GetNameMessage getNameMessage = new GetNameMessage(name);
|
||||||
return null;
|
NamesMessage namesMessage = (NamesMessage) this.sendMessage(getNameMessage, NAMES);
|
||||||
}
|
|
||||||
List<NameData> nameDataList = namesMessage.getNameDataList();
|
|
||||||
if (nameDataList == null || nameDataList.size() != 1) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
// We are only expecting a single item in the list
|
|
||||||
return nameDataList.get(0);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
if (namesMessage == null || namesMessage.getNameDataList() == null || namesMessage.getNameDataList().size() != 1) {
|
||||||
|
LOGGER.warn("Unexpected name data response for name: {}", name);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return namesMessage.getNameDataList().get(0);
|
||||||
|
} catch (Exception e) {
|
||||||
|
LOGGER.error("Failed to fetch name data for name: {}", name, e);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private Message sendMessage(Message message, MessageType expectedResponseMessageType) {
|
private Message sendMessage(Message message, MessageType expectedResponseMessageType) {
|
||||||
// This asks a random peer for the data
|
LOGGER.debug("Preparing to send {} message", message.getType());
|
||||||
// TODO: ask multiple peers, and disregard everything if there are any significant differences in the responses
|
|
||||||
|
|
||||||
// Needs a mutable copy of the unmodifiableList
|
|
||||||
List<Peer> peers = new ArrayList<>(Network.getInstance().getImmutableHandshakedPeers());
|
|
||||||
|
|
||||||
// Disregard peers that have "misbehaved" recently
|
|
||||||
peers.removeIf(Controller.hasMisbehaved);
|
|
||||||
|
|
||||||
// Disregard peers that only have genesis block
|
|
||||||
// TODO: peers.removeIf(Controller.hasOnlyGenesisBlock);
|
|
||||||
|
|
||||||
// Disregard peers that are on an old version
|
|
||||||
peers.removeIf(Controller.hasOldVersion);
|
|
||||||
|
|
||||||
// Disregard peers that are on a known inferior chain tip
|
|
||||||
// TODO: peers.removeIf(Controller.hasInferiorChainTip);
|
|
||||||
|
|
||||||
if (peers.isEmpty()) {
|
|
||||||
LOGGER.info("No peers available to send {} message to", message.getType());
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Pick random peer
|
|
||||||
int index = new SecureRandom().nextInt(peers.size());
|
|
||||||
Peer peer = peers.get(index);
|
|
||||||
|
|
||||||
LOGGER.info("Sending {} message to peer {}...", message.getType(), peer);
|
|
||||||
|
|
||||||
Message responseMessage;
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
responseMessage = peer.getResponse(message);
|
List<Peer> peers = new ArrayList<>(Network.getInstance().getImmutableHandshakedPeers());
|
||||||
|
peers.removeIf(Controller.hasMisbehaved);
|
||||||
|
peers.removeIf(Controller.hasOldVersion);
|
||||||
|
|
||||||
|
if (peers.isEmpty()) {
|
||||||
|
LOGGER.warn("No suitable peers available to send {} message", message.getType());
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
Peer peer = peers.get(new SecureRandom().nextInt(peers.size()));
|
||||||
|
LOGGER.debug("Sending {} message to peer {}", message.getType(), peer);
|
||||||
|
|
||||||
|
Message responseMessage = peer.getResponse(message);
|
||||||
|
if (responseMessage == null) {
|
||||||
|
LOGGER.warn("No response received for {} message from peer {}", message.getType(), peer);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (responseMessage.getType() != expectedResponseMessageType) {
|
||||||
|
LOGGER.warn("Unexpected response type {} for {} message from peer {}", responseMessage.getType(), message.getType(), peer);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
LOGGER.debug("Successfully received {} message from peer {}", responseMessage.getType(), peer);
|
||||||
|
return responseMessage;
|
||||||
|
|
||||||
} catch (InterruptedException e) {
|
} catch (InterruptedException e) {
|
||||||
|
LOGGER.error("Message sending interrupted for {} message", message.getType(), e);
|
||||||
|
Thread.currentThread().interrupt(); // Restore interrupt status
|
||||||
|
return null;
|
||||||
|
} catch (Exception e) {
|
||||||
|
LOGGER.error("Error sending {} message", message.getType(), e);
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (responseMessage == null) {
|
|
||||||
LOGGER.info("Peer {} didn't respond to {} message", peer, message.getType());
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
else if (responseMessage.getType() != expectedResponseMessageType) {
|
|
||||||
LOGGER.info("Peer responded with unexpected message type {} (should be {})", peer, responseMessage.getType(), expectedResponseMessageType);
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
LOGGER.info("Peer {} responded with {} message", peer, responseMessage.getType());
|
|
||||||
|
|
||||||
return responseMessage;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user