Merge pull request #6552 from stejbac/speed-up-closed-trades-view-load

Speed up loading and scrolling of trade history
This commit is contained in:
Alejandro García 2023-02-03 15:13:58 +00:00 committed by GitHub
commit 2239924225
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
3 changed files with 70 additions and 22 deletions

View file

@ -72,6 +72,7 @@ import org.bitcoinj.wallet.listeners.WalletReorganizeEventListener;
import javax.inject.Inject; import javax.inject.Inject;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableMultiset; import com.google.common.collect.ImmutableMultiset;
import com.google.common.collect.ImmutableSetMultimap; import com.google.common.collect.ImmutableSetMultimap;
import com.google.common.collect.Multiset; import com.google.common.collect.Multiset;
@ -87,6 +88,7 @@ import org.bouncycastle.crypto.params.KeyParameter;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
import java.util.Map;
import java.util.Objects; import java.util.Objects;
import java.util.Set; import java.util.Set;
import java.util.concurrent.CopyOnWriteArraySet; import java.util.concurrent.CopyOnWriteArraySet;
@ -122,6 +124,7 @@ public abstract class WalletService {
private final WalletChangeEventListener cacheInvalidationListener; private final WalletChangeEventListener cacheInvalidationListener;
private final AtomicReference<Multiset<Address>> txOutputAddressCache = new AtomicReference<>(); private final AtomicReference<Multiset<Address>> txOutputAddressCache = new AtomicReference<>();
private final AtomicReference<SetMultimap<Address, Transaction>> addressToMatchingTxSetCache = new AtomicReference<>(); private final AtomicReference<SetMultimap<Address, Transaction>> addressToMatchingTxSetCache = new AtomicReference<>();
private final AtomicReference<Map<Sha256Hash, Transaction>> txByIdCache = new AtomicReference<>();
@Getter @Getter
protected Wallet wallet; protected Wallet wallet;
@Getter @Getter
@ -147,6 +150,7 @@ public abstract class WalletService {
cacheInvalidationListener = wallet -> { cacheInvalidationListener = wallet -> {
txOutputAddressCache.set(null); txOutputAddressCache.set(null);
addressToMatchingTxSetCache.set(null); addressToMatchingTxSetCache.set(null);
txByIdCache.set(null);
}; };
} }
@ -440,15 +444,15 @@ public abstract class WalletService {
public TransactionConfidence getConfidenceForAddress(Address address) { public TransactionConfidence getConfidenceForAddress(Address address) {
List<TransactionConfidence> transactionConfidenceList = new ArrayList<>(); List<TransactionConfidence> transactionConfidenceList = new ArrayList<>();
if (wallet != null) { if (wallet != null) {
Set<Transaction> transactions = getAddressToMatchingTxSetMultiset().get(address); Set<Transaction> transactions = getAddressToMatchingTxSetMultimap().get(address);
transactionConfidenceList.addAll(transactions.stream().map(tx -> transactionConfidenceList.addAll(transactions.stream().map(tx ->
getTransactionConfidence(tx, address)).collect(Collectors.toList())); getTransactionConfidence(tx, address)).collect(Collectors.toList()));
} }
return getMostRecentConfidence(transactionConfidenceList); return getMostRecentConfidence(transactionConfidenceList);
} }
private SetMultimap<Address, Transaction> getAddressToMatchingTxSetMultiset() { private SetMultimap<Address, Transaction> getAddressToMatchingTxSetMultimap() {
return addressToMatchingTxSetCache.updateAndGet(set -> set != null ? set : computeAddressToMatchingTxSetMultimap()); return addressToMatchingTxSetCache.updateAndGet(map -> map != null ? map : computeAddressToMatchingTxSetMultimap());
} }
private SetMultimap<Address, Transaction> computeAddressToMatchingTxSetMultimap() { private SetMultimap<Address, Transaction> computeAddressToMatchingTxSetMultimap() {
@ -463,17 +467,25 @@ public abstract class WalletService {
} }
@Nullable @Nullable
public TransactionConfidence getConfidenceForTxId(String txId) { public TransactionConfidence getConfidenceForTxId(@Nullable String txId) {
if (wallet != null) { if (wallet != null && txId != null) {
Set<Transaction> transactions = wallet.getTransactions(false); Transaction tx = getTxByIdMap().get(Sha256Hash.wrap(txId));
for (Transaction tx : transactions) { if (tx != null) {
if (tx.getTxId().toString().equals(txId))
return tx.getConfidence(); return tx.getConfidence();
} }
} }
return null; return null;
} }
private Map<Sha256Hash, Transaction> getTxByIdMap() {
return txByIdCache.updateAndGet(map -> map != null ? map : computeTxByIdMap());
}
private Map<Sha256Hash, Transaction> computeTxByIdMap() {
return wallet.getTransactions(false).stream()
.collect(ImmutableMap.toImmutableMap(Transaction::getTxId, tx -> tx));
}
@Nullable @Nullable
private TransactionConfidence getTransactionConfidence(Transaction tx, Address address) { private TransactionConfidence getTransactionConfidence(Transaction tx, Address address) {
List<TransactionConfidence> transactionConfidenceList = getOutputsWithConnectedOutputs(tx).stream() List<TransactionConfidence> transactionConfidenceList = getOutputsWithConnectedOutputs(tx).stream()
@ -761,7 +773,7 @@ public abstract class WalletService {
} }
@Nullable @Nullable
public Transaction getTransaction(String txId) { public Transaction getTransaction(@Nullable String txId) {
if (txId == null) { if (txId == null) {
return null; return null;
} }

View file

@ -29,7 +29,6 @@ import bisq.core.trade.bsq_swap.BsqSwapTradeManager;
import bisq.core.trade.model.MakerTrade; import bisq.core.trade.model.MakerTrade;
import bisq.core.trade.model.Tradable; import bisq.core.trade.model.Tradable;
import bisq.core.trade.model.TradableList; import bisq.core.trade.model.TradableList;
import bisq.core.trade.model.TradeModel;
import bisq.core.trade.model.bisq_v1.Trade; import bisq.core.trade.model.bisq_v1.Trade;
import bisq.core.trade.statistics.TradeStatisticsManager; import bisq.core.trade.statistics.TradeStatisticsManager;
import bisq.core.user.Preferences; import bisq.core.user.Preferences;
@ -47,6 +46,8 @@ import org.bitcoinj.utils.Fiat;
import com.google.inject.Inject; import com.google.inject.Inject;
import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMultiset;
import com.google.common.collect.Multiset;
import javafx.collections.ObservableList; import javafx.collections.ObservableList;
@ -63,6 +64,8 @@ import java.util.stream.Stream;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import javax.annotation.Nullable;
import static bisq.core.offer.OpenOffer.State.CANCELED; import static bisq.core.offer.OpenOffer.State.CANCELED;
import static bisq.core.trade.ClosedTradableUtil.castToTrade; import static bisq.core.trade.ClosedTradableUtil.castToTrade;
import static bisq.core.trade.ClosedTradableUtil.castToTradeModel; import static bisq.core.trade.ClosedTradableUtil.castToTradeModel;
@ -88,6 +91,8 @@ public class ClosedTradableManager implements PersistedDataHost {
private final DumpDelayedPayoutTx dumpDelayedPayoutTx; private final DumpDelayedPayoutTx dumpDelayedPayoutTx;
private final TradableList<Tradable> closedTradables = new TradableList<>(); private final TradableList<Tradable> closedTradables = new TradableList<>();
@Nullable
private Multiset<NodeAddress> closedTradeNodeAddressCache;
@Inject @Inject
public ClosedTradableManager(KeyRing keyRing, public ClosedTradableManager(KeyRing keyRing,
@ -109,6 +114,8 @@ public class ClosedTradableManager implements PersistedDataHost {
this.dumpDelayedPayoutTx = dumpDelayedPayoutTx; this.dumpDelayedPayoutTx = dumpDelayedPayoutTx;
this.persistenceManager = persistenceManager; this.persistenceManager = persistenceManager;
closedTradables.addListener(c -> closedTradeNodeAddressCache = null);
this.persistenceManager.initialize(closedTradables, "ClosedTrades", PersistenceManager.Source.PRIVATE); this.persistenceManager.initialize(closedTradables, "ClosedTrades", PersistenceManager.Source.PRIVATE);
} }
@ -206,8 +213,7 @@ public class ClosedTradableManager implements PersistedDataHost {
Instant safeDate = getSafeDateForSensitiveDataClearing(); Instant safeDate = getSafeDateForSensitiveDataClearing();
return closedTradables.stream() return closedTradables.stream()
.filter(e -> e.getId().equals(tradeId)) .filter(e -> e.getId().equals(tradeId))
.filter(e -> e.getDate().toInstant().isBefore(safeDate)) .anyMatch(e -> e.getDate().toInstant().isBefore(safeDate));
.count() > 0;
} }
public Instant getSafeDateForSensitiveDataClearing() { public Instant getSafeDateForSensitiveDataClearing() {
@ -220,8 +226,16 @@ public class ClosedTradableManager implements PersistedDataHost {
.filter(Trade::isFundsLockedIn); .filter(Trade::isFundsLockedIn);
} }
public Stream<TradeModel> getTradeModelStream() { private Multiset<NodeAddress> getClosedTradeNodeAddresses() {
return Stream.concat(bsqSwapTradeManager.getConfirmedBsqSwapTrades(), getClosedTrades().stream()); var addresses = closedTradeNodeAddressCache;
if (addresses == null) {
closedTradeNodeAddressCache = addresses = closedTradables.stream()
.filter(t -> t instanceof Trade)
.map(t -> ((Trade) t).getTradingPeerNodeAddress())
.filter(Objects::nonNull)
.collect(ImmutableMultiset.toImmutableMultiset());
}
return addresses;
} }
public int getNumPastTrades(Tradable tradable) { public int getNumPastTrades(Tradable tradable) {
@ -229,11 +243,8 @@ public class ClosedTradableManager implements PersistedDataHost {
return 0; return 0;
} }
NodeAddress addressInTrade = castToTradeModel(tradable).getTradingPeerNodeAddress(); NodeAddress addressInTrade = castToTradeModel(tradable).getTradingPeerNodeAddress();
return (int) getTradeModelStream() return bsqSwapTradeManager.getConfirmedBsqSwapNodeAddresses().count(addressInTrade) +
.map(TradeModel::getTradingPeerNodeAddress) getClosedTradeNodeAddresses().count(addressInTrade);
.filter(Objects::nonNull)
.filter(address -> address.equals(addressInTrade))
.count();
} }
public boolean isCurrencyForTradeFeeBtc(Tradable tradable) { public boolean isCurrencyForTradeFeeBtc(Tradable tradable) {

View file

@ -22,8 +22,11 @@ import bisq.core.offer.Offer;
import bisq.core.provider.price.PriceFeedService; import bisq.core.provider.price.PriceFeedService;
import bisq.core.trade.model.Tradable; import bisq.core.trade.model.Tradable;
import bisq.core.trade.model.TradableList; import bisq.core.trade.model.TradableList;
import bisq.core.trade.model.TradeModel;
import bisq.core.trade.model.bsq_swap.BsqSwapTrade; import bisq.core.trade.model.bsq_swap.BsqSwapTrade;
import bisq.network.p2p.NodeAddress;
import bisq.common.crypto.KeyRing; import bisq.common.crypto.KeyRing;
import bisq.common.persistence.PersistenceManager; import bisq.common.persistence.PersistenceManager;
import bisq.common.proto.persistable.PersistedDataHost; import bisq.common.proto.persistable.PersistedDataHost;
@ -34,6 +37,8 @@ import com.google.inject.Inject;
import com.google.inject.Singleton; import com.google.inject.Singleton;
import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMultiset;
import com.google.common.collect.Multiset;
import javafx.beans.property.ObjectProperty; import javafx.beans.property.ObjectProperty;
import javafx.beans.property.SimpleObjectProperty; import javafx.beans.property.SimpleObjectProperty;
@ -42,20 +47,25 @@ import javafx.collections.ObservableList;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
import java.util.Objects;
import java.util.Optional; import java.util.Optional;
import java.util.stream.Stream; import java.util.stream.Stream;
import lombok.Getter; import lombok.Getter;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import javax.annotation.Nullable;
@Slf4j @Slf4j
@Singleton @Singleton
public class BsqSwapTradeManager implements PersistedDataHost { public class BsqSwapTradeManager implements PersistedDataHost {
private final KeyRing keyRing;
private final PriceFeedService priceFeedService;
private final BsqWalletService bsqWalletService; private final BsqWalletService bsqWalletService;
private final PersistenceManager<TradableList<BsqSwapTrade>> persistenceManager; private final PersistenceManager<TradableList<BsqSwapTrade>> persistenceManager;
private final TradableList<BsqSwapTrade> bsqSwapTrades = new TradableList<>(); private final TradableList<BsqSwapTrade> bsqSwapTrades = new TradableList<>();
private final KeyRing keyRing; @Nullable
private final PriceFeedService priceFeedService; private Multiset<NodeAddress> confirmedBsqSwapNodeAddressCache;
// Used for listening for notifications in the UI // Used for listening for notifications in the UI
@Getter @Getter
@ -71,6 +81,9 @@ public class BsqSwapTradeManager implements PersistedDataHost {
this.bsqWalletService = bsqWalletService; this.bsqWalletService = bsqWalletService;
this.persistenceManager = persistenceManager; this.persistenceManager = persistenceManager;
bsqWalletService.addWalletTransactionsChangeListener(() -> confirmedBsqSwapNodeAddressCache = null);
bsqSwapTrades.addListener(c -> confirmedBsqSwapNodeAddressCache = null);
this.persistenceManager.initialize(bsqSwapTrades, "BsqSwapTrades", PersistenceManager.Source.PRIVATE); this.persistenceManager.initialize(bsqSwapTrades, "BsqSwapTrades", PersistenceManager.Source.PRIVATE);
} }
@ -133,6 +146,18 @@ public class BsqSwapTradeManager implements PersistedDataHost {
return getObservableList().stream().filter(this::isConfirmed); return getObservableList().stream().filter(this::isConfirmed);
} }
public Multiset<NodeAddress> getConfirmedBsqSwapNodeAddresses() {
var addresses = confirmedBsqSwapNodeAddressCache;
if (addresses == null) {
confirmedBsqSwapNodeAddressCache = addresses = bsqSwapTrades.stream()
.filter(this::isConfirmed)
.map(TradeModel::getTradingPeerNodeAddress)
.filter(Objects::nonNull)
.collect(ImmutableMultiset.toImmutableMultiset());
}
return addresses;
}
private boolean isUnconfirmed(BsqSwapTrade bsqSwapTrade) { private boolean isUnconfirmed(BsqSwapTrade bsqSwapTrade) {
return matchesConfidence(bsqSwapTrade, TransactionConfidence.ConfidenceType.PENDING); return matchesConfidence(bsqSwapTrade, TransactionConfidence.ConfidenceType.PENDING);
} }