optimize and reduce electrum server rpc calls

This commit is contained in:
Craig Raw 2025-04-28 14:39:30 +02:00
parent 7a4015fdb5
commit e3138f3392
2 changed files with 82 additions and 50 deletions

View file

@ -61,11 +61,13 @@ public class ElectrumServer {
private static Server previousServer; private static Server previousServer;
private static Map<String, String> retrievedScriptHashes = Collections.synchronizedMap(new HashMap<>()); private static final Map<String, String> retrievedScriptHashes = Collections.synchronizedMap(new HashMap<>());
private static Map<Sha256Hash, BlockTransaction> retrievedTransactions = Collections.synchronizedMap(new HashMap<>()); private static final Map<Sha256Hash, BlockTransaction> retrievedTransactions = Collections.synchronizedMap(new HashMap<>());
private static Set<String> sameHeightTxioScriptHashes = Collections.synchronizedSet(new HashSet<>()); private static final Map<Integer, BlockHeader> retrievedBlockHeaders = Collections.synchronizedMap(new HashMap<>());
private static final Set<String> sameHeightTxioScriptHashes = Collections.synchronizedSet(new HashSet<>());
private static ElectrumServerRpc electrumServerRpc = new SimpleElectrumServerRpc(); private static ElectrumServerRpc electrumServerRpc = new SimpleElectrumServerRpc();
@ -113,6 +115,7 @@ public class ElectrumServer {
if(previousServer != null && !electrumServer.equals(previousServer)) { if(previousServer != null && !electrumServer.equals(previousServer)) {
retrievedScriptHashes.clear(); retrievedScriptHashes.clear();
retrievedTransactions.clear(); retrievedTransactions.clear();
retrievedBlockHeaders.clear();
TransactionHistoryService.walletLocks.values().forEach(walletLock -> walletLock.initialized = false); TransactionHistoryService.walletLocks.values().forEach(walletLock -> walletLock.initialized = false);
} }
previousServer = electrumServer; previousServer = electrumServer;
@ -555,22 +558,29 @@ public class ElectrumServer {
} }
public void getReferencedTransactions(Wallet wallet, Map<WalletNode, Set<BlockTransactionHash>> nodeTransactionMap) throws ServerException { public void getReferencedTransactions(Wallet wallet, Map<WalletNode, Set<BlockTransactionHash>> nodeTransactionMap) throws ServerException {
Set<BlockTransactionHash> references = new TreeSet<>(); Map<BlockTransactionHash, Transaction> references = new TreeMap<>();
for(Set<BlockTransactionHash> nodeReferences : nodeTransactionMap.values()) { for(Set<BlockTransactionHash> nodeReferences : nodeTransactionMap.values()) {
references.addAll(nodeReferences); for(BlockTransactionHash nodeReference : nodeReferences) {
references.put(nodeReference, null);
}
} }
for(Iterator<BlockTransactionHash> iter = references.iterator(); iter.hasNext(); ) { for(Iterator<Map.Entry<BlockTransactionHash, Transaction>> iter = references.entrySet().iterator(); iter.hasNext(); ) {
BlockTransactionHash reference = iter.next(); Map.Entry<BlockTransactionHash, Transaction> entry = iter.next();
BlockTransaction blockTransaction = wallet.getTransactions().get(reference.getHash()); BlockTransactionHash reference = entry.getKey();
if(blockTransaction != null && reference.getHeight() == blockTransaction.getHeight()) { BlockTransaction blockTransaction = wallet.getWalletTransaction(reference.getHash());
if(blockTransaction != null) {
if(reference.getHeight() == blockTransaction.getHeight()) {
iter.remove(); iter.remove();
} else {
entry.setValue(blockTransaction.getTransaction());
}
} }
} }
Map<Sha256Hash, BlockTransaction> transactionMap = new HashMap<>(); Map<Sha256Hash, BlockTransaction> transactionMap = new HashMap<>();
if(!references.isEmpty()) { if(!references.isEmpty()) {
Map<Integer, BlockHeader> blockHeaderMap = getBlockHeaders(wallet, references); Map<Integer, BlockHeader> blockHeaderMap = getBlockHeaders(wallet, references.keySet());
transactionMap = getTransactions(wallet, references, blockHeaderMap); transactionMap = getTransactions(wallet, references, blockHeaderMap);
} }
@ -581,24 +591,29 @@ public class ElectrumServer {
public Map<Integer, BlockHeader> getBlockHeaders(Wallet wallet, Set<BlockTransactionHash> references) throws ServerException { public Map<Integer, BlockHeader> getBlockHeaders(Wallet wallet, Set<BlockTransactionHash> references) throws ServerException {
try { try {
Map<Integer, BlockHeader> blockHeaderMap = new TreeMap<>();
Set<Integer> blockHeights = new TreeSet<>(); Set<Integer> blockHeights = new TreeSet<>();
for(BlockTransactionHash reference : references) { for(BlockTransactionHash reference : references) {
if(reference.getHeight() > 0) { if(reference.getHeight() > 0) {
if(retrievedBlockHeaders.get(reference.getHeight()) != null) {
blockHeaderMap.put(reference.getHeight(), retrievedBlockHeaders.get(reference.getHeight()));
} else {
blockHeights.add(reference.getHeight()); blockHeights.add(reference.getHeight());
} }
} }
}
if(blockHeights.isEmpty()) { if(blockHeights.isEmpty()) {
return Collections.emptyMap(); return blockHeaderMap;
} }
Map<Integer, String> result = electrumServerRpc.getBlockHeaders(getTransport(), wallet, blockHeights); Map<Integer, String> result = electrumServerRpc.getBlockHeaders(getTransport(), wallet, blockHeights);
Map<Integer, BlockHeader> blockHeaderMap = new TreeMap<>();
for(Integer height : result.keySet()) { for(Integer height : result.keySet()) {
byte[] blockHeaderBytes = Utils.hexToBytes(result.get(height)); byte[] blockHeaderBytes = Utils.hexToBytes(result.get(height));
BlockHeader blockHeader = new BlockHeader(blockHeaderBytes); BlockHeader blockHeader = new BlockHeader(blockHeaderBytes);
blockHeaderMap.put(height, blockHeader); blockHeaderMap.put(height, blockHeader);
updateRetrievedBlockHeaders(height, blockHeader);
blockHeights.remove(height); blockHeights.remove(height);
} }
@ -616,19 +631,22 @@ public class ElectrumServer {
} }
} }
public Map<Sha256Hash, BlockTransaction> getTransactions(Wallet wallet, Set<BlockTransactionHash> references, Map<Integer, BlockHeader> blockHeaderMap) throws ServerException { public Map<Sha256Hash, BlockTransaction> getTransactions(Wallet wallet, Map<BlockTransactionHash, Transaction> references, Map<Integer, BlockHeader> blockHeaderMap) throws ServerException {
try { try {
Set<BlockTransactionHash> checkReferences = new TreeSet<>(references); Map<Sha256Hash, BlockTransaction> transactionMap = new HashMap<>();
Set<BlockTransactionHash> checkReferences = new TreeSet<>(references.keySet());
Set<String> txids = new LinkedHashSet<>(references.size()); Set<String> txids = new LinkedHashSet<>(references.size());
for(BlockTransactionHash reference : references) { for(BlockTransactionHash reference : references.keySet()) {
if(references.get(reference) == null) {
txids.add(reference.getHashAsString()); txids.add(reference.getHashAsString());
} }
}
if(!txids.isEmpty()) {
Map<String, String> result = electrumServerRpc.getTransactions(getTransport(), wallet, txids); Map<String, String> result = electrumServerRpc.getTransactions(getTransport(), wallet, txids);
String strErrorTx = Sha256Hash.ZERO_HASH.toString(); String strErrorTx = Sha256Hash.ZERO_HASH.toString();
Map<Sha256Hash, BlockTransaction> transactionMap = new HashMap<>();
for(String txid : result.keySet()) { for(String txid : result.keySet()) {
Sha256Hash hash = Sha256Hash.wrap(txid); Sha256Hash hash = Sha256Hash.wrap(txid);
String strRawTx = result.get(txid); String strRawTx = result.get(txid);
@ -649,18 +667,25 @@ public class ElectrumServer {
continue; continue;
} }
Optional<BlockTransactionHash> optionalReference = references.stream().filter(reference -> reference.getHash().equals(hash)).findFirst(); Optional<BlockTransactionHash> optionalReference = references.keySet().stream().filter(reference -> reference.getHash().equals(hash)).findFirst();
if(optionalReference.isEmpty()) { if(optionalReference.isEmpty()) {
throw new IllegalStateException("Returned transaction " + hash.toString() + " that was not requested"); throw new IllegalStateException("Returned transaction " + hash.toString() + " that was not requested");
} }
BlockTransactionHash reference = optionalReference.get(); BlockTransactionHash reference = optionalReference.get();
references.put(reference, transaction);
}
}
for(BlockTransactionHash reference : references.keySet()) {
Transaction transaction = references.get(reference);
Date blockDate = null; Date blockDate = null;
if(reference.getHeight() > 0) { if(reference.getHeight() > 0) {
BlockHeader blockHeader = blockHeaderMap.get(reference.getHeight()); BlockHeader blockHeader = blockHeaderMap.get(reference.getHeight());
if(blockHeader == null) { if(blockHeader == null) {
transactionMap.put(hash, UNFETCHABLE_BLOCK_TRANSACTION); transactionMap.put(reference.getHash(), UNFETCHABLE_BLOCK_TRANSACTION);
checkReferences.removeIf(ref -> ref.getHash().equals(hash)); checkReferences.removeIf(ref -> ref.getHash().equals(reference.getHash()));
continue; continue;
} }
blockDate = blockHeader.getTimeAsDate(); blockDate = blockHeader.getTimeAsDate();
@ -668,7 +693,7 @@ public class ElectrumServer {
BlockTransaction blockchainTransaction = new BlockTransaction(reference.getHash(), reference.getHeight(), blockDate, reference.getFee(), transaction); BlockTransaction blockchainTransaction = new BlockTransaction(reference.getHash(), reference.getHeight(), blockDate, reference.getFee(), transaction);
transactionMap.put(hash, blockchainTransaction); transactionMap.put(reference.getHash(), blockchainTransaction);
checkReferences.remove(reference); checkReferences.remove(reference);
} }
@ -1024,6 +1049,10 @@ public class ElectrumServer {
existingStatuses.add(status); existingStatuses.add(status);
} }
public static void updateRetrievedBlockHeaders(Integer blockHeight, BlockHeader blockHeader) {
retrievedBlockHeaders.put(blockHeight, blockHeader);
}
public static ServerCapability getServerCapability(List<String> serverVersion) { public static ServerCapability getServerCapability(List<String> serverVersion) {
if(!serverVersion.isEmpty()) { if(!serverVersion.isEmpty()) {
String server = serverVersion.getFirst().toLowerCase(Locale.ROOT); String server = serverVersion.getFirst().toLowerCase(Locale.ROOT);
@ -1630,15 +1659,17 @@ public class ElectrumServer {
ElectrumServer electrumServer = new ElectrumServer(); ElectrumServer electrumServer = new ElectrumServer();
List<Set<BlockTransactionHash>> outputTransactionReferences = electrumServer.getOutputTransactionReferences(transaction, indexStart, indexEnd, blockTransactionHashes); List<Set<BlockTransactionHash>> outputTransactionReferences = electrumServer.getOutputTransactionReferences(transaction, indexStart, indexEnd, blockTransactionHashes);
Set<BlockTransactionHash> setReferences = new HashSet<>(); Map<BlockTransactionHash, Transaction> setReferences = new HashMap<>();
for(Set<BlockTransactionHash> outputReferences : outputTransactionReferences) { for(Set<BlockTransactionHash> outputReferences : outputTransactionReferences) {
if(outputReferences != null) { if(outputReferences != null) {
setReferences.addAll(outputReferences); for(BlockTransactionHash outputReference : outputReferences) {
setReferences.put(outputReference, null);
}
} }
} }
setReferences.remove(null); setReferences.remove(null);
setReferences.remove(UNFETCHABLE_BLOCK_TRANSACTION); setReferences.remove(UNFETCHABLE_BLOCK_TRANSACTION);
setReferences.removeIf(ref -> transactionMap.get(ref.getHash()) != null); setReferences.keySet().removeIf(ref -> transactionMap.get(ref.getHash()) != null);
List<BlockTransaction> blockTransactions = new ArrayList<>(transaction.getOutputs().size()); List<BlockTransaction> blockTransactions = new ArrayList<>(transaction.getOutputs().size());
for(int i = 0; i < transaction.getOutputs().size(); i++) { for(int i = 0; i < transaction.getOutputs().size(); i++) {
@ -1646,7 +1677,7 @@ public class ElectrumServer {
} }
if(!setReferences.isEmpty()) { if(!setReferences.isEmpty()) {
Map<Integer, BlockHeader> blockHeaderMap = electrumServer.getBlockHeaders(null, setReferences); Map<Integer, BlockHeader> blockHeaderMap = electrumServer.getBlockHeaders(null, setReferences.keySet());
transactionMap.putAll(electrumServer.getTransactions(null, setReferences, blockHeaderMap)); transactionMap.putAll(electrumServer.getTransactions(null, setReferences, blockHeaderMap));
} }

View file

@ -20,6 +20,7 @@ public class SubscriptionService {
@JsonRpcMethod("blockchain.headers.subscribe") @JsonRpcMethod("blockchain.headers.subscribe")
public void newBlockHeaderTip(@JsonRpcParam("header") final BlockHeaderTip header) { public void newBlockHeaderTip(@JsonRpcParam("header") final BlockHeaderTip header) {
ElectrumServer.updateRetrievedBlockHeaders(header.height, header.getBlockHeader());
Platform.runLater(() -> EventManager.get().post(new NewBlockEvent(header.height, header.getBlockHeader()))); Platform.runLater(() -> EventManager.get().post(new NewBlockEvent(header.height, header.getBlockHeader())));
} }