cache wallet state to avoid requests on main thread

This commit is contained in:
woodser 2024-02-18 14:56:21 -05:00
parent d8ef7e82d4
commit eaf096adeb
4 changed files with 76 additions and 100 deletions

View file

@ -155,6 +155,10 @@ public class XmrWalletService {
private ExecutorService syncWalletThreadPool = Executors.newFixedThreadPool(10); // TODO: adjust based on connection type private ExecutorService syncWalletThreadPool = Executors.newFixedThreadPool(10); // TODO: adjust based on connection type
private Long syncStartHeight = null; private Long syncStartHeight = null;
private TaskLooper syncLooper = null; private TaskLooper syncLooper = null;
private BigInteger cachedBalance = null;
private BigInteger cachedAvailableBalance = null;
private List<MoneroSubaddress> cachedSubaddresses;
private List<MoneroTxWallet> cachedTxs;
@Inject @Inject
XmrWalletService(User user, XmrWalletService(User user,
@ -439,7 +443,6 @@ public class XmrWalletService {
if (!unreservedFrozenKeyImages.isEmpty()) { if (!unreservedFrozenKeyImages.isEmpty()) {
log.warn("Thawing outputs which are not reserved for offer or trade: " + unreservedFrozenKeyImages); log.warn("Thawing outputs which are not reserved for offer or trade: " + unreservedFrozenKeyImages);
thawOutputs(unreservedFrozenKeyImages); thawOutputs(unreservedFrozenKeyImages);
saveMainWallet();
} }
} }
} }
@ -452,6 +455,8 @@ public class XmrWalletService {
public void freezeOutputs(Collection<String> keyImages) { public void freezeOutputs(Collection<String> keyImages) {
synchronized (walletLock) { synchronized (walletLock) {
for (String keyImage : keyImages) wallet.freezeOutput(keyImage); for (String keyImage : keyImages) wallet.freezeOutput(keyImage);
saveMainWallet();
cacheWalletState();
} }
updateBalanceListeners(); // TODO (monero-java): balance listeners not notified on freeze/thaw output updateBalanceListeners(); // TODO (monero-java): balance listeners not notified on freeze/thaw output
} }
@ -464,6 +469,8 @@ public class XmrWalletService {
public void thawOutputs(Collection<String> keyImages) { public void thawOutputs(Collection<String> keyImages) {
synchronized (walletLock) { synchronized (walletLock) {
for (String keyImage : keyImages) wallet.thawOutput(keyImage); for (String keyImage : keyImages) wallet.thawOutput(keyImage);
saveMainWallet();
cacheWalletState();
} }
updateBalanceListeners(); // TODO (monero-java): balance listeners not notified on freeze/thaw output updateBalanceListeners(); // TODO (monero-java): balance listeners not notified on freeze/thaw output
} }
@ -845,11 +852,12 @@ public class XmrWalletService {
long time = System.currentTimeMillis(); long time = System.currentTimeMillis();
syncWalletWithProgress(); // blocking syncWalletWithProgress(); // blocking
log.info("Done syncing main wallet in " + (System.currentTimeMillis() - time) + " ms"); log.info("Done syncing main wallet in " + (System.currentTimeMillis() - time) + " ms");
cacheWalletState();
// log wallet balances // log wallet balances
if (getMoneroNetworkType() != MoneroNetworkType.MAINNET) { if (getMoneroNetworkType() != MoneroNetworkType.MAINNET) {
BigInteger balance = wallet.getBalance(); BigInteger balance = getBalance();
BigInteger unlockedBalance = wallet.getUnlockedBalance(); BigInteger unlockedBalance = getAvailableBalance();
log.info("Monero wallet unlocked balance={}, pending balance={}, total balance={}", unlockedBalance, balance.subtract(unlockedBalance), balance); log.info("Monero wallet unlocked balance={}, pending balance={}, total balance={}", unlockedBalance, balance.subtract(unlockedBalance), balance);
} }
@ -1118,7 +1126,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<XmrAddressEntry> unusedAddressEntries = getUnusedAddressEntries(getTxsWithIncomingOutputs(), wallet.getSubaddresses(0)); List<XmrAddressEntry> unusedAddressEntries = getUnusedAddressEntries();
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");
@ -1131,6 +1139,7 @@ public class XmrWalletService {
private XmrAddressEntry getNewAddressEntryAux(String offerId, XmrAddressEntry.Context context) { private XmrAddressEntry getNewAddressEntryAux(String offerId, XmrAddressEntry.Context context) {
MoneroSubaddress subaddress = wallet.createSubaddress(0); MoneroSubaddress subaddress = wallet.createSubaddress(0);
cacheWalletState();
XmrAddressEntry entry = new XmrAddressEntry(subaddress.getIndex(), subaddress.getAddress(), context, offerId, null); XmrAddressEntry entry = new XmrAddressEntry(subaddress.getIndex(), subaddress.getAddress(), context, offerId, null);
log.info("Add new XmrAddressEntry {}", entry); log.info("Add new XmrAddressEntry {}", entry);
xmrAddressEntryList.addAddressEntry(entry); xmrAddressEntryList.addAddressEntry(entry);
@ -1143,12 +1152,6 @@ public class XmrWalletService {
else return unusedAddressEntries.get(0); else return unusedAddressEntries.get(0);
} }
public synchronized XmrAddressEntry getFreshAddressEntry(List<MoneroTxWallet> cachedTxs, List<MoneroSubaddress> cachedSubaddresses) {
List<XmrAddressEntry> unusedAddressEntries = getUnusedAddressEntries(cachedTxs, cachedSubaddresses);
if (unusedAddressEntries.isEmpty()) return getNewAddressEntry();
else return unusedAddressEntries.get(0);
}
public synchronized XmrAddressEntry recoverAddressEntry(String offerId, String address, XmrAddressEntry.Context context) { public synchronized XmrAddressEntry recoverAddressEntry(String offerId, String address, XmrAddressEntry.Context context) {
var available = findAddressEntry(address, XmrAddressEntry.Context.AVAILABLE); var available = findAddressEntry(address, XmrAddressEntry.Context.AVAILABLE);
if (!available.isPresent()) return null; if (!available.isPresent()) return null;
@ -1228,15 +1231,13 @@ public class XmrWalletService {
public List<XmrAddressEntry> getFundedAvailableAddressEntries() { public List<XmrAddressEntry> getFundedAvailableAddressEntries() {
synchronized (walletLock) { synchronized (walletLock) {
List<MoneroSubaddress> subaddresses = wallet.getSubaddresses(0); return getAvailableAddressEntries().stream().filter(addressEntry -> getBalanceForSubaddress(addressEntry.getSubaddressIndex()).compareTo(BigInteger.ZERO) > 0).collect(Collectors.toList());
return getAvailableAddressEntries().stream().filter(addressEntry -> getBalanceForSubaddress(addressEntry.getSubaddressIndex(), subaddresses).compareTo(BigInteger.ZERO) > 0).collect(Collectors.toList());
} }
} }
public List<XmrAddressEntry> getAddressEntryListAsImmutableList() { public List<XmrAddressEntry> getAddressEntryListAsImmutableList() {
synchronized (walletLock) { synchronized (walletLock) {
List<MoneroSubaddress> subaddresses = wallet.getSubaddresses(0); for (MoneroSubaddress subaddress : cachedSubaddresses) {
for (MoneroSubaddress subaddress : subaddresses) {
boolean exists = xmrAddressEntryList.getAddressEntriesAsListImmutable().stream().filter(addressEntry -> addressEntry.getAddressString().equals(subaddress.getAddress())).findAny().isPresent(); boolean exists = xmrAddressEntryList.getAddressEntriesAsListImmutable().stream().filter(addressEntry -> addressEntry.getAddressString().equals(subaddress.getAddress())).findAny().isPresent();
if (!exists) { if (!exists) {
XmrAddressEntry entry = new XmrAddressEntry(subaddress.getIndex(), subaddress.getAddress(), subaddress.getIndex() == 0 ? XmrAddressEntry.Context.BASE_ADDRESS : XmrAddressEntry.Context.AVAILABLE, null, null); XmrAddressEntry entry = new XmrAddressEntry(subaddress.getIndex(), subaddress.getAddress(), subaddress.getIndex() == 0 ? XmrAddressEntry.Context.BASE_ADDRESS : XmrAddressEntry.Context.AVAILABLE, null, null);
@ -1249,46 +1250,37 @@ public class XmrWalletService {
public List<XmrAddressEntry> getUnusedAddressEntries() { public List<XmrAddressEntry> getUnusedAddressEntries() {
synchronized (walletLock) { synchronized (walletLock) {
return getUnusedAddressEntries(getTxsWithIncomingOutputs(), wallet.getSubaddresses(0));
}
}
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, cachedSubaddresses)) .filter(e -> e.getContext() == XmrAddressEntry.Context.AVAILABLE && !subaddressHasIncomingTransfers(e.getSubaddressIndex()))
.collect(Collectors.toList()); .collect(Collectors.toList());
} }
}
public boolean subaddressHasIncomingTransfers(int subaddressIndex) { public boolean subaddressHasIncomingTransfers(int subaddressIndex) {
return subaddressHasIncomingTransfers(subaddressIndex, null, null); return getNumOutputsForSubaddress(subaddressIndex) > 0;
} }
private boolean subaddressHasIncomingTransfers(int subaddressIndex, List<MoneroTxWallet> incomingTxs, List<MoneroSubaddress> subaddresses) { public int getNumOutputsForSubaddress(int subaddressIndex) {
return getNumOutputsForSubaddress(subaddressIndex, incomingTxs, subaddresses) > 0;
}
public int getNumOutputsForSubaddress(int subaddressIndex, List<MoneroTxWallet> incomingTxs, List<MoneroSubaddress> subaddresses) {
incomingTxs = getTxsWithIncomingOutputs(subaddressIndex, incomingTxs);
int numUnspentOutputs = 0; int numUnspentOutputs = 0;
for (MoneroTxWallet tx : incomingTxs) { for (MoneroTxWallet tx : cachedTxs) {
//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 = getSubaddress(subaddresses, subaddressIndex).getBalance().compareTo(BigInteger.ZERO) > 0; boolean positiveBalance = getSubaddress(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;
} }
private MoneroSubaddress getSubaddress(Collection<MoneroSubaddress> subaddresses, int subaddressIndex) { private MoneroSubaddress getSubaddress(int subaddressIndex) {
for (MoneroSubaddress subaddress : subaddresses) { for (MoneroSubaddress subaddress : cachedSubaddresses) {
if (subaddress.getIndex() == subaddressIndex) return subaddress; if (subaddress.getIndex() == subaddressIndex) return subaddress;
} }
return null; return null;
} }
public int getNumTxsWithIncomingOutputs(int subaddressIndex, List<MoneroTxWallet> txs, List<MoneroSubaddress> subaddresses) { public int getNumTxsWithIncomingOutputs(int subaddressIndex) {
List<MoneroTxWallet> txsWithIncomingOutputs = getTxsWithIncomingOutputs(subaddressIndex, txs); List<MoneroTxWallet> txsWithIncomingOutputs = getTxsWithIncomingOutputs(subaddressIndex);
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 if (txsWithIncomingOutputs.isEmpty() && subaddressHasIncomingTransfers(subaddressIndex)) 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();
} }
@ -1297,13 +1289,8 @@ public class XmrWalletService {
} }
public List<MoneroTxWallet> getTxsWithIncomingOutputs(Integer subaddressIndex) { public List<MoneroTxWallet> getTxsWithIncomingOutputs(Integer subaddressIndex) {
return getTxsWithIncomingOutputs(subaddressIndex, null);
}
public List<MoneroTxWallet> getTxsWithIncomingOutputs(Integer subaddressIndex, List<MoneroTxWallet> txs) {
if (txs == null) txs = wallet.getTxs(new MoneroTxQuery().setIncludeOutputs(true));
List<MoneroTxWallet> incomingTxs = new ArrayList<>(); List<MoneroTxWallet> incomingTxs = new ArrayList<>();
for (MoneroTxWallet tx : txs) { for (MoneroTxWallet tx : cachedTxs) {
boolean isIncoming = false; boolean isIncoming = false;
if (tx.getIncomingTransfers() != null) { if (tx.getIncomingTransfers() != null) {
for (MoneroIncomingTransfer transfer : tx.getIncomingTransfers()) { for (MoneroIncomingTransfer transfer : tx.getIncomingTransfers()) {
@ -1331,35 +1318,23 @@ public class XmrWalletService {
} }
public BigInteger getBalanceForSubaddress(int subaddressIndex) { public BigInteger getBalanceForSubaddress(int subaddressIndex) {
synchronized (walletLock) { return getSubaddress(subaddressIndex).getBalance();
return wallet.getBalance(0, subaddressIndex);
}
}
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) { return getSubaddress(subaddressIndex).getUnlockedBalance();
if (wallet == null) throw new IllegalStateException("Cannot get available balance for subaddress because main wallet is null");
return wallet.getUnlockedBalance(0, subaddressIndex);
}
} }
public BigInteger getBalance() { public BigInteger getBalance() {
synchronized (walletLock) { return cachedBalance;
if (wallet == null) throw new IllegalStateException("Cannot get balance because main wallet is null");
return wallet.getBalance(0);
}
} }
public BigInteger getAvailableBalance() { public BigInteger getAvailableBalance() {
synchronized (walletLock) { return cachedAvailableBalance;
if (wallet == null) throw new IllegalStateException("Cannot get available balance because main wallet is null");
return wallet.getUnlockedBalance(0);
} }
public List<MoneroSubaddress> getSubaddresses() {
return cachedSubaddresses;
} }
public Stream<XmrAddressEntry> getAddressEntriesForAvailableBalanceStream() { public Stream<XmrAddressEntry> getAddressEntriesForAvailableBalanceStream() {
@ -1368,8 +1343,7 @@ 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()));
List<MoneroSubaddress> subaddresses = wallet.getSubaddresses(0); return available.filter(addressEntry -> getBalanceForSubaddress(addressEntry.getSubaddressIndex()).compareTo(BigInteger.ZERO) > 0);
return available.filter(addressEntry -> getBalanceForSubaddress(addressEntry.getSubaddressIndex(), subaddresses).compareTo(BigInteger.ZERO) > 0);
} }
} }
@ -1417,7 +1391,8 @@ public class XmrWalletService {
} }
public List<MoneroTxWallet> getTransactions(boolean includeDead) { public List<MoneroTxWallet> getTransactions(boolean includeDead) {
return wallet.getTxs(new MoneroTxQuery().setIsFailed(includeDead ? null : false)); if (includeDead) return cachedTxs;
return cachedTxs.stream().filter(tx -> !tx.isFailed()).collect(Collectors.toList());
} }
/////////////////////////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////////////////////////
@ -1445,6 +1420,13 @@ public class XmrWalletService {
// -------------------------------- HELPERS ------------------------------- // -------------------------------- HELPERS -------------------------------
private void cacheWalletState() {
cachedBalance = wallet.getBalance(0);
cachedAvailableBalance = wallet.getUnlockedBalance(0);
cachedSubaddresses = wallet.getSubaddresses(0);
cachedTxs = wallet.getTxs(new MoneroTxQuery().setIncludeOutputs(true));
}
/** /**
* Relays wallet notifications to external listeners. * Relays wallet notifications to external listeners.
*/ */
@ -1457,6 +1439,7 @@ public class XmrWalletService {
@Override @Override
public void onNewBlock(long height) { public void onNewBlock(long height) {
cacheWalletState();
UserThread.execute(() -> { UserThread.execute(() -> {
walletHeight.set(height); walletHeight.set(height);
for (MoneroWalletListenerI listener : walletListeners) ThreadUtils.submitToPool(() -> listener.onNewBlock(height)); for (MoneroWalletListenerI listener : walletListeners) ThreadUtils.submitToPool(() -> listener.onNewBlock(height));
@ -1465,6 +1448,7 @@ public class XmrWalletService {
@Override @Override
public void onBalancesChanged(BigInteger newBalance, BigInteger newUnlockedBalance) { public void onBalancesChanged(BigInteger newBalance, BigInteger newUnlockedBalance) {
cacheWalletState();
updateBalanceListeners(); updateBalanceListeners();
for (MoneroWalletListenerI listener : walletListeners) ThreadUtils.submitToPool(() -> listener.onBalancesChanged(newBalance, newUnlockedBalance)); for (MoneroWalletListenerI listener : walletListeners) ThreadUtils.submitToPool(() -> listener.onBalancesChanged(newBalance, newUnlockedBalance));
} }

View file

@ -32,7 +32,6 @@ 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;
@ -57,14 +56,14 @@ class DepositListItem {
return lazyFieldsSupplier.get(); return lazyFieldsSupplier.get();
} }
DepositListItem(XmrAddressEntry addressEntry, XmrWalletService xmrWalletService, CoinFormatter formatter, List<MoneroTxWallet> cachedTxs, List<MoneroSubaddress> cachedSubaddresses) { DepositListItem(XmrAddressEntry addressEntry, XmrWalletService xmrWalletService, CoinFormatter formatter) {
this.xmrWalletService = xmrWalletService; this.xmrWalletService = xmrWalletService;
this.addressEntry = addressEntry; this.addressEntry = addressEntry;
balanceAsBI = xmrWalletService.getBalanceForSubaddress(addressEntry.getSubaddressIndex(), cachedSubaddresses); balanceAsBI = xmrWalletService.getBalanceForSubaddress(addressEntry.getSubaddressIndex());
balance.set(HavenoUtils.formatXmr(balanceAsBI)); balance.set(HavenoUtils.formatXmr(balanceAsBI));
updateUsage(addressEntry.getSubaddressIndex(), cachedTxs, cachedSubaddresses); updateUsage(addressEntry.getSubaddressIndex());
// confidence // confidence
lazyFieldsSupplier = Suppliers.memoize(() -> new LazyFields() {{ lazyFieldsSupplier = Suppliers.memoize(() -> new LazyFields() {{
@ -73,7 +72,7 @@ class DepositListItem {
tooltip = new Tooltip(Res.get("shared.notUsedYet")); tooltip = new Tooltip(Res.get("shared.notUsedYet"));
txConfidenceIndicator.setProgress(0); txConfidenceIndicator.setProgress(0);
txConfidenceIndicator.setTooltip(tooltip); txConfidenceIndicator.setTooltip(tooltip);
MoneroTx tx = getTxWithFewestConfirmations(cachedTxs); MoneroTx tx = getTxWithFewestConfirmations();
if (tx == null) { if (tx == null) {
txConfidenceIndicator.setVisible(false); txConfidenceIndicator.setVisible(false);
} else { } else {
@ -83,8 +82,8 @@ class DepositListItem {
}}); }});
} }
private void updateUsage(int subaddressIndex, List<MoneroTxWallet> cachedTxs, List<MoneroSubaddress> cachedSubaddresses) { private void updateUsage(int subaddressIndex) {
numTxsWithOutputs = xmrWalletService.getNumTxsWithIncomingOutputs(addressEntry.getSubaddressIndex(), cachedTxs, cachedSubaddresses); numTxsWithOutputs = xmrWalletService.getNumTxsWithIncomingOutputs(addressEntry.getSubaddressIndex());
switch (addressEntry.getContext()) { switch (addressEntry.getContext()) {
case BASE_ADDRESS: case BASE_ADDRESS:
usage = Res.get("funds.deposit.baseAddress"); usage = Res.get("funds.deposit.baseAddress");
@ -138,15 +137,15 @@ class DepositListItem {
return numTxsWithOutputs; return numTxsWithOutputs;
} }
public long getNumConfirmationsSinceFirstUsed(List<MoneroTxWallet> incomingTxs) { public long getNumConfirmationsSinceFirstUsed() {
MoneroTx tx = getTxWithFewestConfirmations(incomingTxs); MoneroTx tx = getTxWithFewestConfirmations();
return tx == null ? 0 : tx.getNumConfirmations(); return tx == null ? 0 : tx.getNumConfirmations();
} }
private MoneroTxWallet getTxWithFewestConfirmations(List<MoneroTxWallet> allIncomingTxs) { private MoneroTxWallet getTxWithFewestConfirmations() {
// get txs with incoming outputs to subaddress index // get txs with incoming outputs to subaddress index
List<MoneroTxWallet> txs = xmrWalletService.getTxsWithIncomingOutputs(addressEntry.getSubaddressIndex(), allIncomingTxs); List<MoneroTxWallet> txs = xmrWalletService.getTxsWithIncomingOutputs(addressEntry.getSubaddressIndex());
// get tx with fewest confirmations // get tx with fewest confirmations
MoneroTxWallet highestTx = null; MoneroTxWallet highestTx = null;

View file

@ -76,9 +76,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.MoneroWalletListener; import monero.wallet.model.MoneroWalletListener;
import net.glxn.qrgen.QRCode; import net.glxn.qrgen.QRCode;
import net.glxn.qrgen.image.ImageType; import net.glxn.qrgen.image.ImageType;
@ -127,7 +125,6 @@ public class DepositView extends ActivatableView<VBox, Void> {
private Subscription amountTextFieldSubscription; private Subscription amountTextFieldSubscription;
private ChangeListener<DepositListItem> tableViewSelectionListener; private ChangeListener<DepositListItem> tableViewSelectionListener;
private int gridRow = 0; private int gridRow = 0;
List<MoneroTxWallet> txsWithIncomingOutputs;
/////////////////////////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////////////////////////
// Constructor, lifecycle // Constructor, lifecycle
@ -151,15 +148,9 @@ public class DepositView extends ActivatableView<VBox, Void> {
confirmationsColumn.setGraphic(new AutoTooltipLabel(Res.get("shared.confirmations"))); confirmationsColumn.setGraphic(new AutoTooltipLabel(Res.get("shared.confirmations")));
usageColumn.setGraphic(new AutoTooltipLabel(Res.get("shared.usage"))); usageColumn.setGraphic(new AutoTooltipLabel(Res.get("shared.usage")));
// try to initialize with wallet txs
try {
// prefetch to avoid query per subaddress
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, subaddresses); try {
xmrWalletService.getFreshAddressEntry();
} 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();
@ -181,7 +172,7 @@ public class DepositView extends ActivatableView<VBox, Void> {
addressColumn.setComparator(Comparator.comparing(DepositListItem::getAddressString)); addressColumn.setComparator(Comparator.comparing(DepositListItem::getAddressString));
balanceColumn.setComparator(Comparator.comparing(DepositListItem::getBalanceAsBI)); balanceColumn.setComparator(Comparator.comparing(DepositListItem::getBalanceAsBI));
confirmationsColumn.setComparator(Comparator.comparingLong(o -> o.getNumConfirmationsSinceFirstUsed(txsWithIncomingOutputs))); confirmationsColumn.setComparator(Comparator.comparingLong(o -> o.getNumConfirmationsSinceFirstUsed()));
usageColumn.setComparator(Comparator.comparing(DepositListItem::getUsage)); usageColumn.setComparator(Comparator.comparing(DepositListItem::getUsage));
tableView.getSortOrder().add(usageColumn); tableView.getSortOrder().add(usageColumn);
tableView.setItems(sortedList); tableView.setItems(sortedList);
@ -335,16 +326,12 @@ public class DepositView extends ActivatableView<VBox, Void> {
private void updateList() { private void updateList() {
// cache incoming txs
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.isTrade()) continue; // skip reserved for trade if (addressEntry.isTrade()) continue; // skip reserved for trade
items.add(new DepositListItem(addressEntry, xmrWalletService, formatter, txsWithIncomingOutputs, subaddresses)); items.add(new DepositListItem(addressEntry, xmrWalletService, formatter));
} }
// update list // update list

View file

@ -375,6 +375,8 @@ public abstract class MutableOfferView<M extends MutableOfferViewModel<?>> exten
advancedOptionsBox.setVisible(false); advancedOptionsBox.setVisible(false);
advancedOptionsBox.setManaged(false); advancedOptionsBox.setManaged(false);
updateQrCode();
model.onShowPayFundsScreen(() -> { model.onShowPayFundsScreen(() -> {
if (!DevEnv.isDevMode()) { if (!DevEnv.isDevMode()) {
String key = "createOfferFundWalletInfo"; String key = "createOfferFundWalletInfo";
@ -755,14 +757,7 @@ public abstract class MutableOfferView<M extends MutableOfferViewModel<?>> exten
missingCoinListener = (observable, oldValue, newValue) -> { missingCoinListener = (observable, oldValue, newValue) -> {
if (!newValue.toString().equals("")) { if (!newValue.toString().equals("")) {
final byte[] imageBytes = QRCode //updateQrCode(); // disabled to avoid wallet requests on key strokes
.from(getMoneroURI())
.withSize(300, 300)
.to(ImageType.PNG)
.stream()
.toByteArray();
Image qrImage = new Image(new ByteArrayInputStream(imageBytes));
qrCodeImageView.setImage(qrImage);
} }
}; };
@ -810,6 +805,17 @@ public abstract class MutableOfferView<M extends MutableOfferViewModel<?>> exten
}); });
} }
private void updateQrCode() {
final byte[] imageBytes = QRCode
.from(getMoneroURI())
.withSize(300, 300)
.to(ImageType.PNG)
.stream()
.toByteArray();
Image qrImage = new Image(new ByteArrayInputStream(imageBytes));
qrCodeImageView.setImage(qrImage);
}
private void closeAndGoToOpenOffers() { private void closeAndGoToOpenOffers() {
//go to open offers //go to open offers
UserThread.runAfter(() -> UserThread.runAfter(() ->