Compare commits
3 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| de6924114d | |||
| 75613df547 | |||
| 5ca8dfbc21 |
@@ -114,12 +114,13 @@ 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,
|
||||
core.debug.topology.ignore_ingress_epoch_role,
|
||||
None,
|
||||
)
|
||||
.await?
|
||||
};
|
||||
|
||||
@@ -173,12 +173,13 @@ 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,
|
||||
core.debug.topology.ignore_ingress_epoch_role,
|
||||
None,
|
||||
)
|
||||
.await?
|
||||
};
|
||||
|
||||
@@ -45,7 +45,6 @@ type WsConn = JSWebsocket;
|
||||
|
||||
const CONCURRENT_GATEWAYS_MEASURED: usize = 20;
|
||||
const MEASUREMENTS: usize = 3;
|
||||
const DEFAULT_NYM_API_RETRIES: usize = 3;
|
||||
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
const CONN_TIMEOUT: Duration = Duration::from_millis(1500);
|
||||
@@ -133,27 +132,25 @@ impl<'a, G: ConnectableGateway> GatewayWithLatency<'a, G> {
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn gateways_for_init(
|
||||
pub async fn gateways_for_init<R: Rng>(
|
||||
rng: &mut R,
|
||||
nym_apis: &[Url],
|
||||
user_agent: Option<UserAgent>,
|
||||
minimum_performance: u8,
|
||||
ignore_epoch_roles: bool,
|
||||
retry_count: Option<usize>,
|
||||
) -> Result<Vec<RoutingNode>, ClientCoreError> {
|
||||
// Build client with ALL URLs for fallback support
|
||||
let nym_api_urls: Vec<nym_http_api_client::Url> = nym_apis
|
||||
.iter()
|
||||
.map(|url| nym_http_api_client::Url::from(url.clone()))
|
||||
.collect();
|
||||
let nym_api = nym_apis
|
||||
.choose(rng)
|
||||
.ok_or(ClientCoreError::ListOfNymApisIsEmpty)?;
|
||||
|
||||
if nym_api_urls.is_empty() {
|
||||
return Err(ClientCoreError::ListOfNymApisIsEmpty);
|
||||
}
|
||||
|
||||
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())
|
||||
.with_retries(retry_count)
|
||||
.with_bincode();
|
||||
// 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 let Some(user_agent) = user_agent {
|
||||
builder = builder.with_user_agent(user_agent);
|
||||
@@ -163,7 +160,7 @@ pub async fn gateways_for_init(
|
||||
ClientCoreError::ValidatorClientError(nym_validator_client::ValidatorClientError::from(e))
|
||||
})?;
|
||||
|
||||
tracing::debug!("Fetching list of gateways from: {:?}", nym_api_urls);
|
||||
tracing::debug!("Fetching list of gateways from: {nym_api}");
|
||||
|
||||
// Use our helper to handle pagination
|
||||
let gateways = get_all_basic_entry_nodes_with_metadata(&client, true)
|
||||
@@ -175,15 +172,17 @@ pub async fn gateways_for_init(
|
||||
|
||||
// filter out gateways below minimum performance and ones that could operate as a mixnode
|
||||
// (we don't want instability)
|
||||
let valid_gateways: Vec<RoutingNode> = gateways
|
||||
let valid_gateways = 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();
|
||||
.collect::<Vec<_>>();
|
||||
tracing::debug!("After checking validity: {}", valid_gateways.len());
|
||||
tracing::trace!("Valid gateways: {valid_gateways:#?}");
|
||||
|
||||
tracing::info!(
|
||||
"Found {} valid gateways after filtering",
|
||||
"and {} after validity and performance filtering",
|
||||
valid_gateways.len()
|
||||
);
|
||||
|
||||
@@ -346,20 +345,13 @@ 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(|| {
|
||||
tracing::debug!(
|
||||
"Gateway {gateway_identity} not found in {} available gateways",
|
||||
gateways.len()
|
||||
);
|
||||
ClientCoreError::NoGatewayWithId(gateway_identity.to_string())
|
||||
})?;
|
||||
.ok_or_else(|| ClientCoreError::NoGatewayWithId(gateway_identity.to_string()))?;
|
||||
|
||||
let Some(entry_details) = gateway.entry.as_ref() else {
|
||||
return Err(ClientCoreError::UnsupportedEntry {
|
||||
@@ -422,52 +414,3 @@ 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()
|
||||
.map(|url| nym_http_api_client::Url::from(url.clone()))
|
||||
.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()
|
||||
.map(|url| nym_http_api_client::Url::from(url.clone()))
|
||||
.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()
|
||||
.map(|url| nym_http_api_client::Url::from(url.clone()))
|
||||
.collect();
|
||||
|
||||
assert!(nym_api_urls.is_empty(), "Empty list should remain empty");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -183,11 +183,6 @@ impl Url {
|
||||
})
|
||||
}
|
||||
|
||||
/// Returns the underlying URL
|
||||
pub fn inner_url(&self) -> &url::Url {
|
||||
&self.url
|
||||
}
|
||||
|
||||
/// Returns true if the URL has a front domain set
|
||||
pub fn has_front(&self) -> bool {
|
||||
if let Some(fronts) = &self.fronts {
|
||||
@@ -206,11 +201,6 @@ impl Url {
|
||||
.and_then(|url| url.host_str())
|
||||
}
|
||||
|
||||
/// Returns the fronts
|
||||
pub fn fronts(&self) -> Option<&[url::Url]> {
|
||||
self.fronts.as_deref()
|
||||
}
|
||||
|
||||
/// Return the string representation of the host (domain or IP address) for this URL, if any.
|
||||
pub fn host_str(&self) -> Option<&str> {
|
||||
self.url.host_str()
|
||||
|
||||
@@ -160,12 +160,13 @@ 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,
|
||||
None,
|
||||
)
|
||||
.await?;
|
||||
setup_gateway_wasm(client_store, force_tls, chosen_gateway, gateways).await
|
||||
@@ -177,12 +178,13 @@ 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,
|
||||
ignore_epoch_roles,
|
||||
None,
|
||||
)
|
||||
.await
|
||||
}
|
||||
|
||||
@@ -110,7 +110,7 @@ pub fn try_transfer_ownership(
|
||||
DEALERS_INDICES.save(deps.storage, &transfer_to, ¤t_index)?;
|
||||
DEALERS_INDICES.remove(deps.storage, &info.sender);
|
||||
|
||||
// update registration detail and share information for every epoch the current dealer has participated in the protocol
|
||||
// update registration detail 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
|
||||
// forbids that otherwise we'd have introduced inconsistency
|
||||
for epoch_id in 0..=epoch.epoch_id {
|
||||
@@ -118,11 +118,6 @@ pub fn try_transfer_ownership(
|
||||
EPOCH_DEALERS_MAP.remove(deps.storage, (epoch_id, &info.sender));
|
||||
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 {
|
||||
@@ -267,7 +262,6 @@ mod tests_with_mock {
|
||||
contract.run_initial_dummy_dkg();
|
||||
let old_index = DEALERS_INDICES.load(&contract, &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 (deps, env) = contract.deps_mut_env();
|
||||
@@ -297,20 +291,13 @@ mod tests_with_mock {
|
||||
assert!(EPOCH_DEALERS_MAP
|
||||
.may_load(&contract, (0, &group_member))?
|
||||
.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_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
|
||||
assert_eq!(old_index, new_index);
|
||||
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!(
|
||||
OWNERSHIP_TRANSFER_LOG.load(
|
||||
|
||||
@@ -56,53 +56,6 @@ pub struct MixnetClientConfig {
|
||||
}
|
||||
|
||||
impl BuilderConfig {
|
||||
/// Creates a new BuilderConfig with all required parameters.
|
||||
///
|
||||
/// However, consider using `BuilderConfig::builder()` instead.
|
||||
#[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,
|
||||
}
|
||||
}
|
||||
|
||||
/// Creates a builder for BuilderConfig
|
||||
///
|
||||
/// This is the preferred way to construct a BuilderConfig.
|
||||
///
|
||||
/// # Example
|
||||
/// ```ignore
|
||||
/// let config = BuilderConfig::builder()
|
||||
/// .entry_node(entry)
|
||||
/// .exit_node(exit)
|
||||
/// .user_agent(agent)
|
||||
/// .build()?;
|
||||
/// ```
|
||||
pub fn builder() -> BuilderConfigBuilder {
|
||||
BuilderConfigBuilder::default()
|
||||
}
|
||||
|
||||
pub fn mixnet_client_debug_config(&self) -> DebugConfig {
|
||||
if self.two_hops {
|
||||
two_hop_debug_config(&self.mixnet_client_config)
|
||||
@@ -163,7 +116,6 @@ 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);
|
||||
|
||||
@@ -253,205 +205,3 @@ fn log_mixnet_client_config(debug_config: &DebugConfig) {
|
||||
fn true_to_disabled(val: bool) -> &'static str {
|
||||
if val { "disabled" } else { "enabled" }
|
||||
}
|
||||
|
||||
/// Error type for BuilderConfig validation
|
||||
#[derive(Debug, Clone, thiserror::Error)]
|
||||
#[allow(clippy::enum_variant_names)]
|
||||
pub enum BuilderConfigError {
|
||||
#[error("entry_node is required")]
|
||||
MissingEntryNode,
|
||||
#[error("exit_node is required")]
|
||||
MissingExitNode,
|
||||
#[error("mixnet_client_config is required")]
|
||||
MissingMixnetClientConfig,
|
||||
#[error("user_agent is required")]
|
||||
MissingUserAgent,
|
||||
#[error("custom_topology_provider is required")]
|
||||
MissingTopologyProvider,
|
||||
#[error("network_env is required")]
|
||||
MissingNetworkEnv,
|
||||
#[error("cancel_token is required")]
|
||||
MissingCancelToken,
|
||||
#[cfg(unix)]
|
||||
#[error("connection_fd_callback is required")]
|
||||
MissingConnectionFdCallback,
|
||||
}
|
||||
|
||||
/// Builder for `BuilderConfig`
|
||||
///
|
||||
/// This provides a more convenient way to construct a `BuilderConfig` compared to the
|
||||
/// `new()` constructor with many arguments.
|
||||
#[derive(Default)]
|
||||
pub struct BuilderConfigBuilder {
|
||||
entry_node: Option<NymNodeWithKeys>,
|
||||
exit_node: Option<NymNodeWithKeys>,
|
||||
data_path: Option<PathBuf>,
|
||||
mixnet_client_config: Option<MixnetClientConfig>,
|
||||
two_hops: bool,
|
||||
user_agent: Option<UserAgent>,
|
||||
custom_topology_provider: Option<Box<dyn TopologyProvider + Send + Sync>>,
|
||||
network_env: Option<NymNetworkDetails>,
|
||||
cancel_token: Option<CancellationToken>,
|
||||
#[cfg(unix)]
|
||||
connection_fd_callback: Option<Arc<dyn Fn(RawFd) + Send + Sync>>,
|
||||
}
|
||||
|
||||
impl BuilderConfigBuilder {
|
||||
pub fn new() -> Self {
|
||||
Self::default()
|
||||
}
|
||||
|
||||
pub fn entry_node(mut self, entry_node: NymNodeWithKeys) -> Self {
|
||||
self.entry_node = Some(entry_node);
|
||||
self
|
||||
}
|
||||
|
||||
pub fn exit_node(mut self, exit_node: NymNodeWithKeys) -> Self {
|
||||
self.exit_node = Some(exit_node);
|
||||
self
|
||||
}
|
||||
|
||||
pub fn data_path(mut self, data_path: Option<PathBuf>) -> Self {
|
||||
self.data_path = data_path;
|
||||
self
|
||||
}
|
||||
|
||||
pub fn mixnet_client_config(mut self, mixnet_client_config: MixnetClientConfig) -> Self {
|
||||
self.mixnet_client_config = Some(mixnet_client_config);
|
||||
self
|
||||
}
|
||||
|
||||
pub fn two_hops(mut self, two_hops: bool) -> Self {
|
||||
self.two_hops = two_hops;
|
||||
self
|
||||
}
|
||||
|
||||
pub fn user_agent(mut self, user_agent: UserAgent) -> Self {
|
||||
self.user_agent = Some(user_agent);
|
||||
self
|
||||
}
|
||||
|
||||
pub fn custom_topology_provider(
|
||||
mut self,
|
||||
custom_topology_provider: Box<dyn TopologyProvider + Send + Sync>,
|
||||
) -> Self {
|
||||
self.custom_topology_provider = Some(custom_topology_provider);
|
||||
self
|
||||
}
|
||||
|
||||
pub fn network_env(mut self, network_env: NymNetworkDetails) -> Self {
|
||||
self.network_env = Some(network_env);
|
||||
self
|
||||
}
|
||||
|
||||
pub fn cancel_token(mut self, cancel_token: CancellationToken) -> Self {
|
||||
self.cancel_token = Some(cancel_token);
|
||||
self
|
||||
}
|
||||
|
||||
#[cfg(unix)]
|
||||
pub fn connection_fd_callback(
|
||||
mut self,
|
||||
connection_fd_callback: Arc<dyn Fn(RawFd) + Send + Sync>,
|
||||
) -> Self {
|
||||
self.connection_fd_callback = Some(connection_fd_callback);
|
||||
self
|
||||
}
|
||||
|
||||
/// Builds the `BuilderConfig`.
|
||||
///
|
||||
/// Returns an error if any required field is missing.
|
||||
pub fn build(self) -> Result<BuilderConfig, BuilderConfigError> {
|
||||
Ok(BuilderConfig {
|
||||
entry_node: self
|
||||
.entry_node
|
||||
.ok_or(BuilderConfigError::MissingEntryNode)?,
|
||||
exit_node: self.exit_node.ok_or(BuilderConfigError::MissingExitNode)?,
|
||||
data_path: self.data_path,
|
||||
mixnet_client_config: self
|
||||
.mixnet_client_config
|
||||
.ok_or(BuilderConfigError::MissingMixnetClientConfig)?,
|
||||
two_hops: self.two_hops,
|
||||
user_agent: self
|
||||
.user_agent
|
||||
.ok_or(BuilderConfigError::MissingUserAgent)?,
|
||||
custom_topology_provider: self
|
||||
.custom_topology_provider
|
||||
.ok_or(BuilderConfigError::MissingTopologyProvider)?,
|
||||
network_env: self
|
||||
.network_env
|
||||
.ok_or(BuilderConfigError::MissingNetworkEnv)?,
|
||||
cancel_token: self
|
||||
.cancel_token
|
||||
.ok_or(BuilderConfigError::MissingCancelToken)?,
|
||||
#[cfg(unix)]
|
||||
connection_fd_callback: self
|
||||
.connection_fd_callback
|
||||
.ok_or(BuilderConfigError::MissingConnectionFdCallback)?,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
#[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);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_builder_config_builder_fails_without_required_fields() {
|
||||
// Building without any fields should fail with specific error
|
||||
let result = BuilderConfig::builder().build();
|
||||
assert!(result.is_err());
|
||||
match result {
|
||||
Err(BuilderConfigError::MissingEntryNode) => (), // Expected
|
||||
Err(e) => panic!("Expected MissingEntryNode, got: {}", e),
|
||||
Ok(_) => panic!("Expected error, got Ok"),
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_builder_config_builder_validates_all_required_fields() {
|
||||
// Test that each required field is validated
|
||||
let result = BuilderConfig::builder().build();
|
||||
assert!(result.is_err());
|
||||
|
||||
// Short-circuits at first missing field, so we just verify it's one of the expected errors
|
||||
#[allow(unreachable_patterns)] // All variants are covered, but keeping catch-all for safety
|
||||
match result {
|
||||
Err(BuilderConfigError::MissingEntryNode)
|
||||
| Err(BuilderConfigError::MissingExitNode)
|
||||
| Err(BuilderConfigError::MissingMixnetClientConfig)
|
||||
| Err(BuilderConfigError::MissingUserAgent)
|
||||
| Err(BuilderConfigError::MissingTopologyProvider)
|
||||
| Err(BuilderConfigError::MissingNetworkEnv)
|
||||
| Err(BuilderConfigError::MissingCancelToken) => (),
|
||||
#[cfg(unix)]
|
||||
Err(BuilderConfigError::MissingConnectionFdCallback) => (),
|
||||
Err(e) => panic!("Unexpected error: {}", e),
|
||||
Ok(_) => panic!("Expected validation error, got Ok"),
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_builder_config_builder_method_chaining() {
|
||||
// Test that builder methods chain properly and return Self
|
||||
let builder = BuilderConfig::builder();
|
||||
|
||||
// Verify the builder returns itself for chaining
|
||||
let builder = builder.two_hops(true);
|
||||
let builder = builder.two_hops(false);
|
||||
let builder = builder.data_path(None);
|
||||
|
||||
// Builder should still fail because required fields are missing
|
||||
let result = builder.build();
|
||||
assert!(result.is_err());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -555,22 +555,21 @@ 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 {
|
||||
// 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());
|
||||
return Ok(topology.entry_gateways().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,
|
||||
topology_cfg.ignore_ingress_epoch_role,
|
||||
None,
|
||||
)
|
||||
.await
|
||||
}
|
||||
@@ -881,17 +880,3 @@ impl IncludedSurbs {
|
||||
Self::ExposeSelfAddress
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn test_mixnet_builder_default_no_custom_client() {
|
||||
let builder = MixnetClientBuilder::new_ephemeral();
|
||||
assert!(
|
||||
builder.build().is_ok(),
|
||||
"Builder should succeed without custom client"
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user