Skip to content

Commit

Permalink
refactor syncing wallet with progress and switching connections
Browse files Browse the repository at this point in the history
  • Loading branch information
woodser committed Aug 3, 2024
1 parent 8126c84 commit 4872d2c
Show file tree
Hide file tree
Showing 12 changed files with 233 additions and 140 deletions.
2 changes: 1 addition & 1 deletion core/src/main/java/haveno/core/api/CoreWalletsService.java
Original file line number Diff line number Diff line change
Expand Up @@ -178,7 +178,7 @@ String relayXmrTx(String metadata) {
verifyWalletsAreAvailable();
verifyEncryptedWalletIsUnlocked();
try {
return xmrWalletService.getWallet().relayTx(metadata);
return xmrWalletService.relayTx(metadata);
} catch (Exception ex) {
log.error("", ex);
throw new IllegalStateException(ex);
Expand Down
14 changes: 12 additions & 2 deletions core/src/main/java/haveno/core/api/XmrConnectionService.java
Original file line number Diff line number Diff line change
Expand Up @@ -273,17 +273,27 @@ private void switchToBestConnection() {
}

public synchronized boolean requestSwitchToNextBestConnection() {
log.warn("Requesting switch to next best monerod, current monerod={}", getConnection() == null ? null : getConnection().getUri());
return requestSwitchToNextBestConnection(null);
}

public synchronized boolean requestSwitchToNextBestConnection(MoneroRpcConnection sourceConnection) {
log.warn("Requesting switch to next best monerod, source monerod={}", sourceConnection == null ? getConnection() == null ? null : getConnection().getUri() : sourceConnection.getUri());

// skip if shut down started
if (isShutDownStarted) {
log.warn("Skipping switch to next best Monero connection because shut down has started");
return false;
}

// skip if connection is already switched
if (sourceConnection != null && sourceConnection != getConnection()) {
log.warn("Skipping switch to next best Monero connection because source connection is not current connection");
return false;
}

// skip if connection is fixed
if (isFixedConnection() || !connectionManager.getAutoSwitch()) {
log.info("Skipping switch to next best Monero connection because connection is fixed or auto switch is disabled");
log.warn("Skipping switch to next best Monero connection because connection is fixed or auto switch is disabled");
return false;
}

Expand Down
58 changes: 30 additions & 28 deletions core/src/main/java/haveno/core/app/WalletAppSetup.java
Original file line number Diff line number Diff line change
Expand Up @@ -132,42 +132,44 @@ void init(@Nullable Consumer<String> chainFileLockedExceptionHandler,
(numConnectionUpdates, walletDownloadPercentage, walletHeight, exception, errorMsg) -> {
String result;
if (exception == null && errorMsg == null) {

// update wallet sync progress
double walletDownloadPercentageD = (double) walletDownloadPercentage;
xmrWalletSyncProgress.set(walletDownloadPercentageD);
Long bestWalletHeight = walletHeight == null ? null : (Long) walletHeight;
String walletHeightAsString = bestWalletHeight != null && bestWalletHeight > 0 ? String.valueOf(bestWalletHeight) : "";
if (walletDownloadPercentageD == 1) {
String synchronizedWith = Res.get("mainView.footer.xmrInfo.syncedWith", getXmrWalletNetworkAsString(), walletHeightAsString);
String feeInfo = ""; // TODO: feeService.isFeeAvailable() returns true, disable
result = Res.get("mainView.footer.xmrInfo", synchronizedWith, feeInfo);
getXmrSplashSyncIconId().set("image-connection-synced");
downloadCompleteHandler.run();
} else if (walletDownloadPercentageD > 0) {
String synchronizingWith = Res.get("mainView.footer.xmrInfo.synchronizingWalletWith", getXmrWalletNetworkAsString(), walletHeightAsString, FormattingUtils.formatToRoundedPercentWithSymbol(walletDownloadPercentageD));
result = Res.get("mainView.footer.xmrInfo", synchronizingWith, "");
getXmrSplashSyncIconId().set(""); // clear synced icon
} else {

// update daemon sync progress
double chainDownloadPercentageD = xmrConnectionService.downloadPercentageProperty().doubleValue();

// update daemon sync progress
double chainDownloadPercentageD = xmrConnectionService.downloadPercentageProperty().doubleValue();
Long bestChainHeight = xmrConnectionService.chainHeightProperty().get();
String chainHeightAsString = bestChainHeight != null && bestChainHeight > 0 ? String.valueOf(bestChainHeight) : "";
if (chainDownloadPercentageD < 1) {
xmrDaemonSyncProgress.set(chainDownloadPercentageD);
Long bestChainHeight = xmrConnectionService.chainHeightProperty().get();
String chainHeightAsString = bestChainHeight != null && bestChainHeight > 0 ? String.valueOf(bestChainHeight) : "";
if (chainDownloadPercentageD == 1) {
String synchronizedWith = Res.get("mainView.footer.xmrInfo.connectedTo", getXmrDaemonNetworkAsString(), chainHeightAsString);
String feeInfo = ""; // TODO: feeService.isFeeAvailable() returns true, disable
result = Res.get("mainView.footer.xmrInfo", synchronizedWith, feeInfo);
getXmrSplashSyncIconId().set("image-connection-synced");
} else if (chainDownloadPercentageD > 0.0) {
if (chainDownloadPercentageD > 0.0) {
String synchronizingWith = Res.get("mainView.footer.xmrInfo.synchronizingWith", getXmrDaemonNetworkAsString(), chainHeightAsString, FormattingUtils.formatToRoundedPercentWithSymbol(chainDownloadPercentageD));
result = Res.get("mainView.footer.xmrInfo", synchronizingWith, "");
} else {
result = Res.get("mainView.footer.xmrInfo",
Res.get("mainView.footer.xmrInfo.connectingTo"),
getXmrDaemonNetworkAsString());
}
} else {

// update wallet sync progress
double walletDownloadPercentageD = (double) walletDownloadPercentage;
xmrWalletSyncProgress.set(walletDownloadPercentageD);
Long bestWalletHeight = walletHeight == null ? null : (Long) walletHeight;
String walletHeightAsString = bestWalletHeight != null && bestWalletHeight > 0 ? String.valueOf(bestWalletHeight) : "";
if (walletDownloadPercentageD == 1) {
String synchronizedWith = Res.get("mainView.footer.xmrInfo.syncedWith", getXmrWalletNetworkAsString(), walletHeightAsString);
String feeInfo = ""; // TODO: feeService.isFeeAvailable() returns true, disable
result = Res.get("mainView.footer.xmrInfo", synchronizedWith, feeInfo);
getXmrSplashSyncIconId().set("image-connection-synced");
downloadCompleteHandler.run();
} else if (walletDownloadPercentageD >= 0) {
String synchronizingWith = Res.get("mainView.footer.xmrInfo.synchronizingWalletWith", getXmrWalletNetworkAsString(), walletHeightAsString, FormattingUtils.formatToRoundedPercentWithSymbol(walletDownloadPercentageD));
result = Res.get("mainView.footer.xmrInfo", synchronizingWith, "");
getXmrSplashSyncIconId().set(""); // clear synced icon
} else {
String synchronizedWith = Res.get("mainView.footer.xmrInfo.connectedTo", getXmrDaemonNetworkAsString(), chainHeightAsString);
String feeInfo = ""; // TODO: feeService.isFeeAvailable() returns true, disable
result = Res.get("mainView.footer.xmrInfo", synchronizedWith, feeInfo);
getXmrSplashSyncIconId().set("image-connection-synced");
}
}
} else {
result = Res.get("mainView.footer.xmrInfo",
Expand Down
5 changes: 4 additions & 1 deletion core/src/main/java/haveno/core/offer/OpenOfferManager.java
Original file line number Diff line number Diff line change
Expand Up @@ -110,6 +110,7 @@
import javafx.collections.ObservableList;
import javax.annotation.Nullable;
import lombok.Getter;
import monero.common.MoneroRpcConnection;
import monero.daemon.model.MoneroKeyImageSpentStatus;
import monero.daemon.model.MoneroTx;
import monero.wallet.model.MoneroIncomingTransfer;
Expand Down Expand Up @@ -1089,6 +1090,7 @@ private MoneroTxWallet splitAndSchedule(OpenOffer openOffer) {
synchronized (HavenoUtils.getWalletFunctionLock()) {
long startTime = System.currentTimeMillis();
for (int i = 0; i < TradeProtocol.MAX_ATTEMPTS; i++) {
MoneroRpcConnection sourceConnection = xmrConnectionService.getConnection();
try {
log.info("Creating split output tx to fund offer {} at subaddress {}", openOffer.getShortId(), entry.getSubaddressIndex());
splitOutputTx = xmrWalletService.createTx(new MoneroTxConfig()
Expand All @@ -1101,8 +1103,9 @@ private MoneroTxWallet splitAndSchedule(OpenOffer openOffer) {
} catch (Exception e) {
if (e.getMessage().contains("not enough")) throw e; // do not retry if not enough funds
log.warn("Error creating split output tx to fund offer, offerId={}, subaddress={}, attempt={}/{}, error={}", openOffer.getShortId(), entry.getSubaddressIndex(), i + 1, TradeProtocol.MAX_ATTEMPTS, e.getMessage());
if (xmrConnectionService.isConnected()) xmrWalletService.requestSwitchToNextBestConnection(sourceConnection);
if (HavenoUtils.isUnresponsive(e)) xmrWalletService.forceRestartMainWallet();
if (stopped || i == TradeProtocol.MAX_ATTEMPTS - 1) throw e;
if (xmrConnectionService.isConnected()) xmrWalletService.requestSwitchToNextBestConnection();
HavenoUtils.waitFor(TradeProtocol.REPROCESS_DELAY_MS); // wait before retrying
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@
import haveno.core.xmr.model.XmrAddressEntry;
import haveno.core.xmr.wallet.XmrWalletService;
import lombok.extern.slf4j.Slf4j;
import monero.common.MoneroRpcConnection;
import monero.daemon.model.MoneroOutput;
import monero.wallet.model.MoneroTxWallet;

Expand Down Expand Up @@ -82,14 +83,17 @@ protected void run() {
try {
synchronized (HavenoUtils.getWalletFunctionLock()) {
for (int i = 0; i < TradeProtocol.MAX_ATTEMPTS; i++) {
MoneroRpcConnection sourceConnection = model.getXmrWalletService().getConnectionService().getConnection();
try {
//if (true) throw new RuntimeException("Pretend error");
reserveTx = model.getXmrWalletService().createReserveTx(penaltyFee, makerFee, sendAmount, securityDeposit, returnAddress, openOffer.isReserveExactAmount(), preferredSubaddressIndex);
} catch (Exception e) {
log.warn("Error creating reserve tx, offerId={}, attempt={}/{}, error={}", i + 1, TradeProtocol.MAX_ATTEMPTS, openOffer.getShortId(), e.getMessage());
if (model.getXmrWalletService().getConnectionService().isConnected()) model.getXmrWalletService().requestSwitchToNextBestConnection(sourceConnection);
if (HavenoUtils.isUnresponsive(e)) model.getXmrWalletService().forceRestartMainWallet();
verifyPending();
if (i == TradeProtocol.MAX_ATTEMPTS - 1) throw e;
model.getProtocol().startTimeoutTimer(); // reset protocol timeout
if (model.getXmrWalletService().getConnectionService().isConnected()) model.getXmrWalletService().requestSwitchToNextBestConnection();
HavenoUtils.waitFor(TradeProtocol.REPROCESS_DELAY_MS); // wait before retrying
}

Expand Down Expand Up @@ -129,7 +133,11 @@ protected void run() {
}
}

public void verifyPending() {
if (!model.getOpenOffer().isPending()) throw new RuntimeException("Offer " + model.getOpenOffer().getOffer().getId() + " is canceled");
private boolean isPending() {
return model.getOpenOffer().isPending();
}

private void verifyPending() {
if (!isPending()) throw new RuntimeException("Offer " + model.getOpenOffer().getOffer().getId() + " is canceled");
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,7 @@
import haveno.network.p2p.network.Connection;
import haveno.network.p2p.network.MessageListener;
import lombok.extern.slf4j.Slf4j;
import monero.common.MoneroRpcConnection;
import monero.wallet.MoneroWallet;
import monero.wallet.model.MoneroDestination;
import monero.wallet.model.MoneroMultisigSignResult;
Expand Down Expand Up @@ -501,6 +502,7 @@ private MoneroTxSet signAndPublishDisputePayoutTx(Trade trade) {

// submit fully signed payout tx to the network
for (int i = 0; i < TradeProtocol.MAX_ATTEMPTS; i++) {
MoneroRpcConnection sourceConnection = xmrConnectionService.getConnection();
try {
List<String> txHashes = multisigWallet.submitMultisigTxHex(disputeTxSet.getMultisigTxHex());
disputeTxSet.getTxs().get(0).setHash(txHashes.get(0)); // manually update hash which is known after signed
Expand All @@ -509,7 +511,7 @@ private MoneroTxSet signAndPublishDisputePayoutTx(Trade trade) {
if (trade.isPayoutPublished()) throw new IllegalStateException("Payout tx already published for " + trade.getClass().getSimpleName() + " " + trade.getShortId());
log.warn("Failed to submit dispute payout tx, tradeId={}, attempt={}/{}, error={}", trade.getShortId(), i + 1, TradeProtocol.MAX_ATTEMPTS, e.getMessage());
if (i == TradeProtocol.MAX_ATTEMPTS - 1) throw e;
if (trade.getXmrConnectionService().isConnected()) trade.requestSwitchToNextBestConnection();
if (trade.getXmrConnectionService().isConnected()) trade.requestSwitchToNextBestConnection(sourceConnection);
HavenoUtils.waitFor(TradeProtocol.REPROCESS_DELAY_MS); // wait before retrying
}
}
Expand Down
12 changes: 12 additions & 0 deletions core/src/main/java/haveno/core/trade/HavenoUtils.java
Original file line number Diff line number Diff line change
Expand Up @@ -506,4 +506,16 @@ public static int getDefaultMoneroPort() {
public static void setTopError(String msg) {
havenoSetup.getTopErrorMsg().set(msg);
}

public static boolean isConnectionRefused(Exception e) {
return e != null && e.getMessage().contains("Connection refused");
}

public static boolean isReadTimeout(Exception e) {
return e != null && e.getMessage().contains("Read timed out");
}

public static boolean isUnresponsive(Exception e) {
return isConnectionRefused(e) || isReadTimeout(e);
}
}
Loading

0 comments on commit 4872d2c

Please sign in to comment.