Merge pull request #6579 from stejbac/further-speed-up-transactions-view-load

Further speed up transactions view load
This commit is contained in:
Alejandro García 2023-02-12 14:05:36 +00:00 committed by GitHub
commit 581fbd1d2d
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
8 changed files with 222 additions and 100 deletions

View file

@ -7,6 +7,7 @@ import bisq.core.monetary.Altcoin;
import bisq.core.monetary.Price; import bisq.core.monetary.Price;
import bisq.common.util.MathUtils; import bisq.common.util.MathUtils;
import bisq.common.util.Tuple3;
import org.bitcoinj.core.Coin; import org.bitcoinj.core.Coin;
import org.bitcoinj.core.Monetary; import org.bitcoinj.core.Monetary;
@ -33,12 +34,15 @@ import org.jetbrains.annotations.NotNull;
public class FormattingUtils { public class FormattingUtils {
public static final String BTC_FORMATTER_KEY = "BTC"; public static final String BTC_FORMATTER_KEY = "BTC";
public final static String RANGE_SEPARATOR = " - "; public static final String RANGE_SEPARATOR = " - ";
private static final MonetaryFormat fiatPriceFormat = new MonetaryFormat().shift(0).minDecimals(4).repeatOptionalDecimals(0, 0); private static final MonetaryFormat fiatPriceFormat = new MonetaryFormat().shift(0).minDecimals(4).repeatOptionalDecimals(0, 0);
private static final MonetaryFormat altcoinFormat = new MonetaryFormat().shift(0).minDecimals(8).repeatOptionalDecimals(0, 0); private static final MonetaryFormat altcoinFormat = new MonetaryFormat().shift(0).minDecimals(8).repeatOptionalDecimals(0, 0);
private static final DecimalFormat decimalFormat = new DecimalFormat("#.#"); private static final DecimalFormat decimalFormat = new DecimalFormat("#.#");
private static final ThreadLocal<Tuple3<Locale, DateFormat, DateFormat>> cachedUtcDateTimeFormatters = new ThreadLocal<>();
private static final ThreadLocal<Tuple3<Locale, DateFormat, DateFormat>> cachedLocalDateTimeFormatters = new ThreadLocal<>();
public static String formatCoinWithCode(long value, MonetaryFormat coinFormat) { public static String formatCoinWithCode(long value, MonetaryFormat coinFormat) {
return formatCoinWithCode(Coin.valueOf(value), coinFormat); return formatCoinWithCode(Coin.valueOf(value), coinFormat);
} }
@ -183,12 +187,25 @@ public class FormattingUtils {
public static String formatDateTime(Date date, boolean useLocaleAndLocalTimezone) { public static String formatDateTime(Date date, boolean useLocaleAndLocalTimezone) {
Locale locale = useLocaleAndLocalTimezone ? GlobalSettings.getLocale() : Locale.US; Locale locale = useLocaleAndLocalTimezone ? GlobalSettings.getLocale() : Locale.US;
DateFormat dateInstance = DateFormat.getDateInstance(DateFormat.DEFAULT, locale);
DateFormat timeInstance = DateFormat.getTimeInstance(DateFormat.DEFAULT, locale); var formatterTuple = (useLocaleAndLocalTimezone ?
if (!useLocaleAndLocalTimezone) { cachedLocalDateTimeFormatters : cachedUtcDateTimeFormatters).get();
dateInstance.setTimeZone(TimeZone.getTimeZone("UTC")); if (formatterTuple == null || !formatterTuple.first.equals(locale)) {
timeInstance.setTimeZone(TimeZone.getTimeZone("UTC")); formatterTuple = new Tuple3<>(locale,
DateFormat.getDateInstance(DateFormat.DEFAULT, locale),
DateFormat.getTimeInstance(DateFormat.DEFAULT, locale));
if (useLocaleAndLocalTimezone) {
cachedLocalDateTimeFormatters.set(formatterTuple);
} else {
formatterTuple.second.setTimeZone(TimeZone.getTimeZone("UTC"));
formatterTuple.third.setTimeZone(TimeZone.getTimeZone("UTC"));
cachedUtcDateTimeFormatters.set(formatterTuple);
}
} }
DateFormat dateInstance = formatterTuple.second;
DateFormat timeInstance = formatterTuple.third;
return formatDateTime(date, dateInstance, timeInstance); return formatDateTime(date, dateInstance, timeInstance);
} }
@ -288,7 +305,8 @@ public class FormattingUtils {
} }
@NotNull @NotNull
public static String fillUpPlacesWithEmptyStrings(String formattedNumber, @SuppressWarnings("unused") int maxNumberOfDigits) { public static String fillUpPlacesWithEmptyStrings(String formattedNumber,
@SuppressWarnings("unused") int maxNumberOfDigits) {
//FIXME: temporary deactivate adding spaces in front of numbers as we don't use a monospace font right now. //FIXME: temporary deactivate adding spaces in front of numbers as we don't use a monospace font right now.
/*int numberOfPlacesToFill = maxNumberOfDigits - formattedNumber.length(); /*int numberOfPlacesToFill = maxNumberOfDigits - formattedNumber.length();
for (int i = 0; i < numberOfPlacesToFill; i++) { for (int i = 0; i < numberOfPlacesToFill; i++) {

View file

@ -0,0 +1,52 @@
/*
* This file is part of Bisq.
*
* Bisq is free software: you can redistribute it and/or modify it
* under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or (at
* your option) any later version.
*
* Bisq is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public
* License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with Bisq. If not, see <http://www.gnu.org/licenses/>.
*/
package bisq.desktop.main.funds.transactions;
import org.bitcoinj.core.Transaction;
import java.util.Arrays;
import java.util.BitSet;
import java.util.Collection;
import java.util.List;
import java.util.stream.IntStream;
import java.util.stream.Stream;
import static bisq.desktop.main.funds.transactions.TransactionAwareTradable.TX_FILTER_SIZE;
public class RelatedTransactionFilterSlices {
private final List<TransactionAwareTradable> tradables;
private final BitSet[] filterSlices;
public RelatedTransactionFilterSlices(Collection<? extends TransactionAwareTradable> tradables) {
this.tradables = List.copyOf(tradables);
filterSlices = new BitSet[TX_FILTER_SIZE];
Arrays.setAll(filterSlices, i -> new BitSet(this.tradables.size()));
IntStream.range(0, this.tradables.size())
.forEach(j -> this.tradables.get(j).getRelatedTransactionFilter()
.forEach(i -> filterSlices[i].set(j)));
}
public Stream<TransactionAwareTradable> getAllRelatedTradables(Transaction tx) {
int i = TransactionAwareTradable.bucketIndex(tx);
return filterSlices[i].stream()
.mapToObj(tradables::get)
.filter(t -> t.isRelatedToTransaction(tx));
}
}

View file

@ -23,6 +23,8 @@ import bisq.core.trade.model.Tradable;
import org.bitcoinj.core.Transaction; import org.bitcoinj.core.Transaction;
import java.util.stream.IntStream;
class TransactionAwareOpenOffer implements TransactionAwareTradable { class TransactionAwareOpenOffer implements TransactionAwareTradable {
private final OpenOffer delegate; private final OpenOffer delegate;
@ -34,12 +36,18 @@ class TransactionAwareOpenOffer implements TransactionAwareTradable {
Offer offer = delegate.getOffer(); Offer offer = delegate.getOffer();
String paymentTxId = offer.getOfferFeePaymentTxId(); String paymentTxId = offer.getOfferFeePaymentTxId();
String txId = transaction.getTxId().toString(); return paymentTxId != null && paymentTxId.equals(transaction.getTxId().toString());
return paymentTxId != null && paymentTxId.equals(txId);
} }
public Tradable asTradable() { public Tradable asTradable() {
return delegate; return delegate;
} }
@Override
public IntStream getRelatedTransactionFilter() {
Offer offer = delegate.getOffer();
String paymentTxId = offer.getOfferFeePaymentTxId();
return IntStream.of(TransactionAwareTradable.bucketIndex(paymentTxId))
.filter(i -> i >= 0);
}
} }

View file

@ -19,10 +19,35 @@ package bisq.desktop.main.funds.transactions;
import bisq.core.trade.model.Tradable; import bisq.core.trade.model.Tradable;
import org.bitcoinj.core.Sha256Hash;
import org.bitcoinj.core.Transaction; import org.bitcoinj.core.Transaction;
import java.util.stream.IntStream;
import javax.annotation.Nullable;
interface TransactionAwareTradable { interface TransactionAwareTradable {
int TX_FILTER_SIZE = 64;
int DELAYED_PAYOUT_TX_BUCKET_INDEX = TX_FILTER_SIZE - 1;
boolean isRelatedToTransaction(Transaction transaction); boolean isRelatedToTransaction(Transaction transaction);
Tradable asTradable(); Tradable asTradable();
/** Returns a list of bucket indices of all transactions which might be related to this Tradable. */
IntStream getRelatedTransactionFilter();
static int bucketIndex(Transaction tx) {
return tx.getLockTime() == 0 ? bucketIndex(tx.getTxId()) : DELAYED_PAYOUT_TX_BUCKET_INDEX;
}
static int bucketIndex(Sha256Hash hash) {
int i = hash.getBytes()[31] & 255;
return i % TX_FILTER_SIZE != DELAYED_PAYOUT_TX_BUCKET_INDEX ?
i % TX_FILTER_SIZE : i / TX_FILTER_SIZE;
}
static int bucketIndex(@Nullable String txId) {
return txId != null ? bucketIndex(Sha256Hash.wrap(txId)) : -1;
}
} }

View file

@ -29,18 +29,23 @@ import bisq.core.trade.model.bisq_v1.Trade;
import bisq.core.trade.model.bsq_swap.BsqSwapTrade; import bisq.core.trade.model.bsq_swap.BsqSwapTrade;
import bisq.common.crypto.PubKeyRing; import bisq.common.crypto.PubKeyRing;
import bisq.common.util.Tuple2;
import org.bitcoinj.core.Address; import org.bitcoinj.core.Address;
import org.bitcoinj.core.Sha256Hash; import org.bitcoinj.core.Sha256Hash;
import org.bitcoinj.core.Transaction; import org.bitcoinj.core.Transaction;
import org.bitcoinj.core.TransactionOutput; import org.bitcoinj.core.TransactionOutput;
import com.google.common.collect.ImmutableSet;
import javafx.collections.ObservableList; import javafx.collections.ObservableList;
import java.util.Optional; import java.util.Set;
import java.util.stream.IntStream;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import static bisq.desktop.main.funds.transactions.TransactionAwareTradable.bucketIndex;
import static com.google.common.base.Preconditions.checkNotNull; import static com.google.common.base.Preconditions.checkNotNull;
@Slf4j @Slf4j
@ -51,6 +56,11 @@ class TransactionAwareTrade implements TransactionAwareTradable {
private final BtcWalletService btcWalletService; private final BtcWalletService btcWalletService;
private final PubKeyRing pubKeyRing; private final PubKeyRing pubKeyRing;
// As Sha256Hash.toString() is expensive, cache the last result, which will usually be next one needed.
private static Tuple2<Sha256Hash, String> lastTxIdTuple;
// Similarly, cache the last computed set of tx receiver addresses, to speed up 'isRefundPayoutTx'.
private static Tuple2<String, Set<String>> lastReceiverAddressStringsTuple;
TransactionAwareTrade(TradeModel tradeModel, TransactionAwareTrade(TradeModel tradeModel,
ArbitrationManager arbitrationManager, ArbitrationManager arbitrationManager,
RefundManager refundManager, RefundManager refundManager,
@ -66,15 +76,19 @@ class TransactionAwareTrade implements TransactionAwareTradable {
@Override @Override
public boolean isRelatedToTransaction(Transaction transaction) { public boolean isRelatedToTransaction(Transaction transaction) {
Sha256Hash hash = transaction.getTxId(); Sha256Hash hash = transaction.getTxId();
String txId = hash.toString(); var txIdTuple = lastTxIdTuple;
if (txIdTuple == null || !txIdTuple.first.equals(hash)) {
lastTxIdTuple = txIdTuple = new Tuple2<>(hash, hash.toString());
}
String txId = txIdTuple.second;
boolean tradeRelated = false; boolean tradeRelated = false;
if (tradeModel instanceof Trade) { if (tradeModel instanceof Trade) {
Trade trade = (Trade) tradeModel; Trade trade = (Trade) tradeModel;
boolean isTakerOfferFeeTx = txId.equals(trade.getTakerFeeTxId()); boolean isTakerOfferFeeTx = txId.equals(trade.getTakerFeeTxId());
boolean isOfferFeeTx = isOfferFeeTx(txId); boolean isOfferFeeTx = isOfferFeeTx(txId);
boolean isDepositTx = isDepositTx(hash); boolean isDepositTx = isDepositTx(txId);
boolean isPayoutTx = isPayoutTx(hash); boolean isPayoutTx = isPayoutTx(txId);
boolean isDisputedPayoutTx = isDisputedPayoutTx(txId); boolean isDisputedPayoutTx = isDisputedPayoutTx(txId);
boolean isDelayedPayoutTx = transaction.getLockTime() != 0 && isDelayedPayoutTx(txId); boolean isDelayedPayoutTx = transaction.getLockTime() != 0 && isDelayedPayoutTx(txId);
boolean isRefundPayoutTx = isRefundPayoutTx(trade, txId); boolean isRefundPayoutTx = isRefundPayoutTx(trade, txId);
@ -91,36 +105,28 @@ class TransactionAwareTrade implements TransactionAwareTradable {
return tradeRelated || isBsqSwapTrade; return tradeRelated || isBsqSwapTrade;
} }
private boolean isPayoutTx(Sha256Hash txId) { private boolean isPayoutTx(String txId) {
if (isBsqSwapTrade()) if (isBsqSwapTrade())
return false; return false;
Trade trade = (Trade) tradeModel; Trade trade = (Trade) tradeModel;
return Optional.ofNullable(trade.getPayoutTx()) return txId.equals(trade.getPayoutTxId());
.map(Transaction::getTxId)
.map(hash -> hash.equals(txId))
.orElse(false);
} }
private boolean isDepositTx(Sha256Hash txId) { private boolean isDepositTx(String txId) {
if (isBsqSwapTrade()) if (isBsqSwapTrade())
return false; return false;
Trade trade = (Trade) tradeModel; Trade trade = (Trade) tradeModel;
return Optional.ofNullable(trade.getDepositTx()) return txId.equals(trade.getDepositTxId());
.map(Transaction::getTxId)
.map(hash -> hash.equals(txId))
.orElse(false);
} }
private boolean isOfferFeeTx(String txId) { private boolean isOfferFeeTx(String txId) {
if (isBsqSwapTrade()) if (isBsqSwapTrade())
return false; return false;
return Optional.ofNullable(tradeModel.getOffer()) Offer offer = tradeModel.getOffer();
.map(Offer::getOfferFeePaymentTxId) return offer != null && txId.equals(offer.getOfferFeePaymentTxId());
.map(paymentTxId -> paymentTxId.equals(txId))
.orElse(false);
} }
private boolean isDisputedPayoutTx(String txId) { private boolean isDisputedPayoutTx(String txId) {
@ -130,7 +136,7 @@ class TransactionAwareTrade implements TransactionAwareTradable {
String delegateId = tradeModel.getId(); String delegateId = tradeModel.getId();
ObservableList<Dispute> disputes = arbitrationManager.getDisputesAsObservableList(); ObservableList<Dispute> disputes = arbitrationManager.getDisputesAsObservableList();
boolean isAnyDisputeRelatedToThis = arbitrationManager.getDisputedTradeIds().contains(tradeModel.getId()); boolean isAnyDisputeRelatedToThis = arbitrationManager.getDisputedTradeIds().contains(delegateId);
return isAnyDisputeRelatedToThis && disputes.stream() return isAnyDisputeRelatedToThis && disputes.stream()
.anyMatch(dispute -> { .anyMatch(dispute -> {
@ -155,7 +161,7 @@ class TransactionAwareTrade implements TransactionAwareTradable {
if (transaction.getLockTime() == 0) if (transaction.getLockTime() == 0)
return false; return false;
if (transaction.getInputs() == null) if (transaction.getInputs() == null || transaction.getInputs().size() != 1)
return false; return false;
return transaction.getInputs().stream() return transaction.getInputs().stream()
@ -168,7 +174,7 @@ class TransactionAwareTrade implements TransactionAwareTradable {
if (parentTransaction == null) { if (parentTransaction == null) {
return false; return false;
} }
return isDepositTx(parentTransaction.getTxId()); return isDepositTx(parentTransaction.getTxId().toString());
}); });
} }
@ -177,33 +183,45 @@ class TransactionAwareTrade implements TransactionAwareTradable {
return false; return false;
String tradeId = tradeModel.getId(); String tradeId = tradeModel.getId();
ObservableList<Dispute> disputes = refundManager.getDisputesAsObservableList();
boolean isAnyDisputeRelatedToThis = refundManager.getDisputedTradeIds().contains(tradeId); boolean isAnyDisputeRelatedToThis = refundManager.getDisputedTradeIds().contains(tradeId);
if (isAnyDisputeRelatedToThis) { if (isAnyDisputeRelatedToThis) {
Transaction tx = btcWalletService.getTransaction(txId); try {
if (tx != null) { Contract contract = checkNotNull(trade.getContract());
for (TransactionOutput txo : tx.getOutputs()) { String myPayoutAddressString = contract.isMyRoleBuyer(pubKeyRing) ?
if (btcWalletService.isTransactionOutputMine(txo)) { contract.getBuyerPayoutAddressString() :
try { contract.getSellerPayoutAddressString();
Address receiverAddress = txo.getScriptPubKey().getToAddress(btcWalletService.getParams());
Contract contract = checkNotNull(trade.getContract()); return getReceiverAddressStrings(txId).contains(myPayoutAddressString);
String myPayoutAddressString = contract.isMyRoleBuyer(pubKeyRing) ? } catch (RuntimeException ignore) {
contract.getBuyerPayoutAddressString() :
contract.getSellerPayoutAddressString();
if (receiverAddress != null && myPayoutAddressString.equals(receiverAddress.toString())) {
return true;
}
} catch (RuntimeException ignore) {
}
}
}
} }
} }
return false; return false;
} }
private Set<String> getReceiverAddressStrings(String txId) {
var tuple = lastReceiverAddressStringsTuple;
if (tuple == null || !tuple.first.equals(txId)) {
lastReceiverAddressStringsTuple = tuple = computeReceiverAddressStringsTuple(txId);
}
return tuple != null ? tuple.second : ImmutableSet.of();
}
private Tuple2<String, Set<String>> computeReceiverAddressStringsTuple(String txId) {
Transaction tx = btcWalletService.getTransaction(txId);
if (tx == null) {
// Clear cache if the tx isn't found, as theoretically it could be added to the wallet later.
return null;
}
Set<String> addressStrings = tx.getOutputs().stream()
.filter(btcWalletService::isTransactionOutputMine)
.map(txo -> txo.getScriptPubKey().getToAddress(btcWalletService.getParams()))
.map(Address::toString)
.collect(ImmutableSet.toImmutableSet());
return new Tuple2<>(txId, addressStrings);
}
private boolean isBsqSwapTrade() { private boolean isBsqSwapTrade() {
return tradeModel instanceof BsqSwapTrade; return tradeModel instanceof BsqSwapTrade;
} }
@ -219,4 +237,27 @@ class TransactionAwareTrade implements TransactionAwareTradable {
public Tradable asTradable() { public Tradable asTradable() {
return tradeModel; return tradeModel;
} }
@Override
public IntStream getRelatedTransactionFilter() {
if (tradeModel instanceof Trade && !arbitrationManager.getDisputedTradeIds().contains(tradeModel.getId()) &&
!refundManager.getDisputedTradeIds().contains(tradeModel.getId())) {
Trade trade = (Trade) tradeModel;
String takerFeeTxId = trade.getTakerFeeTxId();
String offerFeeTxId = trade.getOffer() != null ? trade.getOffer().getOfferFeePaymentTxId() : null;
String depositTxId = trade.getDepositTxId();
String payoutTxId = trade.getPayoutTxId();
return IntStream.of(DELAYED_PAYOUT_TX_BUCKET_INDEX, bucketIndex(takerFeeTxId), bucketIndex(offerFeeTxId),
bucketIndex(depositTxId), bucketIndex(payoutTxId))
.filter(i -> i >= 0);
} else if (tradeModel instanceof BsqSwapTrade) {
BsqSwapTrade trade = (BsqSwapTrade) tradeModel;
String swapTxId = trade.getTxId();
return IntStream.of(bucketIndex(swapTxId))
.filter(i -> i >= 0);
} else {
// We are involved in a dispute (rare) - don't do any initial tx filtering.
return IntStream.range(0, TX_FILTER_SIZE);
}
}
} }

View file

@ -17,12 +17,11 @@
package bisq.desktop.main.funds.transactions; package bisq.desktop.main.funds.transactions;
import bisq.desktop.util.filtering.FilterableListItem;
import bisq.desktop.components.indicator.TxConfidenceIndicator; import bisq.desktop.components.indicator.TxConfidenceIndicator;
import bisq.desktop.util.DisplayUtils; import bisq.desktop.util.DisplayUtils;
import bisq.desktop.util.GUIUtil; import bisq.desktop.util.GUIUtil;
import bisq.desktop.util.filtering.FilterableListItem;
import bisq.core.btc.listeners.TxConfidenceListener;
import bisq.core.btc.wallet.BsqWalletService; import bisq.core.btc.wallet.BsqWalletService;
import bisq.core.btc.wallet.BtcWalletService; import bisq.core.btc.wallet.BtcWalletService;
import bisq.core.btc.wallet.WalletService; import bisq.core.btc.wallet.WalletService;
@ -59,7 +58,6 @@ import javax.annotation.Nullable;
@Slf4j @Slf4j
class TransactionsListItem implements FilterableListItem { class TransactionsListItem implements FilterableListItem {
private final BtcWalletService btcWalletService;
private final CoinFormatter formatter; private final CoinFormatter formatter;
private String dateString; private String dateString;
private final Date date; private final Date date;
@ -69,7 +67,6 @@ class TransactionsListItem implements FilterableListItem {
private String details = ""; private String details = "";
private String addressString = ""; private String addressString = "";
private String direction = ""; private String direction = "";
private TxConfidenceListener txConfidenceListener;
private boolean received; private boolean received;
private Coin amountAsCoin = Coin.ZERO; private Coin amountAsCoin = Coin.ZERO;
private String memo = ""; private String memo = "";
@ -91,7 +88,6 @@ class TransactionsListItem implements FilterableListItem {
// used at exportCSV // used at exportCSV
TransactionsListItem() { TransactionsListItem() {
date = null; date = null;
btcWalletService = null;
txId = null; txId = null;
formatter = null; formatter = null;
isDustAttackTx = false; isDustAttackTx = false;
@ -105,7 +101,6 @@ class TransactionsListItem implements FilterableListItem {
DaoFacade daoFacade, DaoFacade daoFacade,
CoinFormatter formatter, CoinFormatter formatter,
long ignoreDustThreshold) { long ignoreDustThreshold) {
this.btcWalletService = btcWalletService;
this.formatter = formatter; this.formatter = formatter;
this.memo = transaction.getMemo(); this.memo = transaction.getMemo();
@ -197,7 +192,6 @@ class TransactionsListItem implements FilterableListItem {
if (optionalTradable.isPresent()) { if (optionalTradable.isPresent()) {
tradable = optionalTradable.get(); tradable = optionalTradable.get();
String tradeId = tradable.getShortId();
if (tradable instanceof OpenOffer) { if (tradable instanceof OpenOffer) {
details = Res.get("funds.tx.createOfferFee"); details = Res.get("funds.tx.createOfferFee");
} else if (tradable instanceof Trade) { } else if (tradable instanceof Trade) {
@ -304,19 +298,6 @@ class TransactionsListItem implements FilterableListItem {
GUIUtil.updateConfidence(confidence, tooltip, txConfidenceIndicator); GUIUtil.updateConfidence(confidence, tooltip, txConfidenceIndicator);
confirmations = confidence.getDepthInBlocks(); confirmations = confidence.getDepthInBlocks();
}}); }});
txConfidenceListener = new TxConfidenceListener(txId) {
@Override
public void onTransactionConfidenceChanged(TransactionConfidence confidence) {
GUIUtil.updateConfidence(confidence, lazy().tooltip, lazy().txConfidenceIndicator);
confirmations = confidence.getDepthInBlocks();
}
};
btcWalletService.addTxConfidenceListener(txConfidenceListener);
}
public void cleanup() {
btcWalletService.removeTxConfidenceListener(txConfidenceListener);
} }

View file

@ -95,6 +95,7 @@ import javafx.util.Callback;
import java.util.Comparator; import java.util.Comparator;
import java.util.List; import java.util.List;
import java.util.Objects;
import java.util.Set; import java.util.Set;
import java.util.stream.Collectors; import java.util.stream.Collectors;
@ -201,7 +202,7 @@ public class TransactionsView extends ActivatableView<VBox, Void> {
dateColumn.setComparator(Comparator.comparing(TransactionsListItem::getDate)); dateColumn.setComparator(Comparator.comparing(TransactionsListItem::getDate));
tradeIdColumn.setComparator(Comparator.comparing(o -> o.getTradable() != null ? o.getTradable().getId() : "")); tradeIdColumn.setComparator(Comparator.comparing(o -> o.getTradable() != null ? o.getTradable().getId() : ""));
detailsColumn.setComparator(Comparator.comparing(o -> o.getDetails())); detailsColumn.setComparator(Comparator.comparing(TransactionsListItem::getDetails));
addressColumn.setComparator(Comparator.comparing(item -> item.getDirection() + item.getAddressString())); addressColumn.setComparator(Comparator.comparing(item -> item.getDirection() + item.getAddressString()));
transactionColumn.setComparator(Comparator.comparing(TransactionsListItem::getTxId)); transactionColumn.setComparator(Comparator.comparing(TransactionsListItem::getTxId));
amountColumn.setComparator(Comparator.comparing(TransactionsListItem::getAmountAsCoin)); amountColumn.setComparator(Comparator.comparing(TransactionsListItem::getAmountAsCoin));
@ -211,9 +212,7 @@ public class TransactionsView extends ActivatableView<VBox, Void> {
dateColumn.setSortType(TableColumn.SortType.DESCENDING); dateColumn.setSortType(TableColumn.SortType.DESCENDING);
tableView.getSortOrder().add(dateColumn); tableView.getSortOrder().add(dateColumn);
walletChangeEventListener = wallet -> { walletChangeEventListener = wallet -> updateList();
updateList();
};
keyEventEventHandler = event -> { keyEventEventHandler = event -> {
// Not intended to be public to users as the feature is not well tested // Not intended to be public to users as the feature is not well tested
@ -280,7 +279,6 @@ public class TransactionsView extends ActivatableView<VBox, Void> {
protected void deactivate() { protected void deactivate() {
filterBox.deactivate(); filterBox.deactivate();
sortedList.comparatorProperty().unbind(); sortedList.comparatorProperty().unbind();
observableList.forEach(TransactionsListItem::cleanup);
btcWalletService.removeChangeEventListener(walletChangeEventListener); btcWalletService.removeChangeEventListener(walletChangeEventListener);
if (scene != null) if (scene != null)
@ -290,28 +288,30 @@ public class TransactionsView extends ActivatableView<VBox, Void> {
} }
private void updateList() { private void updateList() {
Set<Tradable> tradables = tradableRepository.getAll();
var filterSlices = new RelatedTransactionFilterSlices(tradables.stream()
.map(tradable -> {
if (tradable instanceof OpenOffer) {
return new TransactionAwareOpenOffer((OpenOffer) tradable);
} else if (tradable instanceof TradeModel) {
return new TransactionAwareTrade(
(TradeModel) tradable,
arbitrationManager,
refundManager,
btcWalletService,
pubKeyRing
);
} else {
return null;
}
})
.filter(Objects::nonNull)
.collect(Collectors.toUnmodifiableList()));
List<TransactionsListItem> transactionsListItems = btcWalletService.getTransactions(false) List<TransactionsListItem> transactionsListItems = btcWalletService.getTransactions(false)
.stream() .stream()
.map(transaction -> { .map(transaction -> {
Set<Tradable> tradables = tradableRepository.getAll(); TransactionAwareTradable maybeTradable = filterSlices.getAllRelatedTradables(transaction)
TransactionAwareTradable maybeTradable = tradables.stream()
.map(tradable -> {
if (tradable instanceof OpenOffer) {
return new TransactionAwareOpenOffer((OpenOffer) tradable);
} else if (tradable instanceof TradeModel) {
return new TransactionAwareTrade(
(TradeModel) tradable,
arbitrationManager,
refundManager,
btcWalletService,
pubKeyRing
);
} else {
return null;
}
})
.filter(tradable -> tradable != null && tradable.isRelatedToTransaction(transaction))
.findAny() .findAny()
.orElse(null); .orElse(null);
@ -327,7 +327,6 @@ public class TransactionsView extends ActivatableView<VBox, Void> {
}) })
.collect(Collectors.toList()); .collect(Collectors.toList());
// are sorted by getRecentTransactions // are sorted by getRecentTransactions
transactionsListItems.forEach(TransactionsListItem::cleanup);
observableList.setAll(transactionsListItems); observableList.setAll(transactionsListItems);
} }
@ -419,15 +418,13 @@ public class TransactionsView extends ActivatableView<VBox, Void> {
TransactionsListItem> column) { TransactionsListItem> column) {
return new TableCell<>() { return new TableCell<>() {
private HyperlinkWithIcon hyperlinkWithIcon;
@Override @Override
public void updateItem(final TransactionsListItem item, boolean empty) { public void updateItem(final TransactionsListItem item, boolean empty) {
super.updateItem(item, empty); super.updateItem(item, empty);
if (item != null && !empty) { if (item != null && !empty) {
if (item.isDustAttackTx()) { if (item.isDustAttackTx()) {
hyperlinkWithIcon = new HyperlinkWithIcon(item.getDetails(), AwesomeIcon.WARNING_SIGN); var hyperlinkWithIcon = new HyperlinkWithIcon(item.getDetails(), AwesomeIcon.WARNING_SIGN);
hyperlinkWithIcon.setOnAction(event -> new Popup().warning(Res.get("funds.tx.dustAttackTx.popup")).show()); hyperlinkWithIcon.setOnAction(event -> new Popup().warning(Res.get("funds.tx.dustAttackTx.popup")).show());
setGraphic(hyperlinkWithIcon); setGraphic(hyperlinkWithIcon);
} else { } else {

View file

@ -66,13 +66,13 @@ public class TransactionAwareTradeTest {
@Test @Test
public void testIsRelatedToTransactionWhenPayoutTx() { public void testIsRelatedToTransactionWhenPayoutTx() {
when(delegate.getPayoutTx().getTxId()).thenReturn(XID); when(delegate.getPayoutTxId()).thenReturn(XID.toString());
assertTrue(trade.isRelatedToTransaction(transaction)); assertTrue(trade.isRelatedToTransaction(transaction));
} }
@Test @Test
public void testIsRelatedToTransactionWhenDepositTx() { public void testIsRelatedToTransactionWhenDepositTx() {
when(delegate.getDepositTx().getTxId()).thenReturn(XID); when(delegate.getDepositTxId()).thenReturn(XID.toString());
assertTrue(trade.isRelatedToTransaction(transaction)); assertTrue(trade.isRelatedToTransaction(transaction));
} }