Skip to content

Commit 7e3a47d

Browse files
authored
prompt to start local node or fallback on startup
1 parent 9668dd2 commit 7e3a47d

File tree

7 files changed

+129
-41
lines changed

7 files changed

+129
-41
lines changed

Makefile

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -423,6 +423,17 @@ haveno-desktop-stagenet:
423423
--apiPort=3204 \
424424
--useNativeXmrWallet=false \
425425

426+
haveno-daemon-stagenet:
427+
./haveno-daemon$(APP_EXT) \
428+
--baseCurrencyNetwork=XMR_STAGENET \
429+
--useLocalhostForP2P=false \
430+
--useDevPrivilegeKeys=false \
431+
--nodePort=9999 \
432+
--appName=Haveno \
433+
--apiPassword=apitest \
434+
--apiPort=3204 \
435+
--useNativeXmrWallet=false \
436+
426437
# Mainnet network
427438

428439
monerod:

core/src/main/java/haveno/core/api/XmrConnectionService.java

Lines changed: 44 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,6 @@
2424
import haveno.common.app.DevEnv;
2525
import haveno.common.config.BaseCurrencyNetwork;
2626
import haveno.common.config.Config;
27-
import haveno.core.locale.Res;
2827
import haveno.core.trade.HavenoUtils;
2928
import haveno.core.user.Preferences;
3029
import haveno.core.xmr.model.EncryptedConnectionList;
@@ -73,6 +72,11 @@ public final class XmrConnectionService {
7372
private static final long REFRESH_PERIOD_HTTP_MS = 20000; // refresh period when connected to remote node over http
7473
private static final long REFRESH_PERIOD_ONION_MS = 30000; // refresh period when connected to remote node over tor
7574

75+
public enum XmrConnectionError {
76+
LOCAL,
77+
CUSTOM
78+
}
79+
7680
private final Object lock = new Object();
7781
private final Object pollLock = new Object();
7882
private final Object listenerLock = new Object();
@@ -90,7 +94,7 @@ public final class XmrConnectionService {
9094
private final LongProperty chainHeight = new SimpleLongProperty(0);
9195
private final DownloadListener downloadListener = new DownloadListener();
9296
@Getter
93-
private final SimpleStringProperty connectionServiceFallbackHandler = new SimpleStringProperty();
97+
private final ObjectProperty<XmrConnectionError> connectionServiceError = new SimpleObjectProperty<>();
9498
@Getter
9599
private final StringProperty connectionServiceErrorMsg = new SimpleStringProperty();
96100
private final LongProperty numUpdates = new SimpleLongProperty(0);
@@ -119,7 +123,7 @@ public final class XmrConnectionService {
119123
private int numRequestsLastMinute;
120124
private long lastSwitchTimestamp;
121125
private Set<MoneroRpcConnection> excludedConnections = new HashSet<>();
122-
private static final long FALLBACK_INVOCATION_PERIOD_MS = 1000 * 60 * 1; // offer to fallback up to once every minute
126+
private static final long FALLBACK_INVOCATION_PERIOD_MS = 1000 * 30 * 1; // offer to fallback up to once every 30s
123127
private boolean fallbackApplied;
124128

125129
@Inject
@@ -260,7 +264,14 @@ public MoneroRpcConnection getBestConnection() {
260264

261265
private MoneroRpcConnection getBestConnection(Collection<MoneroRpcConnection> ignoredConnections) {
262266
accountService.checkAccountOpen();
263-
if (!fallbackApplied && lastUsedLocalSyncingNode() && !xmrLocalNode.isDetected()) return null; // user needs to explicitly allow fallback after syncing local node
267+
268+
// user needs to authorize fallback on startup after using locally synced node
269+
if (lastInfo == null && !fallbackApplied && lastUsedLocalSyncingNode() && !xmrLocalNode.isDetected()) {
270+
log.warn("Cannot get best connection on startup because we last synced local node and user has not opted to fallback");
271+
return null;
272+
}
273+
274+
// get best connection
264275
Set<MoneroRpcConnection> ignoredConnectionsSet = new HashSet<>(ignoredConnections);
265276
addLocalNodeIfIgnored(ignoredConnectionsSet);
266277
MoneroRpcConnection bestConnection = connectionManager.getBestAvailableConnection(ignoredConnectionsSet.toArray(new MoneroRpcConnection[0])); // checks connections
@@ -543,6 +554,11 @@ public void onConnectionChanged(MoneroRpcConnection connection) {
543554
// update connection
544555
if (isConnected) {
545556
setConnection(connection.getUri());
557+
558+
// reset error connecting to local node
559+
if (connectionServiceError.get() == XmrConnectionError.LOCAL && isConnectionLocalHost()) {
560+
connectionServiceError.set(null);
561+
}
546562
} else if (getConnection() != null && getConnection().getUri().equals(connection.getUri())) {
547563
MoneroRpcConnection bestConnection = getBestConnection();
548564
if (bestConnection != null) setConnection(bestConnection); // switch to best connection
@@ -632,6 +648,27 @@ private boolean lastUsedLocalSyncingNode() {
632648
return connectionManager.getConnection() != null && xmrLocalNode.equalsUri(connectionManager.getConnection().getUri()) && !xmrLocalNode.isDetected() && !xmrLocalNode.shouldBeIgnored();
633649
}
634650

651+
public void startLocalNode() {
652+
653+
// cannot start local node as seed node
654+
if (HavenoUtils.isSeedNode()) {
655+
throw new RuntimeException("Cannot start local node on seed node");
656+
}
657+
658+
// start local node if offline and used as last connection
659+
if (connectionManager.getConnection() != null && xmrLocalNode.equalsUri(connectionManager.getConnection().getUri()) && !xmrLocalNode.isDetected() && !xmrLocalNode.shouldBeIgnored()) {
660+
try {
661+
log.info("Starting local node");
662+
xmrLocalNode.start();
663+
} catch (Exception e) {
664+
log.error("Unable to start local monero node, error={}\n", e.getMessage(), e);
665+
throw new RuntimeException(e);
666+
}
667+
} else {
668+
throw new RuntimeException("Local node is not offline and used as last connection");
669+
}
670+
}
671+
635672
private void onConnectionChanged(MoneroRpcConnection currentConnection) {
636673
if (isShutDownStarted || !accountService.isAccountOpen()) return;
637674
if (currentConnection == null) {
@@ -717,14 +754,14 @@ private void doPollDaemon() {
717754
// invoke fallback handling on startup error
718755
boolean canFallback = isFixedConnection() || isCustomConnections() || lastUsedLocalSyncingNode();
719756
if (lastInfo == null && canFallback) {
720-
if (connectionServiceFallbackHandler.get() == null || connectionServiceFallbackHandler.equals("") && (lastFallbackInvocation == null || System.currentTimeMillis() - lastFallbackInvocation > FALLBACK_INVOCATION_PERIOD_MS)) {
757+
if (connectionServiceError.get() == null && (lastFallbackInvocation == null || System.currentTimeMillis() - lastFallbackInvocation > FALLBACK_INVOCATION_PERIOD_MS)) {
721758
lastFallbackInvocation = System.currentTimeMillis();
722759
if (lastUsedLocalSyncingNode()) {
723760
log.warn("Failed to fetch daemon info from local connection on startup: " + e.getMessage());
724-
connectionServiceFallbackHandler.set(Res.get("connectionFallback.localNode"));
761+
connectionServiceError.set(XmrConnectionError.LOCAL);
725762
} else {
726763
log.warn("Failed to fetch daemon info from custom connection on startup: " + e.getMessage());
727-
connectionServiceFallbackHandler.set(Res.get("connectionFallback.customNode"));
764+
connectionServiceError.set(XmrConnectionError.CUSTOM);
728765
}
729766
}
730767
return;

core/src/main/java/haveno/core/app/HavenoHeadlessApp.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -75,7 +75,7 @@ protected void setupHandlers() {
7575
log.info("onDisplayTacHandler: We accept the tacs automatically in headless mode");
7676
acceptedHandler.run();
7777
});
78-
havenoSetup.setDisplayMoneroConnectionFallbackHandler(show -> log.info("onDisplayMoneroConnectionFallbackHandler: show={}", show));
78+
havenoSetup.setDisplayMoneroConnectionErrorHandler(show -> log.warn("onDisplayMoneroConnectionErrorHandler: show={}", show));
7979
havenoSetup.setDisplayTorNetworkSettingsHandler(show -> log.info("onDisplayTorNetworkSettingsHandler: show={}", show));
8080
havenoSetup.setChainFileLockedExceptionHandler(msg -> log.error("onChainFileLockedExceptionHandler: msg={}", msg));
8181
tradeManager.setLockedUpFundsHandler(msg -> log.info("onLockedUpFundsHandler: msg={}", msg));

core/src/main/java/haveno/core/app/HavenoSetup.java

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,7 @@
5555
import haveno.core.alert.PrivateNotificationPayload;
5656
import haveno.core.api.CoreContext;
5757
import haveno.core.api.XmrConnectionService;
58+
import haveno.core.api.XmrConnectionService.XmrConnectionError;
5859
import haveno.core.api.XmrLocalNode;
5960
import haveno.core.locale.Res;
6061
import haveno.core.offer.OpenOfferManager;
@@ -158,7 +159,7 @@ public class HavenoSetup {
158159
rejectedTxErrorMessageHandler;
159160
@Setter
160161
@Nullable
161-
private Consumer<String> displayMoneroConnectionFallbackHandler;
162+
private Consumer<XmrConnectionError> displayMoneroConnectionErrorHandler;
162163
@Setter
163164
@Nullable
164165
private Consumer<Boolean> displayTorNetworkSettingsHandler;
@@ -430,9 +431,9 @@ private void startP2pNetworkAndWallet(Runnable nextStep) {
430431
getXmrWalletSyncProgress().addListener((observable, oldValue, newValue) -> resetStartupTimeout());
431432

432433
// listen for fallback handling
433-
getConnectionServiceFallbackHandler().addListener((observable, oldValue, newValue) -> {
434-
if (displayMoneroConnectionFallbackHandler == null) return;
435-
displayMoneroConnectionFallbackHandler.accept(newValue);
434+
getConnectionServiceError().addListener((observable, oldValue, newValue) -> {
435+
if (displayMoneroConnectionErrorHandler == null) return;
436+
displayMoneroConnectionErrorHandler.accept(newValue);
436437
});
437438

438439
log.info("Init P2P network");
@@ -734,8 +735,8 @@ public StringProperty getConnectionServiceErrorMsg() {
734735
return xmrConnectionService.getConnectionServiceErrorMsg();
735736
}
736737

737-
public StringProperty getConnectionServiceFallbackHandler() {
738-
return xmrConnectionService.getConnectionServiceFallbackHandler();
738+
public ObjectProperty<XmrConnectionError> getConnectionServiceError() {
739+
return xmrConnectionService.getConnectionServiceError();
739740
}
740741

741742
public StringProperty getTopErrorMsg() {

core/src/main/resources/i18n/displayStrings.properties

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2057,9 +2057,12 @@ closedTradesSummaryWindow.totalTradeFeeInXmr.title=Sum of all trade fees paid in
20572057
closedTradesSummaryWindow.totalTradeFeeInXmr.value={0} ({1} of total trade amount)
20582058
walletPasswordWindow.headline=Enter password to unlock
20592059

2060-
connectionFallback.headline=Connection error
2061-
connectionFallback.customNode=Error connecting to your custom Monero node(s).\n\nDo you want to use the next best available Monero node?
2062-
connectionFallback.localNode=Error connecting to your last used local node.\n\nDo you want to use the next best available Monero node?
2060+
xmrConnectionError.headline=Monero connection error
2061+
xmrConnectionError.customNode=Error connecting to your custom Monero node(s).\n\nDo you want to use the next best available Monero node?
2062+
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.
2063+
xmrConnectionError.localNode.start=Start local node
2064+
xmrConnectionError.localNode.start.error=Error starting local node
2065+
xmrConnectionError.localNode.fallback=Use next best node
20632066

20642067
torNetworkSettingWindow.header=Tor networks settings
20652068
torNetworkSettingWindow.noBridges=Don't use bridges

core/src/main/resources/i18n/displayStrings_cs.properties

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2056,7 +2056,7 @@ closedTradesSummaryWindow.totalTradeFeeInXmr.title=Suma obchodních poplatků v
20562056
closedTradesSummaryWindow.totalTradeFeeInXmr.value={0} ({1} z celkového objemu obchodů)
20572057
walletPasswordWindow.headline=Pro odemknutí zadejte heslo
20582058

2059-
connectionFallback.headline=Chyba připojení
2059+
connectionFallback.headline=Chyba připojení k Moneru
20602060
connectionFallback.customNode=Chyba při připojování k vlastním uzlům Monero.\n\nChcete vyzkoušet další nejlepší dostupný uzel Monero?
20612061

20622062
torNetworkSettingWindow.header=Nastavení sítě Tor

desktop/src/main/java/haveno/desktop/main/MainViewModel.java

Lines changed: 59 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,7 @@
5353
import haveno.core.user.User;
5454
import haveno.core.xmr.wallet.XmrWalletService;
5555
import haveno.desktop.Navigation;
56+
import haveno.desktop.app.HavenoApp;
5657
import haveno.desktop.common.model.ViewModel;
5758
import haveno.desktop.components.TxIdTextField;
5859
import haveno.desktop.main.account.AccountView;
@@ -140,7 +141,7 @@ public class MainViewModel implements ViewModel, HavenoSetup.HavenoSetupListener
140141
@SuppressWarnings("FieldCanBeLocal")
141142
private MonadicBinding<Boolean> tradesAndUIReady;
142143
private final Queue<Overlay<?>> popupQueue = new PriorityQueue<>(Comparator.comparing(Overlay::getDisplayOrderPriority));
143-
private Popup moneroConnectionFallbackPopup;
144+
private Popup moneroConnectionErrorPopup;
144145

145146

146147
///////////////////////////////////////////////////////////////////////////////////////////
@@ -335,35 +336,70 @@ private void setupHandlers() {
335336
tacWindow.onAction(acceptedHandler::run).show();
336337
}, 1));
337338

338-
havenoSetup.setDisplayMoneroConnectionFallbackHandler(fallbackMsg -> {
339-
if (fallbackMsg != null && !fallbackMsg.isEmpty()) {
340-
moneroConnectionFallbackPopup = new Popup()
341-
.headLine(Res.get("connectionFallback.headline"))
342-
.warning(fallbackMsg)
343-
.closeButtonText(Res.get("shared.no"))
344-
.actionButtonText(Res.get("shared.yes"))
345-
.onAction(() -> {
346-
havenoSetup.getConnectionServiceFallbackHandler().set("");
347-
new Thread(() -> HavenoUtils.xmrConnectionService.fallbackToBestConnection()).start();
348-
})
349-
.onClose(() -> {
350-
log.warn("User has declined to fallback to the next best available Monero node.");
351-
havenoSetup.getConnectionServiceFallbackHandler().set("");
352-
});
353-
moneroConnectionFallbackPopup.show();
354-
} else if (moneroConnectionFallbackPopup != null && moneroConnectionFallbackPopup.isDisplayed()) {
355-
moneroConnectionFallbackPopup.hide();
339+
havenoSetup.setDisplayMoneroConnectionErrorHandler(connectionError -> {
340+
if (connectionError == null) {
341+
if (moneroConnectionErrorPopup != null) moneroConnectionErrorPopup.hide();
342+
} else {
343+
switch (connectionError) {
344+
case LOCAL:
345+
moneroConnectionErrorPopup = new Popup()
346+
.headLine(Res.get("xmrConnectionError.headline"))
347+
.warning(Res.get("xmrConnectionError.localNode"))
348+
.actionButtonText(Res.get("xmrConnectionError.localNode.start"))
349+
.onAction(() -> {
350+
log.warn("User has chosen to start local node.");
351+
havenoSetup.getConnectionServiceError().set(null);
352+
new Thread(() -> {
353+
try {
354+
HavenoUtils.xmrConnectionService.startLocalNode();
355+
} catch (Exception e) {
356+
log.error("Error starting local node: {}", e.getMessage(), e);
357+
new Popup()
358+
.headLine(Res.get("xmrConnectionError.localNode.start.error"))
359+
.warning(e.getMessage())
360+
.closeButtonText(Res.get("shared.close"))
361+
.onClose(() -> havenoSetup.getConnectionServiceError().set(null))
362+
.show();
363+
}
364+
}).start();
365+
})
366+
.secondaryActionButtonText(Res.get("xmrConnectionError.localNode.fallback"))
367+
.onSecondaryAction(() -> {
368+
log.warn("User has chosen to fallback to the next best available Monero node.");
369+
havenoSetup.getConnectionServiceError().set(null);
370+
new Thread(() -> HavenoUtils.xmrConnectionService.fallbackToBestConnection()).start();
371+
})
372+
.closeButtonText(Res.get("shared.shutDown"))
373+
.onClose(HavenoApp.getShutDownHandler());
374+
break;
375+
case CUSTOM:
376+
moneroConnectionErrorPopup = new Popup()
377+
.headLine(Res.get("xmrConnectionError.headline"))
378+
.warning(Res.get("xmrConnectionError.customNode"))
379+
.actionButtonText(Res.get("shared.yes"))
380+
.onAction(() -> {
381+
havenoSetup.getConnectionServiceError().set(null);
382+
new Thread(() -> HavenoUtils.xmrConnectionService.fallbackToBestConnection()).start();
383+
})
384+
.closeButtonText(Res.get("shared.no"))
385+
.onClose(() -> {
386+
log.warn("User has declined to fallback to the next best available Monero node.");
387+
havenoSetup.getConnectionServiceError().set(null);
388+
});
389+
break;
390+
}
391+
moneroConnectionErrorPopup.show();
356392
}
357393
});
358394

359395
havenoSetup.setDisplayTorNetworkSettingsHandler(show -> {
360396
if (show) {
361397
torNetworkSettingsWindow.show();
362398

363-
// bring connection fallback popup to front if displayed
364-
if (moneroConnectionFallbackPopup != null && moneroConnectionFallbackPopup.isDisplayed()) {
365-
moneroConnectionFallbackPopup.hide();
366-
moneroConnectionFallbackPopup.show();
399+
// bring connection error popup to front if displayed
400+
if (moneroConnectionErrorPopup != null && moneroConnectionErrorPopup.isDisplayed()) {
401+
moneroConnectionErrorPopup.hide();
402+
moneroConnectionErrorPopup.show();
367403
}
368404
} else if (torNetworkSettingsWindow.isDisplayed()) {
369405
torNetworkSettingsWindow.hide();

0 commit comments

Comments
 (0)