From 9c6df6fe5b58cb5886f07d48c0fa85fcb0cd37bd Mon Sep 17 00:00:00 2001 From: durch Date: Fri, 15 Aug 2025 19:32:51 +0200 Subject: [PATCH] feat: migrate NymApiClient usage to unified HTTP client - Wire up domain fronting configuration in NymNetworkDetails - Implement NymApiClientExt trait for base nym_http_api_client::Client - Migrate direct NymApiClient usage in multiple components: - nym-network-monitor - verloc measurements - connection tester - coconut/ecash client - validator rewarder - Add Copy derive to ApiUrlConst to enable iteration - Update error handling and Display implementations This enables automatic domain fronting for all Nym API calls via the configured CDN front hosts. --- common/client-core/src/init/helpers.rs | 70 ++++++++++++++++--- .../validator-client/src/coconut/mod.rs | 18 +++-- .../validator-client/src/connection_tester.rs | 19 +++-- .../validator-client/src/nym_api/mod.rs | 1 + common/network-defaults/src/network.rs | 5 +- common/verloc/src/measurements/measurer.rs | 16 +++-- nym-network-monitor/src/main.rs | 10 +-- .../src/rewarder/nyxd_client.rs | 14 +++- .../src/rewarder/ticketbook_issuance/types.rs | 3 +- 9 files changed, 123 insertions(+), 33 deletions(-) diff --git a/common/client-core/src/init/helpers.rs b/common/client-core/src/init/helpers.rs index 1ca4b68191..834205a363 100644 --- a/common/client-core/src/init/helpers.rs +++ b/common/client-core/src/init/helpers.rs @@ -7,7 +7,7 @@ use futures::{SinkExt, StreamExt}; use nym_crypto::asymmetric::ed25519; use nym_gateway_client::GatewayClient; use nym_topology::node::RoutingNode; -use nym_validator_client::client::IdentityKeyRef; +use nym_validator_client::client::{IdentityKeyRef, NymApiClientExt}; use nym_validator_client::UserAgent; use rand::{seq::SliceRandom, Rng}; #[cfg(unix)] @@ -83,6 +83,48 @@ struct GatewayWithLatency<'a, G: ConnectableGateway> { latency: Duration, } +// Helper to collect all pages of entry nodes - replicates NymApiClient's convenience method +async fn get_all_basic_entry_nodes_with_metadata( + client: &nym_http_api_client::Client, + use_bincode: bool, +) -> Result { + // Get first page to obtain metadata + let mut page = 0; + let res = client + .get_basic_entry_assigned_nodes_v2(false, Some(page), None, use_bincode) + .await?; + let mut nodes = res.nodes.data; + let metadata = res.metadata; + + if res.nodes.pagination.total == nodes.len() { + return Ok(SkimmedNodesWithMetadata::new(nodes, metadata)); + } + + page += 1; + + // Collect remaining pages + loop { + let mut res = client + .get_basic_entry_assigned_nodes_v2(false, Some(page), None, use_bincode) + .await?; + + if !metadata.consistency_check(&res.metadata) { + return Err(ClientCoreError::ValidatorClientError( + nym_validator_client::ValidatorClientError::InconsistentPagedMetadata, + )); + } + + nodes.append(&mut res.nodes.data); + if nodes.len() < res.nodes.pagination.total { + page += 1 + } else { + break; + } + } + + Ok(SkimmedNodesWithMetadata::new(nodes, metadata)) +} + impl<'a, G: ConnectableGateway> GatewayWithLatency<'a, G> { fn new(gateway: &'a G, latency: Duration) -> Self { GatewayWithLatency { gateway, latency } @@ -99,16 +141,28 @@ pub async fn gateways_for_init( let nym_api = nym_apis .choose(rng) .ok_or(ClientCoreError::ListOfNymApisIsEmpty)?; - let client = if let Some(user_agent) = user_agent { - nym_validator_client::client::NymApiClient::new_with_user_agent(nym_api.clone(), user_agent) - } else { - nym_validator_client::client::NymApiClient::new(nym_api.clone()) - }; + + // Use the unified HTTP client directly with optional user agent + let mut builder = nym_http_api_client::Client::builder(nym_api.clone()) + .map_err(|e| ClientCoreError::ValidatorClientError( + nym_validator_client::ValidatorClientError::MalformedUrlProvided(e), + ))? + .with_bincode(); // Use bincode for better performance + + if let Some(user_agent) = user_agent { + builder = builder.with_user_agent(user_agent); + } + + let client = builder + .build::() + .map_err(|e| ClientCoreError::ValidatorClientError( + nym_validator_client::ValidatorClientError::NymAPIError { source: e }, + ))?; tracing::debug!("Fetching list of gateways from: {nym_api}"); - let gateways = client - .get_all_basic_entry_assigned_nodes_with_metadata() + // Use our helper to handle pagination + let gateways = get_all_basic_entry_nodes_with_metadata(&client, true) .await? .nodes; info!("nym api reports {} gateways", gateways.len()); diff --git a/common/client-libs/validator-client/src/coconut/mod.rs b/common/client-libs/validator-client/src/coconut/mod.rs index 67974c0170..c905868361 100644 --- a/common/client-libs/validator-client/src/coconut/mod.rs +++ b/common/client-libs/validator-client/src/coconut/mod.rs @@ -3,7 +3,7 @@ use crate::nyxd::contract_traits::{DkgQueryClient, PagedDkgQueryClient}; use crate::nyxd::error::NyxdError; -use crate::NymApiClient; +use crate::nym_api::NymApiClientExt; use nym_coconut_dkg_common::types::{EpochId, NodeIndex}; use nym_coconut_dkg_common::verification_key::ContractVKShare; use nym_compact_ecash::error::CompactEcashError; @@ -15,7 +15,7 @@ use url::Url; // TODO: it really doesn't feel like this should live in this crate. #[derive(Clone)] pub struct EcashApiClient { - pub api_client: NymApiClient, + pub api_client: nym_http_api_client::Client, pub verification_key: VerificationKeyAuth, pub node_id: NodeIndex, pub cosmos_address: cosmrs::AccountId, @@ -25,10 +25,10 @@ impl Display for EcashApiClient { fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { write!( f, - "[id: {}] {} @ {}", + "[id: {}] {} @ {:?}", self.node_id, self.cosmos_address, - self.api_client.api_url() + self.api_client.base_urls() ) } } @@ -60,6 +60,9 @@ pub enum EcashApiError { source: CompactEcashError, }, + #[error("failed to create API client: {0}")] + ClientError(String), + #[error("the provided account address is malformed: {source}")] MalformedAccountAddress { #[from] @@ -89,8 +92,13 @@ impl TryFrom for EcashApiClient { // In non-client applications this resolver can cause warning logs about H2 connection // failure. This indicates that the long lived https connection was closed by the remote // peer and the resolver will have to reconnect. It should not impact actual functionality + let api_client = nym_http_api_client::Client::builder::<_, nym_api_requests::models::RequestError>(url_address) + .map_err(|e| EcashApiError::ClientError(e.to_string()))? + .build::() + .map_err(|e| EcashApiError::ClientError(e.to_string()))?; + Ok(EcashApiClient { - api_client: NymApiClient::new(url_address), + api_client, verification_key: VerificationKeyAuth::try_from_bs58(&share.share)?, node_id: share.node_index, cosmos_address: share.owner.as_str().parse()?, diff --git a/common/client-libs/validator-client/src/connection_tester.rs b/common/client-libs/validator-client/src/connection_tester.rs index 3e71a3c000..b5c7617d9e 100644 --- a/common/client-libs/validator-client/src/connection_tester.rs +++ b/common/client-libs/validator-client/src/connection_tester.rs @@ -1,7 +1,8 @@ use crate::nyxd::contract_traits::MixnetQueryClient; use crate::nyxd::error::NyxdError; use crate::nyxd::Config as ClientConfig; -use crate::{NymApiClient, QueryHttpRpcNyxdClient, ValidatorClientError}; +use crate::nym_api::NymApiClientExt; +use crate::{QueryHttpRpcNyxdClient, ValidatorClientError}; use colored::Colorize; use core::fmt; use itertools::Itertools; @@ -87,8 +88,16 @@ fn setup_connection_tests( } }); - let api_connection_test_clients = api_urls.map(|(network, url)| { - ClientForConnectionTest::Api(network, url.clone(), NymApiClient::new(url)) + let api_connection_test_clients = api_urls.filter_map(|(network, url)| { + match nym_http_api_client::Client::builder(url.clone()) + .and_then(|b| b.build::()) + { + Ok(client) => Some(ClientForConnectionTest::Api(network, url, client)), + Err(err) => { + eprintln!("Failed to create API client for {}: {err}", network.network_name); + None + } + } }); nyxd_connection_test_clients.chain(api_connection_test_clients) @@ -160,7 +169,7 @@ async fn test_nyxd_connection( async fn test_nym_api_connection( network: NymNetworkDetails, url: &Url, - client: &NymApiClient, + client: &nym_http_api_client::Client, ) -> ConnectionResult { let result = match timeout( Duration::from_secs(CONNECTION_TEST_TIMEOUT_SEC), @@ -186,7 +195,7 @@ async fn test_nym_api_connection( enum ClientForConnectionTest { Nyxd(NymNetworkDetails, Url, Box), - Api(NymNetworkDetails, Url, NymApiClient), + Api(NymNetworkDetails, Url, nym_http_api_client::Client), } impl ClientForConnectionTest { diff --git a/common/client-libs/validator-client/src/nym_api/mod.rs b/common/client-libs/validator-client/src/nym_api/mod.rs index 4e1ce0b286..fa0a8e2eb7 100644 --- a/common/client-libs/validator-client/src/nym_api/mod.rs +++ b/common/client-libs/validator-client/src/nym_api/mod.rs @@ -1375,6 +1375,7 @@ pub trait NymApiClientExt: ApiClient { } } +// Client is already nym_http_api_client::Client (re-exported above), so just one impl needed #[cfg_attr(target_arch = "wasm32", async_trait(?Send))] #[cfg_attr(not(target_arch = "wasm32"), async_trait)] impl NymApiClientExt for Client {} diff --git a/common/network-defaults/src/network.rs b/common/network-defaults/src/network.rs index e8fd8fcfa8..f75b5b7286 100644 --- a/common/network-defaults/src/network.rs +++ b/common/network-defaults/src/network.rs @@ -55,6 +55,7 @@ pub struct ApiUrl { pub front_hosts: Option>, } +#[derive(Copy, Clone)] pub struct ApiUrlConst<'a> { pub url: &'a str, pub front_hosts: Option<&'a [&'a str]>, @@ -188,8 +189,8 @@ impl NymNetworkDetails { ), }, nym_vpn_api_url: parse_optional_str(mainnet::NYM_VPN_API), - nym_api_urls: None, - nym_vpn_api_urls: None, + nym_api_urls: Some(mainnet::NYM_APIS.iter().copied().map(Into::into).collect()), + nym_vpn_api_urls: Some(mainnet::NYM_VPN_APIS.iter().copied().map(Into::into).collect()), } } diff --git a/common/verloc/src/measurements/measurer.rs b/common/verloc/src/measurements/measurer.rs index 5831551363..d9a27254f0 100644 --- a/common/verloc/src/measurements/measurer.rs +++ b/common/verloc/src/measurements/measurer.rs @@ -10,7 +10,7 @@ use futures::StreamExt; use nym_crypto::asymmetric::ed25519; use nym_task::ShutdownToken; use nym_validator_client::models::NymNodeDescription; -use nym_validator_client::NymApiClient; +use nym_validator_client::nym_api::NymApiClientExt; use rand::prelude::SliceRandom; use rand::thread_rng; use std::net::SocketAddr; @@ -135,10 +135,16 @@ impl VerlocMeasurer { let mut api_endpoints = self.config.nym_api_urls.clone(); api_endpoints.shuffle(&mut thread_rng()); for api_endpoint in api_endpoints { - let client = NymApiClient::new_with_user_agent( - api_endpoint.clone(), - self.config.user_agent.clone(), - ); + let client = match nym_http_api_client::Client::builder(api_endpoint.clone()) + .and_then(|b| b.with_user_agent(self.config.user_agent.clone()) + .build::()) + { + Ok(c) => c, + Err(err) => { + warn!("failed to create client for {api_endpoint}: {err}"); + continue; + } + }; match client.get_all_described_nodes().await { Ok(res) => return Some(res), Err(err) => { diff --git a/nym-network-monitor/src/main.rs b/nym-network-monitor/src/main.rs index d0d04d62c6..b336b3dac6 100644 --- a/nym-network-monitor/src/main.rs +++ b/nym-network-monitor/src/main.rs @@ -12,6 +12,7 @@ use nym_sdk::mixnet::{self, MixnetClient}; use nym_sphinx::chunking::monitoring; use nym_topology::provider_trait::ToTopologyMetadata; use nym_topology::{HardcodedTopologyProvider, NymTopology}; +use nym_validator_client::nym_api::NymApiClientExt; use std::fs::File; use std::io::Write; use std::sync::LazyLock; @@ -160,10 +161,11 @@ async fn nym_topology_from_env() -> anyhow::Result { let api_url = std::env::var(NYM_API)?; info!("Generating topology from {api_url}"); - let client = nym_validator_client::client::NymApiClient::new_with_user_agent( - api_url.parse()?, - bin_info!(), - ); + let client = nym_http_api_client::Client::builder(api_url.parse()?) + .map_err(anyhow::Error::from)? + .with_user_agent(bin_info!()) + .build::() + .map_err(anyhow::Error::from)?; let rewarded_set = client.get_current_rewarded_set().await?; diff --git a/nym-validator-rewarder/src/rewarder/nyxd_client.rs b/nym-validator-rewarder/src/rewarder/nyxd_client.rs index bf2bd9e43c..761b4416c8 100644 --- a/nym-validator-rewarder/src/rewarder/nyxd_client.rs +++ b/nym-validator-rewarder/src/rewarder/nyxd_client.rs @@ -15,7 +15,7 @@ use nym_validator_client::nyxd::module_traits::staking::{ use nym_validator_client::nyxd::{ AccountId, Coin, CosmWasmClient, Hash, PageRequest, StakingQueryClient, }; -use nym_validator_client::{nyxd, DirectSigningHttpRpcNyxdClient, NymApiClient}; +use nym_validator_client::{nyxd, DirectSigningHttpRpcNyxdClient}; use std::collections::HashMap; use std::ops::Deref; use std::sync::Arc; @@ -139,10 +139,20 @@ impl NyxdClient { continue; }; + let api_client = match nym_http_api_client::Client::builder(api_address) + .and_then(|b| b.build::()) + { + Ok(client) => client, + Err(err) => { + error!("Failed to create API client for issuer {}: {}", info.assigned_index, err); + continue; + } + }; + issuers.push(CredentialIssuer { public_key, operator_account: addr_to_account_id(share.owner), - api_client: NymApiClient::new(api_address), + api_client, verification_key, node_id: info.assigned_index, }) diff --git a/nym-validator-rewarder/src/rewarder/ticketbook_issuance/types.rs b/nym-validator-rewarder/src/rewarder/ticketbook_issuance/types.rs index dc396a7321..28ac10a785 100644 --- a/nym-validator-rewarder/src/rewarder/ticketbook_issuance/types.rs +++ b/nym-validator-rewarder/src/rewarder/ticketbook_issuance/types.rs @@ -7,7 +7,6 @@ use nym_coconut_dkg_common::types::NodeIndex; use nym_compact_ecash::VerificationKeyAuth; use nym_crypto::asymmetric::ed25519; use nym_validator_client::nyxd::{AccountId, Coin}; -use nym_validator_client::NymApiClient; use std::fmt::{Display, Formatter}; use tracing::info; @@ -68,7 +67,7 @@ impl TicketbookIssuanceResults { pub struct CredentialIssuer { pub public_key: ed25519::PublicKey, pub operator_account: AccountId, - pub api_client: NymApiClient, + pub api_client: nym_http_api_client::Client, pub verification_key: VerificationKeyAuth, pub node_id: NodeIndex, }