set deposit tx confirmations from wallet instead of daemon request

This commit is contained in:
woodser 2024-04-21 12:42:02 -04:00
parent f0862b7aeb
commit adccf27385
6 changed files with 85 additions and 57 deletions

View file

@ -73,11 +73,13 @@ import haveno.network.p2p.AckMessage;
import haveno.network.p2p.NodeAddress; import haveno.network.p2p.NodeAddress;
import haveno.network.p2p.P2PService; import haveno.network.p2p.P2PService;
import javafx.beans.property.DoubleProperty; import javafx.beans.property.DoubleProperty;
import javafx.beans.property.IntegerProperty;
import javafx.beans.property.ObjectProperty; import javafx.beans.property.ObjectProperty;
import javafx.beans.property.ReadOnlyDoubleProperty; import javafx.beans.property.ReadOnlyDoubleProperty;
import javafx.beans.property.ReadOnlyObjectProperty; import javafx.beans.property.ReadOnlyObjectProperty;
import javafx.beans.property.ReadOnlyStringProperty; import javafx.beans.property.ReadOnlyStringProperty;
import javafx.beans.property.SimpleDoubleProperty; import javafx.beans.property.SimpleDoubleProperty;
import javafx.beans.property.SimpleIntegerProperty;
import javafx.beans.property.SimpleObjectProperty; import javafx.beans.property.SimpleObjectProperty;
import javafx.beans.property.SimpleStringProperty; import javafx.beans.property.SimpleStringProperty;
import javafx.beans.property.StringProperty; import javafx.beans.property.StringProperty;
@ -408,6 +410,8 @@ public abstract class Trade implements Tradable, Model {
transient final private ObjectProperty<PayoutState> payoutStateProperty = new SimpleObjectProperty<>(payoutState); transient final private ObjectProperty<PayoutState> payoutStateProperty = new SimpleObjectProperty<>(payoutState);
transient final private ObjectProperty<DisputeState> disputeStateProperty = new SimpleObjectProperty<>(disputeState); transient final private ObjectProperty<DisputeState> disputeStateProperty = new SimpleObjectProperty<>(disputeState);
transient final private ObjectProperty<TradePeriodState> tradePeriodStateProperty = new SimpleObjectProperty<>(periodState); transient final private ObjectProperty<TradePeriodState> tradePeriodStateProperty = new SimpleObjectProperty<>(periodState);
@Getter
transient public final IntegerProperty depositTxsUpdateCounter = new SimpleIntegerProperty(0);
transient final private StringProperty errorMessageProperty = new SimpleStringProperty(); transient final private StringProperty errorMessageProperty = new SimpleStringProperty();
transient private Subscription tradeStateSubscription; transient private Subscription tradeStateSubscription;
transient private Subscription tradePhaseSubscription; transient private Subscription tradePhaseSubscription;
@ -2028,7 +2032,7 @@ public abstract class Trade implements Tradable, Model {
} }
private void tryInitPollingAux() { private void tryInitPollingAux() {
if (!wasWalletSynced) trySyncWallet(false); if (!wasWalletSynced) trySyncWallet(true);
updatePollPeriod(); updatePollPeriod();
// reprocess pending payout messages // reprocess pending payout messages
@ -2135,9 +2139,12 @@ public abstract class Trade implements Tradable, Model {
syncWalletIfBehind(); syncWalletIfBehind();
// get txs from trade wallet // get txs from trade wallet
List<MoneroTxWallet> 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<MoneroTxWallet> txs = wallet.getTxs(query);
setDepositTxs(txs); 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(); setStateDepositsSeen();
// set actual security deposits // set actual security deposits
@ -2176,9 +2183,9 @@ public abstract class Trade implements Tradable, Model {
} }
// get txs from trade wallet // get txs from trade wallet
boolean checkPool = isPayoutExpected && !isPayoutConfirmed();
MoneroTxQuery query = new MoneroTxQuery().setIncludeOutputs(true); 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<MoneroTxWallet> txs = wallet.getTxs(query); List<MoneroTxWallet> txs = wallet.getTxs(query);
setDepositTxs(txs); 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(getMaker().getDepositTxHash())) getMaker().setDepositTx(tx);
if (tx.getHash().equals(getTaker().getDepositTxHash())) getTaker().setDepositTx(tx); if (tx.getHash().equals(getTaker().getDepositTxHash())) getTaker().setDepositTx(tx);
} }
if (!txs.isEmpty()) depositTxsUpdateCounter.set(depositTxsUpdateCounter.get() + 1);
} }
private void forceRestartTradeWallet() { private void forceRestartTradeWallet() {

View file

@ -1775,7 +1775,7 @@ public class XmrWalletService {
if (wallet.getHeight() < xmrConnectionService.getTargetHeight()) wallet.sync(); if (wallet.getHeight() < xmrConnectionService.getTargetHeight()) wallet.sync();
// fetch transactions from pool and store to cache // 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) { if (updateTxs) {
try { try {
cachedTxs = wallet.getTxs(new MoneroTxQuery().setIncludeOutputs(true)); cachedTxs = wallet.getTxs(new MoneroTxQuery().setIncludeOutputs(true));

View file

@ -23,11 +23,13 @@ import de.jensd.fx.fontawesome.AwesomeIcon;
import haveno.common.UserThread; import haveno.common.UserThread;
import haveno.common.util.Utilities; import haveno.common.util.Utilities;
import haveno.core.locale.Res; import haveno.core.locale.Res;
import haveno.core.trade.Trade;
import haveno.core.user.BlockChainExplorer; import haveno.core.user.BlockChainExplorer;
import haveno.core.user.Preferences; import haveno.core.user.Preferences;
import haveno.core.xmr.wallet.XmrWalletService; import haveno.core.xmr.wallet.XmrWalletService;
import haveno.desktop.components.indicator.TxConfidenceIndicator; import haveno.desktop.components.indicator.TxConfidenceIndicator;
import haveno.desktop.util.GUIUtil; import haveno.desktop.util.GUIUtil;
import javafx.beans.value.ChangeListener;
import javafx.scene.control.Label; import javafx.scene.control.Label;
import javafx.scene.control.TextField; import javafx.scene.control.TextField;
import javafx.scene.control.Tooltip; import javafx.scene.control.Tooltip;
@ -51,7 +53,9 @@ public class TxIdTextField extends AnchorPane {
private final TxConfidenceIndicator txConfidenceIndicator; private final TxConfidenceIndicator txConfidenceIndicator;
private final Label copyIcon, blockExplorerIcon, missingTxWarningIcon; private final Label copyIcon, blockExplorerIcon, missingTxWarningIcon;
private MoneroWalletListener txUpdater; private MoneroWalletListener walletListener;
private ChangeListener<Number> tradeListener;
private Trade trade;
/////////////////////////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////////////////////////
// Constructor // Constructor
@ -108,9 +112,18 @@ public class TxIdTextField extends AnchorPane {
} }
public void setup(@Nullable String txId) { public void setup(@Nullable String txId) {
if (txUpdater != null) { setup(txId, null);
xmrWalletService.removeWalletListener(txUpdater); }
txUpdater = 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) { if (txId == null) {
@ -126,15 +139,21 @@ public class TxIdTextField extends AnchorPane {
return; return;
} }
// listen for tx updates // subscribe for tx updates
// TODO: this only listens for new blocks, listen for double spend if (trade == null) {
txUpdater = new MoneroWalletListener() { walletListener = new MoneroWalletListener() {
@Override @Override
public void onNewBlock(long lastBlockHeight) { public void onNewBlock(long height) {
updateConfidence(txId, false, lastBlockHeight); updateConfidence(txId, trade, false, height);
} }
}; };
xmrWalletService.addWalletListener(txUpdater); 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.setText(txId);
textField.setOnMouseClicked(mouseEvent -> openBlockExplorer(txId)); textField.setOnMouseClicked(mouseEvent -> openBlockExplorer(txId));
@ -143,14 +162,19 @@ public class TxIdTextField extends AnchorPane {
txConfidenceIndicator.setVisible(true); txConfidenceIndicator.setVisible(true);
// update off main thread // update off main thread
new Thread(() -> updateConfidence(txId, true, null)).start(); new Thread(() -> updateConfidence(txId, trade, true, null)).start();
} }
public void cleanup() { public void cleanup() {
if (xmrWalletService != null && txUpdater != null) { if (xmrWalletService != null && walletListener != null) {
xmrWalletService.removeWalletListener(txUpdater); xmrWalletService.removeWalletListener(walletListener);
txUpdater = null; walletListener = null;
} }
if (tradeListener != null) {
trade.getDepositTxsUpdateCounter().removeListener(tradeListener);
tradeListener = null;
}
trade = null;
textField.setOnMouseClicked(null); textField.setOnMouseClicked(null);
blockExplorerIcon.setOnMouseClicked(null); blockExplorerIcon.setOnMouseClicked(null);
copyIcon.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; MoneroTx tx = null;
try { try {
tx = useCache ? xmrWalletService.getDaemonTxWithCache(txId) : xmrWalletService.getDaemonTx(txId); if (trade == null) {
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 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) { } catch (Exception e) {
// do nothing // do nothing
} }
@ -185,9 +214,9 @@ public class TxIdTextField extends AnchorPane {
if (txConfidenceIndicator.getProgress() != 0) { if (txConfidenceIndicator.getProgress() != 0) {
AnchorPane.setRightAnchor(txConfidenceIndicator, 0.0); AnchorPane.setRightAnchor(txConfidenceIndicator, 0.0);
} }
if (txConfidenceIndicator.getProgress() >= 1.0 && txUpdater != null) { if (txConfidenceIndicator.getProgress() >= 1.0 && walletListener != null) {
xmrWalletService.removeWalletListener(txUpdater); // unregister listener xmrWalletService.removeWalletListener(walletListener); // unregister listener
txUpdater = null; walletListener = null;
} }
}); });
} }

View file

@ -325,8 +325,10 @@ public class PendingTradesView extends ActivatableViewAndModel<VBox, PendingTrad
selectedTableItemSubscription = EasyBind.subscribe(tableView.getSelectionModel().selectedItemProperty(), selectedTableItemSubscription = EasyBind.subscribe(tableView.getSelectionModel().selectedItemProperty(),
selectedItem -> { selectedItem -> {
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); model.dataModel.onSelectItem(selectedItem);
}
}); });
updateTableSelection(); updateTableSelection();

View file

@ -52,8 +52,6 @@ import haveno.desktop.util.Layout;
import haveno.network.p2p.BootstrapListener; import haveno.network.p2p.BootstrapListener;
import java.time.Duration; import java.time.Duration;
import java.time.Instant; import java.time.Instant;
import java.util.ArrayList;
import java.util.List;
import java.util.Optional; import java.util.Optional;
import javafx.application.Platform; import javafx.application.Platform;
import javafx.beans.property.BooleanProperty; import javafx.beans.property.BooleanProperty;
@ -110,8 +108,6 @@ public abstract class TradeStepView extends AnchorPane {
trade = model.dataModel.getTrade(); trade = model.dataModel.getTrade();
checkNotNull(trade, "Trade must not be null at TradeStepView"); checkNotNull(trade, "Trade must not be null at TradeStepView");
startCachingTxs();
ScrollPane scrollPane = new ScrollPane(); ScrollPane scrollPane = new ScrollPane();
scrollPane.setHbarPolicy(ScrollPane.ScrollBarPolicy.NEVER); scrollPane.setHbarPolicy(ScrollPane.ScrollBarPolicy.NEVER);
scrollPane.setVbarPolicy(ScrollPane.ScrollBarPolicy.AS_NEEDED); scrollPane.setVbarPolicy(ScrollPane.ScrollBarPolicy.AS_NEEDED);
@ -167,13 +163,6 @@ public abstract class TradeStepView extends AnchorPane {
// }; // };
} }
private void startCachingTxs() {
List<String> txIds = new ArrayList<String>();
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() { public void activate() {
if (selfTxIdTextField != null) { if (selfTxIdTextField != null) {
if (selfTxIdSubscription != 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 -> { selfTxIdSubscription = EasyBind.subscribe(model.dataModel.isMaker() ? model.dataModel.makerTxId : model.dataModel.takerTxId, id -> {
if (!id.isEmpty()) { if (!id.isEmpty()) {
startCachingTxs(); selfTxIdTextField.setup(id, trade);
selfTxIdTextField.setup(id);
} else { } else {
selfTxIdTextField.cleanup(); 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 -> { peerTxIdSubscription = EasyBind.subscribe(model.dataModel.isMaker() ? model.dataModel.takerTxId : model.dataModel.makerTxId, id -> {
if (!id.isEmpty()) { if (!id.isEmpty()) {
startCachingTxs(); peerTxIdTextField.setup(id, trade);
peerTxIdTextField.setup(id);
} else { } else {
peerTxIdTextField.cleanup(); 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(); String selfTxId = model.dataModel.isMaker() ? model.dataModel.makerTxId.get() : model.dataModel.takerTxId.get();
if (!selfTxId.isEmpty()) if (!selfTxId.isEmpty())
selfTxIdTextField.setup(selfTxId); selfTxIdTextField.setup(selfTxId, trade);
else else
selfTxIdTextField.cleanup(); 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(); String peerTxId = model.dataModel.isMaker() ? model.dataModel.takerTxId.get() : model.dataModel.makerTxId.get();
if (!peerTxId.isEmpty()) if (!peerTxId.isEmpty())
peerTxIdTextField.setup(peerTxId); peerTxIdTextField.setup(peerTxId, trade);
else else
peerTxIdTextField.cleanup(); peerTxIdTextField.cleanup();

View file

@ -528,18 +528,20 @@ public class GUIUtil {
public static void updateConfidence(MoneroTx tx, public static void updateConfidence(MoneroTx tx,
Tooltip tooltip, Tooltip tooltip,
TxConfidenceIndicator txConfidenceIndicator) { TxConfidenceIndicator txConfidenceIndicator) {
if (tx != null && (tx.getNumConfirmations() == null || !tx.isRelayed())) { if (tx == null || tx.getNumConfirmations() == null || !tx.isRelayed()) {
tooltip.setText(Res.get("confidence.unknown")); tooltip.setText(Res.get("confidence.unknown"));
txConfidenceIndicator.setProgress(0); txConfidenceIndicator.setProgress(-1);
} 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);
} else { } else {
tooltip.setText(Res.get("confidence.seen", 0)); // TODO: replace with numBroadcastPeers if (tx.isFailed()) {
txConfidenceIndicator.setProgress(-1.0); 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); txConfidenceIndicator.setPrefSize(24, 24);