use crate::cli_helpers::traits::{CliClient, CliClientConfig};
use crate::error::ClientCoreError;
use crate::{
client::{
base_client::{
non_wasm_helpers::setup_fs_gateways_storage, storage::helpers::set_active_gateway,
},
key_manager::persistence::OnDiskKeys,
},
init::types::{GatewaySelectionSpecification, GatewaySetup, InitResults},
};
use nym_client_core_gateways_storage::GatewayDetails;
use nym_crypto::asymmetric::ed25519;
use nym_sphinx::addressing::Recipient;
use nym_topology::NymTopology;
use nym_validator_client::UserAgent;
use rand::rngs::OsRng;
use std::path::PathBuf;
use tracing::info;
#[allow(async_fn_in_trait)]
pub trait InitialisableClient: CliClient {
type InitArgs: AsRef<CommonClientInitArgs>;
fn initialise_storage_paths(id: &str) -> Result<(), Self::Error>;
fn default_config_path(id: &str) -> PathBuf;
fn construct_config(init_args: &Self::InitArgs) -> Self::Config;
}
#[cfg_attr(feature = "cli", derive(clap::Args))]
#[derive(Debug, Clone)]
pub struct CommonClientInitArgs {
#[cfg_attr(feature = "cli", clap(long))]
pub id: String,
#[cfg_attr(feature = "cli", clap(long))]
pub gateway: Option<ed25519::PublicKey>,
#[cfg_attr(feature = "cli", clap(long))]
pub force_tls_gateway: bool,
#[cfg_attr(feature = "cli", clap(long, conflicts_with = "gateway"))]
pub latency_based_selection: bool,
#[cfg_attr(
feature = "cli",
clap(long, alias = "nyxd_validators", value_delimiter = ',', hide = true)
)]
pub nyxd_urls: Option<Vec<url::Url>>,
#[cfg_attr(
feature = "cli",
clap(
long,
alias = "api_validators",
value_delimiter = ',',
group = "network"
)
)]
pub nym_apis: Option<Vec<url::Url>>,
#[cfg_attr(feature = "cli", clap(long, group = "network", hide = true))]
pub custom_mixnet: Option<PathBuf>,
#[cfg_attr(feature = "cli", clap(long, hide = true))]
pub enabled_credentials_mode: Option<bool>,
#[cfg_attr(feature = "cli", clap(long, hide = true))]
pub minimum_gateway_performance: Option<u8>,
#[cfg_attr(feature = "cli", clap(long, hide = true))]
pub fastmode: bool,
#[cfg_attr(feature = "cli", clap(long, hide = true))]
pub no_cover: bool,
#[cfg_attr(feature = "cli", clap(long, hide = true))]
pub stats_reporting_address: Option<Recipient>,
#[cfg_attr(feature = "cli", clap(long, hide = true, default_value_t = false))]
pub forget_me: bool,
}
pub struct InitResultsWithConfig<T> {
pub config: T,
pub init_results: InitResults,
}
pub async fn initialise_client<C>(
init_args: C::InitArgs,
user_agent: Option<UserAgent>,
) -> Result<InitResultsWithConfig<C::Config>, C::Error>
where
C: InitialisableClient,
<C as CliClient>::Config: std::fmt::Debug,
<C as InitialisableClient>::InitArgs: std::fmt::Debug,
{
info!("initialising {} client", C::NAME);
let common_args = init_args.as_ref();
let id = &common_args.id;
if C::default_config_path(id).exists() {
eprintln!("{} client \"{id}\" was already initialised before", C::NAME);
return Err(ClientCoreError::AlreadyInitialised {
client_id: id.to_string(),
}
.into());
}
C::initialise_storage_paths(id)?;
let user_chosen_gateway_id = common_args.gateway;
tracing::debug!("User chosen gateway id: {user_chosen_gateway_id:?}");
let selection_spec = GatewaySelectionSpecification::new(
user_chosen_gateway_id.map(|id| id.to_base58_string()),
Some(common_args.latency_based_selection),
common_args.force_tls_gateway,
false,
);
tracing::debug!("Gateway selection specification: {selection_spec:?}");
tracing::debug!("Init arguments: {init_args:#?}");
let config = C::construct_config(&init_args);
tracing::debug!("Constructed config: {config:#?}");
let paths = config.common_paths();
let core = config.core_config();
tracing::info!(
"Using nym-api: {}",
core.client
.nym_api_urls
.iter()
.map(|url| url.as_str())
.collect::<Vec<&str>>()
.join(",")
);
let key_store = OnDiskKeys::new(paths.keys.clone());
let details_store = setup_fs_gateways_storage(&paths.gateway_registrations).await?;
let mut rng = OsRng;
crate::init::generate_new_client_keys(&mut rng, &key_store).await?;
let available_gateways = if let Some(custom_mixnet) = common_args.custom_mixnet.as_ref() {
let hardcoded_topology = NymTopology::new_from_file(custom_mixnet).map_err(|source| {
ClientCoreError::CustomTopologyLoadFailure {
file_path: custom_mixnet.clone(),
source,
}
})?;
hardcoded_topology.entry_capable_nodes().cloned().collect()
} else {
let minimum_performance = common_args
.minimum_gateway_performance
.unwrap_or(core.debug.topology.minimum_gateway_performance);
crate::init::helpers::gateways_for_init(
&core.client.nym_api_urls,
user_agent,
minimum_performance,
core.debug.topology.ignore_ingress_epoch_role,
None,
)
.await?
};
let gateway_setup = GatewaySetup::New {
specification: selection_spec,
available_gateways,
#[cfg(unix)]
connection_fd_callback: None,
};
let init_details =
crate::init::setup_gateway(gateway_setup, &key_store, &details_store).await?;
let config_save_location = config.default_store_location();
if let Err(err) = config.save_to(&config_save_location) {
return Err(ClientCoreError::ConfigSaveFailure {
typ: C::NAME.to_string(),
id: id.to_string(),
path: config_save_location,
source: err,
}
.into());
}
eprintln!(
"Saved configuration file to {}",
config_save_location.display()
);
let address = init_details.client_address();
let GatewayDetails::Remote(gateway_details) = init_details.gateway_registration.details else {
return Err(ClientCoreError::UnexpectedPersistedCustomGatewayDetails)?;
};
let init_results = InitResults::new(
config.core_config(),
address,
&gateway_details,
init_details.gateway_registration.registration_timestamp,
);
set_active_gateway(&details_store, &init_results.gateway_id).await?;
Ok(InitResultsWithConfig {
config,
init_results,
})
}