diff --git a/core/src/main/java/haveno/core/xmr/wallet/XmrWalletService.java b/core/src/main/java/haveno/core/xmr/wallet/XmrWalletService.java index 75ccb78832..a737eb187a 100644 --- a/core/src/main/java/haveno/core/xmr/wallet/XmrWalletService.java +++ b/core/src/main/java/haveno/core/xmr/wallet/XmrWalletService.java @@ -1060,8 +1060,7 @@ public class XmrWalletService { // try to use available and not yet used entries try { - List incomingTxs = getTxsWithIncomingOutputs(); // prefetch all incoming txs to avoid query per subaddress - List unusedAddressEntries = getUnusedAddressEntries(incomingTxs); + List unusedAddressEntries = getUnusedAddressEntries(getTxsWithIncomingOutputs(), wallet.getSubaddresses(0)); if (!unusedAddressEntries.isEmpty()) return xmrAddressEntryList.swapAvailableToAddressEntryWithOfferId(unusedAddressEntries.get(0), context, offerId); } catch (Exception e) { log.warn("Error getting new address entry based on incoming transactions"); @@ -1086,8 +1085,8 @@ public class XmrWalletService { else return unusedAddressEntries.get(0); } - public synchronized XmrAddressEntry getFreshAddressEntry(List cachedTxs) { - List unusedAddressEntries = getUnusedAddressEntries(cachedTxs); + public synchronized XmrAddressEntry getFreshAddressEntry(List cachedTxs, List cachedSubaddresses) { + List unusedAddressEntries = getUnusedAddressEntries(cachedTxs, cachedSubaddresses); if (unusedAddressEntries.isEmpty()) return getNewAddressEntry(); else return unusedAddressEntries.get(0); } @@ -1170,7 +1169,8 @@ public class XmrWalletService { } public List getFundedAvailableAddressEntries() { - return getAvailableAddressEntries().stream().filter(addressEntry -> getBalanceForSubaddress(addressEntry.getSubaddressIndex()).compareTo(BigInteger.ZERO) > 0).collect(Collectors.toList()); + List subaddresses = wallet.getSubaddresses(0); + return getAvailableAddressEntries().stream().filter(addressEntry -> getBalanceForSubaddress(addressEntry.getSubaddressIndex(), subaddresses).compareTo(BigInteger.ZERO) > 0).collect(Collectors.toList()); } public List getAddressEntryListAsImmutableList() { @@ -1188,38 +1188,45 @@ public class XmrWalletService { } public List getUnusedAddressEntries() { - return getUnusedAddressEntries(getTxsWithIncomingOutputs()); + return getUnusedAddressEntries(getTxsWithIncomingOutputs(), wallet.getSubaddresses(0)); } - public List getUnusedAddressEntries(List cachedTxs) { + public List getUnusedAddressEntries(List cachedTxs, List cachedSubaddresses) { 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()); } public boolean subaddressHasIncomingTransfers(int subaddressIndex) { - return subaddressHasIncomingTransfers(subaddressIndex, null); + return subaddressHasIncomingTransfers(subaddressIndex, null, null); } - private boolean subaddressHasIncomingTransfers(int subaddressIndex, List incomingTxs) { - return getNumOutputsForSubaddress(subaddressIndex, incomingTxs) > 0; + private boolean subaddressHasIncomingTransfers(int subaddressIndex, List incomingTxs, List subaddresses) { + return getNumOutputsForSubaddress(subaddressIndex, incomingTxs, subaddresses) > 0; } - public int getNumOutputsForSubaddress(int subaddressIndex, List incomingTxs) { + public int getNumOutputsForSubaddress(int subaddressIndex, List incomingTxs, List subaddresses) { incomingTxs = getTxsWithIncomingOutputs(subaddressIndex, incomingTxs); int numUnspentOutputs = 0; 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 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 return numUnspentOutputs; } - public int getNumTxsWithIncomingOutputs(int subaddressIndex, List txs) { + private MoneroSubaddress getSubaddress(Collection subaddresses, int subaddressIndex) { + for (MoneroSubaddress subaddress : subaddresses) { + if (subaddress.getIndex() == subaddressIndex) return subaddress; + } + return null; + } + + public int getNumTxsWithIncomingOutputs(int subaddressIndex, List txs, List subaddresses) { List 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(); } @@ -1267,6 +1274,11 @@ public class XmrWalletService { } } + public BigInteger getBalanceForSubaddress(int subaddressIndex, Collection subaddresses) { + if (subaddresses == null) return getBalanceForSubaddress(subaddressIndex); + return getSubaddress(subaddresses, subaddressIndex).getBalance(); + } + public BigInteger getAvailableBalanceForSubaddress(int subaddressIndex) { synchronized (walletLock) { 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.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())); - return available.filter(addressEntry -> getBalanceForSubaddress(addressEntry.getSubaddressIndex()).compareTo(BigInteger.ZERO) > 0); + List subaddresses = wallet.getSubaddresses(0); + return available.filter(addressEntry -> getBalanceForSubaddress(addressEntry.getSubaddressIndex(), subaddresses).compareTo(BigInteger.ZERO) > 0); } public void addWalletListener(MoneroWalletListenerI listener) { diff --git a/desktop/src/main/java/haveno/desktop/main/funds/deposit/DepositListItem.java b/desktop/src/main/java/haveno/desktop/main/funds/deposit/DepositListItem.java index c508e6f462..0ce1cb21c4 100644 --- a/desktop/src/main/java/haveno/desktop/main/funds/deposit/DepositListItem.java +++ b/desktop/src/main/java/haveno/desktop/main/funds/deposit/DepositListItem.java @@ -32,6 +32,7 @@ import javafx.beans.property.StringProperty; import javafx.scene.control.Tooltip; import lombok.extern.slf4j.Slf4j; import monero.daemon.model.MoneroTx; +import monero.wallet.model.MoneroSubaddress; import monero.wallet.model.MoneroTxWallet; import java.math.BigInteger; @@ -56,14 +57,14 @@ class DepositListItem { return lazyFieldsSupplier.get(); } - DepositListItem(XmrAddressEntry addressEntry, XmrWalletService xmrWalletService, CoinFormatter formatter, List cachedTxs) { + DepositListItem(XmrAddressEntry addressEntry, XmrWalletService xmrWalletService, CoinFormatter formatter, List cachedTxs, List cachedSubaddresses) { this.xmrWalletService = xmrWalletService; this.addressEntry = addressEntry; - balanceAsBI = xmrWalletService.getBalanceForSubaddress(addressEntry.getSubaddressIndex()); + balanceAsBI = xmrWalletService.getBalanceForSubaddress(addressEntry.getSubaddressIndex(), cachedSubaddresses); balance.set(HavenoUtils.formatXmr(balanceAsBI)); - updateUsage(addressEntry.getSubaddressIndex(), cachedTxs); + updateUsage(addressEntry.getSubaddressIndex(), cachedTxs, cachedSubaddresses); // confidence lazyFieldsSupplier = Suppliers.memoize(() -> new LazyFields() {{ @@ -82,8 +83,8 @@ class DepositListItem { }}); } - private void updateUsage(int subaddressIndex, List cachedTxs) { - numTxsWithOutputs = xmrWalletService.getNumTxsWithIncomingOutputs(addressEntry.getSubaddressIndex(), cachedTxs); + private void updateUsage(int subaddressIndex, List cachedTxs, List cachedSubaddresses) { + numTxsWithOutputs = xmrWalletService.getNumTxsWithIncomingOutputs(addressEntry.getSubaddressIndex(), cachedTxs, cachedSubaddresses); switch (addressEntry.getContext()) { case BASE_ADDRESS: usage = Res.get("funds.deposit.baseAddress"); diff --git a/desktop/src/main/java/haveno/desktop/main/funds/deposit/DepositView.java b/desktop/src/main/java/haveno/desktop/main/funds/deposit/DepositView.java index 270d243555..19344b3a4f 100644 --- a/desktop/src/main/java/haveno/desktop/main/funds/deposit/DepositView.java +++ b/desktop/src/main/java/haveno/desktop/main/funds/deposit/DepositView.java @@ -59,6 +59,7 @@ import javafx.scene.layout.GridPane; import javafx.scene.layout.HBox; import javafx.scene.layout.VBox; import javafx.util.Callback; +import monero.wallet.model.MoneroSubaddress; import monero.wallet.model.MoneroTxConfig; import monero.wallet.model.MoneroTxWallet; import monero.wallet.model.MoneroWalletListener; @@ -136,11 +137,12 @@ public class DepositView extends ActivatableView { // try to initialize with wallet txs try { - // prefetch all incoming txs to avoid query per subaddress + // prefetch to avoid query per subaddress txsWithIncomingOutputs = xmrWalletService.getTxsWithIncomingOutputs(); + List subaddresses = xmrWalletService.getWallet().getSubaddresses(0); // trigger creation of at least 1 address - xmrWalletService.getFreshAddressEntry(txsWithIncomingOutputs); + xmrWalletService.getFreshAddressEntry(txsWithIncomingOutputs, subaddresses); } catch (Exception e) { log.warn("Failed to get wallet txs to initialize DepositView"); e.printStackTrace(); @@ -318,13 +320,14 @@ public class DepositView extends ActivatableView { // cache incoming txs txsWithIncomingOutputs = xmrWalletService.getTxsWithIncomingOutputs(); + List subaddresses = xmrWalletService.getWallet().getSubaddresses(0); // create deposit list items List addressEntries = xmrWalletService.getAddressEntries(); List items = new ArrayList<>(); for (XmrAddressEntry addressEntry : addressEntries) { if (addressEntry.getContext().isReserved()) continue; - items.add(new DepositListItem(addressEntry, xmrWalletService, formatter, txsWithIncomingOutputs)); + items.add(new DepositListItem(addressEntry, xmrWalletService, formatter, txsWithIncomingOutputs, subaddresses)); } // update list