diff --git a/common/client-core/src/client/topology_control/nym_api_provider.rs b/common/client-core/src/client/topology_control/nym_api_provider.rs index 15ffc8d482..8f6563daf4 100644 --- a/common/client-core/src/client/topology_control/nym_api_provider.rs +++ b/common/client-core/src/client/topology_control/nym_api_provider.rs @@ -61,7 +61,7 @@ impl NymApiTopologyProvider { currently_used_api: 0, use_bincode: true, }; - // Set the initial API URL + // Set all API URLs - the client will try them in order with automatic failover provider.validator_client.change_base_urls( provider .nym_api_urls @@ -87,10 +87,18 @@ impl NymApiTopologyProvider { } self.currently_used_api = (self.currently_used_api + 1) % self.nym_api_urls.len(); - self.validator_client - .change_base_urls(vec![self.nym_api_urls[self.currently_used_api] - .clone() - .into()]) + + // Provide all URLs starting from the next one in rotation order + // This enables automatic failover to other endpoints + let rotated_urls: Vec<_> = self.nym_api_urls + .iter() + .cycle() + .skip(self.currently_used_api) + .take(self.nym_api_urls.len()) + .map(|u| u.clone().into()) + .collect(); + + self.validator_client.change_base_urls(rotated_urls) } async fn get_current_compatible_topology(&mut self) -> Option { diff --git a/nym-node/src/node/nym_apis_client.rs b/nym-node/src/node/nym_apis_client.rs index 7e6b490f56..2b7667bbcc 100644 --- a/nym-node/src/node/nym_apis_client.rs +++ b/nym-node/src/node/nym_apis_client.rs @@ -87,9 +87,18 @@ impl NymApisClient { if guard.currently_used_api != last_working_endpoint { drop(guard); let mut guard = self.inner.write().await; - let next_url = guard.available_urls[last_working_endpoint].clone(); guard.currently_used_api = last_working_endpoint; - guard.active_client.change_base_urls(vec![next_url.into()]); + + // Provide all URLs starting from the working endpoint for automatic failover + let rotated_urls: Vec<_> = guard.available_urls + .iter() + .cycle() + .skip(last_working_endpoint) + .take(guard.available_urls.len()) + .map(|u| u.clone().into()) + .collect(); + + guard.active_client.change_base_urls(rotated_urls); } Ok(res) @@ -123,6 +132,8 @@ impl InnerClient { let broadcast_fut = stream::iter(self.available_urls.clone()).for_each_concurrent(None, |url| { let mut nym_api = self.active_client.clone(); + // For broadcast, we intentionally set a single URL per client + // to ensure each endpoint receives the request nym_api.change_base_urls(vec![url.clone().into()]); let req_fut = req(nym_api, request_body); async move { @@ -170,6 +181,8 @@ impl InnerClient { .chain(self.available_urls.iter().enumerate().take(last_working)) { let mut nym_api = self.active_client.clone(); + // For exhaustive query, we test each endpoint individually in sequence + // to find a working one - so single URL is correct here nym_api.change_base_urls(vec![url.clone().into()]); let timeout_fut = sleep(timeout_duration);