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 com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableMultiset;
import com.google.common.collect.ImmutableSetMultimap;
import com.google.common.collect.Multiset;
@ -87,6 +88,7 @@ import org.bouncycastle.crypto.params.KeyParameter;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
import java.util.concurrent.CopyOnWriteArraySet;
@ -122,6 +124,7 @@ public abstract class WalletService {
private final WalletChangeEventListener cacheInvalidationListener;
private final AtomicReference<Multiset<Address>> txOutputAddressCache = new AtomicReference<>();
private final AtomicReference<SetMultimap<Address, Transaction>> addressToMatchingTxSetCache = new AtomicReference<>();
private final AtomicReference<Map<Sha256Hash, Transaction>> txByIdCache = new AtomicReference<>();
@Getter
protected Wallet wallet;
@Getter
@ -147,6 +150,7 @@ public abstract class WalletService {
cacheInvalidationListener = wallet -> {
txOutputAddressCache.set(null);
addressToMatchingTxSetCache.set(null);
txByIdCache.set(null);
};
}
@ -440,15 +444,15 @@ public abstract class WalletService {
public TransactionConfidence getConfidenceForAddress(Address address) {
List<TransactionConfidence> transactionConfidenceList = new ArrayList<>();
if (wallet != null) {
Set<Transaction> transactions = getAddressToMatchingTxSetMultiset().get(address);
Set<Transaction> transactions = getAddressToMatchingTxSetMultimap().get(address);
transactionConfidenceList.addAll(transactions.stream().map(tx ->
getTransactionConfidence(tx, address)).collect(Collectors.toList()));
}
return getMostRecentConfidence(transactionConfidenceList);
}
private SetMultimap<Address, Transaction> getAddressToMatchingTxSetMultiset() {
return addressToMatchingTxSetCache.updateAndGet(set -> set != null ? set : computeAddressToMatchingTxSetMultimap());
private SetMultimap<Address, Transaction> getAddressToMatchingTxSetMultimap() {
return addressToMatchingTxSetCache.updateAndGet(map -> map != null ? map : computeAddressToMatchingTxSetMultimap());
}
private SetMultimap<Address, Transaction> computeAddressToMatchingTxSetMultimap() {
@ -463,17 +467,25 @@ public abstract class WalletService {
}
@Nullable
public TransactionConfidence getConfidenceForTxId(String txId) {
if (wallet != null) {
Set<Transaction> transactions = wallet.getTransactions(false);
for (Transaction tx : transactions) {
if (tx.getTxId().toString().equals(txId))
return tx.getConfidence();
public TransactionConfidence getConfidenceForTxId(@Nullable String txId) {
if (wallet != null && txId != null) {
Transaction tx = getTxByIdMap().get(Sha256Hash.wrap(txId));
if (tx != null) {
return tx.getConfidence();
}
}
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
private TransactionConfidence getTransactionConfidence(Transaction tx, Address address) {
List<TransactionConfidence> transactionConfidenceList = getOutputsWithConnectedOutputs(tx).stream()
@ -761,7 +773,7 @@ public abstract class WalletService {
}
@Nullable
public Transaction getTransaction(String txId) {
public Transaction getTransaction(@Nullable String txId) {
if (txId == 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.Tradable;
import bisq.core.trade.model.TradableList;
import bisq.core.trade.model.TradeModel;
import bisq.core.trade.model.bisq_v1.Trade;
import bisq.core.trade.statistics.TradeStatisticsManager;
import bisq.core.user.Preferences;
@ -47,6 +46,8 @@ import org.bitcoinj.utils.Fiat;
import com.google.inject.Inject;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMultiset;
import com.google.common.collect.Multiset;
import javafx.collections.ObservableList;
@ -63,6 +64,8 @@ import java.util.stream.Stream;
import lombok.extern.slf4j.Slf4j;
import javax.annotation.Nullable;
import static bisq.core.offer.OpenOffer.State.CANCELED;
import static bisq.core.trade.ClosedTradableUtil.castToTrade;
import static bisq.core.trade.ClosedTradableUtil.castToTradeModel;
@ -88,6 +91,8 @@ public class ClosedTradableManager implements PersistedDataHost {
private final DumpDelayedPayoutTx dumpDelayedPayoutTx;
private final TradableList<Tradable> closedTradables = new TradableList<>();
@Nullable
private Multiset<NodeAddress> closedTradeNodeAddressCache;
@Inject
public ClosedTradableManager(KeyRing keyRing,
@ -109,6 +114,8 @@ public class ClosedTradableManager implements PersistedDataHost {
this.dumpDelayedPayoutTx = dumpDelayedPayoutTx;
this.persistenceManager = persistenceManager;
closedTradables.addListener(c -> closedTradeNodeAddressCache = null);
this.persistenceManager.initialize(closedTradables, "ClosedTrades", PersistenceManager.Source.PRIVATE);
}
@ -206,8 +213,7 @@ public class ClosedTradableManager implements PersistedDataHost {
Instant safeDate = getSafeDateForSensitiveDataClearing();
return closedTradables.stream()
.filter(e -> e.getId().equals(tradeId))
.filter(e -> e.getDate().toInstant().isBefore(safeDate))
.count() > 0;
.anyMatch(e -> e.getDate().toInstant().isBefore(safeDate));
}
public Instant getSafeDateForSensitiveDataClearing() {
@ -220,8 +226,16 @@ public class ClosedTradableManager implements PersistedDataHost {
.filter(Trade::isFundsLockedIn);
}
public Stream<TradeModel> getTradeModelStream() {
return Stream.concat(bsqSwapTradeManager.getConfirmedBsqSwapTrades(), getClosedTrades().stream());
private Multiset<NodeAddress> getClosedTradeNodeAddresses() {
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) {
@ -229,11 +243,8 @@ public class ClosedTradableManager implements PersistedDataHost {
return 0;
}
NodeAddress addressInTrade = castToTradeModel(tradable).getTradingPeerNodeAddress();
return (int) getTradeModelStream()
.map(TradeModel::getTradingPeerNodeAddress)
.filter(Objects::nonNull)
.filter(address -> address.equals(addressInTrade))
.count();
return bsqSwapTradeManager.getConfirmedBsqSwapNodeAddresses().count(addressInTrade) +
getClosedTradeNodeAddresses().count(addressInTrade);
}
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.trade.model.Tradable;
import bisq.core.trade.model.TradableList;
import bisq.core.trade.model.TradeModel;
import bisq.core.trade.model.bsq_swap.BsqSwapTrade;
import bisq.network.p2p.NodeAddress;
import bisq.common.crypto.KeyRing;
import bisq.common.persistence.PersistenceManager;
import bisq.common.proto.persistable.PersistedDataHost;
@ -34,6 +37,8 @@ import com.google.inject.Inject;
import com.google.inject.Singleton;
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.SimpleObjectProperty;
@ -42,20 +47,25 @@ import javafx.collections.ObservableList;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
import java.util.Optional;
import java.util.stream.Stream;
import lombok.Getter;
import lombok.extern.slf4j.Slf4j;
import javax.annotation.Nullable;
@Slf4j
@Singleton
public class BsqSwapTradeManager implements PersistedDataHost {
private final KeyRing keyRing;
private final PriceFeedService priceFeedService;
private final BsqWalletService bsqWalletService;
private final PersistenceManager<TradableList<BsqSwapTrade>> persistenceManager;
private final TradableList<BsqSwapTrade> bsqSwapTrades = new TradableList<>();
private final KeyRing keyRing;
private final PriceFeedService priceFeedService;
@Nullable
private Multiset<NodeAddress> confirmedBsqSwapNodeAddressCache;
// Used for listening for notifications in the UI
@Getter
@ -71,6 +81,9 @@ public class BsqSwapTradeManager implements PersistedDataHost {
this.bsqWalletService = bsqWalletService;
this.persistenceManager = persistenceManager;
bsqWalletService.addWalletTransactionsChangeListener(() -> confirmedBsqSwapNodeAddressCache = null);
bsqSwapTrades.addListener(c -> confirmedBsqSwapNodeAddressCache = null);
this.persistenceManager.initialize(bsqSwapTrades, "BsqSwapTrades", PersistenceManager.Source.PRIVATE);
}
@ -133,6 +146,18 @@ public class BsqSwapTradeManager implements PersistedDataHost {
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) {
return matchesConfidence(bsqSwapTrade, TransactionConfidence.ConfidenceType.PENDING);
}