From 02a0a3277bdc4469d7369cb1915a1f47c05e05d1 Mon Sep 17 00:00:00 2001 From: Craig Raw Date: Fri, 12 Jan 2024 15:35:26 +0200 Subject: [PATCH] reduce server calls on opening a transaction tab by using open wallet history when performing spent output lookup --- .../sparrow/net/ElectrumServer.java | 43 +++++++++++-------- .../transaction/TransactionController.java | 43 +++++++++++++++++-- 2 files changed, 66 insertions(+), 20 deletions(-) diff --git a/src/main/java/com/sparrowwallet/sparrow/net/ElectrumServer.java b/src/main/java/com/sparrowwallet/sparrow/net/ElectrumServer.java index 5ee63a09..14efcef8 100644 --- a/src/main/java/com/sparrowwallet/sparrow/net/ElectrumServer.java +++ b/src/main/java/com/sparrowwallet/sparrow/net/ElectrumServer.java @@ -504,19 +504,19 @@ public class ElectrumServer { } } - public List> getOutputTransactionReferences(Transaction transaction, int indexStart, int indexEnd) throws ServerException { + public List> getOutputTransactionReferences(Transaction transaction, int indexStart, int indexEnd, List> blockTransactionHashes) throws ServerException { try { Map pathScriptHashes = new LinkedHashMap<>(); for(int i = indexStart; i < transaction.getOutputs().size() && i < indexEnd; i++) { - TransactionOutput output = transaction.getOutputs().get(i); - pathScriptHashes.put(Integer.toString(i), getScriptHash(output)); + if(blockTransactionHashes.get(i) == null) { + TransactionOutput output = transaction.getOutputs().get(i); + pathScriptHashes.put(Integer.toString(i), getScriptHash(output)); + } } - Map result = electrumServerRpc.getScriptHashHistory(getTransport(), null, pathScriptHashes, false); - - List> blockTransactionHashes = new ArrayList<>(transaction.getOutputs().size()); - for(int i = 0; i < transaction.getOutputs().size(); i++) { - blockTransactionHashes.add(null); + Map result = new HashMap<>(); + if(!pathScriptHashes.isEmpty()) { + result = electrumServerRpc.getScriptHashHistory(getTransport(), null, pathScriptHashes, false); } for(String index : result.keySet()) { @@ -1571,17 +1571,26 @@ public class ElectrumServer { private final Transaction transaction; private final int indexStart; private final int indexEnd; - - public TransactionOutputsReferenceService(Transaction transaction) { - this.transaction = transaction; - this.indexStart = 0; - this.indexEnd = transaction.getOutputs().size(); - } + private final List> blockTransactionHashes; + private final Map transactionMap; public TransactionOutputsReferenceService(Transaction transaction, int indexStart, int indexEnd) { this.transaction = transaction; this.indexStart = Math.min(transaction.getOutputs().size(), indexStart); this.indexEnd = Math.min(transaction.getOutputs().size(), indexEnd); + this.blockTransactionHashes = new ArrayList<>(transaction.getOutputs().size()); + for(int i = 0; i < transaction.getOutputs().size(); i++) { + blockTransactionHashes.add(null); + } + this.transactionMap = new HashMap<>(); + } + + public TransactionOutputsReferenceService(Transaction transaction, int indexStart, int indexEnd, List> blockTransactionHashes, Map transactionMap) { + this.transaction = transaction; + this.indexStart = Math.min(transaction.getOutputs().size(), indexStart); + this.indexEnd = Math.min(transaction.getOutputs().size(), indexEnd); + this.blockTransactionHashes = blockTransactionHashes; + this.transactionMap = transactionMap; } @Override @@ -1589,7 +1598,7 @@ public class ElectrumServer { return new Task<>() { protected List call() throws ServerException { ElectrumServer electrumServer = new ElectrumServer(); - List> outputTransactionReferences = electrumServer.getOutputTransactionReferences(transaction, indexStart, indexEnd); + List> outputTransactionReferences = electrumServer.getOutputTransactionReferences(transaction, indexStart, indexEnd, blockTransactionHashes); Set setReferences = new HashSet<>(); for(Set outputReferences : outputTransactionReferences) { @@ -1599,16 +1608,16 @@ public class ElectrumServer { } setReferences.remove(null); setReferences.remove(UNFETCHABLE_BLOCK_TRANSACTION); + setReferences.removeIf(ref -> transactionMap.get(ref.getHash()) != null); List blockTransactions = new ArrayList<>(transaction.getOutputs().size()); for(int i = 0; i < transaction.getOutputs().size(); i++) { blockTransactions.add(null); } - Map transactionMap = new HashMap<>(); if(!setReferences.isEmpty()) { Map blockHeaderMap = electrumServer.getBlockHeaders(null, setReferences); - transactionMap = electrumServer.getTransactions(null, setReferences, blockHeaderMap); + transactionMap.putAll(electrumServer.getTransactions(null, setReferences, blockHeaderMap)); } for(int i = 0; i < outputTransactionReferences.size(); i++) { diff --git a/src/main/java/com/sparrowwallet/sparrow/transaction/TransactionController.java b/src/main/java/com/sparrowwallet/sparrow/transaction/TransactionController.java index 17671d22..4ab72f74 100644 --- a/src/main/java/com/sparrowwallet/sparrow/transaction/TransactionController.java +++ b/src/main/java/com/sparrowwallet/sparrow/transaction/TransactionController.java @@ -5,8 +5,7 @@ import com.sparrowwallet.drongo.protocol.*; import com.sparrowwallet.drongo.psbt.PSBT; import com.sparrowwallet.drongo.psbt.PSBTInput; import com.sparrowwallet.drongo.psbt.PSBTOutput; -import com.sparrowwallet.drongo.wallet.BlockTransaction; -import com.sparrowwallet.drongo.wallet.Wallet; +import com.sparrowwallet.drongo.wallet.*; import com.sparrowwallet.sparrow.AppServices; import com.sparrowwallet.sparrow.EventManager; import com.sparrowwallet.sparrow.TransactionTabData; @@ -33,6 +32,7 @@ import java.io.IOException; import java.net.URL; import java.util.*; import java.util.stream.Collectors; +import java.util.stream.Stream; public class TransactionController implements Initializable { private static final Logger log = LoggerFactory.getLogger(TransactionController.class); @@ -426,7 +426,44 @@ public class TransactionController implements Initializable { private void fetchOutputBlockTransactions(int indexStart, int indexEnd) { if(AppServices.isConnected() && getPSBT() == null && indexStart < getTransaction().getOutputs().size()) { int maxIndex = Math.min(getTransaction().getOutputs().size(), indexEnd); - ElectrumServer.TransactionOutputsReferenceService transactionOutputsReferenceService = new ElectrumServer.TransactionOutputsReferenceService(getTransaction(), indexStart, maxIndex); + + Map transactionMap = new HashMap<>(); + List> blockTransactionHashes = new ArrayList<>(getTransaction().getOutputs().size()); + for(int i = 0; i < getTransaction().getOutputs().size(); i++) { + blockTransactionHashes.add(null); + } + + Map openWalletOutputScripts = new HashMap<>(); + for(Wallet wallet : AppServices.get().getOpenWallets().keySet()) { + openWalletOutputScripts.putAll(wallet.getWalletOutputScripts()); + for(Wallet childWallet : wallet.getChildWallets()) { + if(!childWallet.isNested()) { + openWalletOutputScripts.putAll(childWallet.getWalletOutputScripts()); + } + } + } + + for(int i = indexStart; i < getTransaction().getOutputs().size() && i < maxIndex; i++) { + TransactionOutput output = getTransaction().getOutputs().get(i); + List chunks = output.getScript().getChunks(); + if(!chunks.isEmpty() && chunks.get(0).isOpCode() && chunks.get(0).getOpcode() == ScriptOpCodes.OP_RETURN) { + blockTransactionHashes.set(i, Collections.emptySet()); + } else if(openWalletOutputScripts.get(output.getScript()) != null) { + WalletNode walletNode = openWalletOutputScripts.get(output.getScript()); + if(walletNode != null) { + Set references = walletNode.getTransactionOutputs().stream().flatMap(txo -> txo.isSpent() ? Stream.of(txo.getSpentBy()) : Stream.empty()).collect(Collectors.toCollection(TreeSet::new)); + blockTransactionHashes.set(i, references); + for(BlockTransactionHash reference : references) { + BlockTransaction blkTx = walletNode.getWallet().getWalletTransaction(reference.getHash()); + if(blkTx != null) { + transactionMap.put(reference.getHash(), blkTx); + } + } + } + } + } + + ElectrumServer.TransactionOutputsReferenceService transactionOutputsReferenceService = new ElectrumServer.TransactionOutputsReferenceService(getTransaction(), indexStart, maxIndex, blockTransactionHashes, transactionMap); transactionOutputsReferenceService.setOnSucceeded(successEvent -> { List outputTransactions = transactionOutputsReferenceService.getValue(); Platform.runLater(() -> {