e2dd8ac743
* squashing localnet-v2 commits (again) cargo fmt fixes to localnet purge provide path in the error message output args log failed exec print based on tty check-prerequisites cmd checked iptables update basic kernel features check enable ipv6 rules add forwarding rules squashing localnet-v2 commits additional changes propagate custom-dns flag to all run containers remove is_mock from EcashManager another localnet squash unused import chore: remove redundant testnet manager missing impl additional linux fixes command to rebuild container image wait for at least 2 blocks additional node startup fixes added --custom-dns flag to nym node setup add gateway probe + wait for DKG magic file fixed localnet down on linux container ls re-enable state resync additional feature locking macos adjustments working nyxd startup on linux wip linux box wip separating network inspect betweewn macos and linux initial linux feature locking moved all container commands into a single location finally working initial node performance squashing orchestrator commits cleanup fixed condition for naive rearrangement added cache of cosmwasm contracts for speed up on subsequent runs 'down' command refreshing described cache after nodes are bonded nym nodes setup + wip on nym api refresh nodes setup WIP first pass cleanup placeholder for nym-node setup bypassing the dkg further progress on nym-api setup wip: api setup up/down/purge placeholders persisting contract setup data fix contract upload by forcing amd64 container platform wip: contracts setup4 wip: contracts setup3 wip: contracts setup2 wip: contracts setup include network setup init and spawn nyxd build nyxd image in dedicated orchestrator build nyxd image squashed cherry-picked lp changes Bits and bobs to make everything work Title MacOS setup instructions Docker/Container localnet * clippy * fixes on non-unix targets --------- Co-authored-by: durch <durch@users.noreply.github.com>
511 lines
18 KiB
Rust
511 lines
18 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>,
|
|
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>>,
|
|
}
|
|
|
|
#[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_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,
|
|
),
|
|
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_nym_vpn_api_url<S: Into<String>>(mut self, endpoint: Option<S>) -> Self {
|
|
self.nym_vpn_api_url = endpoint.map(Into::into);
|
|
self
|
|
}
|
|
|
|
#[must_use]
|
|
pub fn with_nym_api_urls(mut self, urls: Vec<ApiUrl>) -> Self {
|
|
self.nym_api_urls = Some(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!"))
|
|
}
|
|
}
|