use crate::GAS_PRICE_AMOUNT;
use crate::network::{ApiUrl, ChainDetails, DenomDetailsOwned, NymContracts, ValidatorDetails};
use crate::{mainnet, sandbox, v1};
use schemars::JsonSchema;
use serde::{Deserialize, Serialize};
use std::collections::HashMap;
use std::net::IpAddr;
#[cfg(feature = "env")]
use std::env::{VarError, var};
#[cfg(feature = "env")]
use std::ffi::OsStr;
#[cfg(feature = "env")]
use url::Url;
#[derive(Clone, Debug, Deserialize, Eq, Hash, PartialEq, Serialize, JsonSchema)]
#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
#[cfg_attr(feature = "utoipa", schema(as = network::v2::NymNetworkDetails))]
pub struct NymNetworkDetails {
pub network_name: String,
pub chain_details: ChainDetails,
pub endpoints: Vec<ValidatorDetails>,
pub contracts: NymContracts,
pub networking: NetworkingSpecifics,
}
#[derive(Clone, Debug, Hash, Serialize, Deserialize, PartialEq, Eq, JsonSchema)]
#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
#[cfg_attr(feature = "utoipa", schema(as = network::v2::NetworkingSpecifics))]
pub struct NetworkingSpecifics {
pub nym_api_urls: Vec<ApiUrl>,
pub nym_vpn_api_urls: Vec<ApiUrl>,
pub dns_fallbacks: Vec<DnsFallback>,
}
#[derive(Clone, Debug, Hash, Serialize, Deserialize, PartialEq, Eq, JsonSchema)]
#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
#[cfg_attr(feature = "utoipa", schema(as = network::v2::DnsFallback))]
pub struct DnsFallback {
pub url: String,
pub addresses: Vec<String>,
}
pub(crate) fn dns_fallbacks(raw: HashMap<String, Vec<IpAddr>>) -> Vec<DnsFallback> {
let mut fallbacks: Vec<DnsFallback> = raw
.into_iter()
.map(|(url, addresses)| DnsFallback {
url,
addresses: addresses.iter().map(ToString::to_string).collect(),
})
.collect();
fallbacks.sort_by(|a, b| a.url.cmp(&b.url));
fallbacks
}
#[cfg(feature = "env")]
fn dns_fallbacks_from_env(network_name: &str) -> Vec<DnsFallback> {
use crate::var_names;
use std::env::var;
match var(var_names::DNS_FALLBACKS) {
Ok(raw) if !raw.is_empty() => serde_json::from_str(&raw).unwrap_or_else(|e| {
panic!(
"{} is set but could not be parsed: {e}",
var_names::DNS_FALLBACKS
)
}),
_ if network_name == mainnet::NETWORK_NAME => {
dns_fallbacks(mainnet::dns::default_static_addrs())
}
_ if network_name == sandbox::NETWORK_NAME => {
dns_fallbacks(sandbox::dns::default_static_addrs())
}
_ => Vec::new(),
}
}
#[cfg(feature = "env")]
fn serialize_dns_fallbacks(fallbacks: &[DnsFallback]) -> String {
serde_json::to_string(fallbacks)
.inspect_err(|e| tracing::warn!("failed to serialize dns_fallbacks for env: {e}"))
.unwrap_or_default()
}
fn parse_optional_str(raw: &str) -> Option<String> {
(!raw.is_empty()).then(|| raw.to_string())
}
#[cfg(feature = "env")]
fn serialize_api_urls(urls: &[ApiUrl]) -> Option<String> {
serde_json::to_string(urls)
.inspect_err(|e| tracing::warn!("failed to serialize api urls for env: {e}"))
.ok()
}
#[cfg(feature = "env")]
fn try_parse_api_urls(k: impl AsRef<OsStr>) -> Result<Vec<ApiUrl>, serde_json::Error> {
match var(k) {
Ok(raw) if !raw.is_empty() => serde_json::from_str(&raw),
_ => Ok(Vec::new()),
}
}
#[cfg(feature = "env")]
fn get_optional_env<K: AsRef<OsStr>>(env: K) -> Option<String> {
match var(env) {
Ok(var) => (!var.is_empty()).then_some(var),
Err(VarError::NotPresent) => None,
err => panic!("Unable to set: {err:?}"),
}
}
#[cfg(feature = "env")]
fn parse_legacy_nym_api() -> ApiUrl {
use crate::var_names;
let legacy_api = get_optional_env(var_names::NYM_API).unwrap_or_else(|| {
panic!(
"neither {} nor legacy {} is set",
var_names::NYM_APIS,
var_names::NYM_API
)
});
Url::parse(&legacy_api)
.unwrap_or_else(|e| panic!("{} is not a valid url: {e}", var_names::NYM_API))
.into()
}
impl Default for NymNetworkDetails {
fn default() -> Self {
NymNetworkDetails::new_mainnet()
}
}
impl From<v1::NymNetworkDetails> for NymNetworkDetails {
fn from(v1: v1::NymNetworkDetails) -> Self {
NymNetworkDetails {
networking: NetworkingSpecifics {
nym_api_urls: v1.nym_api_urls(),
nym_vpn_api_urls: v1.nym_vpn_api_urls(),
dns_fallbacks: Vec::new(),
},
network_name: v1.network_name,
chain_details: v1.chain_details,
endpoints: v1.endpoints,
contracts: v1.contracts,
}
}
}
impl From<NymNetworkDetails> for v1::NymNetworkDetails {
fn from(v2: NymNetworkDetails) -> Self {
let nym_vpn_api_url = v2
.networking
.nym_vpn_api_urls
.first()
.map(|url| url.url.clone());
v1::NymNetworkDetails {
network_name: v2.network_name,
chain_details: v2.chain_details,
endpoints: v2.endpoints,
contracts: v2.contracts,
nym_api_urls: (!v2.networking.nym_api_urls.is_empty())
.then_some(v2.networking.nym_api_urls),
nym_vpn_api_urls: (!v2.networking.nym_vpn_api_urls.is_empty())
.then_some(v2.networking.nym_vpn_api_urls),
nym_vpn_api_url,
}
}
}
impl NymNetworkDetails {
pub fn new_empty() -> Self {
NymNetworkDetails {
network_name: Default::default(),
chain_details: ChainDetails {
bech32_account_prefix: Default::default(),
mix_denom: DenomDetailsOwned {
base: Default::default(),
display: Default::default(),
display_exponent: Default::default(),
},
stake_denom: DenomDetailsOwned {
base: Default::default(),
display: Default::default(),
display_exponent: Default::default(),
},
},
endpoints: Default::default(),
contracts: Default::default(),
networking: NetworkingSpecifics {
nym_api_urls: Vec::new(),
nym_vpn_api_urls: Vec::new(),
dns_fallbacks: Vec::new(),
},
}
}
pub fn new_mainnet() -> Self {
NymNetworkDetails {
network_name: mainnet::NETWORK_NAME.into(),
chain_details: ChainDetails::mainnet(),
endpoints: mainnet::validators(),
contracts: NymContracts {
mixnet_contract_address: parse_optional_str(mainnet::MIXNET_CONTRACT_ADDRESS),
vesting_contract_address: parse_optional_str(mainnet::VESTING_CONTRACT_ADDRESS),
performance_contract_address: parse_optional_str(
mainnet::PERFORMANCE_CONTRACT_ADDRESS,
),
network_monitors_contract_address: parse_optional_str(
mainnet::NETWORK_MONITORS_CONTRACT_ADDRESS,
),
node_families_contract_address: parse_optional_str(
mainnet::NODE_FAMILIES_CONTRACT_ADDRESS,
),
directory_contract_address: parse_optional_str(mainnet::DIRECTORY_CONTRACT_ADDRESS),
geolocation_contract_address: parse_optional_str(
mainnet::GEOLOCATION_CONTRACT_ADDRESS,
),
ecash_contract_address: parse_optional_str(mainnet::ECASH_CONTRACT_ADDRESS),
group_contract_address: parse_optional_str(mainnet::GROUP_CONTRACT_ADDRESS),
multisig_contract_address: parse_optional_str(mainnet::MULTISIG_CONTRACT_ADDRESS),
coconut_dkg_contract_address: parse_optional_str(
mainnet::COCONUT_DKG_CONTRACT_ADDRESS,
),
},
networking: NetworkingSpecifics {
nym_api_urls: mainnet::NYM_APIS.iter().copied().map(Into::into).collect(),
nym_vpn_api_urls: mainnet::NYM_VPN_APIS
.iter()
.copied()
.map(Into::into)
.collect(),
dns_fallbacks: dns_fallbacks(mainnet::dns::default_static_addrs()),
},
}
}
pub fn new_sandbox() -> Self {
NymNetworkDetails {
network_name: sandbox::NETWORK_NAME.into(),
chain_details: ChainDetails {
bech32_account_prefix: sandbox::BECH32_PREFIX.to_string(),
mix_denom: sandbox::MIX_DENOM.into(),
stake_denom: sandbox::STAKE_DENOM.into(),
},
endpoints: sandbox::validators(),
contracts: NymContracts {
mixnet_contract_address: parse_optional_str(sandbox::MIXNET_CONTRACT_ADDRESS),
vesting_contract_address: parse_optional_str(sandbox::VESTING_CONTRACT_ADDRESS),
performance_contract_address: parse_optional_str(
sandbox::PERFORMANCE_CONTRACT_ADDRESS,
),
network_monitors_contract_address: parse_optional_str(
sandbox::NETWORK_MONITORS_CONTRACT_ADDRESS,
),
node_families_contract_address: parse_optional_str(
sandbox::NODE_FAMILIES_CONTRACT_ADDRESS,
),
directory_contract_address: parse_optional_str(sandbox::DIRECTORY_CONTRACT_ADDRESS),
geolocation_contract_address: parse_optional_str(
sandbox::GEOLOCATION_CONTRACT_ADDRESS,
),
ecash_contract_address: parse_optional_str(sandbox::ECASH_CONTRACT_ADDRESS),
group_contract_address: parse_optional_str(sandbox::GROUP_CONTRACT_ADDRESS),
multisig_contract_address: parse_optional_str(sandbox::MULTISIG_CONTRACT_ADDRESS),
coconut_dkg_contract_address: parse_optional_str(
sandbox::COCONUT_DKG_CONTRACT_ADDRESS,
),
},
networking: NetworkingSpecifics {
nym_api_urls: sandbox::NYM_APIS.iter().copied().map(Into::into).collect(),
nym_vpn_api_urls: sandbox::NYM_VPN_APIS
.iter()
.copied()
.map(Into::into)
.collect(),
dns_fallbacks: dns_fallbacks(sandbox::dns::default_static_addrs()),
},
}
}
#[cfg(feature = "env")]
pub fn new_from_env() -> Self {
use crate::var_names;
let nym_api_urls = try_parse_api_urls(var_names::NYM_APIS).unwrap_or_else(|e| {
panic!(
"{} is set but could not be parsed: {e}",
var_names::NYM_APIS
)
});
let nym_api_urls = if nym_api_urls.is_empty() {
vec![parse_legacy_nym_api()]
} else {
nym_api_urls
};
let nym_api = nym_api_urls
.first()
.expect("nym_api_urls is guaranteed non-empty at this point");
let nym_vpn_api_urls = try_parse_api_urls(var_names::NYM_VPN_APIS).unwrap_or_else(|e| {
panic!(
"{} is set but could not be parsed: {e}",
var_names::NYM_VPN_APIS
)
});
let network_name = var(var_names::NETWORK_NAME).expect("network name not set");
let dns_fallbacks = dns_fallbacks_from_env(&network_name);
NymNetworkDetails {
network_name,
chain_details: ChainDetails {
bech32_account_prefix: var(var_names::BECH32_PREFIX)
.expect("bech32 prefix not set"),
mix_denom: DenomDetailsOwned {
base: var(var_names::MIX_DENOM).expect("mix denomination base not set"),
display: var(var_names::MIX_DENOM_DISPLAY)
.expect("mix denomination display not set"),
display_exponent: var(var_names::DENOMS_EXPONENT)
.expect("denomination exponent not set")
.parse()
.expect("denomination exponent is not u32"),
},
stake_denom: DenomDetailsOwned {
base: var(var_names::STAKE_DENOM).expect("stake denomination base not set"),
display: var(var_names::STAKE_DENOM_DISPLAY)
.expect("stake denomination display not set"),
display_exponent: var(var_names::DENOMS_EXPONENT)
.expect("denomination exponent not set")
.parse()
.expect("denomination exponent is not u32"),
},
},
endpoints: vec![ValidatorDetails::new(
var(var_names::NYXD).expect("nyxd validator not set"),
Some(nym_api.url.clone()),
get_optional_env(var_names::NYXD_WEBSOCKET),
)],
contracts: NymContracts {
mixnet_contract_address: get_optional_env(var_names::MIXNET_CONTRACT_ADDRESS),
vesting_contract_address: get_optional_env(var_names::VESTING_CONTRACT_ADDRESS),
performance_contract_address: get_optional_env(
var_names::PERFORMANCE_CONTRACT_ADDRESS,
),
network_monitors_contract_address: get_optional_env(
var_names::NETWORK_MONITORS_CONTRACT_ADDRESS,
),
node_families_contract_address: get_optional_env(
var_names::NODE_FAMILIES_CONTRACT_ADDRESS,
),
directory_contract_address: get_optional_env(var_names::DIRECTORY_CONTRACT_ADDRESS),
geolocation_contract_address: get_optional_env(
var_names::GEOLOCATION_CONTRACT_ADDRESS,
),
ecash_contract_address: get_optional_env(var_names::ECASH_CONTRACT_ADDRESS),
group_contract_address: get_optional_env(var_names::GROUP_CONTRACT_ADDRESS),
multisig_contract_address: get_optional_env(var_names::MULTISIG_CONTRACT_ADDRESS),
coconut_dkg_contract_address: get_optional_env(
var_names::COCONUT_DKG_CONTRACT_ADDRESS,
),
},
networking: NetworkingSpecifics {
nym_api_urls,
nym_vpn_api_urls,
dns_fallbacks,
},
}
}
#[rustfmt::skip]
#[cfg(feature = "env")]
pub fn export_to_env(self) {
use crate::var_names;
use std::env::{remove_var, set_var};
fn set_optional_var(var_name: &str, value: Option<String>) {
if let Some(value) = value {
unsafe { set_var(var_name, value) }
}
}
unsafe {
let nym_api_urls = serialize_api_urls(&self.networking.nym_api_urls);
let nym_vpn_api_urls = serialize_api_urls(&self.networking.nym_vpn_api_urls);
let dns_fallbacks = self.networking.dns_fallbacks;
set_var(var_names::NETWORK_NAME, self.network_name);
set_var(var_names::BECH32_PREFIX, self.chain_details.bech32_account_prefix);
set_var(var_names::MIX_DENOM, self.chain_details.mix_denom.base);
set_var(var_names::MIX_DENOM_DISPLAY, self.chain_details.mix_denom.display);
set_var(var_names::STAKE_DENOM, self.chain_details.stake_denom.base);
set_var(var_names::STAKE_DENOM_DISPLAY, self.chain_details.stake_denom.display);
set_var(var_names::DENOMS_EXPONENT, self.chain_details.mix_denom.display_exponent.to_string());
if let Some(e) = self.endpoints.first() {
set_var(var_names::NYXD, e.nyxd_url.clone());
set_optional_var(var_names::NYM_API, e.api_url.clone());
set_optional_var(var_names::NYXD_WEBSOCKET, e.websocket_url.clone());
}
set_optional_var(var_names::MIXNET_CONTRACT_ADDRESS, self.contracts.mixnet_contract_address);
set_optional_var(var_names::VESTING_CONTRACT_ADDRESS, self.contracts.vesting_contract_address);
set_optional_var(var_names::PERFORMANCE_CONTRACT_ADDRESS, self.contracts.performance_contract_address);
set_optional_var(var_names::NETWORK_MONITORS_CONTRACT_ADDRESS, self.contracts.network_monitors_contract_address);
set_optional_var(var_names::NODE_FAMILIES_CONTRACT_ADDRESS, self.contracts.node_families_contract_address);
set_optional_var(var_names::DIRECTORY_CONTRACT_ADDRESS, self.contracts.directory_contract_address);
set_optional_var(var_names::GEOLOCATION_CONTRACT_ADDRESS, self.contracts.geolocation_contract_address);
set_optional_var(var_names::ECASH_CONTRACT_ADDRESS, self.contracts.ecash_contract_address);
set_optional_var(var_names::GROUP_CONTRACT_ADDRESS, self.contracts.group_contract_address);
set_optional_var(var_names::MULTISIG_CONTRACT_ADDRESS, self.contracts.multisig_contract_address);
set_optional_var(var_names::COCONUT_DKG_CONTRACT_ADDRESS, self.contracts.coconut_dkg_contract_address);
set_optional_var(var_names::NYM_VPN_APIS, nym_vpn_api_urls);
set_optional_var(var_names::NYM_APIS, nym_api_urls);
if !dns_fallbacks.is_empty() {
set_var(var_names::DNS_FALLBACKS, serialize_dns_fallbacks(&dns_fallbacks));
} else {
remove_var(var_names::DNS_FALLBACKS);
}
}
}
#[must_use]
pub fn with_network_name(mut self, network_name: String) -> Self {
self.network_name = network_name;
self
}
#[must_use]
pub fn with_chain_details(mut self, chain_details: ChainDetails) -> Self {
self.chain_details = chain_details;
self
}
#[must_use]
pub fn with_bech32_account_prefix<S: Into<String>>(mut self, prefix: S) -> Self {
self.chain_details.bech32_account_prefix = prefix.into();
self
}
#[must_use]
pub fn with_mix_denom(mut self, mix_denom: DenomDetailsOwned) -> Self {
self.chain_details.mix_denom = mix_denom;
self
}
#[must_use]
pub fn with_stake_denom(mut self, stake_denom: DenomDetailsOwned) -> Self {
self.chain_details.stake_denom = stake_denom;
self
}
#[must_use]
pub fn with_base_mix_denom<S: Into<String>>(mut self, base_mix_denom: S) -> Self {
self.chain_details.mix_denom = DenomDetailsOwned::base_only(base_mix_denom.into());
self
}
#[must_use]
pub fn with_base_stake_denom<S: Into<String>>(mut self, base_stake_denom: S) -> Self {
self.chain_details.stake_denom = DenomDetailsOwned::base_only(base_stake_denom.into());
self
}
#[must_use]
pub fn with_additional_validator_endpoint(mut self, endpoint: ValidatorDetails) -> Self {
self.endpoints.push(endpoint);
self
}
#[must_use]
pub fn with_validator_endpoint(mut self, endpoint: ValidatorDetails) -> Self {
self.endpoints = vec![endpoint];
self
}
#[must_use]
pub fn with_contracts(mut self, contracts: NymContracts) -> Self {
self.contracts = contracts;
self
}
#[must_use]
pub fn with_mixnet_contract<S: Into<String>>(mut self, contract: Option<S>) -> Self {
self.contracts.mixnet_contract_address = contract.map(Into::into);
self
}
#[must_use]
pub fn with_vesting_contract<S: Into<String>>(mut self, contract: Option<S>) -> Self {
self.contracts.vesting_contract_address = contract.map(Into::into);
self
}
#[must_use]
pub fn with_node_families_contract<S: Into<String>>(mut self, contract: Option<S>) -> Self {
self.contracts.node_families_contract_address = contract.map(Into::into);
self
}
#[must_use]
pub fn with_directory_contract<S: Into<String>>(mut self, contract: Option<S>) -> Self {
self.contracts.directory_contract_address = contract.map(Into::into);
self
}
#[must_use]
pub fn with_geolocation_contract<S: Into<String>>(mut self, contract: Option<S>) -> Self {
self.contracts.geolocation_contract_address = contract.map(Into::into);
self
}
#[must_use]
pub fn with_ecash_contract<S: Into<String>>(mut self, contract: Option<S>) -> Self {
self.contracts.ecash_contract_address = contract.map(Into::into);
self
}
#[must_use]
pub fn with_group_contract<S: Into<String>>(mut self, contract: Option<S>) -> Self {
self.contracts.group_contract_address = contract.map(Into::into);
self
}
#[must_use]
pub fn with_multisig_contract<S: Into<String>>(mut self, contract: Option<S>) -> Self {
self.contracts.multisig_contract_address = contract.map(Into::into);
self
}
#[must_use]
pub fn with_coconut_dkg_contract<S: Into<String>>(mut self, contract: Option<S>) -> Self {
self.contracts.coconut_dkg_contract_address = contract.map(Into::into);
self
}
#[must_use]
pub fn with_performance_contract<S: Into<String>>(mut self, contract: Option<S>) -> Self {
self.contracts.performance_contract_address = contract.map(Into::into);
self
}
#[must_use]
pub fn with_network_monitors_contract<S: Into<String>>(mut self, contract: Option<S>) -> Self {
self.contracts.network_monitors_contract_address = contract.map(Into::into);
self
}
#[must_use]
pub fn with_dns_fallbacks(mut self, dns_fallbacks: Vec<DnsFallback>) -> Self {
self.networking.dns_fallbacks = dns_fallbacks;
self
}
pub fn set_nym_api_urls<U: Into<ApiUrl>>(&mut self, urls: Vec<U>) {
self.networking.nym_api_urls = urls.into_iter().map(Into::into).collect();
}
#[must_use]
pub fn with_nym_api_urls<U: Into<ApiUrl>>(mut self, urls: Vec<U>) -> Self {
self.set_nym_api_urls(urls);
self
}
pub fn set_nym_vpn_api_urls<U: Into<ApiUrl>>(&mut self, urls: Vec<U>) {
self.networking.nym_vpn_api_urls = urls.into_iter().map(Into::into).collect();
}
#[must_use]
pub fn with_nym_vpn_api_urls<U: Into<ApiUrl>>(mut self, urls: Vec<U>) -> Self {
self.set_nym_vpn_api_urls(urls);
self
}
pub fn nym_api_urls(&self) -> Vec<ApiUrl> {
if !self.networking.nym_api_urls.is_empty() {
return self.networking.nym_api_urls.clone();
}
self.endpoints
.iter()
.filter_map(|e| e.api_url())
.map(ApiUrl::from)
.collect()
}
pub fn nym_vpn_api_urls(&self) -> Vec<ApiUrl> {
self.networking.nym_vpn_api_urls.clone()
}
pub fn dns_fallbacks(&self) -> Vec<DnsFallback> {
self.networking.dns_fallbacks.clone()
}
pub fn default_gas_price_amount(&self) -> f64 {
GAS_PRICE_AMOUNT
}
}
#[cfg(all(test, feature = "env"))]
mod tests {
use super::*;
use crate::var_names;
use std::sync::Mutex;
static DNS_FALLBACKS_ENV_LOCK: Mutex<()> = Mutex::new(());
fn with_dns_fallbacks_var_cleared(test: impl FnOnce()) {
let _guard = DNS_FALLBACKS_ENV_LOCK.lock().unwrap();
let previous = std::env::var(var_names::DNS_FALLBACKS).ok();
unsafe { std::env::remove_var(var_names::DNS_FALLBACKS) };
test();
unsafe {
match &previous {
Some(value) => std::env::set_var(var_names::DNS_FALLBACKS, value),
None => std::env::remove_var(var_names::DNS_FALLBACKS),
}
}
}
#[test]
fn dns_fallbacks_from_env_defaults_to_empty_when_unset_on_unknown_network() {
with_dns_fallbacks_var_cleared(|| {
assert_eq!(dns_fallbacks_from_env("some-unknown-network"), Vec::new());
});
}
#[test]
fn dns_fallbacks_from_env_falls_back_to_mainnet_pins_when_unset() {
with_dns_fallbacks_var_cleared(|| {
assert_eq!(
dns_fallbacks_from_env(mainnet::NETWORK_NAME),
dns_fallbacks(mainnet::dns::default_static_addrs())
);
});
}
#[test]
fn dns_fallbacks_from_env_falls_back_to_sandbox_pins_when_unset() {
with_dns_fallbacks_var_cleared(|| {
assert_eq!(
dns_fallbacks_from_env(sandbox::NETWORK_NAME),
dns_fallbacks(sandbox::dns::default_static_addrs())
);
});
}
#[test]
fn dns_fallbacks_round_trip_through_export_and_env() {
with_dns_fallbacks_var_cleared(|| {
let fallbacks = vec![
DnsFallback {
url: "example.com".to_string(),
addresses: vec!["1.2.3.4".to_string(), "::1".to_string()],
},
DnsFallback {
url: "other.example.com".to_string(),
addresses: vec!["5.6.7.8".to_string()],
},
];
NymNetworkDetails::new_mainnet()
.with_dns_fallbacks(fallbacks.clone())
.export_to_env();
assert_eq!(dns_fallbacks_from_env(mainnet::NETWORK_NAME), fallbacks);
assert_eq!(
NymNetworkDetails::new_from_env().networking.dns_fallbacks,
fallbacks
);
});
}
#[test]
fn new_mainnet_dns_fallbacks_are_not_empty() {
assert!(
!NymNetworkDetails::new_mainnet()
.networking
.dns_fallbacks
.is_empty()
);
}
#[test]
fn new_sandbox_dns_fallbacks_are_not_empty() {
assert!(
!NymNetworkDetails::new_sandbox()
.networking
.dns_fallbacks
.is_empty()
);
}
#[test]
fn v1_new_mainnet_still_reports_mainnet_details() {
let v1 = crate::v1::NymNetworkDetails::new_mainnet();
assert_eq!(v1.network_name, mainnet::NETWORK_NAME);
assert!(!v1.nym_api_urls().is_empty());
assert!(v1.contracts.mixnet_contract_address.is_some());
}
#[test]
fn v1_and_v2_mainnet_agree_on_shared_fields() {
let v1 = crate::v1::NymNetworkDetails::new_mainnet();
let v2 = NymNetworkDetails::new_mainnet();
assert_eq!(v1.network_name, v2.network_name);
assert_eq!(v1.chain_details, v2.chain_details);
}
}