Compare commits
2 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 1e74de2d67 | |||
| a886715948 |
@@ -73,6 +73,7 @@ impl From<Init> for OverrideConfig {
|
||||
fn from(init_config: Init) -> Self {
|
||||
OverrideConfig {
|
||||
nym_apis: init_config.common_args.nym_apis,
|
||||
fronting_domains: init_config.common_args.fronting_domains,
|
||||
disable_socket: init_config.disable_socket,
|
||||
port: init_config.port,
|
||||
host: init_config.host,
|
||||
|
||||
@@ -97,6 +97,7 @@ pub(crate) enum Commands {
|
||||
// Configuration that can be overridden.
|
||||
pub(crate) struct OverrideConfig {
|
||||
nym_apis: Option<Vec<url::Url>>,
|
||||
fronting_domains: Option<Vec<url::Url>>,
|
||||
disable_socket: Option<bool>,
|
||||
port: Option<u16>,
|
||||
host: Option<IpAddr>,
|
||||
@@ -133,6 +134,10 @@ pub(crate) fn override_config(config: Config, args: OverrideConfig) -> Config {
|
||||
.with_base(BaseClientConfig::with_disabled_cover_traffic, args.no_cover)
|
||||
.with_optional(Config::with_port, args.port)
|
||||
.with_optional(Config::with_host, args.host)
|
||||
.with_optional_ext(
|
||||
BaseClientConfig::with_fronting_domains,
|
||||
args.fronting_domains,
|
||||
)
|
||||
.with_optional_custom_env_ext(
|
||||
BaseClientConfig::with_custom_nym_apis,
|
||||
args.nym_apis,
|
||||
|
||||
@@ -36,6 +36,7 @@ impl From<Run> for OverrideConfig {
|
||||
fn from(run_config: Run) -> Self {
|
||||
OverrideConfig {
|
||||
nym_apis: run_config.common_args.nym_apis,
|
||||
fronting_domains: run_config.common_args.fronting_domains,
|
||||
disable_socket: run_config.disable_socket,
|
||||
port: run_config.port,
|
||||
host: run_config.host,
|
||||
|
||||
@@ -117,6 +117,11 @@ impl Config {
|
||||
self
|
||||
}
|
||||
|
||||
pub fn with_fronting_domains(mut self, fronting_domains: Vec<Url>) -> Self {
|
||||
self.client.fronting_domains = Some(fronting_domains);
|
||||
self
|
||||
}
|
||||
|
||||
pub fn set_custom_nym_apis(&mut self, nym_api_urls: Vec<Url>) {
|
||||
self.client.nym_api_urls = nym_api_urls;
|
||||
}
|
||||
@@ -289,6 +294,10 @@ impl Config {
|
||||
pub fn get_nym_api_endpoints(&self) -> Vec<Url> {
|
||||
self.client.nym_api_urls.clone()
|
||||
}
|
||||
|
||||
pub fn get_fronting_domains(&self) -> Option<Vec<Url>> {
|
||||
self.client.fronting_domains.clone()
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Deserialize, PartialEq, Eq, Serialize)]
|
||||
@@ -314,6 +323,9 @@ pub struct Client {
|
||||
/// Addresses to APIs running on validator from which the client gets the view of the network.
|
||||
#[serde(alias = "validator_api_urls")]
|
||||
pub nym_api_urls: Vec<Url>,
|
||||
|
||||
/// Domain to use for domain fronting censorship circumvention
|
||||
pub fronting_domains: Option<Vec<Url>>,
|
||||
}
|
||||
|
||||
impl Client {
|
||||
@@ -340,6 +352,7 @@ impl Client {
|
||||
disabled_credentials_mode: true,
|
||||
nyxd_urls,
|
||||
nym_api_urls,
|
||||
fronting_domains: None,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -349,6 +362,7 @@ impl Client {
|
||||
disabled_credentials_mode: bool,
|
||||
nyxd_urls: Vec<Url>,
|
||||
nym_api_urls: Vec<Url>,
|
||||
fronting_domains: Option<Vec<Url>>,
|
||||
) -> Self {
|
||||
Client {
|
||||
version: version.into(),
|
||||
@@ -356,6 +370,7 @@ impl Client {
|
||||
disabled_credentials_mode,
|
||||
nyxd_urls,
|
||||
nym_api_urls,
|
||||
fronting_domains,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -95,6 +95,7 @@ impl From<ConfigV5> for Config {
|
||||
id: value.client.id,
|
||||
disabled_credentials_mode: value.client.disabled_credentials_mode,
|
||||
nyxd_urls: value.client.nyxd_urls,
|
||||
fronting_domains: None, //SW need proper migrations if it gets applied
|
||||
nym_api_urls: value.client.nym_api_urls,
|
||||
},
|
||||
debug: DebugConfig {
|
||||
|
||||
@@ -111,7 +111,7 @@ where
|
||||
hardcoded_topology.get_gateways()
|
||||
} else {
|
||||
let mut rng = rand::thread_rng();
|
||||
crate::init::helpers::current_gateways(&mut rng, &core.client.nym_api_urls).await?
|
||||
crate::init::helpers::current_gateways(&mut rng, &core.client.nym_api_urls, None).await?
|
||||
};
|
||||
|
||||
// since we're registering with a brand new gateway,
|
||||
|
||||
@@ -70,6 +70,13 @@ pub struct CommonClientInitArgs {
|
||||
)]
|
||||
pub nym_apis: Option<Vec<url::Url>>,
|
||||
|
||||
///Comma separated list of urls to use for domain fronting
|
||||
#[cfg_attr(
|
||||
feature = "cli",
|
||||
clap(long, value_delimiter = ',', requires = "nym_apis", hide = true)
|
||||
)]
|
||||
pub fronting_domains: Option<Vec<url::Url>>,
|
||||
|
||||
/// Path to .json file containing custom network specification.
|
||||
#[cfg_attr(feature = "cli", clap(long, group = "network", hide = true))]
|
||||
pub custom_mixnet: Option<PathBuf>,
|
||||
@@ -144,6 +151,16 @@ where
|
||||
.collect::<Vec<&str>>()
|
||||
.join(",")
|
||||
);
|
||||
if let Some(fronting_domains) = &core.client.fronting_domains {
|
||||
log::info!(
|
||||
"fronted by : {}",
|
||||
fronting_domains
|
||||
.iter()
|
||||
.map(|url| url.host_str().unwrap_or_default())
|
||||
.collect::<Vec<&str>>()
|
||||
.join(",")
|
||||
);
|
||||
}
|
||||
|
||||
let key_store = OnDiskKeys::new(paths.keys.clone());
|
||||
let details_store = setup_fs_gateways_storage(&paths.gateway_registrations).await?;
|
||||
@@ -163,7 +180,12 @@ where
|
||||
hardcoded_topology.get_gateways()
|
||||
} else {
|
||||
let mut rng = rand::thread_rng();
|
||||
crate::init::helpers::current_gateways(&mut rng, &core.client.nym_api_urls).await?
|
||||
crate::init::helpers::current_gateways(
|
||||
&mut rng,
|
||||
&core.client.nym_api_urls,
|
||||
core.client.fronting_domains.as_ref(),
|
||||
)
|
||||
.await?
|
||||
};
|
||||
|
||||
let gateway_setup = GatewaySetup::New {
|
||||
|
||||
@@ -35,6 +35,13 @@ pub struct CommonClientRunArgs {
|
||||
)]
|
||||
pub nym_apis: Option<Vec<url::Url>>,
|
||||
|
||||
///Comma separated list of urls to use for domain fronting
|
||||
#[cfg_attr(
|
||||
feature = "cli",
|
||||
clap(long, value_delimiter = ',', requires = "nym_apis", hide = true)
|
||||
)]
|
||||
pub fronting_domains: Option<Vec<url::Url>>,
|
||||
|
||||
/// Path to .json file containing custom network specification.
|
||||
#[cfg_attr(feature = "cli", clap(long, group = "network", hide = true))]
|
||||
pub custom_mixnet: Option<PathBuf>,
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
|
||||
use super::packet_statistics_control::PacketStatisticsReporter;
|
||||
use super::received_buffer::ReceivedBufferMessage;
|
||||
use super::topology_control::fronted_api_provider::FrontedApiTopologyProvider;
|
||||
use super::topology_control::geo_aware_provider::GeoAwareTopologyProvider;
|
||||
use crate::client::base_client::storage::helpers::store_client_keys;
|
||||
use crate::client::base_client::storage::MixnetClientStorage;
|
||||
@@ -467,17 +468,29 @@ where
|
||||
custom_provider: Option<Box<dyn TopologyProvider + Send + Sync>>,
|
||||
config_topology: config::Topology,
|
||||
nym_api_urls: Vec<Url>,
|
||||
fronting_domains: Option<Vec<Url>>,
|
||||
) -> Box<dyn TopologyProvider + Send + Sync> {
|
||||
// if no custom provider was ... provided ..., create one using nym-api
|
||||
custom_provider.unwrap_or_else(|| match config_topology.topology_structure {
|
||||
config::TopologyStructure::NymApi => Box::new(NymApiTopologyProvider::new(
|
||||
nym_api_provider::Config {
|
||||
min_mixnode_performance: config_topology.minimum_mixnode_performance,
|
||||
min_gateway_performance: config_topology.minimum_gateway_performance,
|
||||
},
|
||||
nym_api_urls,
|
||||
env!("CARGO_PKG_VERSION").to_string(),
|
||||
)),
|
||||
config::TopologyStructure::NymApi => match fronting_domains {
|
||||
Some(domains) => Box::new(FrontedApiTopologyProvider::new(
|
||||
nym_api_provider::Config {
|
||||
min_mixnode_performance: config_topology.minimum_mixnode_performance,
|
||||
min_gateway_performance: config_topology.minimum_gateway_performance,
|
||||
},
|
||||
nym_api_urls,
|
||||
domains,
|
||||
env!("CARGO_PKG_VERSION").to_string(),
|
||||
)),
|
||||
None => Box::new(NymApiTopologyProvider::new(
|
||||
nym_api_provider::Config {
|
||||
min_mixnode_performance: config_topology.minimum_mixnode_performance,
|
||||
min_gateway_performance: config_topology.minimum_gateway_performance,
|
||||
},
|
||||
nym_api_urls,
|
||||
env!("CARGO_PKG_VERSION").to_string(),
|
||||
)),
|
||||
},
|
||||
config::TopologyStructure::GeoAware(group_by) => {
|
||||
Box::new(GeoAwareTopologyProvider::new(
|
||||
nym_api_urls,
|
||||
@@ -689,6 +702,7 @@ where
|
||||
self.custom_topology_provider.take(),
|
||||
self.config.debug.topology,
|
||||
self.config.get_nym_api_endpoints(),
|
||||
self.config.get_fronting_domains(),
|
||||
);
|
||||
|
||||
// needs to be started as the first thing to block if required waiting for the gateway
|
||||
|
||||
@@ -0,0 +1,147 @@
|
||||
// Copyright 2023 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use async_trait::async_trait;
|
||||
use log::{debug, error, warn};
|
||||
use nym_topology::provider_trait::TopologyProvider;
|
||||
use nym_topology::{NymTopology, NymTopologyError};
|
||||
use rand::prelude::SliceRandom;
|
||||
use rand::thread_rng;
|
||||
use url::Url;
|
||||
|
||||
use super::nym_api_provider::Config;
|
||||
|
||||
pub(crate) struct FrontedApiTopologyProvider {
|
||||
config: Config,
|
||||
|
||||
validator_client: nym_validator_client::client::NymApiClient,
|
||||
nym_api_urls: Vec<Url>,
|
||||
fronting_domains: Vec<Url>,
|
||||
shuffling: Vec<usize>,
|
||||
|
||||
client_version: String,
|
||||
currently_used_api: usize,
|
||||
}
|
||||
|
||||
impl FrontedApiTopologyProvider {
|
||||
pub(crate) fn new(
|
||||
config: Config,
|
||||
nym_api_urls: Vec<Url>,
|
||||
fronting_domains: Vec<Url>,
|
||||
client_version: String,
|
||||
) -> Self {
|
||||
//SW for the PoC, we assume same lenght between fronting domains and api_urls
|
||||
let mut shuffling = (0..nym_api_urls.len()).collect::<Vec<_>>();
|
||||
shuffling.shuffle(&mut thread_rng());
|
||||
|
||||
FrontedApiTopologyProvider {
|
||||
config,
|
||||
validator_client: nym_validator_client::client::NymApiClient::new_fronted(
|
||||
nym_api_urls[shuffling[0]].clone(),
|
||||
fronting_domains[shuffling[0]].clone(),
|
||||
),
|
||||
nym_api_urls,
|
||||
fronting_domains,
|
||||
shuffling,
|
||||
client_version,
|
||||
currently_used_api: 0,
|
||||
}
|
||||
}
|
||||
|
||||
fn use_next_nym_api(&mut self) {
|
||||
if self.nym_api_urls.len() == 1 {
|
||||
warn!("There's only a single nym API available - it won't be possible to use a different one");
|
||||
return;
|
||||
}
|
||||
|
||||
self.currently_used_api = (self.currently_used_api + 1) % self.nym_api_urls.len();
|
||||
self.validator_client.change_nym_api_with_fronting(
|
||||
self.nym_api_urls[self.shuffling[self.currently_used_api]].clone(),
|
||||
self.fronting_domains[self.shuffling[self.currently_used_api]].clone(),
|
||||
);
|
||||
}
|
||||
|
||||
/// Verifies whether nodes a reasonably distributed among all mix layers.
|
||||
///
|
||||
/// In ideal world we would have 33% nodes on layer 1, 33% on layer 2 and 33% on layer 3.
|
||||
/// However, this is a rather unrealistic expectation, instead we check whether there exists
|
||||
/// a layer with more than 66% of nodes or with fewer than 15% and if so, we trigger a failure.
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `topology`: active topology constructed from validator api data
|
||||
fn check_layer_distribution(
|
||||
&self,
|
||||
active_topology: &NymTopology,
|
||||
) -> Result<(), NymTopologyError> {
|
||||
let lower_threshold = 0.15;
|
||||
let upper_threshold = 0.66;
|
||||
active_topology.ensure_even_layer_distribution(lower_threshold, upper_threshold)
|
||||
}
|
||||
|
||||
async fn get_current_compatible_topology(&mut self) -> Option<NymTopology> {
|
||||
let mixnodes = match self
|
||||
.validator_client
|
||||
.get_basic_mixnodes(Some(self.client_version.clone()))
|
||||
.await
|
||||
{
|
||||
Err(err) => {
|
||||
error!("failed to get network mixnodes - {err}");
|
||||
return None;
|
||||
}
|
||||
Ok(mixes) => mixes,
|
||||
};
|
||||
|
||||
let gateways = match self
|
||||
.validator_client
|
||||
.get_basic_gateways(Some(self.client_version.clone()))
|
||||
.await
|
||||
{
|
||||
Err(err) => {
|
||||
error!("failed to get network gateways - {err}");
|
||||
return None;
|
||||
}
|
||||
Ok(gateways) => gateways,
|
||||
};
|
||||
|
||||
debug!(
|
||||
"there are {} mixnodes and {} gateways in total (before performance filtering)",
|
||||
mixnodes.len(),
|
||||
gateways.len()
|
||||
);
|
||||
|
||||
let topology = NymTopology::from_unordered(
|
||||
mixnodes.iter().filter(|m| {
|
||||
m.performance.round_to_integer() >= self.config.min_mixnode_performance
|
||||
}),
|
||||
gateways.iter().filter(|g| {
|
||||
g.performance.round_to_integer() >= self.config.min_gateway_performance
|
||||
}),
|
||||
);
|
||||
|
||||
if let Err(err) = self.check_layer_distribution(&topology) {
|
||||
warn!("The current filtered active topology has extremely skewed layer distribution. It cannot be used: {err}");
|
||||
self.use_next_nym_api();
|
||||
None
|
||||
} else {
|
||||
Some(topology)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// hehe, wasm
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
#[async_trait]
|
||||
impl TopologyProvider for FrontedApiTopologyProvider {
|
||||
async fn get_new_topology(&mut self) -> Option<NymTopology> {
|
||||
self.get_current_compatible_topology().await
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
#[async_trait(?Send)]
|
||||
impl TopologyProvider for FrontedApiTopologyProvider {
|
||||
async fn get_new_topology(&mut self) -> Option<NymTopology> {
|
||||
self.get_current_compatible_topology().await
|
||||
}
|
||||
}
|
||||
@@ -17,6 +17,7 @@ use tokio::time::sleep;
|
||||
use wasmtimer::tokio::sleep;
|
||||
|
||||
mod accessor;
|
||||
pub(crate) mod fronted_api_provider;
|
||||
pub mod geo_aware_provider;
|
||||
pub(crate) mod nym_api_provider;
|
||||
|
||||
|
||||
@@ -9,6 +9,7 @@ use nym_crypto::asymmetric::identity;
|
||||
use nym_gateway_client::GatewayClient;
|
||||
use nym_topology::{filter::VersionFilterable, gateway, mix};
|
||||
use nym_validator_client::client::IdentityKeyRef;
|
||||
use rand::seq::IteratorRandom;
|
||||
use rand::{seq::SliceRandom, Rng};
|
||||
use std::{sync::Arc, time::Duration};
|
||||
use tungstenite::Message;
|
||||
@@ -59,13 +60,29 @@ impl<'a> GatewayWithLatency<'a> {
|
||||
pub async fn current_gateways<R: Rng>(
|
||||
rng: &mut R,
|
||||
nym_apis: &[Url],
|
||||
fronting_domains: Option<&Vec<Url>>,
|
||||
) -> Result<Vec<gateway::Node>, ClientCoreError> {
|
||||
let nym_api = nym_apis
|
||||
.choose(rng)
|
||||
.ok_or(ClientCoreError::ListOfNymApisIsEmpty)?;
|
||||
let client = nym_validator_client::client::NymApiClient::new(nym_api.clone());
|
||||
let client = match fronting_domains {
|
||||
Some(domains) => {
|
||||
let (api_url, fronting_url) = nym_apis
|
||||
.iter()
|
||||
.zip(domains)
|
||||
.choose(rng)
|
||||
.ok_or(ClientCoreError::ListOfNymApisIsEmpty)?;
|
||||
|
||||
log::debug!("Fetching list of gateways from: {nym_api}");
|
||||
nym_validator_client::client::NymApiClient::new_fronted(
|
||||
api_url.clone(),
|
||||
fronting_url.clone(),
|
||||
)
|
||||
}
|
||||
None => {
|
||||
let nym_api = nym_apis
|
||||
.choose(rng)
|
||||
.ok_or(ClientCoreError::ListOfNymApisIsEmpty)?;
|
||||
nym_validator_client::client::NymApiClient::new(nym_api.clone())
|
||||
}
|
||||
};
|
||||
log::debug!("Fetching list of gateways from: {}", client.api_url());
|
||||
|
||||
let gateways = client.get_cached_described_gateways().await?;
|
||||
log::debug!("Found {} gateways", gateways.len());
|
||||
|
||||
@@ -258,6 +258,12 @@ impl NymApiClient {
|
||||
NymApiClient { nym_api }
|
||||
}
|
||||
|
||||
pub fn new_fronted(api_url: Url, fronting_url: Url) -> Self {
|
||||
let nym_api = nym_api::Client::new_fronted(api_url, fronting_url, None);
|
||||
|
||||
NymApiClient { nym_api }
|
||||
}
|
||||
|
||||
pub fn api_url(&self) -> &Url {
|
||||
self.nym_api.current_url()
|
||||
}
|
||||
@@ -266,6 +272,15 @@ impl NymApiClient {
|
||||
self.nym_api.change_base_url(new_endpoint);
|
||||
}
|
||||
|
||||
pub fn change_nym_api_with_fronting(
|
||||
&mut self,
|
||||
new_api_endpoint: Url,
|
||||
new_fronting_domain: Url,
|
||||
) {
|
||||
self.nym_api
|
||||
.change_fronted_url(new_api_endpoint, new_fronting_domain);
|
||||
}
|
||||
|
||||
pub async fn get_basic_mixnodes(
|
||||
&self,
|
||||
semver_compatibility: Option<String>,
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use async_trait::async_trait;
|
||||
use http::header;
|
||||
use reqwest::header::HeaderValue;
|
||||
use reqwest::{RequestBuilder, Response, StatusCode};
|
||||
use serde::de::DeserializeOwned;
|
||||
@@ -96,6 +97,13 @@ impl ClientBuilder {
|
||||
self
|
||||
}
|
||||
|
||||
pub fn with_host_header(mut self, host: &str) -> Self {
|
||||
let mut headers = header::HeaderMap::new();
|
||||
headers.insert(header::HOST, HeaderValue::from_str(host).unwrap()); //SW Handle this unwrap later
|
||||
self.reqwest_client_builder = self.reqwest_client_builder.default_headers(headers);
|
||||
self
|
||||
}
|
||||
|
||||
pub fn with_user_agent<V>(mut self, value: V) -> Self
|
||||
where
|
||||
V: TryInto<HeaderValue>,
|
||||
@@ -155,6 +163,23 @@ impl Client {
|
||||
)
|
||||
}
|
||||
|
||||
pub fn new_fronted(base_url: Url, fronting_url: Url, timeout: Option<Duration>) -> Self {
|
||||
let host = base_url.host_str().unwrap();
|
||||
let mut fronted_url = base_url.clone();
|
||||
fronted_url.set_host(fronting_url.host_str()).unwrap();
|
||||
let builder = ClientBuilder::new::<_, String>(fronted_url)
|
||||
.expect(
|
||||
"we provided valid url and we were unwrapping previous construction errors anyway",
|
||||
)
|
||||
.with_host_header(host);
|
||||
|
||||
//SW polish that later if needed
|
||||
match timeout {
|
||||
Some(timeout) => builder.with_timeout(timeout).build::<String>().unwrap(),
|
||||
None => builder.build::<String>().unwrap(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn new_url<U, E>(url: U, timeout: Option<Duration>) -> Result<Self, HttpClientError<E>>
|
||||
where
|
||||
U: IntoUrl,
|
||||
@@ -179,6 +204,21 @@ impl Client {
|
||||
self.base_url = new_url
|
||||
}
|
||||
|
||||
pub fn change_fronted_url(&mut self, new_api_url: Url, new_fronting_url: Url) {
|
||||
let host = new_api_url.host_str().unwrap();
|
||||
let mut new_fronted_url = new_api_url.clone();
|
||||
new_fronted_url
|
||||
.set_host(new_fronting_url.host_str())
|
||||
.unwrap();
|
||||
let mut headers = header::HeaderMap::new();
|
||||
headers.insert(header::HOST, HeaderValue::from_str(host).unwrap()); //SW Handle this unwrap later
|
||||
self.reqwest_client = reqwest::ClientBuilder::new()
|
||||
.default_headers(headers)
|
||||
.build()
|
||||
.unwrap();
|
||||
self.base_url = new_fronted_url
|
||||
}
|
||||
|
||||
pub fn current_url(&self) -> &Url {
|
||||
&self.base_url
|
||||
}
|
||||
|
||||
@@ -122,7 +122,7 @@ pub async fn setup_gateway_from_api(
|
||||
nym_apis: &[Url],
|
||||
) -> Result<InitialisationResult, WasmCoreError> {
|
||||
let mut rng = thread_rng();
|
||||
let gateways = current_gateways(&mut rng, nym_apis).await?;
|
||||
let gateways = current_gateways(&mut rng, nym_apis, None).await?;
|
||||
setup_gateway_wasm(client_store, force_tls, chosen_gateway, &gateways).await
|
||||
}
|
||||
|
||||
|
||||
@@ -274,7 +274,7 @@ where
|
||||
specification: GatewaySelectionSpecification::UniformRemote {
|
||||
must_use_tls: false,
|
||||
},
|
||||
available_gateways: current_gateways(&mut rng, &nym_apis).await?,
|
||||
available_gateways: current_gateways(&mut rng, &nym_apis, None).await?,
|
||||
wg_tun_address: None,
|
||||
});
|
||||
|
||||
|
||||
@@ -459,7 +459,7 @@ where
|
||||
);
|
||||
|
||||
let mut rng = OsRng;
|
||||
let available_gateways = current_gateways(&mut rng, &nym_api_endpoints).await?;
|
||||
let available_gateways = current_gateways(&mut rng, &nym_api_endpoints, None).await?;
|
||||
|
||||
Ok(GatewaySetup::New {
|
||||
specification: selection_spec,
|
||||
|
||||
Reference in New Issue
Block a user