diff --git a/core/src/main/java/haveno/core/trade/Trade.java b/core/src/main/java/haveno/core/trade/Trade.java index 448f364aec..e17a50c02a 100644 --- a/core/src/main/java/haveno/core/trade/Trade.java +++ b/core/src/main/java/haveno/core/trade/Trade.java @@ -73,11 +73,13 @@ import haveno.network.p2p.AckMessage; import haveno.network.p2p.NodeAddress; import haveno.network.p2p.P2PService; import javafx.beans.property.DoubleProperty; +import javafx.beans.property.IntegerProperty; import javafx.beans.property.ObjectProperty; import javafx.beans.property.ReadOnlyDoubleProperty; import javafx.beans.property.ReadOnlyObjectProperty; import javafx.beans.property.ReadOnlyStringProperty; import javafx.beans.property.SimpleDoubleProperty; +import javafx.beans.property.SimpleIntegerProperty; import javafx.beans.property.SimpleObjectProperty; import javafx.beans.property.SimpleStringProperty; import javafx.beans.property.StringProperty; @@ -408,6 +410,8 @@ public abstract class Trade implements Tradable, Model { transient final private ObjectProperty payoutStateProperty = new SimpleObjectProperty<>(payoutState); transient final private ObjectProperty disputeStateProperty = new SimpleObjectProperty<>(disputeState); transient final private ObjectProperty tradePeriodStateProperty = new SimpleObjectProperty<>(periodState); + @Getter + transient public final IntegerProperty depositTxsUpdateCounter = new SimpleIntegerProperty(0); transient final private StringProperty errorMessageProperty = new SimpleStringProperty(); transient private Subscription tradeStateSubscription; transient private Subscription tradePhaseSubscription; @@ -2028,7 +2032,7 @@ public abstract class Trade implements Tradable, Model { } private void tryInitPollingAux() { - if (!wasWalletSynced) trySyncWallet(false); + if (!wasWalletSynced) trySyncWallet(true); updatePollPeriod(); // reprocess pending payout messages @@ -2135,9 +2139,12 @@ public abstract class Trade implements Tradable, Model { syncWalletIfBehind(); // get txs from trade wallet - List txs = wallet.getTxs(new MoneroTxQuery().setIncludeOutputs(true).setInTxPool(false)); // TODO (monero-wallet-rpc): cannot get pool txs without re-refetching from pool + MoneroTxQuery query = new MoneroTxQuery().setIncludeOutputs(true); + Boolean updatePool = !isDepositsConfirmed() && (getMaker().getDepositTx() == null || getTaker().getDepositTx() == null); + if (!updatePool) query.setInTxPool(false); // avoid updating from pool if possible + List txs = wallet.getTxs(query); setDepositTxs(txs); - if (txs.size() != 2) return; // skip if either tx not seen + if (getMaker().getDepositTx() == null || getTaker().getDepositTx() == null) return; // skip if either deposit tx not seen setStateDepositsSeen(); // set actual security deposits @@ -2176,9 +2183,9 @@ public abstract class Trade implements Tradable, Model { } // get txs from trade wallet - boolean checkPool = isPayoutExpected && !isPayoutConfirmed(); MoneroTxQuery query = new MoneroTxQuery().setIncludeOutputs(true); - if (!checkPool) query.setInTxPool(false); // avoid pool check if possible + boolean updatePool = isPayoutExpected && !isPayoutConfirmed(); + if (!updatePool) query.setInTxPool(false); // avoid updating from pool if possible List txs = wallet.getTxs(query); setDepositTxs(txs); @@ -2224,6 +2231,7 @@ public abstract class Trade implements Tradable, Model { if (tx.getHash().equals(getMaker().getDepositTxHash())) getMaker().setDepositTx(tx); if (tx.getHash().equals(getTaker().getDepositTxHash())) getTaker().setDepositTx(tx); } + if (!txs.isEmpty()) depositTxsUpdateCounter.set(depositTxsUpdateCounter.get() + 1); } private void forceRestartTradeWallet() { 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 aa28314d67..6c1103b18f 100644 --- a/core/src/main/java/haveno/core/xmr/wallet/XmrWalletService.java +++ b/core/src/main/java/haveno/core/xmr/wallet/XmrWalletService.java @@ -1775,7 +1775,7 @@ public class XmrWalletService { if (wallet.getHeight() < xmrConnectionService.getTargetHeight()) wallet.sync(); // fetch transactions from pool and store to cache - // TODO: ideally wallet should sync every poll and then avoid checking pool on fetching txs + // TODO: ideally wallet should sync every poll and then avoid updating from pool on fetching txs if (updateTxs) { try { cachedTxs = wallet.getTxs(new MoneroTxQuery().setIncludeOutputs(true)); diff --git a/desktop/src/main/java/haveno/desktop/components/TxIdTextField.java b/desktop/src/main/java/haveno/desktop/components/TxIdTextField.java index 3be92dc32b..625e2d9d6b 100644 --- a/desktop/src/main/java/haveno/desktop/components/TxIdTextField.java +++ b/desktop/src/main/java/haveno/desktop/components/TxIdTextField.java @@ -23,11 +23,13 @@ import de.jensd.fx.fontawesome.AwesomeIcon; import haveno.common.UserThread; import haveno.common.util.Utilities; import haveno.core.locale.Res; +import haveno.core.trade.Trade; import haveno.core.user.BlockChainExplorer; import haveno.core.user.Preferences; import haveno.core.xmr.wallet.XmrWalletService; import haveno.desktop.components.indicator.TxConfidenceIndicator; import haveno.desktop.util.GUIUtil; +import javafx.beans.value.ChangeListener; import javafx.scene.control.Label; import javafx.scene.control.TextField; import javafx.scene.control.Tooltip; @@ -51,7 +53,9 @@ public class TxIdTextField extends AnchorPane { private final TxConfidenceIndicator txConfidenceIndicator; private final Label copyIcon, blockExplorerIcon, missingTxWarningIcon; - private MoneroWalletListener txUpdater; + private MoneroWalletListener walletListener; + private ChangeListener tradeListener; + private Trade trade; /////////////////////////////////////////////////////////////////////////////////////////// // Constructor @@ -108,9 +112,18 @@ public class TxIdTextField extends AnchorPane { } public void setup(@Nullable String txId) { - if (txUpdater != null) { - xmrWalletService.removeWalletListener(txUpdater); - txUpdater = null; + setup(txId, null); + } + + public void setup(@Nullable String txId, Trade trade) { + this.trade = trade; + if (walletListener != null) { + xmrWalletService.removeWalletListener(walletListener); + walletListener = null; + } + if (tradeListener != null) { + trade.getDepositTxsUpdateCounter().removeListener(tradeListener); + tradeListener = null; } if (txId == null) { @@ -126,15 +139,21 @@ public class TxIdTextField extends AnchorPane { return; } - // listen for tx updates - // TODO: this only listens for new blocks, listen for double spend - txUpdater = new MoneroWalletListener() { - @Override - public void onNewBlock(long lastBlockHeight) { - updateConfidence(txId, false, lastBlockHeight); - } - }; - xmrWalletService.addWalletListener(txUpdater); + // subscribe for tx updates + if (trade == null) { + walletListener = new MoneroWalletListener() { + @Override + public void onNewBlock(long height) { + updateConfidence(txId, trade, false, height); + } + }; + xmrWalletService.addWalletListener(walletListener); // TODO: this only listens for new blocks, listen for double spend + } else { + tradeListener = (observable, oldValue, newValue) -> { + updateConfidence(txId, trade, null, null); + }; + trade.getDepositTxsUpdateCounter().addListener(tradeListener); + } textField.setText(txId); textField.setOnMouseClicked(mouseEvent -> openBlockExplorer(txId)); @@ -143,14 +162,19 @@ public class TxIdTextField extends AnchorPane { txConfidenceIndicator.setVisible(true); // update off main thread - new Thread(() -> updateConfidence(txId, true, null)).start(); + new Thread(() -> updateConfidence(txId, trade, true, null)).start(); } public void cleanup() { - if (xmrWalletService != null && txUpdater != null) { - xmrWalletService.removeWalletListener(txUpdater); - txUpdater = null; + if (xmrWalletService != null && walletListener != null) { + xmrWalletService.removeWalletListener(walletListener); + walletListener = null; } + if (tradeListener != null) { + trade.getDepositTxsUpdateCounter().removeListener(tradeListener); + tradeListener = null; + } + trade = null; textField.setOnMouseClicked(null); blockExplorerIcon.setOnMouseClicked(null); copyIcon.setOnMouseClicked(null); @@ -168,11 +192,16 @@ public class TxIdTextField extends AnchorPane { } } - private synchronized void updateConfidence(String txId, boolean useCache, Long height) { + private synchronized void updateConfidence(String txId, Trade trade, Boolean useCache, Long height) { MoneroTx tx = null; try { - tx = useCache ? xmrWalletService.getDaemonTxWithCache(txId) : xmrWalletService.getDaemonTx(txId); - tx.setNumConfirmations(tx.isConfirmed() ? (height == null ? xmrWalletService.getConnectionService().getLastInfo().getHeight() : height) - tx.getHeight(): 0l); // TODO: don't set if tx.getNumConfirmations() works reliably on non-local testnet + if (trade == null) { + tx = useCache ? xmrWalletService.getDaemonTxWithCache(txId) : xmrWalletService.getDaemonTx(txId); + tx.setNumConfirmations(tx.isConfirmed() ? (height == null ? xmrWalletService.getConnectionService().getLastInfo().getHeight() : height) - tx.getHeight(): 0l); // TODO: don't set if tx.getNumConfirmations() works reliably on non-local testnet + } else { + if (txId.equals(trade.getMaker().getDepositTxHash())) tx = trade.getMaker().getDepositTx(); + else if (txId.equals(trade.getTaker().getDepositTxHash())) tx = trade.getTaker().getDepositTx(); + } } catch (Exception e) { // do nothing } @@ -185,9 +214,9 @@ public class TxIdTextField extends AnchorPane { if (txConfidenceIndicator.getProgress() != 0) { AnchorPane.setRightAnchor(txConfidenceIndicator, 0.0); } - if (txConfidenceIndicator.getProgress() >= 1.0 && txUpdater != null) { - xmrWalletService.removeWalletListener(txUpdater); // unregister listener - txUpdater = null; + if (txConfidenceIndicator.getProgress() >= 1.0 && walletListener != null) { + xmrWalletService.removeWalletListener(walletListener); // unregister listener + walletListener = null; } }); } diff --git a/desktop/src/main/java/haveno/desktop/main/portfolio/pendingtrades/PendingTradesView.java b/desktop/src/main/java/haveno/desktop/main/portfolio/pendingtrades/PendingTradesView.java index 0bde7cfd70..5157c9b4c5 100644 --- a/desktop/src/main/java/haveno/desktop/main/portfolio/pendingtrades/PendingTradesView.java +++ b/desktop/src/main/java/haveno/desktop/main/portfolio/pendingtrades/PendingTradesView.java @@ -325,8 +325,10 @@ public class PendingTradesView extends ActivatableViewAndModel { - if (selectedItem != null && !selectedItem.equals(model.dataModel.selectedItemProperty.get())) + if (selectedItem != null && !selectedItem.equals(model.dataModel.selectedItemProperty.get())) { + if (selectedSubView != null) selectedSubView.deactivate(); model.dataModel.onSelectItem(selectedItem); + } }); updateTableSelection(); diff --git a/desktop/src/main/java/haveno/desktop/main/portfolio/pendingtrades/steps/TradeStepView.java b/desktop/src/main/java/haveno/desktop/main/portfolio/pendingtrades/steps/TradeStepView.java index 1831978d61..32ac5895ab 100644 --- a/desktop/src/main/java/haveno/desktop/main/portfolio/pendingtrades/steps/TradeStepView.java +++ b/desktop/src/main/java/haveno/desktop/main/portfolio/pendingtrades/steps/TradeStepView.java @@ -52,8 +52,6 @@ import haveno.desktop.util.Layout; import haveno.network.p2p.BootstrapListener; import java.time.Duration; import java.time.Instant; -import java.util.ArrayList; -import java.util.List; import java.util.Optional; import javafx.application.Platform; import javafx.beans.property.BooleanProperty; @@ -110,8 +108,6 @@ public abstract class TradeStepView extends AnchorPane { trade = model.dataModel.getTrade(); checkNotNull(trade, "Trade must not be null at TradeStepView"); - startCachingTxs(); - ScrollPane scrollPane = new ScrollPane(); scrollPane.setHbarPolicy(ScrollPane.ScrollBarPolicy.NEVER); scrollPane.setVbarPolicy(ScrollPane.ScrollBarPolicy.AS_NEEDED); @@ -167,13 +163,6 @@ public abstract class TradeStepView extends AnchorPane { // }; } - private void startCachingTxs() { - List txIds = new ArrayList(); - if (!model.dataModel.makerTxId.isEmpty().get()) txIds.add(model.dataModel.makerTxId.get()); - if (!model.dataModel.takerTxId.isEmpty().get()) txIds.add(model.dataModel.takerTxId.get()); - new Thread(() -> trade.getXmrWalletService().getDaemonTxsWithCache(txIds)).start(); - } - public void activate() { if (selfTxIdTextField != null) { if (selfTxIdSubscription != null) @@ -181,8 +170,7 @@ public abstract class TradeStepView extends AnchorPane { selfTxIdSubscription = EasyBind.subscribe(model.dataModel.isMaker() ? model.dataModel.makerTxId : model.dataModel.takerTxId, id -> { if (!id.isEmpty()) { - startCachingTxs(); - selfTxIdTextField.setup(id); + selfTxIdTextField.setup(id, trade); } else { selfTxIdTextField.cleanup(); } @@ -194,8 +182,7 @@ public abstract class TradeStepView extends AnchorPane { peerTxIdSubscription = EasyBind.subscribe(model.dataModel.isMaker() ? model.dataModel.takerTxId : model.dataModel.makerTxId, id -> { if (!id.isEmpty()) { - startCachingTxs(); - peerTxIdTextField.setup(id); + peerTxIdTextField.setup(id, trade); } else { peerTxIdTextField.cleanup(); } @@ -350,7 +337,7 @@ public abstract class TradeStepView extends AnchorPane { String selfTxId = model.dataModel.isMaker() ? model.dataModel.makerTxId.get() : model.dataModel.takerTxId.get(); if (!selfTxId.isEmpty()) - selfTxIdTextField.setup(selfTxId); + selfTxIdTextField.setup(selfTxId, trade); else selfTxIdTextField.cleanup(); @@ -364,7 +351,7 @@ public abstract class TradeStepView extends AnchorPane { String peerTxId = model.dataModel.isMaker() ? model.dataModel.takerTxId.get() : model.dataModel.makerTxId.get(); if (!peerTxId.isEmpty()) - peerTxIdTextField.setup(peerTxId); + peerTxIdTextField.setup(peerTxId, trade); else peerTxIdTextField.cleanup(); diff --git a/desktop/src/main/java/haveno/desktop/util/GUIUtil.java b/desktop/src/main/java/haveno/desktop/util/GUIUtil.java index 883fb02924..b780698989 100644 --- a/desktop/src/main/java/haveno/desktop/util/GUIUtil.java +++ b/desktop/src/main/java/haveno/desktop/util/GUIUtil.java @@ -528,18 +528,20 @@ public class GUIUtil { public static void updateConfidence(MoneroTx tx, Tooltip tooltip, TxConfidenceIndicator txConfidenceIndicator) { - if (tx != null && (tx.getNumConfirmations() == null || !tx.isRelayed())) { + if (tx == null || tx.getNumConfirmations() == null || !tx.isRelayed()) { tooltip.setText(Res.get("confidence.unknown")); - txConfidenceIndicator.setProgress(0); - } else if (tx != null && tx.isFailed()) { - tooltip.setText(Res.get("confidence.invalid")); - txConfidenceIndicator.setProgress(0); - } else if (tx != null && tx.isConfirmed()) { - tooltip.setText(Res.get("confidence.confirmed", tx.getNumConfirmations())); - txConfidenceIndicator.setProgress((double) tx.getNumConfirmations() / (double) XmrWalletService.NUM_BLOCKS_UNLOCK); + txConfidenceIndicator.setProgress(-1); } else { - tooltip.setText(Res.get("confidence.seen", 0)); // TODO: replace with numBroadcastPeers - txConfidenceIndicator.setProgress(-1.0); + if (tx.isFailed()) { + tooltip.setText(Res.get("confidence.invalid")); + txConfidenceIndicator.setProgress(0); + } else if (tx.isConfirmed()) { + tooltip.setText(Res.get("confidence.confirmed", tx.getNumConfirmations())); + txConfidenceIndicator.setProgress((double) tx.getNumConfirmations() / (double) XmrWalletService.NUM_BLOCKS_UNLOCK); + } else { + tooltip.setText(Res.get("confidence.seen", 0)); + txConfidenceIndicator.setProgress(-1); + } } txConfidenceIndicator.setPrefSize(24, 24);