mirror of
https://github.com/bisq-network/bisq.git
synced 2024-11-19 09:52:23 +01:00
Place filter in front of 'isRelatedToTransaction' for speedup
Use a crude Bloom filter (of sorts) to cut down the quadratic number of calls to 'TransactionAwareTradable.isRelatedToTransaction' (that is, one for each tx-tradable pair) during the Transactions view load. In this way, we may reduce the number of calls roughly 40-fold, for a Bisq instance with similar numbers of BSQ swap trades and escrow trades. (Sadly, profiling does not show a 40-fold reduction in the size of the 'isRelatedToTransaction' hotspot, likely due to the remaining calls being expensive ones involving disputed trades or unusual txs with nonzero locktime, e.g. dust attacks or funds from Electrum wallets.) To this end, partition the wallet transactions into 64 pseudo-randomly chosen buckets (with a dedicated bucket for txs which might be delayed payouts, namely those with nonzero locktime). Add an interface method, 'TransactionAwareTradable.getRelatedTransactionFilter', which returns an IntStream of all the indices of buckets where a related tx may plausibly be found. Where this is unclear, e.g. for trades involved in a dispute, just return everything (that is, the range 0..63 inclusive). Add a class, 'RelatedTransactionFilterSlices', that holds a provided list of TransactionAwareTradable instances and 64 bitsets of all the slices through their respective filters (each realised as 64-bit word instead of a streams of integers). In this way, a list of tradables plausibly related to any given tx may be quickly found by simply selecting the appropriate bitset of the 64 (by the tx bucket index).
This commit is contained in:
parent
19a80d19bb
commit
97ef9c1308
@ -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 java.util.stream.IntStream;
|
||||
|
||||
class TransactionAwareOpenOffer implements TransactionAwareTradable {
|
||||
private final OpenOffer delegate;
|
||||
|
||||
@ -40,4 +42,12 @@ class TransactionAwareOpenOffer implements TransactionAwareTradable {
|
||||
public Tradable asTradable() {
|
||||
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 org.bitcoinj.core.Sha256Hash;
|
||||
import org.bitcoinj.core.Transaction;
|
||||
|
||||
import java.util.stream.IntStream;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
|
||||
interface TransactionAwareTradable {
|
||||
int TX_FILTER_SIZE = 64;
|
||||
int DELAYED_PAYOUT_TX_BUCKET_INDEX = TX_FILTER_SIZE - 1;
|
||||
|
||||
boolean isRelatedToTransaction(Transaction transaction);
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
@ -38,8 +38,11 @@ import org.bitcoinj.core.TransactionOutput;
|
||||
|
||||
import javafx.collections.ObservableList;
|
||||
|
||||
import java.util.stream.IntStream;
|
||||
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
import static bisq.desktop.main.funds.transactions.TransactionAwareTradable.bucketIndex;
|
||||
import static com.google.common.base.Preconditions.checkNotNull;
|
||||
|
||||
@Slf4j
|
||||
@ -128,7 +131,7 @@ class TransactionAwareTrade implements TransactionAwareTradable {
|
||||
String delegateId = tradeModel.getId();
|
||||
ObservableList<Dispute> disputes = arbitrationManager.getDisputesAsObservableList();
|
||||
|
||||
boolean isAnyDisputeRelatedToThis = arbitrationManager.getDisputedTradeIds().contains(tradeModel.getId());
|
||||
boolean isAnyDisputeRelatedToThis = arbitrationManager.getDisputedTradeIds().contains(delegateId);
|
||||
|
||||
return isAnyDisputeRelatedToThis && disputes.stream()
|
||||
.anyMatch(dispute -> {
|
||||
@ -216,4 +219,27 @@ class TransactionAwareTrade implements TransactionAwareTradable {
|
||||
public Tradable asTradable() {
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -95,6 +95,7 @@ import javafx.util.Callback;
|
||||
|
||||
import java.util.Comparator;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
import java.util.Set;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
@ -289,11 +290,7 @@ public class TransactionsView extends ActivatableView<VBox, Void> {
|
||||
|
||||
private void updateList() {
|
||||
Set<Tradable> tradables = tradableRepository.getAll();
|
||||
|
||||
List<TransactionsListItem> transactionsListItems = btcWalletService.getTransactions(false)
|
||||
.stream()
|
||||
.map(transaction -> {
|
||||
TransactionAwareTradable maybeTradable = tradables.stream()
|
||||
var filterSlices = new RelatedTransactionFilterSlices(tradables.stream()
|
||||
.map(tradable -> {
|
||||
if (tradable instanceof OpenOffer) {
|
||||
return new TransactionAwareOpenOffer((OpenOffer) tradable);
|
||||
@ -309,7 +306,13 @@ public class TransactionsView extends ActivatableView<VBox, Void> {
|
||||
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()
|
||||
.orElse(null);
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user