From 3a70213d1b8f5c7d622aebf11193a9827af972bd Mon Sep 17 00:00:00 2001 From: Steven Barclay Date: Sun, 5 May 2024 23:57:12 +0200 Subject: [PATCH] Add new tx-by-id cache to speed up BsqWalletService.isWalletTransaction Reduce a hotspot searching the BSQ wallet for the user's votes, upon selecting a list item of the Vote Result view, by optimising the method 'BsqWalletService.isWalletTransaction(String)'. Do this by adding a lazily initialised Map field, 'walletTransactionsById', kept in sync with the existing 'walletTransactions' List field, similar to the tx-by- id cache removed from the base class in the previous commit, so that a linear scan of that list can be avoided. Don't bother to make the cache thread safe, however, since 'isWalletTransaction' is only called from the user thread and wasn't thread safe to begin with -- access to 'walletTransactions' isn't synchronised, and it is updated only on the user thread, after a 100 ms delay upon any changes to the BSQ wallet. Also remove the unused methods 'getUnverifiedBsqTransactions()' and 'getBsqWalletTransactions()' from the class. --- .../core/btc/wallet/BsqWalletService.java | 55 +++++-------------- 1 file changed, 15 insertions(+), 40 deletions(-) diff --git a/core/src/main/java/bisq/core/btc/wallet/BsqWalletService.java b/core/src/main/java/bisq/core/btc/wallet/BsqWalletService.java index f07ea8cf97..78ee3991b4 100644 --- a/core/src/main/java/bisq/core/btc/wallet/BsqWalletService.java +++ b/core/src/main/java/bisq/core/btc/wallet/BsqWalletService.java @@ -48,7 +48,6 @@ import org.bitcoinj.core.InsufficientMoneyException; import org.bitcoinj.core.NetworkParameters; import org.bitcoinj.core.Sha256Hash; import org.bitcoinj.core.Transaction; -import org.bitcoinj.core.TransactionConfidence; import org.bitcoinj.core.TransactionInput; import org.bitcoinj.core.TransactionOutPoint; import org.bitcoinj.core.TransactionOutput; @@ -60,14 +59,12 @@ import org.bitcoinj.wallet.SendRequest; import javax.inject.Inject; import java.util.ArrayList; -import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Optional; import java.util.Set; import java.util.concurrent.CopyOnWriteArraySet; import java.util.concurrent.TimeUnit; -import java.util.function.Function; import java.util.stream.Collectors; import java.util.stream.Stream; @@ -95,6 +92,7 @@ public class BsqWalletService extends WalletService implements DaoStateListener private final DaoStateService daoStateService; private final UnconfirmedBsqChangeOutputListService unconfirmedBsqChangeOutputListService; private final List walletTransactions = new ArrayList<>(); + private Map walletTransactionsById; private final CopyOnWriteArraySet bsqBalanceListeners = new CopyOnWriteArraySet<>(); private final List walletTransactionsChangeListeners = new ArrayList<>(); private boolean updateBsqWalletTransactionsPending; @@ -233,7 +231,7 @@ public class BsqWalletService extends WalletService implements DaoStateListener private void updateBsqBalance() { long ts = System.currentTimeMillis(); unverifiedBalance = Coin.valueOf( - getTransactions(false).stream() + walletTransactions.stream() .filter(tx -> tx.getConfidence().getConfidenceType() == PENDING) .mapToLong(tx -> { // Sum up outputs into BSQ wallet and subtract the inputs using lockup or unlocking @@ -270,7 +268,7 @@ public class BsqWalletService extends WalletService implements DaoStateListener .sum() ); - Set confirmedTxIdSet = getTransactions(false).stream() + Set confirmedTxIdSet = walletTransactions.stream() .filter(tx -> tx.getConfidence().getConfidenceType() == BUILDING) .map(Transaction::getTxId) .map(Sha256Hash::toString) @@ -343,13 +341,15 @@ public class BsqWalletService extends WalletService implements DaoStateListener // BSQ TransactionOutputs and Transactions /////////////////////////////////////////////////////////////////////////////////////////// + // not thread safe - call only from user thread public List getClonedWalletTransactions() { return new ArrayList<>(walletTransactions); } + // not thread safe - call only from user thread public Stream getPendingWalletTransactionsStream() { return walletTransactions.stream() - .filter(transaction -> transaction.getConfidence().getConfidenceType() == TransactionConfidence.ConfidenceType.PENDING); + .filter(transaction -> transaction.getConfidence().getConfidenceType() == PENDING); } private void updateBsqWalletTransactions() { @@ -363,6 +363,7 @@ public class BsqWalletService extends WalletService implements DaoStateListener UserThread.runAfter(() -> { walletTransactions.clear(); walletTransactions.addAll(getTransactions(false)); + walletTransactionsById = null; walletTransactionsChangeListeners.forEach(WalletTransactionsChangeListener::onWalletTransactionsChange); updateBsqBalance(); updateBsqWalletTransactionsPending = false; @@ -371,37 +372,6 @@ public class BsqWalletService extends WalletService implements DaoStateListener } } - private Set getBsqWalletTransactions() { - return getTransactions(false).stream() - .filter(transaction -> transaction.getConfidence().getConfidenceType() == PENDING || - daoStateService.containsTx(transaction.getTxId().toString())) - .collect(Collectors.toSet()); - } - - public Set getUnverifiedBsqTransactions() { - Set bsqWalletTransactions = getBsqWalletTransactions(); - Set walletTxs = new HashSet<>(getTransactions(false)); - checkArgument(walletTxs.size() >= bsqWalletTransactions.size(), - "We cannot have more txsWithOutputsFoundInBsqTxo than walletTxs"); - if (walletTxs.size() == bsqWalletTransactions.size()) { - // As expected - return new HashSet<>(); - } else { - Map map = walletTxs.stream() - .collect(Collectors.toMap(t -> t.getTxId().toString(), Function.identity())); - - Set walletTxIds = walletTxs.stream() - .map(Transaction::getTxId).map(Sha256Hash::toString).collect(Collectors.toSet()); - Set bsqTxIds = bsqWalletTransactions.stream() - .map(Transaction::getTxId).map(Sha256Hash::toString).collect(Collectors.toSet()); - - walletTxIds.stream() - .filter(bsqTxIds::contains) - .forEach(map::remove); - return new HashSet<>(map.values()); - } - } - @Override public Coin getValueSentFromMeForTransaction(Transaction transaction) throws ScriptException { Coin result = Coin.ZERO; @@ -414,7 +384,7 @@ public class BsqWalletService extends WalletService implements DaoStateListener // We grab the parent tx of the connected output final Transaction parentTransaction = connectedOutput.getParentTransaction(); final boolean isConfirmed = parentTransaction != null && - parentTransaction.getConfidence().getConfidenceType() == TransactionConfidence.ConfidenceType.BUILDING; + parentTransaction.getConfidence().getConfidenceType() == BUILDING; if (connectedOutput.isMineOrWatched(wallet)) { if (isConfirmed) { // We lookup if we have a BSQ tx matching the parent tx @@ -455,7 +425,7 @@ public class BsqWalletService extends WalletService implements DaoStateListener for (int i = 0; i < transaction.getOutputs().size(); i++) { TransactionOutput output = transaction.getOutputs().get(i); final boolean isConfirmed = output.getParentTransaction() != null && - output.getParentTransaction().getConfidence().getConfidenceType() == TransactionConfidence.ConfidenceType.BUILDING; + output.getParentTransaction().getConfidence().getConfidenceType() == BUILDING; if (output.isMineOrWatched(wallet)) { if (isConfirmed) { if (txOptional.isPresent()) { @@ -484,8 +454,13 @@ public class BsqWalletService extends WalletService implements DaoStateListener return result; } + // not thread safe - call only from user thread public Optional isWalletTransaction(String txId) { - return walletTransactions.stream().filter(e -> e.getTxId().toString().equals(txId)).findAny(); + if (walletTransactionsById == null) { + walletTransactionsById = walletTransactions.stream() + .collect(Collectors.toMap(tx -> tx.getTxId().toString(), tx -> tx)); + } + return Optional.ofNullable(walletTransactionsById.get(txId)); }