46c67440bb
* Squashing the mix stress testing branch (#6575) reduced chain watcher per block log severity update network monitors contract semver to 1.0.0 fix build issues fix mixnet client dropping initial packet on egress reconnection adjusted logs for network monitor agent changed default testing interval to 2h refresh NM contract information explicit return type for batch submission for mixnet listener task to get scheduled before beginning connectivity test make sure to always use canonical ip for network monitor noise keys feat: NMv3: make agents decide egress port (#6746) add config v12->v13 config migration for nym nodes fix formatting in wallet types simplified client config creation remove other swagger redirect removed swagger redirect on /swagger/ route log version info on startup add workflows, contract address, and dockerfile bugfix: use correct endpoints when setting up orchestrator (#6733) clippy adjust DEFAULT_MIN_STRESS_TESTED_NODES ratio expose route with new performance metrics fixes and additional docs use stress testing scores stub for usage of stress testing scores stub traits added new fields to nym-api config controlling usage of stress test data guard against duplicate packets prevent usage of chain_authorisation_check_max_attempts with value of 0 make sure duplicate results cant be inserted into the db submit test results from orchestrator on an interval docs and fixes nym-api side of handling result submission stubs for submitting results NM orchestrator verifying nym-api result submission permissions NM orchestrator to update announced key on startup allow NM orchestrator to announce its identity key to the contract stubs within nym-api for accepting NMv3 results added additional metrics docs bugfixes + making sure to only assign mixnode testruns fixed node refresher to only retrieve mixnodes and add additional metrics topology metrics defined basic prometheus metrics authorised endpoint for returning prometheus data create initial stub for prometheus metrics post rebasing fixes adjusted routes missing implementation for storage getters a lot of new stubs and db accessors stubs for results endpoints update utoipa tags for agent rountes shared auth between metrics and results moved stale results eviction into the interval.tick branch refactor and comments create background process to evict stale data include sphinx packet delay as part of the stats fix mock construction add median to the calculated latency distribution remove unused imports cleanup performing testrun and submitting the results assigning testruns to requesting agents basic stub for http server for the NMv3 orchestrator chore: rename existing 'NetworkMonitorAgent' to 'NodeStressTester' make sure to use canonical ips within the noise config fixed contract tests cargo fmt additional comments and unit tests contract and nym-node support of NM agents being run on the same host basic unit tests refactoring make agents retrieve mix port assignment from the orchestrator provide sensible defaults to CLI arguments stub the initial structure for the agent chore: remove redundant import missed tick behaviour removed redundant mutex removed redundant try_get_client reuse existing constant for default nymnode port add node refresher for periodic scraping of bonded nym-node details - NodeRefresher periodically queries the mixnet contract for all bonded nodes and probes each node's HTTP API for host information, sphinx keys, noise keys, and key rotation IDs - Extract NymNodeApiClientRetriever into nym-node-requests with port probing, identity verification, and host information signature checking - Add clone_query_client on NyxdClient so the refresher can hold its own query client without locking the signing client - Batch upsert for nym_node rows (single transaction instead of per-row) - Reuse the new helpers in nym-api's node_describe_cache ensure assignment of testrun begins an IMMEDIATE tx construction of the orchestrator struct initial set of cli args make sure to not assign testable nodes too often very initial database structure and cli fixed construction of RoutableNetworkMonitors remove redundant constructor for NoiseNode forbid 0-nonsense config values add type safety for test route construction moved lioness and arrayref to workspace deps fixed dockerfile build always use canonical addresses in RoutableNetworkMonitors fixed old contract formatting issues removed redundant into() call network monitor agent fixes additional logs config unit tests more docs standalone stress testing invocation further refactoring and changes refactor testing loop and return valid test result upon completion initial sending/receiving test loop generating reusable sphinx headers additional structure for receiving ingress packets initial scaffolding for NMv3 agent added validation of x25519 noise key removed unstable call to 'is_multiple_of' remove calls to from_octets as they're unavailable in pre 1.91 additional docs/comments propagating noise information about NM for mixnet routing pass full socket address of the agent into the contract storage feat: store noise keys alongside ip addresses within the contract removed redundant comment ensure NM packets can only go to NM PR review comments added additional docs allow NM to replay packets + fix replay prometheus metrics propagate information about nm agent to connection handler updated nym-node config migration feat: introduced nym-node websocket subscription for keeping updated list of NM agents allow admin to also revoke monitor agents remove agents upon orchestrator removal fixed schema generation and regenerated the contract schema removed rustc restriction on contracts-common added client methods for interacting with the contract added unit tests for contract methods implemented logic of the network monitors contract create initial structure for network monitors contract start mix stress testing topic branch * make nym-node default to the new blockstream rpc/ws node cluster * reduced mixnet-client log severity * set network monitors contract address for mainnet
545 lines
19 KiB
Rust
545 lines
19 KiB
Rust
// Copyright 2024 - Nym Technologies SA <contact@nymtech.net>
|
|
// SPDX-License-Identifier: Apache-2.0
|
|
|
|
use crate::{GAS_PRICE_AMOUNT, mainnet};
|
|
use schemars::JsonSchema;
|
|
use serde::{Deserialize, Serialize};
|
|
use std::ops::Not;
|
|
use url::Url;
|
|
|
|
#[cfg(feature = "env")]
|
|
use crate::var_names;
|
|
#[cfg(feature = "env")]
|
|
use std::env::{VarError, var};
|
|
#[cfg(feature = "env")]
|
|
use std::ffi::OsStr;
|
|
|
|
#[derive(Clone, Debug, Deserialize, Eq, Hash, PartialEq, Serialize, JsonSchema)]
|
|
#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
|
|
pub struct ChainDetails {
|
|
pub bech32_account_prefix: String,
|
|
pub mix_denom: DenomDetailsOwned,
|
|
pub stake_denom: DenomDetailsOwned,
|
|
}
|
|
|
|
impl ChainDetails {
|
|
pub fn mainnet() -> Self {
|
|
ChainDetails {
|
|
bech32_account_prefix: mainnet::BECH32_PREFIX.into(),
|
|
mix_denom: mainnet::MIX_DENOM.into(),
|
|
stake_denom: mainnet::STAKE_DENOM.into(),
|
|
}
|
|
}
|
|
}
|
|
|
|
#[derive(Clone, Debug, Default, Deserialize, Eq, Hash, PartialEq, Serialize, JsonSchema)]
|
|
#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
|
|
pub struct NymContracts {
|
|
pub mixnet_contract_address: Option<String>,
|
|
pub vesting_contract_address: Option<String>,
|
|
#[serde(default)]
|
|
pub performance_contract_address: Option<String>,
|
|
#[serde(default)]
|
|
pub network_monitors_contract_address: Option<String>,
|
|
pub ecash_contract_address: Option<String>,
|
|
pub group_contract_address: Option<String>,
|
|
pub multisig_contract_address: Option<String>,
|
|
pub coconut_dkg_contract_address: Option<String>,
|
|
}
|
|
|
|
// I wanted to use the simpler `NetworkDetails` name, but there's a clash
|
|
// with `NetworkDetails` defined in all.rs...
|
|
#[derive(Clone, Debug, Deserialize, Eq, Hash, PartialEq, Serialize, JsonSchema)]
|
|
#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
|
|
pub struct NymNetworkDetails {
|
|
pub network_name: String,
|
|
pub chain_details: ChainDetails,
|
|
pub endpoints: Vec<ValidatorDetails>,
|
|
pub contracts: NymContracts,
|
|
pub nym_vpn_api_url: Option<String>,
|
|
pub nym_api_urls: Option<Vec<ApiUrl>>,
|
|
pub nym_vpn_api_urls: Option<Vec<ApiUrl>>,
|
|
}
|
|
|
|
#[derive(Clone, Debug, Deserialize, Eq, Hash, PartialEq, Serialize, JsonSchema)]
|
|
#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
|
|
pub struct ApiUrl {
|
|
/// Expects a string formatted Url
|
|
///
|
|
/// see https://docs.rs/url/latest/url/struct.Url.html
|
|
pub url: String,
|
|
/// Optional alternative equivalent hostnames. Each entry must parse as valid Host
|
|
///
|
|
/// see https://docs.rs/url/latest/url/enum.Host.html
|
|
pub front_hosts: Option<Vec<String>>,
|
|
}
|
|
|
|
impl From<Url> for ApiUrl {
|
|
fn from(value: Url) -> Self {
|
|
ApiUrl {
|
|
url: value.to_string(),
|
|
front_hosts: None,
|
|
}
|
|
}
|
|
}
|
|
|
|
#[derive(Copy, Clone, Debug, Serialize)]
|
|
pub struct ApiUrlConst<'a> {
|
|
pub url: &'a str,
|
|
pub front_hosts: Option<&'a [&'a str]>,
|
|
}
|
|
|
|
impl From<ApiUrlConst<'_>> for ApiUrl {
|
|
fn from(value: ApiUrlConst) -> Self {
|
|
ApiUrl {
|
|
url: value.url.to_string(),
|
|
front_hosts: value
|
|
.front_hosts
|
|
.map(|slice| slice.iter().map(|s| s.to_string()).collect()),
|
|
}
|
|
}
|
|
}
|
|
|
|
// by default we assume the same defaults as mainnet, i.e. same prefixes and denoms
|
|
impl Default for NymNetworkDetails {
|
|
fn default() -> Self {
|
|
NymNetworkDetails::new_mainnet()
|
|
}
|
|
}
|
|
|
|
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(),
|
|
nym_vpn_api_url: Default::default(),
|
|
nym_api_urls: Default::default(),
|
|
nym_vpn_api_urls: Default::default(),
|
|
}
|
|
}
|
|
|
|
#[cfg(feature = "env")]
|
|
pub fn new_from_env() -> Self {
|
|
fn get_optional_env<K: AsRef<OsStr>>(env: K) -> Option<String> {
|
|
match var(env) {
|
|
Ok(var) => {
|
|
if var.is_empty() {
|
|
None
|
|
} else {
|
|
Some(var)
|
|
}
|
|
}
|
|
Err(VarError::NotPresent) => None,
|
|
err => panic!("Unable to set: {err:?}"),
|
|
}
|
|
}
|
|
|
|
let nym_api = var(var_names::NYM_API).expect("nym api not set");
|
|
let nym_api_urls = try_parse_api_urls(var_names::NYM_APIS).unwrap_or(vec![ApiUrl {
|
|
url: nym_api.clone(),
|
|
front_hosts: None,
|
|
}]);
|
|
let nym_vpn_api_urls = try_parse_api_urls(var_names::NYM_VPN_APIS);
|
|
|
|
NymNetworkDetails::new_empty()
|
|
.with_network_name(var(var_names::NETWORK_NAME).expect("network name not set"))
|
|
.with_bech32_account_prefix(
|
|
var(var_names::BECH32_PREFIX).expect("bech32 prefix not set"),
|
|
)
|
|
.with_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"),
|
|
})
|
|
.with_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"),
|
|
})
|
|
.with_additional_validator_endpoint(ValidatorDetails::new(
|
|
var(var_names::NYXD).expect("nyxd validator not set"),
|
|
Some(nym_api.clone()),
|
|
get_optional_env(var_names::NYXD_WEBSOCKET),
|
|
))
|
|
.with_mixnet_contract(get_optional_env(var_names::MIXNET_CONTRACT_ADDRESS))
|
|
.with_vesting_contract(get_optional_env(var_names::VESTING_CONTRACT_ADDRESS))
|
|
.with_ecash_contract(get_optional_env(var_names::ECASH_CONTRACT_ADDRESS))
|
|
.with_group_contract(get_optional_env(var_names::GROUP_CONTRACT_ADDRESS))
|
|
.with_multisig_contract(get_optional_env(var_names::MULTISIG_CONTRACT_ADDRESS))
|
|
.with_coconut_dkg_contract(get_optional_env(var_names::COCONUT_DKG_CONTRACT_ADDRESS))
|
|
.with_performance_contract(get_optional_env(var_names::PERFORMANCE_CONTRACT_ADDRESS))
|
|
.with_network_monitors_contract(get_optional_env(
|
|
var_names::NETWORK_MONITORS_CONTRACT_ADDRESS,
|
|
))
|
|
.with_nym_vpn_api_url(get_optional_env(var_names::NYM_VPN_API))
|
|
.with_nym_vpn_api_urls(nym_vpn_api_urls)
|
|
.with_nym_api_urls(nym_api_urls)
|
|
}
|
|
|
|
pub fn new_mainnet() -> Self {
|
|
fn parse_optional_str(raw: &str) -> Option<String> {
|
|
raw.is_empty().not().then(|| raw.into())
|
|
}
|
|
|
|
// Consider caching this process (lazy static)
|
|
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,
|
|
),
|
|
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,
|
|
),
|
|
},
|
|
nym_vpn_api_url: parse_optional_str(mainnet::NYM_VPN_API),
|
|
nym_api_urls: Some(mainnet::NYM_APIS.iter().copied().map(Into::into).collect()),
|
|
nym_vpn_api_urls: Some(
|
|
mainnet::NYM_VPN_APIS
|
|
.iter()
|
|
.copied()
|
|
.map(Into::into)
|
|
.collect(),
|
|
),
|
|
}
|
|
}
|
|
|
|
#[rustfmt::skip]
|
|
#[cfg(feature = "env")]
|
|
pub fn export_to_env(self) {
|
|
use crate::var_names;
|
|
use std::env::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 = self.nym_api_urls();
|
|
let nym_vpn_api_urls = self.nym_vpn_api_urls();
|
|
|
|
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::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_API, self.nym_vpn_api_url);
|
|
set_optional_var(var_names::NYM_VPN_APIS, nym_vpn_api_urls);
|
|
set_optional_var(var_names::NYM_APIS, nym_api_urls);
|
|
|
|
}
|
|
}
|
|
|
|
pub fn default_gas_price_amount(&self) -> f64 {
|
|
GAS_PRICE_AMOUNT
|
|
}
|
|
|
|
#[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_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_nym_vpn_api_url<S: Into<String>>(mut self, endpoint: Option<S>) -> Self {
|
|
self.nym_vpn_api_url = endpoint.map(Into::into);
|
|
self
|
|
}
|
|
|
|
pub fn set_nym_api_urls<U: Into<ApiUrl>>(&mut self, urls: Vec<U>) {
|
|
self.nym_api_urls = Some(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
|
|
}
|
|
|
|
#[must_use]
|
|
pub fn with_nym_vpn_api_urls(mut self, urls: Option<Vec<ApiUrl>>) -> Self {
|
|
self.nym_vpn_api_urls = urls;
|
|
self
|
|
}
|
|
|
|
pub fn nym_vpn_api_url(&self) -> Option<Url> {
|
|
self.nym_vpn_api_url.as_ref().map(|url| {
|
|
url.parse()
|
|
.expect("the provided nym-vpn api url is invalid!")
|
|
})
|
|
}
|
|
|
|
#[cfg(feature = "env")]
|
|
fn nym_api_urls(&self) -> Option<String> {
|
|
serde_json::to_string(self.nym_api_urls.as_deref()?)
|
|
.inspect_err(|e| tracing::warn!("failed to serialize nym_api_urls for env: {e}"))
|
|
.ok()
|
|
}
|
|
|
|
#[cfg(feature = "env")]
|
|
fn nym_vpn_api_urls(&self) -> Option<String> {
|
|
serde_json::to_string(self.nym_vpn_api_urls.as_deref()?)
|
|
.inspect_err(|e| tracing::warn!("failed to serialize nym_vpn_api_urls for env: {e}"))
|
|
.ok()
|
|
}
|
|
}
|
|
|
|
#[cfg(feature = "env")]
|
|
fn try_parse_api_urls(k: impl AsRef<OsStr>) -> Option<Vec<ApiUrl>> {
|
|
let raw = var(k).ok()?;
|
|
serde_json::from_str(&raw)
|
|
.inspect_err(|e| tracing::warn!("failed to parse api urls from env \"{raw:?}\": {e}"))
|
|
.ok()
|
|
}
|
|
|
|
#[derive(Debug, Copy, Serialize, Deserialize, Clone, PartialEq, Eq)]
|
|
pub struct DenomDetails {
|
|
pub base: &'static str,
|
|
pub display: &'static str,
|
|
// i.e. display_amount * 10^display_exponent = base_amount
|
|
pub display_exponent: u32,
|
|
}
|
|
|
|
impl DenomDetails {
|
|
pub const fn new(base: &'static str, display: &'static str, display_exponent: u32) -> Self {
|
|
DenomDetails {
|
|
base,
|
|
display,
|
|
display_exponent,
|
|
}
|
|
}
|
|
}
|
|
|
|
#[derive(Debug, Serialize, Deserialize, Hash, Clone, PartialEq, Eq, JsonSchema)]
|
|
#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
|
|
pub struct DenomDetailsOwned {
|
|
pub base: String,
|
|
pub display: String,
|
|
// i.e. display_amount * 10^display_exponent = base_amount
|
|
pub display_exponent: u32,
|
|
}
|
|
|
|
impl From<DenomDetails> for DenomDetailsOwned {
|
|
fn from(details: DenomDetails) -> Self {
|
|
DenomDetailsOwned {
|
|
base: details.base.to_owned(),
|
|
display: details.display.to_owned(),
|
|
display_exponent: details.display_exponent,
|
|
}
|
|
}
|
|
}
|
|
|
|
impl DenomDetailsOwned {
|
|
pub fn base_only(base: String) -> Self {
|
|
DenomDetailsOwned {
|
|
base: base.clone(),
|
|
display: base,
|
|
display_exponent: 0,
|
|
}
|
|
}
|
|
}
|
|
|
|
#[derive(Clone, Debug, Deserialize, Eq, Hash, PartialEq, Serialize, JsonSchema)]
|
|
#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
|
|
pub struct ValidatorDetails {
|
|
// it is assumed those values are always valid since they're being provided in our defaults file
|
|
pub nyxd_url: String,
|
|
//
|
|
pub websocket_url: Option<String>,
|
|
|
|
// Right now api_url is optional as we are not running the api reliably on all validators
|
|
// however, later on it should be a mandatory field
|
|
pub api_url: Option<String>,
|
|
// TODO: I'd argue this one should also have a field like `gas_price` since its a validator-specific setting
|
|
}
|
|
|
|
impl ValidatorDetails {
|
|
pub fn new<S: Into<String>>(nyxd_url: S, api_url: Option<S>, websocket_url: Option<S>) -> Self {
|
|
ValidatorDetails {
|
|
nyxd_url: nyxd_url.into(),
|
|
websocket_url: websocket_url.map(Into::into),
|
|
api_url: api_url.map(Into::into),
|
|
}
|
|
}
|
|
|
|
pub fn new_nyxd_only<S: Into<String>>(nyxd_url: S) -> Self {
|
|
ValidatorDetails {
|
|
nyxd_url: nyxd_url.into(),
|
|
websocket_url: None,
|
|
api_url: None,
|
|
}
|
|
}
|
|
|
|
pub fn nyxd_url(&self) -> Url {
|
|
self.nyxd_url
|
|
.parse()
|
|
.expect("the provided nyxd url is invalid!")
|
|
}
|
|
|
|
pub fn api_url(&self) -> Option<Url> {
|
|
self.api_url
|
|
.as_ref()
|
|
.map(|url| url.parse().expect("the provided api url is invalid!"))
|
|
}
|
|
|
|
pub fn websocket_url(&self) -> Option<Url> {
|
|
self.websocket_url
|
|
.as_ref()
|
|
.map(|url| url.parse().expect("the provided websocket url is invalid!"))
|
|
}
|
|
}
|