Compare commits
19 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 637d9a4ebf | |||
| ecf6e849d9 | |||
| 6156e2656e | |||
| bd2174641e | |||
| 59b62fabc9 | |||
| e6f4bae895 | |||
| d71742af32 | |||
| 3b7c07e249 | |||
| 3b429dde69 | |||
| 3a29c296da | |||
| 8544c54f8f | |||
| 9f9639950b | |||
| 111a0b20b6 | |||
| 67b300d0b8 | |||
| f6800aff0a | |||
| 0c7c927ca2 | |||
| a69c8b1660 | |||
| f3ea310a46 | |||
| 92f9ff035d |
@@ -36,7 +36,7 @@ nym-bandwidth-controller = { path = "../bandwidth-controller" }
|
|||||||
nym-crypto = { path = "../crypto" }
|
nym-crypto = { path = "../crypto" }
|
||||||
nym-gateway-client = { path = "../client-libs/gateway-client" }
|
nym-gateway-client = { path = "../client-libs/gateway-client" }
|
||||||
nym-gateway-requests = { path = "../gateway-requests" }
|
nym-gateway-requests = { path = "../gateway-requests" }
|
||||||
nym-http-api-client = { path = "../http-api-client" }
|
nym-http-api-client = { path = "../http-api-client", features = ["network-defaults"] }
|
||||||
nym-nonexhaustive-delayqueue = { path = "../nonexhaustive-delayqueue" }
|
nym-nonexhaustive-delayqueue = { path = "../nonexhaustive-delayqueue" }
|
||||||
nym-sphinx = { path = "../nymsphinx" }
|
nym-sphinx = { path = "../nymsphinx" }
|
||||||
nym-statistics-common = { path = "../statistics" }
|
nym-statistics-common = { path = "../statistics" }
|
||||||
|
|||||||
@@ -140,6 +140,7 @@ where
|
|||||||
available_gateways,
|
available_gateways,
|
||||||
#[cfg(unix)]
|
#[cfg(unix)]
|
||||||
connection_fd_callback: None,
|
connection_fd_callback: None,
|
||||||
|
connect_timeout: None,
|
||||||
};
|
};
|
||||||
|
|
||||||
let init_details =
|
let init_details =
|
||||||
|
|||||||
@@ -188,6 +188,7 @@ where
|
|||||||
available_gateways,
|
available_gateways,
|
||||||
#[cfg(unix)]
|
#[cfg(unix)]
|
||||||
connection_fd_callback: None,
|
connection_fd_callback: None,
|
||||||
|
connect_timeout: None,
|
||||||
};
|
};
|
||||||
|
|
||||||
let init_details =
|
let init_details =
|
||||||
|
|||||||
@@ -65,6 +65,7 @@ use std::fmt::Debug;
|
|||||||
use std::os::raw::c_int as RawFd;
|
use std::os::raw::c_int as RawFd;
|
||||||
use std::path::Path;
|
use std::path::Path;
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
|
use std::time::Duration;
|
||||||
use time::OffsetDateTime;
|
use time::OffsetDateTime;
|
||||||
use tokio::sync::mpsc::Sender;
|
use tokio::sync::mpsc::Sender;
|
||||||
use url::Url;
|
use url::Url;
|
||||||
@@ -73,6 +74,10 @@ use url::Url;
|
|||||||
#[cfg(debug_assertions)]
|
#[cfg(debug_assertions)]
|
||||||
use wasm_utils::console_log;
|
use wasm_utils::console_log;
|
||||||
|
|
||||||
|
/// Default number of retries for Nym API requests when using network details with domain fronting.
|
||||||
|
/// This allows the client to try alternative URLs if the primary endpoint is unavailable.
|
||||||
|
const DEFAULT_NYM_API_RETRIES: usize = 3;
|
||||||
|
|
||||||
#[cfg(all(
|
#[cfg(all(
|
||||||
not(target_arch = "wasm32"),
|
not(target_arch = "wasm32"),
|
||||||
feature = "fs-surb-storage",
|
feature = "fs-surb-storage",
|
||||||
@@ -212,6 +217,9 @@ pub struct BaseClientBuilder<C, S: MixnetClientStorage> {
|
|||||||
client_store: S,
|
client_store: S,
|
||||||
dkg_query_client: Option<C>,
|
dkg_query_client: Option<C>,
|
||||||
|
|
||||||
|
// Optional API URLs for domain fronting support
|
||||||
|
nym_api_urls: Option<Vec<nym_network_defaults::ApiUrl>>,
|
||||||
|
|
||||||
wait_for_gateway: bool,
|
wait_for_gateway: bool,
|
||||||
custom_topology_provider: Option<Box<dyn TopologyProvider + Send + Sync>>,
|
custom_topology_provider: Option<Box<dyn TopologyProvider + Send + Sync>>,
|
||||||
custom_gateway_transceiver: Option<Box<dyn GatewayTransceiver + Send>>,
|
custom_gateway_transceiver: Option<Box<dyn GatewayTransceiver + Send>>,
|
||||||
@@ -223,6 +231,7 @@ pub struct BaseClientBuilder<C, S: MixnetClientStorage> {
|
|||||||
|
|
||||||
#[cfg(unix)]
|
#[cfg(unix)]
|
||||||
connection_fd_callback: Option<Arc<dyn Fn(RawFd) + Send + Sync>>,
|
connection_fd_callback: Option<Arc<dyn Fn(RawFd) + Send + Sync>>,
|
||||||
|
connect_timeout: Option<Duration>,
|
||||||
|
|
||||||
derivation_material: Option<DerivationMaterial>,
|
derivation_material: Option<DerivationMaterial>,
|
||||||
}
|
}
|
||||||
@@ -241,6 +250,7 @@ where
|
|||||||
config: base_config,
|
config: base_config,
|
||||||
client_store,
|
client_store,
|
||||||
dkg_query_client,
|
dkg_query_client,
|
||||||
|
nym_api_urls: None,
|
||||||
wait_for_gateway: false,
|
wait_for_gateway: false,
|
||||||
custom_topology_provider: None,
|
custom_topology_provider: None,
|
||||||
custom_gateway_transceiver: None,
|
custom_gateway_transceiver: None,
|
||||||
@@ -250,6 +260,7 @@ where
|
|||||||
setup_method: GatewaySetup::MustLoad { gateway_id: None },
|
setup_method: GatewaySetup::MustLoad { gateway_id: None },
|
||||||
#[cfg(unix)]
|
#[cfg(unix)]
|
||||||
connection_fd_callback: None,
|
connection_fd_callback: None,
|
||||||
|
connect_timeout: None,
|
||||||
derivation_material: None,
|
derivation_material: None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -263,6 +274,16 @@ where
|
|||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Set Nym API URLs for domain fronting support.
|
||||||
|
///
|
||||||
|
/// When provided, the client will use these API URLs (which include front_hosts)
|
||||||
|
/// to construct HTTP clients with domain fronting enabled.
|
||||||
|
#[must_use]
|
||||||
|
pub fn with_nym_api_urls(mut self, nym_api_urls: Vec<nym_network_defaults::ApiUrl>) -> Self {
|
||||||
|
self.nym_api_urls = Some(nym_api_urls);
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
#[must_use]
|
#[must_use]
|
||||||
pub fn with_forget_me(mut self, forget_me: &ForgetMe) -> Self {
|
pub fn with_forget_me(mut self, forget_me: &ForgetMe) -> Self {
|
||||||
self.config.debug.forget_me = *forget_me;
|
self.config.debug.forget_me = *forget_me;
|
||||||
@@ -338,6 +359,11 @@ where
|
|||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn with_connect_timeout(mut self, timeout: Duration) -> Self {
|
||||||
|
self.connect_timeout = Some(timeout);
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
// note: do **NOT** make this method public as its only valid usage is from within `start_base`
|
// note: do **NOT** make this method public as its only valid usage is from within `start_base`
|
||||||
// because it relies on the crypto keys being already loaded
|
// because it relies on the crypto keys being already loaded
|
||||||
fn mix_address(details: &InitialisationResult) -> Recipient {
|
fn mix_address(details: &InitialisationResult) -> Recipient {
|
||||||
@@ -515,6 +541,7 @@ where
|
|||||||
packet_router: PacketRouter,
|
packet_router: PacketRouter,
|
||||||
stats_reporter: ClientStatsSender,
|
stats_reporter: ClientStatsSender,
|
||||||
#[cfg(unix)] connection_fd_callback: Option<Arc<dyn Fn(RawFd) + Send + Sync>>,
|
#[cfg(unix)] connection_fd_callback: Option<Arc<dyn Fn(RawFd) + Send + Sync>>,
|
||||||
|
connect_timeout: Option<Duration>,
|
||||||
shutdown_tracker: &ShutdownTracker,
|
shutdown_tracker: &ShutdownTracker,
|
||||||
) -> Result<GatewayClient<C, S::CredentialStore>, ClientCoreError>
|
) -> Result<GatewayClient<C, S::CredentialStore>, ClientCoreError>
|
||||||
where
|
where
|
||||||
@@ -559,6 +586,7 @@ where
|
|||||||
stats_reporter,
|
stats_reporter,
|
||||||
#[cfg(unix)]
|
#[cfg(unix)]
|
||||||
connection_fd_callback,
|
connection_fd_callback,
|
||||||
|
connect_timeout,
|
||||||
shutdown_tracker.clone_shutdown_token(),
|
shutdown_tracker.clone_shutdown_token(),
|
||||||
)
|
)
|
||||||
};
|
};
|
||||||
@@ -622,6 +650,7 @@ where
|
|||||||
packet_router: PacketRouter,
|
packet_router: PacketRouter,
|
||||||
stats_reporter: ClientStatsSender,
|
stats_reporter: ClientStatsSender,
|
||||||
#[cfg(unix)] connection_fd_callback: Option<Arc<dyn Fn(RawFd) + Send + Sync>>,
|
#[cfg(unix)] connection_fd_callback: Option<Arc<dyn Fn(RawFd) + Send + Sync>>,
|
||||||
|
connect_timeout: Option<Duration>,
|
||||||
shutdown_tracker: &ShutdownTracker,
|
shutdown_tracker: &ShutdownTracker,
|
||||||
) -> Result<Box<dyn GatewayTransceiver + Send>, ClientCoreError>
|
) -> Result<Box<dyn GatewayTransceiver + Send>, ClientCoreError>
|
||||||
where
|
where
|
||||||
@@ -654,6 +683,7 @@ where
|
|||||||
stats_reporter,
|
stats_reporter,
|
||||||
#[cfg(unix)]
|
#[cfg(unix)]
|
||||||
connection_fd_callback,
|
connection_fd_callback,
|
||||||
|
connect_timeout,
|
||||||
shutdown_tracker,
|
shutdown_tracker,
|
||||||
)
|
)
|
||||||
.await?;
|
.await?;
|
||||||
@@ -863,21 +893,67 @@ where
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn construct_nym_api_client(
|
fn construct_nym_api_client(
|
||||||
|
nym_api_urls: Option<&Vec<nym_network_defaults::ApiUrl>>,
|
||||||
config: &Config,
|
config: &Config,
|
||||||
user_agent: Option<UserAgent>,
|
user_agent: Option<UserAgent>,
|
||||||
) -> Result<nym_http_api_client::Client, ClientCoreError> {
|
) -> Result<nym_http_api_client::Client, ClientCoreError> {
|
||||||
|
tracing::debug!(
|
||||||
|
"construct_nym_api_client called with nym_api_urls: {}",
|
||||||
|
nym_api_urls.is_some()
|
||||||
|
);
|
||||||
|
|
||||||
|
// If API URLs are provided, use new_with_fronted_urls() which handles domain fronting
|
||||||
|
if let Some(nym_api_urls) = nym_api_urls {
|
||||||
|
if nym_api_urls.is_empty() {
|
||||||
|
tracing::warn!("Provided nym_api_urls is empty, falling back to config endpoints");
|
||||||
|
} else {
|
||||||
|
tracing::info!(
|
||||||
|
"Building nym-api client from provided URLs (with domain fronting support): {} URLs",
|
||||||
|
nym_api_urls.len()
|
||||||
|
);
|
||||||
|
|
||||||
|
let mut builder =
|
||||||
|
nym_http_api_client::ClientBuilder::new_with_fronted_urls(nym_api_urls.clone())
|
||||||
|
.map_err(ClientCoreError::from)?
|
||||||
|
.with_retries(DEFAULT_NYM_API_RETRIES);
|
||||||
|
|
||||||
|
if let Some(user_agent) = user_agent {
|
||||||
|
builder = builder.with_user_agent(user_agent);
|
||||||
|
}
|
||||||
|
|
||||||
|
return builder.build().map_err(ClientCoreError::from);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fallback to basic client for backwards compatibility
|
||||||
|
tracing::debug!("Building basic nym-api HTTP client from config endpoints");
|
||||||
|
|
||||||
let mut nym_api_urls = config.get_nym_api_endpoints();
|
let mut nym_api_urls = config.get_nym_api_endpoints();
|
||||||
|
if nym_api_urls.is_empty() {
|
||||||
|
tracing::warn!("No API endpoints configured in config, this may cause issues");
|
||||||
|
}
|
||||||
nym_api_urls.shuffle(&mut thread_rng());
|
nym_api_urls.shuffle(&mut thread_rng());
|
||||||
|
|
||||||
let mut builder = nym_http_api_client::Client::builder(nym_api_urls[0].clone())
|
// Convert config URLs to ApiUrl format for consistency
|
||||||
.map_err(ClientCoreError::from)?;
|
let api_urls: Vec<nym_network_defaults::ApiUrl> = nym_api_urls
|
||||||
|
.into_iter()
|
||||||
|
.map(|url| nym_network_defaults::ApiUrl {
|
||||||
|
url: url.to_string(),
|
||||||
|
front_hosts: None,
|
||||||
|
})
|
||||||
|
.collect();
|
||||||
|
|
||||||
|
tracing::debug!("Using {} config API endpoints", api_urls.len());
|
||||||
|
|
||||||
|
let mut builder = nym_http_api_client::ClientBuilder::new_with_fronted_urls(api_urls)
|
||||||
|
.map_err(ClientCoreError::from)?
|
||||||
|
.with_retries(DEFAULT_NYM_API_RETRIES)
|
||||||
|
.with_bincode();
|
||||||
|
|
||||||
if let Some(user_agent) = user_agent {
|
if let Some(user_agent) = user_agent {
|
||||||
builder = builder.with_user_agent(user_agent);
|
builder = builder.with_user_agent(user_agent);
|
||||||
}
|
}
|
||||||
|
|
||||||
builder = builder.with_bincode();
|
|
||||||
|
|
||||||
builder.build().map_err(ClientCoreError::from)
|
builder.build().map_err(ClientCoreError::from)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -961,7 +1037,11 @@ where
|
|||||||
.dkg_query_client
|
.dkg_query_client
|
||||||
.map(|client| BandwidthController::new(credential_store, 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.nym_api_urls.as_ref(),
|
||||||
|
&self.config,
|
||||||
|
self.user_agent.clone(),
|
||||||
|
)?;
|
||||||
let key_rotation_config = Self::determine_key_rotation_state(&nym_api_client).await?;
|
let key_rotation_config = Self::determine_key_rotation_state(&nym_api_client).await?;
|
||||||
|
|
||||||
let topology_provider = Self::setup_topology_provider(
|
let topology_provider = Self::setup_topology_provider(
|
||||||
@@ -1006,6 +1086,7 @@ where
|
|||||||
stats_reporter.clone(),
|
stats_reporter.clone(),
|
||||||
#[cfg(unix)]
|
#[cfg(unix)]
|
||||||
self.connection_fd_callback,
|
self.connection_fd_callback,
|
||||||
|
self.connect_timeout,
|
||||||
&shutdown_tracker.child_tracker(),
|
&shutdown_tracker.child_tracker(),
|
||||||
)
|
)
|
||||||
.await?;
|
.await?;
|
||||||
@@ -1136,3 +1217,53 @@ pub struct BaseClient {
|
|||||||
pub forget_me: ForgetMe,
|
pub forget_me: ForgetMe,
|
||||||
pub remember_me: RememberMe,
|
pub remember_me: RememberMe,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::*;
|
||||||
|
use nym_network_defaults::{ApiUrl, NymNetworkDetails};
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_network_details_with_multiple_urls() {
|
||||||
|
// Verify that network details can be configured with multiple API URLs
|
||||||
|
let mut network_details = NymNetworkDetails::new_empty();
|
||||||
|
network_details.nym_api_urls = Some(vec![
|
||||||
|
ApiUrl {
|
||||||
|
url: "https://validator.nymtech.net/api/".to_string(),
|
||||||
|
front_hosts: None,
|
||||||
|
},
|
||||||
|
ApiUrl {
|
||||||
|
url: "https://nym-frontdoor.vercel.app/api/".to_string(),
|
||||||
|
front_hosts: Some(vec!["vercel.app".to_string(), "vercel.com".to_string()]),
|
||||||
|
},
|
||||||
|
]);
|
||||||
|
|
||||||
|
assert_eq!(network_details.nym_api_urls.as_ref().unwrap().len(), 2);
|
||||||
|
assert!(network_details.nym_api_urls.as_ref().unwrap()[1]
|
||||||
|
.front_hosts
|
||||||
|
.is_some());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_network_details_with_front_hosts() {
|
||||||
|
// Verify that ApiUrl can store domain fronting configuration
|
||||||
|
let api_url = ApiUrl {
|
||||||
|
url: "https://nym-frontdoor.vercel.app/api/".to_string(),
|
||||||
|
front_hosts: Some(vec!["vercel.app".to_string(), "vercel.com".to_string()]),
|
||||||
|
};
|
||||||
|
|
||||||
|
assert_eq!(api_url.url, "https://nym-frontdoor.vercel.app/api/");
|
||||||
|
assert_eq!(api_url.front_hosts.as_ref().unwrap().len(), 2);
|
||||||
|
assert!(api_url
|
||||||
|
.front_hosts
|
||||||
|
.as_ref()
|
||||||
|
.unwrap()
|
||||||
|
.contains(&"vercel.app".to_string()));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_default_nym_api_retries_constant() {
|
||||||
|
// Verify the retry constant is set correctly
|
||||||
|
assert_eq!(DEFAULT_NYM_API_RETRIES, 3);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -151,7 +151,7 @@ pub async fn gateways_for_init(
|
|||||||
}
|
}
|
||||||
|
|
||||||
let retry_count = retry_count.unwrap_or(DEFAULT_NYM_API_RETRIES);
|
let retry_count = retry_count.unwrap_or(DEFAULT_NYM_API_RETRIES);
|
||||||
let mut builder = nym_http_api_client::ClientBuilder::new_with_urls(nym_api_urls.clone())
|
let mut builder = nym_http_api_client::ClientBuilder::new_with_urls(nym_api_urls.clone())?
|
||||||
.with_retries(retry_count)
|
.with_retries(retry_count)
|
||||||
.with_bincode();
|
.with_bincode();
|
||||||
|
|
||||||
@@ -382,6 +382,7 @@ pub(super) async fn register_with_gateway(
|
|||||||
gateway_listener: Url,
|
gateway_listener: Url,
|
||||||
our_identity: Arc<ed25519::KeyPair>,
|
our_identity: Arc<ed25519::KeyPair>,
|
||||||
#[cfg(unix)] connection_fd_callback: Option<Arc<dyn Fn(RawFd) + Send + Sync>>,
|
#[cfg(unix)] connection_fd_callback: Option<Arc<dyn Fn(RawFd) + Send + Sync>>,
|
||||||
|
connect_timeout: Option<Duration>,
|
||||||
) -> Result<RegistrationResult, ClientCoreError> {
|
) -> Result<RegistrationResult, ClientCoreError> {
|
||||||
let mut gateway_client = GatewayClient::new_init(
|
let mut gateway_client = GatewayClient::new_init(
|
||||||
gateway_listener,
|
gateway_listener,
|
||||||
@@ -389,6 +390,7 @@ pub(super) async fn register_with_gateway(
|
|||||||
our_identity.clone(),
|
our_identity.clone(),
|
||||||
#[cfg(unix)]
|
#[cfg(unix)]
|
||||||
connection_fd_callback,
|
connection_fd_callback,
|
||||||
|
connect_timeout,
|
||||||
);
|
);
|
||||||
|
|
||||||
gateway_client.establish_connection().await.map_err(|err| {
|
gateway_client.establish_connection().await.map_err(|err| {
|
||||||
|
|||||||
@@ -23,6 +23,7 @@ use nym_topology::node::RoutingNode;
|
|||||||
use rand::rngs::OsRng;
|
use rand::rngs::OsRng;
|
||||||
use rand::{CryptoRng, RngCore};
|
use rand::{CryptoRng, RngCore};
|
||||||
use serde::Serialize;
|
use serde::Serialize;
|
||||||
|
use std::time::Duration;
|
||||||
#[cfg(unix)]
|
#[cfg(unix)]
|
||||||
use std::{os::fd::RawFd, sync::Arc};
|
use std::{os::fd::RawFd, sync::Arc};
|
||||||
|
|
||||||
@@ -56,6 +57,7 @@ async fn setup_new_gateway<K, D>(
|
|||||||
selection_specification: GatewaySelectionSpecification,
|
selection_specification: GatewaySelectionSpecification,
|
||||||
available_gateways: Vec<RoutingNode>,
|
available_gateways: Vec<RoutingNode>,
|
||||||
#[cfg(unix)] connection_fd_callback: Option<Arc<dyn Fn(RawFd) + Send + Sync>>,
|
#[cfg(unix)] connection_fd_callback: Option<Arc<dyn Fn(RawFd) + Send + Sync>>,
|
||||||
|
connect_timeout: Option<Duration>,
|
||||||
) -> Result<InitialisationResult, ClientCoreError>
|
) -> Result<InitialisationResult, ClientCoreError>
|
||||||
where
|
where
|
||||||
K: KeyStore,
|
K: KeyStore,
|
||||||
@@ -117,6 +119,7 @@ where
|
|||||||
our_identity,
|
our_identity,
|
||||||
#[cfg(unix)]
|
#[cfg(unix)]
|
||||||
connection_fd_callback,
|
connection_fd_callback,
|
||||||
|
connect_timeout,
|
||||||
)
|
)
|
||||||
.await?;
|
.await?;
|
||||||
(
|
(
|
||||||
@@ -213,6 +216,7 @@ where
|
|||||||
available_gateways,
|
available_gateways,
|
||||||
#[cfg(unix)]
|
#[cfg(unix)]
|
||||||
connection_fd_callback,
|
connection_fd_callback,
|
||||||
|
connect_timeout,
|
||||||
} => {
|
} => {
|
||||||
tracing::debug!("GatewaySetup::New with spec: {specification:?}");
|
tracing::debug!("GatewaySetup::New with spec: {specification:?}");
|
||||||
setup_new_gateway(
|
setup_new_gateway(
|
||||||
@@ -222,6 +226,7 @@ where
|
|||||||
available_gateways,
|
available_gateways,
|
||||||
#[cfg(unix)]
|
#[cfg(unix)]
|
||||||
connection_fd_callback,
|
connection_fd_callback,
|
||||||
|
connect_timeout,
|
||||||
)
|
)
|
||||||
.await
|
.await
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -21,6 +21,7 @@ use std::fmt::{Debug, Display};
|
|||||||
#[cfg(unix)]
|
#[cfg(unix)]
|
||||||
use std::os::fd::RawFd;
|
use std::os::fd::RawFd;
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
|
use std::time::Duration;
|
||||||
use time::OffsetDateTime;
|
use time::OffsetDateTime;
|
||||||
use url::Url;
|
use url::Url;
|
||||||
|
|
||||||
@@ -214,6 +215,9 @@ pub enum GatewaySetup {
|
|||||||
/// Callback useful for allowing initial connection to gateway
|
/// Callback useful for allowing initial connection to gateway
|
||||||
#[cfg(unix)]
|
#[cfg(unix)]
|
||||||
connection_fd_callback: Option<Arc<dyn Fn(RawFd) + Send + Sync>>,
|
connection_fd_callback: Option<Arc<dyn Fn(RawFd) + Send + Sync>>,
|
||||||
|
|
||||||
|
/// Timeout for establishing connection
|
||||||
|
connect_timeout: Option<Duration>,
|
||||||
},
|
},
|
||||||
|
|
||||||
ReuseConnection {
|
ReuseConnection {
|
||||||
@@ -239,6 +243,7 @@ impl Debug for GatewaySetup {
|
|||||||
available_gateways,
|
available_gateways,
|
||||||
#[cfg(unix)]
|
#[cfg(unix)]
|
||||||
connection_fd_callback: _,
|
connection_fd_callback: _,
|
||||||
|
connect_timeout: _,
|
||||||
} => f
|
} => f
|
||||||
.debug_struct("GatewaySetup::New")
|
.debug_struct("GatewaySetup::New")
|
||||||
.field("specification", specification)
|
.field("specification", specification)
|
||||||
@@ -280,6 +285,7 @@ impl GatewaySetup {
|
|||||||
available_gateways: vec![],
|
available_gateways: vec![],
|
||||||
#[cfg(unix)]
|
#[cfg(unix)]
|
||||||
connection_fd_callback: None,
|
connection_fd_callback: None,
|
||||||
|
connect_timeout: None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -38,6 +38,7 @@ use url::Url;
|
|||||||
|
|
||||||
#[cfg(unix)]
|
#[cfg(unix)]
|
||||||
use std::os::fd::RawFd;
|
use std::os::fd::RawFd;
|
||||||
|
use std::time::Duration;
|
||||||
#[cfg(not(target_arch = "wasm32"))]
|
#[cfg(not(target_arch = "wasm32"))]
|
||||||
use tokio::time::sleep;
|
use tokio::time::sleep;
|
||||||
|
|
||||||
@@ -104,10 +105,13 @@ pub struct GatewayClient<C, St = EphemeralCredentialStorage> {
|
|||||||
// currently unused (but populated)
|
// currently unused (but populated)
|
||||||
negotiated_protocol: Option<u8>,
|
negotiated_protocol: Option<u8>,
|
||||||
|
|
||||||
// Callback on the fd as soon as the connection has been established
|
/// Callback on the fd as soon as the connection has been established
|
||||||
#[cfg(unix)]
|
#[cfg(unix)]
|
||||||
connection_fd_callback: Option<Arc<dyn Fn(RawFd) + Send + Sync>>,
|
connection_fd_callback: Option<Arc<dyn Fn(RawFd) + Send + Sync>>,
|
||||||
|
|
||||||
|
/// Maximum duration to wait for a connection to be established when set
|
||||||
|
connect_timeout: Option<Duration>,
|
||||||
|
|
||||||
/// Listen to shutdown messages and send notifications back to the task manager
|
/// Listen to shutdown messages and send notifications back to the task manager
|
||||||
shutdown_token: ShutdownToken,
|
shutdown_token: ShutdownToken,
|
||||||
}
|
}
|
||||||
@@ -124,6 +128,7 @@ impl<C, St> GatewayClient<C, St> {
|
|||||||
bandwidth_controller: Option<BandwidthController<C, St>>,
|
bandwidth_controller: Option<BandwidthController<C, St>>,
|
||||||
stats_reporter: ClientStatsSender,
|
stats_reporter: ClientStatsSender,
|
||||||
#[cfg(unix)] connection_fd_callback: Option<Arc<dyn Fn(RawFd) + Send + Sync>>,
|
#[cfg(unix)] connection_fd_callback: Option<Arc<dyn Fn(RawFd) + Send + Sync>>,
|
||||||
|
connect_timeout: Option<Duration>,
|
||||||
shutdown_token: ShutdownToken,
|
shutdown_token: ShutdownToken,
|
||||||
) -> Self {
|
) -> Self {
|
||||||
GatewayClient {
|
GatewayClient {
|
||||||
@@ -141,6 +146,7 @@ impl<C, St> GatewayClient<C, St> {
|
|||||||
negotiated_protocol: None,
|
negotiated_protocol: None,
|
||||||
#[cfg(unix)]
|
#[cfg(unix)]
|
||||||
connection_fd_callback,
|
connection_fd_callback,
|
||||||
|
connect_timeout,
|
||||||
shutdown_token,
|
shutdown_token,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -208,6 +214,7 @@ impl<C, St> GatewayClient<C, St> {
|
|||||||
&self.gateway_address,
|
&self.gateway_address,
|
||||||
#[cfg(unix)]
|
#[cfg(unix)]
|
||||||
self.connection_fd_callback.clone(),
|
self.connection_fd_callback.clone(),
|
||||||
|
self.connect_timeout,
|
||||||
)
|
)
|
||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
@@ -1132,6 +1139,7 @@ impl GatewayClient<InitOnly, EphemeralCredentialStorage> {
|
|||||||
gateway_identity: ed25519::PublicKey,
|
gateway_identity: ed25519::PublicKey,
|
||||||
local_identity: Arc<ed25519::KeyPair>,
|
local_identity: Arc<ed25519::KeyPair>,
|
||||||
#[cfg(unix)] connection_fd_callback: Option<Arc<dyn Fn(RawFd) + Send + Sync>>,
|
#[cfg(unix)] connection_fd_callback: Option<Arc<dyn Fn(RawFd) + Send + Sync>>,
|
||||||
|
connect_timeout: Option<Duration>,
|
||||||
) -> Self {
|
) -> Self {
|
||||||
log::trace!("Initialising gateway client");
|
log::trace!("Initialising gateway client");
|
||||||
use futures::channel::mpsc;
|
use futures::channel::mpsc;
|
||||||
@@ -1158,6 +1166,7 @@ impl GatewayClient<InitOnly, EphemeralCredentialStorage> {
|
|||||||
negotiated_protocol: None,
|
negotiated_protocol: None,
|
||||||
#[cfg(unix)]
|
#[cfg(unix)]
|
||||||
connection_fd_callback,
|
connection_fd_callback,
|
||||||
|
connect_timeout,
|
||||||
shutdown_token,
|
shutdown_token,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1190,6 +1199,7 @@ impl GatewayClient<InitOnly, EphemeralCredentialStorage> {
|
|||||||
negotiated_protocol: self.negotiated_protocol,
|
negotiated_protocol: self.negotiated_protocol,
|
||||||
#[cfg(unix)]
|
#[cfg(unix)]
|
||||||
connection_fd_callback: self.connection_fd_callback,
|
connection_fd_callback: self.connection_fd_callback,
|
||||||
|
connect_timeout: self.connect_timeout,
|
||||||
shutdown_token,
|
shutdown_token,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
use crate::error::GatewayClientError;
|
use crate::error::GatewayClientError;
|
||||||
|
|
||||||
use nym_http_api_client::HickoryDnsResolver;
|
use nym_http_api_client::HickoryDnsResolver;
|
||||||
|
use std::time::Duration;
|
||||||
#[cfg(unix)]
|
#[cfg(unix)]
|
||||||
use std::{
|
use std::{
|
||||||
os::fd::{AsRawFd, RawFd},
|
os::fd::{AsRawFd, RawFd},
|
||||||
@@ -17,6 +18,7 @@ use std::net::SocketAddr;
|
|||||||
pub(crate) async fn connect_async(
|
pub(crate) async fn connect_async(
|
||||||
endpoint: &str,
|
endpoint: &str,
|
||||||
#[cfg(unix)] connection_fd_callback: Option<Arc<dyn Fn(RawFd) + Send + Sync>>,
|
#[cfg(unix)] connection_fd_callback: Option<Arc<dyn Fn(RawFd) + Send + Sync>>,
|
||||||
|
connect_timeout: Option<Duration>,
|
||||||
) -> Result<(WebSocketStream<MaybeTlsStream<TcpStream>>, Response), GatewayClientError> {
|
) -> Result<(WebSocketStream<MaybeTlsStream<TcpStream>>, Response), GatewayClientError> {
|
||||||
use tokio::net::TcpSocket;
|
use tokio::net::TcpSocket;
|
||||||
|
|
||||||
@@ -64,7 +66,22 @@ pub(crate) async fn connect_async(
|
|||||||
callback.as_ref()(socket.as_raw_fd());
|
callback.as_ref()(socket.as_raw_fd());
|
||||||
}
|
}
|
||||||
|
|
||||||
match socket.connect(sock_addr).await {
|
let connect_res = if let Some(connect_timeout) = connect_timeout {
|
||||||
|
match tokio::time::timeout(connect_timeout, socket.connect(sock_addr)).await {
|
||||||
|
Ok(res) => res,
|
||||||
|
Err(_elapsed) => {
|
||||||
|
stream = Err(GatewayClientError::NetworkConnectionTimeout {
|
||||||
|
address: endpoint.to_owned(),
|
||||||
|
timeout: connect_timeout,
|
||||||
|
});
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
socket.connect(sock_addr).await
|
||||||
|
};
|
||||||
|
|
||||||
|
match connect_res {
|
||||||
Ok(s) => {
|
Ok(s) => {
|
||||||
stream = Ok(s);
|
stream = Ok(s);
|
||||||
break;
|
break;
|
||||||
|
|||||||
@@ -4,6 +4,7 @@
|
|||||||
use nym_gateway_requests::registration::handshake::error::HandshakeError;
|
use nym_gateway_requests::registration::handshake::error::HandshakeError;
|
||||||
use nym_gateway_requests::{GatewayRequestsError, SimpleGatewayRequestsError};
|
use nym_gateway_requests::{GatewayRequestsError, SimpleGatewayRequestsError};
|
||||||
use std::io;
|
use std::io;
|
||||||
|
use std::time::Duration;
|
||||||
use thiserror::Error;
|
use thiserror::Error;
|
||||||
use tungstenite::Error as WsError;
|
use tungstenite::Error as WsError;
|
||||||
|
|
||||||
@@ -46,6 +47,9 @@ pub enum GatewayClientError {
|
|||||||
source: Box<WsError>,
|
source: Box<WsError>,
|
||||||
},
|
},
|
||||||
|
|
||||||
|
#[error("timeout when establishing connection: {address}, timeout: {timeout:?}")]
|
||||||
|
NetworkConnectionTimeout { address: String, timeout: Duration },
|
||||||
|
|
||||||
#[error("no socket address for endpoint: {address}")]
|
#[error("no socket address for endpoint: {address}")]
|
||||||
NoEndpointForConnection { address: String },
|
NoEndpointForConnection { address: String },
|
||||||
|
|
||||||
|
|||||||
@@ -296,6 +296,9 @@ impl std::error::Error for ReqwestErrorWrapper {}
|
|||||||
#[derive(Debug, Error)]
|
#[derive(Debug, Error)]
|
||||||
#[allow(missing_docs)]
|
#[allow(missing_docs)]
|
||||||
pub enum HttpClientError {
|
pub enum HttpClientError {
|
||||||
|
#[error("did not provide any valid client URLs")]
|
||||||
|
NoUrlsProvided,
|
||||||
|
|
||||||
#[error("failed to construct inner reqwest client: {source}")]
|
#[error("failed to construct inner reqwest client: {source}")]
|
||||||
ReqwestBuildError {
|
ReqwestBuildError {
|
||||||
#[source]
|
#[source]
|
||||||
@@ -582,24 +585,29 @@ impl ClientBuilder {
|
|||||||
Self::new(alt)
|
Self::new(alt)
|
||||||
} else {
|
} else {
|
||||||
let url = url.to_url()?;
|
let url = url.to_url()?;
|
||||||
Ok(Self::new_with_urls(vec![url]))
|
Self::new_with_urls(vec![url])
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Create a client builder from network details with sensible defaults
|
/// Create a client builder from network details with sensible defaults
|
||||||
#[cfg(feature = "network-defaults")]
|
#[cfg(feature = "network-defaults")]
|
||||||
|
// deprecating function since it's not clear from its signature whether the client
|
||||||
|
// would be constructed using `nym_api_urls` or `nym_vpn_api_urls`
|
||||||
|
#[deprecated(note = "use explicit Self::new_with_fronted_urls instead")]
|
||||||
pub fn from_network(
|
pub fn from_network(
|
||||||
network: &nym_network_defaults::NymNetworkDetails,
|
network: &nym_network_defaults::NymNetworkDetails,
|
||||||
) -> Result<Self, HttpClientError> {
|
) -> Result<Self, HttpClientError> {
|
||||||
let urls = network
|
let urls = network.nym_api_urls.as_ref().cloned().unwrap_or_default();
|
||||||
.nym_api_urls
|
Self::new_with_fronted_urls(urls.clone())
|
||||||
.as_ref()
|
}
|
||||||
.ok_or_else(|| {
|
|
||||||
HttpClientError::GenericRequestFailure(
|
/// Create a client builder using the provided set of domain-fronted URLs
|
||||||
"No API URLs configured in network details".to_string(),
|
#[cfg(feature = "network-defaults")]
|
||||||
)
|
pub fn new_with_fronted_urls(
|
||||||
})?
|
urls: Vec<nym_network_defaults::ApiUrl>,
|
||||||
.iter()
|
) -> Result<Self, HttpClientError> {
|
||||||
|
let urls = urls
|
||||||
|
.into_iter()
|
||||||
.map(|api_url| {
|
.map(|api_url| {
|
||||||
// Convert ApiUrl to our Url type with fronting support
|
// Convert ApiUrl to our Url type with fronting support
|
||||||
let mut url = Url::parse(&api_url.url)?;
|
let mut url = Url::parse(&api_url.url)?;
|
||||||
@@ -611,15 +619,19 @@ impl ClientBuilder {
|
|||||||
.iter()
|
.iter()
|
||||||
.map(|host| format!("https://{}", host))
|
.map(|host| format!("https://{}", host))
|
||||||
.collect();
|
.collect();
|
||||||
url = Url::new(api_url.url.clone(), Some(fronts))
|
url = Url::new(api_url.url.clone(), Some(fronts)).map_err(|source| {
|
||||||
.map_err(|e| HttpClientError::GenericRequestFailure(e.to_string()))?;
|
HttpClientError::MalformedUrl {
|
||||||
|
raw: api_url.url.clone(),
|
||||||
|
source,
|
||||||
|
}
|
||||||
|
})?;
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(url)
|
Ok(url)
|
||||||
})
|
})
|
||||||
.collect::<Result<Vec<_>, HttpClientError>>()?;
|
.collect::<Result<Vec<_>, HttpClientError>>()?;
|
||||||
|
|
||||||
let mut builder = Self::new_with_urls(urls);
|
let mut builder = Self::new_with_urls(urls)?;
|
||||||
|
|
||||||
// Enable domain fronting by default (on retry)
|
// Enable domain fronting by default (on retry)
|
||||||
#[cfg(feature = "tunneling")]
|
#[cfg(feature = "tunneling")]
|
||||||
@@ -631,7 +643,11 @@ impl ClientBuilder {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Constructs a new http `ClientBuilder` from a valid url.
|
/// Constructs a new http `ClientBuilder` from a valid url.
|
||||||
pub fn new_with_urls(urls: Vec<Url>) -> Self {
|
pub fn new_with_urls(urls: Vec<Url>) -> Result<Self, HttpClientError> {
|
||||||
|
if urls.is_empty() {
|
||||||
|
return Err(HttpClientError::NoUrlsProvided);
|
||||||
|
}
|
||||||
|
|
||||||
let urls = Self::check_urls(urls);
|
let urls = Self::check_urls(urls);
|
||||||
|
|
||||||
#[cfg(target_arch = "wasm32")]
|
#[cfg(target_arch = "wasm32")]
|
||||||
@@ -640,7 +656,7 @@ impl ClientBuilder {
|
|||||||
#[cfg(not(target_arch = "wasm32"))]
|
#[cfg(not(target_arch = "wasm32"))]
|
||||||
let reqwest_client_builder = default_builder();
|
let reqwest_client_builder = default_builder();
|
||||||
|
|
||||||
ClientBuilder {
|
Ok(ClientBuilder {
|
||||||
urls,
|
urls,
|
||||||
timeout: None,
|
timeout: None,
|
||||||
custom_user_agent: false,
|
custom_user_agent: false,
|
||||||
@@ -651,7 +667,7 @@ impl ClientBuilder {
|
|||||||
|
|
||||||
retry_limit: 0,
|
retry_limit: 0,
|
||||||
serialization: SerializationFormat::Json,
|
serialization: SerializationFormat::Json,
|
||||||
}
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Add an additional URL to the set usable by this constructed `Client`
|
/// Add an additional URL to the set usable by this constructed `Client`
|
||||||
@@ -948,13 +964,13 @@ impl Client {
|
|||||||
|
|
||||||
return (url.as_str(), url.front_str());
|
return (url.as_str(), url.front_str());
|
||||||
} else {
|
} else {
|
||||||
warn!(
|
tracing::debug!(
|
||||||
"Domain fronting is enabled, but no host_url is defined! Domain fronting WILL NOT WORK"
|
"Domain fronting is enabled, but no host_url is defined for current URL"
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
warn!(
|
tracing::debug!(
|
||||||
"Domain fronting is enabled, but no front_url is defined! Domain fronting WILL NOT WORK"
|
"Domain fronting is enabled, but current URL has no front_hosts configured"
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -21,6 +21,10 @@ inventory::collect!(ConfigRecord);
|
|||||||
/// Returns the default builder with all registered configurations applied.
|
/// Returns the default builder with all registered configurations applied.
|
||||||
pub fn default_builder() -> ReqwestClientBuilder {
|
pub fn default_builder() -> ReqwestClientBuilder {
|
||||||
let mut b = ReqwestClientBuilder::new();
|
let mut b = ReqwestClientBuilder::new();
|
||||||
|
|
||||||
|
#[cfg(feature = "debug-inventory")]
|
||||||
|
let mut test_client = ReqwestClientBuilder::new();
|
||||||
|
|
||||||
let mut records: Vec<&'static ConfigRecord> =
|
let mut records: Vec<&'static ConfigRecord> =
|
||||||
inventory::iter::<ConfigRecord>.into_iter().collect();
|
inventory::iter::<ConfigRecord>.into_iter().collect();
|
||||||
records.sort_by_key(|r| r.priority); // lower runs first
|
records.sort_by_key(|r| r.priority); // lower runs first
|
||||||
@@ -35,6 +39,10 @@ pub fn default_builder() -> ReqwestClientBuilder {
|
|||||||
|
|
||||||
for r in records {
|
for r in records {
|
||||||
b = (r.apply)(b);
|
b = (r.apply)(b);
|
||||||
|
#[cfg(feature = "debug-inventory")]
|
||||||
|
{
|
||||||
|
test_client = (r.apply)(test_client);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(feature = "debug-inventory")]
|
#[cfg(feature = "debug-inventory")]
|
||||||
@@ -47,7 +55,7 @@ pub fn default_builder() -> ReqwestClientBuilder {
|
|||||||
eprintln!("[HTTP-INVENTORY] Building test client to verify configuration...");
|
eprintln!("[HTTP-INVENTORY] Building test client to verify configuration...");
|
||||||
|
|
||||||
// Try to build a client to see if it works
|
// Try to build a client to see if it works
|
||||||
match b.try_clone().unwrap().build() {
|
match test_client.build() {
|
||||||
Ok(client) => {
|
Ok(client) => {
|
||||||
eprintln!("[HTTP-INVENTORY] ✓ Client built successfully");
|
eprintln!("[HTTP-INVENTORY] ✓ Client built successfully");
|
||||||
eprintln!("[HTTP-INVENTORY] Client debug info: {:#?}", client);
|
eprintln!("[HTTP-INVENTORY] Client debug info: {:#?}", client);
|
||||||
|
|||||||
@@ -2,77 +2,77 @@ use super::*;
|
|||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn sanitizing_urls() {
|
fn sanitizing_urls() {
|
||||||
let base_url: Url = "http://foomp.com".parse().unwrap();
|
let base_url: Url = "http://api.test".parse().unwrap();
|
||||||
|
|
||||||
// works with a full string
|
// works with a full string
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
"http://foomp.com/foo/bar",
|
"http://api.test/foo/bar",
|
||||||
sanitize_url(&base_url, "/foo//bar/", NO_PARAMS).as_str()
|
sanitize_url(&base_url, "/foo//bar/", NO_PARAMS).as_str()
|
||||||
);
|
);
|
||||||
|
|
||||||
// (and leading slash doesn't matter)
|
// (and leading slash doesn't matter)
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
"http://foomp.com/foo/bar",
|
"http://api.test/foo/bar",
|
||||||
sanitize_url(&base_url, "foo//bar/", NO_PARAMS).as_str()
|
sanitize_url(&base_url, "foo//bar/", NO_PARAMS).as_str()
|
||||||
);
|
);
|
||||||
|
|
||||||
// works with 1 segment
|
// works with 1 segment
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
"http://foomp.com/foo",
|
"http://api.test/foo",
|
||||||
sanitize_url(&base_url, &["foo"], NO_PARAMS).as_str()
|
sanitize_url(&base_url, &["foo"], NO_PARAMS).as_str()
|
||||||
);
|
);
|
||||||
|
|
||||||
// works with 2 segments
|
// works with 2 segments
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
"http://foomp.com/foo/bar",
|
"http://api.test/foo/bar",
|
||||||
sanitize_url(&base_url, &["foo", "bar"], NO_PARAMS).as_str()
|
sanitize_url(&base_url, &["foo", "bar"], NO_PARAMS).as_str()
|
||||||
);
|
);
|
||||||
|
|
||||||
// works with leading slash
|
// works with leading slash
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
"http://foomp.com/foo",
|
"http://api.test/foo",
|
||||||
sanitize_url(&base_url, &["/foo"], NO_PARAMS).as_str()
|
sanitize_url(&base_url, &["/foo"], NO_PARAMS).as_str()
|
||||||
);
|
);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
"http://foomp.com/foo/bar",
|
"http://api.test/foo/bar",
|
||||||
sanitize_url(&base_url, &["/foo", "bar"], NO_PARAMS).as_str()
|
sanitize_url(&base_url, &["/foo", "bar"], NO_PARAMS).as_str()
|
||||||
);
|
);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
"http://foomp.com/foo/bar",
|
"http://api.test/foo/bar",
|
||||||
sanitize_url(&base_url, &["foo", "/bar"], NO_PARAMS).as_str()
|
sanitize_url(&base_url, &["foo", "/bar"], NO_PARAMS).as_str()
|
||||||
);
|
);
|
||||||
|
|
||||||
// works with trailing slash
|
// works with trailing slash
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
"http://foomp.com/foo",
|
"http://api.test/foo",
|
||||||
sanitize_url(&base_url, &["foo/"], NO_PARAMS).as_str()
|
sanitize_url(&base_url, &["foo/"], NO_PARAMS).as_str()
|
||||||
);
|
);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
"http://foomp.com/foo/bar",
|
"http://api.test/foo/bar",
|
||||||
sanitize_url(&base_url, &["foo/", "bar"], NO_PARAMS).as_str()
|
sanitize_url(&base_url, &["foo/", "bar"], NO_PARAMS).as_str()
|
||||||
);
|
);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
"http://foomp.com/foo/bar",
|
"http://api.test/foo/bar",
|
||||||
sanitize_url(&base_url, &["foo", "bar/"], NO_PARAMS).as_str()
|
sanitize_url(&base_url, &["foo", "bar/"], NO_PARAMS).as_str()
|
||||||
);
|
);
|
||||||
|
|
||||||
// works with both leading and trailing slash
|
// works with both leading and trailing slash
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
"http://foomp.com/foo",
|
"http://api.test/foo",
|
||||||
sanitize_url(&base_url, &["/foo/"], NO_PARAMS).as_str()
|
sanitize_url(&base_url, &["/foo/"], NO_PARAMS).as_str()
|
||||||
);
|
);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
"http://foomp.com/foo/bar",
|
"http://api.test/foo/bar",
|
||||||
sanitize_url(&base_url, &["/foo/", "/bar/"], NO_PARAMS).as_str()
|
sanitize_url(&base_url, &["/foo/", "/bar/"], NO_PARAMS).as_str()
|
||||||
);
|
);
|
||||||
|
|
||||||
// adds params
|
// adds params
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
"http://foomp.com/foo/bar?foomp=baz",
|
"http://api.test/foo/bar?foomp=baz",
|
||||||
sanitize_url(&base_url, &["foo", "bar"], &[("foomp", "baz")]).as_str()
|
sanitize_url(&base_url, &["foo", "bar"], &[("foomp", "baz")]).as_str()
|
||||||
);
|
);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
"http://foomp.com/foo/bar?arg1=val1&arg2=val2",
|
"http://api.test/foo/bar?arg1=val1&arg2=val2",
|
||||||
sanitize_url(
|
sanitize_url(
|
||||||
&base_url,
|
&base_url,
|
||||||
&["/foo/", "/bar/"],
|
&["/foo/", "/bar/"],
|
||||||
@@ -91,83 +91,87 @@ fn sanitizing_urls() {
|
|||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
async fn api_client_retry() -> Result<(), Box<dyn std::error::Error>> {
|
async fn api_client_retry() -> Result<(), Box<dyn std::error::Error>> {
|
||||||
let client = ClientBuilder::new_with_urls(vec![
|
let client = ClientBuilder::new_with_urls(vec![
|
||||||
"http://broken.nym.badurl".parse()?,
|
"http://broken.nym.test".parse()?, // This will fail
|
||||||
"http://example.com/".parse()?,
|
"https://httpbin.org/status/200".parse()?, // This will succeed
|
||||||
])
|
])?
|
||||||
.with_retries(3)
|
.with_retries(3)
|
||||||
.build()?;
|
.build()?;
|
||||||
|
|
||||||
let req = client.create_get_request(&["/"], NO_PARAMS).unwrap();
|
let req = client.create_get_request(&[], NO_PARAMS).unwrap();
|
||||||
let resp = client.send(req).await?;
|
let resp = client.send(req).await?;
|
||||||
|
|
||||||
assert_eq!(resp.status(), 200);
|
// The main test is that we successfully retried and switched to the working URL
|
||||||
|
// We accept any response from the working endpoint since external services can be unreliable
|
||||||
|
assert_eq!(
|
||||||
|
client.current_url().as_str(),
|
||||||
|
"https://httpbin.org/status/200"
|
||||||
|
);
|
||||||
|
|
||||||
// check that the url was updated
|
println!("Response status: {}", resp.status());
|
||||||
assert_eq!(client.current_url().as_str(), "http://example.com/");
|
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn host_updating() {
|
fn host_updating() {
|
||||||
let url = Url::new("http://example.com", None).unwrap();
|
let url = Url::new("http://nym-api1.test", None).unwrap();
|
||||||
let mut client = ClientBuilder::new(url).unwrap().build().unwrap();
|
let mut client = ClientBuilder::new(url).unwrap().build().unwrap();
|
||||||
|
|
||||||
// check that the url is set correctly
|
// check that the url is set correctly
|
||||||
let current_url = client.current_url();
|
let current_url = client.current_url();
|
||||||
assert_eq!(current_url.as_str(), "http://example.com/");
|
assert_eq!(current_url.as_str(), "http://nym-api1.test/");
|
||||||
assert_eq!(current_url.front_str(), None);
|
assert_eq!(current_url.front_str(), None);
|
||||||
|
|
||||||
// update the url
|
// update the url
|
||||||
client.update_host();
|
client.update_host();
|
||||||
|
|
||||||
// check that the url is still the same since there is one URL
|
// check that the url is still the same since there is one URL
|
||||||
assert_eq!(client.current_url().as_str(), "http://example.com/");
|
assert_eq!(client.current_url().as_str(), "http://nym-api1.test/");
|
||||||
|
|
||||||
// =======================================
|
// =======================================
|
||||||
// we rotate through urls when available
|
// we rotate through urls when available
|
||||||
|
|
||||||
let new_urls = vec![
|
let new_urls = vec![
|
||||||
Url::new("http://example.com", None).unwrap(),
|
Url::new("http://nym-api1.test", None).unwrap(),
|
||||||
Url::new("http://example.org", None).unwrap(),
|
Url::new("http://nym-api2.test", None).unwrap(),
|
||||||
];
|
];
|
||||||
client.change_base_urls(new_urls);
|
client.change_base_urls(new_urls);
|
||||||
assert_eq!(client.current_url().as_str(), "http://example.com/");
|
assert_eq!(client.current_url().as_str(), "http://nym-api1.test/");
|
||||||
|
|
||||||
client.update_host();
|
client.update_host();
|
||||||
|
|
||||||
// check that the url got updated now that there are multiple URLs
|
// check that the url got updated now that there are multiple URLs
|
||||||
assert_eq!(client.current_url().as_str(), "http://example.org/");
|
assert_eq!(client.current_url().as_str(), "http://nym-api2.test/");
|
||||||
assert_eq!(client.current_url().front_str(), None);
|
assert_eq!(client.current_url().front_str(), None);
|
||||||
|
|
||||||
client.update_host();
|
client.update_host();
|
||||||
assert_eq!(client.current_url().as_str(), "http://example.com/");
|
assert_eq!(client.current_url().as_str(), "http://nym-api1.test/");
|
||||||
|
|
||||||
// =======================================
|
// =======================================
|
||||||
// we rotate through urls when available if fronting is disabled
|
// we rotate through urls when available if fronting is disabled
|
||||||
|
|
||||||
let new_urls = vec![
|
let new_urls = vec![
|
||||||
Url::new(
|
Url::new(
|
||||||
"http://example.com",
|
"http://nym-api1.test",
|
||||||
Some(vec!["http://front1.com", "http://front2.com"]),
|
Some(vec!["http://cdn1.test", "http://cdn2.test"]),
|
||||||
)
|
)
|
||||||
.unwrap(),
|
.unwrap(),
|
||||||
Url::new("http://example.org", None).unwrap(),
|
Url::new("http://nym-api2.test", None).unwrap(),
|
||||||
];
|
];
|
||||||
client.change_base_urls(new_urls);
|
client.change_base_urls(new_urls);
|
||||||
|
|
||||||
assert_eq!(client.current_url().as_str(), "http://example.com/");
|
assert_eq!(client.current_url().as_str(), "http://nym-api1.test/");
|
||||||
|
|
||||||
client.update_host();
|
client.update_host();
|
||||||
|
|
||||||
// check that the url got updated now that there are multiple URLs
|
// check that the url got updated now that there are multiple URLs
|
||||||
assert_eq!(client.current_url().as_str(), "http://example.org/");
|
assert_eq!(client.current_url().as_str(), "http://nym-api2.test/");
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
#[cfg(feature = "tunneling")]
|
#[cfg(feature = "tunneling")]
|
||||||
fn fronted_host_updating() {
|
fn fronted_host_updating() {
|
||||||
let url = Url::new("http://example.com", Some(vec!["http://front1.com"])).unwrap();
|
let url = Url::new("http://nym-api.test", Some(vec!["http://cdn1.test"])).unwrap();
|
||||||
let mut client = ClientBuilder::new(url)
|
let mut client = ClientBuilder::new(url)
|
||||||
.unwrap()
|
.unwrap()
|
||||||
.with_fronting(crate::fronted::FrontPolicy::Always)
|
.with_fronting(crate::fronted::FrontPolicy::Always)
|
||||||
@@ -176,46 +180,103 @@ fn fronted_host_updating() {
|
|||||||
|
|
||||||
// check that the url is set correctly
|
// check that the url is set correctly
|
||||||
let current_url = client.current_url();
|
let current_url = client.current_url();
|
||||||
assert_eq!(current_url.as_str(), "http://example.com/");
|
assert_eq!(current_url.as_str(), "http://nym-api.test/");
|
||||||
assert_eq!(current_url.front_str(), Some("front1.com"));
|
assert_eq!(current_url.front_str(), Some("cdn1.test"));
|
||||||
|
|
||||||
// update the url
|
// update the url
|
||||||
client.update_host();
|
client.update_host();
|
||||||
|
|
||||||
// check that the url is still the same since there is one URL and one front
|
// check that the url is still the same since there is one URL and one front
|
||||||
let current_url = client.current_url();
|
let current_url = client.current_url();
|
||||||
assert_eq!(current_url.as_str(), "http://example.com/");
|
assert_eq!(current_url.as_str(), "http://nym-api.test/");
|
||||||
assert_eq!(current_url.front_str(), Some("front1.com"));
|
assert_eq!(current_url.front_str(), Some("cdn1.test"));
|
||||||
|
|
||||||
// =======================================
|
// =======================================
|
||||||
// we rotate through front urls when available if fronting is enabled
|
// we rotate through front urls when available if fronting is enabled
|
||||||
|
|
||||||
let new_urls = vec![
|
let new_urls = vec![
|
||||||
Url::new(
|
Url::new(
|
||||||
"http://example.com",
|
"http://nym-api.test",
|
||||||
Some(vec!["http://front1.com", "http://front2.com"]),
|
Some(vec!["http://cdn1.test", "http://cdn2.test"]),
|
||||||
)
|
)
|
||||||
.unwrap(),
|
.unwrap(),
|
||||||
Url::new("http://example.org", None).unwrap(),
|
Url::new("http://nym-api2.test", None).unwrap(),
|
||||||
];
|
];
|
||||||
client.change_base_urls(new_urls);
|
client.change_base_urls(new_urls);
|
||||||
|
|
||||||
let current_url = client.current_url();
|
let current_url = client.current_url();
|
||||||
assert_eq!(current_url.as_str(), "http://example.com/");
|
assert_eq!(current_url.as_str(), "http://nym-api.test/");
|
||||||
assert_eq!(current_url.front_str(), Some("front1.com"));
|
assert_eq!(current_url.front_str(), Some("cdn1.test"));
|
||||||
|
|
||||||
// update the url - this should keep the same host but change the front
|
// update the url - this should keep the same host but change the front
|
||||||
client.update_host();
|
client.update_host();
|
||||||
|
|
||||||
let current_url = client.current_url();
|
let current_url = client.current_url();
|
||||||
// check that the url is still the same since there is one URL
|
// check that the url is still the same since there is one URL
|
||||||
assert_eq!(current_url.as_str(), "http://example.com/");
|
assert_eq!(current_url.as_str(), "http://nym-api.test/");
|
||||||
assert_eq!(current_url.front_str(), Some("front2.com"));
|
assert_eq!(current_url.front_str(), Some("cdn2.test"));
|
||||||
|
|
||||||
// update the url - this should wrap around to the first front as the second url is not fronted
|
// update the url - this should wrap around to the first front as the second url is not fronted
|
||||||
client.update_host();
|
client.update_host();
|
||||||
|
|
||||||
let current_url = client.current_url();
|
let current_url = client.current_url();
|
||||||
assert_eq!(current_url.as_str(), "http://example.com/");
|
assert_eq!(current_url.as_str(), "http://nym-api.test/");
|
||||||
assert_eq!(current_url.front_str(), Some("front1.com"));
|
assert_eq!(current_url.front_str(), Some("cdn1.test"));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
#[cfg(feature = "network-defaults")]
|
||||||
|
fn from_network_configures_multiple_urls_and_retries() {
|
||||||
|
use nym_network_defaults::{ApiUrl, NymNetworkDetails};
|
||||||
|
|
||||||
|
// Create network details with multiple URLs and fronting
|
||||||
|
let mut network_details = NymNetworkDetails::new_empty();
|
||||||
|
network_details.nym_api_urls = Some(vec![
|
||||||
|
ApiUrl {
|
||||||
|
url: "https://validator.nymtech.net/api/".to_string(),
|
||||||
|
front_hosts: None,
|
||||||
|
},
|
||||||
|
ApiUrl {
|
||||||
|
url: "https://nym-frontdoor.vercel.app/api/".to_string(),
|
||||||
|
front_hosts: Some(vec!["vercel.app".to_string(), "vercel.com".to_string()]),
|
||||||
|
},
|
||||||
|
ApiUrl {
|
||||||
|
url: "https://nym-frontdoor.global.ssl.fastly.net/api/".to_string(),
|
||||||
|
front_hosts: Some(vec!["yelp.global.ssl.fastly.net".to_string()]),
|
||||||
|
},
|
||||||
|
]);
|
||||||
|
|
||||||
|
// Build client from network details
|
||||||
|
let client = ClientBuilder::new_with_fronted_urls(
|
||||||
|
network_details.nym_api_urls.clone().unwrap_or_default(),
|
||||||
|
)
|
||||||
|
.expect("Failed to create client from network")
|
||||||
|
.build()
|
||||||
|
.expect("Failed to build client");
|
||||||
|
|
||||||
|
// Verify all URLs were configured
|
||||||
|
assert_eq!(
|
||||||
|
client.base_urls().len(),
|
||||||
|
3,
|
||||||
|
"Expected 3 URLs to be configured from network details"
|
||||||
|
);
|
||||||
|
|
||||||
|
// Verify the URLs have fronting configured where appropriate
|
||||||
|
assert_eq!(
|
||||||
|
client.base_urls()[0].as_str(),
|
||||||
|
"https://validator.nymtech.net/api/"
|
||||||
|
);
|
||||||
|
assert!(client.base_urls()[0].front_str().is_none());
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
client.base_urls()[1].as_str(),
|
||||||
|
"https://nym-frontdoor.vercel.app/api/"
|
||||||
|
);
|
||||||
|
assert!(client.base_urls()[1].front_str().is_some());
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
client.base_urls()[2].as_str(),
|
||||||
|
"https://nym-frontdoor.global.ssl.fastly.net/api/"
|
||||||
|
);
|
||||||
|
assert!(client.base_urls()[2].front_str().is_some());
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -124,6 +124,8 @@ impl NymNetworkDetails {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let nym_api = var(var_names::NYM_API).expect("nym api not set");
|
||||||
|
|
||||||
NymNetworkDetails::new_empty()
|
NymNetworkDetails::new_empty()
|
||||||
.with_network_name(var(var_names::NETWORK_NAME).expect("network name not set"))
|
.with_network_name(var(var_names::NETWORK_NAME).expect("network name not set"))
|
||||||
.with_bech32_account_prefix(
|
.with_bech32_account_prefix(
|
||||||
@@ -149,7 +151,7 @@ impl NymNetworkDetails {
|
|||||||
})
|
})
|
||||||
.with_additional_validator_endpoint(ValidatorDetails::new(
|
.with_additional_validator_endpoint(ValidatorDetails::new(
|
||||||
var(var_names::NYXD).expect("nyxd validator not set"),
|
var(var_names::NYXD).expect("nyxd validator not set"),
|
||||||
Some(var(var_names::NYM_API).expect("nym api not set")),
|
Some(nym_api.clone()),
|
||||||
get_optional_env(var_names::NYXD_WEBSOCKET),
|
get_optional_env(var_names::NYXD_WEBSOCKET),
|
||||||
))
|
))
|
||||||
.with_mixnet_contract(get_optional_env(var_names::MIXNET_CONTRACT_ADDRESS))
|
.with_mixnet_contract(get_optional_env(var_names::MIXNET_CONTRACT_ADDRESS))
|
||||||
@@ -159,6 +161,10 @@ impl NymNetworkDetails {
|
|||||||
.with_multisig_contract(get_optional_env(var_names::MULTISIG_CONTRACT_ADDRESS))
|
.with_multisig_contract(get_optional_env(var_names::MULTISIG_CONTRACT_ADDRESS))
|
||||||
.with_coconut_dkg_contract(get_optional_env(var_names::COCONUT_DKG_CONTRACT_ADDRESS))
|
.with_coconut_dkg_contract(get_optional_env(var_names::COCONUT_DKG_CONTRACT_ADDRESS))
|
||||||
.with_nym_vpn_api_url(get_optional_env(var_names::NYM_VPN_API))
|
.with_nym_vpn_api_url(get_optional_env(var_names::NYM_VPN_API))
|
||||||
|
.with_nym_api_urls(Some(vec![ApiUrl {
|
||||||
|
url: nym_api,
|
||||||
|
front_hosts: None,
|
||||||
|
}]))
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn new_mainnet() -> Self {
|
pub fn new_mainnet() -> Self {
|
||||||
@@ -348,6 +354,12 @@ impl NymNetworkDetails {
|
|||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[must_use]
|
||||||
|
pub fn with_nym_api_urls(mut self, urls: Option<Vec<ApiUrl>>) -> Self {
|
||||||
|
self.nym_api_urls = urls;
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
pub fn nym_vpn_api_url(&self) -> Option<Url> {
|
pub fn nym_vpn_api_url(&self) -> Option<Url> {
|
||||||
self.nym_vpn_api_url.as_ref().map(|url| {
|
self.nym_vpn_api_url.as_ref().map(|url| {
|
||||||
url.parse()
|
url.parse()
|
||||||
|
|||||||
@@ -23,6 +23,7 @@ use nym_topology::{EpochRewardedSet, NymTopology, RoutingNode};
|
|||||||
use nym_validator_client::client::IdentityKey;
|
use nym_validator_client::client::IdentityKey;
|
||||||
use nym_validator_client::{nym_api::NymApiClientExt, UserAgent};
|
use nym_validator_client::{nym_api::NymApiClientExt, UserAgent};
|
||||||
use rand::thread_rng;
|
use rand::thread_rng;
|
||||||
|
use std::time::Duration;
|
||||||
use url::Url;
|
use url::Url;
|
||||||
use wasm_bindgen::prelude::wasm_bindgen;
|
use wasm_bindgen::prelude::wasm_bindgen;
|
||||||
use wasm_bindgen_futures::future_to_promise;
|
use wasm_bindgen_futures::future_to_promise;
|
||||||
@@ -127,6 +128,7 @@ pub async fn setup_gateway_wasm(
|
|||||||
force_tls: bool,
|
force_tls: bool,
|
||||||
chosen_gateway: Option<IdentityKey>,
|
chosen_gateway: Option<IdentityKey>,
|
||||||
gateways: Vec<RoutingNode>,
|
gateways: Vec<RoutingNode>,
|
||||||
|
connect_timeout: Option<Duration>,
|
||||||
) -> Result<InitialisationResult, WasmCoreError> {
|
) -> Result<InitialisationResult, WasmCoreError> {
|
||||||
// TODO: so much optimization and extra features could be added here, but that's for the future
|
// TODO: so much optimization and extra features could be added here, but that's for the future
|
||||||
|
|
||||||
@@ -144,6 +146,7 @@ pub async fn setup_gateway_wasm(
|
|||||||
GatewaySetup::New {
|
GatewaySetup::New {
|
||||||
specification: selection_spec,
|
specification: selection_spec,
|
||||||
available_gateways: gateways,
|
available_gateways: gateways,
|
||||||
|
connect_timeout,
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -159,6 +162,7 @@ pub async fn setup_gateway_from_api(
|
|||||||
nym_apis: &[Url],
|
nym_apis: &[Url],
|
||||||
minimum_performance: u8,
|
minimum_performance: u8,
|
||||||
ignore_epoch_roles: bool,
|
ignore_epoch_roles: bool,
|
||||||
|
connect_timeout: Option<Duration>,
|
||||||
) -> Result<InitialisationResult, WasmCoreError> {
|
) -> Result<InitialisationResult, WasmCoreError> {
|
||||||
let gateways = gateways_for_init(
|
let gateways = gateways_for_init(
|
||||||
nym_apis,
|
nym_apis,
|
||||||
@@ -168,7 +172,14 @@ pub async fn setup_gateway_from_api(
|
|||||||
None,
|
None,
|
||||||
)
|
)
|
||||||
.await?;
|
.await?;
|
||||||
setup_gateway_wasm(client_store, force_tls, chosen_gateway, gateways).await
|
setup_gateway_wasm(
|
||||||
|
client_store,
|
||||||
|
force_tls,
|
||||||
|
chosen_gateway,
|
||||||
|
gateways,
|
||||||
|
connect_timeout,
|
||||||
|
)
|
||||||
|
.await
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn current_gateways_wasm(
|
pub async fn current_gateways_wasm(
|
||||||
@@ -192,9 +203,17 @@ pub async fn setup_from_topology(
|
|||||||
force_tls: bool,
|
force_tls: bool,
|
||||||
topology: &NymTopology,
|
topology: &NymTopology,
|
||||||
client_store: &ClientStorage,
|
client_store: &ClientStorage,
|
||||||
|
connect_timeout: Option<Duration>,
|
||||||
) -> Result<InitialisationResult, WasmCoreError> {
|
) -> Result<InitialisationResult, WasmCoreError> {
|
||||||
let gateways = topology.entry_capable_nodes().cloned().collect::<Vec<_>>();
|
let gateways = topology.entry_capable_nodes().cloned().collect::<Vec<_>>();
|
||||||
setup_gateway_wasm(client_store, force_tls, explicit_gateway, gateways).await
|
setup_gateway_wasm(
|
||||||
|
client_store,
|
||||||
|
force_tls,
|
||||||
|
explicit_gateway,
|
||||||
|
gateways,
|
||||||
|
connect_timeout,
|
||||||
|
)
|
||||||
|
.await
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn generate_new_client_keys(store: &ClientStorage) -> Result<(), WasmCoreError> {
|
pub async fn generate_new_client_keys(store: &ClientStorage) -> Result<(), WasmCoreError> {
|
||||||
@@ -213,6 +232,7 @@ pub async fn add_gateway(
|
|||||||
min_performance: u8,
|
min_performance: u8,
|
||||||
ignore_epoch_roles: bool,
|
ignore_epoch_roles: bool,
|
||||||
storage: &ClientStorage,
|
storage: &ClientStorage,
|
||||||
|
connect_timeout: Option<Duration>,
|
||||||
) -> Result<(), WasmCoreError> {
|
) -> Result<(), WasmCoreError> {
|
||||||
let selection_spec = GatewaySelectionSpecification::new(
|
let selection_spec = GatewaySelectionSpecification::new(
|
||||||
preferred_gateway.clone(),
|
preferred_gateway.clone(),
|
||||||
@@ -267,6 +287,7 @@ pub async fn add_gateway(
|
|||||||
let gateway_setup = GatewaySetup::New {
|
let gateway_setup = GatewaySetup::New {
|
||||||
specification: selection_spec,
|
specification: selection_spec,
|
||||||
available_gateways,
|
available_gateways,
|
||||||
|
connect_timeout,
|
||||||
};
|
};
|
||||||
|
|
||||||
let init_details = setup_gateway(gateway_setup, storage, storage).await?;
|
let init_details = setup_gateway(gateway_setup, storage, storage).await?;
|
||||||
|
|||||||
@@ -110,7 +110,7 @@ pub fn try_transfer_ownership(
|
|||||||
DEALERS_INDICES.save(deps.storage, &transfer_to, ¤t_index)?;
|
DEALERS_INDICES.save(deps.storage, &transfer_to, ¤t_index)?;
|
||||||
DEALERS_INDICES.remove(deps.storage, &info.sender);
|
DEALERS_INDICES.remove(deps.storage, &info.sender);
|
||||||
|
|
||||||
// update registration detail for every epoch the current dealer has participated in the protocol
|
// update registration detail and share information for every epoch the current dealer has participated in the protocol
|
||||||
// ideally, we'd have only updated the current epoch, but the way the contract is constructed
|
// ideally, we'd have only updated the current epoch, but the way the contract is constructed
|
||||||
// forbids that otherwise we'd have introduced inconsistency
|
// forbids that otherwise we'd have introduced inconsistency
|
||||||
for epoch_id in 0..=epoch.epoch_id {
|
for epoch_id in 0..=epoch.epoch_id {
|
||||||
@@ -118,6 +118,11 @@ pub fn try_transfer_ownership(
|
|||||||
EPOCH_DEALERS_MAP.remove(deps.storage, (epoch_id, &info.sender));
|
EPOCH_DEALERS_MAP.remove(deps.storage, (epoch_id, &info.sender));
|
||||||
EPOCH_DEALERS_MAP.save(deps.storage, (epoch_id, &transfer_to), &details)?;
|
EPOCH_DEALERS_MAP.save(deps.storage, (epoch_id, &transfer_to), &details)?;
|
||||||
}
|
}
|
||||||
|
if let Some(mut vk_share) = vk_shares().may_load(deps.storage, (&info.sender, epoch_id))? {
|
||||||
|
vk_shares().remove(deps.storage, (&info.sender, epoch_id))?;
|
||||||
|
vk_share.owner = transfer_to.clone();
|
||||||
|
vk_shares().save(deps.storage, (&transfer_to, epoch_id), &vk_share)?;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let Some(transaction_info) = env.transaction else {
|
let Some(transaction_info) = env.transaction else {
|
||||||
@@ -262,6 +267,7 @@ mod tests_with_mock {
|
|||||||
contract.run_initial_dummy_dkg();
|
contract.run_initial_dummy_dkg();
|
||||||
let old_index = DEALERS_INDICES.load(&contract, &group_member)?;
|
let old_index = DEALERS_INDICES.load(&contract, &group_member)?;
|
||||||
let old_details = EPOCH_DEALERS_MAP.load(&contract, (0, &group_member))?;
|
let old_details = EPOCH_DEALERS_MAP.load(&contract, (0, &group_member))?;
|
||||||
|
let old_share = vk_shares().load(&contract, (&group_member, 0))?;
|
||||||
|
|
||||||
let not_group_member = contract.addr_make("not_group_member");
|
let not_group_member = contract.addr_make("not_group_member");
|
||||||
let (deps, env) = contract.deps_mut_env();
|
let (deps, env) = contract.deps_mut_env();
|
||||||
@@ -291,13 +297,20 @@ mod tests_with_mock {
|
|||||||
assert!(EPOCH_DEALERS_MAP
|
assert!(EPOCH_DEALERS_MAP
|
||||||
.may_load(&contract, (0, &group_member))?
|
.may_load(&contract, (0, &group_member))?
|
||||||
.is_none());
|
.is_none());
|
||||||
|
assert!(vk_shares()
|
||||||
|
.may_load(&contract, (&group_member, 0))?
|
||||||
|
.is_none());
|
||||||
|
|
||||||
let new_index = DEALERS_INDICES.load(&contract, &new_group_member)?;
|
let new_index = DEALERS_INDICES.load(&contract, &new_group_member)?;
|
||||||
let new_details = EPOCH_DEALERS_MAP.load(&contract, (0, &new_group_member))?;
|
let new_details = EPOCH_DEALERS_MAP.load(&contract, (0, &new_group_member))?;
|
||||||
|
let new_share = vk_shares().load(&contract, (&new_group_member, 0))?;
|
||||||
|
|
||||||
// the underlying info hasn't changed
|
// the underlying info hasn't changed
|
||||||
assert_eq!(old_index, new_index);
|
assert_eq!(old_index, new_index);
|
||||||
assert_eq!(old_details, new_details);
|
assert_eq!(old_details, new_details);
|
||||||
|
assert_ne!(old_share, new_share);
|
||||||
|
assert_eq!(old_share.owner, group_member);
|
||||||
|
assert_eq!(new_share.owner, new_group_member);
|
||||||
|
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
OWNERSHIP_TRANSFER_LOG.load(
|
OWNERSHIP_TRANSFER_LOG.load(
|
||||||
|
|||||||
@@ -190,6 +190,7 @@ impl PacketSender {
|
|||||||
),
|
),
|
||||||
#[cfg(unix)]
|
#[cfg(unix)]
|
||||||
None,
|
None,
|
||||||
|
None,
|
||||||
fresh_gateway_client_data.shutdown_token.clone(),
|
fresh_gateway_client_data.shutdown_token.clone(),
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|||||||
@@ -57,7 +57,7 @@ async fn run(
|
|||||||
.clone()
|
.clone()
|
||||||
.expect("rust sdk mainnet default missing api_url");
|
.expect("rust sdk mainnet default missing api_url");
|
||||||
|
|
||||||
let nym_api = nym_http_api_client::ClientBuilder::new_with_urls(vec![default_api_url.into()])
|
let nym_api = nym_http_api_client::ClientBuilder::new_with_urls(vec![default_api_url.into()])?
|
||||||
.no_hickory_dns()
|
.no_hickory_dns()
|
||||||
.with_timeout(nym_api_client_timeout)
|
.with_timeout(nym_api_client_timeout)
|
||||||
.build()?;
|
.build()?;
|
||||||
|
|||||||
@@ -98,7 +98,7 @@ impl Monitor {
|
|||||||
.expect("rust sdk mainnet default missing api_url");
|
.expect("rust sdk mainnet default missing api_url");
|
||||||
|
|
||||||
let nym_api =
|
let nym_api =
|
||||||
nym_http_api_client::ClientBuilder::new_with_urls(vec![default_api_url.into()])
|
nym_http_api_client::ClientBuilder::new_with_urls(vec![default_api_url.into()])?
|
||||||
.no_hickory_dns()
|
.no_hickory_dns()
|
||||||
.with_timeout(self.nym_api_client_timeout)
|
.with_timeout(self.nym_api_client_timeout)
|
||||||
.build()?;
|
.build()?;
|
||||||
|
|||||||
@@ -38,6 +38,7 @@ pub struct BuilderConfig {
|
|||||||
pub cancel_token: CancellationToken,
|
pub cancel_token: CancellationToken,
|
||||||
#[cfg(unix)]
|
#[cfg(unix)]
|
||||||
pub connection_fd_callback: Arc<dyn Fn(RawFd) + Send + Sync>,
|
pub connection_fd_callback: Arc<dyn Fn(RawFd) + Send + Sync>,
|
||||||
|
pub connect_timeout: Option<Duration>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Default, Debug, Eq, PartialEq)]
|
#[derive(Clone, Default, Debug, Eq, PartialEq)]
|
||||||
@@ -71,6 +72,7 @@ impl BuilderConfig {
|
|||||||
network_env: NymNetworkDetails,
|
network_env: NymNetworkDetails,
|
||||||
cancel_token: CancellationToken,
|
cancel_token: CancellationToken,
|
||||||
#[cfg(unix)] connection_fd_callback: Arc<dyn Fn(RawFd) + Send + Sync>,
|
#[cfg(unix)] connection_fd_callback: Arc<dyn Fn(RawFd) + Send + Sync>,
|
||||||
|
connect_timeout: Option<Duration>,
|
||||||
) -> Self {
|
) -> Self {
|
||||||
Self {
|
Self {
|
||||||
entry_node,
|
entry_node,
|
||||||
@@ -84,6 +86,7 @@ impl BuilderConfig {
|
|||||||
cancel_token,
|
cancel_token,
|
||||||
#[cfg(unix)]
|
#[cfg(unix)]
|
||||||
connection_fd_callback,
|
connection_fd_callback,
|
||||||
|
connect_timeout,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -294,6 +297,7 @@ pub struct BuilderConfigBuilder {
|
|||||||
cancel_token: Option<CancellationToken>,
|
cancel_token: Option<CancellationToken>,
|
||||||
#[cfg(unix)]
|
#[cfg(unix)]
|
||||||
connection_fd_callback: Option<Arc<dyn Fn(RawFd) + Send + Sync>>,
|
connection_fd_callback: Option<Arc<dyn Fn(RawFd) + Send + Sync>>,
|
||||||
|
connect_timeout: Option<Duration>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl BuilderConfigBuilder {
|
impl BuilderConfigBuilder {
|
||||||
@@ -358,6 +362,11 @@ impl BuilderConfigBuilder {
|
|||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn with_connect_timeout(mut self, connect_timeout: Duration) -> Self {
|
||||||
|
self.connect_timeout = Some(connect_timeout);
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
/// Builds the `BuilderConfig`.
|
/// Builds the `BuilderConfig`.
|
||||||
///
|
///
|
||||||
/// Returns an error if any required field is missing.
|
/// Returns an error if any required field is missing.
|
||||||
@@ -388,6 +397,7 @@ impl BuilderConfigBuilder {
|
|||||||
connection_fd_callback: self
|
connection_fd_callback: self
|
||||||
.connection_fd_callback
|
.connection_fd_callback
|
||||||
.ok_or(BuilderConfigError::MissingConnectionFdCallback)?,
|
.ok_or(BuilderConfigError::MissingConnectionFdCallback)?,
|
||||||
|
connect_timeout: self.connect_timeout,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -38,6 +38,7 @@ use std::path::Path;
|
|||||||
use std::path::PathBuf;
|
use std::path::PathBuf;
|
||||||
#[cfg(unix)]
|
#[cfg(unix)]
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
|
use std::time::Duration;
|
||||||
use url::Url;
|
use url::Url;
|
||||||
use zeroize::Zeroizing;
|
use zeroize::Zeroizing;
|
||||||
|
|
||||||
@@ -405,6 +406,9 @@ where
|
|||||||
#[cfg(unix)]
|
#[cfg(unix)]
|
||||||
connection_fd_callback: Option<Arc<dyn Fn(std::os::fd::RawFd) + Send + Sync>>,
|
connection_fd_callback: Option<Arc<dyn Fn(std::os::fd::RawFd) + Send + Sync>>,
|
||||||
|
|
||||||
|
/// Timeout for establishing a connection
|
||||||
|
connect_timeout: Option<Duration>,
|
||||||
|
|
||||||
forget_me: ForgetMe,
|
forget_me: ForgetMe,
|
||||||
|
|
||||||
remember_me: RememberMe,
|
remember_me: RememberMe,
|
||||||
@@ -466,6 +470,7 @@ where
|
|||||||
user_agent: None,
|
user_agent: None,
|
||||||
#[cfg(unix)]
|
#[cfg(unix)]
|
||||||
connection_fd_callback: None,
|
connection_fd_callback: None,
|
||||||
|
connect_timeout: None,
|
||||||
forget_me,
|
forget_me,
|
||||||
remember_me,
|
remember_me,
|
||||||
derivation_material: None,
|
derivation_material: None,
|
||||||
@@ -589,6 +594,7 @@ where
|
|||||||
available_gateways,
|
available_gateways,
|
||||||
#[cfg(unix)]
|
#[cfg(unix)]
|
||||||
connection_fd_callback: self.connection_fd_callback.clone(),
|
connection_fd_callback: self.connection_fd_callback.clone(),
|
||||||
|
connect_timeout: self.connect_timeout,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -706,6 +712,16 @@ where
|
|||||||
.config
|
.config
|
||||||
.as_base_client_config(nyxd_endpoints, nym_api_endpoints.clone());
|
.as_base_client_config(nyxd_endpoints, nym_api_endpoints.clone());
|
||||||
|
|
||||||
|
tracing::debug!(
|
||||||
|
"SDK: Passing nym_api_urls to BaseClientBuilder (has {} nym_api_urls)",
|
||||||
|
self.config
|
||||||
|
.network_details
|
||||||
|
.nym_api_urls
|
||||||
|
.as_ref()
|
||||||
|
.map(|urls| urls.len())
|
||||||
|
.unwrap_or(0)
|
||||||
|
);
|
||||||
|
|
||||||
let mut base_builder: BaseClientBuilder<_, _> =
|
let mut base_builder: BaseClientBuilder<_, _> =
|
||||||
BaseClientBuilder::new(base_config, self.storage, self.dkg_query_client)
|
BaseClientBuilder::new(base_config, self.storage, self.dkg_query_client)
|
||||||
.with_wait_for_gateway(self.wait_for_gateway)
|
.with_wait_for_gateway(self.wait_for_gateway)
|
||||||
@@ -713,6 +729,11 @@ where
|
|||||||
.with_remember_me(&self.remember_me)
|
.with_remember_me(&self.remember_me)
|
||||||
.with_derivation_material(self.derivation_material);
|
.with_derivation_material(self.derivation_material);
|
||||||
|
|
||||||
|
// Add nym_api_urls if available in network_details
|
||||||
|
if let Some(nym_api_urls) = self.config.network_details.nym_api_urls.clone() {
|
||||||
|
base_builder = base_builder.with_nym_api_urls(nym_api_urls);
|
||||||
|
}
|
||||||
|
|
||||||
if let Some(user_agent) = self.user_agent {
|
if let Some(user_agent) = self.user_agent {
|
||||||
base_builder = base_builder.with_user_agent(user_agent);
|
base_builder = base_builder.with_user_agent(user_agent);
|
||||||
}
|
}
|
||||||
@@ -743,6 +764,10 @@ where
|
|||||||
base_builder = base_builder.with_connection_fd_callback(connection_fd_callback);
|
base_builder = base_builder.with_connection_fd_callback(connection_fd_callback);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if let Some(connect_timeout) = self.connect_timeout {
|
||||||
|
base_builder = base_builder.with_connect_timeout(connect_timeout);
|
||||||
|
}
|
||||||
|
|
||||||
let started_client = base_builder.start_base().await?;
|
let started_client = base_builder.start_base().await?;
|
||||||
self.state = BuilderState::Registered {};
|
self.state = BuilderState::Registered {};
|
||||||
let nym_address = started_client.address;
|
let nym_address = started_client.address;
|
||||||
|
|||||||
@@ -226,7 +226,8 @@ mod tests {
|
|||||||
error!("{err}");
|
error!("{err}");
|
||||||
// this is not an ideal way of checking it, but if test fails due to networking failures
|
// this is not an ideal way of checking it, but if test fails due to networking failures
|
||||||
// it should be fine to progress
|
// it should be fine to progress
|
||||||
if err.to_string().contains("nym api request failed") {
|
let err_str = err.to_string();
|
||||||
|
if err_str.contains("nym api") || err_str.contains("failed to connect") {
|
||||||
return Ok(());
|
return Ok(());
|
||||||
}
|
}
|
||||||
return Err(err);
|
return Err(err);
|
||||||
@@ -291,7 +292,8 @@ mod tests {
|
|||||||
error!("{err}");
|
error!("{err}");
|
||||||
// this is not an ideal way of checking it, but if test fails due to networking failures
|
// this is not an ideal way of checking it, but if test fails due to networking failures
|
||||||
// it should be fine to progress
|
// it should be fine to progress
|
||||||
if err.to_string().contains("nym api request failed") {
|
let err_str = err.to_string();
|
||||||
|
if err_str.contains("nym api") || err_str.contains("failed to connect") {
|
||||||
return Ok(());
|
return Ok(());
|
||||||
}
|
}
|
||||||
return Err(err);
|
return Err(err);
|
||||||
|
|||||||
@@ -222,6 +222,7 @@ impl NymClientBuilder {
|
|||||||
self.config.base.debug.topology.minimum_gateway_performance,
|
self.config.base.debug.topology.minimum_gateway_performance,
|
||||||
self.config.base.debug.topology.ignore_ingress_epoch_role,
|
self.config.base.debug.topology.ignore_ingress_epoch_role,
|
||||||
&client_store,
|
&client_store,
|
||||||
|
None,
|
||||||
)
|
)
|
||||||
.await?;
|
.await?;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -158,6 +158,7 @@ impl MixFetchClientBuilder {
|
|||||||
self.config.base.debug.topology.minimum_gateway_performance,
|
self.config.base.debug.topology.minimum_gateway_performance,
|
||||||
self.config.base.debug.topology.ignore_ingress_epoch_role,
|
self.config.base.debug.topology.ignore_ingress_epoch_role,
|
||||||
&client_store,
|
&client_store,
|
||||||
|
None,
|
||||||
)
|
)
|
||||||
.await?;
|
.await?;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -153,6 +153,7 @@ impl NymNodeTesterBuilder {
|
|||||||
false,
|
false,
|
||||||
&self.base_topology,
|
&self.base_topology,
|
||||||
client_store,
|
client_store,
|
||||||
|
None,
|
||||||
)
|
)
|
||||||
.await?)
|
.await?)
|
||||||
}
|
}
|
||||||
@@ -211,6 +212,7 @@ impl NymNodeTesterBuilder {
|
|||||||
packet_router,
|
packet_router,
|
||||||
self.bandwidth_controller.take(),
|
self.bandwidth_controller.take(),
|
||||||
ClientStatsSender::new(None, stats_sender_task),
|
ClientStatsSender::new(None, stats_sender_task),
|
||||||
|
None,
|
||||||
gateway_task,
|
gateway_task,
|
||||||
)
|
)
|
||||||
};
|
};
|
||||||
|
|||||||
Reference in New Issue
Block a user