Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
54 changes: 37 additions & 17 deletions core/src/main/java/haveno/core/api/XmrConnectionService.java
Original file line number Diff line number Diff line change
Expand Up @@ -43,13 +43,15 @@

import org.apache.commons.lang3.exception.ExceptionUtils;

import javafx.beans.property.BooleanProperty;
import javafx.beans.property.IntegerProperty;
import javafx.beans.property.LongProperty;
import javafx.beans.property.ObjectProperty;
import javafx.beans.property.ReadOnlyDoubleProperty;
import javafx.beans.property.ReadOnlyIntegerProperty;
import javafx.beans.property.ReadOnlyLongProperty;
import javafx.beans.property.ReadOnlyObjectProperty;
import javafx.beans.property.SimpleBooleanProperty;
import javafx.beans.property.SimpleIntegerProperty;
import javafx.beans.property.SimpleLongProperty;
import javafx.beans.property.SimpleObjectProperty;
Expand Down Expand Up @@ -89,6 +91,8 @@ public final class XmrConnectionService {
private final LongProperty chainHeight = new SimpleLongProperty(0);
private final DownloadListener downloadListener = new DownloadListener();
@Getter
private final BooleanProperty connectionServiceFallbackHandlerActive = new SimpleBooleanProperty();
@Getter
private final StringProperty connectionServiceErrorMsg = new SimpleStringProperty();
private final LongProperty numUpdates = new SimpleLongProperty(0);
private Socks5ProxyProvider socks5ProxyProvider;
Expand All @@ -99,6 +103,7 @@ public final class XmrConnectionService {
private Boolean isConnected = false;
@Getter
private MoneroDaemonInfo lastInfo;
private Long lastFallbackInvocation;
private Long lastLogPollErrorTimestamp;
private long lastLogDaemonNotSyncedTimestamp;
private Long syncStartHeight;
Expand All @@ -115,6 +120,8 @@ public final class XmrConnectionService {
private int numRequestsLastMinute;
private long lastSwitchTimestamp;
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
public XmrConnectionService(P2PService p2PService,
Expand Down Expand Up @@ -142,7 +149,7 @@ public XmrConnectionService(P2PService p2PService,
p2PService.addP2PServiceListener(new P2PServiceListener() {
@Override
public void onTorNodeReady() {
initialize();
ThreadUtils.submitToPool(() -> initialize());
}
@Override
public void onHiddenServicePublished() {}
Expand Down Expand Up @@ -424,6 +431,19 @@ public ReadOnlyLongProperty numUpdatesProperty() {
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 --------------------------------

private void doneDownload() {
Expand Down Expand Up @@ -533,7 +553,7 @@ public void onConnectionChanged(MoneroRpcConnection connection) {
}

// restore connections
if ("".equals(config.xmrNode)) {
if (!isFixedConnection()) {

// load previous or default connections
if (coreContext.isApiUser()) {
Expand Down Expand Up @@ -569,10 +589,7 @@ public void onConnectionChanged(MoneroRpcConnection connection) {
}

// restore last connection
if (isFixedConnection()) {
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 (connectionList.getCurrentConnectionUri().isPresent() && connectionManager.hasConnection(connectionList.getCurrentConnectionUri().get())) {
if (!xmrLocalNode.shouldBeIgnored() || !xmrLocalNode.equalsUri(connectionList.getCurrentConnectionUri().get())) {
connectionManager.setConnection(connectionList.getCurrentConnectionUri().get());
}
Expand All @@ -592,7 +609,7 @@ public void onConnectionChanged(MoneroRpcConnection connection) {
maybeStartLocalNode();

// update connection
if (!isFixedConnection() && (connectionManager.getConnection() == null || connectionManager.getAutoSwitch())) {
if (connectionManager.getConnection() == null || connectionManager.getAutoSwitch()) {
MoneroRpcConnection bestConnection = getBestAvailableConnection();
if (bestConnection != null) setConnection(bestConnection);
}
Expand All @@ -614,6 +631,7 @@ public void onConnectionChanged(MoneroRpcConnection connection) {
}

// notify initial connection
lastRefreshPeriodMs = getRefreshPeriodMs();
onConnectionChanged(connectionManager.getConnection());
}

Expand Down Expand Up @@ -716,16 +734,14 @@ private void doPollDaemon() {
// skip handling if shutting down
if (isShutDownStarted) return;

// fallback to provided or public nodes if custom connection fails on startup
if (lastInfo == null && "".equals(config.xmrNode) && preferences.getMoneroNodesOption() == XmrNodes.MoneroNodesOption.CUSTOM) {
if (xmrNodes.getProvidedXmrNodes().isEmpty()) {
log.warn("Failed to fetch daemon info from custom node on startup, falling back to public nodes: " + e.getMessage());
preferences.setMoneroNodesOptionOrdinal(XmrNodes.MoneroNodesOption.PUBLIC.ordinal());
} else {
log.warn("Failed to fetch daemon info from custom node on startup, falling back to provided nodes: " + e.getMessage());
preferences.setMoneroNodesOptionOrdinal(XmrNodes.MoneroNodesOption.PROVIDED.ordinal());
// invoke fallback handling on startup error
boolean canFallback = isFixedConnection() || isCustomConnections();
if (lastInfo == null && canFallback) {
if (!connectionServiceFallbackHandlerActive.get() && (lastFallbackInvocation == null || System.currentTimeMillis() - lastFallbackInvocation > FALLBACK_INVOCATION_PERIOD_MS)) {
log.warn("Failed to fetch daemon info from custom connection on startup: " + e.getMessage());
lastFallbackInvocation = System.currentTimeMillis();
connectionServiceFallbackHandlerActive.set(true);
}
initializeConnections();
return;
}

Expand Down Expand Up @@ -819,6 +835,10 @@ else if (lastInfo.isBusySyncing()) {
}

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;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,7 @@ protected void setupHandlers() {
log.info("onDisplayTacHandler: We accept the tacs automatically in headless mode");
acceptedHandler.run();
});
havenoSetup.setDisplayMoneroConnectionFallbackHandler(show -> log.info("onDisplayMoneroConnectionFallbackHandler: show={}", show));
havenoSetup.setDisplayTorNetworkSettingsHandler(show -> log.info("onDisplayTorNetworkSettingsHandler: show={}", show));
havenoSetup.setChainFileLockedExceptionHandler(msg -> log.error("onChainFileLockedExceptionHandler: msg={}", msg));
tradeManager.setLockedUpFundsHandler(msg -> log.info("onLockedUpFundsHandler: msg={}", msg));
Expand Down
13 changes: 13 additions & 0 deletions core/src/main/java/haveno/core/app/HavenoSetup.java
Original file line number Diff line number Diff line change
Expand Up @@ -158,6 +158,9 @@ public class HavenoSetup {
rejectedTxErrorMessageHandler;
@Setter
@Nullable
private Consumer<Boolean> displayMoneroConnectionFallbackHandler;
@Setter
@Nullable
private Consumer<Boolean> displayTorNetworkSettingsHandler;
@Setter
@Nullable
Expand Down Expand Up @@ -426,6 +429,12 @@ private void startP2pNetworkAndWallet(Runnable nextStep) {
getXmrDaemonSyncProgress().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");
havenoSetupListeners.forEach(HavenoSetupListener::onInitP2pNetwork);
p2pNetworkReady = p2PNetworkSetup.init(this::initWallet, displayTorNetworkSettingsHandler);
Expand Down Expand Up @@ -725,6 +734,10 @@ public StringProperty getConnectionServiceErrorMsg() {
return xmrConnectionService.getConnectionServiceErrorMsg();
}

public BooleanProperty getConnectionServiceFallbackHandlerActive() {
return xmrConnectionService.getConnectionServiceFallbackHandlerActive();
}

public StringProperty getTopErrorMsg() {
return topErrorMsg;
}
Expand Down
2 changes: 1 addition & 1 deletion core/src/main/java/haveno/core/app/WalletAppSetup.java
Original file line number Diff line number Diff line change
Expand Up @@ -120,7 +120,7 @@ void init(@Nullable Consumer<String> chainFileLockedExceptionHandler,
@Nullable Runnable showPopupIfInvalidBtcConfigHandler,
Runnable downloadCompleteHandler,
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<>();
xmrInfoBinding = EasyBind.combine(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1298,9 +1298,10 @@ private void initialize() {
} else {

// force restart main wallet if connection changed while syncing
log.warn("Force restarting main wallet because connection changed while syncing");
forceRestartMainWallet();
return;
if (wallet != null) {
log.warn("Force restarting main wallet because connection changed while syncing");
forceRestartMainWallet();
}
}
});

Expand Down
3 changes: 3 additions & 0 deletions core/src/main/resources/i18n/displayStrings.properties
Original file line number Diff line number Diff line change
Expand Up @@ -2044,6 +2044,9 @@ closedTradesSummaryWindow.totalTradeFeeInXmr.title=Sum of all trade fees paid in
closedTradesSummaryWindow.totalTradeFeeInXmr.value={0} ({1} of total trade amount)
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.noBridges=Don't use bridges
torNetworkSettingWindow.providedBridges=Connect with provided bridges
Expand Down
1 change: 1 addition & 0 deletions desktop/src/main/java/haveno/desktop/main/MainView.java
Original file line number Diff line number Diff line change
Expand Up @@ -674,6 +674,7 @@ private AnchorPane createFooter() {
}
} else {
xmrInfoLabel.setId("footer-pane");
xmrInfoLabel.getStyleClass().remove("error-text");
if (xmrNetworkWarnMsgPopup != null)
xmrNetworkWarnMsgPopup.hide();
}
Expand Down
30 changes: 30 additions & 0 deletions desktop/src/main/java/haveno/desktop/main/MainViewModel.java
Original file line number Diff line number Diff line change
Expand Up @@ -140,6 +140,7 @@ public class MainViewModel implements ViewModel, HavenoSetup.HavenoSetupListener
@SuppressWarnings("FieldCanBeLocal")
private MonadicBinding<Boolean> tradesAndUIReady;
private final Queue<Overlay<?>> popupQueue = new PriorityQueue<>(Comparator.comparing(Overlay::getDisplayOrderPriority));
private Popup moneroConnectionFallbackPopup;


///////////////////////////////////////////////////////////////////////////////////////////
Expand Down Expand Up @@ -334,9 +335,38 @@ private void setupHandlers() {
tacWindow.onAction(acceptedHandler::run).show();
}, 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 -> {
if (show) {
torNetworkSettingsWindow.show();

// bring connection fallback popup to front if displayed
if (moneroConnectionFallbackPopup != null && moneroConnectionFallbackPopup.isDisplayed()) {
moneroConnectionFallbackPopup.hide();
moneroConnectionFallbackPopup.show();
}
} else if (torNetworkSettingsWindow.isDisplayed()) {
torNetworkSettingsWindow.hide();
}
Expand Down
Loading