diff --git a/lib/core/node_switching_service.dart b/lib/core/node_switching_service.dart index 5b99f56ab..83e087417 100644 --- a/lib/core/node_switching_service.dart +++ b/lib/core/node_switching_service.dart @@ -5,6 +5,7 @@ import 'package:cake_wallet/store/app_store.dart'; import 'package:cake_wallet/store/settings_store.dart'; import 'package:cw_core/utils/print_verbose.dart'; import 'package:hive/hive.dart'; +import 'package:connectivity_plus/connectivity_plus.dart'; class NodeSwitchingService { NodeSwitchingService({ @@ -15,12 +16,16 @@ class NodeSwitchingService { static const int _healthCheckIntervalSeconds = 30; - // The number of times we want to reset the overall used trusted nodes list. - // We don't want an infinite loop if all trusted nodes are down. - static const int _usedTrustedNodeResetCount = 1; + // Maximum number of node switching attempts per session + static const int _maxNodeSwitchingAttempts = 5; - // State to manage the reset count - int _resetCount = 0; + // Cooldown period between node switching attempts (in seconds) + static const int _nodeSwitchingCooldownSeconds = 15; + + // State to manage switching attempts and cooldown + int _switchingAttempts = 0; + DateTime? _lastSwitchingAttempt; + bool _hasExhaustedAllNodes = false; String walletName = ''; @@ -58,11 +63,45 @@ class NodeSwitchingService { if (_isSwitching) return; + // Reset counters when wallet changes + if (walletName.isNotEmpty && walletName != appStore.wallet!.name) { + _resetSwitchingState(); + } + walletName = appStore.wallet!.name; + + // Check if we've exhausted all switching attempts + if (_hasExhaustedAllNodes) { + printV('Node switching exhausted for wallet: $walletName. Skipping health check.'); + return; + } + + // Check cooldown period + if (_lastSwitchingAttempt != null) { + final timeSinceLastAttempt = DateTime.now().difference(_lastSwitchingAttempt!); + if (timeSinceLastAttempt.inSeconds < _nodeSwitchingCooldownSeconds) { + printV('Node switching in cooldown period. Skipping health check.'); + return; + } + } + try { + // Check network connectivity first + final connectivityResult = await Connectivity().checkConnectivity(); + if (connectivityResult == ConnectivityResult.none) { + printV('No network connectivity detected. Skipping node health check.'); + return; + } + final isHealthy = await appStore.wallet!.checkNodeHealth(); if (!isHealthy) { + printV('Node health check failed. Attempting to switch to next trusted node.'); await _switchToNextTrustedNode(); + } else { + // Reset switching attempts on successful health check + _switchingAttempts = 0; + _hasExhaustedAllNodes = false; + printV('Node health check passed. Current node is healthy.'); } } catch (e) { printV('Error during health check: $e'); @@ -73,11 +112,15 @@ class NodeSwitchingService { Future _switchToNextTrustedNode() async { _isSwitching = true; - if (walletName.isNotEmpty && (walletName != appStore.wallet!.name)) _resetCount = 0; - - walletName = appStore.wallet!.name; - try { + // Check if we've exceeded maximum switching attempts + if (_switchingAttempts >= _maxNodeSwitchingAttempts) { + printV('Maximum node switching attempts ($_maxNodeSwitchingAttempts) reached. ' + 'Disabling automatic switching.'); + _hasExhaustedAllNodes = true; + return; + } + final walletType = appStore.wallet!.type; final currentNode = settingsStore.getCurrentNode(walletType); @@ -88,6 +131,7 @@ class NodeSwitchingService { if (trustedNodes.isEmpty) { printV('No trusted nodes available for switching'); + _hasExhaustedAllNodes = true; return; } @@ -108,21 +152,27 @@ class NodeSwitchingService { } } - // If all trusted nodes have been used, reset the list and start over + // If all trusted nodes have been used, check if we should reset if (nextNode == null) { - printV('All trusted nodes have been tried, resetting and starting over'); - _resetCount++; + printV('All trusted nodes have been tried for wallet type: $walletType'); - if (_resetCount > _usedTrustedNodeResetCount) return; - - _usedNodeKeys[walletType]!.clear(); - nextNode = trustedNodes.first; + // If we've tried all nodes and still haven't reached max attempts, reset and try again + if (_switchingAttempts < _maxNodeSwitchingAttempts) { + printV('Resetting used nodes list and trying again'); + _usedNodeKeys[walletType]!.clear(); + nextNode = trustedNodes.first; + } else { + printV('Maximum attempts reached. No more node switching.'); + _hasExhaustedAllNodes = true; + return; + } } // Add the next node to used list _usedNodeKeys[walletType]!.add(nextNode.key); - printV('Switching from ${currentNode.uriRaw} to ${nextNode.uriRaw}'); + printV( + 'Switching from ${currentNode.uriRaw} to ${nextNode.uriRaw} (attempt $_switchingAttempts/$_maxNodeSwitchingAttempts)'); printV('Used nodes for ${walletType}: ${_usedNodeKeys[walletType]}'); // Update the current node in settings @@ -137,7 +187,22 @@ class NodeSwitchingService { } catch (e) { printV('Error switching to next trusted node: $e'); } finally { + // Increment switching attempts counter on every attempt + _switchingAttempts++; + _lastSwitchingAttempt = DateTime.now(); _isSwitching = false; } } + + /// Reset switching state when wallet changes + void _resetSwitchingState() { + _switchingAttempts = 0; + _lastSwitchingAttempt = null; + _hasExhaustedAllNodes = false; + _usedNodeKeys.clear(); + printV('Node switching state reset for new wallet'); + } + + /// Check if automatic node switching is currently disabled due to exhaustion + bool get isAutomaticSwitchingDisabled => _hasExhaustedAllNodes; }