Compare commits

...

6 Commits

Author SHA1 Message Date
Bogdan-Ștefan Neacşu 1898853263 Use default value for the ports until api is deployed 2025-09-04 11:42:11 +03:00
durch 4e5ccf7926 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.
2025-08-29 13:37:45 +02:00
durch 0a8eb940bb feat: complete migration to unified HTTP client and fix all compilation errors
- Added missing NymApiClientExt trait methods (get_all_expanded_nodes, change_base_urls)
- Fixed all compilation errors across the workspace
- Updated nym-node to use unified client instead of deprecated NymApiClient
- Fixed type conversions for RewardedSetResponse → EpochRewardedSet
- Added nym-http-api-client dependency where needed
- Updated all examples and documentation to use new client API
2025-08-29 13:36:22 +02:00
durch 34fb67602c fix: resolve all compilation errors after NymApiClient migration
- Add missing nym-http-api-client dependencies to multiple crates
- Add NymApiClientExt trait imports where needed
- Fix type mismatches from NymApiClient to unified Client
- Add error conversions for NymAPIError in various error enums
- Implement missing trait methods (get_current_rewarded_set, get_all_basic_nodes_with_metadata, get_all_described_nodes)
- Fix type conversions for RewardedSetResponse in network monitor
- Update all API client instantiation to use new unified HTTP client
2025-08-29 13:35:41 +02:00
durch 766ae8dd8e 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.
2025-08-29 13:33:37 +02:00
durch bd1fd73ba0 feat: unify HTTP client creation and enable domain fronting
Enhanced the base nym_http_api_client to reduce fragmentation and enable domain fronting:

- Added SerializationFormat enum for explicit JSON/bincode choice (no auto-detection)
- Added from_network() method to create clients from NymNetworkDetails with domain fronting
- Added with_bincode() builder method for explicit serialization configuration
- Set Accept header based on serialization preference
- Added deprecation paths for NymApiClient wrapper and nym_api::Client re-export
- Enabled domain fronting support via network defaults feature

