fix startup error with localhost, support fallback from provided nodes

This commit is contained in:
woodser 2025-04-17 19:58:12 -04:00 committed by GitHub
parent 8f778be4d9
commit 695f2b8dd3
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
7 changed files with 115 additions and 66 deletions

View file

@ -75,9 +75,10 @@ public final class XmrConnectionService {
private static final long KEY_IMAGE_REFRESH_PERIOD_MS_LOCAL = 20000; // 20 seconds private static final long KEY_IMAGE_REFRESH_PERIOD_MS_LOCAL = 20000; // 20 seconds
private static final long KEY_IMAGE_REFRESH_PERIOD_MS_REMOTE = 300000; // 5 minutes private static final long KEY_IMAGE_REFRESH_PERIOD_MS_REMOTE = 300000; // 5 minutes
public enum XmrConnectionError { public enum XmrConnectionFallbackType {
LOCAL, LOCAL,
CUSTOM CUSTOM,
PROVIDED
} }
private final Object lock = new Object(); private final Object lock = new Object();
@ -97,7 +98,7 @@ public final class XmrConnectionService {
private final LongProperty chainHeight = new SimpleLongProperty(0); private final LongProperty chainHeight = new SimpleLongProperty(0);
private final DownloadListener downloadListener = new DownloadListener(); private final DownloadListener downloadListener = new DownloadListener();
@Getter @Getter
private final ObjectProperty<XmrConnectionError> connectionServiceError = new SimpleObjectProperty<>(); private final ObjectProperty<XmrConnectionFallbackType> connectionServiceFallbackType = new SimpleObjectProperty<>();
@Getter @Getter
private final StringProperty connectionServiceErrorMsg = new SimpleStringProperty(); private final StringProperty connectionServiceErrorMsg = new SimpleStringProperty();
private final LongProperty numUpdates = new SimpleLongProperty(0); private final LongProperty numUpdates = new SimpleLongProperty(0);
@ -129,6 +130,7 @@ public final class XmrConnectionService {
private Set<MoneroRpcConnection> excludedConnections = new HashSet<>(); private Set<MoneroRpcConnection> excludedConnections = new HashSet<>();
private static final long FALLBACK_INVOCATION_PERIOD_MS = 1000 * 30 * 1; // offer to fallback up to once every 30s private static final long FALLBACK_INVOCATION_PERIOD_MS = 1000 * 30 * 1; // offer to fallback up to once every 30s
private boolean fallbackApplied; private boolean fallbackApplied;
private boolean usedSyncingLocalNodeBeforeStartup;
@Inject @Inject
public XmrConnectionService(P2PService p2PService, public XmrConnectionService(P2PService p2PService,
@ -156,7 +158,13 @@ public final class XmrConnectionService {
p2PService.addP2PServiceListener(new P2PServiceListener() { p2PService.addP2PServiceListener(new P2PServiceListener() {
@Override @Override
public void onTorNodeReady() { public void onTorNodeReady() {
ThreadUtils.submitToPool(() -> initialize()); ThreadUtils.submitToPool(() -> {
try {
initialize();
} catch (Exception e) {
log.warn("Error initializing connection service, error={}\n", e.getMessage(), e);
}
});
} }
@Override @Override
public void onHiddenServicePublished() {} public void onHiddenServicePublished() {}
@ -270,7 +278,7 @@ public final class XmrConnectionService {
accountService.checkAccountOpen(); accountService.checkAccountOpen();
// user needs to authorize fallback on startup after using locally synced node // user needs to authorize fallback on startup after using locally synced node
if (lastInfo == null && !fallbackApplied && lastUsedLocalSyncingNode() && !xmrLocalNode.isDetected()) { if (fallbackRequiredBeforeConnectionSwitch()) {
log.warn("Cannot get best connection on startup because we last synced local node and user has not opted to fallback"); log.warn("Cannot get best connection on startup because we last synced local node and user has not opted to fallback");
return null; return null;
} }
@ -283,6 +291,10 @@ public final class XmrConnectionService {
return bestConnection; return bestConnection;
} }
private boolean fallbackRequiredBeforeConnectionSwitch() {
return lastInfo == null && !fallbackApplied && usedSyncingLocalNodeBeforeStartup && (!xmrLocalNode.isDetected() || xmrLocalNode.shouldBeIgnored());
}
private void addLocalNodeIfIgnored(Collection<MoneroRpcConnection> ignoredConnections) { private void addLocalNodeIfIgnored(Collection<MoneroRpcConnection> ignoredConnections) {
if (xmrLocalNode.shouldBeIgnored() && connectionManager.hasConnection(xmrLocalNode.getUri())) ignoredConnections.add(connectionManager.getConnectionByUri(xmrLocalNode.getUri())); if (xmrLocalNode.shouldBeIgnored() && connectionManager.hasConnection(xmrLocalNode.getUri())) ignoredConnections.add(connectionManager.getConnectionByUri(xmrLocalNode.getUri()));
} }
@ -458,15 +470,20 @@ public final class XmrConnectionService {
public void fallbackToBestConnection() { public void fallbackToBestConnection() {
if (isShutDownStarted) return; if (isShutDownStarted) return;
if (xmrNodes.getProvidedXmrNodes().isEmpty()) { fallbackApplied = true;
if (isProvidedConnections() || xmrNodes.getProvidedXmrNodes().isEmpty()) {
log.warn("Falling back to public nodes"); log.warn("Falling back to public nodes");
preferences.setMoneroNodesOptionOrdinal(XmrNodes.MoneroNodesOption.PUBLIC.ordinal()); preferences.setMoneroNodesOptionOrdinal(XmrNodes.MoneroNodesOption.PUBLIC.ordinal());
initializeConnections();
} else { } else {
log.warn("Falling back to provided nodes"); log.warn("Falling back to provided nodes");
preferences.setMoneroNodesOptionOrdinal(XmrNodes.MoneroNodesOption.PROVIDED.ordinal()); preferences.setMoneroNodesOptionOrdinal(XmrNodes.MoneroNodesOption.PROVIDED.ordinal());
}
fallbackApplied = true;
initializeConnections(); initializeConnections();
if (getConnection() == null) {
log.warn("No provided nodes available, falling back to public nodes");
fallbackToBestConnection();
}
}
} }
// ------------------------------- HELPERS -------------------------------- // ------------------------------- HELPERS --------------------------------
@ -578,8 +595,8 @@ public final class XmrConnectionService {
setConnection(connection.getUri()); setConnection(connection.getUri());
// reset error connecting to local node // reset error connecting to local node
if (connectionServiceError.get() == XmrConnectionError.LOCAL && isConnectionLocalHost()) { if (connectionServiceFallbackType.get() == XmrConnectionFallbackType.LOCAL && isConnectionLocalHost()) {
connectionServiceError.set(null); connectionServiceFallbackType.set(null);
} }
} else if (getConnection() != null && getConnection().getUri().equals(connection.getUri())) { } else if (getConnection() != null && getConnection().getUri().equals(connection.getUri())) {
MoneroRpcConnection bestConnection = getBestConnection(); MoneroRpcConnection bestConnection = getBestConnection();
@ -602,9 +619,11 @@ public final class XmrConnectionService {
// add default connections // add default connections
for (XmrNode node : xmrNodes.getAllXmrNodes()) { for (XmrNode node : xmrNodes.getAllXmrNodes()) {
if (node.hasClearNetAddress()) { if (node.hasClearNetAddress()) {
MoneroRpcConnection connection = new MoneroRpcConnection(node.getAddress() + ":" + node.getPort()).setPriority(node.getPriority()); if (!xmrLocalNode.shouldBeIgnored() || !xmrLocalNode.equalsUri(node.getClearNetUri())) {
MoneroRpcConnection connection = new MoneroRpcConnection(node.getHostNameOrAddress() + ":" + node.getPort()).setPriority(node.getPriority());
if (!connectionList.hasConnection(connection.getUri())) addConnection(connection); if (!connectionList.hasConnection(connection.getUri())) addConnection(connection);
} }
}
if (node.hasOnionAddress()) { if (node.hasOnionAddress()) {
MoneroRpcConnection connection = new MoneroRpcConnection(node.getOnionAddress() + ":" + node.getPort()).setPriority(node.getPriority()); MoneroRpcConnection connection = new MoneroRpcConnection(node.getOnionAddress() + ":" + node.getPort()).setPriority(node.getPriority());
if (!connectionList.hasConnection(connection.getUri())) addConnection(connection); if (!connectionList.hasConnection(connection.getUri())) addConnection(connection);
@ -615,9 +634,11 @@ public final class XmrConnectionService {
// add default connections // add default connections
for (XmrNode node : xmrNodes.selectPreferredNodes(new XmrNodesSetupPreferences(preferences))) { for (XmrNode node : xmrNodes.selectPreferredNodes(new XmrNodesSetupPreferences(preferences))) {
if (node.hasClearNetAddress()) { if (node.hasClearNetAddress()) {
MoneroRpcConnection connection = new MoneroRpcConnection(node.getAddress() + ":" + node.getPort()).setPriority(node.getPriority()); if (!xmrLocalNode.shouldBeIgnored() || !xmrLocalNode.equalsUri(node.getClearNetUri())) {
MoneroRpcConnection connection = new MoneroRpcConnection(node.getHostNameOrAddress() + ":" + node.getPort()).setPriority(node.getPriority());
addConnection(connection); addConnection(connection);
} }
}
if (node.hasOnionAddress()) { if (node.hasOnionAddress()) {
MoneroRpcConnection connection = new MoneroRpcConnection(node.getOnionAddress() + ":" + node.getPort()).setPriority(node.getPriority()); MoneroRpcConnection connection = new MoneroRpcConnection(node.getOnionAddress() + ":" + node.getPort()).setPriority(node.getPriority());
addConnection(connection); addConnection(connection);
@ -632,6 +653,11 @@ public final class XmrConnectionService {
} }
} }
// set if last node was locally syncing
if (!isInitialized) {
usedSyncingLocalNodeBeforeStartup = connectionList.getCurrentConnectionUri().isPresent() && xmrLocalNode.equalsUri(connectionList.getCurrentConnectionUri().get()) && preferences.getXmrNodeSettings().getSyncBlockchain();
}
// set connection proxies // set connection proxies
log.info("TOR proxy URI: " + getProxyUri()); log.info("TOR proxy URI: " + getProxyUri());
for (MoneroRpcConnection connection : connectionManager.getConnections()) { for (MoneroRpcConnection connection : connectionManager.getConnections()) {
@ -666,29 +692,16 @@ public final class XmrConnectionService {
onConnectionChanged(connectionManager.getConnection()); onConnectionChanged(connectionManager.getConnection());
} }
private boolean lastUsedLocalSyncingNode() { public void startLocalNode() throws Exception {
return connectionManager.getConnection() != null && xmrLocalNode.equalsUri(connectionManager.getConnection().getUri()) && !xmrLocalNode.isDetected() && !xmrLocalNode.shouldBeIgnored();
}
public void startLocalNode() {
// cannot start local node as seed node // cannot start local node as seed node
if (HavenoUtils.isSeedNode()) { if (HavenoUtils.isSeedNode()) {
throw new RuntimeException("Cannot start local node on seed node"); throw new RuntimeException("Cannot start local node on seed node");
} }
// start local node if offline and used as last connection // start local node
if (connectionManager.getConnection() != null && xmrLocalNode.equalsUri(connectionManager.getConnection().getUri()) && !xmrLocalNode.isDetected() && !xmrLocalNode.shouldBeIgnored()) {
try {
log.info("Starting local node"); log.info("Starting local node");
xmrLocalNode.start(); xmrLocalNode.start();
} catch (Exception e) {
log.error("Unable to start local monero node, error={}\n", e.getMessage(), e);
throw new RuntimeException(e);
}
} else {
throw new RuntimeException("Local node is not offline and used as last connection");
}
} }
private void onConnectionChanged(MoneroRpcConnection currentConnection) { private void onConnectionChanged(MoneroRpcConnection currentConnection) {
@ -768,7 +781,7 @@ public final class XmrConnectionService {
try { try {
// poll daemon // poll daemon
if (daemon == null) switchToBestConnection(); if (daemon == null && !fallbackRequiredBeforeConnectionSwitch()) switchToBestConnection();
try { try {
if (daemon == null) throw new RuntimeException("No connection to Monero daemon"); if (daemon == null) throw new RuntimeException("No connection to Monero daemon");
lastInfo = daemon.getInfo(); lastInfo = daemon.getInfo();
@ -778,16 +791,19 @@ public final class XmrConnectionService {
if (isShutDownStarted) return; if (isShutDownStarted) return;
// invoke fallback handling on startup error // invoke fallback handling on startup error
boolean canFallback = isFixedConnection() || isCustomConnections() || lastUsedLocalSyncingNode(); boolean canFallback = isFixedConnection() || isProvidedConnections() || isCustomConnections() || usedSyncingLocalNodeBeforeStartup;
if (lastInfo == null && canFallback) { if (lastInfo == null && canFallback) {
if (connectionServiceError.get() == null && (lastFallbackInvocation == null || System.currentTimeMillis() - lastFallbackInvocation > FALLBACK_INVOCATION_PERIOD_MS)) { if (connectionServiceFallbackType.get() == null && (lastFallbackInvocation == null || System.currentTimeMillis() - lastFallbackInvocation > FALLBACK_INVOCATION_PERIOD_MS)) {
lastFallbackInvocation = System.currentTimeMillis(); lastFallbackInvocation = System.currentTimeMillis();
if (lastUsedLocalSyncingNode()) { if (usedSyncingLocalNodeBeforeStartup) {
log.warn("Failed to fetch daemon info from local connection on startup: " + e.getMessage()); log.warn("Failed to fetch daemon info from local connection on startup: " + e.getMessage());
connectionServiceError.set(XmrConnectionError.LOCAL); connectionServiceFallbackType.set(XmrConnectionFallbackType.LOCAL);
} else if (isProvidedConnections()) {
log.warn("Failed to fetch daemon info from provided connections on startup: " + e.getMessage());
connectionServiceFallbackType.set(XmrConnectionFallbackType.PROVIDED);
} else { } else {
log.warn("Failed to fetch daemon info from custom connection on startup: " + e.getMessage()); log.warn("Failed to fetch daemon info from custom connection on startup: " + e.getMessage());
connectionServiceError.set(XmrConnectionError.CUSTOM); connectionServiceFallbackType.set(XmrConnectionFallbackType.CUSTOM);
} }
} }
return; return;
@ -808,7 +824,7 @@ public final class XmrConnectionService {
// connected to daemon // connected to daemon
isConnected = true; isConnected = true;
connectionServiceError.set(null); connectionServiceFallbackType.set(null);
// determine if blockchain is syncing locally // determine if blockchain is syncing locally
boolean blockchainSyncing = lastInfo.getHeight().equals(lastInfo.getHeightWithoutBootstrap()) || (lastInfo.getTargetHeight().equals(0l) && lastInfo.getHeightWithoutBootstrap().equals(0l)); // blockchain is syncing if height equals height without bootstrap, or target height and height without bootstrap both equal 0 boolean blockchainSyncing = lastInfo.getHeight().equals(lastInfo.getHeightWithoutBootstrap()) || (lastInfo.getTargetHeight().equals(0l) && lastInfo.getHeightWithoutBootstrap().equals(0l)); // blockchain is syncing if height equals height without bootstrap, or target height and height without bootstrap both equal 0
@ -885,10 +901,14 @@ public final class XmrConnectionService {
} }
private boolean isFixedConnection() { private boolean isFixedConnection() {
return !"".equals(config.xmrNode) && (!HavenoUtils.isLocalHost(config.xmrNode) || !xmrLocalNode.shouldBeIgnored()) && !fallbackApplied; return !"".equals(config.xmrNode) && !(HavenoUtils.isLocalHost(config.xmrNode) && xmrLocalNode.shouldBeIgnored()) && !fallbackApplied;
} }
private boolean isCustomConnections() { private boolean isCustomConnections() {
return preferences.getMoneroNodesOption() == XmrNodes.MoneroNodesOption.CUSTOM; return preferences.getMoneroNodesOption() == XmrNodes.MoneroNodesOption.CUSTOM;
} }
private boolean isProvidedConnections() {
return preferences.getMoneroNodesOption() == XmrNodes.MoneroNodesOption.PROVIDED;
}
} }

View file

@ -109,17 +109,18 @@ public class XmrLocalNode {
public boolean shouldBeIgnored() { public boolean shouldBeIgnored() {
if (config.ignoreLocalXmrNode) return true; if (config.ignoreLocalXmrNode) return true;
// determine if local node is configured // ignore if fixed connection is not local
if (!"".equals(config.xmrNode)) return !HavenoUtils.isLocalHost(config.xmrNode);
// check if local node is within configuration
boolean hasConfiguredLocalNode = false; boolean hasConfiguredLocalNode = false;
for (XmrNode node : xmrNodes.selectPreferredNodes(new XmrNodesSetupPreferences(preferences))) { for (XmrNode node : xmrNodes.selectPreferredNodes(new XmrNodesSetupPreferences(preferences))) {
if (node.getAddress() != null && equalsUri("http://" + node.getAddress() + ":" + node.getPort())) { if (node.hasClearNetAddress() && equalsUri(node.getClearNetUri())) {
hasConfiguredLocalNode = true; hasConfiguredLocalNode = true;
break; break;
} }
} }
if (!hasConfiguredLocalNode) return true; return !hasConfiguredLocalNode;
return false;
} }
public void addListener(XmrLocalNodeListener listener) { public void addListener(XmrLocalNodeListener listener) {

View file

@ -75,7 +75,7 @@ public class HavenoHeadlessApp implements HeadlessApp {
log.info("onDisplayTacHandler: We accept the tacs automatically in headless mode"); log.info("onDisplayTacHandler: We accept the tacs automatically in headless mode");
acceptedHandler.run(); acceptedHandler.run();
}); });
havenoSetup.setDisplayMoneroConnectionErrorHandler(show -> log.warn("onDisplayMoneroConnectionErrorHandler: show={}", show)); havenoSetup.setDisplayMoneroConnectionFallbackHandler(show -> log.warn("onDisplayMoneroConnectionFallbackHandler: show={}", show));
havenoSetup.setDisplayTorNetworkSettingsHandler(show -> log.info("onDisplayTorNetworkSettingsHandler: show={}", show)); havenoSetup.setDisplayTorNetworkSettingsHandler(show -> log.info("onDisplayTorNetworkSettingsHandler: show={}", show));
havenoSetup.setChainFileLockedExceptionHandler(msg -> log.error("onChainFileLockedExceptionHandler: msg={}", msg)); havenoSetup.setChainFileLockedExceptionHandler(msg -> log.error("onChainFileLockedExceptionHandler: msg={}", msg));
tradeManager.setLockedUpFundsHandler(msg -> log.info("onLockedUpFundsHandler: msg={}", msg)); tradeManager.setLockedUpFundsHandler(msg -> log.info("onLockedUpFundsHandler: msg={}", msg));

View file

@ -55,7 +55,7 @@ import haveno.core.alert.PrivateNotificationManager;
import haveno.core.alert.PrivateNotificationPayload; import haveno.core.alert.PrivateNotificationPayload;
import haveno.core.api.CoreContext; import haveno.core.api.CoreContext;
import haveno.core.api.XmrConnectionService; import haveno.core.api.XmrConnectionService;
import haveno.core.api.XmrConnectionService.XmrConnectionError; import haveno.core.api.XmrConnectionService.XmrConnectionFallbackType;
import haveno.core.api.XmrLocalNode; import haveno.core.api.XmrLocalNode;
import haveno.core.locale.Res; import haveno.core.locale.Res;
import haveno.core.offer.OpenOfferManager; import haveno.core.offer.OpenOfferManager;
@ -159,7 +159,7 @@ public class HavenoSetup {
rejectedTxErrorMessageHandler; rejectedTxErrorMessageHandler;
@Setter @Setter
@Nullable @Nullable
private Consumer<XmrConnectionError> displayMoneroConnectionErrorHandler; private Consumer<XmrConnectionFallbackType> displayMoneroConnectionFallbackHandler;
@Setter @Setter
@Nullable @Nullable
private Consumer<Boolean> displayTorNetworkSettingsHandler; private Consumer<Boolean> displayTorNetworkSettingsHandler;
@ -431,9 +431,9 @@ public class HavenoSetup {
getXmrWalletSyncProgress().addListener((observable, oldValue, newValue) -> resetStartupTimeout()); getXmrWalletSyncProgress().addListener((observable, oldValue, newValue) -> resetStartupTimeout());
// listen for fallback handling // listen for fallback handling
getConnectionServiceError().addListener((observable, oldValue, newValue) -> { getConnectionServiceFallbackType().addListener((observable, oldValue, newValue) -> {
if (displayMoneroConnectionErrorHandler == null) return; if (displayMoneroConnectionFallbackHandler == null) return;
displayMoneroConnectionErrorHandler.accept(newValue); displayMoneroConnectionFallbackHandler.accept(newValue);
}); });
log.info("Init P2P network"); log.info("Init P2P network");
@ -735,8 +735,8 @@ public class HavenoSetup {
return xmrConnectionService.getConnectionServiceErrorMsg(); return xmrConnectionService.getConnectionServiceErrorMsg();
} }
public ObjectProperty<XmrConnectionError> getConnectionServiceError() { public ObjectProperty<XmrConnectionFallbackType> getConnectionServiceFallbackType() {
return xmrConnectionService.getConnectionServiceError(); return xmrConnectionService.getConnectionServiceFallbackType();
} }
public StringProperty getTopErrorMsg() { public StringProperty getTopErrorMsg() {

View file

@ -184,10 +184,6 @@ public class XmrNodes {
this.operator = operator; this.operator = operator;
} }
public boolean hasOnionAddress() {
return onionAddress != null;
}
public String getHostNameOrAddress() { public String getHostNameOrAddress() {
if (hostName != null) if (hostName != null)
return hostName; return hostName;
@ -195,10 +191,19 @@ public class XmrNodes {
return address; return address;
} }
public boolean hasOnionAddress() {
return onionAddress != null;
}
public boolean hasClearNetAddress() { public boolean hasClearNetAddress() {
return hostName != null || address != null; return hostName != null || address != null;
} }
public String getClearNetUri() {
if (!hasClearNetAddress()) throw new IllegalStateException("XmrNode does not have clearnet address");
return "http://" + getHostNameOrAddress() + ":" + port;
}
@Override @Override
public String toString() { public String toString() {
return "onionAddress='" + onionAddress + '\'' + return "onionAddress='" + onionAddress + '\'' +

View file

@ -2090,7 +2090,8 @@ closedTradesSummaryWindow.totalTradeFeeInXmr.value={0} ({1} of total trade amoun
walletPasswordWindow.headline=Enter password to unlock walletPasswordWindow.headline=Enter password to unlock
xmrConnectionError.headline=Monero connection error xmrConnectionError.headline=Monero connection error
xmrConnectionError.customNode=Error connecting to your custom Monero node(s).\n\nDo you want to use the next best available Monero node? xmrConnectionError.providedNodes=Error connecting to provided Monero node(s).\n\nDo you want to use the next best available Monero node?
xmrConnectionError.customNodes=Error connecting to your custom Monero node(s).\n\nDo you want to use the next best available Monero node?
xmrConnectionError.localNode=We previously synced using a local Monero node, but it appears to be unreachable.\n\nPlease check that it's running and synced. xmrConnectionError.localNode=We previously synced using a local Monero node, but it appears to be unreachable.\n\nPlease check that it's running and synced.
xmrConnectionError.localNode.start=Start local node xmrConnectionError.localNode.start=Start local node
xmrConnectionError.localNode.start.error=Error starting local node xmrConnectionError.localNode.start.error=Error starting local node

View file

@ -337,7 +337,7 @@ public class MainViewModel implements ViewModel, HavenoSetup.HavenoSetupListener
tacWindow.onAction(acceptedHandler::run).show(); tacWindow.onAction(acceptedHandler::run).show();
}, 1)); }, 1));
havenoSetup.setDisplayMoneroConnectionErrorHandler(connectionError -> { havenoSetup.setDisplayMoneroConnectionFallbackHandler(connectionError -> {
if (connectionError == null) { if (connectionError == null) {
if (moneroConnectionErrorPopup != null) moneroConnectionErrorPopup.hide(); if (moneroConnectionErrorPopup != null) moneroConnectionErrorPopup.hide();
} else { } else {
@ -349,7 +349,6 @@ public class MainViewModel implements ViewModel, HavenoSetup.HavenoSetupListener
.actionButtonText(Res.get("xmrConnectionError.localNode.start")) .actionButtonText(Res.get("xmrConnectionError.localNode.start"))
.onAction(() -> { .onAction(() -> {
log.warn("User has chosen to start local node."); log.warn("User has chosen to start local node.");
havenoSetup.getConnectionServiceError().set(null);
new Thread(() -> { new Thread(() -> {
try { try {
HavenoUtils.xmrConnectionService.startLocalNode(); HavenoUtils.xmrConnectionService.startLocalNode();
@ -359,16 +358,20 @@ public class MainViewModel implements ViewModel, HavenoSetup.HavenoSetupListener
.headLine(Res.get("xmrConnectionError.localNode.start.error")) .headLine(Res.get("xmrConnectionError.localNode.start.error"))
.warning(e.getMessage()) .warning(e.getMessage())
.closeButtonText(Res.get("shared.close")) .closeButtonText(Res.get("shared.close"))
.onClose(() -> havenoSetup.getConnectionServiceError().set(null)) .onClose(() -> havenoSetup.getConnectionServiceFallbackType().set(null))
.show(); .show();
} finally {
havenoSetup.getConnectionServiceFallbackType().set(null);
} }
}).start(); }).start();
}) })
.secondaryActionButtonText(Res.get("xmrConnectionError.localNode.fallback")) .secondaryActionButtonText(Res.get("xmrConnectionError.localNode.fallback"))
.onSecondaryAction(() -> { .onSecondaryAction(() -> {
log.warn("User has chosen to fallback to the next best available Monero node."); log.warn("User has chosen to fallback to the next best available Monero node.");
havenoSetup.getConnectionServiceError().set(null); new Thread(() -> {
new Thread(() -> HavenoUtils.xmrConnectionService.fallbackToBestConnection()).start(); HavenoUtils.xmrConnectionService.fallbackToBestConnection();
havenoSetup.getConnectionServiceFallbackType().set(null);
}).start();
}) })
.closeButtonText(Res.get("shared.shutDown")) .closeButtonText(Res.get("shared.shutDown"))
.onClose(HavenoApp.getShutDownHandler()); .onClose(HavenoApp.getShutDownHandler());
@ -376,16 +379,35 @@ public class MainViewModel implements ViewModel, HavenoSetup.HavenoSetupListener
case CUSTOM: case CUSTOM:
moneroConnectionErrorPopup = new Popup() moneroConnectionErrorPopup = new Popup()
.headLine(Res.get("xmrConnectionError.headline")) .headLine(Res.get("xmrConnectionError.headline"))
.warning(Res.get("xmrConnectionError.customNode")) .warning(Res.get("xmrConnectionError.customNodes"))
.actionButtonText(Res.get("shared.yes")) .actionButtonText(Res.get("shared.yes"))
.onAction(() -> { .onAction(() -> {
havenoSetup.getConnectionServiceError().set(null); new Thread(() -> {
new Thread(() -> HavenoUtils.xmrConnectionService.fallbackToBestConnection()).start(); HavenoUtils.xmrConnectionService.fallbackToBestConnection();
havenoSetup.getConnectionServiceFallbackType().set(null);
}).start();
}) })
.closeButtonText(Res.get("shared.no")) .closeButtonText(Res.get("shared.no"))
.onClose(() -> { .onClose(() -> {
log.warn("User has declined to fallback to the next best available Monero node."); log.warn("User has declined to fallback to the next best available Monero node.");
havenoSetup.getConnectionServiceError().set(null); havenoSetup.getConnectionServiceFallbackType().set(null);
});
break;
case PROVIDED:
moneroConnectionErrorPopup = new Popup()
.headLine(Res.get("xmrConnectionError.headline"))
.warning(Res.get("xmrConnectionError.providedNodes"))
.actionButtonText(Res.get("shared.yes"))
.onAction(() -> {
new Thread(() -> {
HavenoUtils.xmrConnectionService.fallbackToBestConnection();
havenoSetup.getConnectionServiceFallbackType().set(null);
}).start();
})
.closeButtonText(Res.get("shared.no"))
.onClose(() -> {
log.warn("User has declined to fallback to the next best available Monero node.");
havenoSetup.getConnectionServiceFallbackType().set(null);
}); });
break; break;
} }