refactor: Enhance node switching logic to prioritize unused active nodes (#2639)

David Adegoke committed Nov 8, 2025 at 21:11 UTC ddf6043ed9175b88663885077f865db626e0cdb3
1 file changed +33 -16
lib/core/node_switching_service.dart
+33 -16
@@ -22,7 +22,6 @@ class NodeSwitchingService {
22 // Cooldown period between node switching attempts (in seconds)
23 static const int _nodeSwitchingCooldownSeconds = 15;
24
25 - // State to manage switching attempts and cooldown
25 int _switchingAttempts = 0;
26 DateTime? _lastSwitchingAttempt;
27 bool _hasExhaustedAllNodes = false;
@@ -38,7 +37,6 @@ class NodeSwitchingService {
37 final SettingsStore settingsStore;
38 final Box<Node> nodeSource;
39
41 - // Track used nodes to cycle through all trusted nodes
40 final Map<WalletType, List<dynamic>> _usedNodeKeys = {};
41
42 void startHealthCheckTimer() {
@@ -85,7 +83,6 @@ class NodeSwitchingService {
83 }
84
85 try {
88 - // Check network connectivity first
86 final connectivityResult = await Connectivity().checkConnectivity();
87 if (connectivityResult == ConnectivityResult.none) {
88 printV('No network connectivity detected. Skipping node health check.');
@@ -108,6 +105,26 @@ class NodeSwitchingService {
105 }
106 }
107
108 + /// Find an active node from the provided list, checking only unused nodes
109 + /// Marks inactive nodes as used to avoid retrying them
110 + Future<Node?> _findActiveNode(
111 + List<Node> nodes,
112 + WalletType walletType,
113 + ) async {
114 + for (final node in nodes) {
115 + if (!_usedNodeKeys[walletType]!.contains(node.key)) {
116 + final isActive = await node.requestNode();
117 + if (isActive) {
118 + return node;
119 + } else {
120 + printV('Node ${node.uriRaw} is not active. Marking as used.');
121 + _usedNodeKeys[walletType]!.add(node.key);
122 + }
123 + }
124 + }
125 + return null;
126 + }
127 +
128 /// Switch to the next available trusted node
129 Future<void> _switchToNextTrustedNode() async {
130 _isSwitching = true;
@@ -143,14 +160,8 @@ class NodeSwitchingService {
160 _usedNodeKeys[walletType]!.add(currentNode.key);
161 }
162
146 - // Get next unused trusted node from the list
147 - Node? nextNode;
148 - for (final node in trustedNodes) {
149 - if (!_usedNodeKeys[walletType]!.contains(node.key)) {
150 - nextNode = node;
151 - break;
152 - }
153 - }
163 + // Try to find an active unused node
164 + Node? nextNode = await _findActiveNode(trustedNodes, walletType);
165
166 // If all trusted nodes have been used, check if we should reset
167 if (nextNode == null) {
@@ -160,16 +171,22 @@ class NodeSwitchingService {
171 if (_switchingAttempts < _maxNodeSwitchingAttempts) {
172 printV('Resetting used nodes list and trying again');
173 _usedNodeKeys[walletType]!.clear();
163 - nextNode = trustedNodes.first;
164 - } else {
165 - printV('Maximum attempts reached. No more node switching.');
174 + // Try again with cleared used list
175 + nextNode = await _findActiveNode(trustedNodes, walletType);
176 + }
177 +
178 + // If still no active node found, we give up
179 + if (nextNode == null) {
180 + printV('No active nodes available for switching after checking all nodes.');
181 _hasExhaustedAllNodes = true;
182 return;
183 }
184 }
185
171 - // Add the next node to used list
172 - _usedNodeKeys[walletType]!.add(nextNode.key);
186 + // Ensure the selected node is marked as used
187 + if (!_usedNodeKeys[walletType]!.contains(nextNode.key)) {
188 + _usedNodeKeys[walletType]!.add(nextNode.key);
189 + }
190
191 printV(
192 'Switching from ${currentNode.uriRaw} to ${nextNode.uriRaw} (attempt $_switchingAttempts/$_maxNodeSwitchingAttempts)');