mirror of
https://github.com/bisq-network/bisq.git
synced 2025-03-03 18:56:59 +01:00
Merge pull request #6579 from stejbac/further-speed-up-transactions-view-load
Further speed up transactions view load
This commit is contained in:
commit
581fbd1d2d
8 changed files with 222 additions and 100 deletions
|
@ -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++) {
|
||||||
|
|
|
@ -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));
|
||||||
|
}
|
||||||
|
}
|
|
@ -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);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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);
|
|
||||||
if (tx != null) {
|
|
||||||
for (TransactionOutput txo : tx.getOutputs()) {
|
|
||||||
if (btcWalletService.isTransactionOutputMine(txo)) {
|
|
||||||
try {
|
try {
|
||||||
Address receiverAddress = txo.getScriptPubKey().getToAddress(btcWalletService.getParams());
|
|
||||||
Contract contract = checkNotNull(trade.getContract());
|
Contract contract = checkNotNull(trade.getContract());
|
||||||
String myPayoutAddressString = contract.isMyRoleBuyer(pubKeyRing) ?
|
String myPayoutAddressString = contract.isMyRoleBuyer(pubKeyRing) ?
|
||||||
contract.getBuyerPayoutAddressString() :
|
contract.getBuyerPayoutAddressString() :
|
||||||
contract.getSellerPayoutAddressString();
|
contract.getSellerPayoutAddressString();
|
||||||
if (receiverAddress != null && myPayoutAddressString.equals(receiverAddress.toString())) {
|
|
||||||
return true;
|
return getReceiverAddressStrings(txId).contains(myPayoutAddressString);
|
||||||
}
|
|
||||||
} catch (RuntimeException ignore) {
|
} 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);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -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,12 +288,8 @@ public class TransactionsView extends ActivatableView<VBox, Void> {
|
||||||
}
|
}
|
||||||
|
|
||||||
private void updateList() {
|
private void updateList() {
|
||||||
List<TransactionsListItem> transactionsListItems = btcWalletService.getTransactions(false)
|
|
||||||
.stream()
|
|
||||||
.map(transaction -> {
|
|
||||||
Set<Tradable> tradables = tradableRepository.getAll();
|
Set<Tradable> tradables = tradableRepository.getAll();
|
||||||
|
var filterSlices = new RelatedTransactionFilterSlices(tradables.stream()
|
||||||
TransactionAwareTradable maybeTradable = tradables.stream()
|
|
||||||
.map(tradable -> {
|
.map(tradable -> {
|
||||||
if (tradable instanceof OpenOffer) {
|
if (tradable instanceof OpenOffer) {
|
||||||
return new TransactionAwareOpenOffer((OpenOffer) tradable);
|
return new TransactionAwareOpenOffer((OpenOffer) tradable);
|
||||||
|
@ -311,7 +305,13 @@ public class TransactionsView extends ActivatableView<VBox, Void> {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.filter(tradable -> tradable != null && tradable.isRelatedToTransaction(transaction))
|
.filter(Objects::nonNull)
|
||||||
|
.collect(Collectors.toUnmodifiableList()));
|
||||||
|
|
||||||
|
List<TransactionsListItem> transactionsListItems = btcWalletService.getTransactions(false)
|
||||||
|
.stream()
|
||||||
|
.map(transaction -> {
|
||||||
|
TransactionAwareTradable maybeTradable = filterSlices.getAllRelatedTradables(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 {
|
||||||
|
|
|
@ -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));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Add table
Reference in a new issue