diff --git a/core/src/main/java/haveno/core/api/XmrConnectionService.java b/core/src/main/java/haveno/core/api/XmrConnectionService.java index 30497020ea..9020954b9a 100644 --- a/core/src/main/java/haveno/core/api/XmrConnectionService.java +++ b/core/src/main/java/haveno/core/api/XmrConnectionService.java @@ -76,6 +76,7 @@ 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_REMOTE = 300000; // 5 minutes private static final int MAX_CONSECUTIVE_ERRORS = 3; // max errors before switching connections + private static final int SYNC_TOLERANCE_NUM_BLOCKS = 3; private static int numConsecutiveErrors = 0; public enum XmrConnectionFallbackType { @@ -289,11 +290,39 @@ public final class XmrConnectionService { // get best connection Set ignoredConnectionsSet = new HashSet<>(ignoredConnections); addLocalNodeIfIgnored(ignoredConnectionsSet); - MoneroRpcConnection bestConnection = connectionManager.getBestAvailableConnection(ignoredConnectionsSet.toArray(new MoneroRpcConnection[0])); // checks connections - if (bestConnection == null && connectionManager.getConnections().size() == 1 && !ignoredConnectionsSet.contains(connectionManager.getConnections().get(0))) bestConnection = connectionManager.getConnections().get(0); + MoneroRpcConnection bestConnection = connectionManager.getBestAvailableConnection(ignoredConnectionsSet.toArray(new MoneroRpcConnection[0])); // checks connections // TODO: capture set of connections to avoid repeatedly querying their status + + // return only connection if no best connection + if (bestConnection == null && connectionManager.getConnections().size() == 1 && !ignoredConnectionsSet.contains(connectionManager.getConnections().get(0))) { + return connectionManager.getConnections().get(0); + } + + // return next best connection if not connected or synced + if (bestConnection != null && bestConnection.isConnected() && !isSyncedWithinTolerance(new MoneroDaemonRpc(bestConnection))) { + ignoredConnectionsSet.add(bestConnection); + MoneroRpcConnection nextBestConnection = getBestConnection(ignoredConnectionsSet); + if (nextBestConnection != null) bestConnection = nextBestConnection; + } + return bestConnection; } + private static boolean isSyncedWithinTolerance(MoneroDaemonRpc monerod) { + try { + return isSyncedWithinTolerance(monerod.getInfo()); + } catch (Exception e) { + log.warn("Error checking if connection is synced within tolerance, connection={}, error={}", monerod.getRpcConnection().getUri(), e.getMessage()); + return false; + } + } + + private static boolean isSyncedWithinTolerance(MoneroDaemonInfo info) { + Long targetHeight = getTargetHeight(info); + if (targetHeight == null) return false; + if (targetHeight - getHeight(info) <= SYNC_TOLERANCE_NUM_BLOCKS) return true; // synced if within 3 blocks of target height + return false; + } + private boolean fallbackRequiredBeforeConnectionSwitch() { return lastInfo == null && !fallbackApplied && usedSyncingLocalNodeBeforeStartup && (!xmrLocalNode.isDetected() || xmrLocalNode.shouldBeIgnored()); } @@ -418,20 +447,25 @@ public final class XmrConnectionService { } public Long getHeight() { - if (lastInfo == null) return null; - return lastInfo.getHeight(); + return getHeight(lastInfo); + } + + private static Long getHeight(MoneroDaemonInfo info) { + if (info == null) return null; + return info.getHeightWithoutBootstrap() == null || info.getHeightWithoutBootstrap() == 0 ? info.getHeight() : info.getHeightWithoutBootstrap(); } public Long getTargetHeight() { - if (lastInfo == null) return null; - return lastInfo.getTargetHeight() == 0 ? lastInfo.getHeight() : lastInfo.getTargetHeight(); + return getTargetHeight(lastInfo); + } + + private static Long getTargetHeight(MoneroDaemonInfo info) { + if (info == null) return null; + return info.getTargetHeight() == 0 ? info.getHeight() : info.getTargetHeight(); } public boolean isSyncedWithinTolerance() { - Long targetHeight = getTargetHeight(); - if (targetHeight == null) return false; - if (targetHeight - chainHeight.get() <= 3) return true; // synced if within 3 blocks of target height - return false; + return isSyncedWithinTolerance(lastInfo); } public XmrKeyImagePoller getKeyImagePoller() { @@ -607,14 +641,19 @@ public final class XmrConnectionService { } // update connection + boolean isCurrentConnection = getConnection() != null && getConnection().getUri().equals(connection.getUri()); if (isConnected) { - setConnection(connection.getUri()); - // reset error connecting to local node - if (connectionServiceFallbackType.get() == XmrConnectionFallbackType.LOCAL && isConnectionLocalHost()) { - connectionServiceFallbackType.set(null); + // set connection if local node is synced + if (isSyncedWithinTolerance(xmrLocalNode.getDaemon())) { + setConnection(connection.getUri()); + + // reset error connecting to local node + if (connectionServiceFallbackType.get() == XmrConnectionFallbackType.LOCAL && isConnectionLocalHost()) { + connectionServiceFallbackType.set(null); + } } - } else if (getConnection() != null && getConnection().getUri().equals(connection.getUri())) { + } else if (isCurrentConnection) { MoneroRpcConnection bestConnection = getBestConnection(); if (bestConnection != null) setConnection(bestConnection); // switch to best connection } @@ -863,7 +902,7 @@ public final class XmrConnectionService { connectionServiceFallbackType.set(null); // set chain height - chainHeight.set(lastInfo.getHeight()); + chainHeight.set(getHeight()); // 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