Compare commits

...

17 Commits

Author SHA1 Message Date
durch 86d74ebca1 Feature flag IPv4 forcing 2025-09-04 17:04:10 +02:00
Mark Sinclair 6dcfb81b95 format 2025-09-04 14:52:12 +01:00
Bogdan-Ștefan Neacşu 59451365b6 Use default value for the ports until api is deployed 2025-09-04 16:18:42 +03:00
Bogdan-Ștefan Neacşu e8242ae271 Fix axum-test feature removal 2025-09-04 16:04:10 +03:00
Mark Sinclair 96d58655ef Force IPv4 2025-09-04 13:44:19 +01:00
Drazen Urch 043fb31c09 Merge branch 'develop' into domain-fronting-integration 2025-09-01 19:10:41 +02:00
durch e36573463c Merge branch 'domain-fronting-integration' of https://github.com/nymtech/nym into domain-fronting-integration 2025-09-01 14:50:05 +02:00
durch c79471e9ad Assorted CI fixes 2025-09-01 14:48:19 +02:00
Drazen Urch 1cd8713d26 Merge branch 'develop' into domain-fronting-integration 2025-09-01 14:04:10 +02:00
durch edb797da68 Explicit warning on missing fronting configuration 2025-09-01 12:25:25 +02:00
durch 72e1290d8f Remove error generics, address PR comments 2025-09-01 11:28:17 +02:00
Drazen Urch 905d8ed7de Update common/client-core/src/client/base_client/mod.rs
Co-authored-by: Jędrzej Stuczyński <jedrzej.stuczynski@gmail.com>
2025-09-01 11:21:24 +02: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
80 changed files with 1032 additions and 402 deletions
Generated
+26 -11
View File
@@ -4831,6 +4831,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",
@@ -5042,6 +5043,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",
@@ -5130,6 +5132,7 @@ dependencies = [
"nym-http-api-client",
"nym-id",
"nym-mixnet-client",
"nym-mixnet-contract-common",
"nym-network-defaults",
"nym-nonexhaustive-delayqueue",
"nym-pemstore",
@@ -5482,6 +5485,7 @@ dependencies = [
"nym-crypto",
"nym-ecash-contract-common",
"nym-ecash-time",
"nym-http-api-client",
"nym-network-defaults",
"nym-serde-helpers",
"nym-validator-client",
@@ -5581,6 +5585,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",
@@ -5845,10 +5850,13 @@ dependencies = [
"mime",
"nym-bin-common",
"nym-http-api-common",
"nym-network-defaults",
"once_cell",
"reqwest 0.12.22",
"serde",
"serde_json",
"serde_plain",
"serde_yaml",
"thiserror 2.0.12",
"tokio",
"tracing",
@@ -6105,6 +6113,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",
@@ -6572,6 +6582,7 @@ dependencies = [
"nym-credentials-interface",
"nym-crypto",
"nym-gateway-requests",
"nym-http-api-client",
"nym-network-defaults",
"nym-ordered-buffer",
"nym-service-providers-common",
@@ -7147,6 +7158,7 @@ dependencies = [
"nym-credentials-interface",
"nym-crypto",
"nym-ecash-time",
"nym-http-api-client",
"nym-network-defaults",
"nym-pemstore",
"nym-serde-helpers",
@@ -7176,7 +7188,9 @@ dependencies = [
"bytes",
"futures",
"humantime",
"nym-api-requests",
"nym-crypto",
"nym-http-api-client",
"nym-task",
"nym-validator-client",
"rand 0.8.5",
@@ -9245,6 +9259,15 @@ dependencies = [
"serde",
]
[[package]]
name = "serde_plain"
version = "1.0.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9ce1fc6db65a611022b23a0dec6975d63fb80a302cb3388835ff02c097258d50"
dependencies = [
"serde",
]
[[package]]
name = "serde_repr"
version = "0.1.20"
@@ -10167,6 +10190,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",
@@ -11289,6 +11313,7 @@ dependencies = [
"clap",
"comfy-table",
"nym-bin-common",
"nym-http-api-client",
"nym-network-defaults",
"nym-validator-client",
"serde",
@@ -11519,6 +11544,7 @@ dependencies = [
"nym-credential-storage",
"nym-crypto",
"nym-gateway-client",
"nym-http-api-client",
"nym-sphinx",
"nym-sphinx-acknowledgements",
"nym-statistics-common",
@@ -11804,17 +11830,6 @@ dependencies = [
"windows-link",
]
[[package]]
name = "windows-registry"
version = "0.5.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5b8a9ed28765efc97bbc954883f4e6796c33a06546ebafacbabee9696967499e"
dependencies = [
"windows-link",
"windows-result",
"windows-strings",
]
[[package]]
name = "windows-result"
version = "0.3.4"
+1
View File
@@ -317,6 +317,7 @@ serde_json_path = "0.7.2"
serde_repr = "0.1"
serde_with = "3.9.0"
serde_yaml = "0.9.25"
serde_plain = "1.0.2"
sha2 = "0.10.9"
si-scale = "0.2.3"
snow = "0.9.6"
+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_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()
.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() {
+65 -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,30 @@ 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().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,10 +418,10 @@ 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(api_url)
.expect("invalid api url")
.with_user_agent(user_agent.into())
.build::<ValidatorClientError>()
.build()
.expect("failed to build nym api client");
NymApiClient {
@@ -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,13 @@ 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(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()?,
@@ -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,17 @@ 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()) {
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 +170,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 +196,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;
@@ -1,7 +1,6 @@
// Copyright 2022-2023 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use nym_api_requests::models::RequestError;
use nym_http_api_client::HttpClientError;
pub type NymAPIError = HttpClientError<RequestError>;
pub type NymAPIError = HttpClientError;
@@ -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,156 @@ 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: "Inconsistent paged metadata".to_string(),
});
}
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: "Inconsistent paged metadata".to_string(),
});
}
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: "Inconsistent paged metadata".to_string(),
});
}
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 +422,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 +1544,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 {
+6
View File
@@ -13,6 +13,8 @@ license.workspace = true
[features]
default=["tunneling"]
tunneling=[]
network-defaults = ["dep:nym-network-defaults"]
force-ipv4 = []
[dependencies]
async-trait = { workspace = true }
@@ -23,6 +25,8 @@ url = { workspace = true }
once_cell = { workspace = true }
serde = { workspace = true }
serde_json = { workspace = true }
serde_yaml = { workspace = true}
serde_plain = { workspace = true }
thiserror = { workspace = true }
tracing = { workspace = true }
itertools = { workspace = true }
@@ -34,6 +38,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"] }
@@ -46,3 +51,4 @@ features = ["tokio"]
[dev-dependencies]
tokio = { workspace = true, features = ["rt", "macros"] }
+7 -3
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,
}
@@ -96,14 +100,14 @@ mod tests {
// Some(vec!["https://cdn77.com"]),
// ).unwrap(); // cdn77
let client = ClientBuilder::new::<_, &str>(url1)
let client = ClientBuilder::new(url1)
.expect("bad url")
.with_fronting(FrontPolicy::Always)
.build::<&str>()
.build()
.expect("failed to build client");
let response = client
.send_request::<_, (), &str, &str, &str>(
.send_request::<_, (), &str, &str>(
reqwest::Method::GET,
&["api", "v1", "network", "details"],
NO_PARAMS,
+243 -102
View File
@@ -15,7 +15,7 @@
//! # use url::Url;
//! # use nym_http_api_client::{ApiClient, NO_PARAMS, HttpClientError};
//!
//! # type Err = HttpClientError<String>;
//! # type Err = HttpClientError;
//! # async fn run() -> Result<(), Err> {
//! let url: Url = "https://nymvpn.com".parse()?;
//! let client = nym_http_api_client::Client::new(url, None);
@@ -114,7 +114,7 @@
//! }
//! }
//!
//! pub type SpecificAPIError = HttpClientError<error::RequestError>;
//! pub type SpecificAPIError = HttpClientError;
//!
//! #[cfg_attr(target_arch = "wasm32", async_trait(?Send))]
//! #[cfg_attr(not(target_arch = "wasm32"), async_trait)]
@@ -142,7 +142,7 @@ pub use reqwest::StatusCode;
use crate::path::RequestPath;
use async_trait::async_trait;
use bytes::Bytes;
use http::header::CONTENT_TYPE;
use http::header::{ACCEPT, CONTENT_TYPE};
use http::HeaderMap;
use itertools::Itertools;
use mime::Mime;
@@ -151,6 +151,7 @@ use reqwest::{RequestBuilder, Response};
use serde::de::DeserializeOwned;
use serde::{Deserialize, Serialize};
use std::fmt::Display;
use std::net::IpAddr;
use std::sync::atomic::{AtomicUsize, Ordering};
use std::time::Duration;
use thiserror::Error;
@@ -158,10 +159,13 @@ use tracing::{debug, instrument, warn};
#[cfg(not(target_arch = "wasm32"))]
use std::net::SocketAddr;
use std::str::FromStr;
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,10 +196,46 @@ 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,
/// Use YAML serialization
Yaml,
/// Use Text serialization
Text,
}
impl Display for SerializationFormat {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
SerializationFormat::Json => write!(f, "json"),
SerializationFormat::Bincode => write!(f, "bincode"),
SerializationFormat::Yaml => write!(f, "yaml"),
SerializationFormat::Text => write!(f, "text"),
}
}
}
impl SerializationFormat {
#[allow(missing_docs)]
pub fn content_type(&self) -> String {
match self {
SerializationFormat::Json => "application/json".to_string(),
SerializationFormat::Bincode => "application/bincode".to_string(),
SerializationFormat::Yaml => "application/yaml".to_string(),
SerializationFormat::Text => "text/plain".to_string(),
}
}
}
/// The Errors that may occur when creating or using an HTTP client.
#[derive(Debug, Error)]
#[allow(missing_docs)]
pub enum HttpClientError<E: Display = String> {
pub enum HttpClientError {
#[error("there was an issue with the REST request: {source}")]
ReqwestClientError {
#[from]
@@ -224,11 +264,23 @@ pub enum HttpClientError<E: Display = String> {
EmptyResponse { status: StatusCode },
#[error("failed to resolve request. status: '{status}', additional error message: {error}")]
EndpointFailure { status: StatusCode, error: E },
EndpointFailure { status: StatusCode, error: String },
#[error("failed to decode response body: {message} from {content}")]
ResponseDecodeFailure { message: String, content: String },
#[error("Failed to encode bincode: {0}")]
Bincode(#[from] bincode::Error),
#[error("Failed to json: {0}")]
Json(#[from] serde_json::Error),
#[error("Failed to yaml: {0}")]
Yaml(#[from] serde_yaml::Error),
#[error("Failed to plain: {0}")]
Plain(#[from] serde_plain::Error),
#[cfg(target_arch = "wasm32")]
#[error("the request has timed out")]
RequestTimeout,
@@ -270,8 +322,8 @@ pub trait ApiClientCore {
method: reqwest::Method,
path: P,
params: Params<'_, K, V>,
json_body: Option<&B>,
) -> RequestBuilder
body: Option<&B>,
) -> Result<RequestBuilder, HttpClientError>
where
P: RequestPath,
B: Serialize + ?Sized,
@@ -297,8 +349,8 @@ pub trait ApiClientCore {
&self,
method: reqwest::Method,
endpoint: S,
json_body: Option<&B>,
) -> RequestBuilder
body: Option<&B>,
) -> Result<RequestBuilder, HttpClientError>
where
B: Serialize + ?Sized,
S: AsRef<str>,
@@ -324,7 +376,7 @@ pub trait ApiClientCore {
};
let params: Vec<(String, String)> = standin_url.query_pairs().into_owned().collect();
self.create_request(method, path.as_slice(), &params, json_body)
self.create_request(method, path.as_slice(), &params, body)
}
/// Send a created HTTP request.
@@ -332,26 +384,23 @@ pub trait ApiClientCore {
/// A [`RequestBuilder`] can be created with [`ApiClientCore::create_request`] or
/// [`ApiClientCore::create_request_endpoint`] or if absolutely necessary, using reqwest
/// tooling directly.
async fn send<E>(&self, request: RequestBuilder) -> Result<Response, HttpClientError<E>>
where
E: Display;
async fn send(&self, request: RequestBuilder) -> Result<Response, HttpClientError>;
/// Create and send a created HTTP request.
async fn send_request<P, B, K, V, E>(
async fn send_request<P, B, K, V>(
&self,
method: reqwest::Method,
path: P,
params: Params<'_, K, V>,
json_body: Option<&B>,
) -> Result<Response, HttpClientError<E>>
) -> Result<Response, HttpClientError>
where
P: RequestPath + Send + Sync,
B: Serialize + ?Sized + Sync,
K: AsRef<str> + Sync,
V: AsRef<str> + Sync,
E: Display,
{
let req = self.create_request(method, path, params, json_body);
let req = self.create_request(method, path, params, json_body)?;
self.send(req).await
}
}
@@ -371,16 +420,16 @@ pub struct ClientBuilder {
front: Option<fronted::Front>,
retry_limit: usize,
serialization: SerializationFormat,
}
impl ClientBuilder {
/// Constructs a new `ClientBuilder`.
///
/// This is the same as `Client::builder()`.
pub fn new<U, E>(url: U) -> Result<Self, HttpClientError<E>>
pub fn new<U>(url: U) -> Result<Self, HttpClientError>
where
U: IntoUrl,
E: Display,
{
let str_url = url.as_str();
@@ -396,6 +445,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);
@@ -403,6 +496,8 @@ impl ClientBuilder {
#[cfg(target_arch = "wasm32")]
let reqwest_client_builder = reqwest::ClientBuilder::new();
warn!("FORCING IPv4 CONNECTIONS");
#[cfg(not(target_arch = "wasm32"))]
let reqwest_client_builder = {
// Note: I believe the manual enable calls for the compression methods are extra
@@ -419,6 +514,9 @@ impl ClientBuilder {
.zstd(true)
};
#[cfg(feature = "force-ipv4")]
let reqwest_client_builder = reqwest_client_builder.local_address(IpAddr::from_str("0.0.0.0").unwrap());
ClientBuilder {
urls,
timeout: None,
@@ -429,6 +527,7 @@ impl ClientBuilder {
front: None,
retry_limit: 0,
serialization: SerializationFormat::Json,
}
}
@@ -501,11 +600,19 @@ 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
E: Display,
{
pub fn build(self) -> Result<Client, HttpClientError> {
#[cfg(target_arch = "wasm32")]
let reqwest_client = self.reqwest_client_builder.build()?;
@@ -542,6 +649,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 +670,7 @@ pub struct Client {
request_timeout: Duration,
retry_limit: usize,
serialization: SerializationFormat,
}
impl Client {
@@ -571,16 +680,15 @@ impl Client {
// In order to prevent interference in API requests at the DNS phase we default to a resolver
// that uses DoT and DoH.
pub fn new(base_url: ::url::Url, timeout: Option<Duration>) -> Self {
Self::new_url::<_, String>(base_url, timeout).expect(
Self::new_url(base_url, timeout).expect(
"we provided valid url and we were unwrapping previous construction errors anyway",
)
}
/// Attempt to create a new http client from a something that can be converted to a URL
pub fn new_url<U, E>(url: U, timeout: Option<Duration>) -> Result<Self, HttpClientError<E>>
pub fn new_url<U>(url: U, timeout: Option<Duration>) -> Result<Self, HttpClientError>
where
U: IntoUrl,
E: Display,
{
let builder = Self::builder(url)?;
match timeout {
@@ -592,10 +700,9 @@ impl Client {
/// Creates a [`ClientBuilder`] to configure a [`Client`].
///
/// This is the same as [`ClientBuilder::new()`].
pub fn builder<U, E>(url: U) -> Result<ClientBuilder, HttpClientError<E>>
pub fn builder<U>(url: U) -> Result<ClientBuilder, HttpClientError>
where
U: IntoUrl,
E: Display,
{
ClientBuilder::new(url)
}
@@ -619,6 +726,7 @@ impl Client {
#[cfg(target_arch = "wasm32")]
request_timeout: self.request_timeout,
serialization: self.serialization,
}
}
@@ -694,26 +802,42 @@ 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();
if let Some(front_host) = url.front_str() {
if let Some(actual_host) = url.host_str() {
tracing::debug!(
"Domain fronting enabled: routing via CDN {} to actual host {}",
front_host,
actual_host
);
let actual_host: HeaderValue = url
.host_str()
.unwrap_or("")
.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);
// 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_header);
return (url.as_str(), url.front_str());
} else {
warn!("Domain fronting is enabled, but no host_url is defined! Domain fronting WILL NOT WORK")
}
} else {
warn!("Domain fronting is enabled, but no front_url is defined! Domain fronting WILL NOT WORK")
}
}
}
(url.as_str(), None)
}
}
@@ -726,8 +850,8 @@ impl ApiClientCore for Client {
method: reqwest::Method,
path: P,
params: Params<'_, K, V>,
json_body: Option<&B>,
) -> RequestBuilder
body: Option<&B>,
) -> Result<RequestBuilder, HttpClientError>
where
P: RequestPath,
B: Serialize + ?Sized,
@@ -743,17 +867,35 @@ impl ApiClientCore for Client {
let mut rb = RequestBuilder::from_parts(self.reqwest_client.clone(), req);
if let Some(body) = json_body {
rb = rb.json(body);
rb = rb
.header(ACCEPT, self.serialization.content_type())
.header(CONTENT_TYPE, self.serialization.content_type());
if let Some(body) = body {
match self.serialization {
SerializationFormat::Json => {
rb = rb.json(body);
}
SerializationFormat::Bincode => {
let body = bincode::serialize(body)?;
rb = rb.body(body);
}
SerializationFormat::Yaml => {
let mut body_bytes = Vec::new();
serde_yaml::to_writer(&mut body_bytes, &body)?;
rb = rb.body(body_bytes);
}
SerializationFormat::Text => {
let body = serde_plain::to_string(&body)?.as_bytes().to_vec();
rb = rb.body(body);
}
}
}
rb
Ok(rb)
}
async fn send<E>(&self, request: RequestBuilder) -> Result<Response, HttpClientError<E>>
where
E: Display,
{
async fn send(&self, request: RequestBuilder) -> Result<Response, HttpClientError> {
let mut attempts = 0;
loop {
// try_clone may fail if the body is a stream in which case using retries is not advised.
@@ -769,7 +911,7 @@ impl ApiClientCore for Client {
self.apply_hosts_to_req(&mut req);
#[cfg(target_arch = "wasm32")]
let response: Result<Response, HttpClientError<E>> = {
let response: Result<Response, HttpClientError> = {
Ok(wasmtimer::tokio::timeout(
self.request_timeout,
self.reqwest_client.execute(req),
@@ -791,7 +933,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 {
@@ -816,7 +965,11 @@ impl ApiClientCore for Client {
#[cfg_attr(not(target_arch = "wasm32"), async_trait)]
pub trait ApiClient: ApiClientCore {
/// Create an HTTP GET Request with the provided path and parameters
fn create_get_request<P, K, V>(&self, path: P, params: Params<'_, K, V>) -> RequestBuilder
fn create_get_request<P, K, V>(
&self,
path: P,
params: Params<'_, K, V>,
) -> Result<RequestBuilder, HttpClientError>
where
P: RequestPath,
K: AsRef<str>,
@@ -831,7 +984,7 @@ pub trait ApiClient: ApiClientCore {
path: P,
params: Params<'_, K, V>,
json_body: &B,
) -> RequestBuilder
) -> Result<RequestBuilder, HttpClientError>
where
P: RequestPath,
B: Serialize + ?Sized,
@@ -842,7 +995,11 @@ pub trait ApiClient: ApiClientCore {
}
/// Create an HTTP DELETE Request with the provided path and parameters
fn create_delete_request<P, K, V>(&self, path: P, params: Params<'_, K, V>) -> RequestBuilder
fn create_delete_request<P, K, V>(
&self,
path: P,
params: Params<'_, K, V>,
) -> Result<RequestBuilder, HttpClientError>
where
P: RequestPath,
K: AsRef<str>,
@@ -857,7 +1014,7 @@ pub trait ApiClient: ApiClientCore {
path: P,
params: Params<'_, K, V>,
json_body: &B,
) -> RequestBuilder
) -> Result<RequestBuilder, HttpClientError>
where
P: RequestPath,
B: Serialize + ?Sized,
@@ -869,68 +1026,64 @@ pub trait ApiClient: ApiClientCore {
/// Create and send an HTTP GET Request with the provided path and parameters
#[instrument(level = "debug", skip_all, fields(path=?path))]
async fn send_get_request<P, K, V, E>(
async fn send_get_request<P, K, V>(
&self,
path: P,
params: Params<'_, K, V>,
) -> Result<Response, HttpClientError<E>>
) -> Result<Response, HttpClientError>
where
P: RequestPath + Send + Sync,
K: AsRef<str> + Sync,
V: AsRef<str> + Sync,
E: Display,
{
self.send_request(reqwest::Method::GET, path, params, None::<&()>)
.await
}
/// Create and send an HTTP POST Request with the provided path, parameters, and json data
async fn send_post_request<P, B, K, V, E>(
async fn send_post_request<P, B, K, V>(
&self,
path: P,
params: Params<'_, K, V>,
json_body: &B,
) -> Result<Response, HttpClientError<E>>
) -> Result<Response, HttpClientError>
where
P: RequestPath + Send + Sync,
B: Serialize + ?Sized + Sync,
K: AsRef<str> + Sync,
V: AsRef<str> + Sync,
E: Display,
{
self.send_request(reqwest::Method::POST, path, params, Some(json_body))
.await
}
/// Create and send an HTTP DELETE Request with the provided path and parameters
async fn send_delete_request<P, K, V, E>(
async fn send_delete_request<P, K, V>(
&self,
path: P,
params: Params<'_, K, V>,
) -> Result<Response, HttpClientError<E>>
) -> Result<Response, HttpClientError>
where
P: RequestPath + Send + Sync,
K: AsRef<str> + Sync,
V: AsRef<str> + Sync,
E: Display,
{
self.send_request(reqwest::Method::DELETE, path, params, None::<&()>)
.await
}
/// Create and send an HTTP PATCH Request with the provided path, parameters, and json data
async fn send_patch_request<P, B, K, V, E>(
async fn send_patch_request<P, B, K, V>(
&self,
path: P,
params: Params<'_, K, V>,
json_body: &B,
) -> Result<Response, HttpClientError<E>>
) -> Result<Response, HttpClientError>
where
P: RequestPath + Send + Sync,
B: Serialize + ?Sized + Sync,
K: AsRef<str> + Sync,
V: AsRef<str> + Sync,
E: Display,
{
self.send_request(reqwest::Method::PATCH, path, params, Some(json_body))
.await
@@ -941,17 +1094,16 @@ pub trait ApiClient: ApiClientCore {
/// into the provided type `T`.
#[instrument(level = "debug", skip_all, fields(path=?path))]
// TODO: deprecate in favour of get_response that works based on mime type in the response
async fn get_json<P, T, K, V, E>(
async fn get_json<P, T, K, V>(
&self,
path: P,
params: Params<'_, K, V>,
) -> Result<T, HttpClientError<E>>
) -> Result<T, HttpClientError>
where
P: RequestPath + Send + Sync,
for<'a> T: Deserialize<'a>,
K: AsRef<str> + Sync,
V: AsRef<str> + Sync,
E: Display + DeserializeOwned,
{
self.get_response(path, params).await
}
@@ -959,17 +1111,16 @@ pub trait ApiClient: ApiClientCore {
/// 'get' data from the segment-defined path, e.g. `["api", "v1", "mixnodes"]`, with tuple
/// defined key-value parameters, e.g. `[("since", "12345")]`. Attempt to parse the response
/// into the provided type `T` based on the content type header
async fn get_response<P, T, K, V, E>(
async fn get_response<P, T, K, V>(
&self,
path: P,
params: Params<'_, K, V>,
) -> Result<T, HttpClientError<E>>
) -> Result<T, HttpClientError>
where
P: RequestPath + Send + Sync,
for<'a> T: Deserialize<'a>,
K: AsRef<str> + Sync,
V: AsRef<str> + Sync,
E: Display + DeserializeOwned,
{
let res = self
.send_request(reqwest::Method::GET, path, params, None::<&()>)
@@ -980,19 +1131,18 @@ pub trait ApiClient: ApiClientCore {
/// 'post' json data to the segment-defined path, e.g. `["api", "v1", "mixnodes"]`, with tuple
/// defined key-value parameters, e.g. `[("since", "12345")]`. Attempt to parse the response
/// into the provided type `T`.
async fn post_json<P, B, T, K, V, E>(
async fn post_json<P, B, T, K, V>(
&self,
path: P,
params: Params<'_, K, V>,
json_body: &B,
) -> Result<T, HttpClientError<E>>
) -> Result<T, HttpClientError>
where
P: RequestPath + Send + Sync,
B: Serialize + ?Sized + Sync,
for<'a> T: Deserialize<'a>,
K: AsRef<str> + Sync,
V: AsRef<str> + Sync,
E: Display + DeserializeOwned,
{
let res = self
.send_request(reqwest::Method::POST, path, params, Some(json_body))
@@ -1003,17 +1153,16 @@ pub trait ApiClient: ApiClientCore {
/// 'delete' json data from the segment-defined path, e.g. `["api", "v1", "mixnodes"]`, with
/// tuple defined key-value parameters, e.g. `[("since", "12345")]`. Attempt to parse the
/// response into the provided type `T`.
async fn delete_json<P, T, K, V, E>(
async fn delete_json<P, T, K, V>(
&self,
path: P,
params: Params<'_, K, V>,
) -> Result<T, HttpClientError<E>>
) -> Result<T, HttpClientError>
where
P: RequestPath + Send + Sync,
for<'a> T: Deserialize<'a>,
K: AsRef<str> + Sync,
V: AsRef<str> + Sync,
E: Display + DeserializeOwned,
{
let res = self
.send_request(reqwest::Method::DELETE, path, params, None::<&()>)
@@ -1024,19 +1173,18 @@ pub trait ApiClient: ApiClientCore {
/// 'patch' json data at the segment-defined path, e.g. `["api", "v1", "mixnodes"]`, with tuple
/// defined key-value parameters, e.g. `[("since", "12345")]`. Attempt to parse the response
/// into the provided type `T`.
async fn patch_json<P, B, T, K, V, E>(
async fn patch_json<P, B, T, K, V>(
&self,
path: P,
params: Params<'_, K, V>,
json_body: &B,
) -> Result<T, HttpClientError<E>>
) -> Result<T, HttpClientError>
where
P: RequestPath + Send + Sync,
B: Serialize + ?Sized + Sync,
for<'a> T: Deserialize<'a>,
K: AsRef<str> + Sync,
V: AsRef<str> + Sync,
E: Display + DeserializeOwned,
{
let res = self
.send_request(reqwest::Method::PATCH, path, params, Some(json_body))
@@ -1046,62 +1194,59 @@ pub trait ApiClient: ApiClientCore {
/// `get` json data from the provided absolute endpoint, e.g. `"/api/v1/mixnodes?since=12345"`.
/// Attempt to parse the response into the provided type `T`.
async fn get_json_from<T, S, E>(&self, endpoint: S) -> Result<T, HttpClientError<E>>
async fn get_json_from<T, S>(&self, endpoint: S) -> Result<T, HttpClientError>
where
for<'a> T: Deserialize<'a>,
E: Display + DeserializeOwned,
S: AsRef<str> + Sync + Send,
{
let req = self.create_request_endpoint(reqwest::Method::GET, endpoint, None::<&()>);
let req = self.create_request_endpoint(reqwest::Method::GET, endpoint, None::<&()>)?;
let res = self.send(req).await?;
parse_response(res, false).await
}
/// `post` json data to the provided absolute endpoint, e.g. `"/api/v1/mixnodes?since=12345"`.
/// Attempt to parse the response into the provided type `T`.
async fn post_json_data_to<B, T, S, E>(
async fn post_json_data_to<B, T, S>(
&self,
endpoint: S,
json_body: &B,
) -> Result<T, HttpClientError<E>>
) -> Result<T, HttpClientError>
where
B: Serialize + ?Sized + Sync,
for<'a> T: Deserialize<'a>,
E: Display + DeserializeOwned,
S: AsRef<str> + Sync + Send,
{
let req = self.create_request_endpoint(reqwest::Method::POST, endpoint, Some(json_body));
let req = self.create_request_endpoint(reqwest::Method::POST, endpoint, Some(json_body))?;
let res = self.send(req).await?;
parse_response(res, false).await
}
/// `delete` json data from the provided absolute endpoint, e.g.
/// `"/api/v1/mixnodes?since=12345"`. Attempt to parse the response into the provided type `T`.
async fn delete_json_from<T, S, E>(&self, endpoint: S) -> Result<T, HttpClientError<E>>
async fn delete_json_from<T, S>(&self, endpoint: S) -> Result<T, HttpClientError>
where
for<'a> T: Deserialize<'a>,
E: Display + DeserializeOwned,
S: AsRef<str> + Sync + Send,
{
let req = self.create_request_endpoint(reqwest::Method::DELETE, endpoint, None::<&()>);
let req = self.create_request_endpoint(reqwest::Method::DELETE, endpoint, None::<&()>)?;
let res = self.send(req).await?;
parse_response(res, false).await
}
/// `patch` json data at the provided absolute endpoint, e.g. `"/api/v1/mixnodes?since=12345"`.
/// Attempt to parse the response into the provided type `T`.
async fn patch_json_data_at<B, T, S, E>(
async fn patch_json_data_at<B, T, S>(
&self,
endpoint: S,
json_body: &B,
) -> Result<T, HttpClientError<E>>
) -> Result<T, HttpClientError>
where
B: Serialize + ?Sized + Sync,
for<'a> T: Deserialize<'a>,
E: Display + DeserializeOwned,
S: AsRef<str> + Sync + Send,
{
let req = self.create_request_endpoint(reqwest::Method::PATCH, endpoint, Some(json_body));
let req =
self.create_request_endpoint(reqwest::Method::PATCH, endpoint, Some(json_body))?;
let res = self.send(req).await?;
parse_response(res, false).await
}
@@ -1157,10 +1302,9 @@ fn decode_as_text(bytes: &bytes::Bytes, headers: &HeaderMap) -> String {
/// Attempt to parse a response object from an HTTP response
#[instrument(level = "debug", skip_all)]
pub async fn parse_response<T, E>(res: Response, allow_empty: bool) -> Result<T, HttpClientError<E>>
pub async fn parse_response<T>(res: Response, allow_empty: bool) -> Result<T, HttpClientError>
where
T: DeserializeOwned,
E: DeserializeOwned + Display,
{
let status = res.status();
tracing::trace!("Status: {} (success: {})", &status, status.is_success());
@@ -1196,10 +1340,9 @@ where
}
}
fn decode_as_json<T, E>(headers: &HeaderMap, content: Bytes) -> Result<T, HttpClientError<E>>
fn decode_as_json<T>(headers: &HeaderMap, content: Bytes) -> Result<T, HttpClientError>
where
T: DeserializeOwned,
E: DeserializeOwned + Display,
{
match serde_json::from_slice(&content) {
Ok(data) => Ok(data),
@@ -1213,10 +1356,9 @@ where
}
}
fn decode_as_bincode<T, E>(headers: &HeaderMap, content: Bytes) -> Result<T, HttpClientError<E>>
fn decode_as_bincode<T>(headers: &HeaderMap, content: Bytes) -> Result<T, HttpClientError>
where
T: DeserializeOwned,
E: DeserializeOwned + Display,
{
use bincode::Options;
@@ -1233,10 +1375,9 @@ where
}
}
fn decode_raw_response<T, E>(headers: &HeaderMap, content: Bytes) -> Result<T, HttpClientError<E>>
fn decode_raw_response<T>(headers: &HeaderMap, content: Bytes) -> Result<T, HttpClientError>
where
T: DeserializeOwned,
E: DeserializeOwned + Display,
{
// if content type header is missing, fallback to our old default, json
let mime = try_get_mime_type(headers).unwrap_or(mime::APPLICATION_JSON);
+6 -9
View File
@@ -95,10 +95,10 @@ async fn api_client_retry() -> Result<(), Box<dyn std::error::Error>> {
"http://example.com/".parse()?,
])
.with_retries(3)
.build::<HttpClientError>()?;
.build()?;
let req = client.create_get_request(&["/"], NO_PARAMS);
let resp = client.send::<HttpClientError>(req).await?;
let req = client.create_get_request(&["/"], NO_PARAMS).unwrap();
let resp = client.send(req).await?;
assert_eq!(resp.status(), 200);
@@ -111,10 +111,7 @@ async fn api_client_retry() -> Result<(), Box<dyn std::error::Error>> {
#[test]
fn host_updating() {
let url = Url::new("http://example.com", None).unwrap();
let mut client = ClientBuilder::new::<_, &str>(url)
.unwrap()
.build::<&str>()
.unwrap();
let mut client = ClientBuilder::new(url).unwrap().build().unwrap();
// check that the url is set correctly
let current_url = client.current_url();
@@ -171,10 +168,10 @@ fn host_updating() {
#[cfg(feature = "tunneling")]
fn fronted_host_updating() {
let url = Url::new("http://example.com", Some(vec!["http://front1.com"])).unwrap();
let mut client = ClientBuilder::new::<_, &str>(url)
let mut client = ClientBuilder::new(url)
.unwrap()
.with_fronting(crate::fronted::FrontPolicy::Always)
.build::<&str>()
.build()
.unwrap();
// check that the url is set correctly
+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" }
+10 -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,15 @@ 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) => {
+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]
+20 -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,16 @@ pub async fn current_network_topology_async(
}
};
let api_client = NymApiClient::new(url);
let api_client = nym_http_api_client::Client::builder(url.clone())
.map_err(|_err| WasmCoreError::MalformedUrl {
raw: nym_api_url.to_string(),
source: url::ParseError::EmptyHost,
})?
.build()
.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 +99,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;
@@ -7,16 +7,14 @@ use tracing::instrument;
use nym_http_api_client::{ApiClient, Client, HttpClientError, NO_PARAMS};
use nym_wireguard_private_metadata_shared::{
routes, Version, {ErrorResponse, Request, Response},
routes, Version, {Request, Response},
};
pub type WireguardMetadataApiClientError = HttpClientError<ErrorResponse>;
#[cfg_attr(target_arch = "wasm32", async_trait(?Send))]
#[cfg_attr(not(target_arch = "wasm32"), async_trait)]
pub trait WireguardMetadataApiClient: ApiClient {
#[instrument(level = "debug", skip(self))]
async fn version(&self) -> Result<Version, WireguardMetadataApiClientError> {
async fn version(&self) -> Result<Version, HttpClientError> {
let version: u64 = self
.get_json(
&[routes::V1_API_VERSION, routes::BANDWIDTH, routes::VERSION],
@@ -30,7 +28,7 @@ pub trait WireguardMetadataApiClient: ApiClient {
async fn available_bandwidth(
&self,
request_body: &Request,
) -> Result<Response, WireguardMetadataApiClientError> {
) -> Result<Response, HttpClientError> {
self.post_json(
&[routes::V1_API_VERSION, routes::BANDWIDTH, routes::AVAILABLE],
NO_PARAMS,
@@ -40,10 +38,7 @@ pub trait WireguardMetadataApiClient: ApiClient {
}
#[instrument(level = "debug", skip(self, request_body))]
async fn topup_bandwidth(
&self,
request_body: &Request,
) -> Result<Response, WireguardMetadataApiClientError> {
async fn topup_bandwidth(&self, request_body: &Request) -> Result<Response, HttpClientError> {
self.post_json(
&[routes::V1_API_VERSION, routes::BANDWIDTH, routes::TOPUP],
NO_PARAMS,
@@ -13,7 +13,7 @@ mod tests {
use nym_wireguard_private_metadata_server::{
AppState, PeerControllerTransceiver, RouterBuilder,
};
use nym_wireguard_private_metadata_shared::{latest, v0, v1, ErrorResponse};
use nym_wireguard_private_metadata_shared::{latest, v0, v1};
use tokio::{net::TcpListener, sync::mpsc};
pub(crate) const VERIFIER_AVAILABLE_BANDWIDTH: i64 = 42;
@@ -140,7 +140,7 @@ mod tests {
.await
.unwrap();
});
Client::new_url::<_, ErrorResponse>(addr.to_string(), None).unwrap()
Client::new_url(addr.to_string(), None).unwrap()
}
#[tokio::test]
@@ -15,7 +15,6 @@ pub(crate) mod test {
use nym_http_api_common::{FormattedResponse, OutputParams};
use nym_wireguard::{peer_controller::PeerControlRequest, CONTROL_CHANNEL_SIZE};
use nym_wireguard_private_metadata_server::PeerControllerTransceiver;
use nym_wireguard_private_metadata_shared::ErrorResponse;
use nym_wireguard_private_metadata_shared::{
v0 as latest, AxumErrorResponse, AxumResult, Construct, Extract, Request, Response,
};
@@ -141,6 +140,6 @@ pub(crate) mod test {
.await
.unwrap();
});
Client::new_url::<_, ErrorResponse>(addr.to_string(), None).unwrap()
Client::new_url(addr.to_string(), None).unwrap()
}
}
+1 -1
View File
@@ -52,7 +52,7 @@ impl TestSetup {
}
}
pub fn query_ctx(&self) -> QueryCtx {
pub fn query_ctx(&self) -> QueryCtx<'_> {
QueryCtx::from((self.deps.as_ref(), self.env.clone()))
}
}
+2 -2
View File
@@ -73,13 +73,13 @@ impl TestSetupSimple {
message_info(&admin, &[])
}
pub fn execute_ctx(&mut self, sender: MessageInfo) -> ExecCtx {
pub fn execute_ctx(&mut self, sender: MessageInfo) -> ExecCtx<'_> {
let env = self.env.clone();
ExecCtx::from((self.deps.as_mut(), env, sender))
}
#[allow(dead_code)]
pub fn query_ctx(&self) -> QueryCtx {
pub fn query_ctx(&self) -> QueryCtx<'_> {
QueryCtx::from((self.deps.as_ref(), self.env.clone()))
}
+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)
}
}
@@ -300,7 +300,7 @@ mod tests {
let amount1 = coin(100_000_000, TEST_COIN_DENOM);
let sender1 = message_info(owner, &[amount1.clone()]);
let sender1 = message_info(owner, std::slice::from_ref(&amount1));
try_delegate_to_node(test.deps_mut(), env.clone(), sender1, mix_id).unwrap();
+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,
+3 -3
View File
@@ -131,7 +131,7 @@ impl PreInitContract {
}
}
pub(crate) fn deps(&self) -> Deps {
pub(crate) fn deps(&self) -> Deps<'_> {
Deps {
storage: &self.storage,
api: &self.api,
@@ -139,7 +139,7 @@ impl PreInitContract {
}
}
pub(crate) fn deps_mut(&mut self) -> DepsMut {
pub(crate) fn deps_mut(&mut self) -> DepsMut<'_> {
DepsMut {
storage: &mut self.storage,
api: &self.api,
@@ -147,7 +147,7 @@ impl PreInitContract {
}
}
pub(crate) fn querier(&self) -> QuerierWrapper {
pub(crate) fn querier(&self) -> QuerierWrapper<'_> {
self.tester_builder.querier()
}
+1 -1
View File
@@ -137,7 +137,7 @@ mod tests {
let response = execute(
deps.as_mut(),
env.clone(),
message_info(&admin, &[amount.clone()]),
message_info(&admin, std::slice::from_ref(&amount)),
msg,
);
assert_eq!(
@@ -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,
@@ -97,7 +97,7 @@ impl NymVpnApiClient for VpnApiClient {
{
let req = self
.inner
.create_get_request(path, NO_PARAMS)
.create_get_request(path, NO_PARAMS)?
.bearer_auth(&self.bearer_token)
.send();
@@ -129,7 +129,7 @@ impl NymVpnApiClient for VpnApiClient {
{
let req = self
.inner
.create_post_request(path, params, json_body)
.create_post_request(path, params, json_body)?
.bearer_auth(&self.bearer_token)
.send();
@@ -12,6 +12,7 @@ use nym_credential_proxy_requests::api::v1::ticketbook::models::{
};
use nym_credentials_interface::Base58;
use nym_validator_client::ecash::BlindSignRequestBody;
use nym_validator_client::nym_api::NymApiClientExt;
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" }
+14 -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,9 @@ 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(api_url)?
.with_user_agent(UserAgent::from(bin_info!()))
.build()?;
let rewarded_set = client.get_current_rewarded_set().await?;
@@ -172,10 +174,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;
@@ -60,13 +60,11 @@ async fn run(
let nym_api = nym_http_api_client::ClientBuilder::new_with_urls(vec![default_api_url.into()])
.no_hickory_dns()
.with_timeout(nym_api_client_timeout)
.build::<&str>()?;
let api_client = NymApiClient::from(nym_api);
.build()?;
//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},
@@ -109,11 +109,9 @@ impl Monitor {
nym_http_api_client::ClientBuilder::new_with_urls(vec![default_api_url.into()])
.no_hickory_dns()
.with_timeout(self.nym_api_client_timeout)
.build::<&str>()?;
.build()?;
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")?
+1 -2
View File
@@ -5,7 +5,6 @@ use crate::api::v1::gateway::models::WebSockets;
use crate::api::v1::node::models::{
AuxiliaryDetails, NodeDescription, NodeRoles, SignedHostInformation,
};
use crate::api::ErrorResponse;
use crate::routes;
use async_trait::async_trait;
use nym_bin_common::build_information::BinaryBuildInformationOwned;
@@ -21,7 +20,7 @@ use crate::api::v1::network_requester::models::NetworkRequester;
use crate::api::v1::node_load::models::NodeLoad;
pub use nym_http_api_client::Client;
pub type NymNodeApiClientError = HttpClientError<ErrorResponse>;
pub type NymNodeApiClientError = HttpClientError;
#[cfg_attr(target_arch = "wasm32", async_trait(?Send))]
#[cfg_attr(not(target_arch = "wasm32"), async_trait)]
+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(url)?
.no_hickory_dns()
.with_user_agent("node-statistics-api")
.build()?)
}
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())
{
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};
+30
View File
@@ -4224,6 +4224,8 @@ dependencies = [
"reqwest 0.12.15",
"serde",
"serde_json",
"serde_plain",
"serde_yaml",
"thiserror 2.0.12",
"tracing",
"url",
@@ -6308,6 +6310,15 @@ dependencies = [
"serde",
]
[[package]]
name = "serde_plain"
version = "1.0.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9ce1fc6db65a611022b23a0dec6975d63fb80a302cb3388835ff02c097258d50"
dependencies = [
"serde",
]
[[package]]
name = "serde_repr"
version = "0.1.20"
@@ -6370,6 +6381,19 @@ dependencies = [
"syn 2.0.100",
]
[[package]]
name = "serde_yaml"
version = "0.9.34+deprecated"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6a8b1a1a2ebf674015cc02edccce75287f1a0130d394307b36743c2f5d504b47"
dependencies = [
"indexmap 2.8.0",
"itoa 1.0.15",
"ryu",
"serde",
"unsafe-libyaml",
]
[[package]]
name = "serdect"
version = "0.2.0"
@@ -7859,6 +7883,12 @@ dependencies = [
"subtle",
]
[[package]]
name = "unsafe-libyaml"
version = "0.2.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "673aac59facbab8a9007c7f6108d11f63b603f7cabff99fabf650fea5c32b861"
[[package]]
name = "untrusted"
version = "0.9.0"
-6
View File
@@ -2,7 +2,6 @@ use nym_contracts_common::signing::SigningAlgorithm;
use nym_crypto::asymmetric::ed25519::Ed25519RecoveryError;
use nym_node_requests::api::client::NymNodeApiClientError;
use nym_types::error::TypesError;
use nym_validator_client::nym_api::error::NymAPIError;
use nym_validator_client::signing::direct_wallet::DirectSecp256k1HdWalletError;
use nym_validator_client::{nyxd::error::NyxdError, ValidatorClientError};
use nym_wallet_types::network::Network;
@@ -45,11 +44,6 @@ pub enum BackendError {
source: eyre::Report,
},
#[error(transparent)]
NymApiError {
#[from]
source: NymAPIError,
},
#[error(transparent)]
NymNodeApiError {
#[from]
source: NymNodeApiClientError,
@@ -15,7 +15,6 @@ use nym_mixnet_contract_common::nym_node::{NodeConfigUpdate, StakeSaturationResp
use nym_mixnet_contract_common::{MixNodeConfigUpdate, NodeId, NymNode};
use nym_node_requests::api::client::NymNodeApiClientExt;
use nym_node_requests::api::v1::node::models::NodeDescription;
use nym_node_requests::api::ErrorResponse;
use nym_types::currency::DecCoin;
use nym_types::gateway::GatewayBond;
use nym_types::mixnode::{MixNodeDetails, NodeCostParams};
@@ -586,14 +585,12 @@ pub async fn get_nym_node_description(
port: u16,
) -> Result<NodeDescription, BackendError> {
Ok(
nym_node_requests::api::Client::builder::<_, ErrorResponse>(format!(
"http://{host}:{port}"
))?
.with_timeout(Duration::from_millis(1000))
.with_user_agent(format!("nym-wallet/{}", env!("CARGO_PKG_VERSION")))
.build::<ErrorResponse>()?
.get_description()
.await?,
nym_node_requests::api::Client::builder(format!("http://{host}:{port}"))?
.with_timeout(Duration::from_millis(1000))
.with_user_agent(format!("nym-wallet/{}", env!("CARGO_PKG_VERSION")))
.build()?
.get_description()
.await?,
)
}
+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,22 @@
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_api_url)
.expect("Failed to create API client builder")
.build()
.expect("Failed to build API client");
MyTopologyProvider { validator_client }
}
async fn get_topology(&self) -> NymTopology {
@@ -33,7 +37,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,10 @@ 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(api_url.clone())
.expect("Failed to create API client builder")
.build()
.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,17 @@ impl SignerStatus {
}
};
Some(NymApiClient::new(api_endpoint))
nym_http_api_client::Client::builder(api_endpoint)
.ok()?
.build()
.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 +86,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 =
+2 -2
View File
@@ -1,7 +1,7 @@
// Copyright 2024 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use crate::vpn_api_client::NymVpnApiClientError;
use nym_http_api_client::HttpClientError;
use thiserror::Error;
use wasm_utils::wasm_error;
@@ -16,7 +16,7 @@ pub enum ZkNymError {
#[error("failed to contact the vpn api")]
HttpClientFailure {
#[from]
source: NymVpnApiClientError,
source: HttpClientError,
},
#[error("the provided shares and issuers are not from the same epoch! {shares} and {issuers}")]
InconsistentEpochId { shares: u64, issuers: u64 },
+9 -11
View File
@@ -1,14 +1,14 @@
// Copyright 2024 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use super::NymVpnApiClientError;
use crate::error::ZkNymError;
use crate::vpn_api_client::types::{
AttributesResponse, MasterVerificationKeyResponse, PartialVerificationKeysResponse,
};
use async_trait::async_trait;
pub use nym_http_api_client::Client;
use nym_http_api_client::{parse_response, ApiClient, IntoUrl, PathSegments, NO_PARAMS};
use nym_http_api_client::{
parse_response, ApiClient, HttpClientError, IntoUrl, PathSegments, NO_PARAMS,
};
use serde::de::DeserializeOwned;
#[allow(dead_code)]
@@ -34,13 +34,11 @@ pub fn new_client(
#[allow(dead_code)]
#[async_trait(?Send)]
pub trait NymVpnApiClient {
async fn simple_get<T>(&self, path: PathSegments<'_>) -> Result<T, NymVpnApiClientError>
async fn simple_get<T>(&self, path: PathSegments<'_>) -> Result<T, HttpClientError>
where
T: DeserializeOwned;
async fn get_prehashed_public_attributes(
&self,
) -> Result<AttributesResponse, NymVpnApiClientError> {
async fn get_prehashed_public_attributes(&self) -> Result<AttributesResponse, HttpClientError> {
self.simple_get(&[
"/api",
"/v1",
@@ -52,7 +50,7 @@ pub trait NymVpnApiClient {
async fn get_partial_verification_keys(
&self,
) -> Result<PartialVerificationKeysResponse, NymVpnApiClientError> {
) -> Result<PartialVerificationKeysResponse, HttpClientError> {
self.simple_get(&[
"/api",
"/v1",
@@ -64,7 +62,7 @@ pub trait NymVpnApiClient {
async fn get_master_verification_key(
&self,
) -> Result<MasterVerificationKeyResponse, NymVpnApiClientError> {
) -> Result<MasterVerificationKeyResponse, HttpClientError> {
self.simple_get(&[
"/api",
"/v1",
@@ -77,13 +75,13 @@ pub trait NymVpnApiClient {
#[async_trait(?Send)]
impl NymVpnApiClient for VpnApiClient {
async fn simple_get<T>(&self, path: PathSegments<'_>) -> Result<T, NymVpnApiClientError>
async fn simple_get<T>(&self, path: PathSegments<'_>) -> Result<T, HttpClientError>
where
T: DeserializeOwned,
{
let req = self
.inner
.create_get_request(path, NO_PARAMS)
.create_get_request(path, NO_PARAMS)?
.bearer_auth(&self.bearer_token)
.send();
-5
View File
@@ -1,12 +1,7 @@
// Copyright 2024 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use crate::vpn_api_client::types::ErrorResponse;
use nym_http_api_client::HttpClientError;
#[cfg(test)]
pub(crate) mod client;
pub mod types;
pub type NymVpnApiClientError = HttpClientError<ErrorResponse>;