Compare commits
7 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 1381599c13 | |||
| 62967f736d | |||
| 41ff3f7824 | |||
| c9d4d62446 | |||
| e839a0d80e | |||
| cd61f930bf | |||
| edcf2b1204 |
@@ -36,7 +36,7 @@ nym-bandwidth-controller = { path = "../bandwidth-controller" }
|
||||
nym-crypto = { path = "../crypto" }
|
||||
nym-gateway-client = { path = "../client-libs/gateway-client" }
|
||||
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-sphinx = { path = "../nymsphinx" }
|
||||
nym-statistics-common = { path = "../statistics" }
|
||||
|
||||
@@ -114,9 +114,7 @@ where
|
||||
})?;
|
||||
hardcoded_topology.entry_capable_nodes().cloned().collect()
|
||||
} else {
|
||||
let mut rng = rand::thread_rng();
|
||||
crate::init::helpers::gateways_for_init(
|
||||
&mut rng,
|
||||
&core.client.nym_api_urls,
|
||||
user_agent,
|
||||
core.debug.topology.minimum_gateway_performance,
|
||||
|
||||
@@ -173,9 +173,7 @@ where
|
||||
})?;
|
||||
hardcoded_topology.entry_capable_nodes().cloned().collect()
|
||||
} else {
|
||||
let mut rng = rand::thread_rng();
|
||||
crate::init::helpers::gateways_for_init(
|
||||
&mut rng,
|
||||
&core.client.nym_api_urls,
|
||||
user_agent,
|
||||
core.debug.topology.minimum_gateway_performance,
|
||||
|
||||
@@ -46,6 +46,7 @@ use nym_gateway_client::client::config::GatewayClientConfig;
|
||||
use nym_gateway_client::{
|
||||
AcknowledgementReceiver, GatewayClient, GatewayConfig, MixnetMessageReceiver, PacketRouter,
|
||||
};
|
||||
use nym_network_defaults::NymNetworkDetails;
|
||||
use nym_sphinx::acknowledgements::AckKey;
|
||||
use nym_sphinx::addressing::clients::Recipient;
|
||||
use nym_sphinx::addressing::nodes::NodeIdentity;
|
||||
@@ -212,6 +213,9 @@ pub struct BaseClientBuilder<C, S: MixnetClientStorage> {
|
||||
client_store: S,
|
||||
dkg_query_client: Option<C>,
|
||||
|
||||
// Optional network details for domain fronting support
|
||||
network_details: Option<NymNetworkDetails>,
|
||||
|
||||
wait_for_gateway: bool,
|
||||
custom_topology_provider: Option<Box<dyn TopologyProvider + Send + Sync>>,
|
||||
custom_gateway_transceiver: Option<Box<dyn GatewayTransceiver + Send>>,
|
||||
@@ -241,6 +245,7 @@ where
|
||||
config: base_config,
|
||||
client_store,
|
||||
dkg_query_client,
|
||||
network_details: None,
|
||||
wait_for_gateway: false,
|
||||
custom_topology_provider: None,
|
||||
custom_gateway_transceiver: None,
|
||||
@@ -254,6 +259,16 @@ where
|
||||
}
|
||||
}
|
||||
|
||||
/// Provide network details for domain fronting support
|
||||
///
|
||||
/// When provided, the SDK will use `from_network()` to build the nym-api client,
|
||||
/// which automatically handles domain fronting if configured in the network details.
|
||||
#[must_use]
|
||||
pub fn with_network_details(mut self, network_details: NymNetworkDetails) -> Self {
|
||||
self.network_details = Some(network_details);
|
||||
self
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
pub fn with_derivation_material(
|
||||
mut self,
|
||||
@@ -863,9 +878,29 @@ where
|
||||
}
|
||||
|
||||
fn construct_nym_api_client(
|
||||
network_details: Option<&NymNetworkDetails>,
|
||||
config: &Config,
|
||||
user_agent: Option<UserAgent>,
|
||||
) -> Result<nym_http_api_client::Client, ClientCoreError> {
|
||||
// If network details are provided, use from_network() which handles domain fronting
|
||||
if let Some(network_details) = network_details {
|
||||
tracing::debug!(
|
||||
"Building nym-api client from network details (with domain fronting support)"
|
||||
);
|
||||
|
||||
let mut builder = nym_http_api_client::ClientBuilder::from_network(network_details)
|
||||
.map_err(ClientCoreError::from)?;
|
||||
|
||||
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();
|
||||
nym_api_urls.shuffle(&mut thread_rng());
|
||||
|
||||
@@ -961,7 +996,11 @@ 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.network_details.as_ref(),
|
||||
&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(
|
||||
|
||||
@@ -132,25 +132,25 @@ impl<'a, G: ConnectableGateway> GatewayWithLatency<'a, G> {
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn gateways_for_init<R: Rng>(
|
||||
rng: &mut R,
|
||||
pub async fn gateways_for_init(
|
||||
nym_apis: &[Url],
|
||||
user_agent: Option<UserAgent>,
|
||||
minimum_performance: u8,
|
||||
ignore_epoch_roles: bool,
|
||||
) -> Result<Vec<RoutingNode>, ClientCoreError> {
|
||||
let nym_api = nym_apis
|
||||
.choose(rng)
|
||||
.ok_or(ClientCoreError::ListOfNymApisIsEmpty)?;
|
||||
// Build client with ALL URLs for fallback support
|
||||
let nym_api_urls: Vec<nym_http_api_client::Url> = nym_apis
|
||||
.iter()
|
||||
.filter_map(|url| nym_http_api_client::Url::parse(url.as_str()).ok())
|
||||
.collect();
|
||||
|
||||
// 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::from(
|
||||
e,
|
||||
))
|
||||
})?
|
||||
.with_bincode(); // Use bincode for better performance
|
||||
if nym_api_urls.is_empty() {
|
||||
return Err(ClientCoreError::ListOfNymApisIsEmpty);
|
||||
}
|
||||
|
||||
let mut builder = nym_http_api_client::ClientBuilder::new_with_urls(nym_api_urls.clone())
|
||||
.with_retries(3)
|
||||
.with_bincode();
|
||||
|
||||
if let Some(user_agent) = user_agent {
|
||||
builder = builder.with_user_agent(user_agent);
|
||||
@@ -160,7 +160,7 @@ pub async fn gateways_for_init<R: Rng>(
|
||||
ClientCoreError::ValidatorClientError(nym_validator_client::ValidatorClientError::from(e))
|
||||
})?;
|
||||
|
||||
tracing::debug!("Fetching list of gateways from: {nym_api}");
|
||||
tracing::debug!("Fetching list of gateways from: {:?}", nym_api_urls);
|
||||
|
||||
// Use our helper to handle pagination
|
||||
let gateways = get_all_basic_entry_nodes_with_metadata(&client, true)
|
||||
@@ -172,17 +172,15 @@ pub async fn gateways_for_init<R: Rng>(
|
||||
|
||||
// filter out gateways below minimum performance and ones that could operate as a mixnode
|
||||
// (we don't want instability)
|
||||
let valid_gateways = gateways
|
||||
let valid_gateways: Vec<RoutingNode> = gateways
|
||||
.iter()
|
||||
.filter(|g| ignore_epoch_roles || !g.supported_roles.mixnode)
|
||||
.filter(|g| g.performance.round_to_integer() >= minimum_performance)
|
||||
.filter_map(|gateway| gateway.try_into().ok())
|
||||
.collect::<Vec<_>>();
|
||||
tracing::debug!("After checking validity: {}", valid_gateways.len());
|
||||
tracing::trace!("Valid gateways: {valid_gateways:#?}");
|
||||
.collect();
|
||||
|
||||
tracing::info!(
|
||||
"and {} after validity and performance filtering",
|
||||
"Found {} valid gateways after filtering",
|
||||
valid_gateways.len()
|
||||
);
|
||||
|
||||
@@ -345,13 +343,20 @@ pub(super) fn get_specified_gateway(
|
||||
must_use_tls: bool,
|
||||
) -> Result<RoutingNode, ClientCoreError> {
|
||||
tracing::debug!("Requesting specified gateway: {gateway_identity}");
|
||||
|
||||
let user_gateway = ed25519::PublicKey::from_base58_string(gateway_identity)
|
||||
.map_err(ClientCoreError::UnableToCreatePublicKeyFromGatewayId)?;
|
||||
|
||||
let gateway = gateways
|
||||
.iter()
|
||||
.find(|gateway| gateway.identity_key == user_gateway)
|
||||
.ok_or_else(|| ClientCoreError::NoGatewayWithId(gateway_identity.to_string()))?;
|
||||
.ok_or_else(|| {
|
||||
tracing::debug!(
|
||||
"Gateway {gateway_identity} not found in {} available gateways",
|
||||
gateways.len()
|
||||
);
|
||||
ClientCoreError::NoGatewayWithId(gateway_identity.to_string())
|
||||
})?;
|
||||
|
||||
let Some(entry_details) = gateway.entry.as_ref() else {
|
||||
return Err(ClientCoreError::UnsupportedEntry {
|
||||
@@ -414,3 +419,52 @@ pub(super) async fn register_with_gateway(
|
||||
authenticated_ephemeral_client: gateway_client,
|
||||
})
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use url::Url;
|
||||
|
||||
#[test]
|
||||
fn test_single_url_builds_without_retries() {
|
||||
let urls = [Url::parse("https://api.nym.com").unwrap()];
|
||||
|
||||
let nym_api_urls: Vec<nym_http_api_client::Url> = urls
|
||||
.iter()
|
||||
.filter_map(|url| nym_http_api_client::Url::parse(url.as_str()).ok())
|
||||
.collect();
|
||||
|
||||
assert_eq!(nym_api_urls.len(), 1, "Should have exactly one URL");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_multiple_urls_prepared_for_retries() {
|
||||
let urls = vec![
|
||||
Url::parse("https://api1.nym.com").unwrap(),
|
||||
Url::parse("https://api2.nym.com").unwrap(),
|
||||
Url::parse("https://api3.nym.com").unwrap(),
|
||||
];
|
||||
|
||||
let nym_api_urls: Vec<nym_http_api_client::Url> = urls
|
||||
.iter()
|
||||
.filter_map(|url| nym_http_api_client::Url::parse(url.as_str()).ok())
|
||||
.collect();
|
||||
|
||||
assert_eq!(nym_api_urls.len(), 3, "Should have all three URLs");
|
||||
assert!(
|
||||
nym_api_urls.len() > 1,
|
||||
"Multiple URLs trigger retry behavior"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_empty_url_list_is_detected() {
|
||||
let urls: Vec<Url> = vec![];
|
||||
|
||||
let nym_api_urls: Vec<nym_http_api_client::Url> = urls
|
||||
.iter()
|
||||
.filter_map(|url| nym_http_api_client::Url::parse(url.as_str()).ok())
|
||||
.collect();
|
||||
|
||||
assert!(nym_api_urls.is_empty(), "Empty list should remain empty");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -594,10 +594,9 @@ impl ClientBuilder {
|
||||
let urls = network
|
||||
.nym_api_urls
|
||||
.as_ref()
|
||||
.ok_or_else(|| {
|
||||
HttpClientError::GenericRequestFailure(
|
||||
"No API URLs configured in network details".to_string(),
|
||||
)
|
||||
.ok_or_else(|| HttpClientError::InternalResponseInconsistency {
|
||||
url: ::url::Url::parse("about:blank").unwrap(),
|
||||
details: "No API URLs configured in network details".to_string(),
|
||||
})?
|
||||
.iter()
|
||||
.map(|api_url| {
|
||||
@@ -611,8 +610,12 @@ impl ClientBuilder {
|
||||
.iter()
|
||||
.map(|host| format!("https://{}", host))
|
||||
.collect();
|
||||
url = Url::new(api_url.url.clone(), Some(fronts))
|
||||
.map_err(|e| HttpClientError::GenericRequestFailure(e.to_string()))?;
|
||||
url = Url::new(api_url.url.clone(), Some(fronts)).map_err(|e| {
|
||||
HttpClientError::MalformedUrl {
|
||||
raw: api_url.url.clone(),
|
||||
source: e,
|
||||
}
|
||||
})?;
|
||||
}
|
||||
|
||||
Ok(url)
|
||||
|
||||
@@ -160,15 +160,8 @@ pub async fn setup_gateway_from_api(
|
||||
minimum_performance: u8,
|
||||
ignore_epoch_roles: bool,
|
||||
) -> Result<InitialisationResult, WasmCoreError> {
|
||||
let mut rng = thread_rng();
|
||||
let gateways = gateways_for_init(
|
||||
&mut rng,
|
||||
nym_apis,
|
||||
None,
|
||||
minimum_performance,
|
||||
ignore_epoch_roles,
|
||||
)
|
||||
.await?;
|
||||
let gateways =
|
||||
gateways_for_init(nym_apis, None, minimum_performance, ignore_epoch_roles).await?;
|
||||
setup_gateway_wasm(client_store, force_tls, chosen_gateway, gateways).await
|
||||
}
|
||||
|
||||
@@ -178,9 +171,7 @@ pub async fn current_gateways_wasm(
|
||||
minimum_performance: u8,
|
||||
ignore_epoch_roles: bool,
|
||||
) -> Result<Vec<RoutingNode>, ClientCoreError> {
|
||||
let mut rng = thread_rng();
|
||||
gateways_for_init(
|
||||
&mut rng,
|
||||
nym_apis,
|
||||
user_agent,
|
||||
minimum_performance,
|
||||
|
||||
@@ -56,6 +56,34 @@ pub struct MixnetClientConfig {
|
||||
}
|
||||
|
||||
impl BuilderConfig {
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
pub fn new(
|
||||
entry_node: NymNodeWithKeys,
|
||||
exit_node: NymNodeWithKeys,
|
||||
data_path: Option<PathBuf>,
|
||||
mixnet_client_config: MixnetClientConfig,
|
||||
two_hops: bool,
|
||||
user_agent: UserAgent,
|
||||
custom_topology_provider: Box<dyn TopologyProvider + Send + Sync>,
|
||||
network_env: NymNetworkDetails,
|
||||
cancel_token: CancellationToken,
|
||||
#[cfg(unix)] connection_fd_callback: Arc<dyn Fn(RawFd) + Send + Sync>,
|
||||
) -> Self {
|
||||
Self {
|
||||
entry_node,
|
||||
exit_node,
|
||||
data_path,
|
||||
mixnet_client_config,
|
||||
two_hops,
|
||||
user_agent,
|
||||
custom_topology_provider,
|
||||
network_env,
|
||||
cancel_token,
|
||||
#[cfg(unix)]
|
||||
connection_fd_callback,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn mixnet_client_debug_config(&self) -> DebugConfig {
|
||||
if self.two_hops {
|
||||
two_hop_debug_config(&self.mixnet_client_config)
|
||||
@@ -116,6 +144,7 @@ impl BuilderConfig {
|
||||
.credentials_mode(true)
|
||||
.with_remember_me(remember_me)
|
||||
.custom_topology_provider(self.custom_topology_provider);
|
||||
|
||||
#[cfg(unix)]
|
||||
let builder = builder.with_connection_fd_callback(self.connection_fd_callback);
|
||||
|
||||
@@ -205,3 +234,17 @@ fn log_mixnet_client_config(debug_config: &DebugConfig) {
|
||||
fn true_to_disabled(val: bool) -> &'static str {
|
||||
if val { "disabled" } else { "enabled" }
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn test_mixnet_client_config_default_values() {
|
||||
let config = MixnetClientConfig::default();
|
||||
assert!(!config.disable_poisson_rate);
|
||||
assert!(!config.disable_background_cover_traffic);
|
||||
assert_eq!(config.min_mixnode_performance, None);
|
||||
assert_eq!(config.min_gateway_performance, None);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -555,17 +555,17 @@ where
|
||||
async fn available_gateways(&mut self) -> Result<Vec<RoutingNode>, ClientCoreError> {
|
||||
if let Some(ref mut custom_provider) = self.custom_topology_provider {
|
||||
if let Some(topology) = custom_provider.get_new_topology().await {
|
||||
return Ok(topology.entry_gateways().cloned().collect());
|
||||
// Use entry_capable_nodes() instead of entry_gateways() to include
|
||||
// all entry-capable nodes, not just actively assigned ones
|
||||
return Ok(topology.entry_capable_nodes().cloned().collect());
|
||||
}
|
||||
}
|
||||
|
||||
let nym_api_endpoints = self.get_api_endpoints();
|
||||
let topology_cfg = &self.config.debug_config.topology;
|
||||
let user_agent = self.user_agent.clone();
|
||||
let mut rng = OsRng;
|
||||
|
||||
gateways_for_init(
|
||||
&mut rng,
|
||||
&nym_api_endpoints,
|
||||
user_agent,
|
||||
topology_cfg.minimum_gateway_performance,
|
||||
@@ -707,6 +707,7 @@ where
|
||||
|
||||
let mut base_builder: BaseClientBuilder<_, _> =
|
||||
BaseClientBuilder::new(base_config, self.storage, self.dkg_query_client)
|
||||
.with_network_details(self.config.network_details.clone())
|
||||
.with_wait_for_gateway(self.wait_for_gateway)
|
||||
.with_forget_me(&self.forget_me)
|
||||
.with_remember_me(&self.remember_me)
|
||||
@@ -880,3 +881,30 @@ impl IncludedSurbs {
|
||||
Self::ExposeSelfAddress
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn test_mixnet_builder_default() {
|
||||
let builder = MixnetClientBuilder::new_ephemeral();
|
||||
assert!(
|
||||
builder.build().is_ok(),
|
||||
"Builder should succeed with default configuration"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_mixnet_builder_with_network_details() {
|
||||
use nym_network_defaults::NymNetworkDetails;
|
||||
|
||||
let builder = MixnetClientBuilder::new_ephemeral();
|
||||
let disconnected_client = builder.build();
|
||||
|
||||
assert!(
|
||||
disconnected_client.is_ok(),
|
||||
"Builder should succeed and domain fronting is handled via network_details"
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user