improve performance by pre-fetching txs for subaddress queries

This commit is contained in:
woodser 2022-11-05 15:07:23 -04:00
parent 4fb62d8669
commit d9f2ce425f
3 changed files with 33 additions and 27 deletions

View file

@ -37,9 +37,6 @@ import java.util.Map;
import java.util.Optional; import java.util.Optional;
import java.util.Set; import java.util.Set;
import java.util.concurrent.CopyOnWriteArraySet; import java.util.concurrent.CopyOnWriteArraySet;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors; import java.util.stream.Collectors;
import java.util.stream.Stream; import java.util.stream.Stream;
import javax.inject.Inject; import javax.inject.Inject;
@ -57,6 +54,7 @@ import monero.wallet.MoneroWallet;
import monero.wallet.MoneroWalletRpc; import monero.wallet.MoneroWalletRpc;
import monero.wallet.model.MoneroCheckTx; import monero.wallet.model.MoneroCheckTx;
import monero.wallet.model.MoneroDestination; import monero.wallet.model.MoneroDestination;
import monero.wallet.model.MoneroOutputQuery;
import monero.wallet.model.MoneroOutputWallet; import monero.wallet.model.MoneroOutputWallet;
import monero.wallet.model.MoneroSubaddress; import monero.wallet.model.MoneroSubaddress;
import monero.wallet.model.MoneroTransferQuery; import monero.wallet.model.MoneroTransferQuery;
@ -784,8 +782,9 @@ public class XmrWalletService {
return addressEntry.get(); return addressEntry.get();
} else { } else {
// We try to use available and not yet used entries // We try to use available and not yet used entries
List<MoneroTxWallet> incomingTxs = getIncomingTxs(null); // pre-fetch all incoming txs to avoid query per subaddress
Optional<XmrAddressEntry> emptyAvailableAddressEntry = getAddressEntryListAsImmutableList().stream().filter(e -> XmrAddressEntry.Context.AVAILABLE == e.getContext()) Optional<XmrAddressEntry> emptyAvailableAddressEntry = getAddressEntryListAsImmutableList().stream().filter(e -> XmrAddressEntry.Context.AVAILABLE == e.getContext())
.filter(e -> isSubaddressUnused(e.getSubaddressIndex())).findAny(); .filter(e -> isSubaddressUnused(e.getSubaddressIndex(), incomingTxs)).findAny();
if (emptyAvailableAddressEntry.isPresent()) { if (emptyAvailableAddressEntry.isPresent()) {
return xmrAddressEntryList.swapAvailableToAddressEntryWithOfferId(emptyAvailableAddressEntry.get(), context, offerId); return xmrAddressEntryList.swapAvailableToAddressEntryWithOfferId(emptyAvailableAddressEntry.get(), context, offerId);
} else { } else {
@ -890,7 +889,33 @@ public class XmrWalletService {
} }
public boolean isSubaddressUnused(int subaddressIndex) { public boolean isSubaddressUnused(int subaddressIndex) {
return getNumTxOutputsForSubaddress(subaddressIndex) == 0; return isSubaddressUnused(subaddressIndex, null);
}
private boolean isSubaddressUnused(int subaddressIndex, List<MoneroTxWallet> incomingTxs) {
return getNumTxOutputsForSubaddress(subaddressIndex, incomingTxs) == 0;
}
public int getNumTxOutputsForSubaddress(int subaddressIndex) {
return getNumTxOutputsForSubaddress(subaddressIndex, null);
}
private int getNumTxOutputsForSubaddress(int subaddressIndex, List<MoneroTxWallet> incomingTxs) {
if (incomingTxs == null) incomingTxs = getIncomingTxs(subaddressIndex);
int numUnspentOutputs = 0;
for (MoneroTxWallet tx : incomingTxs) {
numUnspentOutputs += tx.isConfirmed() ? tx.getOutputsWallet(new MoneroOutputQuery().setSubaddressIndex(subaddressIndex)).size() : 1; // TODO: monero-project does not provide outputs for unconfirmed txs
}
return numUnspentOutputs;
}
private List<MoneroTxWallet> getIncomingTxs(Integer subaddressIndex) {
return wallet.getTxs(new MoneroTxQuery()
.setTransferQuery((new MoneroTransferQuery()
.setAccountIndex(0)
.setSubaddressIndex(subaddressIndex)
.setIsIncoming(true)))
.setIncludeOutputs(true));
} }
public Coin getBalanceForAddress(String address) { public Coin getBalanceForAddress(String address) {
@ -917,24 +942,6 @@ public class XmrWalletService {
return Coin.valueOf(balance.longValueExact()); return Coin.valueOf(balance.longValueExact());
} }
public int getNumTxOutputsForSubaddress(int subaddressIndex) {
// get txs with transfers to the subaddress
List<MoneroTxWallet> txs = wallet.getTxs(new MoneroTxQuery()
.setTransferQuery((new MoneroTransferQuery()
.setAccountIndex(0)
.setSubaddressIndex(subaddressIndex)
.setIsIncoming(true)))
.setIncludeOutputs(true));
// count num outputs
int numUnspentOutputs = 0;
for (MoneroTxWallet tx : txs) {
numUnspentOutputs += tx.isConfirmed() ? tx.getOutputs().size() : 1; // TODO: monero-project does not provide outputs for unconfirmed txs
}
return numUnspentOutputs;
}
public Coin getAvailableConfirmedBalance() { public Coin getAvailableConfirmedBalance() {
return wallet != null ? Coin.valueOf(wallet.getUnlockedBalance(0).longValueExact()) : Coin.ZERO; return wallet != null ? Coin.valueOf(wallet.getUnlockedBalance(0).longValueExact()) : Coin.ZERO;
} }

View file

@ -90,7 +90,7 @@ public class ArbitratorProtocol extends DisputeProtocol {
ArbitratorProcessDepositRequest.class) ArbitratorProcessDepositRequest.class)
.using(new TradeTaskRunner(trade, .using(new TradeTaskRunner(trade,
() -> { () -> {
if (trade.getState() == Trade.State.ARBITRATOR_PUBLISHED_DEPOSIT_TXS) { if (trade.getState().ordinal() >= Trade.State.ARBITRATOR_PUBLISHED_DEPOSIT_TXS.ordinal()) {
stopTimeout(); stopTimeout();
this.errorMessageHandler = null; this.errorMessageHandler = null;
} }

View file

@ -424,7 +424,6 @@ public abstract class TradeProtocol implements DecryptedDirectMessageListener, D
handleTaskRunnerSuccess(peer, message); handleTaskRunnerSuccess(peer, message);
}, },
(errorMessage) -> { (errorMessage) -> {
stopTimeout();
handleTaskRunnerFault(peer, message, errorMessage); handleTaskRunnerFault(peer, message, errorMessage);
}))) })))
.executeTasks(true); .executeTasks(true);
@ -589,14 +588,14 @@ public abstract class TradeProtocol implements DecryptedDirectMessageListener, D
// Timeout // Timeout
/////////////////////////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////////////////////////
protected void startTimeout(long timeoutSec) { protected synchronized void startTimeout(long timeoutSec) {
stopTimeout(); stopTimeout();
timeoutTimer = UserThread.runAfter(() -> { timeoutTimer = UserThread.runAfter(() -> {
handleError("Timeout reached. Protocol did not complete in " + timeoutSec + " sec. TradeID=" + trade.getId() + ", state=" + trade.stateProperty().get()); handleError("Timeout reached. Protocol did not complete in " + timeoutSec + " sec. TradeID=" + trade.getId() + ", state=" + trade.stateProperty().get());
}, timeoutSec); }, timeoutSec);
} }
protected void stopTimeout() { protected synchronized void stopTimeout() {
if (timeoutTimer != null) { if (timeoutTimer != null) {
timeoutTimer.stop(); timeoutTimer.stop();
timeoutTimer = null; timeoutTimer = null;