From 4e9153b31c057c2518fba7c83cf4d47dc0caa167 Mon Sep 17 00:00:00 2001 From: durch Date: Fri, 22 Aug 2025 18:13:36 +0200 Subject: [PATCH] fix: provide all API URLs for automatic failover in endpoint rotation Previously, when rotating API endpoints, only a single URL was provided to the HTTP client, defeating the purpose of having multiple URLs for resilience. Changes: - NymApiTopologyProvider now provides all URLs in rotated order when switching endpoints - NymApisClient similarly provides all URLs starting from the working endpoint - Added clarifying comments for broadcast/exhaustive query methods where single URLs are intentionally used - This enables the HTTP client's built-in failover mechanism while maintaining endpoint rotation behavior The fix ensures that if the primary endpoint fails, the client can automatically failover to alternative endpoints without manual intervention, improving overall network resilience. --- .../topology_control/nym_api_provider.rs | 18 +++++++++++++----- nym-node/src/node/nym_apis_client.rs | 17 +++++++++++++++-- 2 files changed, 28 insertions(+), 7 deletions(-) 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);