This is part of a broader effort to consolidate HTTP client implementations across the codebase,
reducing ~500 lines of wrapper code and providing automatic domain fronting for censorship resistance.
2025-08-29 13:33:37 +02:00
61 changed files with 831 additions and 223 deletions
Generated
+15
View File
@@ -4863,6 +4863,7 @@ dependencies = [
"nym-ecash-signer-check",
"nym-ecash-time",
"nym-gateway-client",
"nym-http-api-client",
"nym-http-api-common",
"nym-mixnet-contract-common",
"nym-node-requests",
@@ -5074,6 +5075,7 @@ dependencies = [
"nym-crypto",
"nym-ecash-contract-common",
"nym-ecash-time",
"nym-http-api-client",
"nym-id",
"nym-mixnet-contract-common",
"nym-multisig-contract-common",
@@ -5162,6 +5164,7 @@ dependencies = [
"nym-http-api-client",
"nym-id",
"nym-mixnet-client",
"nym-mixnet-contract-common",
"nym-network-defaults",
"nym-nonexhaustive-delayqueue",
"nym-pemstore",
@@ -5514,6 +5517,7 @@ dependencies = [
"nym-crypto",
"nym-ecash-contract-common",
"nym-ecash-time",
"nym-http-api-client",
"nym-network-defaults",
"nym-serde-helpers",
"nym-validator-client",
@@ -5613,6 +5617,7 @@ version = "0.1.0"
dependencies = [
"futures",
"nym-ecash-signer-check-types",
"nym-http-api-client",
"nym-network-defaults",
"nym-validator-client",
"semver 1.0.26",
@@ -5885,6 +5890,7 @@ dependencies = [
"mime",
"nym-bin-common",
"nym-http-api-common",
"nym-network-defaults",
"once_cell",
"reqwest 0.12.22",
"serde",
@@ -6145,6 +6151,8 @@ dependencies = [
"nym-client-core",
"nym-crypto",
"nym-gateway-requests",
"nym-http-api-client",
"nym-mixnet-contract-common",
"nym-network-defaults",
"nym-sdk",
"nym-sphinx",
@@ -6612,6 +6620,7 @@ dependencies = [
"nym-credentials-interface",
"nym-crypto",
"nym-gateway-requests",
"nym-http-api-client",
"nym-network-defaults",
"nym-ordered-buffer",
"nym-service-providers-common",
@@ -7185,6 +7194,7 @@ dependencies = [
"nym-credentials-interface",
"nym-crypto",
"nym-ecash-time",
"nym-http-api-client",
"nym-network-defaults",
"nym-pemstore",
"nym-serde-helpers",
@@ -7214,7 +7224,9 @@ dependencies = [
"bytes",
"futures",
"humantime",
"nym-api-requests",
"nym-crypto",
"nym-http-api-client",
"nym-task",
"nym-validator-client",
"rand 0.8.5",
@@ -10195,6 +10207,7 @@ dependencies = [
"nym-crypto",
"nym-ecash-contract-common",
"nym-group-contract-common",
"nym-http-api-client",
"nym-mixnet-contract-common",
"nym-multisig-contract-common",
"nym-pemstore",
@@ -11317,6 +11330,7 @@ dependencies = [
"clap",
"comfy-table",
"nym-bin-common",
"nym-http-api-client",
"nym-network-defaults",
"nym-validator-client",
"serde",
@@ -11547,6 +11561,7 @@ dependencies = [
"nym-credential-storage",
"nym-crypto",
"nym-gateway-client",
"nym-http-api-client",
"nym-sphinx",
"nym-sphinx-acknowledgements",
"nym-statistics-common",
+1 -1
View File
@@ -13,7 +13,7 @@ use nym_credentials_interface::{
};
use nym_ecash_time::Date;
use nym_validator_client::coconut::all_ecash_api_clients;
use nym_validator_client::nym_api::EpochId;
use nym_validator_client::nym_api::{EpochId, NymApiClientExt};
use nym_validator_client::nyxd::contract_traits::DkgQueryClient;
use nym_validator_client::EcashApiClient;
use rand::prelude::SliceRandom;
+1
View File
@@ -53,6 +53,7 @@ nym-client-core-config-types = { path = "./config-types", features = [
nym-client-core-surb-storage = { path = "./surb-storage" }
nym-client-core-gateways-storage = { path = "./gateways-storage" }
nym-ecash-time = { path = "../ecash-time" }
nym-mixnet-contract-common = { path = "../cosmwasm-smart-contracts/mixnet-contract" }
[target."cfg(not(target_arch = \"wasm32\"))".dependencies]
nym-mixnet-client = { path = "../client-libs/mixnet-client", default-features = false }
@@ -57,7 +57,7 @@ use nym_task::{TaskClient, TaskHandle};
use nym_topology::provider_trait::TopologyProvider;
use nym_topology::HardcodedTopologyProvider;
use nym_validator_client::nym_api::NymApiClientExt;
use nym_validator_client::{nyxd::contract_traits::DkgQueryClient, NymApiClient, UserAgent};
use nym_validator_client::{nyxd::contract_traits::DkgQueryClient, UserAgent};
use rand::prelude::SliceRandom;
use rand::rngs::OsRng;
use rand::thread_rng;
@@ -566,7 +566,7 @@ where
custom_provider: Option<Box<dyn TopologyProvider + Send + Sync>>,
config_topology: config::Topology,
nym_api_urls: Vec<Url>,
nym_api_client: NymApiClient,
nym_api_client: nym_http_api_client::Client,
) -> Box<dyn TopologyProvider + Send + Sync> {
// if no custom provider was ... provided ..., create one using nym-api
custom_provider.unwrap_or_else(|| {
@@ -749,21 +749,42 @@ where
setup_gateway(setup_method, key_store, details_store).await
}
fn construct_nym_api_client(config: &Config, user_agent: Option<UserAgent>) -> NymApiClient {
fn construct_nym_api_client(
config: &Config,
user_agent: Option<UserAgent>,
) -> Result<nym_http_api_client::Client, ClientCoreError> {
let mut nym_api_urls = config.get_nym_api_endpoints();
nym_api_urls.shuffle(&mut thread_rng());
let mut builder = nym_http_api_client::Client::builder::<
_,
nym_validator_client::models::RequestError,
>(nym_api_urls[0].clone())
.map_err(|e| ClientCoreError::NymApiQueryFailure {
source: nym_validator_client::nym_api::error::NymAPIError::GenericRequestFailure(
e.to_string(),
),
})?;
if let Some(user_agent) = user_agent {
NymApiClient::new_with_user_agent(nym_api_urls[0].clone(), user_agent)
} else {
NymApiClient::new(nym_api_urls[0].clone())
builder = builder.with_user_agent(user_agent);
}
builder = builder.with_bincode();
builder
.build::<nym_validator_client::models::RequestError>()
.map_err(|e| ClientCoreError::NymApiQueryFailure {
source: nym_validator_client::nym_api::error::NymAPIError::GenericRequestFailure(
e.to_string(),
),
})
}
async fn determine_key_rotation_state(
client: &NymApiClient,
client: &nym_http_api_client::Client,
) -> Result<KeyRotationConfig, ClientCoreError> {
Ok(client.nym_api.get_key_rotation_info().await?.into())
Ok(client.get_key_rotation_info().await?.into())
}
pub async fn start_base(mut self) -> Result<BaseClient, ClientCoreError>
@@ -830,7 +851,7 @@ where
.dkg_query_client
.map(|client| BandwidthController::new(credential_store, client));
let nym_api_client = Self::construct_nym_api_client(&self.config, self.user_agent.clone());
let nym_api_client = Self::construct_nym_api_client(&self.config, self.user_agent.clone())?;
let key_rotation_config = Self::determine_key_rotation_state(&nym_api_client).await?;
let topology_provider = Self::setup_topology_provider(
@@ -2,8 +2,10 @@
// SPDX-License-Identifier: Apache-2.0
use async_trait::async_trait;
use nym_mixnet_contract_common::EpochRewardedSet;
use nym_topology::provider_trait::{ToTopologyMetadata, TopologyProvider};
use nym_topology::NymTopology;
use nym_validator_client::nym_api::NymApiClientExt;
use rand::prelude::SliceRandom;
use rand::thread_rng;
use std::cmp::min;
@@ -39,30 +41,43 @@ impl Config {
pub struct NymApiTopologyProvider {
config: Config,
validator_client: nym_validator_client::client::NymApiClient,
validator_client: nym_http_api_client::Client,
nym_api_urls: Vec<Url>,
currently_used_api: usize,
use_bincode: bool,
}
impl NymApiTopologyProvider {
pub fn new(
config: impl Into<Config>,
mut nym_api_urls: Vec<Url>,
mut validator_client: nym_validator_client::client::NymApiClient,
validator_client: nym_http_api_client::Client,
) -> Self {
nym_api_urls.shuffle(&mut thread_rng());
validator_client.change_nym_api(nym_api_urls[0].clone());
NymApiTopologyProvider {
let mut provider = NymApiTopologyProvider {
config: config.into(),
validator_client,
nym_api_urls,
currently_used_api: 0,
}
use_bincode: true,
};
// Set all API URLs - the client will try them in order with automatic failover
provider.validator_client.change_base_urls(
provider
.nym_api_urls
.iter()
.map(|u| u.clone().into())
.collect(),
);
provider
}
pub fn disable_bincode(&mut self) {
self.validator_client.use_bincode = false;
self.use_bincode = false;
// Note: The unified client doesn't support toggling bincode after creation.
// This would require recreating the client without bincode.
// For now, we'll track the preference but it won't take effect.
warn!("Disabling bincode on existing client is not currently supported");
}
fn use_next_nym_api(&mut self) {
@@ -72,8 +87,19 @@ impl NymApiTopologyProvider {
}
self.currently_used_api = (self.currently_used_api + 1) % self.nym_api_urls.len();
self.validator_client
.change_nym_api(self.nym_api_urls[self.currently_used_api].clone())
// 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<NymTopology> {
@@ -99,8 +125,13 @@ impl NymApiTopologyProvider {
.filter(|n| n.performance.round_to_integer() >= self.config.min_node_performance())
.collect::<Vec<_>>();
NymTopology::new(metadata.to_topology_metadata(), rewarded_set, Vec::new())
.with_skimmed_nodes(&nodes_filtered)
let epoch_rewarded_set: EpochRewardedSet = rewarded_set.into();
NymTopology::new(
metadata.to_topology_metadata(),
epoch_rewarded_set,
Vec::new(),
)
.with_skimmed_nodes(&nodes_filtered)
} else {
// if we're not using extended topology, we're only getting active set mixnodes and gateways
@@ -148,8 +179,13 @@ impl NymApiTopologyProvider {
}
}
NymTopology::new(metadata.to_topology_metadata(), rewarded_set, Vec::new())
.with_skimmed_nodes(&nodes)
let epoch_rewarded_set: EpochRewardedSet = rewarded_set.into();
NymTopology::new(
metadata.to_topology_metadata(),
epoch_rewarded_set,
Vec::new(),
)
.with_skimmed_nodes(&nodes)
};
if !topology.is_minimally_routable() {
+67 -8
View File
@@ -7,7 +7,8 @@ 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::nym_nodes::SkimmedNodesWithMetadata;
use nym_validator_client::UserAgent;
use rand::{seq::SliceRandom, Rng};
#[cfg(unix)]
@@ -83,6 +84,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<SkimmedNodesWithMetadata, ClientCoreError> {
// 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 +142,32 @@ pub async fn gateways_for_init<R: Rng>(
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::NymAPIError { source: 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::<nym_validator_client::models::RequestError>()
.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());
@@ -5,8 +5,8 @@ use crate::nyxd::{self, NyxdClient};
use crate::signing::direct_wallet::DirectSecp256k1HdWallet;
use crate::signing::signer::{NoSigner, OfflineSigner};
use crate::{
nym_api, DirectSigningReqwestRpcValidatorClient, QueryReqwestRpcValidatorClient,
ReqwestRpcClient, ValidatorClientError,
DirectSigningReqwestRpcValidatorClient, QueryReqwestRpcValidatorClient, ReqwestRpcClient,
ValidatorClientError,
};
use nym_api_requests::ecash::models::{
AggregatedCoinIndicesSignatureResponse, AggregatedExpirationDateSignatureResponse,
@@ -153,7 +153,7 @@ impl Config {
pub struct Client<C, S = NoSigner> {
// ideally they would have been read-only, but unfortunately rust doesn't have such features
// #[deprecated(note = "please use `nym_api_client` instead")]
pub nym_api: nym_api::Client,
pub nym_api: nym_http_api_client::Client,
// pub nym_api_client: NymApiClient,
pub nyxd: NyxdClient<C, S>,
}
@@ -214,7 +214,7 @@ impl Client<ReqwestRpcClient> {
impl<C> Client<C> {
pub fn new_with_rpc_client(config: Config, rpc_client: C) -> Self {
let nym_api_client = nym_api::Client::new(config.api_url.clone(), None);
let nym_api_client = nym_http_api_client::Client::new(config.api_url.clone(), None);
Client {
nym_api: nym_api_client,
@@ -228,7 +228,7 @@ impl<C, S> Client<C, S> {
where
S: OfflineSigner,
{
let nym_api_client = nym_api::Client::new(config.api_url.clone(), None);
let nym_api_client = nym_http_api_client::Client::new(config.api_url.clone(), None);
Client {
nym_api: nym_api_client,
@@ -385,38 +385,25 @@ impl<C, S> Client<C, S> {
}
}
/// DEPRECATED: Use nym_http_api_client::Client with from_network() or with_bincode() instead
#[deprecated(
since = "1.2.0",
note = "Use nym_http_api_client::Client::from_network() or ClientBuilder::with_bincode() instead"
)]
#[derive(Clone)]
pub struct NymApiClient {
pub use_bincode: bool,
pub nym_api: nym_api::Client,
pub nym_api: nym_http_api_client::Client,
// TODO: perhaps if we really need it at some (currently I don't see any reasons for it)
// we could re-implement the communication with the REST API on port 1317
}
impl From<nym_api::Client> for NymApiClient {
fn from(nym_api: nym_api::Client) -> Self {
NymApiClient {
use_bincode: false,
nym_api,
}
}
}
// we have to allow the use of deprecated method here as they're calling the deprecated trait methods
#[allow(deprecated)]
impl NymApiClient {
pub fn new(api_url: Url) -> Self {
let nym_api = nym_api::Client::new(api_url, None);
NymApiClient {
use_bincode: true,
nym_api,
}
}
#[cfg(not(target_arch = "wasm32"))]
pub fn new_with_timeout(api_url: Url, timeout: std::time::Duration) -> Self {
let nym_api = nym_api::Client::new(api_url, Some(timeout));
let nym_api = nym_http_api_client::Client::new(api_url, Some(timeout));
NymApiClient {
use_bincode: true,
@@ -431,7 +418,7 @@ impl NymApiClient {
}
pub fn new_with_user_agent(api_url: Url, user_agent: impl Into<UserAgent>) -> Self {
let nym_api = nym_api::Client::builder::<_, ValidatorClientError>(api_url)
let nym_api = nym_http_api_client::Client::builder::<_, ValidatorClientError>(api_url)
.expect("invalid api url")
.with_user_agent(user_agent.into())
.build::<ValidatorClientError>()
@@ -3,7 +3,6 @@
use crate::nyxd::contract_traits::{DkgQueryClient, PagedDkgQueryClient};
use crate::nyxd::error::NyxdError;
use crate::NymApiClient;
use nym_coconut_dkg_common::types::{EpochId, NodeIndex};
use nym_coconut_dkg_common::verification_key::ContractVKShare;
use nym_compact_ecash::error::CompactEcashError;
@@ -15,7 +14,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 +24,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 +59,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 +91,16 @@ impl TryFrom<ContractVKShare> 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::<nym_api_requests::models::RequestError>()
.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()?,
@@ -1,7 +1,8 @@
use crate::nym_api::NymApiClientExt;
use crate::nyxd::contract_traits::MixnetQueryClient;
use crate::nyxd::error::NyxdError;
use crate::nyxd::Config as ClientConfig;
use crate::{NymApiClient, QueryHttpRpcNyxdClient, ValidatorClientError};
use crate::{QueryHttpRpcNyxdClient, ValidatorClientError};
use colored::Colorize;
use core::fmt;
use itertools::Itertools;
@@ -87,8 +88,19 @@ fn setup_connection_tests<H: BuildHasher + 'static>(
}
});
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::<nym_api_requests::models::RequestError>())
{
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 +172,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 +198,7 @@ async fn test_nym_api_connection(
enum ClientForConnectionTest {
Nyxd(NymNetworkDetails, Url, Box<QueryHttpRpcNyxdClient>),
Api(NymNetworkDetails, Url, NymApiClient),
Api(NymNetworkDetails, Url, nym_http_api_client::Client),
}
impl ClientForConnectionTest {
@@ -14,7 +14,6 @@ pub mod signing;
pub use crate::error::ValidatorClientError;
pub use crate::rpc::reqwest::ReqwestRpcClient;
pub use crate::signing::direct_wallet::DirectSecp256k1HdWallet;
pub use client::NymApiClient;
pub use client::{Client, Config, EcashApiClient};
pub use nym_api_requests::*;
pub use nym_http_api_client::UserAgent;
@@ -3,6 +3,7 @@
use crate::nym_api::error::NymAPIError;
use crate::nym_api::routes::{ecash, CORE_STATUS_COUNT, SINCE_ARG};
use crate::nym_nodes::SkimmedNodesWithMetadata;
use async_trait::async_trait;
use nym_api_requests::ecash::models::{
AggregatedCoinIndicesSignatureResponse, AggregatedExpirationDateSignatureResponse,
@@ -37,7 +38,7 @@ pub use nym_api_requests::{
MixnodeStatusResponse, MixnodeUptimeHistoryResponse, RewardEstimationResponse,
StakeSaturationResponse, UptimeResponse,
},
nym_nodes::{CachedNodesResponse, SemiSkimmedNode, SkimmedNode},
nym_nodes::{CachedNodesResponse, SemiSkimmedNode, SemiSkimmedNodesWithMetadata, SkimmedNode},
NymNetworkDetailsResponse,
};
use nym_contracts_common::IdentityKey;
@@ -49,8 +50,8 @@ use time::format_description::BorrowedFormatItem;
use time::Date;
use tracing::instrument;
use crate::ValidatorClientError;
pub use nym_coconut_dkg_common::types::EpochId;
pub use nym_http_api_client::Client;
pub mod error;
pub mod routes;
@@ -62,6 +63,9 @@ pub fn rfc_3339_date() -> Vec<BorrowedFormatItem<'static>> {
#[cfg_attr(target_arch = "wasm32", async_trait(?Send))]
#[cfg_attr(not(target_arch = "wasm32"), async_trait)]
pub trait NymApiClientExt: ApiClient {
/// Get the current API URL being used by the client
fn api_url(&self) -> &url::Url;
async fn health(&self) -> Result<ApiHealthResponse, NymAPIError> {
self.get_json(
&[
@@ -241,6 +245,162 @@ pub trait NymApiClientExt: ApiClient {
.await
}
async fn get_current_rewarded_set(&self) -> Result<RewardedSetResponse, NymAPIError> {
self.get_rewarded_set().await
}
async fn get_all_basic_nodes_with_metadata(
&self,
) -> Result<SkimmedNodesWithMetadata, NymAPIError> {
// unroll first loop iteration in order to obtain the metadata
let mut page = 0;
let res = self
.get_basic_nodes_v2(false, Some(page), None, true)
.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;
loop {
let mut res = self
.get_basic_nodes_v2(false, Some(page), None, true)
.await?;
if !metadata.consistency_check(&res.metadata) {
// Create a custom error for inconsistent metadata
return Err(NymAPIError::EndpointFailure {
status: reqwest::StatusCode::INTERNAL_SERVER_ERROR,
error: nym_api_requests::models::RequestError::new(
"Inconsistent paged metadata",
),
});
}
nodes.append(&mut res.nodes.data);
if nodes.len() >= res.nodes.pagination.total {
break;
} else {
page += 1
}
}
Ok(SkimmedNodesWithMetadata::new(nodes, metadata))
}
async fn get_all_basic_active_mixing_assigned_nodes_with_metadata(
&self,
) -> Result<SkimmedNodesWithMetadata, NymAPIError> {
// Get all mixing nodes that are in the active/rewarded set
let mut page = 0;
let res = self
.get_basic_active_mixing_assigned_nodes_v2(false, Some(page), None, false)
.await?;
let metadata = res.metadata;
let mut nodes = res.nodes.data;
if res.nodes.pagination.total == nodes.len() {
return Ok(SkimmedNodesWithMetadata::new(nodes, metadata));
}
page += 1;
loop {
let res = self
.get_basic_active_mixing_assigned_nodes_v2(false, Some(page), None, false)
.await?;
if !metadata.consistency_check(&res.metadata) {
return Err(NymAPIError::EndpointFailure {
status: reqwest::StatusCode::INTERNAL_SERVER_ERROR,
error: nym_api_requests::models::RequestError::new(
"Inconsistent paged metadata",
),
});
}
nodes.append(&mut res.nodes.data.clone());
// Check if we've got all nodes
if nodes.len() >= res.nodes.pagination.total {
break;
} else {
page += 1;
}
}
Ok(SkimmedNodesWithMetadata::new(nodes, metadata))
}
async fn get_all_basic_entry_assigned_nodes_with_metadata(
&self,
) -> Result<SkimmedNodesWithMetadata, NymAPIError> {
// Get all nodes that can act as entry gateways
let mut page = 0;
let res = self
.get_basic_entry_assigned_nodes_v2(false, Some(page), None, false)
.await?;
let metadata = res.metadata;
let mut nodes = res.nodes.data;
if res.nodes.pagination.total == nodes.len() {
return Ok(SkimmedNodesWithMetadata::new(nodes, metadata));
}
page += 1;
loop {
let res = self
.get_basic_entry_assigned_nodes_v2(false, Some(page), None, false)
.await?;
if !metadata.consistency_check(&res.metadata) {
return Err(NymAPIError::EndpointFailure {
status: reqwest::StatusCode::INTERNAL_SERVER_ERROR,
error: nym_api_requests::models::RequestError::new(
"Inconsistent paged metadata",
),
});
}
nodes.append(&mut res.nodes.data.clone());
// Check if we've got all nodes
if nodes.len() >= res.nodes.pagination.total {
break;
} else {
page += 1;
}
}
Ok(SkimmedNodesWithMetadata::new(nodes, metadata))
}
async fn get_all_described_nodes(&self) -> Result<Vec<NymNodeDescription>, NymAPIError> {
// TODO: deal with paging in macro or some helper function or something, because it's the same pattern everywhere
let mut page = 0;
let mut descriptions = Vec::new();
loop {
let mut res = self.get_nodes_described(Some(page), None).await?;
descriptions.append(&mut res.data);
if descriptions.len() < res.pagination.total {
page += 1
} else {
break;
}
}
Ok(descriptions)
}
#[tracing::instrument(level = "debug", skip_all)]
async fn get_nym_nodes(
&self,
@@ -268,6 +428,25 @@ pub trait NymApiClientExt: ApiClient {
.await
}
async fn get_all_bonded_nym_nodes(&self) -> Result<Vec<NymNodeDetails>, ValidatorClientError> {
// TODO: deal with paging in macro or some helper function or something, because it's the same pattern everywhere
let mut page = 0;
let mut bonds = Vec::new();
loop {
let mut res = self.get_nym_nodes(Some(page), None).await?;
bonds.append(&mut res.data);
if bonds.len() < res.pagination.total {
page += 1
} else {
break;
}
}
Ok(bonds)
}
#[deprecated]
#[tracing::instrument(level = "debug", skip_all)]
async fn get_basic_mixnodes(&self) -> Result<CachedNodesResponse<SkimmedNode>, NymAPIError> {
@@ -1371,8 +1550,49 @@ pub trait NymApiClientExt: ApiClient {
)
.await
}
/// Method to change the base API URLs being used by the client
fn change_base_urls(&mut self, urls: Vec<url::Url>);
/// Retrieve expanded information for all bonded nodes on the network
async fn get_all_expanded_nodes(&self) -> Result<SemiSkimmedNodesWithMetadata, NymAPIError> {
// Unroll the first iteration to get the metadata
let mut page = 0;
let res = self.get_expanded_nodes(false, Some(page), None).await?;
let mut nodes = res.nodes.data;
let metadata = res.metadata;
if res.nodes.pagination.total == nodes.len() {
return Ok(SemiSkimmedNodesWithMetadata::new(nodes, metadata));
}
page += 1;
loop {
let mut res = self.get_expanded_nodes(false, Some(page), None).await?;
nodes.append(&mut res.nodes.data);
if nodes.len() < res.nodes.pagination.total {
page += 1
} else {
break;
}
}
Ok(SemiSkimmedNodesWithMetadata::new(nodes, metadata))
}
}
// 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 {}
impl NymApiClientExt for nym_http_api_client::Client {
fn api_url(&self) -> &url::Url {
self.current_url().as_ref()
}
fn change_base_urls(&mut self, urls: Vec<url::Url>) {
self.change_base_urls(urls.into_iter().map(|u| u.into()).collect());
}
}
+1
View File
@@ -38,6 +38,7 @@ cosmrs = { workspace = true }
cosmwasm-std = { workspace = true }
nym-validator-client = { path = "../client-libs/validator-client" }
nym-http-api-client = { path = "../http-api-client" }
nym-bin-common = { path = "../../common/bin-common", features = ["output_format"] }
nym-crypto = { path = "../../common/crypto", features = ["asymmetric"] }
nym-network-defaults = { path = "../network-defaults" }
+1 -1
View File
@@ -2,12 +2,12 @@
// SPDX-License-Identifier: Apache-2.0
use crate::context::errors::ContextError;
pub use nym_http_api_client::Client as NymApiClient;
use nym_network_defaults::{
setup_env,
var_names::{MIXNET_CONTRACT_ADDRESS, NYM_API, NYXD, VESTING_CONTRACT_ADDRESS},
NymNetworkDetails,
};
pub use nym_validator_client::nym_api::Client as NymApiClient;
use nym_validator_client::nyxd::{self, AccountId, NyxdClient};
use nym_validator_client::{
DirectSigningHttpRpcNyxdClient, DirectSigningHttpRpcValidatorClient, QueryHttpRpcNyxdClient,
@@ -14,7 +14,7 @@ use nym_api_requests::ecash::models::{BatchRedeemTicketsBody, VerifyEcashTicketB
use nym_credentials_interface::Bandwidth;
use nym_credentials_interface::{ClientTicket, TicketType};
use nym_validator_client::coconut::EcashApiError;
use nym_validator_client::nym_api::EpochId;
use nym_validator_client::nym_api::{EpochId, NymApiClientExt};
use nym_validator_client::nyxd::contract_traits::{
EcashSigningClient, MultisigQueryClient, MultisigSigningClient, PagedMultisigQueryClient,
};
@@ -354,7 +354,7 @@ impl CredentialHandler {
Err(err) => {
error!("failed to send ticket {ticket_id} for verification to ecash signer '{client}': {err}. if we don't reach quorum, we'll retry later");
Err(EcashTicketError::ApiFailure(EcashApiError::NymApi {
source: err,
source: nym_validator_client::ValidatorClientError::NymAPIError { source: err },
}))
}
}
+1
View File
@@ -22,6 +22,7 @@ nym-ecash-time = { path = "../ecash-time", features = ["expiration"] }
nym-credentials-interface = { path = "../credentials-interface" }
nym-crypto = { path = "../crypto" }
nym-api-requests = { path = "../../nym-api/nym-api-requests" }
nym-http-api-client = { path = "../http-api-client" }
nym-validator-client = { path = "../client-libs/validator-client", default-features = false }
nym-ecash-contract-common = { path = "../cosmwasm-smart-contracts/ecash-contract" }
nym-network-defaults = { path = "../network-defaults" }
@@ -15,7 +15,7 @@ use nym_credentials_interface::{
use nym_crypto::asymmetric::ed25519;
use nym_ecash_contract_common::deposit::DepositId;
use nym_ecash_time::{ecash_default_expiration_date, ecash_today, EcashTime};
use nym_validator_client::nym_api::EpochId;
use nym_validator_client::nym_api::{EpochId, NymApiClientExt};
use serde::{Deserialize, Serialize};
use time::Date;
@@ -116,7 +116,7 @@ impl IssuanceTicketBook {
pub async fn obtain_blinded_credential(
&self,
client: &nym_validator_client::client::NymApiClient,
client: &nym_http_api_client::Client,
request_body: &BlindSignRequestBody,
) -> Result<BlindedSignature, Error> {
let server_response = client.blind_sign(request_body).await?;
@@ -179,7 +179,7 @@ impl IssuanceTicketBook {
// ideally this would have been generic over credential type, but we really don't need secp256k1 keys for bandwidth vouchers
pub async fn obtain_partial_ticketbook_credential(
&self,
client: &nym_validator_client::client::NymApiClient,
client: &nym_http_api_client::Client,
signer_index: u64,
validator_vk: &VerificationKeyAuth,
signing_data: CredentialSigningData,
+1
View File
@@ -10,6 +10,7 @@ use nym_credentials_interface::{
VerificationKeyAuth, WalletSignatures,
};
use nym_validator_client::client::EcashApiClient;
use nym_validator_client::nym_api::NymApiClientExt;
// so we wouldn't break all the existing imports
pub use nym_ecash_time::{cred_exp_date, ecash_date_offset, ecash_today, EcashTime};
+4 -1
View File
@@ -4,7 +4,7 @@
use crate::ecash::bandwidth::issued::CURRENT_SERIALIZATION_REVISION;
use nym_credentials_interface::CompactEcashError;
use nym_crypto::asymmetric::x25519::KeyRecoveryError;
use nym_validator_client::ValidatorClientError;
use nym_validator_client::{nym_api::error::NymAPIError, ValidatorClientError};
use thiserror::Error;
#[derive(Debug, Error)]
@@ -37,6 +37,9 @@ pub enum Error {
#[error("Ran into a validator client error - {0}")]
ValidatorClientError(#[from] ValidatorClientError),
#[error("Nym API request failed - {0}")]
NymAPIError(#[from] NymAPIError),
#[error("Bandwidth operation overflowed. {0}")]
BandwidthOverflow(String),
+1
View File
@@ -22,6 +22,7 @@ url = { workspace = true }
nym-validator-client = { path = "../client-libs/validator-client" }
nym-network-defaults = { path = "../network-defaults" }
nym-ecash-signer-check-types = { path = "../ecash-signer-check-types" }
nym-http-api-client = { path = "../http-api-client" }
[lints]
workspace = true
+31 -25
View File
@@ -1,15 +1,14 @@
// Copyright 2025 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use crate::{LocalChainStatus, SigningStatus, TypedSignerResult};
use crate::{LocalChainStatus, SignerCheckError, SigningStatus, TypedSignerResult};
use nym_ecash_signer_check_types::dealer_information::RawDealerInformation;
use nym_ecash_signer_check_types::status::{SignerStatus, SignerTestResult};
use nym_validator_client::client::NymApiClientExt;
use nym_validator_client::models::BinaryBuildInformationOwned;
use nym_validator_client::nym_api::NymApiClientExt;
use nym_validator_client::nyxd::contract_traits::dkg_query_client::{
ContractVKShare, DealerDetails,
};
use nym_validator_client::NymApiClient;
use std::time::Duration;
use tracing::{error, warn};
use url::Url;
@@ -32,37 +31,38 @@ pub(crate) mod signing_status {
}
struct ClientUnderTest {
api_client: NymApiClient,
api_client: nym_http_api_client::Client,
build_info: Option<BinaryBuildInformationOwned>,
}
impl ClientUnderTest {
pub(crate) fn new(api_url: &Url) -> Self {
ClientUnderTest {
api_client: NymApiClient::new(api_url.clone()),
pub(crate) fn new(api_url: &Url) -> Result<Self, SignerCheckError> {
// The builder should not fail with a valid URL that's already parsed
// If it does fail, it's an internal error that we can't recover from
let api_client = nym_http_api_client::Client::builder(api_url.clone())?.build()?;
Ok(ClientUnderTest {
api_client,
build_info: None,
}
})
}
pub(crate) async fn try_retrieve_build_information(&mut self) -> bool {
match tokio::time::timeout(
Duration::from_secs(5),
self.api_client.nym_api.build_information(),
)
.await
match tokio::time::timeout(Duration::from_secs(5), self.api_client.build_information())
.await
{
Ok(Ok(build_information)) => {
self.build_info = Some(build_information);
true
}
Ok(Err(err)) => {
warn!("{}: failed to retrieve build information: {err}. the signer is most likely down", self.api_client.api_url());
warn!("{}: failed to retrieve build information: {err}. the signer is most likely down", self.api_client.current_url());
false
}
Err(_timeout) => {
warn!(
"{}: timed out while attempting to retrieve build information",
self.api_client.api_url()
self.api_client.current_url()
);
false
}
@@ -77,7 +77,7 @@ impl ClientUnderTest {
.inspect_err(|err| {
error!(
"ecash signer '{}' reports invalid version {}: {err}",
self.api_client.api_url(),
self.api_client.current_url(),
build_info.build_version
)
})
@@ -121,14 +121,14 @@ impl ClientUnderTest {
// check if it supports the current query
if self.supports_chain_status_query() {
return match self.api_client.nym_api.get_chain_blocks_status().await {
return match self.api_client.get_chain_blocks_status().await {
Ok(status) => LocalChainStatus::Reachable {
response: Box::new(status),
},
Err(err) => {
warn!(
"{}: failed to retrieve local chain status: {err}",
self.api_client.api_url()
self.api_client.current_url()
);
LocalChainStatus::Unreachable
}
@@ -136,14 +136,14 @@ impl ClientUnderTest {
}
// fallback to the legacy query
match self.api_client.nym_api.get_chain_status().await {
match self.api_client.get_chain_status().await {
Ok(status) => LocalChainStatus::ReachableLegacy {
response: Box::new(status),
},
Err(err) => {
warn!(
"{}: failed to retrieve [legacy] local chain status: {err}",
self.api_client.api_url()
self.api_client.current_url()
);
LocalChainStatus::Unreachable
}
@@ -158,14 +158,14 @@ impl ClientUnderTest {
// check if it supports the current query
if self.supports_signing_status_query() {
return match self.api_client.nym_api.get_signer_status().await {
return match self.api_client.get_signer_status().await {
Ok(response) => SigningStatus::Reachable {
response: Box::new(response),
},
Err(err) => {
warn!(
"{}: failed to retrieve signer chain status: {err}",
self.api_client.api_url()
self.api_client.current_url()
);
SigningStatus::Unreachable
}
@@ -173,14 +173,14 @@ impl ClientUnderTest {
}
// fallback to the legacy query
match self.api_client.nym_api.get_signer_information().await {
match self.api_client.get_signer_information().await {
Ok(status) => SigningStatus::ReachableLegacy {
response: Box::new(status),
},
Err(err) => {
warn!(
"{}: failed to retrieve [legacy] signer chain status: {err}",
self.api_client.api_url()
self.api_client.current_url()
);
// NOTE: this might equally mean the signing is disabled
SigningStatus::Unreachable
@@ -201,7 +201,13 @@ pub(crate) async fn check_client(
return SignerStatus::ProvidedInvalidDetails.with_details(dealer_information, dkg_epoch);
};
let mut client = ClientUnderTest::new(&parsed_information.announce_address);
let mut client = match ClientUnderTest::new(&parsed_information.announce_address) {
Ok(client) => client,
Err(err) => {
error!("failed to create client instance: {err}");
return SignerStatus::Unreachable.with_details(dealer_information, dkg_epoch);
}
};
// 8. check basic connection status - can you retrieve build information?
if !client.try_retrieve_build_information().await {
+7
View File
@@ -1,6 +1,7 @@
// Copyright 2025 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use nym_http_api_client::HttpClientError;
use nym_validator_client::nyxd::error::NyxdError;
use thiserror::Error;
@@ -11,6 +12,12 @@ pub enum SignerCheckError {
#[error("failed to query the DKG contract: {source}")]
DKGContractQueryFailure { source: NyxdError },
#[error("failed to build client: {source}")]
HttpClient {
#[from]
source: HttpClientError,
},
}
impl SignerCheckError {
+2
View File
@@ -13,6 +13,7 @@ license.workspace = true
[features]
default=["tunneling"]
tunneling=[]
network-defaults = ["dep:nym-network-defaults"]
[dependencies]
async-trait = { workspace = true }
@@ -34,6 +35,7 @@ mime = { workspace = true }
nym-http-api-common = { path = "../http-api-common", default-features = false }
nym-bin-common = { path = "../bin-common" }
nym-network-defaults = { path = "../network-defaults", optional = true }
[target."cfg(not(target_arch = \"wasm32\"))".dependencies]
hickory-resolver = { workspace = true, features = ["https-ring", "tls-ring", "webpki-roots"] }
+4
View File
@@ -54,10 +54,14 @@ impl Front {
#[derive(Debug, Default, PartialEq, Clone)]
#[cfg(feature = "tunneling")]
/// Policy for when to use domain fronting for HTTP requests.
pub enum FrontPolicy {
/// Always use domain fronting for all requests.
Always,
/// Only use domain fronting when retrying failed requests.
OnRetry,
#[default]
/// Never use domain fronting.
Off,
}
+105 -9
View File
@@ -162,6 +162,8 @@ use std::sync::Arc;
#[cfg(feature = "tunneling")]
mod fronted;
#[cfg(feature = "tunneling")]
pub use fronted::FrontPolicy;
mod url;
pub use url::{IntoUrl, Url};
mod user_agent;
@@ -192,6 +194,15 @@ pub type Params<'a, K, V> = &'a [(K, V)];
/// Empty collection of HTTP Request Parameters.
pub const NO_PARAMS: Params<'_, &'_ str, &'_ str> = &[];
/// Serialization format for API requests and responses
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum SerializationFormat {
/// Use JSON serialization (default, always works)
Json,
/// Use bincode serialization (must be explicitly opted into)
Bincode,
}
/// The Errors that may occur when creating or using an HTTP client.
#[derive(Debug, Error)]
#[allow(missing_docs)]
@@ -371,6 +382,7 @@ pub struct ClientBuilder {
front: Option<fronted::Front>,
retry_limit: usize,
serialization: SerializationFormat,
}
impl ClientBuilder {
@@ -396,6 +408,50 @@ impl ClientBuilder {
}
}
/// Create a client builder from network details with sensible defaults
#[cfg(feature = "network-defaults")]
pub fn from_network(
network: &nym_network_defaults::NymNetworkDetails,
) -> Result<Self, HttpClientError> {
let urls = network
.nym_api_urls
.as_ref()
.ok_or_else(|| {
HttpClientError::GenericRequestFailure(
"No API URLs configured in network details".to_string(),
)
})?
.iter()
.map(|api_url| {
// Convert ApiUrl to our Url type with fronting support
let mut url = Url::parse(&api_url.url)?;
// Add fronting domains if available
#[cfg(feature = "tunneling")]
if let Some(ref front_hosts) = api_url.front_hosts {
let fronts: Vec<String> = front_hosts
.iter()
.map(|host| format!("https://{}", host))
.collect();
url = Url::new(api_url.url.clone(), Some(fronts))
.map_err(|e| HttpClientError::GenericRequestFailure(e.to_string()))?;
}
Ok(url)
})
.collect::<Result<Vec<_>, HttpClientError>>()?;
let mut builder = Self::new_with_urls(urls);
// Enable domain fronting by default (on retry)
#[cfg(feature = "tunneling")]
{
builder = builder.with_fronting(FrontPolicy::OnRetry);
}
Ok(builder)
}
/// Constructs a new http `ClientBuilder` from a valid url.
pub fn new_with_urls(urls: Vec<Url>) -> Self {
let urls = Self::check_urls(urls);
@@ -429,6 +485,7 @@ impl ClientBuilder {
front: None,
retry_limit: 0,
serialization: SerializationFormat::Json,
}
}
@@ -501,6 +558,17 @@ impl ClientBuilder {
self
}
/// Set the serialization format for API requests and responses
pub fn with_serialization(mut self, format: SerializationFormat) -> Self {
self.serialization = format;
self
}
/// Configure the client to use bincode serialization
pub fn with_bincode(self) -> Self {
self.with_serialization(SerializationFormat::Bincode)
}
/// Returns a Client that uses this ClientBuilder configuration.
pub fn build<E>(self) -> Result<Client, HttpClientError<E>>
where
@@ -542,6 +610,7 @@ impl ClientBuilder {
#[cfg(target_arch = "wasm32")]
request_timeout: self.timeout.unwrap_or(DEFAULT_TIMEOUT),
retry_limit: self.retry_limit,
serialization: self.serialization,
};
Ok(client)
@@ -562,6 +631,7 @@ pub struct Client {
request_timeout: Duration,
retry_limit: usize,
serialization: SerializationFormat,
}
impl Client {
@@ -619,6 +689,7 @@ impl Client {
#[cfg(target_arch = "wasm32")]
request_timeout: self.request_timeout,
serialization: self.serialization,
}
}
@@ -694,26 +765,37 @@ impl Client {
/// this method. For example, if the client is configured to rotate hosts after each error, this
/// method should be called after the host has been updated -- i.e. as part of the subsequent
/// send.
fn apply_hosts_to_req(&self, r: &mut reqwest::Request) {
fn apply_hosts_to_req(&self, r: &mut reqwest::Request) -> (&str, Option<&str>) {
let url = self.current_url();
r.url_mut().set_host(url.host_str()).unwrap();
#[cfg(feature = "tunneling")]
if let Some(ref front) = self.front {
if front.is_enabled() {
// this should never fail as we are transplanting the host from one url to another
r.url_mut().set_host(url.front_str()).unwrap();
let front_host = url.front_str().unwrap_or("");
let actual_host = url.host_str().unwrap_or("");
let actual_host: HeaderValue = url
.host_str()
.unwrap_or("")
.parse()
.unwrap_or(HeaderValue::from_static(""));
tracing::debug!(
"Domain fronting enabled: routing via CDN {} to actual host {}",
front_host,
actual_host
);
// this should never fail as we are transplanting the host from one url to another
r.url_mut().set_host(Some(front_host)).unwrap();
let actual_host_header: HeaderValue =
actual_host.parse().unwrap_or(HeaderValue::from_static(""));
// If the map did have this key present, the new value is associated with the key
// and all previous values are removed. (reqwest HeaderMap docs)
_ = r.headers_mut().insert(reqwest::header::HOST, actual_host);
_ = r
.headers_mut()
.insert(reqwest::header::HOST, actual_host_header);
return (url.as_str(), url.front_str());
}
}
(url.as_str(), None)
}
}
@@ -743,6 +825,13 @@ impl ApiClientCore for Client {
let mut rb = RequestBuilder::from_parts(self.reqwest_client.clone(), req);
// Set Accept header based on serialization preference
let accept_header = match self.serialization {
SerializationFormat::Json => "application/json",
SerializationFormat::Bincode => "application/bincode",
};
rb = rb.header(reqwest::header::ACCEPT, accept_header);
if let Some(body) = json_body {
rb = rb.json(body);
}
@@ -791,7 +880,14 @@ impl ApiClientCore for Client {
if let Some(ref front) = self.front {
// If fronting is set to be enabled on error, enable domain fronting as we
// have encountered an error.
let was_enabled = front.is_enabled();
front.retry_enable();
if !was_enabled && front.is_enabled() {
tracing::info!(
"Domain fronting activated after connection failure: {}",
e
);
}
}
if attempts < self.retry_limit {
+9 -2
View File
@@ -55,6 +55,7 @@ pub struct ApiUrl {
pub front_hosts: Option<Vec<String>>,
}
#[derive(Copy, Clone)]
pub struct ApiUrlConst<'a> {
pub url: &'a str,
pub front_hosts: Option<&'a [&'a str]>,
@@ -188,8 +189,14 @@ 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(),
),
}
}
+2
View File
@@ -25,3 +25,5 @@ url = { workspace = true }
nym-crypto = { path = "../crypto", features = ["asymmetric"] }
nym-task = { path = "../task" }
nym-validator-client = { path = "../client-libs/validator-client" }
nym-http-api-client = { path = "../http-api-client" }
nym-api-requests = { path = "../../nym-api/nym-api-requests" }
+12 -5
View File
@@ -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,17 @@ 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::<nym_api_requests::models::RequestError>()
}) {
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) => {
+1
View File
@@ -34,6 +34,7 @@ nym-statistics-common = { path = "../../statistics" }
nym-task = { path = "../../task" }
nym-topology = { path = "../../topology", features = ["wasm-serde-types"] }
nym-validator-client = { path = "../../client-libs/validator-client", default-features = false }
nym-http-api-client = { path = "../../http-api-client" }
wasm-utils = { path = "../utils" }
wasm-storage = { path = "../storage" }
+6
View File
@@ -37,6 +37,12 @@ pub enum WasmCoreError {
source: ValidatorClientError,
},
#[error("failed to query nym api: {source}")]
NymApiQueryError {
#[from]
source: nym_validator_client::nym_api::error::NymAPIError,
},
#[error("The provided wasm topology was invalid: {source}")]
WasmTopologyError {
#[from]
+23 -6
View File
@@ -19,9 +19,9 @@ use nym_client_core::init::{
use nym_sphinx::addressing::clients::Recipient;
use nym_sphinx::anonymous_replies::requests::AnonymousSenderTag;
use nym_topology::wasm_helpers::WasmFriendlyNymTopology;
use nym_topology::{NymTopology, RoutingNode};
use nym_topology::{EpochRewardedSet, NymTopology, RoutingNode};
use nym_validator_client::client::IdentityKey;
use nym_validator_client::{NymApiClient, UserAgent};
use nym_validator_client::{nym_api::NymApiClientExt, UserAgent};
use rand::thread_rng;
use url::Url;
use wasm_bindgen::prelude::wasm_bindgen;
@@ -72,7 +72,19 @@ pub async fn current_network_topology_async(
}
};
let api_client = NymApiClient::new(url);
let api_client = nym_http_api_client::Client::builder::<
_,
nym_validator_client::models::RequestError,
>(url.clone())
.map_err(|_err| WasmCoreError::MalformedUrl {
raw: nym_api_url.to_string(),
source: url::ParseError::EmptyHost,
})?
.build::<nym_validator_client::models::RequestError>()
.map_err(|_err| WasmCoreError::MalformedUrl {
raw: nym_api_url.to_string(),
source: url::ParseError::EmptyHost,
})?;
let rewarded_set = api_client.get_current_rewarded_set().await?;
let mixnodes_res = api_client
.get_all_basic_active_mixing_assigned_nodes_with_metadata()
@@ -90,9 +102,14 @@ pub async fn current_network_topology_async(
let gateways = gateways_res.nodes;
let topology = NymTopology::new(metadata.to_topology_metadata(), rewarded_set, Vec::new())
.with_skimmed_nodes(&mixnodes)
.with_skimmed_nodes(&gateways);
let epoch_rewarded_set: EpochRewardedSet = rewarded_set.into();
let topology = NymTopology::new(
metadata.to_topology_metadata(),
epoch_rewarded_set,
Vec::new(),
)
.with_skimmed_nodes(&mixnodes)
.with_skimmed_nodes(&gateways);
Ok(topology.into())
}
+1 -1
View File
@@ -21,6 +21,7 @@ pub use nym_client_core::{
pub use nym_gateway_client::{
error::GatewayClientError, GatewayClient, GatewayClientConfig, GatewayConfig,
};
pub use nym_http_api_client::Client as ApiClient;
pub use nym_sphinx::{
addressing::{clients::Recipient, nodes::NodeIdentity},
params::PacketType,
@@ -29,7 +30,6 @@ pub use nym_sphinx::{
pub use nym_statistics_common::clients::ClientStatsSender;
pub use nym_task;
pub use nym_topology::{HardcodedTopologyProvider, MixLayer, NymTopology, TopologyProvider};
pub use nym_validator_client::nym_api::Client as ApiClient;
pub use nym_validator_client::{DirectSigningReqwestRpcNyxdClient, QueryReqwestRpcNyxdClient};
// TODO: that's a very nasty import path. it should come from contracts instead!
pub use nym_validator_client::client::IdentityKey;
+2 -2
View File
@@ -139,9 +139,9 @@ mod tests {
fn add_dummy_mixes_with_delegations(test: &mut TestSetup, delegators: usize, mixes: usize) {
for i in 0..mixes {
let mix_id = test.add_legacy_mixnode(&test.make_addr(format!("mix-owner{}", i)), None);
let mix_id = test.add_legacy_mixnode(&test.make_addr(format!("mix-owner{i}")), None);
for delegator in 0..delegators {
let name = &test.make_addr(format!("delegator{}", delegator));
let name = &test.make_addr(format!("delegator{delegator}"));
test.add_immediate_delegation(name, 100_000_000u32, mix_id)
}
}
+4 -4
View File
@@ -503,8 +503,8 @@ pub(crate) mod tests {
storage,
id,
&UnbondedMixnode {
identity_key: format!("dummy{}", id),
owner: Addr::unchecked(format!("dummy{}", id)),
identity_key: format!("dummy{id}"),
owner: Addr::unchecked(format!("dummy{id}")),
proxy: None,
unbonding_height: 123,
},
@@ -570,7 +570,7 @@ pub(crate) mod tests {
storage,
id,
&UnbondedMixnode {
identity_key: format!("dummy{}", id),
identity_key: format!("dummy{id}"),
owner: owner.clone(),
proxy: None,
unbonding_height: 123,
@@ -817,7 +817,7 @@ pub(crate) mod tests {
id,
&UnbondedMixnode {
identity_key: identity.to_string(),
owner: Addr::unchecked(format!("dummy{}", id)),
owner: Addr::unchecked(format!("dummy{id}")),
proxy: None,
unbonding_height: 123,
},
+7 -7
View File
@@ -165,9 +165,9 @@ pub mod test_helpers {
#[track_caller]
pub fn assert_eq_with_leeway(a: Uint128, b: Uint128, leeway: Uint128) {
if a > b {
assert!(a - b <= leeway, "{} != {}", a, b)
assert!(a - b <= leeway, "{a} != {b}")
} else {
assert!(b - a <= leeway, "{} != {}", a, b)
assert!(b - a <= leeway, "{a} != {b}")
}
}
@@ -175,9 +175,9 @@ pub mod test_helpers {
pub fn assert_decimals(a: Decimal, b: Decimal) {
let epsilon = Decimal::from_ratio(1u128, 100_000_000u128);
if a > b {
assert!(a - b < epsilon, "{} != {}", a, b)
assert!(a - b < epsilon, "{a} != {b}")
} else {
assert!(b - a < epsilon, "{} != {}", a, b)
assert!(b - a < epsilon, "{a} != {b}")
}
}
@@ -1699,7 +1699,7 @@ pub mod test_helpers {
deps.branch(),
&env,
env.block.height,
Addr::unchecked(format!("owner{}", i)),
Addr::unchecked(format!("owner{i}")),
mix_id,
tests::fixtures::good_mixnode_pledge().pop().unwrap(),
)
@@ -1713,7 +1713,7 @@ pub mod test_helpers {
n: usize,
) {
for i in 0..n {
add_unbonded_mixnode(&mut rng, deps.branch(), None, &addr(format!("owner{}", i)));
add_unbonded_mixnode(&mut rng, deps.branch(), None, &addr(format!("owner{i}")));
}
}
@@ -1765,7 +1765,7 @@ pub mod test_helpers {
id,
&UnbondedMixnode {
identity_key: identity_key
.unwrap_or(&*format!("identity{}", id))
.unwrap_or(&*format!("identity{id}"))
.to_string(),
owner: Addr::unchecked(owner),
proxy: None,
@@ -20,16 +20,22 @@ use nym_sdk::mixnet;
use nym_sdk::mixnet::MixnetMessageSender;
use nym_topology::provider_trait::{async_trait, TopologyProvider};
use nym_topology::{nym_topology_from_detailed, NymTopology};
use nym_validator_client::nym_api::NymApiClientExt;
use url::Url;
struct MyTopologyProvider {
validator_client: nym_validator_client::client::NymApiClient,
validator_client: nym_http_api_client::Client,
}
impl MyTopologyProvider {
fn new(nym_api_url: Url) -> MyTopologyProvider {
let validator_client = nym_http_api_client::Client::builder::<_, nym_validator_client::models::RequestError>(nym_api_url)
.expect("Failed to create API client builder")
.build::<nym_validator_client::models::RequestError>()
.expect("Failed to build API client");
MyTopologyProvider {
validator_client: nym_validator_client::client::NymApiClient::new(nym_api_url),
validator_client,
}
}
+1
View File
@@ -93,6 +93,7 @@ nym-task = { path = "../common/task" }
nym-topology = { path = "../common/topology" }
nym-api-requests = { path = "nym-api-requests" }
nym-validator-client = { path = "../common/client-libs/validator-client" }
nym-http-api-client = { path = "../common/http-api-client" }
nym-bin-common = { path = "../common/bin-common", features = ["output_format", "openapi", "basic_tracing"] }
nym-node-tester-utils = { path = "../common/node-tester-utils" }
nym-node-requests = { path = "../nym-node/nym-node-requests" }
@@ -8,7 +8,9 @@ use nym_crypto::asymmetric::x25519::serde_helpers::bs58_x25519_pubkey;
use nym_crypto::asymmetric::{ed25519, x25519};
use nym_mixnet_contract_common::reward_params::Performance;
use nym_mixnet_contract_common::NodeId;
use nym_network_defaults::{DEFAULT_MIX_LISTENING_PORT, DEFAULT_VERLOC_LISTENING_PORT};
use nym_network_defaults::{
DEFAULT_MIX_LISTENING_PORT, DEFAULT_VERLOC_LISTENING_PORT, WG_METADATA_PORT, WG_TUNNEL_PORT,
};
use nym_node_requests::api::v1::authenticator::models::Authenticator;
use nym_node_requests::api::v1::gateway::models::Wireguard;
use nym_node_requests::api::v1::ip_packet_router::models::IpPacketRouter;
@@ -313,11 +315,20 @@ impl From<Authenticator> for AuthenticatorDetails {
pub struct WireguardDetails {
// NOTE: the port field is deprecated in favour of tunnel_port
pub port: u16,
#[serde(default = "default_tunnel_port")]
pub tunnel_port: u16,
#[serde(default = "default_metadata_port")]
pub metadata_port: u16,
pub public_key: String,
}
fn default_tunnel_port() -> u16 {
WG_TUNNEL_PORT
}
fn default_metadata_port() -> u16 {
WG_METADATA_PORT
}
// works for current simple case.
impl From<Wireguard> for WireguardDetails {
fn from(value: Wireguard) -> Self {
+4
View File
@@ -13,6 +13,7 @@ use nym_dkg::Threshold;
use nym_ecash_contract_common::deposit::DepositId;
use nym_ecash_contract_common::redeem_credential::BATCH_REDEMPTION_PROPOSAL_TITLE;
use nym_validator_client::coconut::EcashApiError;
use nym_validator_client::nym_api::error::NymAPIError;
use nym_validator_client::nyxd::error::NyxdError;
use nym_validator_client::nyxd::AccountId;
use thiserror::Error;
@@ -46,6 +47,9 @@ pub enum EcashError {
#[error("coconut api query failure: {0}")]
CoconutApiError(#[from] EcashApiError),
#[error("nym api query failure: {0}")]
NymApiError(#[from] NymAPIError),
#[error(transparent)]
SerdeJsonError(#[from] serde_json::Error),
+1
View File
@@ -46,6 +46,7 @@ use nym_ecash_contract_common::redeem_credential::BATCH_REDEMPTION_PROPOSAL_TITL
use nym_ecash_time::{ecash_default_expiration_date, ecash_today_date};
use nym_task::TaskClient;
use nym_ticketbooks_merkle::{IssuedTicketbook, IssuedTicketbooksFullMerkleProof, MerkleLeaf};
use nym_validator_client::nym_api::NymApiClientExt;
use nym_validator_client::nyxd::AccountId;
use nym_validator_client::EcashApiClient;
use rand::{thread_rng, RngCore};
+5 -2
View File
@@ -67,7 +67,7 @@ use nym_validator_client::nym_api::routes::{
use nym_validator_client::nyxd::cosmwasm_client::logs::Log;
use nym_validator_client::nyxd::cosmwasm_client::types::ExecuteResult;
use nym_validator_client::nyxd::{AccountId, ExecTxResult, Fee, Hash, TxResponse};
use nym_validator_client::{EcashApiClient, NymApiClient};
use nym_validator_client::EcashApiClient;
use rand::rngs::OsRng;
use rand::RngCore;
use std::collections::{BTreeMap, HashMap};
@@ -1148,7 +1148,10 @@ impl DummyCommunicationChannel {
cosmos_address: AccountId,
) -> Self {
let client = EcashApiClient {
api_client: NymApiClient::new("http://localhost:1234".parse().unwrap()),
api_client: nym_http_api_client::Client::new(
"http://localhost:1234".parse().unwrap(),
None,
),
verification_key: aggregated_verification_key,
node_id: 1,
cosmos_address,
@@ -12,6 +12,9 @@ use nym_credential_proxy_requests::api::v1::ticketbook::models::{
};
use nym_credentials_interface::Base58;
use nym_validator_client::ecash::BlindSignRequestBody;
use nym_validator_client::nyxd::Coin;
use nym_validator_client::nym_api::NymApiClientExt;
use rand::rngs::OsRng;
use std::collections::HashMap;
use std::sync::Arc;
use std::time::Duration;
@@ -3,7 +3,7 @@
use nym_ecash_signer_check::SignerCheckError;
use nym_validator_client::coconut::EcashApiError;
use nym_validator_client::nym_api::EpochId;
use nym_validator_client::nym_api::{error::NymAPIError, EpochId};
use nym_validator_client::nyxd::error::NyxdError;
use std::io;
use std::net::SocketAddr;
@@ -71,6 +71,12 @@ pub enum CredentialProxyError {
source: EcashApiError,
},
#[error("Nym API request failed: {source}")]
NymApiFailure {
#[from]
source: NymAPIError,
},
#[error("Compact ecash internal error: {0}")]
CompactEcashInternalError(#[from] nym_compact_ecash::error::CompactEcashError),
@@ -36,6 +36,7 @@ use nym_ecash_contract_common::deposit::DepositId;
use nym_ecash_contract_common::msg::ExecuteMsg;
use nym_validator_client::coconut::EcashApiError;
use nym_validator_client::nym_api::EpochId;
use nym_validator_client::nym_api::NymApiClientExt;
use nym_validator_client::nyxd::contract_traits::dkg_query_client::Epoch;
use nym_validator_client::nyxd::contract_traits::{
DkgQueryClient, NymContractsProvider, PagedDkgQueryClient,
+2
View File
@@ -40,3 +40,5 @@ nym-sphinx = { path = "../common/nymsphinx" }
nym-topology = { path = "../common/topology" }
nym-types = { path = "../common/types" }
nym-validator-client = { path = "../common/client-libs/validator-client" }
nym-http-api-client = { path = "../common/http-api-client" }
nym-mixnet-contract-common = { path = "../common/cosmwasm-smart-contracts/mixnet-contract" }
+17 -7
View File
@@ -6,12 +6,15 @@ use log::{info, warn};
use nym_bin_common::bin_info;
use nym_client_core::config::ForgetMe;
use nym_crypto::asymmetric::ed25519::PrivateKey;
use nym_mixnet_contract_common::EpochRewardedSet;
use nym_network_defaults::setup_env;
use nym_network_defaults::var_names::NYM_API;
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 nym_validator_client::UserAgent;
use std::fs::File;
use std::io::Write;
use std::sync::LazyLock;
@@ -160,10 +163,12 @@ async fn nym_topology_from_env() -> anyhow::Result<NymTopology> {
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::<
_,
nym_validator_client::models::RequestError,
>(api_url)?
.with_user_agent(UserAgent::from(bin_info!()))
.build::<nym_validator_client::models::RequestError>()?;
let rewarded_set = client.get_current_rewarded_set().await?;
@@ -172,10 +177,15 @@ async fn nym_topology_from_env() -> anyhow::Result<NymTopology> {
let nodes = nodes_response.nodes;
let metadata = nodes_response.metadata;
Ok(
NymTopology::new(metadata.to_topology_metadata(), rewarded_set, Vec::new())
.with_skimmed_nodes(&nodes),
// Convert RewardedSetResponse to EpochRewardedSet which can then be converted to CachedEpochRewardedSet
let epoch_rewarded_set: EpochRewardedSet = rewarded_set.into();
Ok(NymTopology::new(
metadata.to_topology_metadata(),
epoch_rewarded_set,
Vec::new(),
)
.with_skimmed_nodes(&nodes))
}
#[tokio::main]
@@ -5,11 +5,11 @@ use nym_node_requests::api::{client::NymNodeApiClientExt, v1::metrics::models::S
use nym_validator_client::{
client::{NodeId, NymNodeDetails},
models::{DescribedNodeType, NymNodeDescription},
NymApiClient,
};
use time::OffsetDateTime;
use nym_statistics_common::types::SessionType;
use nym_validator_client::client::NymApiClientExt;
use std::collections::HashMap;
use tokio::time::Duration;
use tracing::instrument;
@@ -62,11 +62,9 @@ async fn run(
.with_timeout(nym_api_client_timeout)
.build::<&str>()?;
let api_client = NymApiClient::from(nym_api);
//SW TBC what nodes exactly need to be scraped, the skimmed node endpoint seems to return more nodes
let bonded_nodes = api_client.get_all_bonded_nym_nodes().await?;
let all_nodes = api_client.get_all_described_nodes().await?; //legacy node that did not upgrade the contract bond yet
let bonded_nodes = nym_api.get_all_bonded_nym_nodes().await?;
let all_nodes = nym_api.get_all_described_nodes().await?; //legacy node that did not upgrade the contract bond yet
tracing::debug!("Fetched {} total nodes", all_nodes.len());
let mut nodes_to_scrape: HashMap<NodeId, MetricsScrapingData> = bonded_nodes
@@ -19,7 +19,7 @@ use nym_validator_client::{
use nym_validator_client::{
nym_nodes::{NodeRole, SkimmedNode},
nyxd::{contract_traits::PagedMixnetQueryClient, AccountId},
NymApiClient, QueryHttpRpcNyxdClient,
QueryHttpRpcNyxdClient,
};
use std::{
collections::{HashMap, HashSet},
@@ -111,9 +111,7 @@ impl Monitor {
.with_timeout(self.nym_api_client_timeout)
.build::<&str>()?;
let api_client = NymApiClient::from(nym_api);
let described_nodes = api_client
let described_nodes = nym_api
.get_all_described_nodes()
.await
.log_error("get_all_described_nodes")?
@@ -135,7 +133,7 @@ impl Monitor {
tracing::info!("🟣 🚪 gateway nodes: {}", gateways.len());
let bonded_nym_nodes = api_client
let bonded_nym_nodes = nym_api
.get_all_bonded_nym_nodes()
.await?
.into_iter()
@@ -146,10 +144,11 @@ impl Monitor {
tracing::info!("🟣 bonded_nodes: {}", bonded_nym_nodes.len());
// returns only bonded nodes
let nym_nodes = api_client
.get_all_basic_nodes()
let nym_nodes = nym_api
.get_all_basic_nodes_with_metadata()
.await
.log_error("get_all_basic_nodes")?;
.log_error("get_all_basic_nodes")?
.nodes;
let nym_node_count = nym_nodes.len();
tracing::info!("🟣 get_all_basic_nodes: {}", nym_node_count);
@@ -167,8 +166,7 @@ impl Monitor {
self.location_cached(node_description).await;
}
let mixnodes_detailed = api_client
.nym_api
let mixnodes_detailed = nym_api
.get_mixnodes_detailed_unfiltered()
.await
.log_error("get_mixnodes_detailed_unfiltered")?;
@@ -190,15 +188,13 @@ impl Monitor {
})
.collect::<Vec<_>>();
let mixnodes_described = api_client
.nym_api
let mixnodes_described = nym_api
.get_mixnodes_described()
.await
.log_error("get_mixnodes_described")?;
tracing::info!("🟣 mixnodes_described: {}", mixnodes_described.len());
let mixing_assigned_nodes = api_client
.nym_api
let mixing_assigned_nodes = nym_api
.get_basic_active_mixing_assigned_nodes(false, None, None, false)
.await
.log_error("get_basic_active_mixing_assigned_nodes")?
+25 -16
View File
@@ -10,7 +10,6 @@ use nym_task::ShutdownToken;
use nym_validator_client::client::NymApiClientExt;
use nym_validator_client::models::{KeyRotationInfoResponse, NodeRefreshBody};
use nym_validator_client::nym_api::error::NymAPIError;
use nym_validator_client::NymApiClient;
use rand::prelude::SliceRandom;
use rand::thread_rng;
use std::sync::Arc;
@@ -27,7 +26,7 @@ pub struct NymApisClient {
struct InnerClient {
// NOTE: this was implemented before the internal http client supported multiple URLs
active_client: NymApiClient,
active_client: Client,
available_urls: Vec<Url>,
shutdown_token: ShutdownToken,
currently_used_api: usize,
@@ -45,7 +44,7 @@ impl NymApisClient {
let mut urls = nym_apis.to_vec();
urls.shuffle(&mut thread_rng());
let active_client = nym_http_api_client::Client::builder(urls[0].clone())?
let active_client = Client::builder(urls[0].clone())?
.no_hickory_dns()
.with_user_agent(NymNode::user_agent())
.with_timeout(Duration::from_secs(5))
@@ -53,7 +52,7 @@ impl NymApisClient {
Ok(NymApisClient {
inner: Arc::new(RwLock::new(InnerClient {
active_client: NymApiClient::from(active_client),
active_client: active_client.clone(),
available_urls: urls,
shutdown_token,
currently_used_api: 0,
@@ -88,9 +87,19 @@ 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_nym_api(next_url);
// 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,10 +132,10 @@ impl InnerClient {
{
let broadcast_fut =
stream::iter(self.available_urls.clone()).for_each_concurrent(None, |url| {
let nym_api = self
.active_client
.nym_api
.clone_with_new_url(url.clone().into());
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 {
if let Err(err) = req_fut.await {
@@ -172,10 +181,10 @@ impl InnerClient {
.skip(last_working)
.chain(self.available_urls.iter().enumerate().take(last_working))
{
let nym_api = self
.active_client
.nym_api
.clone_with_new_url(url.clone().into());
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);
let query_fut = req(nym_api);
@@ -216,8 +225,8 @@ impl InnerClient {
}
}
impl AsRef<NymApiClient> for InnerClient {
fn as_ref(&self) -> &NymApiClient {
impl AsRef<Client> for InnerClient {
fn as_ref(&self) -> &Client {
&self.active_client
}
}
+7 -7
View File
@@ -7,6 +7,7 @@ use crate::node::routing_filter::network_filter::NetworkRoutingFilter;
use async_trait::async_trait;
use nym_crypto::asymmetric::ed25519;
use nym_gateway::node::UserAgent;
use nym_http_api_client::Client;
use nym_node_metrics::prometheus_wrapper::{PrometheusMetric, PROMETHEUS_METRICS};
use nym_noise::config::NoiseNetworkView;
use nym_task::ShutdownToken;
@@ -20,7 +21,7 @@ use nym_validator_client::nym_api::NymApiClientExt;
use nym_validator_client::nym_nodes::{
NodesByAddressesResponse, SemiSkimmedNode, SemiSkimmedNodesWithMetadata,
};
use nym_validator_client::{NymApiClient, ValidatorClientError};
use nym_validator_client::ValidatorClientError;
use std::collections::{HashMap, HashSet};
use std::net::{IpAddr, SocketAddr};
use std::ops::Deref;
@@ -35,7 +36,7 @@ use url::Url;
const LOCAL_NODE_ID: NodeId = 1234567890;
struct NodesQuerier {
client: NymApiClient,
client: Client,
nym_api_urls: Vec<Url>,
currently_used_api: usize,
}
@@ -49,7 +50,7 @@ impl NodesQuerier {
self.currently_used_api = (self.currently_used_api + 1) % self.nym_api_urls.len();
self.client
.change_nym_api(self.nym_api_urls[self.currently_used_api].clone())
.change_base_urls(self.nym_api_urls.iter().map(|u| u.clone().into()).collect())
}
async fn rewarded_set(&mut self) -> Result<EpochRewardedSet, ValidatorClientError> {
@@ -62,7 +63,7 @@ impl NodesQuerier {
if res.is_err() {
self.use_next_nym_api()
}
res
Ok(res?.into())
}
async fn current_nymnodes(
@@ -77,7 +78,7 @@ impl NodesQuerier {
if res.is_err() {
self.use_next_nym_api()
}
res
Ok(res?)
}
async fn query_for_info(
@@ -86,7 +87,6 @@ impl NodesQuerier {
) -> Result<NodesByAddressesResponse, ValidatorClientError> {
let res = self
.client
.nym_api
.nodes_by_addresses(ips)
.await
.inspect_err(|err| error!("failed to obtain node information: {err}"));
@@ -228,7 +228,7 @@ impl NetworkRefresher {
let mut this = NetworkRefresher {
querier: NodesQuerier {
client: NymApiClient::from(nym_api),
client: nym_api,
nym_api_urls,
currently_used_api: 0,
},
+8 -10
View File
@@ -6,7 +6,6 @@ use nym_task::ShutdownToken;
use celes::Country;
use nym_validator_client::models::NymNodeDescription;
use nym_validator_client::NymApiClient;
use std::collections::HashMap;
use std::time::Duration;
use std::{net::IpAddr, sync::Arc};
@@ -14,6 +13,8 @@ use tokio::sync::RwLock;
use tokio::time::interval;
use url::Url;
use nym_http_api_client::Client;
use nym_validator_client::client::NymApiClientExt;
use tracing::{error, info, trace, warn};
const NETWORK_CACHE_TTL: Duration = Duration::from_secs(600);
@@ -22,7 +23,7 @@ type IpToCountryMap = HashMap<IpAddr, Option<Country>>;
// SW this should use a proper NS API client once it exists
struct NodesQuerier {
client: NymApiClient,
client: Client,
}
impl NodesQuerier {
@@ -99,14 +100,11 @@ impl NetworkRefresher {
this
}
fn build_http_api_client(url: Url) -> Result<NymApiClient> {
Ok(
nym_http_api_client::Client::builder::<_, anyhow::Error>(url)?
.no_hickory_dns()
.with_user_agent("node-statistics-api")
.build::<anyhow::Error>()?
.into(),
)
fn build_http_api_client(url: Url) -> Result<Client> {
Ok(Client::builder::<_, anyhow::Error>(url)?
.no_hickory_dns()
.with_user_agent("node-statistics-api")
.build::<anyhow::Error>()?)
}
async fn refresh_network_nodes(&mut self) -> Result<()> {
+1
View File
@@ -42,6 +42,7 @@ nym-credentials = { path = "../common/credentials" }
nym-network-defaults = { path = "../common/network-defaults" }
nym-task = { path = "../common/task" }
nym-validator-client = { path = "../common/client-libs/validator-client" }
nym-http-api-client = { path = "../common/http-api-client" }
nym-coconut-dkg-common = { path = "../common/cosmwasm-smart-contracts/coconut-dkg" }
nyxd-scraper = { path = "../common/nyxd-scraper" }
nym-ticketbooks-merkle = { path = "../common/ticketbooks-merkle" }
@@ -9,6 +9,7 @@ use crate::rewarder::ticketbook_issuance::verifier::TicketbookIssuanceVerifier;
use crate::rewarder::Rewarder;
use anyhow::bail;
use nym_ecash_time::ecash_default_expiration_date;
use nym_validator_client::nym_api::NymApiClientExt;
use std::collections::HashSet;
use std::path::PathBuf;
use time::macros::format_description;
@@ -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,23 @@ impl NyxdClient {
continue;
};
let api_client = match nym_http_api_client::Client::builder(api_address)
.and_then(|b| b.build::<nym_validator_client::models::RequestError>())
{
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,
})
@@ -6,8 +6,8 @@ use cosmwasm_std::{Addr, Decimal, Uint128};
use nym_coconut_dkg_common::types::NodeIndex;
use nym_compact_ecash::VerificationKeyAuth;
use nym_crypto::asymmetric::ed25519;
use nym_validator_client::nym_api::NymApiClientExt;
use nym_validator_client::nyxd::{AccountId, Coin};
use nym_validator_client::NymApiClient;
use std::fmt::{Display, Formatter};
use tracing::info;
@@ -68,7 +68,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,
}
@@ -16,6 +16,7 @@ use nym_validator_client::ecash::models::{
IssuedTicketbooksChallengeCommitmentResponse, IssuedTicketbooksDataRequestBody,
IssuedTicketbooksDataResponse, IssuedTicketbooksDataResponseBody, IssuedTicketbooksForResponse,
};
use nym_validator_client::nym_api::NymApiClientExt;
use nym_validator_client::nyxd::AccountId;
use nym_validator_client::signable::{SignableMessageBody, SignedMessage};
use rand::distributions::{Distribution, WeightedIndex};
+1
View File
@@ -38,6 +38,7 @@ nym-socks5-client-core = { path = "../../../common/socks5-client-core" }
nym-validator-client = { path = "../../../common/client-libs/validator-client", features = [
"http-client",
] }
nym-http-api-client = { path = "../../../common/http-api-client" }
nym-socks5-requests = { path = "../../../common/socks5/requests" }
nym-ordered-buffer = { path = "../../../common/socks5/ordered-buffer" }
nym-service-providers-common = { path = "../../../service-providers/common" }
@@ -4,18 +4,25 @@
use nym_sdk::mixnet;
use nym_sdk::mixnet::MixnetMessageSender;
use nym_topology::provider_trait::{async_trait, ToTopologyMetadata, TopologyProvider};
use nym_topology::NymTopology;
use nym_topology::{EpochRewardedSet, NymTopology};
use nym_validator_client::nym_api::NymApiClientExt;
use url::Url;
struct MyTopologyProvider {
validator_client: nym_validator_client::client::NymApiClient,
validator_client: nym_http_api_client::Client,
}
impl MyTopologyProvider {
fn new(nym_api_url: Url) -> MyTopologyProvider {
MyTopologyProvider {
validator_client: nym_validator_client::client::NymApiClient::new(nym_api_url),
}
let validator_client = nym_http_api_client::Client::builder::<
_,
nym_validator_client::models::RequestError,
>(nym_api_url)
.expect("Failed to create API client builder")
.build::<nym_validator_client::models::RequestError>()
.expect("Failed to build API client");
MyTopologyProvider { validator_client }
}
async fn get_topology(&self) -> NymTopology {
@@ -33,7 +40,8 @@ impl MyTopologyProvider {
let metadata = mixnodes_response.metadata.to_topology_metadata();
let mut base_topology = NymTopology::new(metadata, rewarded_set, Vec::new());
let epoch_rewarded_set: EpochRewardedSet = rewarded_set.into();
let mut base_topology = NymTopology::new(metadata, epoch_rewarded_set, Vec::new());
// in our topology provider only use mixnodes that have node_id divisible by 3
// and has exactly 100 performance score
@@ -37,6 +37,7 @@ nym-crypto = { path = "../../../common/crypto", features = ["asymmetric", "rand"
nym-config = { path = "../../../common/config" }
nym-validator-client = { path = "../../../common/client-libs/validator-client" }
nym-compact-ecash = { path = "../../../common/nym_offline_compact_ecash" }
nym-http-api-client = { path = "../../../common/http-api-client" }
dkg-bypass-contract = { path = "dkg-bypass-contract", default-features = false }
# contracts:
@@ -7,7 +7,7 @@ use crate::manager::network::LoadedNetwork;
use crate::manager::NetworkManager;
use console::style;
use nym_config::{must_get_home, DEFAULT_CONFIG_DIR, DEFAULT_CONFIG_FILENAME, NYM_DIR};
use nym_validator_client::NymApiClient;
use nym_validator_client::nym_api::NymApiClientExt;
use rand::{thread_rng, RngCore};
use std::fs;
use std::fs::OpenOptions;
@@ -91,7 +91,13 @@ impl NetworkManager {
"⌛waiting for any gateway to appear in the directory ({api_url})..."
));
let api_client = NymApiClient::new(api_url);
let api_client = nym_http_api_client::Client::builder::<
_,
nym_validator_client::models::RequestError,
>(api_url.clone())
.expect("Failed to create API client builder")
.build::<nym_validator_client::models::RequestError>()
.expect("Failed to build API client");
let wait_fut = async {
let inner_fut = async {
@@ -25,6 +25,7 @@ time = { workspace = true }
nym-validator-client = { path = "../../../common/client-libs/validator-client" }
nym-bin-common = { path = "../../../common/bin-common", features = ["output_format", "basic_tracing"] }
nym-network-defaults = { path = "../../../common/network-defaults" }
nym-http-api-client = { path = "../../../common/http-api-client" }
[lints]
workspace = true
@@ -1,8 +1,7 @@
// Copyright 2025 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: GPL-3.0-only
use nym_validator_client::client::NymApiClientExt;
use nym_validator_client::NymApiClient;
use nym_validator_client::nym_api::NymApiClientExt;
use serde::{Deserialize, Serialize};
use std::fmt::Display;
use strum::{Display, EnumProperty};
@@ -48,8 +47,8 @@ impl SignerStatus {
matches!(self.rpc_status, RpcStatus::Up)
}
fn build_api_client(&self) -> Option<NymApiClient> {
let api_endpoint = match self.api_endpoint.as_str().parse() {
fn build_api_client(&self) -> Option<nym_http_api_client::Client> {
let api_endpoint: nym_http_api_client::Url = match self.api_endpoint.as_str().parse() {
Ok(endpoint) => endpoint,
Err(err) => {
error!("{} is not a valid api endpoint: {err}", self.api_endpoint);
@@ -57,14 +56,19 @@ impl SignerStatus {
}
};
Some(NymApiClient::new(api_endpoint))
nym_http_api_client::Client::builder::<_, nym_validator_client::models::RequestError>(
api_endpoint,
)
.ok()?
.build::<nym_validator_client::models::RequestError>()
.ok()
}
pub(crate) async fn try_update_api_version(&mut self) {
let Some(client) = self.build_api_client() else {
return;
};
match client.nym_api.build_information().await {
match client.build_information().await {
Ok(build_info) => {
self.api_version = ApiVersion::Available {
version: build_info.build_version,
@@ -84,7 +88,7 @@ impl SignerStatus {
return;
};
match client.nym_api.get_chain_status().await {
match client.get_chain_status().await {
Ok(chain_status) => {
self.used_rpc_endpoint = RpcEndpoint(chain_status.connected_nyxd);
let last_block =