prompt to fall back on startup error with custom node

This commit is contained in:
woodser 2024-11-30 13:17:05 -05:00
parent 1aef8a6bab
commit 7f6d28f1fb
8 changed files with 89 additions and 20 deletions

View file

@ -43,6 +43,7 @@ import java.util.Set;
import org.apache.commons.lang3.exception.ExceptionUtils; import org.apache.commons.lang3.exception.ExceptionUtils;
import javafx.beans.property.BooleanProperty;
import javafx.beans.property.IntegerProperty; import javafx.beans.property.IntegerProperty;
import javafx.beans.property.LongProperty; import javafx.beans.property.LongProperty;
import javafx.beans.property.ObjectProperty; import javafx.beans.property.ObjectProperty;
@ -50,6 +51,7 @@ import javafx.beans.property.ReadOnlyDoubleProperty;
import javafx.beans.property.ReadOnlyIntegerProperty; import javafx.beans.property.ReadOnlyIntegerProperty;
import javafx.beans.property.ReadOnlyLongProperty; import javafx.beans.property.ReadOnlyLongProperty;
import javafx.beans.property.ReadOnlyObjectProperty; import javafx.beans.property.ReadOnlyObjectProperty;
import javafx.beans.property.SimpleBooleanProperty;
import javafx.beans.property.SimpleIntegerProperty; import javafx.beans.property.SimpleIntegerProperty;
import javafx.beans.property.SimpleLongProperty; import javafx.beans.property.SimpleLongProperty;
import javafx.beans.property.SimpleObjectProperty; import javafx.beans.property.SimpleObjectProperty;
@ -89,6 +91,8 @@ 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 BooleanProperty connectionServiceFallbackHandlerActive = new SimpleBooleanProperty();
@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);
private Socks5ProxyProvider socks5ProxyProvider; private Socks5ProxyProvider socks5ProxyProvider;
@ -99,6 +103,7 @@ public final class XmrConnectionService {
private Boolean isConnected = false; private Boolean isConnected = false;
@Getter @Getter
private MoneroDaemonInfo lastInfo; private MoneroDaemonInfo lastInfo;
private Long lastFallbackInvocation;
private Long lastLogPollErrorTimestamp; private Long lastLogPollErrorTimestamp;
private long lastLogDaemonNotSyncedTimestamp; private long lastLogDaemonNotSyncedTimestamp;
private Long syncStartHeight; private Long syncStartHeight;
@ -115,6 +120,8 @@ public final class XmrConnectionService {
private int numRequestsLastMinute; private int numRequestsLastMinute;
private long lastSwitchTimestamp; private long lastSwitchTimestamp;
private Set<MoneroRpcConnection> excludedConnections = new HashSet<>(); private Set<MoneroRpcConnection> excludedConnections = new HashSet<>();
private static final long FALLBACK_INVOCATION_PERIOD_MS = 1000 * 60 * 1; // offer to fallback up to once every minute
private boolean fallbackApplied;
@Inject @Inject
public XmrConnectionService(P2PService p2PService, public XmrConnectionService(P2PService p2PService,
@ -424,6 +431,19 @@ public final class XmrConnectionService {
return numUpdates; return numUpdates;
} }
public void fallbackToBestConnection() {
if (isShutDownStarted) return;
if (xmrNodes.getProvidedXmrNodes().isEmpty()) {
log.warn("Falling back to public nodes");
preferences.setMoneroNodesOptionOrdinal(XmrNodes.MoneroNodesOption.PUBLIC.ordinal());
} else {
log.warn("Falling back to provided nodes");
preferences.setMoneroNodesOptionOrdinal(XmrNodes.MoneroNodesOption.PROVIDED.ordinal());
}
fallbackApplied = true;
initializeConnections();
}
// ------------------------------- HELPERS -------------------------------- // ------------------------------- HELPERS --------------------------------
private void doneDownload() { private void doneDownload() {
@ -533,7 +553,7 @@ public final class XmrConnectionService {
} }
// restore connections // restore connections
if ("".equals(config.xmrNode)) { if (!isFixedConnection()) {
// load previous or default connections // load previous or default connections
if (coreContext.isApiUser()) { if (coreContext.isApiUser()) {
@ -569,10 +589,7 @@ public final class XmrConnectionService {
} }
// restore last connection // restore last connection
if (isFixedConnection()) { if (connectionList.getCurrentConnectionUri().isPresent() && connectionManager.hasConnection(connectionList.getCurrentConnectionUri().get())) {
if (getConnections().size() != 1) throw new IllegalStateException("Expected connection list to have 1 fixed connection but was: " + getConnections().size());
connectionManager.setConnection(getConnections().get(0));
} else if (connectionList.getCurrentConnectionUri().isPresent() && connectionManager.hasConnection(connectionList.getCurrentConnectionUri().get())) {
if (!xmrLocalNode.shouldBeIgnored() || !xmrLocalNode.equalsUri(connectionList.getCurrentConnectionUri().get())) { if (!xmrLocalNode.shouldBeIgnored() || !xmrLocalNode.equalsUri(connectionList.getCurrentConnectionUri().get())) {
connectionManager.setConnection(connectionList.getCurrentConnectionUri().get()); connectionManager.setConnection(connectionList.getCurrentConnectionUri().get());
} }
@ -592,7 +609,7 @@ public final class XmrConnectionService {
maybeStartLocalNode(); maybeStartLocalNode();
// update connection // update connection
if (!isFixedConnection() && (connectionManager.getConnection() == null || connectionManager.getAutoSwitch())) { if (connectionManager.getConnection() == null || connectionManager.getAutoSwitch()) {
MoneroRpcConnection bestConnection = getBestAvailableConnection(); MoneroRpcConnection bestConnection = getBestAvailableConnection();
if (bestConnection != null) setConnection(bestConnection); if (bestConnection != null) setConnection(bestConnection);
} }
@ -614,6 +631,7 @@ public final class XmrConnectionService {
} }
// notify initial connection // notify initial connection
lastRefreshPeriodMs = getRefreshPeriodMs();
onConnectionChanged(connectionManager.getConnection()); onConnectionChanged(connectionManager.getConnection());
} }
@ -716,16 +734,14 @@ public final class XmrConnectionService {
// skip handling if shutting down // skip handling if shutting down
if (isShutDownStarted) return; if (isShutDownStarted) return;
// fallback to provided or public nodes if custom connection fails on startup // invoke fallback handling on startup error
if (lastInfo == null && "".equals(config.xmrNode) && preferences.getMoneroNodesOption() == XmrNodes.MoneroNodesOption.CUSTOM) { boolean canFallback = isFixedConnection() || isCustomConnections();
if (xmrNodes.getProvidedXmrNodes().isEmpty()) { if (lastInfo == null && canFallback) {
log.warn("Failed to fetch daemon info from custom node on startup, falling back to public nodes: " + e.getMessage()); if (!connectionServiceFallbackHandlerActive.get() && (lastFallbackInvocation == null || System.currentTimeMillis() - lastFallbackInvocation > FALLBACK_INVOCATION_PERIOD_MS)) {
preferences.setMoneroNodesOptionOrdinal(XmrNodes.MoneroNodesOption.PUBLIC.ordinal()); log.warn("Failed to fetch daemon info from custom connection on startup: " + e.getMessage());
} else { lastFallbackInvocation = System.currentTimeMillis();
log.warn("Failed to fetch daemon info from custom node on startup, falling back to provided nodes: " + e.getMessage()); connectionServiceFallbackHandlerActive.set(true);
preferences.setMoneroNodesOptionOrdinal(XmrNodes.MoneroNodesOption.PROVIDED.ordinal());
} }
initializeConnections();
return; return;
} }
@ -819,6 +835,10 @@ public final class XmrConnectionService {
} }
private boolean isFixedConnection() { private boolean isFixedConnection() {
return !"".equals(config.xmrNode) || preferences.getMoneroNodesOption() == XmrNodes.MoneroNodesOption.CUSTOM; return !"".equals(config.xmrNode) && !fallbackApplied;
}
private boolean isCustomConnections() {
return preferences.getMoneroNodesOption() == XmrNodes.MoneroNodesOption.CUSTOM;
} }
} }

View file

@ -75,6 +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.setDisplayMoneroConnectionFallbackHandler(show -> log.info("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

@ -158,6 +158,9 @@ public class HavenoSetup {
rejectedTxErrorMessageHandler; rejectedTxErrorMessageHandler;
@Setter @Setter
@Nullable @Nullable
private Consumer<Boolean> displayMoneroConnectionFallbackHandler;
@Setter
@Nullable
private Consumer<Boolean> displayTorNetworkSettingsHandler; private Consumer<Boolean> displayTorNetworkSettingsHandler;
@Setter @Setter
@Nullable @Nullable
@ -426,6 +429,12 @@ public class HavenoSetup {
getXmrDaemonSyncProgress().addListener((observable, oldValue, newValue) -> resetStartupTimeout()); getXmrDaemonSyncProgress().addListener((observable, oldValue, newValue) -> resetStartupTimeout());
getXmrWalletSyncProgress().addListener((observable, oldValue, newValue) -> resetStartupTimeout()); getXmrWalletSyncProgress().addListener((observable, oldValue, newValue) -> resetStartupTimeout());
// listen for fallback handling
getConnectionServiceFallbackHandlerActive().addListener((observable, oldValue, newValue) -> {
if (displayMoneroConnectionFallbackHandler == null) return;
displayMoneroConnectionFallbackHandler.accept(newValue);
});
log.info("Init P2P network"); log.info("Init P2P network");
havenoSetupListeners.forEach(HavenoSetupListener::onInitP2pNetwork); havenoSetupListeners.forEach(HavenoSetupListener::onInitP2pNetwork);
p2pNetworkReady = p2PNetworkSetup.init(this::initWallet, displayTorNetworkSettingsHandler); p2pNetworkReady = p2PNetworkSetup.init(this::initWallet, displayTorNetworkSettingsHandler);
@ -725,6 +734,10 @@ public class HavenoSetup {
return xmrConnectionService.getConnectionServiceErrorMsg(); return xmrConnectionService.getConnectionServiceErrorMsg();
} }
public BooleanProperty getConnectionServiceFallbackHandlerActive() {
return xmrConnectionService.getConnectionServiceFallbackHandlerActive();
}
public StringProperty getTopErrorMsg() { public StringProperty getTopErrorMsg() {
return topErrorMsg; return topErrorMsg;
} }

View file

@ -120,7 +120,7 @@ public class WalletAppSetup {
@Nullable Runnable showPopupIfInvalidBtcConfigHandler, @Nullable Runnable showPopupIfInvalidBtcConfigHandler,
Runnable downloadCompleteHandler, Runnable downloadCompleteHandler,
Runnable walletInitializedHandler) { Runnable walletInitializedHandler) {
log.info("Initialize WalletAppSetup with monero-java version {}", MoneroUtils.getVersion()); log.info("Initialize WalletAppSetup with monero-java v{}", MoneroUtils.getVersion());
ObjectProperty<Throwable> walletServiceException = new SimpleObjectProperty<>(); ObjectProperty<Throwable> walletServiceException = new SimpleObjectProperty<>();
xmrInfoBinding = EasyBind.combine( xmrInfoBinding = EasyBind.combine(

View file

@ -1298,9 +1298,10 @@ public class XmrWalletService extends XmrWalletBase {
} else { } else {
// force restart main wallet if connection changed while syncing // force restart main wallet if connection changed while syncing
if (wallet != null) {
log.warn("Force restarting main wallet because connection changed while syncing"); log.warn("Force restarting main wallet because connection changed while syncing");
forceRestartMainWallet(); forceRestartMainWallet();
return; }
} }
}); });

View file

@ -2044,6 +2044,9 @@ closedTradesSummaryWindow.totalTradeFeeInXmr.title=Sum of all trade fees paid in
closedTradesSummaryWindow.totalTradeFeeInXmr.value={0} ({1} of total trade amount) closedTradesSummaryWindow.totalTradeFeeInXmr.value={0} ({1} of total trade amount)
walletPasswordWindow.headline=Enter password to unlock walletPasswordWindow.headline=Enter password to unlock
connectionFallback.headline=Connection error
connectionFallback.msg=Error connecting to your custom Monero node(s).\n\nDo you want to try the next best available Monero node?
torNetworkSettingWindow.header=Tor networks settings torNetworkSettingWindow.header=Tor networks settings
torNetworkSettingWindow.noBridges=Don't use bridges torNetworkSettingWindow.noBridges=Don't use bridges
torNetworkSettingWindow.providedBridges=Connect with provided bridges torNetworkSettingWindow.providedBridges=Connect with provided bridges

View file

@ -674,6 +674,7 @@ public class MainView extends InitializableView<StackPane, MainViewModel> {
} }
} else { } else {
xmrInfoLabel.setId("footer-pane"); xmrInfoLabel.setId("footer-pane");
xmrInfoLabel.getStyleClass().remove("error-text");
if (xmrNetworkWarnMsgPopup != null) if (xmrNetworkWarnMsgPopup != null)
xmrNetworkWarnMsgPopup.hide(); xmrNetworkWarnMsgPopup.hide();
} }

View file

@ -140,6 +140,7 @@ public class MainViewModel implements ViewModel, HavenoSetup.HavenoSetupListener
@SuppressWarnings("FieldCanBeLocal") @SuppressWarnings("FieldCanBeLocal")
private MonadicBinding<Boolean> tradesAndUIReady; private MonadicBinding<Boolean> tradesAndUIReady;
private final Queue<Overlay<?>> popupQueue = new PriorityQueue<>(Comparator.comparing(Overlay::getDisplayOrderPriority)); private final Queue<Overlay<?>> popupQueue = new PriorityQueue<>(Comparator.comparing(Overlay::getDisplayOrderPriority));
private Popup moneroConnectionFallbackPopup;
/////////////////////////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////////////////////////
@ -334,9 +335,38 @@ public class MainViewModel implements ViewModel, HavenoSetup.HavenoSetupListener
tacWindow.onAction(acceptedHandler::run).show(); tacWindow.onAction(acceptedHandler::run).show();
}, 1)); }, 1));
havenoSetup.setDisplayMoneroConnectionFallbackHandler(show -> {
if (moneroConnectionFallbackPopup == null) {
moneroConnectionFallbackPopup = new Popup()
.headLine(Res.get("connectionFallback.headline"))
.warning(Res.get("connectionFallback.msg"))
.closeButtonText(Res.get("shared.no"))
.actionButtonText(Res.get("shared.yes"))
.onAction(() -> {
havenoSetup.getConnectionServiceFallbackHandlerActive().set(false);
new Thread(() -> HavenoUtils.xmrConnectionService.fallbackToBestConnection()).start();
})
.onClose(() -> {
log.warn("User has declined to fallback to the next best available Monero node.");
havenoSetup.getConnectionServiceFallbackHandlerActive().set(false);
});
}
if (show) {
moneroConnectionFallbackPopup.show();
} else if (moneroConnectionFallbackPopup.isDisplayed()) {
moneroConnectionFallbackPopup.hide();
}
});
havenoSetup.setDisplayTorNetworkSettingsHandler(show -> { havenoSetup.setDisplayTorNetworkSettingsHandler(show -> {
if (show) { if (show) {
torNetworkSettingsWindow.show(); torNetworkSettingsWindow.show();
// bring connection fallback popup to front if displayed
if (moneroConnectionFallbackPopup != null && moneroConnectionFallbackPopup.isDisplayed()) {
moneroConnectionFallbackPopup.hide();
moneroConnectionFallbackPopup.show();
}
} else if (torNetworkSettingsWindow.isDisplayed()) { } else if (torNetworkSettingsWindow.isDisplayed()) {
torNetworkSettingsWindow.hide(); torNetworkSettingsWindow.hide();
} }