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.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<PayoutState> payoutStateProperty = new SimpleObjectProperty<>(payoutState);
transient final private ObjectProperty<DisputeState> disputeStateProperty = new SimpleObjectProperty<>(disputeState);
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 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<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);
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<MoneroTxWallet> 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() {

View file

@ -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));

View file

@ -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<Number> 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;
}
});
}

View file

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

View file

@ -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<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() {
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();

View file

@ -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);