mirror of
https://github.com/haveno-dex/haveno.git
synced 2025-01-18 08:45:02 +00:00
refactor syncing wallet with progress and switching connections
This commit is contained in:
parent
dbb3d4f891
commit
1b5c03bce8
12 changed files with 240 additions and 141 deletions
|
@ -178,7 +178,7 @@ class CoreWalletsService {
|
||||||
verifyWalletsAreAvailable();
|
verifyWalletsAreAvailable();
|
||||||
verifyEncryptedWalletIsUnlocked();
|
verifyEncryptedWalletIsUnlocked();
|
||||||
try {
|
try {
|
||||||
return xmrWalletService.getWallet().relayTx(metadata);
|
return xmrWalletService.relayTx(metadata);
|
||||||
} catch (Exception ex) {
|
} catch (Exception ex) {
|
||||||
log.error("", ex);
|
log.error("", ex);
|
||||||
throw new IllegalStateException(ex);
|
throw new IllegalStateException(ex);
|
||||||
|
|
|
@ -273,7 +273,11 @@ public final class XmrConnectionService {
|
||||||
}
|
}
|
||||||
|
|
||||||
public synchronized boolean requestSwitchToNextBestConnection() {
|
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
|
// skip if shut down started
|
||||||
if (isShutDownStarted) {
|
if (isShutDownStarted) {
|
||||||
|
@ -281,9 +285,15 @@ public final class XmrConnectionService {
|
||||||
return false;
|
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
|
// skip if connection is fixed
|
||||||
if (isFixedConnection() || !connectionManager.getAutoSwitch()) {
|
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;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -133,6 +133,22 @@ public class WalletAppSetup {
|
||||||
String result;
|
String result;
|
||||||
if (exception == null && errorMsg == null) {
|
if (exception == null && errorMsg == null) {
|
||||||
|
|
||||||
|
// 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);
|
||||||
|
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
|
// update wallet sync progress
|
||||||
double walletDownloadPercentageD = (double) walletDownloadPercentage;
|
double walletDownloadPercentageD = (double) walletDownloadPercentage;
|
||||||
xmrWalletSyncProgress.set(walletDownloadPercentageD);
|
xmrWalletSyncProgress.set(walletDownloadPercentageD);
|
||||||
|
@ -144,29 +160,15 @@ public class WalletAppSetup {
|
||||||
result = Res.get("mainView.footer.xmrInfo", synchronizedWith, feeInfo);
|
result = Res.get("mainView.footer.xmrInfo", synchronizedWith, feeInfo);
|
||||||
getXmrSplashSyncIconId().set("image-connection-synced");
|
getXmrSplashSyncIconId().set("image-connection-synced");
|
||||||
downloadCompleteHandler.run();
|
downloadCompleteHandler.run();
|
||||||
} else if (walletDownloadPercentageD > 0) {
|
} else if (walletDownloadPercentageD >= 0) {
|
||||||
String synchronizingWith = Res.get("mainView.footer.xmrInfo.synchronizingWalletWith", getXmrWalletNetworkAsString(), walletHeightAsString, FormattingUtils.formatToRoundedPercentWithSymbol(walletDownloadPercentageD));
|
String synchronizingWith = Res.get("mainView.footer.xmrInfo.synchronizingWalletWith", getXmrWalletNetworkAsString(), walletHeightAsString, FormattingUtils.formatToRoundedPercentWithSymbol(walletDownloadPercentageD));
|
||||||
result = Res.get("mainView.footer.xmrInfo", synchronizingWith, "");
|
result = Res.get("mainView.footer.xmrInfo", synchronizingWith, "");
|
||||||
getXmrSplashSyncIconId().set(""); // clear synced icon
|
getXmrSplashSyncIconId().set(""); // clear synced icon
|
||||||
} else {
|
} else {
|
||||||
|
|
||||||
// update daemon sync progress
|
|
||||||
double chainDownloadPercentageD = xmrConnectionService.downloadPercentageProperty().doubleValue();
|
|
||||||
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 synchronizedWith = Res.get("mainView.footer.xmrInfo.connectedTo", getXmrDaemonNetworkAsString(), chainHeightAsString);
|
||||||
String feeInfo = ""; // TODO: feeService.isFeeAvailable() returns true, disable
|
String feeInfo = ""; // TODO: feeService.isFeeAvailable() returns true, disable
|
||||||
result = Res.get("mainView.footer.xmrInfo", synchronizedWith, feeInfo);
|
result = Res.get("mainView.footer.xmrInfo", synchronizedWith, feeInfo);
|
||||||
getXmrSplashSyncIconId().set("image-connection-synced");
|
getXmrSplashSyncIconId().set("image-connection-synced");
|
||||||
} else 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 {
|
} else {
|
||||||
|
|
|
@ -110,6 +110,7 @@ import javafx.collections.FXCollections;
|
||||||
import javafx.collections.ObservableList;
|
import javafx.collections.ObservableList;
|
||||||
import javax.annotation.Nullable;
|
import javax.annotation.Nullable;
|
||||||
import lombok.Getter;
|
import lombok.Getter;
|
||||||
|
import monero.common.MoneroRpcConnection;
|
||||||
import monero.daemon.model.MoneroKeyImageSpentStatus;
|
import monero.daemon.model.MoneroKeyImageSpentStatus;
|
||||||
import monero.daemon.model.MoneroTx;
|
import monero.daemon.model.MoneroTx;
|
||||||
import monero.wallet.model.MoneroIncomingTransfer;
|
import monero.wallet.model.MoneroIncomingTransfer;
|
||||||
|
@ -1089,6 +1090,7 @@ public class OpenOfferManager implements PeerManager.Listener, DecryptedDirectMe
|
||||||
synchronized (HavenoUtils.getWalletFunctionLock()) {
|
synchronized (HavenoUtils.getWalletFunctionLock()) {
|
||||||
long startTime = System.currentTimeMillis();
|
long startTime = System.currentTimeMillis();
|
||||||
for (int i = 0; i < TradeProtocol.MAX_ATTEMPTS; i++) {
|
for (int i = 0; i < TradeProtocol.MAX_ATTEMPTS; i++) {
|
||||||
|
MoneroRpcConnection sourceConnection = xmrConnectionService.getConnection();
|
||||||
try {
|
try {
|
||||||
log.info("Creating split output tx to fund offer {} at subaddress {}", openOffer.getShortId(), entry.getSubaddressIndex());
|
log.info("Creating split output tx to fund offer {} at subaddress {}", openOffer.getShortId(), entry.getSubaddressIndex());
|
||||||
splitOutputTx = xmrWalletService.createTx(new MoneroTxConfig()
|
splitOutputTx = xmrWalletService.createTx(new MoneroTxConfig()
|
||||||
|
@ -1101,8 +1103,8 @@ public class OpenOfferManager implements PeerManager.Listener, DecryptedDirectMe
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
if (e.getMessage().contains("not enough")) throw e; // do not retry if not enough funds
|
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());
|
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());
|
||||||
|
xmrWalletService.handleWalletError(e, sourceConnection);
|
||||||
if (stopped || i == TradeProtocol.MAX_ATTEMPTS - 1) throw e;
|
if (stopped || i == TradeProtocol.MAX_ATTEMPTS - 1) throw e;
|
||||||
if (xmrConnectionService.isConnected()) xmrWalletService.requestSwitchToNextBestConnection();
|
|
||||||
HavenoUtils.waitFor(TradeProtocol.REPROCESS_DELAY_MS); // wait before retrying
|
HavenoUtils.waitFor(TradeProtocol.REPROCESS_DELAY_MS); // wait before retrying
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -32,6 +32,7 @@ import haveno.core.trade.protocol.TradeProtocol;
|
||||||
import haveno.core.xmr.model.XmrAddressEntry;
|
import haveno.core.xmr.model.XmrAddressEntry;
|
||||||
import haveno.core.xmr.wallet.XmrWalletService;
|
import haveno.core.xmr.wallet.XmrWalletService;
|
||||||
import lombok.extern.slf4j.Slf4j;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import monero.common.MoneroRpcConnection;
|
||||||
import monero.daemon.model.MoneroOutput;
|
import monero.daemon.model.MoneroOutput;
|
||||||
import monero.wallet.model.MoneroTxWallet;
|
import monero.wallet.model.MoneroTxWallet;
|
||||||
|
|
||||||
|
@ -82,14 +83,16 @@ public class MakerReserveOfferFunds extends Task<PlaceOfferModel> {
|
||||||
try {
|
try {
|
||||||
synchronized (HavenoUtils.getWalletFunctionLock()) {
|
synchronized (HavenoUtils.getWalletFunctionLock()) {
|
||||||
for (int i = 0; i < TradeProtocol.MAX_ATTEMPTS; i++) {
|
for (int i = 0; i < TradeProtocol.MAX_ATTEMPTS; i++) {
|
||||||
|
MoneroRpcConnection sourceConnection = model.getXmrWalletService().getConnectionService().getConnection();
|
||||||
try {
|
try {
|
||||||
//if (true) throw new RuntimeException("Pretend error");
|
//if (true) throw new RuntimeException("Pretend error");
|
||||||
reserveTx = model.getXmrWalletService().createReserveTx(penaltyFee, makerFee, sendAmount, securityDeposit, returnAddress, openOffer.isReserveExactAmount(), preferredSubaddressIndex);
|
reserveTx = model.getXmrWalletService().createReserveTx(penaltyFee, makerFee, sendAmount, securityDeposit, returnAddress, openOffer.isReserveExactAmount(), preferredSubaddressIndex);
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
log.warn("Error creating reserve tx, offerId={}, attempt={}/{}, error={}", i + 1, TradeProtocol.MAX_ATTEMPTS, openOffer.getShortId(), e.getMessage());
|
log.warn("Error creating reserve tx, offerId={}, attempt={}/{}, error={}", i + 1, TradeProtocol.MAX_ATTEMPTS, openOffer.getShortId(), e.getMessage());
|
||||||
|
model.getXmrWalletService().handleWalletError(e, sourceConnection);
|
||||||
|
verifyPending();
|
||||||
if (i == TradeProtocol.MAX_ATTEMPTS - 1) throw e;
|
if (i == TradeProtocol.MAX_ATTEMPTS - 1) throw e;
|
||||||
model.getProtocol().startTimeoutTimer(); // reset protocol timeout
|
model.getProtocol().startTimeoutTimer(); // reset protocol timeout
|
||||||
if (model.getXmrWalletService().getConnectionService().isConnected()) model.getXmrWalletService().requestSwitchToNextBestConnection();
|
|
||||||
HavenoUtils.waitFor(TradeProtocol.REPROCESS_DELAY_MS); // wait before retrying
|
HavenoUtils.waitFor(TradeProtocol.REPROCESS_DELAY_MS); // wait before retrying
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -129,7 +132,11 @@ public class MakerReserveOfferFunds extends Task<PlaceOfferModel> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void verifyPending() {
|
private boolean isPending() {
|
||||||
if (!model.getOpenOffer().isPending()) throw new RuntimeException("Offer " + model.getOpenOffer().getOffer().getId() + " is canceled");
|
return model.getOpenOffer().isPending();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void verifyPending() {
|
||||||
|
if (!isPending()) throw new RuntimeException("Offer " + model.getOpenOffer().getOffer().getId() + " is canceled");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -78,6 +78,7 @@ import haveno.network.p2p.P2PService;
|
||||||
import haveno.network.p2p.network.Connection;
|
import haveno.network.p2p.network.Connection;
|
||||||
import haveno.network.p2p.network.MessageListener;
|
import haveno.network.p2p.network.MessageListener;
|
||||||
import lombok.extern.slf4j.Slf4j;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import monero.common.MoneroRpcConnection;
|
||||||
import monero.wallet.MoneroWallet;
|
import monero.wallet.MoneroWallet;
|
||||||
import monero.wallet.model.MoneroDestination;
|
import monero.wallet.model.MoneroDestination;
|
||||||
import monero.wallet.model.MoneroMultisigSignResult;
|
import monero.wallet.model.MoneroMultisigSignResult;
|
||||||
|
@ -501,6 +502,7 @@ public final class ArbitrationManager extends DisputeManager<ArbitrationDisputeL
|
||||||
|
|
||||||
// submit fully signed payout tx to the network
|
// submit fully signed payout tx to the network
|
||||||
for (int i = 0; i < TradeProtocol.MAX_ATTEMPTS; i++) {
|
for (int i = 0; i < TradeProtocol.MAX_ATTEMPTS; i++) {
|
||||||
|
MoneroRpcConnection sourceConnection = xmrConnectionService.getConnection();
|
||||||
try {
|
try {
|
||||||
List<String> txHashes = multisigWallet.submitMultisigTxHex(disputeTxSet.getMultisigTxHex());
|
List<String> txHashes = multisigWallet.submitMultisigTxHex(disputeTxSet.getMultisigTxHex());
|
||||||
disputeTxSet.getTxs().get(0).setHash(txHashes.get(0)); // manually update hash which is known after signed
|
disputeTxSet.getTxs().get(0).setHash(txHashes.get(0)); // manually update hash which is known after signed
|
||||||
|
@ -509,7 +511,7 @@ public final class ArbitrationManager extends DisputeManager<ArbitrationDisputeL
|
||||||
if (trade.isPayoutPublished()) throw new IllegalStateException("Payout tx already published for " + trade.getClass().getSimpleName() + " " + trade.getShortId());
|
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());
|
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 (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
|
HavenoUtils.waitFor(TradeProtocol.REPROCESS_DELAY_MS); // wait before retrying
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -506,4 +506,16 @@ public class HavenoUtils {
|
||||||
public static void setTopError(String msg) {
|
public static void setTopError(String msg) {
|
||||||
havenoSetup.getTopErrorMsg().set(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);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -850,8 +850,8 @@ public abstract class Trade implements Tradable, Model {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean requestSwitchToNextBestConnection() {
|
public boolean requestSwitchToNextBestConnection(MoneroRpcConnection sourceConnection) {
|
||||||
if (xmrConnectionService.requestSwitchToNextBestConnection()) {
|
if (xmrConnectionService.requestSwitchToNextBestConnection(sourceConnection)) {
|
||||||
onConnectionChanged(xmrConnectionService.getConnection()); // change connection on same thread
|
onConnectionChanged(xmrConnectionService.getConnection()); // change connection on same thread
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
@ -895,10 +895,6 @@ public abstract class Trade implements Tradable, Model {
|
||||||
}).start();
|
}).start();
|
||||||
}
|
}
|
||||||
|
|
||||||
private boolean isReadTimeoutError(String errMsg) {
|
|
||||||
return errMsg.contains("Read timed out");
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO: checking error strings isn't robust, but the library doesn't provide a way to check if multisig hex is invalid. throw IllegalArgumentException from library on invalid multisig hex?
|
// TODO: checking error strings isn't robust, but the library doesn't provide a way to check if multisig hex is invalid. throw IllegalArgumentException from library on invalid multisig hex?
|
||||||
private boolean isInvalidImportError(String errMsg) {
|
private boolean isInvalidImportError(String errMsg) {
|
||||||
return errMsg.contains("Failed to parse hex") || errMsg.contains("Multisig info is for a different account");
|
return errMsg.contains("Failed to parse hex") || errMsg.contains("Multisig info is for a different account");
|
||||||
|
@ -1081,6 +1077,7 @@ public abstract class Trade implements Tradable, Model {
|
||||||
synchronized (walletLock) {
|
synchronized (walletLock) {
|
||||||
synchronized (HavenoUtils.getDaemonLock()) { // lock on daemon because import calls full refresh
|
synchronized (HavenoUtils.getDaemonLock()) { // lock on daemon because import calls full refresh
|
||||||
for (int i = 0; i < TradeProtocol.MAX_ATTEMPTS; i++) {
|
for (int i = 0; i < TradeProtocol.MAX_ATTEMPTS; i++) {
|
||||||
|
MoneroRpcConnection sourceConnection = xmrConnectionService.getConnection();
|
||||||
try {
|
try {
|
||||||
doImportMultisigHex();
|
doImportMultisigHex();
|
||||||
break;
|
break;
|
||||||
|
@ -1088,9 +1085,8 @@ public abstract class Trade implements Tradable, Model {
|
||||||
throw e;
|
throw e;
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
log.warn("Failed to import multisig hex, tradeId={}, attempt={}/{}, error={}", getShortId(), i + 1, TradeProtocol.MAX_ATTEMPTS, e.getMessage());
|
log.warn("Failed to import multisig hex, tradeId={}, attempt={}/{}, error={}", getShortId(), i + 1, TradeProtocol.MAX_ATTEMPTS, e.getMessage());
|
||||||
|
handleWalletError(e, sourceConnection);
|
||||||
if (i == TradeProtocol.MAX_ATTEMPTS - 1) throw e;
|
if (i == TradeProtocol.MAX_ATTEMPTS - 1) throw e;
|
||||||
if (xmrConnectionService.isConnected()) requestSwitchToNextBestConnection();
|
|
||||||
if (isReadTimeoutError(e.getMessage())) forceRestartTradeWallet(); // wallet can be stuck a while
|
|
||||||
HavenoUtils.waitFor(TradeProtocol.REPROCESS_DELAY_MS); // wait before retrying
|
HavenoUtils.waitFor(TradeProtocol.REPROCESS_DELAY_MS); // wait before retrying
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1168,6 +1164,12 @@ public abstract class Trade implements Tradable, Model {
|
||||||
log.info("Done importing multisig hexes for {} {} in {} ms, count={}", getClass().getSimpleName(), getShortId(), System.currentTimeMillis() - startTime, multisigHexes.size());
|
log.info("Done importing multisig hexes for {} {} in {} ms, count={}", getClass().getSimpleName(), getShortId(), System.currentTimeMillis() - startTime, multisigHexes.size());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void handleWalletError(Exception e, MoneroRpcConnection sourceConnection) {
|
||||||
|
if (HavenoUtils.isUnresponsive(e)) forceCloseWallet(); // wallet can be stuck a while
|
||||||
|
if (xmrConnectionService.isConnected()) requestSwitchToNextBestConnection(sourceConnection);
|
||||||
|
getWallet(); // re-open wallet
|
||||||
|
}
|
||||||
|
|
||||||
private String getMultisigHexRole(String multisigHex) {
|
private String getMultisigHexRole(String multisigHex) {
|
||||||
if (multisigHex.equals(getArbitrator().getUpdatedMultisigHex())) return "arbitrator";
|
if (multisigHex.equals(getArbitrator().getUpdatedMultisigHex())) return "arbitrator";
|
||||||
if (multisigHex.equals(getBuyer().getUpdatedMultisigHex())) return "buyer";
|
if (multisigHex.equals(getBuyer().getUpdatedMultisigHex())) return "buyer";
|
||||||
|
@ -1189,14 +1191,15 @@ public abstract class Trade implements Tradable, Model {
|
||||||
synchronized (walletLock) {
|
synchronized (walletLock) {
|
||||||
synchronized (HavenoUtils.getWalletFunctionLock()) {
|
synchronized (HavenoUtils.getWalletFunctionLock()) {
|
||||||
for (int i = 0; i < TradeProtocol.MAX_ATTEMPTS; i++) {
|
for (int i = 0; i < TradeProtocol.MAX_ATTEMPTS; i++) {
|
||||||
|
MoneroRpcConnection sourceConnection = xmrConnectionService.getConnection();
|
||||||
try {
|
try {
|
||||||
return doCreatePayoutTx();
|
return doCreatePayoutTx();
|
||||||
} catch (IllegalArgumentException | IllegalStateException e) {
|
} catch (IllegalArgumentException | IllegalStateException e) {
|
||||||
throw e;
|
throw e;
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
log.warn("Failed to create payout tx, tradeId={}, attempt={}/{}, error={}", i + 1, TradeProtocol.MAX_ATTEMPTS, getShortId(), e.getMessage());
|
log.warn("Failed to create payout tx, tradeId={}, attempt={}/{}, error={}", i + 1, TradeProtocol.MAX_ATTEMPTS, getShortId(), e.getMessage());
|
||||||
|
handleWalletError(e, sourceConnection);
|
||||||
if (i == TradeProtocol.MAX_ATTEMPTS - 1) throw e;
|
if (i == TradeProtocol.MAX_ATTEMPTS - 1) throw e;
|
||||||
if (xmrConnectionService.isConnected()) requestSwitchToNextBestConnection();
|
|
||||||
HavenoUtils.waitFor(TradeProtocol.REPROCESS_DELAY_MS); // wait before retrying
|
HavenoUtils.waitFor(TradeProtocol.REPROCESS_DELAY_MS); // wait before retrying
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1246,6 +1249,7 @@ public abstract class Trade implements Tradable, Model {
|
||||||
synchronized (walletLock) {
|
synchronized (walletLock) {
|
||||||
synchronized (HavenoUtils.getWalletFunctionLock()) {
|
synchronized (HavenoUtils.getWalletFunctionLock()) {
|
||||||
for (int i = 0; i < TradeProtocol.MAX_ATTEMPTS; i++) {
|
for (int i = 0; i < TradeProtocol.MAX_ATTEMPTS; i++) {
|
||||||
|
MoneroRpcConnection sourceConnection = xmrConnectionService.getConnection();
|
||||||
try {
|
try {
|
||||||
if (wallet.isMultisigImportNeeded()) throw new IllegalStateException("Cannot create dispute payout tx because multisig import is needed for " + getClass().getSimpleName() + " " + getShortId());
|
if (wallet.isMultisigImportNeeded()) throw new IllegalStateException("Cannot create dispute payout tx because multisig import is needed for " + getClass().getSimpleName() + " " + getShortId());
|
||||||
return createTx(txConfig);
|
return createTx(txConfig);
|
||||||
|
@ -1254,8 +1258,8 @@ public abstract class Trade implements Tradable, Model {
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
if (e.getMessage().contains("not possible")) throw new IllegalArgumentException("Loser payout is too small to cover the mining fee");
|
if (e.getMessage().contains("not possible")) throw new IllegalArgumentException("Loser payout is too small to cover the mining fee");
|
||||||
log.warn("Failed to create dispute payout tx, tradeId={}, attempt={}/{}, error={}", i + 1, TradeProtocol.MAX_ATTEMPTS, getShortId(), e.getMessage());
|
log.warn("Failed to create dispute payout tx, tradeId={}, attempt={}/{}, error={}", i + 1, TradeProtocol.MAX_ATTEMPTS, getShortId(), e.getMessage());
|
||||||
|
handleWalletError(e, sourceConnection);
|
||||||
if (i == TradeProtocol.MAX_ATTEMPTS - 1) throw e;
|
if (i == TradeProtocol.MAX_ATTEMPTS - 1) throw e;
|
||||||
if (xmrConnectionService.isConnected()) requestSwitchToNextBestConnection();
|
|
||||||
HavenoUtils.waitFor(TradeProtocol.REPROCESS_DELAY_MS); // wait before retrying
|
HavenoUtils.waitFor(TradeProtocol.REPROCESS_DELAY_MS); // wait before retrying
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1275,6 +1279,7 @@ public abstract class Trade implements Tradable, Model {
|
||||||
synchronized (walletLock) {
|
synchronized (walletLock) {
|
||||||
synchronized (HavenoUtils.getWalletFunctionLock()) {
|
synchronized (HavenoUtils.getWalletFunctionLock()) {
|
||||||
for (int i = 0; i < TradeProtocol.MAX_ATTEMPTS; i++) {
|
for (int i = 0; i < TradeProtocol.MAX_ATTEMPTS; i++) {
|
||||||
|
MoneroRpcConnection sourceConnection = xmrConnectionService.getConnection();
|
||||||
try {
|
try {
|
||||||
doProcessPayoutTx(payoutTxHex, sign, publish);
|
doProcessPayoutTx(payoutTxHex, sign, publish);
|
||||||
break;
|
break;
|
||||||
|
@ -1282,8 +1287,8 @@ public abstract class Trade implements Tradable, Model {
|
||||||
throw e;
|
throw e;
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
log.warn("Failed to process payout tx, attempt={}/{}, tradeId={}, error={}", i + 1, TradeProtocol.MAX_ATTEMPTS, getShortId(), e.getMessage());
|
log.warn("Failed to process payout tx, attempt={}/{}, tradeId={}, error={}", i + 1, TradeProtocol.MAX_ATTEMPTS, getShortId(), e.getMessage());
|
||||||
|
handleWalletError(e, sourceConnection);
|
||||||
if (i == TradeProtocol.MAX_ATTEMPTS - 1) throw e;
|
if (i == TradeProtocol.MAX_ATTEMPTS - 1) throw e;
|
||||||
if (xmrConnectionService.isConnected()) requestSwitchToNextBestConnection();
|
|
||||||
HavenoUtils.waitFor(TradeProtocol.REPROCESS_DELAY_MS); // wait before retrying
|
HavenoUtils.waitFor(TradeProtocol.REPROCESS_DELAY_MS); // wait before retrying
|
||||||
} finally {
|
} finally {
|
||||||
requestSaveWallet();
|
requestSaveWallet();
|
||||||
|
@ -2412,6 +2417,7 @@ public abstract class Trade implements Tradable, Model {
|
||||||
}
|
}
|
||||||
|
|
||||||
private void syncWallet(boolean pollWallet) {
|
private void syncWallet(boolean pollWallet) {
|
||||||
|
MoneroRpcConnection sourceConnection = xmrConnectionService.getConnection();
|
||||||
try {
|
try {
|
||||||
if (getWallet() == null) throw new RuntimeException("Cannot sync trade wallet because it doesn't exist for " + getClass().getSimpleName() + ", " + getId());
|
if (getWallet() == null) throw new RuntimeException("Cannot sync trade wallet because it doesn't exist for " + getClass().getSimpleName() + ", " + getId());
|
||||||
if (getWallet().getDaemonConnection() == null) throw new RuntimeException("Cannot sync trade wallet because it's not connected to a Monero daemon for " + getClass().getSimpleName() + ", " + getId());
|
if (getWallet().getDaemonConnection() == null) throw new RuntimeException("Cannot sync trade wallet because it's not connected to a Monero daemon for " + getClass().getSimpleName() + ", " + getId());
|
||||||
|
@ -2432,7 +2438,7 @@ public abstract class Trade implements Tradable, Model {
|
||||||
|
|
||||||
if (pollWallet) pollWallet();
|
if (pollWallet) pollWallet();
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
ThreadUtils.execute(() -> requestSwitchToNextBestConnection(), getId());
|
ThreadUtils.execute(() -> requestSwitchToNextBestConnection(sourceConnection), getId());
|
||||||
throw e;
|
throw e;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -2561,11 +2567,12 @@ public abstract class Trade implements Tradable, Model {
|
||||||
|
|
||||||
// rescan spent outputs to detect unconfirmed payout tx
|
// rescan spent outputs to detect unconfirmed payout tx
|
||||||
if (isPayoutExpected && wallet.getBalance().compareTo(BigInteger.ZERO) > 0) {
|
if (isPayoutExpected && wallet.getBalance().compareTo(BigInteger.ZERO) > 0) {
|
||||||
|
MoneroRpcConnection sourceConnection = xmrConnectionService.getConnection();
|
||||||
try {
|
try {
|
||||||
wallet.rescanSpent();
|
wallet.rescanSpent();
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
log.warn("Failed to rescan spent outputs for {} {}, errorMessage={}", getClass().getSimpleName(), getShortId(), e.getMessage());
|
log.warn("Failed to rescan spent outputs for {} {}, errorMessage={}", getClass().getSimpleName(), getShortId(), e.getMessage());
|
||||||
ThreadUtils.execute(() -> requestSwitchToNextBestConnection(), getId()); // do not block polling thread
|
ThreadUtils.execute(() -> requestSwitchToNextBestConnection(sourceConnection), getId()); // do not block polling thread
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -2610,12 +2617,11 @@ public abstract class Trade implements Tradable, Model {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
boolean isConnectionRefused = e.getMessage() != null && e.getMessage().contains("Connection refused");
|
if (HavenoUtils.isUnresponsive(e)) forceRestartTradeWallet();
|
||||||
if (isConnectionRefused) forceRestartTradeWallet();
|
|
||||||
else {
|
else {
|
||||||
boolean isWalletConnected = isWalletConnectedToDaemon();
|
boolean isWalletConnected = isWalletConnectedToDaemon();
|
||||||
if (wallet != null && !isShutDownStarted && isWalletConnected) {
|
if (wallet != null && !isShutDownStarted && isWalletConnected) {
|
||||||
log.warn("Error polling trade wallet for {} {}, errorMessage={}. Monerod={}", getClass().getSimpleName(), getShortId(), e.getMessage(), getXmrWalletService().getConnectionService().getConnection());
|
log.warn("Error polling trade wallet for {} {}, errorMessage={}. Monerod={}", getClass().getSimpleName(), getShortId(), e.getMessage(), wallet.getDaemonConnection());
|
||||||
//e.printStackTrace();
|
//e.printStackTrace();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -2675,7 +2681,8 @@ public abstract class Trade implements Tradable, Model {
|
||||||
log.warn("Rescanning blockchain for {} {}", getClass().getSimpleName(), getShortId());
|
log.warn("Rescanning blockchain for {} {}", getClass().getSimpleName(), getShortId());
|
||||||
wallet.rescanBlockchain();
|
wallet.rescanBlockchain();
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
if (isReadTimeoutError(e.getMessage())) forceRestartTradeWallet(); // wallet can be stuck a while
|
log.warn("Error rescanning blockchain for {} {}, errorMessage={}", getClass().getSimpleName(), getShortId(), e.getMessage());
|
||||||
|
if (HavenoUtils.isUnresponsive(e)) forceRestartTradeWallet(); // wallet can be stuck a while
|
||||||
throw e;
|
throw e;
|
||||||
} finally {
|
} finally {
|
||||||
|
|
||||||
|
|
|
@ -31,6 +31,7 @@ import haveno.core.xmr.model.XmrAddressEntry;
|
||||||
import haveno.core.xmr.wallet.XmrWalletService;
|
import haveno.core.xmr.wallet.XmrWalletService;
|
||||||
import haveno.network.p2p.SendDirectMessageListener;
|
import haveno.network.p2p.SendDirectMessageListener;
|
||||||
import lombok.extern.slf4j.Slf4j;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import monero.common.MoneroRpcConnection;
|
||||||
import monero.wallet.model.MoneroTxWallet;
|
import monero.wallet.model.MoneroTxWallet;
|
||||||
|
|
||||||
import java.math.BigInteger;
|
import java.math.BigInteger;
|
||||||
|
@ -100,12 +101,14 @@ public class MaybeSendSignContractRequest extends TradeTask {
|
||||||
try {
|
try {
|
||||||
synchronized (HavenoUtils.getWalletFunctionLock()) {
|
synchronized (HavenoUtils.getWalletFunctionLock()) {
|
||||||
for (int i = 0; i < TradeProtocol.MAX_ATTEMPTS; i++) {
|
for (int i = 0; i < TradeProtocol.MAX_ATTEMPTS; i++) {
|
||||||
|
MoneroRpcConnection sourceConnection = trade.getXmrConnectionService().getConnection();
|
||||||
try {
|
try {
|
||||||
depositTx = trade.getXmrWalletService().createDepositTx(trade, reserveExactAmount, subaddressIndex);
|
depositTx = trade.getXmrWalletService().createDepositTx(trade, reserveExactAmount, subaddressIndex);
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
log.warn("Error creating deposit tx, attempt={}/{}, tradeId={}, error={}", i + 1, TradeProtocol.MAX_ATTEMPTS, trade.getShortId(), e.getMessage());
|
log.warn("Error creating deposit tx, attempt={}/{}, tradeId={}, error={}", i + 1, TradeProtocol.MAX_ATTEMPTS, trade.getShortId(), e.getMessage());
|
||||||
|
trade.getXmrWalletService().handleWalletError(e, sourceConnection);
|
||||||
|
if (isTimedOut()) throw new RuntimeException("Trade protocol has timed out while creating deposit tx, tradeId=" + trade.getShortId());
|
||||||
if (i == TradeProtocol.MAX_ATTEMPTS - 1) throw e;
|
if (i == TradeProtocol.MAX_ATTEMPTS - 1) throw e;
|
||||||
if (trade.getXmrConnectionService().isConnected()) trade.getXmrWalletService().requestSwitchToNextBestConnection();
|
|
||||||
HavenoUtils.waitFor(TradeProtocol.REPROCESS_DELAY_MS); // wait before retrying
|
HavenoUtils.waitFor(TradeProtocol.REPROCESS_DELAY_MS); // wait before retrying
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -26,6 +26,7 @@ import haveno.core.trade.protocol.TradeProtocol;
|
||||||
import haveno.core.xmr.model.XmrAddressEntry;
|
import haveno.core.xmr.model.XmrAddressEntry;
|
||||||
import haveno.core.xmr.wallet.XmrWalletService;
|
import haveno.core.xmr.wallet.XmrWalletService;
|
||||||
import lombok.extern.slf4j.Slf4j;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import monero.common.MoneroRpcConnection;
|
||||||
import monero.wallet.model.MoneroTxWallet;
|
import monero.wallet.model.MoneroTxWallet;
|
||||||
|
|
||||||
import java.math.BigInteger;
|
import java.math.BigInteger;
|
||||||
|
@ -66,12 +67,14 @@ public class TakerReserveTradeFunds extends TradeTask {
|
||||||
try {
|
try {
|
||||||
synchronized (HavenoUtils.getWalletFunctionLock()) {
|
synchronized (HavenoUtils.getWalletFunctionLock()) {
|
||||||
for (int i = 0; i < TradeProtocol.MAX_ATTEMPTS; i++) {
|
for (int i = 0; i < TradeProtocol.MAX_ATTEMPTS; i++) {
|
||||||
|
MoneroRpcConnection sourceConnection = trade.getXmrConnectionService().getConnection();
|
||||||
try {
|
try {
|
||||||
reserveTx = model.getXmrWalletService().createReserveTx(penaltyFee, takerFee, sendAmount, securityDeposit, returnAddress, false, null);
|
reserveTx = model.getXmrWalletService().createReserveTx(penaltyFee, takerFee, sendAmount, securityDeposit, returnAddress, false, null);
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
log.warn("Error creating reserve tx, attempt={}/{}, tradeId={}, error={}", i + 1, TradeProtocol.MAX_ATTEMPTS, trade.getShortId(), e.getMessage());
|
log.warn("Error creating reserve tx, attempt={}/{}, tradeId={}, error={}", i + 1, TradeProtocol.MAX_ATTEMPTS, trade.getShortId(), e.getMessage());
|
||||||
|
trade.getXmrWalletService().handleWalletError(e, sourceConnection);
|
||||||
|
if (isTimedOut()) throw new RuntimeException("Trade protocol has timed out while creating reserve tx, tradeId=" + trade.getShortId());
|
||||||
if (i == TradeProtocol.MAX_ATTEMPTS - 1) throw e;
|
if (i == TradeProtocol.MAX_ATTEMPTS - 1) throw e;
|
||||||
if (trade.getXmrConnectionService().isConnected()) trade.getXmrWalletService().requestSwitchToNextBestConnection();
|
|
||||||
HavenoUtils.waitFor(TradeProtocol.REPROCESS_DELAY_MS); // wait before retrying
|
HavenoUtils.waitFor(TradeProtocol.REPROCESS_DELAY_MS); // wait before retrying
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -160,9 +160,11 @@ public class XmrWalletService {
|
||||||
private boolean isClosingWallet;
|
private boolean isClosingWallet;
|
||||||
private boolean isShutDownStarted;
|
private boolean isShutDownStarted;
|
||||||
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 boolean isSyncingWithProgress;
|
||||||
private Long syncStartHeight;
|
private Long syncStartHeight;
|
||||||
private TaskLooper syncProgressLooper;
|
private TaskLooper syncProgressLooper;
|
||||||
private CountDownLatch syncProgressLatch;
|
private CountDownLatch syncProgressLatch;
|
||||||
|
private Exception syncProgressError;
|
||||||
private Timer syncProgressTimeout;
|
private Timer syncProgressTimeout;
|
||||||
private static final int SYNC_PROGRESS_TIMEOUT_SECONDS = 60;
|
private static final int SYNC_PROGRESS_TIMEOUT_SECONDS = 60;
|
||||||
|
|
||||||
|
@ -178,7 +180,9 @@ public class XmrWalletService {
|
||||||
private List<MoneroSubaddress> cachedSubaddresses;
|
private List<MoneroSubaddress> cachedSubaddresses;
|
||||||
private List<MoneroOutputWallet> cachedOutputs;
|
private List<MoneroOutputWallet> cachedOutputs;
|
||||||
private List<MoneroTxWallet> cachedTxs;
|
private List<MoneroTxWallet> cachedTxs;
|
||||||
private boolean runReconnectTestOnStartup = false; // test reconnecting on startup while syncing so the wallet is blocked
|
private boolean testReconnectOnStartup = false; // test reconnecting on startup while syncing so the wallet is blocked
|
||||||
|
private String testReconnectMonerod1 = "http://node.community.rino.io:18081";
|
||||||
|
private String testReconnectMonerod2 = "http://nodex.monerujo.io:18081";
|
||||||
|
|
||||||
@SuppressWarnings("unused")
|
@SuppressWarnings("unused")
|
||||||
@Inject
|
@Inject
|
||||||
|
@ -473,6 +477,14 @@ public class XmrWalletService {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public String relayTx(String metadata) {
|
||||||
|
synchronized (WALLET_LOCK) {
|
||||||
|
String txId = wallet.relayTx(metadata);
|
||||||
|
requestSaveMainWallet();
|
||||||
|
return txId;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public MoneroTxWallet createTx(List<MoneroDestination> destinations) {
|
public MoneroTxWallet createTx(List<MoneroDestination> destinations) {
|
||||||
MoneroTxWallet tx = createTx(new MoneroTxConfig().setAccountIndex(0).setDestinations(destinations).setRelay(false).setCanSplit(false));
|
MoneroTxWallet tx = createTx(new MoneroTxConfig().setAccountIndex(0).setDestinations(destinations).setRelay(false).setCanSplit(false));
|
||||||
//printTxs("XmrWalletService.createTx", tx);
|
//printTxs("XmrWalletService.createTx", tx);
|
||||||
|
@ -1289,9 +1301,19 @@ public class XmrWalletService {
|
||||||
else log.info(appliedMsg);
|
else log.info(appliedMsg);
|
||||||
|
|
||||||
// listen for connection changes
|
// listen for connection changes
|
||||||
xmrConnectionService.addConnectionListener(connection -> ThreadUtils.execute(() -> {
|
xmrConnectionService.addConnectionListener(connection -> {
|
||||||
|
if (wasWalletSynced && !isSyncingWithProgress) {
|
||||||
|
ThreadUtils.execute(() -> {
|
||||||
onConnectionChanged(connection);
|
onConnectionChanged(connection);
|
||||||
}, THREAD_ID));
|
}, THREAD_ID);
|
||||||
|
} else {
|
||||||
|
|
||||||
|
// force restart main wallet if connection changed while syncing
|
||||||
|
log.warn("Force restarting main wallet because connection changed while syncing");
|
||||||
|
forceRestartMainWallet();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
// initialize main wallet when daemon synced
|
// initialize main wallet when daemon synced
|
||||||
walletInitListener = (obs, oldVal, newVal) -> initMainWalletIfConnected();
|
walletInitListener = (obs, oldVal, newVal) -> initMainWalletIfConnected();
|
||||||
|
@ -1340,6 +1362,7 @@ public class XmrWalletService {
|
||||||
long date = localDateTime.toEpochSecond(ZoneOffset.UTC);
|
long date = localDateTime.toEpochSecond(ZoneOffset.UTC);
|
||||||
user.setWalletCreationDate(date);
|
user.setWalletCreationDate(date);
|
||||||
}
|
}
|
||||||
|
walletHeight.set(wallet.getHeight());
|
||||||
isClosingWallet = false;
|
isClosingWallet = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1348,6 +1371,7 @@ public class XmrWalletService {
|
||||||
log.info("Monero wallet path={}", wallet.getPath());
|
log.info("Monero wallet path={}", wallet.getPath());
|
||||||
|
|
||||||
// sync main wallet if applicable
|
// sync main wallet if applicable
|
||||||
|
// TODO: error handling and re-initialization is jenky, refactor
|
||||||
if (sync && numAttempts > 0) {
|
if (sync && numAttempts > 0) {
|
||||||
try {
|
try {
|
||||||
|
|
||||||
|
@ -1360,7 +1384,16 @@ public class XmrWalletService {
|
||||||
// sync main wallet
|
// sync main wallet
|
||||||
log.info("Syncing main wallet");
|
log.info("Syncing main wallet");
|
||||||
long time = System.currentTimeMillis();
|
long time = System.currentTimeMillis();
|
||||||
|
MoneroRpcConnection sourceConnection = xmrConnectionService.getConnection();
|
||||||
|
try {
|
||||||
syncWithProgress(); // blocking
|
syncWithProgress(); // blocking
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.warn("Error syncing wallet with progress on startup: " + e.getMessage());
|
||||||
|
forceCloseMainWallet();
|
||||||
|
requestSwitchToNextBestConnection(sourceConnection);
|
||||||
|
maybeInitMainWallet(true, numAttempts - 1); // re-initialize wallet and sync again
|
||||||
|
return;
|
||||||
|
}
|
||||||
log.info("Done syncing main wallet in " + (System.currentTimeMillis() - time) + " ms");
|
log.info("Done syncing main wallet in " + (System.currentTimeMillis() - time) + " ms");
|
||||||
|
|
||||||
// poll wallet
|
// poll wallet
|
||||||
|
@ -1435,69 +1468,83 @@ public class XmrWalletService {
|
||||||
}
|
}
|
||||||
|
|
||||||
private void syncWithProgress() {
|
private void syncWithProgress() {
|
||||||
|
synchronized (WALLET_LOCK) {
|
||||||
|
|
||||||
// start sync progress timeout
|
// set initial state
|
||||||
resetSyncProgressTimeout();
|
isSyncingWithProgress = true;
|
||||||
|
syncProgressError = null;
|
||||||
// show sync progress
|
updateSyncProgress(walletHeight.get());
|
||||||
updateSyncProgress(wallet.getHeight());
|
|
||||||
|
|
||||||
// test connection changing on startup before wallet synced
|
// test connection changing on startup before wallet synced
|
||||||
if (runReconnectTestOnStartup) {
|
if (testReconnectOnStartup) {
|
||||||
UserThread.runAfter(() -> {
|
UserThread.runAfter(() -> {
|
||||||
log.warn("Testing connection change on startup before wallet synced");
|
log.warn("Testing connection change on startup before wallet synced");
|
||||||
xmrConnectionService.setConnection("http://node.community.rino.io:18081"); // TODO: needs to be online
|
if (xmrConnectionService.getConnection().getUri().equals(testReconnectMonerod1)) xmrConnectionService.setConnection(testReconnectMonerod2);
|
||||||
|
else xmrConnectionService.setConnection(testReconnectMonerod1);
|
||||||
}, 1);
|
}, 1);
|
||||||
runReconnectTestOnStartup = false; // only run once
|
testReconnectOnStartup = false; // only run once
|
||||||
}
|
}
|
||||||
|
|
||||||
// get sync notifications from native wallet
|
// native wallet provides sync notifications
|
||||||
if (wallet instanceof MoneroWalletFull) {
|
if (wallet instanceof MoneroWalletFull) {
|
||||||
if (runReconnectTestOnStartup) HavenoUtils.waitFor(1000); // delay sync to test
|
if (testReconnectOnStartup) HavenoUtils.waitFor(1000); // delay sync to test
|
||||||
wallet.sync(new MoneroWalletListener() {
|
wallet.sync(new MoneroWalletListener() {
|
||||||
@Override
|
@Override
|
||||||
public void onSyncProgress(long height, long startHeight, long endHeight, double percentDone, String message) {
|
public void onSyncProgress(long height, long startHeight, long endHeight, double percentDone, String message) {
|
||||||
updateSyncProgress(height);
|
updateSyncProgress(height);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
wasWalletSynced = true;
|
setWalletSyncedWithProgress();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// poll wallet for progress
|
// start polling wallet for progress
|
||||||
wallet.startSyncing(xmrConnectionService.getRefreshPeriodMs());
|
|
||||||
syncProgressLatch = new CountDownLatch(1);
|
syncProgressLatch = new CountDownLatch(1);
|
||||||
syncProgressLooper = new TaskLooper(() -> {
|
syncProgressLooper = new TaskLooper(() -> {
|
||||||
if (wallet == null) return;
|
if (wallet == null) return;
|
||||||
long height = 0;
|
long height;
|
||||||
try {
|
try {
|
||||||
height = wallet.getHeight(); // can get read timeout while syncing
|
height = wallet.getHeight(); // can get read timeout while syncing
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
if (!isShutDownStarted) e.printStackTrace();
|
log.warn("Error getting wallet height while syncing with progress: " + e.getMessage());
|
||||||
|
if (wallet != null && !isShutDownStarted) e.printStackTrace();
|
||||||
|
|
||||||
|
// stop polling and release latch
|
||||||
|
syncProgressError = e;
|
||||||
|
syncProgressLatch.countDown();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (height < xmrConnectionService.getTargetHeight()) updateSyncProgress(height);
|
|
||||||
else {
|
|
||||||
syncProgressLooper.stop();
|
|
||||||
wasWalletSynced = true;
|
|
||||||
updateSyncProgress(height);
|
updateSyncProgress(height);
|
||||||
|
if (height >= xmrConnectionService.getTargetHeight()) {
|
||||||
|
setWalletSyncedWithProgress();
|
||||||
syncProgressLatch.countDown();
|
syncProgressLatch.countDown();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
wallet.startSyncing(xmrConnectionService.getRefreshPeriodMs());
|
||||||
syncProgressLooper.start(1000);
|
syncProgressLooper.start(1000);
|
||||||
|
|
||||||
|
// wait for sync to complete
|
||||||
HavenoUtils.awaitLatch(syncProgressLatch);
|
HavenoUtils.awaitLatch(syncProgressLatch);
|
||||||
wallet.stopSyncing();
|
|
||||||
if (!wasWalletSynced) throw new IllegalStateException("Failed to sync wallet with progress");
|
// stop polling
|
||||||
|
syncProgressLooper.stop();
|
||||||
|
syncProgressTimeout.stop();
|
||||||
|
if (wallet != null) wallet.stopSyncing(); // can become null if interrupted by force close
|
||||||
|
isSyncingWithProgress = false;
|
||||||
|
if (syncProgressError != null) throw new RuntimeException(syncProgressError);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void updateSyncProgress(long height) {
|
private void updateSyncProgress(long height) {
|
||||||
UserThread.execute(() -> {
|
|
||||||
walletHeight.set(height);
|
|
||||||
resetSyncProgressTimeout();
|
resetSyncProgressTimeout();
|
||||||
|
UserThread.execute(() -> {
|
||||||
|
|
||||||
|
// set wallet height
|
||||||
|
walletHeight.set(height);
|
||||||
|
|
||||||
// new wallet reports height 1 before synced
|
// new wallet reports height 1 before synced
|
||||||
if (height == 1) {
|
if (height == 1) {
|
||||||
downloadListener.progress(.0001, xmrConnectionService.getTargetHeight() - height, null); // >0% shows progress bar
|
downloadListener.progress(0, xmrConnectionService.getTargetHeight() - height, null);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1505,7 +1552,7 @@ public class XmrWalletService {
|
||||||
long targetHeight = xmrConnectionService.getTargetHeight();
|
long targetHeight = xmrConnectionService.getTargetHeight();
|
||||||
long blocksLeft = targetHeight - walletHeight.get();
|
long blocksLeft = targetHeight - walletHeight.get();
|
||||||
if (syncStartHeight == null) syncStartHeight = walletHeight.get();
|
if (syncStartHeight == null) syncStartHeight = walletHeight.get();
|
||||||
double percent = Math.min(1.0, targetHeight == syncStartHeight ? 1.0 : ((double) Math.max(1, (double) walletHeight.get() - syncStartHeight) / (double) (targetHeight - syncStartHeight))); // grant at least 1 block to show progress
|
double percent = Math.min(1.0, targetHeight == syncStartHeight ? 1.0 : ((double) walletHeight.get() - syncStartHeight) / (double) (targetHeight - syncStartHeight));
|
||||||
downloadListener.progress(percent, blocksLeft, null);
|
downloadListener.progress(percent, blocksLeft, null);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -1513,15 +1560,18 @@ public class XmrWalletService {
|
||||||
private synchronized void resetSyncProgressTimeout() {
|
private synchronized void resetSyncProgressTimeout() {
|
||||||
if (syncProgressTimeout != null) syncProgressTimeout.stop();
|
if (syncProgressTimeout != null) syncProgressTimeout.stop();
|
||||||
syncProgressTimeout = UserThread.runAfter(() -> {
|
syncProgressTimeout = UserThread.runAfter(() -> {
|
||||||
if (isShutDownStarted || wasWalletSynced) return;
|
if (isShutDownStarted) return;
|
||||||
log.warn("Sync progress timeout called");
|
syncProgressError = new RuntimeException("Sync progress timeout called");
|
||||||
forceCloseMainWallet();
|
syncProgressLatch.countDown();
|
||||||
requestSwitchToNextBestConnection();
|
|
||||||
maybeInitMainWallet(true);
|
|
||||||
resetSyncProgressTimeout();
|
|
||||||
}, SYNC_PROGRESS_TIMEOUT_SECONDS, TimeUnit.SECONDS);
|
}, SYNC_PROGRESS_TIMEOUT_SECONDS, TimeUnit.SECONDS);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void setWalletSyncedWithProgress() {
|
||||||
|
wasWalletSynced = true;
|
||||||
|
isSyncingWithProgress = false;
|
||||||
|
syncProgressTimeout.stop();
|
||||||
|
}
|
||||||
|
|
||||||
private MoneroWalletFull createWalletFull(MoneroWalletConfig config) {
|
private MoneroWalletFull createWalletFull(MoneroWalletConfig config) {
|
||||||
|
|
||||||
// must be connected to daemon
|
// must be connected to daemon
|
||||||
|
@ -1686,14 +1736,6 @@ public class XmrWalletService {
|
||||||
String newProxyUri = connection == null ? null : connection.getProxyUri();
|
String newProxyUri = connection == null ? null : connection.getProxyUri();
|
||||||
log.info("Setting daemon connection for main wallet, monerod={}, proxyUri={}", connection == null ? null : connection.getUri(), newProxyUri);
|
log.info("Setting daemon connection for main wallet, monerod={}, proxyUri={}", connection == null ? null : connection.getUri(), newProxyUri);
|
||||||
|
|
||||||
// force restart main wallet if connection changed before synced
|
|
||||||
if (!wasWalletSynced) {
|
|
||||||
if (!Boolean.TRUE.equals(xmrConnectionService.isConnected())) return;
|
|
||||||
log.warn("Force restarting main wallet because connection changed before inital sync");
|
|
||||||
forceRestartMainWallet();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// update connection
|
// update connection
|
||||||
if (wallet instanceof MoneroWalletRpc) {
|
if (wallet instanceof MoneroWalletRpc) {
|
||||||
if (StringUtils.equals(oldProxyUri, newProxyUri)) {
|
if (StringUtils.equals(oldProxyUri, newProxyUri)) {
|
||||||
|
@ -1702,7 +1744,7 @@ public class XmrWalletService {
|
||||||
log.info("Restarting main wallet because proxy URI has changed, old={}, new={}", oldProxyUri, newProxyUri); // TODO: set proxy without restarting wallet
|
log.info("Restarting main wallet because proxy URI has changed, old={}, new={}", oldProxyUri, newProxyUri); // TODO: set proxy without restarting wallet
|
||||||
closeMainWallet(true);
|
closeMainWallet(true);
|
||||||
doMaybeInitMainWallet(false, MAX_SYNC_ATTEMPTS);
|
doMaybeInitMainWallet(false, MAX_SYNC_ATTEMPTS);
|
||||||
return; // wallet is re-initialized
|
return; // wallet re-initializes off thread
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
wallet.setDaemonConnection(connection);
|
wallet.setDaemonConnection(connection);
|
||||||
|
@ -1771,19 +1813,26 @@ public class XmrWalletService {
|
||||||
|
|
||||||
private void forceCloseMainWallet() {
|
private void forceCloseMainWallet() {
|
||||||
stopPolling();
|
stopPolling();
|
||||||
if (wallet != null) {
|
if (wallet != null && !isClosingWallet) {
|
||||||
isClosingWallet = true;
|
isClosingWallet = true;
|
||||||
forceCloseWallet(wallet, getWalletPath(MONERO_WALLET_NAME));
|
forceCloseWallet(wallet, getWalletPath(MONERO_WALLET_NAME));
|
||||||
wallet = null;
|
wallet = null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void forceRestartMainWallet() {
|
public void forceRestartMainWallet() {
|
||||||
log.warn("Force restarting main wallet");
|
log.warn("Force restarting main wallet");
|
||||||
|
if (isClosingWallet) return;
|
||||||
forceCloseMainWallet();
|
forceCloseMainWallet();
|
||||||
maybeInitMainWallet(true);
|
maybeInitMainWallet(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void handleWalletError(Exception e, MoneroRpcConnection sourceConnection) {
|
||||||
|
if (HavenoUtils.isUnresponsive(e)) forceCloseMainWallet(); // wallet can be stuck a while
|
||||||
|
if (xmrConnectionService.isConnected()) requestSwitchToNextBestConnection(sourceConnection);
|
||||||
|
getWallet(); // re-open wallet
|
||||||
|
}
|
||||||
|
|
||||||
private void startPolling() {
|
private void startPolling() {
|
||||||
synchronized (WALLET_LOCK) {
|
synchronized (WALLET_LOCK) {
|
||||||
if (isShutDownStarted || isPolling()) return;
|
if (isShutDownStarted || isPolling()) return;
|
||||||
|
@ -1849,17 +1898,10 @@ public class XmrWalletService {
|
||||||
log.warn("Monero daemon is not synced within tolerance, height={}, targetHeight={}", xmrConnectionService.chainHeightProperty().get(), xmrConnectionService.getTargetHeight());
|
log.warn("Monero daemon is not synced within tolerance, height={}, targetHeight={}", xmrConnectionService.chainHeightProperty().get(), xmrConnectionService.getTargetHeight());
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// switch to best connection if wallet is too far behind
|
|
||||||
if (wasWalletSynced && walletHeight.get() < xmrConnectionService.getTargetHeight() - NUM_BLOCKS_BEHIND_TOLERANCE && !Config.baseCurrencyNetwork().isTestnet()) {
|
|
||||||
log.warn("Updating connection because main wallet is {} blocks behind monerod, wallet height={}, monerod height={}", xmrConnectionService.getTargetHeight() - walletHeight.get(), walletHeight.get(), lastInfo.getHeight());
|
|
||||||
if (xmrConnectionService.isConnected()) requestSwitchToNextBestConnection();
|
|
||||||
}
|
|
||||||
|
|
||||||
// sync wallet if behind daemon
|
// sync wallet if behind daemon
|
||||||
if (walletHeight.get() < xmrConnectionService.getTargetHeight()) {
|
if (walletHeight.get() < xmrConnectionService.getTargetHeight()) {
|
||||||
synchronized (WALLET_LOCK) { // avoid long sync from blocking other operations
|
synchronized (WALLET_LOCK) { // avoid long sync from blocking other operations
|
||||||
syncMainWallet();
|
syncWithProgress();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1868,13 +1910,17 @@ public class XmrWalletService {
|
||||||
if (updateTxs) {
|
if (updateTxs) {
|
||||||
synchronized (WALLET_LOCK) { // avoid long fetch from blocking other operations
|
synchronized (WALLET_LOCK) { // avoid long fetch from blocking other operations
|
||||||
synchronized (HavenoUtils.getDaemonLock()) {
|
synchronized (HavenoUtils.getDaemonLock()) {
|
||||||
|
MoneroRpcConnection sourceConnection = xmrConnectionService.getConnection();
|
||||||
try {
|
try {
|
||||||
cachedTxs = wallet.getTxs(new MoneroTxQuery().setIncludeOutputs(true));
|
cachedTxs = wallet.getTxs(new MoneroTxQuery().setIncludeOutputs(true));
|
||||||
} catch (Exception e) { // fetch from pool can fail
|
} catch (Exception e) { // fetch from pool can fail
|
||||||
if (!isShutDownStarted) {
|
if (!isShutDownStarted) {
|
||||||
if (lastLogPollErrorTimestamp == null || System.currentTimeMillis() - lastLogPollErrorTimestamp > HavenoUtils.LOG_POLL_ERROR_PERIOD_MS) { // limit error logging
|
|
||||||
|
// throttle error handling
|
||||||
|
if (lastLogPollErrorTimestamp == null || System.currentTimeMillis() - lastLogPollErrorTimestamp > HavenoUtils.LOG_POLL_ERROR_PERIOD_MS) {
|
||||||
log.warn("Error polling main wallet's transactions from the pool: {}", e.getMessage());
|
log.warn("Error polling main wallet's transactions from the pool: {}", e.getMessage());
|
||||||
lastLogPollErrorTimestamp = System.currentTimeMillis();
|
lastLogPollErrorTimestamp = System.currentTimeMillis();
|
||||||
|
requestSwitchToNextBestConnection(sourceConnection);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1883,8 +1929,7 @@ public class XmrWalletService {
|
||||||
}
|
}
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
if (wallet == null || isShutDownStarted) return;
|
if (wallet == null || isShutDownStarted) return;
|
||||||
boolean isConnectionRefused = e.getMessage() != null && e.getMessage().contains("Connection refused");
|
if (HavenoUtils.isUnresponsive(e)) forceRestartMainWallet();
|
||||||
if (isConnectionRefused) forceRestartMainWallet();
|
|
||||||
else if (isWalletConnectedToDaemon()) {
|
else if (isWalletConnectedToDaemon()) {
|
||||||
log.warn("Error polling main wallet, errorMessage={}. Monerod={}", e.getMessage(), getConnectionService().getConnection());
|
log.warn("Error polling main wallet, errorMessage={}. Monerod={}", e.getMessage(), getConnectionService().getConnection());
|
||||||
//e.printStackTrace();
|
//e.printStackTrace();
|
||||||
|
@ -1927,8 +1972,12 @@ public class XmrWalletService {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean requestSwitchToNextBestConnection() {
|
private boolean requestSwitchToNextBestConnection() {
|
||||||
return xmrConnectionService.requestSwitchToNextBestConnection();
|
return requestSwitchToNextBestConnection(null);
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean requestSwitchToNextBestConnection(MoneroRpcConnection sourceConnection) {
|
||||||
|
return xmrConnectionService.requestSwitchToNextBestConnection(sourceConnection);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void onNewBlock(long height) {
|
private void onNewBlock(long height) {
|
||||||
|
|
|
@ -65,6 +65,7 @@ import javafx.scene.control.Button;
|
||||||
import javafx.scene.layout.GridPane;
|
import javafx.scene.layout.GridPane;
|
||||||
import javafx.scene.layout.StackPane;
|
import javafx.scene.layout.StackPane;
|
||||||
import javafx.scene.layout.VBox;
|
import javafx.scene.layout.VBox;
|
||||||
|
import monero.common.MoneroRpcConnection;
|
||||||
import monero.common.MoneroUtils;
|
import monero.common.MoneroUtils;
|
||||||
import monero.wallet.model.MoneroTxConfig;
|
import monero.wallet.model.MoneroTxConfig;
|
||||||
import monero.wallet.model.MoneroTxWallet;
|
import monero.wallet.model.MoneroTxWallet;
|
||||||
|
@ -256,6 +257,7 @@ public class WithdrawalView extends ActivatableView<VBox, Void> {
|
||||||
// create tx
|
// create tx
|
||||||
MoneroTxWallet tx = null;
|
MoneroTxWallet tx = null;
|
||||||
for (int i = 0; i < TradeProtocol.MAX_ATTEMPTS; i++) {
|
for (int i = 0; i < TradeProtocol.MAX_ATTEMPTS; i++) {
|
||||||
|
MoneroRpcConnection sourceConnection = xmrWalletService.getConnectionService().getConnection();
|
||||||
try {
|
try {
|
||||||
log.info("Creating withdraw tx");
|
log.info("Creating withdraw tx");
|
||||||
long startTime = System.currentTimeMillis();
|
long startTime = System.currentTimeMillis();
|
||||||
|
@ -270,7 +272,7 @@ public class WithdrawalView extends ActivatableView<VBox, Void> {
|
||||||
if (isNotEnoughMoney(e.getMessage())) throw e;
|
if (isNotEnoughMoney(e.getMessage())) throw e;
|
||||||
log.warn("Error creating creating withdraw tx, attempt={}/{}, error={}", i + 1, TradeProtocol.MAX_ATTEMPTS, e.getMessage());
|
log.warn("Error creating creating withdraw tx, attempt={}/{}, error={}", i + 1, TradeProtocol.MAX_ATTEMPTS, e.getMessage());
|
||||||
if (i == TradeProtocol.MAX_ATTEMPTS - 1) throw e;
|
if (i == TradeProtocol.MAX_ATTEMPTS - 1) throw e;
|
||||||
if (xmrWalletService.getConnectionService().isConnected()) xmrWalletService.requestSwitchToNextBestConnection();
|
if (xmrWalletService.getConnectionService().isConnected()) xmrWalletService.requestSwitchToNextBestConnection(sourceConnection);
|
||||||
HavenoUtils.waitFor(TradeProtocol.REPROCESS_DELAY_MS); // wait before retrying
|
HavenoUtils.waitFor(TradeProtocol.REPROCESS_DELAY_MS); // wait before retrying
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue