avoid fetching wallet subaddresses individually by caching

This commit is contained in:
woodser 2024-01-08 09:59:38 -05:00
parent c28ffb70ff
commit 6c83fc4cf8
3 changed files with 41 additions and 24 deletions

View file

@ -1060,8 +1060,7 @@ public class XmrWalletService {
// try to use available and not yet used entries // try to use available and not yet used entries
try { try {
List<MoneroTxWallet> incomingTxs = getTxsWithIncomingOutputs(); // prefetch all incoming txs to avoid query per subaddress List<XmrAddressEntry> unusedAddressEntries = getUnusedAddressEntries(getTxsWithIncomingOutputs(), wallet.getSubaddresses(0));
List<XmrAddressEntry> unusedAddressEntries = getUnusedAddressEntries(incomingTxs);
if (!unusedAddressEntries.isEmpty()) return xmrAddressEntryList.swapAvailableToAddressEntryWithOfferId(unusedAddressEntries.get(0), context, offerId); if (!unusedAddressEntries.isEmpty()) return xmrAddressEntryList.swapAvailableToAddressEntryWithOfferId(unusedAddressEntries.get(0), context, offerId);
} catch (Exception e) { } catch (Exception e) {
log.warn("Error getting new address entry based on incoming transactions"); log.warn("Error getting new address entry based on incoming transactions");
@ -1086,8 +1085,8 @@ public class XmrWalletService {
else return unusedAddressEntries.get(0); else return unusedAddressEntries.get(0);
} }
public synchronized XmrAddressEntry getFreshAddressEntry(List<MoneroTxWallet> cachedTxs) { public synchronized XmrAddressEntry getFreshAddressEntry(List<MoneroTxWallet> cachedTxs, List<MoneroSubaddress> cachedSubaddresses) {
List<XmrAddressEntry> unusedAddressEntries = getUnusedAddressEntries(cachedTxs); List<XmrAddressEntry> unusedAddressEntries = getUnusedAddressEntries(cachedTxs, cachedSubaddresses);
if (unusedAddressEntries.isEmpty()) return getNewAddressEntry(); if (unusedAddressEntries.isEmpty()) return getNewAddressEntry();
else return unusedAddressEntries.get(0); else return unusedAddressEntries.get(0);
} }
@ -1170,7 +1169,8 @@ public class XmrWalletService {
} }
public List<XmrAddressEntry> getFundedAvailableAddressEntries() { public List<XmrAddressEntry> getFundedAvailableAddressEntries() {
return getAvailableAddressEntries().stream().filter(addressEntry -> getBalanceForSubaddress(addressEntry.getSubaddressIndex()).compareTo(BigInteger.ZERO) > 0).collect(Collectors.toList()); List<MoneroSubaddress> subaddresses = wallet.getSubaddresses(0);
return getAvailableAddressEntries().stream().filter(addressEntry -> getBalanceForSubaddress(addressEntry.getSubaddressIndex(), subaddresses).compareTo(BigInteger.ZERO) > 0).collect(Collectors.toList());
} }
public List<XmrAddressEntry> getAddressEntryListAsImmutableList() { public List<XmrAddressEntry> getAddressEntryListAsImmutableList() {
@ -1188,38 +1188,45 @@ public class XmrWalletService {
} }
public List<XmrAddressEntry> getUnusedAddressEntries() { public List<XmrAddressEntry> getUnusedAddressEntries() {
return getUnusedAddressEntries(getTxsWithIncomingOutputs()); return getUnusedAddressEntries(getTxsWithIncomingOutputs(), wallet.getSubaddresses(0));
} }
public List<XmrAddressEntry> getUnusedAddressEntries(List<MoneroTxWallet> cachedTxs) { public List<XmrAddressEntry> getUnusedAddressEntries(List<MoneroTxWallet> cachedTxs, List<MoneroSubaddress> cachedSubaddresses) {
return getAvailableAddressEntries().stream() return getAvailableAddressEntries().stream()
.filter(e -> e.getContext() == XmrAddressEntry.Context.AVAILABLE && !subaddressHasIncomingTransfers(e.getSubaddressIndex(), cachedTxs)) .filter(e -> e.getContext() == XmrAddressEntry.Context.AVAILABLE && !subaddressHasIncomingTransfers(e.getSubaddressIndex(), cachedTxs, cachedSubaddresses))
.collect(Collectors.toList()); .collect(Collectors.toList());
} }
public boolean subaddressHasIncomingTransfers(int subaddressIndex) { public boolean subaddressHasIncomingTransfers(int subaddressIndex) {
return subaddressHasIncomingTransfers(subaddressIndex, null); return subaddressHasIncomingTransfers(subaddressIndex, null, null);
} }
private boolean subaddressHasIncomingTransfers(int subaddressIndex, List<MoneroTxWallet> incomingTxs) { private boolean subaddressHasIncomingTransfers(int subaddressIndex, List<MoneroTxWallet> incomingTxs, List<MoneroSubaddress> subaddresses) {
return getNumOutputsForSubaddress(subaddressIndex, incomingTxs) > 0; return getNumOutputsForSubaddress(subaddressIndex, incomingTxs, subaddresses) > 0;
} }
public int getNumOutputsForSubaddress(int subaddressIndex, List<MoneroTxWallet> incomingTxs) { public int getNumOutputsForSubaddress(int subaddressIndex, List<MoneroTxWallet> incomingTxs, List<MoneroSubaddress> subaddresses) {
incomingTxs = getTxsWithIncomingOutputs(subaddressIndex, incomingTxs); incomingTxs = getTxsWithIncomingOutputs(subaddressIndex, incomingTxs);
int numUnspentOutputs = 0; int numUnspentOutputs = 0;
for (MoneroTxWallet tx : incomingTxs) { for (MoneroTxWallet tx : incomingTxs) {
//if (tx.getTransfers(new MoneroTransferQuery().setSubaddressIndex(subaddressIndex)).isEmpty()) continue; // TODO monero-project: transfers are occluded by transfers from/to same account, so this will return unused when used //if (tx.getTransfers(new MoneroTransferQuery().setSubaddressIndex(subaddressIndex)).isEmpty()) continue; // TODO monero-project: transfers are occluded by transfers from/to same account, so this will return unused when used
numUnspentOutputs += tx.isConfirmed() ? tx.getOutputsWallet(new MoneroOutputQuery().setAccountIndex(0).setSubaddressIndex(subaddressIndex)).size() : 1; // TODO: monero-project does not provide outputs for unconfirmed txs numUnspentOutputs += tx.isConfirmed() ? tx.getOutputsWallet(new MoneroOutputQuery().setAccountIndex(0).setSubaddressIndex(subaddressIndex)).size() : 1; // TODO: monero-project does not provide outputs for unconfirmed txs
} }
boolean positiveBalance = wallet.getBalance(0, subaddressIndex).compareTo(BigInteger.ZERO) > 0; boolean positiveBalance = getSubaddress(subaddresses, subaddressIndex).getBalance().compareTo(BigInteger.ZERO) > 0;
if (positiveBalance && numUnspentOutputs == 0) return 1; // outputs do not appear until confirmed and internal transfers are occluded, so report 1 if positive balance if (positiveBalance && numUnspentOutputs == 0) return 1; // outputs do not appear until confirmed and internal transfers are occluded, so report 1 if positive balance
return numUnspentOutputs; return numUnspentOutputs;
} }
public int getNumTxsWithIncomingOutputs(int subaddressIndex, List<MoneroTxWallet> txs) { private MoneroSubaddress getSubaddress(Collection<MoneroSubaddress> subaddresses, int subaddressIndex) {
for (MoneroSubaddress subaddress : subaddresses) {
if (subaddress.getIndex() == subaddressIndex) return subaddress;
}
return null;
}
public int getNumTxsWithIncomingOutputs(int subaddressIndex, List<MoneroTxWallet> txs, List<MoneroSubaddress> subaddresses) {
List<MoneroTxWallet> txsWithIncomingOutputs = getTxsWithIncomingOutputs(subaddressIndex, txs); List<MoneroTxWallet> txsWithIncomingOutputs = getTxsWithIncomingOutputs(subaddressIndex, txs);
if (txsWithIncomingOutputs.isEmpty() && subaddressHasIncomingTransfers(subaddressIndex, txsWithIncomingOutputs)) return 1; // outputs do not appear until confirmed and internal transfers are occluded, so report 1 if positive balance if (txsWithIncomingOutputs.isEmpty() && subaddressHasIncomingTransfers(subaddressIndex, txsWithIncomingOutputs, subaddresses)) return 1; // outputs do not appear until confirmed and internal transfers are occluded, so report 1 if positive balance
return txsWithIncomingOutputs.size(); return txsWithIncomingOutputs.size();
} }
@ -1267,6 +1274,11 @@ public class XmrWalletService {
} }
} }
public BigInteger getBalanceForSubaddress(int subaddressIndex, Collection<MoneroSubaddress> subaddresses) {
if (subaddresses == null) return getBalanceForSubaddress(subaddressIndex);
return getSubaddress(subaddresses, subaddressIndex).getBalance();
}
public BigInteger getAvailableBalanceForSubaddress(int subaddressIndex) { public BigInteger getAvailableBalanceForSubaddress(int subaddressIndex) {
synchronized (walletLock) { synchronized (walletLock) {
if (wallet == null) throw new IllegalStateException("Cannot get available balance for subaddress because main wallet is null"); if (wallet == null) throw new IllegalStateException("Cannot get available balance for subaddress because main wallet is null");
@ -1293,7 +1305,8 @@ public class XmrWalletService {
available = Stream.concat(available, getAddressEntries(XmrAddressEntry.Context.ARBITRATOR).stream()); available = Stream.concat(available, getAddressEntries(XmrAddressEntry.Context.ARBITRATOR).stream());
available = Stream.concat(available, getAddressEntries(XmrAddressEntry.Context.OFFER_FUNDING).stream().filter(entry -> !tradeManager.getOpenOfferManager().getOpenOfferById(entry.getOfferId()).isPresent())); available = Stream.concat(available, getAddressEntries(XmrAddressEntry.Context.OFFER_FUNDING).stream().filter(entry -> !tradeManager.getOpenOfferManager().getOpenOfferById(entry.getOfferId()).isPresent()));
available = Stream.concat(available, getAddressEntries(XmrAddressEntry.Context.TRADE_PAYOUT).stream().filter(entry -> tradeManager.getTrade(entry.getOfferId()) == null || tradeManager.getTrade(entry.getOfferId()).isPayoutUnlocked())); available = Stream.concat(available, getAddressEntries(XmrAddressEntry.Context.TRADE_PAYOUT).stream().filter(entry -> tradeManager.getTrade(entry.getOfferId()) == null || tradeManager.getTrade(entry.getOfferId()).isPayoutUnlocked()));
return available.filter(addressEntry -> getBalanceForSubaddress(addressEntry.getSubaddressIndex()).compareTo(BigInteger.ZERO) > 0); List<MoneroSubaddress> subaddresses = wallet.getSubaddresses(0);
return available.filter(addressEntry -> getBalanceForSubaddress(addressEntry.getSubaddressIndex(), subaddresses).compareTo(BigInteger.ZERO) > 0);
} }
public void addWalletListener(MoneroWalletListenerI listener) { public void addWalletListener(MoneroWalletListenerI listener) {

View file

@ -32,6 +32,7 @@ import javafx.beans.property.StringProperty;
import javafx.scene.control.Tooltip; import javafx.scene.control.Tooltip;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import monero.daemon.model.MoneroTx; import monero.daemon.model.MoneroTx;
import monero.wallet.model.MoneroSubaddress;
import monero.wallet.model.MoneroTxWallet; import monero.wallet.model.MoneroTxWallet;
import java.math.BigInteger; import java.math.BigInteger;
@ -56,14 +57,14 @@ class DepositListItem {
return lazyFieldsSupplier.get(); return lazyFieldsSupplier.get();
} }
DepositListItem(XmrAddressEntry addressEntry, XmrWalletService xmrWalletService, CoinFormatter formatter, List<MoneroTxWallet> cachedTxs) { DepositListItem(XmrAddressEntry addressEntry, XmrWalletService xmrWalletService, CoinFormatter formatter, List<MoneroTxWallet> cachedTxs, List<MoneroSubaddress> cachedSubaddresses) {
this.xmrWalletService = xmrWalletService; this.xmrWalletService = xmrWalletService;
this.addressEntry = addressEntry; this.addressEntry = addressEntry;
balanceAsBI = xmrWalletService.getBalanceForSubaddress(addressEntry.getSubaddressIndex()); balanceAsBI = xmrWalletService.getBalanceForSubaddress(addressEntry.getSubaddressIndex(), cachedSubaddresses);
balance.set(HavenoUtils.formatXmr(balanceAsBI)); balance.set(HavenoUtils.formatXmr(balanceAsBI));
updateUsage(addressEntry.getSubaddressIndex(), cachedTxs); updateUsage(addressEntry.getSubaddressIndex(), cachedTxs, cachedSubaddresses);
// confidence // confidence
lazyFieldsSupplier = Suppliers.memoize(() -> new LazyFields() {{ lazyFieldsSupplier = Suppliers.memoize(() -> new LazyFields() {{
@ -82,8 +83,8 @@ class DepositListItem {
}}); }});
} }
private void updateUsage(int subaddressIndex, List<MoneroTxWallet> cachedTxs) { private void updateUsage(int subaddressIndex, List<MoneroTxWallet> cachedTxs, List<MoneroSubaddress> cachedSubaddresses) {
numTxsWithOutputs = xmrWalletService.getNumTxsWithIncomingOutputs(addressEntry.getSubaddressIndex(), cachedTxs); numTxsWithOutputs = xmrWalletService.getNumTxsWithIncomingOutputs(addressEntry.getSubaddressIndex(), cachedTxs, cachedSubaddresses);
switch (addressEntry.getContext()) { switch (addressEntry.getContext()) {
case BASE_ADDRESS: case BASE_ADDRESS:
usage = Res.get("funds.deposit.baseAddress"); usage = Res.get("funds.deposit.baseAddress");

View file

@ -59,6 +59,7 @@ import javafx.scene.layout.GridPane;
import javafx.scene.layout.HBox; import javafx.scene.layout.HBox;
import javafx.scene.layout.VBox; import javafx.scene.layout.VBox;
import javafx.util.Callback; import javafx.util.Callback;
import monero.wallet.model.MoneroSubaddress;
import monero.wallet.model.MoneroTxConfig; import monero.wallet.model.MoneroTxConfig;
import monero.wallet.model.MoneroTxWallet; import monero.wallet.model.MoneroTxWallet;
import monero.wallet.model.MoneroWalletListener; import monero.wallet.model.MoneroWalletListener;
@ -136,11 +137,12 @@ public class DepositView extends ActivatableView<VBox, Void> {
// try to initialize with wallet txs // try to initialize with wallet txs
try { try {
// prefetch all incoming txs to avoid query per subaddress // prefetch to avoid query per subaddress
txsWithIncomingOutputs = xmrWalletService.getTxsWithIncomingOutputs(); txsWithIncomingOutputs = xmrWalletService.getTxsWithIncomingOutputs();
List<MoneroSubaddress> subaddresses = xmrWalletService.getWallet().getSubaddresses(0);
// trigger creation of at least 1 address // trigger creation of at least 1 address
xmrWalletService.getFreshAddressEntry(txsWithIncomingOutputs); xmrWalletService.getFreshAddressEntry(txsWithIncomingOutputs, subaddresses);
} catch (Exception e) { } catch (Exception e) {
log.warn("Failed to get wallet txs to initialize DepositView"); log.warn("Failed to get wallet txs to initialize DepositView");
e.printStackTrace(); e.printStackTrace();
@ -318,13 +320,14 @@ public class DepositView extends ActivatableView<VBox, Void> {
// cache incoming txs // cache incoming txs
txsWithIncomingOutputs = xmrWalletService.getTxsWithIncomingOutputs(); txsWithIncomingOutputs = xmrWalletService.getTxsWithIncomingOutputs();
List<MoneroSubaddress> subaddresses = xmrWalletService.getWallet().getSubaddresses(0);
// create deposit list items // create deposit list items
List<XmrAddressEntry> addressEntries = xmrWalletService.getAddressEntries(); List<XmrAddressEntry> addressEntries = xmrWalletService.getAddressEntries();
List<DepositListItem> items = new ArrayList<>(); List<DepositListItem> items = new ArrayList<>();
for (XmrAddressEntry addressEntry : addressEntries) { for (XmrAddressEntry addressEntry : addressEntries) {
if (addressEntry.getContext().isReserved()) continue; if (addressEntry.getContext().isReserved()) continue;
items.add(new DepositListItem(addressEntry, xmrWalletService, formatter, txsWithIncomingOutputs)); items.add(new DepositListItem(addressEntry, xmrWalletService, formatter, txsWithIncomingOutputs, subaddresses));
} }
// update list // update list