diff --git a/CHANGELOG.md b/CHANGELOG.md index f340a152dd..21bfa1a68b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,6 +11,10 @@ Post 1.0.0 release, the changelog format is based on [Keep a Changelog](https:// - network-requester: added additional Blockstream Green wallet endpoint to `example.allowed.list` ([#1611](https://github.com/nymtech/nym/pull/1611)) - common/ledger: new library for communicating with a Ledger device ([#1640]) +### Fixed + +- validator-api, mixnode, gateway should now prefer values in config.toml over mainnet defaults ([#1645]) + ### Changed - validator-client: made `fee` argument optional for `execute` and `execute_multiple` ([#1541]) @@ -23,6 +27,7 @@ Post 1.0.0 release, the changelog format is based on [Keep a Changelog](https:// [#1585]: https://github.com/nymtech/nym/pull/1585 [#1591]: https://github.com/nymtech/nym/pull/1591 [#1640]: https://github.com/nymtech/nym/pull/1640 +[#1645]: https://github.com/nymtech/nym/pull/1645 ## [nym-binaries-1.0.2](https://github.com/nymtech/nym/tree/nym-binaries-1.0.2) diff --git a/Cargo.lock b/Cargo.lock index 4534ee11cd..69b2e902cd 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -681,6 +681,15 @@ dependencies = [ "unicode-width", ] +[[package]] +name = "completions" +version = "0.1.0" +dependencies = [ + "clap 3.2.8", + "clap_complete", + "clap_complete_fig", +] + [[package]] name = "config" version = "0.1.0" @@ -919,6 +928,7 @@ dependencies = [ "cfg-if 0.1.10", "clap 3.2.8", "coconut-interface", + "completions", "config", "credential-storage", "credentials", @@ -1659,6 +1669,7 @@ dependencies = [ "isocountry", "itertools", "log", + "maxminddb", "mixnet-contract-common", "network-defaults", "okapi", @@ -2632,6 +2643,15 @@ version = "2.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "35e70ee094dc02fd9c13fdad4940090f22dbd6ac7c9e7094a46cf0232a50bc7c" +[[package]] +name = "ipnetwork" +version = "0.18.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4088d739b183546b239688ddbc79891831df421773df95e236daf7867866d355" +dependencies = [ + "serde", +] + [[package]] name = "ipnetwork" version = "0.20.0" @@ -2905,6 +2925,18 @@ version = "0.1.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a3e378b66a060d48947b590737b30a1be76706c8dd7b8ba0f2fe3989c68a853f" +[[package]] +name = "maxminddb" +version = "0.23.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fe2ba61113f9f7a9f0e87c519682d39c43a6f3f79c2cc42c3ba3dda83b1fa334" +dependencies = [ + "ipnetwork 0.18.0", + "log", + "memchr", + "serde", +] + [[package]] name = "maybe-uninit" version = "2.0.0" @@ -3236,6 +3268,7 @@ dependencies = [ "clap 3.2.8", "client-core", "coconut-interface", + "completions", "config", "credential-storage", "credentials", @@ -3275,6 +3308,7 @@ dependencies = [ "clap 3.2.8", "coconut-interface", "colored", + "completions", "config", "credentials", "crypto", @@ -3317,6 +3351,7 @@ dependencies = [ "bs58", "clap 3.2.8", "colored", + "completions", "config", "crypto", "cupid", @@ -3355,9 +3390,10 @@ version = "1.0.2" dependencies = [ "async-trait", "clap 3.2.8", + "completions", "dirs", "futures", - "ipnetwork", + "ipnetwork 0.20.0", "log", "network-defaults", "nymsphinx", @@ -3400,6 +3436,7 @@ dependencies = [ "clap 3.2.8", "client-core", "coconut-interface", + "completions", "config", "credential-storage", "credentials", @@ -3520,7 +3557,9 @@ dependencies = [ "config", "cosmrs", "cosmwasm-std", + "hex-literal", "mixnet-contract-common", + "network-defaults", "nym-types", "serde", "serde_json", diff --git a/Cargo.toml b/Cargo.toml index b6c45da88b..fb3a7d491e 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -63,6 +63,7 @@ members = [ "common/topology", "common/types", "common/wasm-utils", + "common/completions", "explorer-api", "gateway", "gateway/gateway-requests", diff --git a/clients/credential/Cargo.toml b/clients/credential/Cargo.toml index 74311917e6..078ada1f52 100644 --- a/clients/credential/Cargo.toml +++ b/clients/credential/Cargo.toml @@ -9,7 +9,7 @@ edition = "2021" async-trait = "0.1.52" bip39 = "1.0.1" cfg-if = "0.1" -clap = { version = "3.0.10", features = ["cargo", "derive"] } +clap = { version = "3.2", features = ["cargo", "derive"] } pickledb = "0.4.1" rand = "0.7.3" serde = { version = "1.0", features = ["derive"] } @@ -19,6 +19,7 @@ tokio = { version = "1.19.1", features = ["rt-multi-thread", "net", "signal", "m coconut-interface = { path = "../../common/coconut-interface" } config = { path = "../../common/config" } +completions = { path = "../../common/completions" } credentials = { path = "../../common/credentials" } credential-storage = { path = "../../common/credential-storage" } crypto = { path = "../../common/crypto", features = ["rand", "asymmetric", "symmetric", "aes", "hashing"] } diff --git a/clients/credential/src/commands.rs b/clients/credential/src/commands.rs index c586b4a3e8..c281332810 100644 --- a/clients/credential/src/commands.rs +++ b/clients/credential/src/commands.rs @@ -3,6 +3,7 @@ use async_trait::async_trait; use clap::{Args, Subcommand}; +use completions::ArgShell; use pickledb::PickleDb; use rand::rngs::OsRng; use std::str::FromStr; @@ -28,6 +29,12 @@ pub(crate) enum Commands { ListDeposits(ListDeposits), /// Get a credential for a given deposit GetCredential(GetCredential), + + /// Generate shell completions + Completions(ArgShell), + + /// Generate Fig specification + GenerateFigSpec, } #[async_trait] diff --git a/clients/credential/src/main.rs b/clients/credential/src/main.rs index 88e20f3da2..a3d286f09f 100644 --- a/clients/credential/src/main.rs +++ b/clients/credential/src/main.rs @@ -12,6 +12,8 @@ cfg_if::cfg_if! { use commands::{Commands, Execute}; use error::Result; use network_defaults::setup_env; + use clap::CommandFactory; + use completions::fig_generate; use clap::Parser; use pickledb::{PickleDb, PickleDbDumpPolicy, SerializationMethod}; @@ -52,10 +54,14 @@ cfg_if::cfg_if! { ), }; + let bin_name = "nym-credential-client"; + match &args.command { Commands::Deposit(m) => m.execute(&mut db, shared_storage).await?, Commands::ListDeposits(m) => m.execute(&mut db, shared_storage).await?, Commands::GetCredential(m) => m.execute(&mut db, shared_storage).await?, + Commands::Completions(s) => s.generate(&mut crate::Cli::into_app(), bin_name), + Commands::GenerateFigSpec => fig_generate(&mut crate::Cli::into_app(), bin_name) } Ok(()) diff --git a/clients/native/Cargo.toml b/clients/native/Cargo.toml index 0904d7ee01..da9cd342b3 100644 --- a/clients/native/Cargo.toml +++ b/clients/native/Cargo.toml @@ -20,7 +20,7 @@ futures = "0.3" # bunch of futures stuff, however, now that I think about it, it # and the single instance of abortable we have should really be refactored anyway url = "2.2" -clap = { version = "3.2.8", features = ["cargo", "derive"] } +clap = { version = "3.2", features = ["cargo", "derive"] } dirs = "4.0" log = "0.4" # self explanatory pretty_env_logger = "0.4" # for formatting log messages @@ -34,6 +34,7 @@ tokio-tungstenite = "0.14" # websocket client-core = { path = "../client-core" } coconut-interface = { path = "../../common/coconut-interface", optional = true } config = { path = "../../common/config" } +completions = { path = "../../common/completions" } credential-storage = { path = "../../common/credential-storage" } credentials = { path = "../../common/credentials", optional = true } crypto = { path = "../../common/crypto" } diff --git a/clients/native/src/commands/mod.rs b/clients/native/src/commands/mod.rs index 9f95b7945c..fdbf7ff5bf 100644 --- a/clients/native/src/commands/mod.rs +++ b/clients/native/src/commands/mod.rs @@ -2,7 +2,9 @@ // SPDX-License-Identifier: Apache-2.0 use crate::client::config::{Config, SocketType}; +use clap::CommandFactory; use clap::{Parser, Subcommand}; +use completions::{fig_generate, ArgShell}; pub(crate) mod init; pub(crate) mod run; @@ -62,6 +64,12 @@ pub(crate) enum Commands { Run(run::Run), /// Try to upgrade the client Upgrade(upgrade::Upgrade), + + /// Generate shell completions + Completions(ArgShell), + + /// Generate Fig specification + GenerateFigSpec, } // Configuration that can be overridden. @@ -76,10 +84,14 @@ pub(crate) struct OverrideConfig { } pub(crate) async fn execute(args: &Cli) { + let bin_name = "nym-native-client"; + match &args.command { Commands::Init(m) => init::execute(m).await, Commands::Run(m) => run::execute(m).await, Commands::Upgrade(m) => upgrade::execute(m), + Commands::Completions(s) => s.generate(&mut Cli::into_app(), bin_name), + Commands::GenerateFigSpec => fig_generate(&mut Cli::into_app(), bin_name), } } diff --git a/clients/socks5/Cargo.toml b/clients/socks5/Cargo.toml index e9091ea4e8..78325d01d1 100644 --- a/clients/socks5/Cargo.toml +++ b/clients/socks5/Cargo.toml @@ -11,7 +11,7 @@ name = "nym_socks5" path = "src/lib.rs" [dependencies] -clap = { version = "3.2.8", features = ["cargo", "derive"] } +clap = { version = "3.2", features = ["cargo", "derive"] } dirs = "4.0" futures = "0.3" log = "0.4" @@ -27,6 +27,7 @@ url = "2.2" client-core = { path = "../client-core" } coconut-interface = { path = "../../common/coconut-interface", optional = true } config = { path = "../../common/config" } +completions = { path = "../../common/completions" } credential-storage = { path = "../../common/credential-storage" } credentials = { path = "../../common/credentials", optional = true } crypto = { path = "../../common/crypto" } diff --git a/clients/socks5/src/commands/mod.rs b/clients/socks5/src/commands/mod.rs index 651f88bab2..dbed90777f 100644 --- a/clients/socks5/src/commands/mod.rs +++ b/clients/socks5/src/commands/mod.rs @@ -2,7 +2,9 @@ // SPDX-License-Identifier: Apache-2.0 use crate::client::config::Config; +use clap::CommandFactory; use clap::{Parser, Subcommand}; +use completions::{fig_generate, ArgShell}; use config::parse_validators; pub mod init; @@ -63,6 +65,12 @@ pub(crate) enum Commands { Run(run::Run), /// Try to upgrade the client Upgrade(upgrade::Upgrade), + + /// Generate shell completions + Completions(ArgShell), + + /// Generate Fig specification + GenerateFigSpec, } // Configuration that can be overridden. @@ -76,10 +84,14 @@ pub(crate) struct OverrideConfig { } pub(crate) async fn execute(args: &Cli) { + let bin_name = "nym-socks5-client"; + match &args.command { Commands::Init(m) => init::execute(m).await, Commands::Run(m) => run::execute(m).await, Commands::Upgrade(m) => upgrade::execute(m), + Commands::Completions(s) => s.generate(&mut Cli::into_app(), bin_name), + Commands::GenerateFigSpec => fig_generate(&mut Cli::into_app(), bin_name), } } diff --git a/common/client-libs/validator-client/src/connection_tester.rs b/common/client-libs/validator-client/src/connection_tester.rs index 3193fc32e0..dc1acb8cd9 100644 --- a/common/client-libs/validator-client/src/connection_tester.rs +++ b/common/client-libs/validator-client/src/connection_tester.rs @@ -1,12 +1,12 @@ use crate::nymd::error::NymdError; use crate::nymd::{Config as ClientConfig, NymdClient, QueryNymdClient}; use crate::ApiClient; -use network_defaults::all::Network; use crate::nymd::traits::MixnetQueryClient; use colored::Colorize; use core::fmt; use itertools::Itertools; +use network_defaults::NymNetworkDetails; use std::collections::HashMap; use std::hash::BuildHasher; use std::time::Duration; @@ -18,12 +18,12 @@ const CONNECTION_TEST_TIMEOUT_SEC: u64 = 2; // Run connection tests for all specified nymd and api urls. These are all run concurrently. pub async fn run_validator_connection_test( - nymd_urls: impl Iterator, - api_urls: impl Iterator, - mixnet_contract_address: HashMap, + nymd_urls: impl Iterator, + api_urls: impl Iterator, + mixnet_contract_address: HashMap, ) -> ( - HashMap>, - HashMap>, + HashMap>, + HashMap>, ) { // Setup all the clients for the connection tests let connection_test_clients = @@ -46,16 +46,16 @@ pub async fn run_validator_connection_test( } fn setup_connection_tests( - nymd_urls: impl Iterator, - api_urls: impl Iterator, - mixnet_contract_address: HashMap, + nymd_urls: impl Iterator, + api_urls: impl Iterator, + mixnet_contract_address: HashMap, ) -> impl Iterator { let nymd_connection_test_clients = nymd_urls.filter_map(move |(network, url)| { let address = mixnet_contract_address .get(&network) .expect("No configured contract address") .clone(); - let config = ClientConfig::try_from_nym_network_details(&network.details()) + let config = ClientConfig::try_from_nym_network_details(&network) .expect("failed to create valid nymd client config"); if let Ok(mut client) = NymdClient::::connect(config, url.as_str()) { @@ -81,7 +81,7 @@ fn setup_connection_tests( fn extract_and_collect_results_into_map( connection_results: &[ConnectionResult], url_type: &UrlType, -) -> HashMap> { +) -> HashMap> { connection_results .iter() .filter(|c| &c.url_type() == url_type) @@ -93,7 +93,7 @@ fn extract_and_collect_results_into_map( } async fn test_nymd_connection( - network: Network, + network: NymNetworkDetails, url: &Url, client: &NymdClient, ) -> ConnectionResult { @@ -105,56 +105,47 @@ async fn test_nymd_connection( { Ok(Err(NymdError::TendermintError(e))) => { // If we get a tendermint-rpc error, we classify the node as not contactable - log::debug!( - "Checking: nymd_url: {network}: {url}: {}: {}", - "failed".red(), - e - ); + log::debug!("Checking: nymd_url: {url}: {}: {}", "failed".red(), e); false } Ok(Err(NymdError::AbciError(code, log))) => { // We accept the mixnet contract not found as ok from a connection standpoint. This happens // for example on a pre-launch network. log::debug!( - "Checking: nymd_url: {network}: {url}: {}, but with abci error: {code}: {log}", + "Checking: nymd_url: {url}: {}, but with abci error: {code}: {log}", "success".green() ); code == 18 } Ok(Err(error @ NymdError::NoContractAddressAvailable)) => { - log::debug!( - "Checking: nymd_url: {network}: {url}: {}: {error}", - "failed".red() - ); + log::debug!("Checking: nymd_url: {url}: {}: {error}", "failed".red()); false } Ok(Err(e)) => { // For any other error, we're optimistic and just try anyway. log::debug!( - "Checking: nymd_url: {network}: {url}: {}, but with error: {e}", + "Checking: nymd_url: {url}: {}, but with error: {e}", "success".green() ); true } Ok(Ok(_)) => { - log::debug!( - "Checking: nymd_url: {network}: {url}: {}", - "success".green() - ); + log::debug!("Checking: nymd_url: {url}: {}", "success".green()); true } Err(e) => { - log::debug!( - "Checking: nymd_url: {network}: {url}: {}: {e}", - "failed".red() - ); + log::debug!("Checking: nymd_url: {url}: {}: {e}", "failed".red()); false } }; ConnectionResult::Nymd(network, url.clone(), result) } -async fn test_api_connection(network: Network, url: &Url, client: &ApiClient) -> ConnectionResult { +async fn test_api_connection( + network: NymNetworkDetails, + url: &Url, + client: &ApiClient, +) -> ConnectionResult { let result = match timeout( Duration::from_secs(CONNECTION_TEST_TIMEOUT_SEC), client.get_cached_mixnodes(), @@ -162,21 +153,15 @@ async fn test_api_connection(network: Network, url: &Url, client: &ApiClient) -> .await { Ok(Ok(_)) => { - log::debug!("Checking: api_url: {network}: {url}: {}", "success".green()); + log::debug!("Checking: api_url: {url}: {}", "success".green()); true } Ok(Err(e)) => { - log::debug!( - "Checking: api_url: {network}: {url}: {}: {e}", - "failed".red() - ); + log::debug!("Checking: api_url: {url}: {}: {e}", "failed".red()); false } Err(e) => { - log::debug!( - "Checking: api_url: {network}: {url}: {}: {e}", - "failed".red() - ); + log::debug!("Checking: api_url: {url}: {}: {e}", "failed".red()); false } }; @@ -184,8 +169,8 @@ async fn test_api_connection(network: Network, url: &Url, client: &ApiClient) -> } enum ClientForConnectionTest { - Nymd(Network, Url, Box>), - Api(Network, Url, ApiClient), + Nymd(NymNetworkDetails, Url, Box>), + Api(NymNetworkDetails, Url, ApiClient), } impl ClientForConnectionTest { @@ -218,12 +203,12 @@ impl fmt::Display for UrlType { #[derive(Debug)] enum ConnectionResult { - Nymd(Network, Url, bool), - Api(Network, Url, bool), + Nymd(NymNetworkDetails, Url, bool), + Api(NymNetworkDetails, Url, bool), } impl ConnectionResult { - fn result(&self) -> (&Network, &Url, &bool) { + fn result(&self) -> (&NymNetworkDetails, &Url, &bool) { match self { ConnectionResult::Nymd(network, url, result) | ConnectionResult::Api(network, url, result) => (network, url, result), @@ -240,11 +225,8 @@ impl ConnectionResult { impl fmt::Display for ConnectionResult { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - let (network, url, result) = self.result(); + let (_network, url, result) = self.result(); let url_type = self.url_type(); - write!( - f, - "{network}: {url}: {url_type}: connection is successful: {result}" - ) + write!(f, "{url}: {url_type}: connection is successful: {result}") } } diff --git a/common/client-libs/validator-client/src/nymd/mod.rs b/common/client-libs/validator-client/src/nymd/mod.rs index 1a508aaa2f..c062399357 100644 --- a/common/client-libs/validator-client/src/nymd/mod.rs +++ b/common/client-libs/validator-client/src/nymd/mod.rs @@ -149,7 +149,7 @@ impl NymdClient { // maybe the wallet could be made into a generic, but for now, let's just have this one implementation pub fn connect_with_signer( config: Config, - network: config::defaults::all::Network, + network: config::defaults::NymNetworkDetails, endpoint: U, signer: DirectSecp256k1HdWallet, gas_price: Option, @@ -157,7 +157,7 @@ impl NymdClient { where U: TryInto, { - let denom = network.base_mix_denom(); + let denom = network.chain_details.mix_denom.base; let client_address = signer .try_derive_accounts()? .into_iter() diff --git a/common/client-libs/validator-client/src/nymd/wallet/mod.rs b/common/client-libs/validator-client/src/nymd/wallet/mod.rs index b22dfdd6f4..c78aacde70 100644 --- a/common/client-libs/validator-client/src/nymd/wallet/mod.rs +++ b/common/client-libs/validator-client/src/nymd/wallet/mod.rs @@ -207,8 +207,9 @@ impl DirectSecp256k1HdWalletBuilder { #[cfg(test)] mod tests { + use network_defaults::NymNetworkDetails; + use super::*; - use network_defaults::all::Network::*; #[test] fn generating_account_addresses() { @@ -218,7 +219,9 @@ mod tests { "acquire rebel spot skin gun such erupt pull swear must define ill chief turtle today flower chunk truth battle claw rigid detail gym feel", "step income throw wheat mobile ship wave drink pool sudden upset jaguar bar globe rifle spice frost bless glimpse size regular carry aspect ball" ]; - let prefix = MAINNET.bech32_prefix(); + let prefix = NymNetworkDetails::new_mainnet() + .chain_details + .bech32_account_prefix; let addrs = vec![ "n1jw6mp7d5xqc7w6xm79lha27glmd0vdt3l9artf", diff --git a/common/commands/src/validator/mixnet/delegators/query_for_delegations.rs b/common/commands/src/validator/mixnet/delegators/query_for_delegations.rs index cdf0ef4140..52d43ec553 100644 --- a/common/commands/src/validator/mixnet/delegators/query_for_delegations.rs +++ b/common/commands/src/validator/mixnet/delegators/query_for_delegations.rs @@ -63,7 +63,7 @@ async fn print_delegations(delegations: Vec, client: &SigningClientW for delegation in delegations { table.add_row(vec![ to_iso_timestamp(delegation.height as u32, client).await, - delegation.node_id.to_string(), + delegation.mix_id.to_string(), pretty_cosmwasm_coin(&delegation.amount), delegation .proxy diff --git a/common/completions/Cargo.toml b/common/completions/Cargo.toml new file mode 100644 index 0000000000..ace1f76e7f --- /dev/null +++ b/common/completions/Cargo.toml @@ -0,0 +1,11 @@ +[package] +name = "completions" +version = "0.1.0" +edition = "2021" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +clap = { version = "3.2", features = ["derive"] } +clap_complete = "3.2" +clap_complete_fig = "3.2" \ No newline at end of file diff --git a/common/completions/src/lib.rs b/common/completions/src/lib.rs new file mode 100644 index 0000000000..8526c18d4f --- /dev/null +++ b/common/completions/src/lib.rs @@ -0,0 +1,58 @@ +use clap::builder::Command; +use clap::clap_derive::ArgEnum; +use clap::Args; +use clap_complete::generator::generate; +use clap_complete::Shell as ClapShell; +use std::io; + +pub fn fig_generate(command: &mut Command, name: &str) { + clap_complete::generate( + clap_complete_fig::Fig, + command, + name, + &mut std::io::stdout(), + ) +} + +#[derive(ArgEnum, Copy, Clone)] +pub enum Shell { + Bash, + Elvish, + Fish, + PowerShell, + Zsh, +} + +#[derive(Args, Copy, Clone)] +pub struct ArgShell { + #[clap(arg_enum, value_name = "SHELL")] + shell: Shell, +} + +impl ArgShell { + pub fn generate(&self, command: &mut Command, name: &str) { + self.shell.generate(command, name) + } +} + +impl Shell { + pub fn generate(&self, command: &mut Command, name: &str) { + match &self { + Self::Bash => { + generate(ClapShell::Bash, command, name, &mut io::stdout()); + } + Self::Elvish => { + generate(ClapShell::Elvish, command, name, &mut io::stdout()); + } + Self::Fish => { + generate(ClapShell::Fish, command, name, &mut io::stdout()); + } + Self::PowerShell => { + generate(ClapShell::PowerShell, command, name, &mut io::stdout()); + } + Self::Zsh => { + generate(ClapShell::Zsh, command, name, &mut io::stdout()); + } + } + } +} diff --git a/common/cosmwasm-smart-contracts/mixnet-contract/src/delegation.rs b/common/cosmwasm-smart-contracts/mixnet-contract/src/delegation.rs index 7fbff39f98..a1b8f3f326 100644 --- a/common/cosmwasm-smart-contracts/mixnet-contract/src/delegation.rs +++ b/common/cosmwasm-smart-contracts/mixnet-contract/src/delegation.rs @@ -34,12 +34,13 @@ pub struct Delegation { pub owner: Addr, /// Id of the MixNode that this delegation was performed against. - pub node_id: NodeId, + #[serde(alias = "node_id")] + pub mix_id: NodeId, // Note to UI/UX devs: there's absolutely no point in displaying this value to the users, // it would serve them no purpose. It's only used for calculating rewards /// Value of the "unit delegation" associated with the mixnode at the time of delegation. - #[serde(rename = "crr")] + #[serde(alias = "crr")] pub cumulative_reward_ratio: Decimal, /// Original delegation amount. Note that it is never mutated as delegation accumulates rewards. @@ -55,7 +56,7 @@ pub struct Delegation { impl Delegation { pub fn new( owner: Addr, - node_id: NodeId, + mix_id: NodeId, cumulative_reward_ratio: Decimal, amount: Coin, height: u64, @@ -63,7 +64,7 @@ impl Delegation { ) -> Self { Delegation { owner, - node_id, + mix_id, cumulative_reward_ratio, amount, height, @@ -72,20 +73,20 @@ impl Delegation { } pub fn generate_storage_key( - node_id: NodeId, + mix_id: NodeId, owner_address: &Addr, proxy: Option<&Addr>, ) -> StorageKey { - (node_id, generate_owner_storage_subkey(owner_address, proxy)) + (mix_id, generate_owner_storage_subkey(owner_address, proxy)) } // this function might seem a bit redundant, but I'd rather explicitly keep it around in case // some types change in the future pub fn generate_storage_key_with_subkey( - node_id: NodeId, + mix_id: NodeId, owner_proxy_subkey: OwnerProxySubKey, ) -> StorageKey { - (node_id, owner_proxy_subkey) + (mix_id, owner_proxy_subkey) } pub fn dec_amount(&self) -> Decimal { @@ -99,7 +100,7 @@ impl Delegation { } pub fn storage_key(&self) -> StorageKey { - Self::generate_storage_key(self.node_id, &self.owner, self.proxy.as_ref()) + Self::generate_storage_key(self.mix_id, &self.owner, self.proxy.as_ref()) } } diff --git a/common/cosmwasm-smart-contracts/mixnet-contract/src/error.rs b/common/cosmwasm-smart-contracts/mixnet-contract/src/error.rs index 3808df55d8..2977bbf48f 100644 --- a/common/cosmwasm-smart-contracts/mixnet-contract/src/error.rs +++ b/common/cosmwasm-smart-contracts/mixnet-contract/src/error.rs @@ -31,8 +31,8 @@ pub enum MixnetContractError { #[error("Not enough funds sent for node delegation. (received {received}, minimum {minimum})")] InsufficientDelegation { received: Coin, minimum: Coin }, - #[error("Mixnode ({id}) does not exist")] - MixNodeBondNotFound { id: NodeId }, + #[error("Mixnode ({mix_id}) does not exist")] + MixNodeBondNotFound { mix_id: NodeId }, #[error("{owner} does not seem to own any mixnodes")] NoAssociatedMixNodeBond { owner: Addr }, @@ -83,23 +83,23 @@ pub enum MixnetContractError { epoch_end: i64, }, - #[error("Mixnode {node_id} has already been rewarded during the current rewarding epoch ({absolute_epoch_id})")] + #[error("Mixnode {mix_id} has already been rewarded during the current rewarding epoch ({absolute_epoch_id})")] MixnodeAlreadyRewarded { - node_id: NodeId, + mix_id: NodeId, absolute_epoch_id: u32, }, - #[error("Mixnode {node_id} hasn't been selected to the rewarding set in this epoch ({absolute_epoch_id})")] + #[error("Mixnode {mix_id} hasn't been selected to the rewarding set in this epoch ({absolute_epoch_id})")] MixnodeNotInRewardedSet { - node_id: NodeId, + mix_id: NodeId, absolute_epoch_id: u32, }, - #[error("Mixnode {node_id} is currently in the process of unbonding")] - MixnodeIsUnbonding { node_id: NodeId }, + #[error("Mixnode {mix_id} is currently in the process of unbonding")] + MixnodeIsUnbonding { mix_id: NodeId }, - #[error("Mixnode {node_id} has already unbonded")] - MixnodeHasUnbonded { node_id: NodeId }, + #[error("Mixnode {mix_id} has already unbonded")] + MixnodeHasUnbonded { mix_id: NodeId }, #[error("The contract has ended up in a state that was deemed impossible: {comment}")] InconsistentState { comment: String }, @@ -134,6 +134,6 @@ pub enum MixnetContractError { #[error("Received unexpected value for the rewarded set. Got: {received}, expected at most: {expected}")] UnexpectedRewardedSetSize { received: u32, expected: u32 }, - #[error("Mixnode {node_id} appears multiple times in the provided rewarded set update!")] - DuplicateRewardedSetNode { node_id: NodeId }, + #[error("Mixnode {mix_id} appears multiple times in the provided rewarded set update!")] + DuplicateRewardedSetNode { mix_id: NodeId }, } diff --git a/common/cosmwasm-smart-contracts/mixnet-contract/src/events.rs b/common/cosmwasm-smart-contracts/mixnet-contract/src/events.rs index 2804f8660f..72e47e6de9 100644 --- a/common/cosmwasm-smart-contracts/mixnet-contract/src/events.rs +++ b/common/cosmwasm-smart-contracts/mixnet-contract/src/events.rs @@ -6,7 +6,9 @@ use crate::reward_params::{IntervalRewardParams, IntervalRewardingParamsUpdate}; use crate::rewarding::RewardDistribution; use crate::{ContractStateParams, IdentityKeyRef, Interval, Layer, NodeId}; pub use contracts_common::events::*; -use cosmwasm_std::{Addr, Coin, Event}; +use cosmwasm_std::{Addr, Coin, Decimal, Event}; + +pub const EVENT_VERSION_PREFIX: &str = "v2_"; pub enum MixnetEventType { MixnodeBonding, @@ -47,7 +49,7 @@ impl From for String { impl ToString for MixnetEventType { fn to_string(&self) -> String { - match self { + let event_name = match self { MixnetEventType::MixnodeBonding => "mixnode_bonding", MixnetEventType::GatewayBonding => "gateway_bonding", MixnetEventType::GatewayUnbonding => "gateway_unbonding", @@ -78,8 +80,9 @@ impl ToString for MixnetEventType { MixnetEventType::PendingIntervalConfigUpdate => "pending_interval_config_update", MixnetEventType::IntervalConfigUpdate => "interval_config_update", MixnetEventType::DelegationOnUnbonding => "delegation_on_unbonding_node", - } - .into() + }; + + format!("{}{}", EVENT_VERSION_PREFIX, event_name) } } @@ -96,7 +99,7 @@ pub const DELEGATION_TARGET_KEY: &str = "delegation_target"; pub const DELEGATION_HEIGHT_KEY: &str = "delegation_latest_block_height"; // bonding/unbonding -pub const NODE_ID_KEY: &str = "node_id"; +pub const MIX_ID_KEY: &str = "mix_id"; pub const NODE_IDENTITY_KEY: &str = "identity"; pub const ASSIGNED_LAYER_KEY: &str = "assigned_layer"; @@ -125,6 +128,8 @@ pub const DELEGATES_REWARD_KEY: &str = "delegates_reward"; pub const APPROXIMATE_TIME_LEFT_SECS_KEY: &str = "approximate_time_left_secs"; pub const INTERVAL_REWARDING_PARAMS_UPDATE_KEY: &str = "interval_rewarding_params_update"; pub const UPDATED_INTERVAL_REWARDING_PARAMS_KEY: &str = "updated_interval_rewarding_params"; +pub const PRIOR_DELEGATES_KEY: &str = "prior_delegates"; +pub const PRIOR_UNIT_DELEGATION_KEY: &str = "prior_unit_delegation"; pub const DISTRIBUTED_DELEGATION_REWARDS_KEY: &str = "distributed_delegation_rewards"; pub const FURTHER_DELEGATIONS_TO_REWARD_KEY: &str = "further_delegations"; @@ -197,7 +202,7 @@ pub fn new_withdraw_operator_reward_event( .add_attribute(OWNER_KEY, owner.as_str()) .add_optional_attribute(PROXY_KEY, proxy.as_ref()) .add_attribute(AMOUNT_KEY, amount.to_string()) - .add_attribute(NODE_ID_KEY, mix_id.to_string()) + .add_attribute(MIX_ID_KEY, mix_id.to_string()) } pub fn new_withdraw_delegator_reward_event( @@ -264,7 +269,7 @@ pub fn new_undelegation_event(delegator: &Addr, proxy: &Option, mix_id: No Event::new(MixnetEventType::Undelegation) .add_attribute(DELEGATOR_KEY, delegator) .add_optional_attribute(PROXY_KEY, proxy.as_ref()) - .add_attribute(NODE_ID_KEY, mix_id.to_string()) + .add_attribute(MIX_ID_KEY, mix_id.to_string()) } pub fn new_pending_undelegation_event( @@ -275,7 +280,7 @@ pub fn new_pending_undelegation_event( Event::new(MixnetEventType::PendingUndelegation) .add_attribute(DELEGATOR_KEY, delegator) .add_optional_attribute(PROXY_KEY, proxy.as_ref()) - .add_attribute(NODE_ID_KEY, mix_id.to_string()) + .add_attribute(MIX_ID_KEY, mix_id.to_string()) } pub fn new_gateway_bonding_event( @@ -309,12 +314,12 @@ pub fn new_mixnode_bonding_event( proxy: &Option, amount: &Coin, identity: IdentityKeyRef<'_>, - node_id: NodeId, + mix_id: NodeId, assigned_layer: Layer, ) -> Event { // coin implements Display trait and we use that implementation here Event::new(MixnetEventType::MixnodeBonding) - .add_attribute(NODE_ID_KEY, node_id.to_string()) + .add_attribute(MIX_ID_KEY, mix_id.to_string()) .add_attribute(NODE_IDENTITY_KEY, identity) .add_attribute(OWNER_KEY, owner) .add_optional_attribute(PROXY_KEY, proxy.as_ref()) @@ -322,55 +327,55 @@ pub fn new_mixnode_bonding_event( .add_attribute(AMOUNT_KEY, amount.to_string()) } -pub fn new_mixnode_unbonding_event(node_id: NodeId) -> Event { - Event::new(MixnetEventType::MixnodeUnbonding).add_attribute(NODE_ID_KEY, node_id.to_string()) +pub fn new_mixnode_unbonding_event(mix_id: NodeId) -> Event { + Event::new(MixnetEventType::MixnodeUnbonding).add_attribute(MIX_ID_KEY, mix_id.to_string()) } pub fn new_pending_mixnode_unbonding_event( owner: &Addr, proxy: &Option, identity: IdentityKeyRef<'_>, - node_id: NodeId, + mix_id: NodeId, ) -> Event { Event::new(MixnetEventType::PendingMixnodeUnbonding) - .add_attribute(NODE_ID_KEY, node_id.to_string()) + .add_attribute(MIX_ID_KEY, mix_id.to_string()) .add_attribute(NODE_IDENTITY_KEY, identity) .add_attribute(OWNER_KEY, owner) .add_optional_attribute(PROXY_KEY, proxy.as_ref()) } pub fn new_mixnode_config_update_event( - node_id: NodeId, + mix_id: NodeId, owner: &Addr, proxy: &Option, update: &MixNodeConfigUpdate, ) -> Event { Event::new(MixnetEventType::MixnodeConfigUpdate) - .add_attribute(NODE_ID_KEY, node_id.to_string()) + .add_attribute(MIX_ID_KEY, mix_id.to_string()) .add_attribute(OWNER_KEY, owner) .add_optional_attribute(PROXY_KEY, proxy.as_ref()) .add_attribute(UPDATED_MIXNODE_CONFIG_KEY, update.to_inline_json()) } pub fn new_mixnode_pending_cost_params_update_event( - node_id: NodeId, + mix_id: NodeId, owner: &Addr, proxy: &Option, new_costs: &MixNodeCostParams, ) -> Event { Event::new(MixnetEventType::PendingMixnodeCostParamsUpdate) - .add_attribute(NODE_ID_KEY, node_id.to_string()) + .add_attribute(MIX_ID_KEY, mix_id.to_string()) .add_attribute(OWNER_KEY, owner) .add_optional_attribute(PROXY_KEY, proxy.as_ref()) .add_attribute(UPDATED_MIXNODE_COST_PARAMS_KEY, new_costs.to_inline_json()) } pub fn new_mixnode_cost_params_update_event( - node_id: NodeId, + mix_id: NodeId, new_costs: &MixNodeCostParams, ) -> Event { Event::new(MixnetEventType::MixnodeCostParamsUpdate) - .add_attribute(NODE_ID_KEY, node_id.to_string()) + .add_attribute(MIX_ID_KEY, mix_id.to_string()) .add_attribute(UPDATED_MIXNODE_COST_PARAMS_KEY, new_costs.to_inline_json()) } @@ -426,41 +431,41 @@ pub fn new_settings_update_event( event } -pub fn new_not_found_mix_operator_rewarding_event(interval: Interval, node_id: NodeId) -> Event { +pub fn new_not_found_mix_operator_rewarding_event(interval: Interval, mix_id: NodeId) -> Event { Event::new(MixnetEventType::MixnodeRewarding) .add_attribute( INTERVAL_KEY, interval.current_epoch_absolute_id().to_string(), ) - .add_attribute(NODE_ID_KEY, node_id.to_string()) + .add_attribute(MIX_ID_KEY, mix_id.to_string()) .add_attribute(NO_REWARD_REASON_KEY, BOND_NOT_FOUND_VALUE) } -pub fn new_zero_uptime_mix_operator_rewarding_event(interval: Interval, node_id: NodeId) -> Event { +pub fn new_zero_uptime_mix_operator_rewarding_event(interval: Interval, mix_id: NodeId) -> Event { Event::new(MixnetEventType::MixnodeRewarding) .add_attribute( INTERVAL_KEY, interval.current_epoch_absolute_id().to_string(), ) - .add_attribute(NODE_ID_KEY, node_id.to_string()) + .add_attribute(MIX_ID_KEY, mix_id.to_string()) .add_attribute(NO_REWARD_REASON_KEY, ZERO_PERFORMANCE_VALUE) } pub fn new_mix_rewarding_event( interval: Interval, - node_id: NodeId, + mix_id: NodeId, reward_distribution: RewardDistribution, + prior_delegates: Decimal, + prior_unit_delegation: Decimal, ) -> Event { Event::new(MixnetEventType::MixnodeRewarding) - // TODO: to calculate Timmy's (delegator) reward at this time - // emit: - // - unit delegation BEFORE rewarding (to determine Timmy's state before rewarding happened) - // - total delegation BEFORE rewarding .add_attribute( INTERVAL_KEY, interval.current_epoch_absolute_id().to_string(), ) - .add_attribute(NODE_ID_KEY, node_id.to_string()) + .add_attribute(PRIOR_DELEGATES_KEY, prior_delegates.to_string()) + .add_attribute(PRIOR_UNIT_DELEGATION_KEY, prior_unit_delegation.to_string()) + .add_attribute(MIX_ID_KEY, mix_id.to_string()) .add_attribute( OPERATOR_REWARD_KEY, reward_distribution.operator.to_string(), diff --git a/common/cosmwasm-smart-contracts/mixnet-contract/src/mixnode.rs b/common/cosmwasm-smart-contracts/mixnet-contract/src/mixnode.rs index 898cba50cf..d8ca88fa2a 100644 --- a/common/cosmwasm-smart-contracts/mixnet-contract/src/mixnode.rs +++ b/common/cosmwasm-smart-contracts/mixnet-contract/src/mixnode.rs @@ -78,34 +78,34 @@ impl MixNodeDetails { #[derive(Clone, Debug, Deserialize, PartialEq, Serialize, JsonSchema)] pub struct MixNodeRewarding { /// Information provided by the operator that influence the cost function. - #[serde(rename = "cp")] + #[serde(alias = "cp")] pub cost_params: MixNodeCostParams, /// Total pledge and compounded reward earned by the node operator. - #[serde(rename = "op")] + #[serde(alias = "op")] pub operator: Decimal, /// Total delegation and compounded reward earned by all node delegators. - #[serde(rename = "dg")] + #[serde(alias = "dg")] pub delegates: Decimal, /// Cumulative reward earned by the "unit delegation" since the block 0. - #[serde(rename = "tur")] + #[serde(alias = "tur")] pub total_unit_reward: Decimal, /// Value of the theoretical "unit delegation" that has delegated to this mixnode at block 0. - #[serde(rename = "ud")] + #[serde(alias = "ud")] pub unit_delegation: Decimal, /// Marks the epoch when this node was last rewarded so that we wouldn't accidentally attempt /// to reward it multiple times in the same epoch. - #[serde(rename = "le")] + #[serde(alias = "le")] pub last_rewarded_epoch: EpochId, // technically we don't need that field to determine reward magnitude or anything // but it saves on extra queries to determine if we're removing the final delegation // (so that we could zero the field correctly) - #[serde(rename = "uqd")] + #[serde(alias = "uqd")] pub unique_delegations: u32, } diff --git a/common/network-defaults/src/all.rs b/common/network-defaults/src/all.rs deleted file mode 100644 index 91188326a0..0000000000 --- a/common/network-defaults/src/all.rs +++ /dev/null @@ -1,251 +0,0 @@ -// Copyright 2021-2022 - Nym Technologies SA -// SPDX-License-Identifier: Apache-2.0 - -use crate::{ - DefaultNetworkDetails, DenomDetailsOwned, NymNetworkDetails, ValidatorDetails, - MAINNET_DEFAULTS, QA_DEFAULTS, SANDBOX_DEFAULTS, -}; -use serde::{Deserialize, Serialize}; -use std::{collections::HashMap, fmt, str::FromStr}; -use thiserror::Error; - -#[derive(Error, Debug)] -pub enum NetworkDefaultsError { - #[error("The provided network was invalid")] - MalformedNetworkProvided(String), -} - -// the reason for allowing it is that this is just a temporary solution -#[allow(clippy::large_enum_variant)] -#[derive(Clone, Debug, Deserialize, Eq, Hash, PartialEq, Serialize)] -pub enum Network { - QA, - SANDBOX, - MAINNET, - CUSTOM { details: NymNetworkDetails }, -} - -impl Network { - pub fn new_custom(details: NymNetworkDetails) -> Self { - Network::CUSTOM { details } - } - - pub fn details(&self) -> NymNetworkDetails { - match self { - Self::QA => (&*QA_DEFAULTS).into(), - Self::SANDBOX => (&*SANDBOX_DEFAULTS).into(), - Self::MAINNET => (&*MAINNET_DEFAULTS).into(), - // I dislike the clone here, but for compatibility reasons we cannot define other networks with `NymNetworkDetails` directly yet - Self::CUSTOM { details } => details.clone(), - } - } - - pub fn bech32_prefix(&self) -> String { - self.details().chain_details.bech32_account_prefix - } - - pub fn mix_denom(&self) -> DenomDetailsOwned { - self.details().chain_details.mix_denom - } - - pub fn stake_denom(&self) -> DenomDetailsOwned { - self.details().chain_details.stake_denom - } - - pub fn base_mix_denom(&self) -> String { - self.details().chain_details.mix_denom.base - } - - pub fn base_stake_denom(&self) -> String { - self.details().chain_details.stake_denom.base - } - - pub fn mixnet_contract_address(&self) -> Option { - self.details().contracts.mixnet_contract_address - } - - pub fn vesting_contract_address(&self) -> Option { - self.details().contracts.vesting_contract_address - } - - pub fn bandwidth_claim_contract_address(&self) -> Option { - self.details().contracts.bandwidth_claim_contract_address - } - - pub fn coconut_bandwidth_contract_address(&self) -> Option { - self.details().contracts.coconut_bandwidth_contract_address - } - - pub fn multisig_contract_address(&self) -> Option { - self.details().contracts.multisig_contract_address - } - - pub fn validators(&self) -> Vec { - self.details().endpoints - } - - // only used in mixnet contract tests, but I don't want to be messing with that code now - pub fn rewarding_validator_address(&self) -> &str { - match self { - Network::QA => crate::qa::REWARDING_VALIDATOR_ADDRESS, - Network::SANDBOX => crate::sandbox::REWARDING_VALIDATOR_ADDRESS, - Network::MAINNET => crate::mainnet::REWARDING_VALIDATOR_ADDRESS, - Network::CUSTOM { .. } => { - panic!("rewarding validator address is unavailable for a custom network") - } - } - } - - // this should be handled differently, but I don't want to break compatibility - pub fn statistics_service_url(&self) -> &str { - match self { - Network::MAINNET => crate::mainnet::STATISTICS_SERVICE_DOMAIN_ADDRESS, - Network::SANDBOX => crate::mainnet::STATISTICS_SERVICE_DOMAIN_ADDRESS, - Network::QA => crate::mainnet::STATISTICS_SERVICE_DOMAIN_ADDRESS, - Network::CUSTOM { .. } => { - panic!("statistics service url is unavailable for a custom network") - } - } - } -} - -impl FromStr for Network { - type Err = NetworkDefaultsError; - - fn from_str(s: &str) -> Result { - match s.to_lowercase().as_str() { - "qa" => Ok(Network::QA), - "sandbox" => Ok(Network::SANDBOX), - "mainnet" => Ok(Network::MAINNET), - _ => Err(NetworkDefaultsError::MalformedNetworkProvided( - s.to_string(), - )), - } - } -} - -impl fmt::Display for Network { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - match *self { - Network::QA => f.write_str("QA"), - Network::SANDBOX => f.write_str("Sandbox"), - Network::MAINNET => f.write_str("Mainnet"), - Network::CUSTOM { .. } => f.write_str("Custom"), - } - } -} - -#[derive(Clone, Debug, Deserialize, Serialize, PartialEq, Eq)] -pub struct NetworkDetails { - bech32_prefix: String, - mix_denom: DenomDetailsOwned, - stake_denom: DenomDetailsOwned, - mixnet_contract_address: String, - vesting_contract_address: String, - bandwidth_claim_contract_address: String, - statistics_service_url: String, - validators: Vec, -} - -impl From<&DefaultNetworkDetails> for NetworkDetails { - fn from(details: &DefaultNetworkDetails) -> Self { - NetworkDetails { - bech32_prefix: details.bech32_prefix.into(), - mix_denom: details.mix_denom.into(), - stake_denom: details.stake_denom.into(), - mixnet_contract_address: details.mixnet_contract_address.into(), - vesting_contract_address: details.vesting_contract_address.into(), - bandwidth_claim_contract_address: details.bandwidth_claim_contract_address.into(), - statistics_service_url: details.statistics_service_url.into(), - validators: details.validators.clone(), - } - } -} - -// this also has to exist for compatibility reasons since I don't want to be touching the wallet now -impl From for NetworkDetails { - fn from(details: NymNetworkDetails) -> Self { - NetworkDetails { - bech32_prefix: details.chain_details.bech32_account_prefix, - mix_denom: details.chain_details.mix_denom, - stake_denom: details.chain_details.stake_denom, - mixnet_contract_address: details - .contracts - .mixnet_contract_address - .unwrap_or_default(), - vesting_contract_address: details - .contracts - .vesting_contract_address - .unwrap_or_default(), - bandwidth_claim_contract_address: details - .contracts - .bandwidth_claim_contract_address - .unwrap_or_default(), - statistics_service_url: "".to_string(), - validators: details.endpoints, - } - } -} - -impl NetworkDetails { - pub fn base_mix_denom(&self) -> &str { - &self.mix_denom.base - } -} - -#[derive(Clone, Debug, Default, Deserialize, Serialize, PartialEq, Eq)] -pub struct SupportedNetworks { - networks: HashMap, -} - -impl SupportedNetworks { - pub fn new(support: Vec) -> Self { - SupportedNetworks { - networks: support - .into_iter() - .map(|n| { - let details = n.details().into(); - (n, details) - }) - .collect(), - } - } - - pub fn bech32_prefix(&self, network: Network) -> Option<&str> { - self.networks - .get(&network) - .map(|network_details| network_details.bech32_prefix.as_str()) - } - - pub fn base_mix_denom(&self, network: Network) -> Option<&str> { - self.networks - .get(&network) - .map(|network_details| network_details.base_mix_denom()) - } - - pub fn mixnet_contract_address(&self, network: Network) -> Option<&str> { - self.networks - .get(&network) - .map(|network_details| network_details.mixnet_contract_address.as_str()) - } - - pub fn vesting_contract_address(&self, network: Network) -> Option<&str> { - self.networks - .get(&network) - .map(|network_details| network_details.vesting_contract_address.as_str()) - } - - pub fn bandwidth_claim_contract_address(&self, network: Network) -> Option<&str> { - self.networks - .get(&network) - .map(|network_details| network_details.bandwidth_claim_contract_address.as_str()) - } - - pub fn validators(&self, network: Network) -> impl Iterator { - self.networks - .get(&network) - .map(|network_details| &network_details.validators) - .into_iter() - .flatten() - } -} diff --git a/common/network-defaults/src/lib.rs b/common/network-defaults/src/lib.rs index ddd4a9dabb..c600199140 100644 --- a/common/network-defaults/src/lib.rs +++ b/common/network-defaults/src/lib.rs @@ -1,15 +1,11 @@ // Copyright 2020 - Nym Technologies SA // SPDX-License-Identifier: Apache-2.0 -use once_cell::sync::Lazy; use serde::{Deserialize, Serialize}; -use std::{env::var, path::PathBuf}; +use std::{env::var, ops::Not, path::PathBuf}; use url::Url; -pub mod all; pub mod mainnet; -pub mod qa; -pub mod sandbox; pub mod var_names; pub const ETH_CONTRACT_ADDRESS: [u8; 20] = mainnet::_ETH_CONTRACT_ADDRESS; @@ -103,159 +99,99 @@ impl NymNetworkDetails { } pub fn new_mainnet() -> Self { - (&*MAINNET_DEFAULTS).into() + fn parse_optional_str(raw: &str) -> Option { + raw.is_empty().not().then(|| raw.into()) + } + + // Consider caching this process (lazy static) + NymNetworkDetails { + chain_details: ChainDetails { + bech32_account_prefix: mainnet::BECH32_PREFIX.into(), + mix_denom: mainnet::MIX_DENOM.into(), + stake_denom: mainnet::STAKE_DENOM.into(), + }, + 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), + bandwidth_claim_contract_address: parse_optional_str( + mainnet::BANDWIDTH_CLAIM_CONTRACT_ADDRESS, + ), + coconut_bandwidth_contract_address: parse_optional_str( + mainnet::COCONUT_BANDWIDTH_CONTRACT_ADDRESS, + ), + multisig_contract_address: parse_optional_str(mainnet::MULTISIG_CONTRACT_ADDRESS), + }, + } } + #[must_use] pub fn with_bech32_account_prefix>(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>(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>(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_validator_endpoint(mut self, endpoint: ValidatorDetails) -> Self { self.endpoints.push(endpoint); self } + #[must_use] pub fn with_mixnet_contract>(mut self, contract: Option) -> Self { self.contracts.mixnet_contract_address = contract.map(Into::into); self } + #[must_use] pub fn with_vesting_contract>(mut self, contract: Option) -> Self { self.contracts.vesting_contract_address = contract.map(Into::into); self } + #[must_use] pub fn with_bandwidth_claim_contract>(mut self, contract: Option) -> Self { self.contracts.bandwidth_claim_contract_address = contract.map(Into::into); self } + #[must_use] pub fn with_coconut_bandwidth_contract>(mut self, contract: Option) -> Self { self.contracts.coconut_bandwidth_contract_address = contract.map(Into::into); self } + #[must_use] pub fn with_multisig_contract>(mut self, contract: Option) -> Self { self.contracts.multisig_contract_address = contract.map(Into::into); self } } -// This conversion only exists for convenience reasons until -// we can completely phase out `DefaultNetworkDetails` -impl<'a> From<&'a DefaultNetworkDetails> for NymNetworkDetails { - fn from(details: &'a DefaultNetworkDetails) -> Self { - fn parse_optional_str(raw: &str) -> Option { - if raw.is_empty() { - None - } else { - Some(raw.into()) - } - } - - NymNetworkDetails { - chain_details: ChainDetails { - bech32_account_prefix: details.bech32_prefix.into(), - mix_denom: details.mix_denom.into(), - stake_denom: details.stake_denom.into(), - }, - endpoints: details.validators.clone(), - contracts: NymContracts { - mixnet_contract_address: parse_optional_str(details.mixnet_contract_address), - vesting_contract_address: parse_optional_str(details.vesting_contract_address), - bandwidth_claim_contract_address: parse_optional_str( - details.bandwidth_claim_contract_address, - ), - coconut_bandwidth_contract_address: parse_optional_str( - details.coconut_bandwidth_contract_address, - ), - multisig_contract_address: parse_optional_str(details.multisig_contract_address), - }, - } - } -} - -// Since these are lazily constructed, we can afford to switch some of them to stronger types in the -// future. If we do this, and also get rid of the references we could potentially unify with -// `NetworkDetails`. -pub struct DefaultNetworkDetails { - bech32_prefix: &'static str, - mix_denom: DenomDetails, - stake_denom: DenomDetails, - mixnet_contract_address: &'static str, - vesting_contract_address: &'static str, - bandwidth_claim_contract_address: &'static str, - coconut_bandwidth_contract_address: &'static str, - multisig_contract_address: &'static str, - #[allow(dead_code)] - rewarding_validator_address: &'static str, - statistics_service_url: &'static str, - validators: Vec, -} - -static MAINNET_DEFAULTS: Lazy = Lazy::new(|| DefaultNetworkDetails { - bech32_prefix: mainnet::BECH32_PREFIX, - mix_denom: mainnet::MIX_DENOM, - stake_denom: mainnet::STAKE_DENOM, - mixnet_contract_address: mainnet::MIXNET_CONTRACT_ADDRESS, - vesting_contract_address: mainnet::VESTING_CONTRACT_ADDRESS, - bandwidth_claim_contract_address: mainnet::BANDWIDTH_CLAIM_CONTRACT_ADDRESS, - coconut_bandwidth_contract_address: mainnet::COCONUT_BANDWIDTH_CONTRACT_ADDRESS, - multisig_contract_address: mainnet::MULTISIG_CONTRACT_ADDRESS, - rewarding_validator_address: mainnet::REWARDING_VALIDATOR_ADDRESS, - statistics_service_url: mainnet::STATISTICS_SERVICE_DOMAIN_ADDRESS, - validators: mainnet::validators(), -}); - -static SANDBOX_DEFAULTS: Lazy = Lazy::new(|| DefaultNetworkDetails { - bech32_prefix: sandbox::BECH32_PREFIX, - mix_denom: sandbox::MIX_DENOM, - stake_denom: sandbox::STAKE_DENOM, - mixnet_contract_address: sandbox::MIXNET_CONTRACT_ADDRESS, - vesting_contract_address: sandbox::VESTING_CONTRACT_ADDRESS, - bandwidth_claim_contract_address: sandbox::BANDWIDTH_CLAIM_CONTRACT_ADDRESS, - coconut_bandwidth_contract_address: sandbox::COCONUT_BANDWIDTH_CONTRACT_ADDRESS, - multisig_contract_address: sandbox::MULTISIG_CONTRACT_ADDRESS, - rewarding_validator_address: sandbox::REWARDING_VALIDATOR_ADDRESS, - statistics_service_url: sandbox::STATISTICS_SERVICE_DOMAIN_ADDRESS, - validators: sandbox::validators(), -}); - -static QA_DEFAULTS: Lazy = Lazy::new(|| DefaultNetworkDetails { - bech32_prefix: qa::BECH32_PREFIX, - mix_denom: qa::MIX_DENOM, - stake_denom: qa::STAKE_DENOM, - mixnet_contract_address: qa::MIXNET_CONTRACT_ADDRESS, - vesting_contract_address: qa::VESTING_CONTRACT_ADDRESS, - bandwidth_claim_contract_address: qa::BANDWIDTH_CLAIM_CONTRACT_ADDRESS, - coconut_bandwidth_contract_address: qa::COCONUT_BANDWIDTH_CONTRACT_ADDRESS, - multisig_contract_address: qa::MULTISIG_CONTRACT_ADDRESS, - rewarding_validator_address: qa::REWARDING_VALIDATOR_ADDRESS, - statistics_service_url: qa::STATISTICS_SERVICE_DOMAIN_ADDRESS, - validators: qa::validators(), -}); - #[derive(Debug, Copy, Serialize, Deserialize, Clone, PartialEq, Eq)] pub struct DenomDetails { pub base: &'static str, @@ -349,12 +285,17 @@ pub fn setup_env(config_env_file: Option) { .expect("Invalid path to environment configuration file"); } else { // if nothing is set, the use mainnet defaults + // if the user has not set `CONFIGURED`, then even if they set any of the env variables, + // overwrite them crate::mainnet::export_to_env(); } } Err(_) => crate::mainnet::export_to_env(), _ => {} } + + // if we haven't explicitly defined any of the constants, fallback to defaults + crate::mainnet::export_to_env_if_not_set() } // Name of the event triggered by the eth contract. If the event name is changed, diff --git a/common/network-defaults/src/mainnet.rs b/common/network-defaults/src/mainnet.rs index 53e85d8d89..ed415e2622 100644 --- a/common/network-defaults/src/mainnet.rs +++ b/common/network-defaults/src/mainnet.rs @@ -31,42 +31,107 @@ pub(crate) fn validators() -> Vec { vec![ValidatorDetails::new(NYMD_VALIDATOR, Some(API_VALIDATOR))] } +const DEFAULT_SUFFIX: &str = "_MAINNET_DEFAULT"; + +fn set_var_to_default(var: &str, value: &str) { + std::env::set_var(var, value); + std::env::set_var(format!("{}{}", var, DEFAULT_SUFFIX), "1") +} + +fn set_var_conditionally_to_default(var: &str, value: &str) { + if std::env::var(var).is_err() { + set_var_to_default(var, value) + } +} + +pub fn uses_default(var: &str) -> bool { + std::env::var(format!("{}{}", var, DEFAULT_SUFFIX)).is_ok() +} + +pub fn read_var_if_not_default(var: &str) -> Option { + if uses_default(var) { + None + } else { + std::env::var(var).ok() + } +} + pub fn export_to_env() { - std::env::set_var(var_names::CONFIGURED, "true"); - std::env::set_var(var_names::BECH32_PREFIX, BECH32_PREFIX); - std::env::set_var(var_names::MIX_DENOM, MIX_DENOM.base); - std::env::set_var(var_names::MIX_DENOM_DISPLAY, MIX_DENOM.display); - std::env::set_var(var_names::STAKE_DENOM, STAKE_DENOM.base); - std::env::set_var(var_names::STAKE_DENOM_DISPLAY, STAKE_DENOM.display); - std::env::set_var( + set_var_to_default(var_names::CONFIGURED, "true"); + set_var_to_default(var_names::BECH32_PREFIX, BECH32_PREFIX); + set_var_to_default(var_names::MIX_DENOM, MIX_DENOM.base); + set_var_to_default(var_names::MIX_DENOM_DISPLAY, MIX_DENOM.display); + set_var_to_default(var_names::STAKE_DENOM, STAKE_DENOM.base); + set_var_to_default(var_names::STAKE_DENOM_DISPLAY, STAKE_DENOM.display); + set_var_to_default( var_names::DENOMS_EXPONENT, - STAKE_DENOM.display_exponent.to_string(), + &STAKE_DENOM.display_exponent.to_string(), ); - std::env::set_var(var_names::MIXNET_CONTRACT_ADDRESS, MIXNET_CONTRACT_ADDRESS); - std::env::set_var( + set_var_to_default(var_names::MIXNET_CONTRACT_ADDRESS, MIXNET_CONTRACT_ADDRESS); + set_var_to_default( var_names::VESTING_CONTRACT_ADDRESS, VESTING_CONTRACT_ADDRESS, ); - std::env::set_var( + set_var_to_default( var_names::BANDWIDTH_CLAIM_CONTRACT_ADDRESS, BANDWIDTH_CLAIM_CONTRACT_ADDRESS, ); - std::env::set_var( + set_var_to_default( var_names::COCONUT_BANDWIDTH_CONTRACT_ADDRESS, COCONUT_BANDWIDTH_CONTRACT_ADDRESS, ); - std::env::set_var( + set_var_to_default( var_names::MULTISIG_CONTRACT_ADDRESS, MULTISIG_CONTRACT_ADDRESS, ); - std::env::set_var( + set_var_to_default( var_names::REWARDING_VALIDATOR_ADDRESS, REWARDING_VALIDATOR_ADDRESS, ); - std::env::set_var( + set_var_to_default( var_names::STATISTICS_SERVICE_DOMAIN_ADDRESS, STATISTICS_SERVICE_DOMAIN_ADDRESS, ); - std::env::set_var(var_names::NYMD_VALIDATOR, NYMD_VALIDATOR); - std::env::set_var(var_names::API_VALIDATOR, API_VALIDATOR); + set_var_to_default(var_names::NYMD_VALIDATOR, NYMD_VALIDATOR); + set_var_to_default(var_names::API_VALIDATOR, API_VALIDATOR); +} + +pub fn export_to_env_if_not_set() { + set_var_conditionally_to_default(var_names::CONFIGURED, "true"); + set_var_conditionally_to_default(var_names::BECH32_PREFIX, BECH32_PREFIX); + set_var_conditionally_to_default(var_names::MIX_DENOM, MIX_DENOM.base); + set_var_conditionally_to_default(var_names::MIX_DENOM_DISPLAY, MIX_DENOM.display); + set_var_conditionally_to_default(var_names::STAKE_DENOM, STAKE_DENOM.base); + set_var_conditionally_to_default(var_names::STAKE_DENOM_DISPLAY, STAKE_DENOM.display); + set_var_conditionally_to_default( + var_names::DENOMS_EXPONENT, + &STAKE_DENOM.display_exponent.to_string(), + ); + set_var_conditionally_to_default(var_names::MIXNET_CONTRACT_ADDRESS, MIXNET_CONTRACT_ADDRESS); + set_var_conditionally_to_default( + var_names::VESTING_CONTRACT_ADDRESS, + VESTING_CONTRACT_ADDRESS, + ); + set_var_conditionally_to_default( + var_names::BANDWIDTH_CLAIM_CONTRACT_ADDRESS, + BANDWIDTH_CLAIM_CONTRACT_ADDRESS, + ); + set_var_conditionally_to_default( + var_names::COCONUT_BANDWIDTH_CONTRACT_ADDRESS, + COCONUT_BANDWIDTH_CONTRACT_ADDRESS, + ); + set_var_conditionally_to_default( + var_names::MULTISIG_CONTRACT_ADDRESS, + MULTISIG_CONTRACT_ADDRESS, + ); + set_var_conditionally_to_default( + var_names::REWARDING_VALIDATOR_ADDRESS, + REWARDING_VALIDATOR_ADDRESS, + ); + set_var_conditionally_to_default( + var_names::STATISTICS_SERVICE_DOMAIN_ADDRESS, + STATISTICS_SERVICE_DOMAIN_ADDRESS, + ); + set_var_conditionally_to_default(var_names::NYMD_VALIDATOR, NYMD_VALIDATOR); + set_var_conditionally_to_default(var_names::API_VALIDATOR, API_VALIDATOR); } diff --git a/common/network-defaults/src/qa.rs b/common/network-defaults/src/qa.rs deleted file mode 100644 index 3b5faa4da7..0000000000 --- a/common/network-defaults/src/qa.rs +++ /dev/null @@ -1,32 +0,0 @@ -// Copyright 2021 - Nym Technologies SA -// SPDX-License-Identifier: Apache-2.0 - -use crate::{DenomDetails, ValidatorDetails}; - -pub(crate) const BECH32_PREFIX: &str = "n"; - -pub const MIX_DENOM: DenomDetails = DenomDetails::new("unym", "nym", 6); -pub const STAKE_DENOM: DenomDetails = DenomDetails::new("unyx", "nyx", 6); - -pub(crate) const MIXNET_CONTRACT_ADDRESS: &str = - "n1suhgf5svhu4usrurvxzlgn54ksxmn8gljarjtxqnapv8kjnp4nrsd3qaep"; -pub(crate) const VESTING_CONTRACT_ADDRESS: &str = - "n1xr3rq8yvd7qplsw5yx90ftsr2zdhg4e9z60h5duusgxpv72hud3sjkxkav"; -pub(crate) const BANDWIDTH_CLAIM_CONTRACT_ADDRESS: &str = - "n19lc9u84cz0yz3fww5283nucc9yvr8gsjmgeul0"; -pub(crate) const COCONUT_BANDWIDTH_CONTRACT_ADDRESS: &str = - "n1ghd753shjuwexxywmgs4xz7x2q732vcn7ty4yw"; -pub(crate) const MULTISIG_CONTRACT_ADDRESS: &str = "n17p9rzwnnfxcjp32un9ug7yhhzgtkhvl988qccs"; -pub(crate) const _ETH_CONTRACT_ADDRESS: [u8; 20] = - hex_literal::hex!("0000000000000000000000000000000000000000"); -pub(crate) const _ETH_ERC20_CONTRACT_ADDRESS: [u8; 20] = - hex_literal::hex!("0000000000000000000000000000000000000000"); -pub(crate) const REWARDING_VALIDATOR_ADDRESS: &str = "n1tfzd4qz3a45u8p4mr5zmzv66457uwjgcl05jdq"; - -pub(crate) const STATISTICS_SERVICE_DOMAIN_ADDRESS: &str = "http://0.0.0.0"; -pub(crate) fn validators() -> Vec { - vec![ValidatorDetails::new( - "https://qa-validator.nymtech.net", - Some("https://qa-validator-api.nymtech.net/api"), - )] -} diff --git a/common/network-defaults/src/sandbox.rs b/common/network-defaults/src/sandbox.rs deleted file mode 100644 index e758b1717a..0000000000 --- a/common/network-defaults/src/sandbox.rs +++ /dev/null @@ -1,30 +0,0 @@ -// Copyright 2021 - Nym Technologies SA -// SPDX-License-Identifier: Apache-2.0 - -use crate::{DenomDetails, ValidatorDetails}; - -pub(crate) const BECH32_PREFIX: &str = "nymt"; - -pub const MIX_DENOM: DenomDetails = DenomDetails::new("unymt", "nymt", 6); -pub const STAKE_DENOM: DenomDetails = DenomDetails::new("unyxt", "nyxt", 6); - -pub(crate) const MIXNET_CONTRACT_ADDRESS: &str = "nymt1ghd753shjuwexxywmgs4xz7x2q732vcnstz02j"; -pub(crate) const VESTING_CONTRACT_ADDRESS: &str = "nymt14ejqjyq8um4p3xfqj74yld5waqljf88fn549lh"; -pub(crate) const BANDWIDTH_CLAIM_CONTRACT_ADDRESS: &str = - "nymt17p9rzwnnfxcjp32un9ug7yhhzgtkhvl9f8xzkv"; -pub(crate) const COCONUT_BANDWIDTH_CONTRACT_ADDRESS: &str = - "nymt1nz0r0au8aj6dc00wmm3ufy4g4k86rjzlgq608r"; -pub(crate) const MULTISIG_CONTRACT_ADDRESS: &str = "nymt1k8re7jwz6rnnwrktnejdwkwnncte7ek7kk6fvg"; -pub(crate) const _ETH_CONTRACT_ADDRESS: [u8; 20] = - hex_literal::hex!("8e0DcFF7F3085235C32E845f3667aEB3f1e83133"); -pub(crate) const _ETH_ERC20_CONTRACT_ADDRESS: [u8; 20] = - hex_literal::hex!("E8883BAeF3869e14E4823F46662e81D4F7d2A81F"); -pub(crate) const REWARDING_VALIDATOR_ADDRESS: &str = "nymt1jh0s6qu6tuw9ut438836mmn7f3f2wencrnmdj4"; - -pub(crate) const STATISTICS_SERVICE_DOMAIN_ADDRESS: &str = "http://0.0.0.0"; -pub(crate) fn validators() -> Vec { - vec![ValidatorDetails::new( - "https://sandbox-validator.nymtech.net", - Some("https://sandbox-validator.nymtech.net/api"), - )] -} diff --git a/common/types/src/currency.rs b/common/types/src/currency.rs index e6927a1721..38292d11d3 100644 --- a/common/types/src/currency.rs +++ b/common/types/src/currency.rs @@ -1,6 +1,5 @@ use crate::error::TypesError; -use config::defaults::all::Network; -use config::defaults::{DenomDetails, DenomDetailsOwned}; +use config::defaults::{DenomDetails, DenomDetailsOwned, NymNetworkDetails}; use cosmwasm_std::Fraction; use cosmwasm_std::{Decimal, Uint128}; use schemars::JsonSchema; @@ -56,10 +55,16 @@ pub type Denom = String; pub struct RegisteredCoins(HashMap); impl RegisteredCoins { - pub fn default_denoms(network: Network) -> Self { + pub fn default_denoms(network: &NymNetworkDetails) -> Self { let mut network_coins = HashMap::new(); - network_coins.insert(network.mix_denom().base, network.mix_denom().into()); - network_coins.insert(network.stake_denom().base, network.stake_denom().into()); + network_coins.insert( + network.chain_details.mix_denom.base.clone(), + network.chain_details.mix_denom.clone().into(), + ); + network_coins.insert( + network.chain_details.stake_denom.base.clone(), + network.chain_details.stake_denom.clone().into(), + ); RegisteredCoins(network_coins) } @@ -513,7 +518,7 @@ mod test { #[test] fn converting_to_display() { - let reg = RegisteredCoins::default_denoms(Network::MAINNET); + let reg = RegisteredCoins::default_denoms(&NymNetworkDetails::new_mainnet()); let values = vec![ (1u128, "0.000001"), (10u128, "0.00001"), @@ -527,16 +532,29 @@ mod test { ]; for (raw, expected) in values { - let coin = Coin::new(raw, Network::MAINNET.mix_denom().base); + let coin = Coin::new( + raw, + NymNetworkDetails::new_mainnet() + .chain_details + .mix_denom + .base + .clone(), + ); let display = reg.attempt_convert_to_display_dec_coin(coin).unwrap(); - assert_eq!(Network::MAINNET.mix_denom().display, display.denom); + assert_eq!( + NymNetworkDetails::new_mainnet() + .chain_details + .mix_denom + .display, + display.denom + ); assert_eq!(expected, display.amount.to_string()); } } #[test] fn converting_to_base() { - let reg = RegisteredCoins::default_denoms(Network::MAINNET); + let reg = RegisteredCoins::default_denoms(&NymNetworkDetails::new_mainnet()); let values = vec![ (1u128, "0.000001"), (10u128, "0.00001"), @@ -551,11 +569,21 @@ mod test { for (expected, raw_display) in values { let coin = DecCoin { - denom: Network::MAINNET.mix_denom().display, + denom: NymNetworkDetails::new_mainnet() + .chain_details + .mix_denom + .display + .clone(), amount: raw_display.parse().unwrap(), }; let base = reg.attempt_convert_to_base_coin(coin).unwrap(); - assert_eq!(Network::MAINNET.mix_denom().base, base.denom); + assert_eq!( + NymNetworkDetails::new_mainnet() + .chain_details + .mix_denom + .base, + base.denom + ); assert_eq!(expected, base.amount); } } diff --git a/common/types/src/delegation.rs b/common/types/src/delegation.rs index e2a60b002c..fdf0ee8467 100644 --- a/common/types/src/delegation.rs +++ b/common/types/src/delegation.rs @@ -28,7 +28,7 @@ impl Delegation { ) -> Result { Ok(Delegation { owner: delegation.owner.to_string(), - mix_id: delegation.node_id, + mix_id: delegation.mix_id, amount: reg.attempt_convert_to_display_dec_coin(delegation.amount.into())?, height: delegation.height, proxy: delegation.proxy.map(|d| d.to_string()), diff --git a/common/types/src/error.rs b/common/types/src/error.rs index a49f7587e2..b0874ba7aa 100644 --- a/common/types/src/error.rs +++ b/common/types/src/error.rs @@ -72,6 +72,8 @@ pub enum TypesError { UnknownCoinDenom(String), #[error("Provided event is not a delegation event")] NotADelegationEvent, + #[error("Unknown network - {0}")] + UnknownNetwork(String), } impl Serialize for TypesError { diff --git a/contracts/CHANGELOG.md b/contracts/CHANGELOG.md index 38fbe32341..0050b844a1 100644 --- a/contracts/CHANGELOG.md +++ b/contracts/CHANGELOG.md @@ -3,10 +3,12 @@ ### Added - vesting-contract: added queries for delegation timestamps and paged query for all vesting delegations in the contract ([#1569]) +- all binaries: added shell completion and [Fig](fig.io) spec generation ([#1638]) ### Changed - mixnet-contract: compounding delegator rewards now happens instantaneously as opposed to having to wait for the current epoch to finish ([#1571]) +- network-requester: updated CLI to use `clap` macros ([#1638]) ### Fixed @@ -18,6 +20,7 @@ [#1569]: https://github.com/nymtech/nym/pull/1569 [#1569]: https://github.com/nymtech/nym/pull/1571 [#1613]: https://github.com/nymtech/nym/pull/1613 +[#1638]: https://github.com/nymtech/nym/pull/1638 ## [nym-contracts-v1.0.1](https://github.com/nymtech/nym/tree/nym-contracts-v1.0.1) (2022-06-22) diff --git a/contracts/mixnet/src/delegations/helpers.rs b/contracts/mixnet/src/delegations/helpers.rs index 5a639d6c05..b21e0d9ba4 100644 --- a/contracts/mixnet/src/delegations/helpers.rs +++ b/contracts/mixnet/src/delegations/helpers.rs @@ -15,7 +15,7 @@ pub(crate) fn undelegate( ) -> Result { let tokens = mix_rewarding.undelegate(&delegation)?; - rewards_storage::MIXNODE_REWARDING.save(store, delegation.node_id, &mix_rewarding)?; + rewards_storage::MIXNODE_REWARDING.save(store, delegation.mix_id, &mix_rewarding)?; storage::delegations().replace(store, delegation.storage_key(), None, Some(&delegation))?; Ok(tokens) diff --git a/contracts/mixnet/src/delegations/queries.rs b/contracts/mixnet/src/delegations/queries.rs index d921c22b26..7ef2b0964e 100644 --- a/contracts/mixnet/src/delegations/queries.rs +++ b/contracts/mixnet/src/delegations/queries.rs @@ -74,7 +74,7 @@ pub(crate) fn query_delegator_delegations_paged( let start_next_after = delegations .last() - .map(|del| (del.node_id, del.proxy_storage_key())); + .map(|del| (del.mix_id, del.proxy_storage_key())); Ok(PagedDelegatorDelegationsResponse::new( delegations, @@ -284,19 +284,19 @@ mod tests { let res1 = query_mixnode_delegations_paged(test.deps(), mix_id1, None, None).unwrap(); assert_eq!(res1.delegations.len(), 10); - assert!(res1.delegations.into_iter().all(|d| d.node_id == mix_id1)); + assert!(res1.delegations.into_iter().all(|d| d.mix_id == mix_id1)); let res2 = query_mixnode_delegations_paged(test.deps(), mix_id2, None, None).unwrap(); assert_eq!(res2.delegations.len(), 14); - assert!(res2.delegations.into_iter().all(|d| d.node_id == mix_id2)); + assert!(res2.delegations.into_iter().all(|d| d.mix_id == mix_id2)); let res3 = query_mixnode_delegations_paged(test.deps(), mix_id3, None, None).unwrap(); assert_eq!(res3.delegations.len(), 10); - assert!(res3.delegations.into_iter().all(|d| d.node_id == mix_id3)); + assert!(res3.delegations.into_iter().all(|d| d.mix_id == mix_id3)); let res4 = query_mixnode_delegations_paged(test.deps(), mix_id4, None, None).unwrap(); assert_eq!(res4.delegations.len(), 10); - assert!(res4.delegations.into_iter().all(|d| d.node_id == mix_id4)); + assert!(res4.delegations.into_iter().all(|d| d.mix_id == mix_id4)); } } @@ -610,7 +610,7 @@ mod tests { assert_eq!(1, page1.delegations.len()); assert!( page1.delegations[0].owner.as_str() == delegator1 - && page1.delegations[0].node_id == mix_id1 + && page1.delegations[0].mix_id == mix_id1 ); test.add_immediate_delegation(delegator1, 1000u32, mix_id2); @@ -621,11 +621,11 @@ mod tests { assert_eq!(2, page1.delegations.len()); assert!( page1.delegations[0].owner.as_str() == delegator1 - && page1.delegations[0].node_id == mix_id1 + && page1.delegations[0].mix_id == mix_id1 ); assert!( page1.delegations[1].owner.as_str() == delegator1 - && page1.delegations[1].node_id == mix_id2 + && page1.delegations[1].mix_id == mix_id2 ); test.add_immediate_delegation(delegator2, 1000u32, mix_id1); @@ -636,11 +636,11 @@ mod tests { assert_eq!(2, another_page1.delegations.len()); assert!( another_page1.delegations[0].owner.as_str() == delegator1 - && another_page1.delegations[0].node_id == mix_id1 + && another_page1.delegations[0].mix_id == mix_id1 ); assert!( another_page1.delegations[1].owner.as_str() == delegator2 - && another_page1.delegations[1].node_id == mix_id1 + && another_page1.delegations[1].mix_id == mix_id1 ); // retrieving the next page should start after the last key on this page @@ -652,7 +652,7 @@ mod tests { assert_eq!(1, page2.delegations.len()); assert!( page2.delegations[0].owner.as_str() == delegator1 - && page2.delegations[0].node_id == mix_id2 + && page2.delegations[0].mix_id == mix_id2 ); // save another one @@ -665,11 +665,11 @@ mod tests { assert_eq!(2, page2.delegations.len()); assert!( page2.delegations[0].owner.as_str() == delegator1 - && page2.delegations[0].node_id == mix_id2 + && page2.delegations[0].mix_id == mix_id2 ); assert!( page2.delegations[1].owner.as_str() == delegator2 - && page2.delegations[1].node_id == mix_id2 + && page2.delegations[1].mix_id == mix_id2 ); } } diff --git a/contracts/mixnet/src/delegations/storage.rs b/contracts/mixnet/src/delegations/storage.rs index f33fa97d3d..2844d359a3 100644 --- a/contracts/mixnet/src/delegations/storage.rs +++ b/contracts/mixnet/src/delegations/storage.rs @@ -32,7 +32,7 @@ pub(crate) fn delegations<'a>() -> IndexedMap<'a, PrimaryKey, Delegation, Delega DELEGATION_OWNER_IDX_NAMESPACE, ), mixnode: MultiIndex::new( - |d| d.node_id, + |d| d.mix_id, DELEGATION_PK_NAMESPACE, DELEGATION_MIXNODE_IDX_NAMESPACE, ), diff --git a/contracts/mixnet/src/delegations/transactions.rs b/contracts/mixnet/src/delegations/transactions.rs index 7018c65790..1c61c7c923 100644 --- a/contracts/mixnet/src/delegations/transactions.rs +++ b/contracts/mixnet/src/delegations/transactions.rs @@ -49,9 +49,9 @@ pub(crate) fn _try_delegate_to_mixnode( // check if the target node actually exists and is still bonded match mixnodes_storage::mixnode_bonds().may_load(deps.storage, mix_id)? { - None => return Err(MixnetContractError::MixNodeBondNotFound { id: mix_id }), + None => return Err(MixnetContractError::MixNodeBondNotFound { mix_id }), Some(bond) if bond.is_unbonding => { - return Err(MixnetContractError::MixnodeIsUnbonding { node_id: mix_id }) + return Err(MixnetContractError::MixnodeIsUnbonding { mix_id }) } _ => (), } @@ -144,7 +144,7 @@ mod tests { let res = try_delegate_to_mixnode(test.deps_mut(), sender, 42); assert_eq!( res, - Err(MixnetContractError::MixNodeBondNotFound { id: 42 }) + Err(MixnetContractError::MixNodeBondNotFound { mix_id: 42 }) ) } @@ -240,7 +240,7 @@ mod tests { assert_eq!( res, Err(MixnetContractError::MixnodeIsUnbonding { - node_id: mix_id_unbonding + mix_id: mix_id_unbonding }) ); @@ -248,7 +248,7 @@ mod tests { assert_eq!( res, Err(MixnetContractError::MixNodeBondNotFound { - id: mix_id_unbonded + mix_id: mix_id_unbonded }) ); @@ -256,7 +256,7 @@ mod tests { assert_eq!( res, Err(MixnetContractError::MixNodeBondNotFound { - id: mix_id_unbonded_leftover + mix_id: mix_id_unbonded_leftover }) ); } diff --git a/contracts/mixnet/src/interval/transactions.rs b/contracts/mixnet/src/interval/transactions.rs index 3a6dd400df..cfc9bc6528 100644 --- a/contracts/mixnet/src/interval/transactions.rs +++ b/contracts/mixnet/src/interval/transactions.rs @@ -180,7 +180,7 @@ fn update_rewarded_set( let mut tmp_set = BTreeSet::new(); for node_id in &new_rewarded_set { if !tmp_set.insert(node_id) { - return Err(MixnetContractError::DuplicateRewardedSetNode { node_id: *node_id }); + return Err(MixnetContractError::DuplicateRewardedSetNode { mix_id: *node_id }); } } @@ -1015,7 +1015,7 @@ mod tests { .unwrap_err(); assert_eq!( err, - MixnetContractError::DuplicateRewardedSetNode { node_id: 1 } + MixnetContractError::DuplicateRewardedSetNode { mix_id: 1 } ); let nodes_with_duplicate = vec![1, 2, 3, 5, 4, 5]; let err = update_rewarded_set( @@ -1026,7 +1026,7 @@ mod tests { .unwrap_err(); assert_eq!( err, - MixnetContractError::DuplicateRewardedSetNode { node_id: 5 } + MixnetContractError::DuplicateRewardedSetNode { mix_id: 5 } ); } diff --git a/contracts/mixnet/src/mixnodes/helpers.rs b/contracts/mixnet/src/mixnodes/helpers.rs index 57f162c610..6b25fb89c0 100644 --- a/contracts/mixnet/src/mixnodes/helpers.rs +++ b/contracts/mixnet/src/mixnodes/helpers.rs @@ -28,9 +28,9 @@ pub(crate) fn must_get_mixnode_bond_by_owner( pub(crate) fn get_mixnode_details_by_id( store: &dyn Storage, - node_id: NodeId, + mix_id: NodeId, ) -> StdResult> { - if let Some(bond_information) = storage::mixnode_bonds().may_load(store, node_id)? { + if let Some(bond_information) = storage::mixnode_bonds().may_load(store, mix_id)? { // if bond exists, rewarding details MUST also exist let rewarding_details = rewards_storage::MIXNODE_REWARDING.load(store, bond_information.id)?; diff --git a/contracts/mixnet/src/mixnodes/transactions.rs b/contracts/mixnet/src/mixnodes/transactions.rs index c32f944d6c..e29071d8be 100644 --- a/contracts/mixnet/src/mixnodes/transactions.rs +++ b/contracts/mixnet/src/mixnodes/transactions.rs @@ -453,10 +453,7 @@ pub mod tests { // but fails if repeated (since the node is already in the "unbonding" state)( let res = try_remove_mixnode(deps.as_mut(), info); - assert_eq!( - res, - Err(MixnetContractError::MixnodeIsUnbonding { node_id: mix_id }) - ) + assert_eq!(res, Err(MixnetContractError::MixnodeIsUnbonding { mix_id })) } #[test] @@ -522,10 +519,7 @@ pub mod tests { // but we cannot perform any updates whilst the mixnode is already unbonding try_remove_mixnode(deps.as_mut(), info.clone()).unwrap(); let res = try_update_mixnode_config(deps.as_mut(), info, update); - assert_eq!( - res, - Err(MixnetContractError::MixnodeIsUnbonding { node_id: mix_id }) - ) + assert_eq!(res, Err(MixnetContractError::MixnodeIsUnbonding { mix_id })) } #[test] @@ -586,7 +580,7 @@ pub mod tests { assert_eq!(1, event.0); assert_eq!( PendingIntervalEventData::ChangeMixCostParams { - mix_id: mix_id, + mix_id, new_costs: update.clone() }, event.1 @@ -604,10 +598,7 @@ pub mod tests { // but we cannot perform any updates whilst the mixnode is already unbonding try_remove_mixnode(deps.as_mut(), info.clone()).unwrap(); let res = try_update_mixnode_cost_params(deps.as_mut(), info, update); - assert_eq!( - res, - Err(MixnetContractError::MixnodeIsUnbonding { node_id: mix_id }) - ) + assert_eq!(res, Err(MixnetContractError::MixnodeIsUnbonding { mix_id })) } #[test] diff --git a/contracts/mixnet/src/rewards/helpers.rs b/contracts/mixnet/src/rewards/helpers.rs index 43cbcfa1ae..02cdf83e68 100644 --- a/contracts/mixnet/src/rewards/helpers.rs +++ b/contracts/mixnet/src/rewards/helpers.rs @@ -57,7 +57,7 @@ pub(crate) fn withdraw_delegator_reward( delegation: Delegation, mut mix_rewarding: MixNodeRewarding, ) -> Result { - let mix_id = delegation.node_id; + let mix_id = delegation.mix_id; let mut updated_delegation = delegation.clone(); let reward = mix_rewarding.withdraw_delegator_reward(&mut updated_delegation)?; diff --git a/contracts/mixnet/src/rewards/transactions.rs b/contracts/mixnet/src/rewards/transactions.rs index 02bc1e1b82..569e6eb1ae 100644 --- a/contracts/mixnet/src/rewards/transactions.rs +++ b/contracts/mixnet/src/rewards/transactions.rs @@ -33,7 +33,7 @@ pub(crate) fn try_reward_mixnode( deps: DepsMut<'_>, env: Env, info: MessageInfo, - node_id: NodeId, + mix_id: NodeId, node_performance: Performance, ) -> Result { ensure_is_authorized(info.sender, deps.storage)?; @@ -50,34 +50,34 @@ pub(crate) fn try_reward_mixnode( // there's a chance of this failing to load the details if the mixnode unbonded before rewards // were distributed and all of its delegators are also gone - let mut mix_rewarding = match storage::MIXNODE_REWARDING.may_load(deps.storage, node_id)? { + let mut mix_rewarding = match storage::MIXNODE_REWARDING.may_load(deps.storage, mix_id)? { Some(mix_rewarding) if mix_rewarding.still_bonded() => mix_rewarding, // don't fail if the node has unbonded as we don't want to fail the underlying transaction _ => { - return Ok( - Response::new().add_event(new_not_found_mix_operator_rewarding_event( - interval, node_id, - )), - ); + return Ok(Response::new() + .add_event(new_not_found_mix_operator_rewarding_event(interval, mix_id))); } }; + let prior_delegates = mix_rewarding.delegates; + let prior_unit_delegation = mix_rewarding.unit_delegation; + // check if this node has already been rewarded for the current epoch. // unlike the previous check, this one should be a hard error since this cannot be // influenced by users actions let absolute_epoch_id = interval.current_epoch_absolute_id(); if absolute_epoch_id == mix_rewarding.last_rewarded_epoch { return Err(MixnetContractError::MixnodeAlreadyRewarded { - node_id, + mix_id, absolute_epoch_id, }); } // again a hard error since the rewarding validator should have known not to reward this node let node_status = interval_storage::REWARDED_SET - .load(deps.storage, node_id) + .load(deps.storage, mix_id) .map_err(|_| MixnetContractError::MixnodeNotInRewardedSet { - node_id, + mix_id, absolute_epoch_id, })?; @@ -85,10 +85,10 @@ pub(crate) fn try_reward_mixnode( // however, we still need to update last_rewarded_epoch field if node_performance.is_zero() { mix_rewarding.last_rewarded_epoch = absolute_epoch_id; - storage::MIXNODE_REWARDING.save(deps.storage, node_id, &mix_rewarding)?; + storage::MIXNODE_REWARDING.save(deps.storage, mix_id, &mix_rewarding)?; return Ok( Response::new().add_event(new_zero_uptime_mix_operator_rewarding_event( - interval, node_id, + interval, mix_id, )), ); } @@ -106,13 +106,15 @@ pub(crate) fn try_reward_mixnode( mix_rewarding.distribute_rewards(reward_distribution, absolute_epoch_id); // persist changes happened to the storage - storage::MIXNODE_REWARDING.save(deps.storage, node_id, &mix_rewarding)?; + storage::MIXNODE_REWARDING.save(deps.storage, mix_id, &mix_rewarding)?; storage::reward_accounting(deps.storage, node_reward)?; Ok(Response::new().add_event(new_mix_rewarding_event( interval, - node_id, + mix_id, reward_distribution, + prior_delegates, + prior_unit_delegation, ))) } @@ -228,9 +230,9 @@ pub(crate) fn _try_withdraw_delegator_reward( // (in that case the expected path of getting your tokens back is via undelegation) match mixnodes_storage::mixnode_bonds().may_load(deps.storage, mix_id)? { Some(mix_bond) if mix_bond.is_unbonding => { - return Err(MixnetContractError::MixnodeIsUnbonding { node_id: mix_id }) + return Err(MixnetContractError::MixnodeIsUnbonding { mix_id }) } - None => return Err(MixnetContractError::MixnodeHasUnbonded { node_id: mix_id }), + None => return Err(MixnetContractError::MixnodeHasUnbonded { mix_id }), _ => (), }; @@ -523,7 +525,7 @@ pub mod tests { assert!(res_standby.is_ok()); assert!(matches!( res_inactive, - Err(MixnetContractError::MixnodeNotInRewardedSet { node_id, .. }) if node_id == inactive_mix_id + Err(MixnetContractError::MixnodeNotInRewardedSet { mix_id, .. }) if mix_id == inactive_mix_id )); } @@ -552,7 +554,7 @@ pub mod tests { let res = try_reward_mixnode(test.deps_mut(), env, sender.clone(), mix_id, performance); assert!(matches!( res, - Err(MixnetContractError::MixnodeAlreadyRewarded { node_id, .. }) if node_id == mix_id + Err(MixnetContractError::MixnodeAlreadyRewarded { mix_id, .. }) if mix_id == mix_id )); // in the following epoch we're good again @@ -601,7 +603,7 @@ pub mod tests { ); assert!(matches!( res, - Err(MixnetContractError::MixnodeAlreadyRewarded { node_id, .. }) if node_id == mix_id + Err(MixnetContractError::MixnodeAlreadyRewarded { mix_id, .. }) if mix_id == mix_id )); // but in the next epoch, as always, we're good again @@ -912,7 +914,7 @@ pub mod tests { assert_eq!( res, Err(MixnetContractError::MixnodeIsUnbonding { - node_id: mix_id_unbonding + mix_id: mix_id_unbonding }) ); @@ -921,7 +923,7 @@ pub mod tests { assert_eq!( res, Err(MixnetContractError::MixnodeHasUnbonded { - node_id: mix_id_unbonded_leftover + mix_id: mix_id_unbonded_leftover }) ); } @@ -1193,7 +1195,7 @@ pub mod tests { assert_eq!( res, Err(MixnetContractError::MixnodeIsUnbonding { - node_id: mix_id_unbonding + mix_id: mix_id_unbonding }) ); diff --git a/contracts/mixnet/src/support/helpers.rs b/contracts/mixnet/src/support/helpers.rs index 27d9202713..c1349c018b 100644 --- a/contracts/mixnet/src/support/helpers.rs +++ b/contracts/mixnet/src/support/helpers.rs @@ -152,7 +152,7 @@ pub(crate) fn ensure_proxy_match( pub(crate) fn ensure_bonded(bond: &MixNodeBond) -> Result<(), MixnetContractError> { if bond.is_unbonding { - return Err(MixnetContractError::MixnodeIsUnbonding { node_id: bond.id }); + return Err(MixnetContractError::MixnodeIsUnbonding { mix_id: bond.id }); } Ok(()) } diff --git a/explorer-api/Cargo.toml b/explorer-api/Cargo.toml index f65a46db03..f743b34611 100644 --- a/explorer-api/Cargo.toml +++ b/explorer-api/Cargo.toml @@ -7,7 +7,7 @@ edition = "2021" [dependencies] chrono = { version = "0.4.19", features = ["serde"] } -clap = { version = "3.2.8", features = ["cargo", "derive"] } +clap = { version = "3.2", features = ["cargo", "derive"] } humantime-serde = "1.0" isocountry = "0.3.2" itertools = "0.10.3" @@ -23,6 +23,7 @@ serde = "1.0.126" serde_json = "1.0.66" thiserror = "1.0.29" tokio = {version = "1.19.1", features = ["full"] } +maxminddb = "0.23.0" mixnet-contract-common = { path = "../common/cosmwasm-smart-contracts/mixnet-contract" } network-defaults = { path = "../common/network-defaults" } diff --git a/explorer-api/README.md b/explorer-api/README.md index 0555275f14..e5d78f590f 100644 --- a/explorer-api/README.md +++ b/explorer-api/README.md @@ -11,10 +11,16 @@ Features: ## Running -Supply the environment variable `GEO_IP_SERVICE_API_KEY` with a key from https://app.ipbase.com/. +Supply the environment variable `GEOIP_DATABASE_PATH` with a path +that points to a GeoIP2 database file in binary format. Run as a service and reverse proxy with `nginx` to add `https` with Lets Encrypt. +Setup nginx to inject the request IP to the header `X-Real-IP`. + +Use https://github.com/maxmind/geoipupdate to automatically +provide and update the GeoIP2 database file. + # TODO / Known Issues ## TODO diff --git a/explorer-api/src/buy_terms/http.rs b/explorer-api/src/buy_terms/http.rs new file mode 100644 index 0000000000..5594a1a3be --- /dev/null +++ b/explorer-api/src/buy_terms/http.rs @@ -0,0 +1,26 @@ +// Copyright 2022 - Nym Technologies SA +// SPDX-License-Identifier: Apache-2.0 + +use crate::geo_ip::location::Location; +use crate::state::ExplorerApiStateContext; +use rocket::response::status; +use rocket::serde::json::Json; +use rocket::{Route, State}; +use rocket_okapi::okapi::openapi3::OpenApi; +use rocket_okapi::settings::OpenApiSettings; + +pub fn nym_terms_make_default_routes(settings: &OpenApiSettings) -> (Vec, OpenApi) { + openapi_get_routes_spec![settings: terms] +} + +#[openapi(tag = "terms")] +#[get("/")] +pub(crate) async fn terms( + _state: &State, + location: Location, +) -> Result, status::Forbidden> { + if location.iso_alpha2 == "US" { + return Err(status::Forbidden(Some("US government sucks".to_string()))); + } + Ok(Json("Nym Terms & Conditions: Welcome".to_string())) +} diff --git a/explorer-api/src/buy_terms/mod.rs b/explorer-api/src/buy_terms/mod.rs new file mode 100644 index 0000000000..d064c8bd6f --- /dev/null +++ b/explorer-api/src/buy_terms/mod.rs @@ -0,0 +1 @@ +pub(crate) mod http; diff --git a/explorer-api/src/country_statistics/geolocate.rs b/explorer-api/src/country_statistics/geolocate.rs index 10329c212a..60603a2aaf 100644 --- a/explorer-api/src/country_statistics/geolocate.rs +++ b/explorer-api/src/country_statistics/geolocate.rs @@ -1,12 +1,10 @@ // Copyright 2022 - Nym Technologies SA // SPDX-License-Identifier: Apache-2.0 -use crate::mix_nodes::location::{GeoLocation, Location}; +use crate::mix_nodes::location::Location; use crate::state::ExplorerApiStateContext; use log::{info, warn}; -use reqwest::Error as ReqwestError; use task::ShutdownListener; -use thiserror::Error; pub(crate) struct GeoLocateTask { state: ExplorerApiStateContext, @@ -19,13 +17,6 @@ impl GeoLocateTask { } pub(crate) fn start(mut self) { - if ::std::env::var("GEO_IP_SERVICE_API_KEY").is_err() { - error!( - "Env var GEO_IP_SERVICE_API_KEY is not set. Geolocation tasks will not be started." - ); - return; - } - info!("Spawning mix node locator task runner..."); tokio::spawn(async move { let mut interval_timer = tokio::time::interval(std::time::Duration::from_millis(50)); @@ -53,6 +44,8 @@ impl GeoLocateTask { .await .unwrap_or_default(); + let geo_ip = self.state.inner.geo_ip.0.clone(); + for (i, cache_item) in mixnode_bonds.values().enumerate() { if self .state @@ -65,99 +58,50 @@ impl GeoLocateTask { continue; } - // the mix node has not been located or is the cache time has expired - match locate(&cache_item.mix_node().host).await { - Ok(geo_location) => { - let location = Location::new(geo_location); + match geo_ip.query(&cache_item.mix_node().host) { + Ok(opt) => match opt { + Some(location) => { + let location = Location::new(location); - trace!( - "{} mix nodes already located. Ip {} is located in {:#?}", - i, - cache_item.mix_node().host, - location.three_letter_iso_country_code, - ); - - if i > 0 && (i % 100) == 0 { - info!( - "Located {} mixnodes...", - i + 1, + trace!( + "{} mix nodes already located. Ip {} is located in {:#?}", + i, + cache_item.mix_node().host, + location.three_letter_iso_country_code, ); + + if i > 0 && (i % 100) == 0 { + info!("Located {} mixnodes...", i + 1,); + } + + self.state + .inner + .mixnodes + .set_location(cache_item.mix_id(), Some(location)) + .await; + + // one node has been located, so return out of the loop + return; } - - self.state - .inner - .mixnodes - .set_location(cache_item.mix_id(), Some(location)) - .await; - - // one node has been located, so return out of the loop - return; - } - Err(e) => match e { - LocateError::ReqwestError(e) => warn!( - "❌ Oh no! Location for {} failed {}", - cache_item.mix_node().host, e - ), - LocateError::NotFound(e) => { - warn!( - "❌ Location for {} not found. Response body: {}", - cache_item.mix_node().host, e - ); + None => { + warn!("❌ Location for {} not found.", cache_item.mix_node().host); self.state .inner .mixnodes .set_location(cache_item.mix_id(), None) .await; - }, - LocateError::RateLimited(e) => warn!( - "❌ Oh no, we've been rate limited! Location for {} failed. Response body: {}", - cache_item.mix_node().host, e - ), + } }, - } + Err(e) => { + warn!( + "❌ Oh no! Location for {} failed. Error: {:#?}", + cache_item.mix_node().host, + e + ); + } + }; } trace!("All mix nodes located"); } } - -#[derive(Debug, Error)] -enum LocateError { - #[error("Oops, we have made too many requests and are being rate limited. Request body: {0}")] - RateLimited(String), - - #[error("Geolocation not found. Request body: {0}")] - NotFound(String), - - #[error(transparent)] - ReqwestError(#[from] ReqwestError), -} - -async fn locate(ip: &str) -> Result { - let api_key = ::std::env::var("GEO_IP_SERVICE_API_KEY") - .expect("Env var GEO_IP_SERVICE_API_KEY is not set"); - let uri = format!("{}/?apikey={}&ip={}", crate::GEO_IP_SERVICE, api_key, ip); - match reqwest::get(uri.clone()).await { - Ok(response) => { - if response.status() == 429 { - return Err(LocateError::RateLimited( - response - .text() - .await - .unwrap_or_else(|_| "(the response body is empty)".to_string()), - )); - } - if response.status() == 404 { - return Err(LocateError::NotFound( - response - .text() - .await - .unwrap_or_else(|_| "(the response body is empty)".to_string()), - )); - } - let location = response.json::().await?; - Ok(location) - } - Err(e) => Err(LocateError::ReqwestError(e)), - } -} diff --git a/explorer-api/src/geo_ip/GeoLite2-Country.mmdb b/explorer-api/src/geo_ip/GeoLite2-Country.mmdb new file mode 100644 index 0000000000..ef8c639a46 Binary files /dev/null and b/explorer-api/src/geo_ip/GeoLite2-Country.mmdb differ diff --git a/explorer-api/src/geo_ip/location.rs b/explorer-api/src/geo_ip/location.rs new file mode 100644 index 0000000000..82da674638 --- /dev/null +++ b/explorer-api/src/geo_ip/location.rs @@ -0,0 +1,112 @@ +// Copyright 2022 - Nym Technologies SA +// SPDX-License-Identifier: Apache-2.0 + +use isocountry::CountryCode; +use log::warn; +use maxminddb::{geoip2::Country, MaxMindDBError, Reader}; +use std::{net::IpAddr, str::FromStr, sync::Arc}; + +const DEFAULT_DATABASE_PATH: &str = "./src/geo_ip/GeoLite2-Country.mmdb"; + +#[derive(Debug)] +pub enum GeoIpError { + NoValidIP, + InternalError, +} + +// The current State implementation does not allow to fail on state +// creation, ie. returning Result<>. To avoid to use unwrap family, +// as a workaround, wrap the state inside an Option<> +// If Reader::open_readfile fails for some reason db will will be set to None +// and an error will be logged. +pub(crate) struct GeoIp { + pub(crate) db: Option>>, +} + +#[derive(Clone)] +pub(crate) struct ThreadsafeGeoIp(pub Arc); + +pub(crate) struct Location { + /// two-letter country code (ISO 3166-1 alpha-2) + pub(crate) iso_alpha2: String, + /// three-letter country code (ISO 3166-1 alpha-3) + pub(crate) iso_alpha3: String, + /// English country short name (ISO 3166-1) + pub(crate) name: String, +} + +impl GeoIp { + pub fn new() -> Self { + let db_path = std::env::var("GEOIP_DATABASE_PATH").unwrap_or_else(|e| { + warn!( + "Env variable GEOIP_DATABASE_PATH is not set: {} - Fallback to {}", + e, DEFAULT_DATABASE_PATH + ); + DEFAULT_DATABASE_PATH.to_string() + }); + let reader = Reader::open_readfile(&db_path) + .map_err(|e| { + error!("Fail to open GeoLite2 database file {}: {}", db_path, e); + }) + .ok(); + GeoIp { db: reader } + } + + pub fn query(&self, address: &str) -> Result, GeoIpError> { + let ip: IpAddr = FromStr::from_str(address).map_err(|e| { + error!("Fail to create IpAddr from {}: {}", &address, e); + GeoIpError::NoValidIP + })?; + let result = self + .db + .as_ref() + .ok_or_else(|| { + error!("No registered GeoIP database"); + GeoIpError::InternalError + })? + .lookup::(ip); + match &result { + Ok(v) => Ok(Some( + Location::try_from(v).map_err(|_| GeoIpError::InternalError)?, + )), + Err(e) => match e { + MaxMindDBError::AddressNotFoundError(_) => Ok(None), + _ => Err(GeoIpError::InternalError), + }, + } + } +} + +impl<'a> TryFrom<&Country<'a>> for Location { + type Error = String; + + fn try_from(country: &Country) -> Result { + let data = country.country.as_ref().ok_or_else(|| { + warn!("No Country data found"); + "No Country data found" + })?; + let iso_alpha2 = String::from(data.iso_code.ok_or_else(|| { + warn!("No iso alpha-2 code found in Country data {:#?}", data); + "No iso alpha-2 code found in Country data" + })?); + let iso_codes = CountryCode::for_alpha2(&iso_alpha2).map_err(|e| { + let message = format!( + "Fail to get iso codes from iso alpha-2 country code {}: {}", + &iso_alpha2, e + ); + warn!("{}", &message); + message + })?; + Ok(Location { + iso_alpha2, + iso_alpha3: String::from(iso_codes.alpha3()), + name: String::from(iso_codes.name()), + }) + } +} + +impl ThreadsafeGeoIp { + pub fn new() -> Self { + ThreadsafeGeoIp(Arc::new(GeoIp::new())) + } +} diff --git a/explorer-api/src/geo_ip/mod.rs b/explorer-api/src/geo_ip/mod.rs new file mode 100644 index 0000000000..d137da2c97 --- /dev/null +++ b/explorer-api/src/geo_ip/mod.rs @@ -0,0 +1 @@ +pub(crate) mod location; diff --git a/explorer-api/src/guards/location.rs b/explorer-api/src/guards/location.rs new file mode 100644 index 0000000000..dd1bfe1ff8 --- /dev/null +++ b/explorer-api/src/guards/location.rs @@ -0,0 +1,76 @@ +// Copyright 2022 - Nym Technologies SA +// SPDX-License-Identifier: Apache-2.0 + +use crate::geo_ip::location::{GeoIpError, Location}; +use crate::state::ExplorerApiStateContext; +use rocket::http::Status; +use rocket::request::FromRequest; +use rocket::request::Outcome; +use rocket::Request; +use rocket_okapi::gen::OpenApiGenerator; +use rocket_okapi::request::{OpenApiFromRequest, RequestHeaderInput}; + +const IP_HEADER: &str = "X-Real-IP"; + +#[derive(Debug)] +pub enum LocationError { + NoIP, + LocationNotFound, + InternalError, +} + +fn find_location(request: &Request<'_>) -> Result { + let ip = request + .headers() + .get_one(IP_HEADER) + .map(|f| f.to_string()) + .ok_or_else(|| { + error!("Header not found, {}", IP_HEADER); + (Status::Forbidden, LocationError::NoIP) + })?; + + let geo_ip = &request + .rocket() + .state::() + .ok_or((Status::InternalServerError, LocationError::InternalError))? // should never fail + .inner + .geo_ip; + + let location = geo_ip + .0 + .clone() + .query(&ip) + .map_err(|e| match e { + GeoIpError::NoValidIP => (Status::Forbidden, LocationError::NoIP), + GeoIpError::InternalError => { + (Status::InternalServerError, LocationError::InternalError) + } + })? + .ok_or_else(|| { + warn!("Fail to find a matching location for {}", ip); + (Status::Forbidden, LocationError::LocationNotFound) + })?; + Ok(location) +} + +#[rocket::async_trait] +impl<'r> FromRequest<'r> for Location { + type Error = LocationError; + + async fn from_request(request: &'r Request<'_>) -> Outcome { + match find_location(request) { + Ok(loc) => Outcome::Success(loc), + Err(e) => Outcome::Failure(e), + } + } +} + +impl<'a> OpenApiFromRequest<'a> for Location { + fn from_request_input( + _gen: &mut OpenApiGenerator, + _name: String, + _required: bool, + ) -> rocket_okapi::Result { + Ok(RequestHeaderInput::None) + } +} diff --git a/explorer-api/src/guards/mod.rs b/explorer-api/src/guards/mod.rs new file mode 100644 index 0000000000..d137da2c97 --- /dev/null +++ b/explorer-api/src/guards/mod.rs @@ -0,0 +1 @@ +pub(crate) mod location; diff --git a/explorer-api/src/http/mod.rs b/explorer-api/src/http/mod.rs index 31f638ee5f..f5559124a5 100644 --- a/explorer-api/src/http/mod.rs +++ b/explorer-api/src/http/mod.rs @@ -5,6 +5,7 @@ use rocket::{Build, Request, Rocket}; use rocket_cors::{AllowedHeaders, AllowedOrigins}; use rocket_okapi::swagger_ui::make_swagger_ui; +use crate::buy_terms::http::nym_terms_make_default_routes; use crate::country_statistics::http::country_statistics_make_default_routes; use crate::gateways::http::gateways_make_default_routes; use crate::http::swagger::get_docs; @@ -56,6 +57,7 @@ fn configure_rocket(state: ExplorerApiStateContext) -> Rocket { "/overview" => overview_make_default_routes(&openapi_settings), "/ping" => ping_make_default_routes(&openapi_settings), "/validators" => validators_make_default_routes(&openapi_settings), + "/terms" => nym_terms_make_default_routes(&openapi_settings), }; building_rocket diff --git a/explorer-api/src/main.rs b/explorer-api/src/main.rs index 4e144b56a9..cdd0c53608 100644 --- a/explorer-api/src/main.rs +++ b/explorer-api/src/main.rs @@ -8,11 +8,14 @@ use log::info; use network_defaults::setup_env; use task::ShutdownNotifier; +mod buy_terms; pub(crate) mod cache; mod client; pub(crate) mod commands; mod country_statistics; mod gateways; +mod geo_ip; +mod guards; mod helpers; mod http; mod mix_node; @@ -23,7 +26,6 @@ mod state; mod tasks; mod validators; -const GEO_IP_SERVICE: &str = "https://api.ipbase.com/json"; const COUNTRY_DATA_REFRESH_INTERVAL: u64 = 60 * 15; // every 15 minutes #[tokio::main] diff --git a/explorer-api/src/mix_node/models.rs b/explorer-api/src/mix_node/models.rs index 9e24aa69b8..169356859c 100644 --- a/explorer-api/src/mix_node/models.rs +++ b/explorer-api/src/mix_node/models.rs @@ -173,10 +173,10 @@ fn get_common_owner(delegations: &[Delegation]) -> Option { } fn get_common_mix_id(delegations: &[Delegation]) -> Option { - let mix_id = delegations.iter().next()?.node_id; + let mix_id = delegations.iter().next()?.mix_id; if delegations .iter() - .any(|delegation| delegation.node_id != mix_id) + .any(|delegation| delegation.mix_id != mix_id) { log::warn!("Unexpected different node identities when summing delegations"); return None; diff --git a/explorer-api/src/mix_nodes/location.rs b/explorer-api/src/mix_nodes/location.rs index 5591a2b6a7..9e92f8a831 100644 --- a/explorer-api/src/mix_nodes/location.rs +++ b/explorer-api/src/mix_nodes/location.rs @@ -1,7 +1,7 @@ // Copyright 2021 - Nym Technologies SA // SPDX-License-Identifier: Apache-2.0 -use crate::mix_nodes::utils::map_2_letter_to_3_letter_country_code; +use crate::geo_ip::location; use mixnet_contract_common::NodeId; use serde::{Deserialize, Serialize}; use std::collections::HashMap; @@ -36,19 +36,14 @@ pub(crate) struct Location { pub(crate) two_letter_iso_country_code: String, pub(crate) three_letter_iso_country_code: String, pub(crate) country_name: String, - pub(crate) lat: f32, - pub(crate) lng: f32, } impl Location { - pub(crate) fn new(geo_location: GeoLocation) -> Self { - let three_letter_iso_country_code = map_2_letter_to_3_letter_country_code(&geo_location); + pub(crate) fn new(location: location::Location) -> Self { Location { - country_name: geo_location.country_name, - two_letter_iso_country_code: geo_location.country_code, - three_letter_iso_country_code, - lat: geo_location.latitude, - lng: geo_location.longitude, + country_name: location.name, + two_letter_iso_country_code: location.iso_alpha2, + three_letter_iso_country_code: location.iso_alpha3, } } } diff --git a/explorer-api/src/mix_nodes/mod.rs b/explorer-api/src/mix_nodes/mod.rs index e2aced241a..82c2aedd1f 100644 --- a/explorer-api/src/mix_nodes/mod.rs +++ b/explorer-api/src/mix_nodes/mod.rs @@ -6,7 +6,6 @@ use std::time::Duration; pub(crate) mod http; pub(crate) mod location; pub(crate) mod models; -pub(crate) mod utils; pub(crate) const CACHE_REFRESH_RATE: Duration = Duration::from_secs(30); pub(crate) const CACHE_ENTRY_TTL: Duration = Duration::from_secs(60); diff --git a/explorer-api/src/state.rs b/explorer-api/src/state.rs index e8f89dbf3f..995de56b0c 100644 --- a/explorer-api/src/state.rs +++ b/explorer-api/src/state.rs @@ -7,6 +7,7 @@ use mixnet_contract_common::{IdentityKeyRef, NodeId}; use serde::{Deserialize, Serialize}; use crate::client::ThreadsafeValidatorClient; +use crate::geo_ip::location::ThreadsafeGeoIp; use validator_client::models::MixNodeBondAnnotated; use crate::country_statistics::country_nodes_distribution::{ @@ -30,6 +31,7 @@ pub struct ExplorerApiState { pub(crate) mixnodes: ThreadsafeMixNodesCache, pub(crate) ping: ThreadsafePingCache, pub(crate) validators: ThreadsafeValidatorCache, + pub(crate) geo_ip: ThreadsafeGeoIp, // TODO: discuss with @MS whether this is an appropriate spot for it pub(crate) validator_client: ThreadsafeValidatorClient, @@ -87,6 +89,7 @@ impl ExplorerApiStateContext { ping: ThreadsafePingCache::new(), validators: ThreadsafeValidatorCache::new(), validator_client: ThreadsafeValidatorClient::new(), + geo_ip: ThreadsafeGeoIp::new(), } } else { warn!( @@ -102,6 +105,7 @@ impl ExplorerApiStateContext { ping: ThreadsafePingCache::new(), validators: ThreadsafeValidatorCache::new(), validator_client: ThreadsafeValidatorClient::new(), + geo_ip: ThreadsafeGeoIp::new(), } } } diff --git a/explorer/src/api/constants.ts b/explorer/src/api/constants.ts index 442fd68ad1..5c2b044261 100644 --- a/explorer/src/api/constants.ts +++ b/explorer/src/api/constants.ts @@ -1,7 +1,7 @@ // master APIs export const API_BASE_URL = process.env.EXPLORER_API_URL; export const VALIDATOR_API_BASE_URL = process.env.VALIDATOR_API_URL; -export const { VALIDATOR_URL } = process.env; +export const VALIDATOR_BASE_URL = process.env.VALIDATOR_URL; export const BIG_DIPPER = process.env.BIG_DIPPER_URL; // specific API routes @@ -10,7 +10,7 @@ export const MIXNODE_PING = `${API_BASE_URL}/ping`; export const MIXNODES_API = `${API_BASE_URL}/mix-nodes`; export const MIXNODE_API = `${API_BASE_URL}/mix-node`; export const GATEWAYS_API = `${VALIDATOR_API_BASE_URL}/api/v1/gateways`; -export const VALIDATORS_API = `${VALIDATOR_URL}/validators`; +export const VALIDATORS_API = `${VALIDATOR_BASE_URL}/validators`; export const BLOCK_API = `${VALIDATOR_API_BASE_URL}/block`; export const COUNTRY_DATA_API = `${API_BASE_URL}/countries`; export const UPTIME_STORY_API = `${VALIDATOR_API_BASE_URL}/api/v1/status/mixnode`; // add ID then '/history' to this. diff --git a/explorer/src/components/Filters/Filters.tsx b/explorer/src/components/Filters/Filters.tsx index 4bf165d52e..a01d64361e 100644 --- a/explorer/src/components/Filters/Filters.tsx +++ b/explorer/src/components/Filters/Filters.tsx @@ -60,6 +60,7 @@ export const Filters = () => { const [showFilters, setShowFilters] = useState(false); const [isFiltered, setIsFiltered] = useState(false); const [filters, setFilters] = React.useState(); + const [upperSaturationValue, setUpperSaturationValue] = React.useState(100); const baseFilters = useRef(); const prevFilters = useRef(); @@ -67,11 +68,10 @@ export const Filters = () => { const handleToggleShowFilters = () => setShowFilters(!showFilters); const initialiseFilters = useCallback(async () => { - let upperSaturationValue; const allMixnodes = await Api.fetchMixnodes(); if (allMixnodes) { - upperSaturationValue = Math.round(Math.max(...allMixnodes.map((m) => m.stake_saturation)) * 100 + 1); - const initFilters = generateFilterSchema(upperSaturationValue); + setUpperSaturationValue(Math.round(Math.max(...allMixnodes.map((m) => m.stake_saturation)) * 100 + 1)); + const initFilters = generateFilterSchema(); baseFilters.current = initFilters; prevFilters.current = initFilters; setFilters(initFilters); @@ -79,8 +79,18 @@ export const Filters = () => { }, []); const handleOnChange = (id: EnumFilterKey, newValue: number[]) => { + if (id === 'stakeSaturation' && newValue[1] === 100) { + newValue.splice(1, 1, upperSaturationValue); + } setFilters((ftrs) => { - if (ftrs) return { ...ftrs, [id]: { ...ftrs[id], value: newValue } }; + if (ftrs) + return { + ...ftrs, + [id]: { + ...ftrs[id], + value: newValue, + }, + }; return undefined; }); }; diff --git a/explorer/src/components/Filters/filterSchema.ts b/explorer/src/components/Filters/filterSchema.ts index 7c097c9918..c5011114b1 100644 --- a/explorer/src/components/Filters/filterSchema.ts +++ b/explorer/src/components/Filters/filterSchema.ts @@ -1,6 +1,6 @@ import { EnumFilterKey, TFilters } from '../../typeDefs/filters'; -export const generateFilterSchema = (upperSaturationValue?: number) => ({ +export const generateFilterSchema = () => ({ profitMargin: { label: 'Profit margin (%)', id: EnumFilterKey.profitMargin, @@ -25,13 +25,12 @@ export const generateFilterSchema = (upperSaturationValue?: number) => ({ stakeSaturation: { label: 'Stake saturation (%)', id: EnumFilterKey.stakeSaturation, - value: [0, upperSaturationValue || 100], + value: [0, 100], isSmooth: true, - marks: [0, 10, 20, 30, 40, 50, 60, 70, 80, 90, upperSaturationValue || 100].map((value) => ({ + marks: [0, 10, 20, 30, 40, 50, 60, 70, 80, 90, 100].map((value) => ({ value: value < 100 ? value : 100, label: value < 100 ? value : '>100', })), - max: 100, tooltipInfo: "Select nodes with <100% saturation. Any additional stake above 100% saturation won't get rewards", }, routingScore: { diff --git a/gateway/Cargo.toml b/gateway/Cargo.toml index adf98f871d..f1876961bb 100644 --- a/gateway/Cargo.toml +++ b/gateway/Cargo.toml @@ -19,7 +19,7 @@ anyhow = "1.0.53" async-trait = { version = "0.1.51" } bip39 = "1.0.1" bs58 = "0.4.0" -clap = { version = "3.0.10", features = ["cargo", "derive"] } +clap = { version = "3.2", features = ["cargo", "derive"] } colored = "2.0" dashmap = "4.0" dirs = "4.0" @@ -55,6 +55,7 @@ coconut-interface = { path = "../common/coconut-interface", optional = true } credentials = { path = "../common/credentials" } config = { path = "../common/config" } crypto = { path = "../common/crypto" } +completions = { path = "../common/completions" } gateway-requests = { path = "gateway-requests" } mixnet-client = { path = "../common/client-libs/mixnet-client" } mixnode-common = { path = "../common/mixnode-common" } diff --git a/gateway/src/commands/mod.rs b/gateway/src/commands/mod.rs index 14c90031fd..223a77db8f 100644 --- a/gateway/src/commands/mod.rs +++ b/gateway/src/commands/mod.rs @@ -4,10 +4,13 @@ use std::{process, str::FromStr}; use crate::{config::Config, Cli}; +use clap::CommandFactory; use clap::Subcommand; use colored::Colorize; +use completions::{fig_generate, ArgShell}; use config::parse_validators; use crypto::bech32_address_validation; +use network_defaults::mainnet::read_var_if_not_default; use network_defaults::var_names::{ API_VALIDATOR, BECH32_PREFIX, CONFIGURED, NYMD_VALIDATOR, STATISTICS_SERVICE_DOMAIN_ADDRESS, }; @@ -34,6 +37,12 @@ pub(crate) enum Commands { /// Try to upgrade the gateway Upgrade(upgrade::Upgrade), + + /// Generate shell completions + Completions(ArgShell), + + /// Generate Fig specification + GenerateFigSpec, } // Configuration that can be overridden. @@ -55,12 +64,16 @@ pub(crate) struct OverrideConfig { } pub(crate) async fn execute(args: Cli) { + let bin_name = "nym-gateway"; + match &args.command { Commands::Init(m) => init::execute(m).await, Commands::NodeDetails(m) => node_details::execute(m).await, Commands::Run(m) => run::execute(m).await, Commands::Sign(m) => sign::execute(m), Commands::Upgrade(m) => upgrade::execute(m).await, + Commands::Completions(s) => s.generate(&mut crate::Cli::into_app(), bin_name), + Commands::GenerateFigSpec => fig_generate(&mut crate::Cli::into_app(), bin_name), } } @@ -97,27 +110,29 @@ pub(crate) fn override_config(mut config: Config, args: OverrideConfig) -> Confi .expect("the provided statistics service url is invalid!"), ); } else if std::env::var(CONFIGURED).is_ok() { - let raw_url = std::env::var(STATISTICS_SERVICE_DOMAIN_ADDRESS) - .expect("statistics service url not set"); - config = config.with_custom_statistics_service_url( - raw_url - .parse() - .expect("the provided statistics service url is invalid"), - ) + if let Some(raw_url) = read_var_if_not_default(STATISTICS_SERVICE_DOMAIN_ADDRESS) { + config = config.with_custom_statistics_service_url( + raw_url + .parse() + .expect("the provided statistics service url is invalid"), + ); + } } if let Some(raw_validators) = args.validator_apis { config = config.with_custom_validator_apis(parse_validators(&raw_validators)); } else if std::env::var(CONFIGURED).is_ok() { - let raw_validators = std::env::var(API_VALIDATOR).expect("api validator not set"); - config = config.with_custom_validator_apis(parse_validators(&raw_validators)) + if let Some(raw_validators) = read_var_if_not_default(API_VALIDATOR) { + config = config.with_custom_validator_apis(::config::parse_validators(&raw_validators)) + } } if let Some(ref raw_validators) = args.validators { config = config.with_custom_validator_nymd(parse_validators(raw_validators)); } else if std::env::var(CONFIGURED).is_ok() { - let raw_validators = std::env::var(NYMD_VALIDATOR).expect("nymd validator not set"); - config = config.with_custom_validator_nymd(parse_validators(&raw_validators)) + if let Some(raw_validators) = read_var_if_not_default(NYMD_VALIDATOR) { + config = config.with_custom_validator_nymd(::config::parse_validators(&raw_validators)) + } } if let Some(wallet_address) = args.wallet_address { diff --git a/mixnode/Cargo.toml b/mixnode/Cargo.toml index 259649ce0b..52b0999426 100644 --- a/mixnode/Cargo.toml +++ b/mixnode/Cargo.toml @@ -18,7 +18,7 @@ rust-version = "1.58.1" [dependencies] anyhow = "1.0.40" bs58 = "0.4.0" -clap = { version = "3.0.10", features = ["cargo", "derive"] } +clap = { version = "3.2", features = ["cargo", "derive"] } colored = "2.0" cupid = "0.6.1" dirs = "4.0" @@ -40,6 +40,7 @@ url = { version = "2.2", features = ["serde"] } ## internal config = { path="../common/config" } crypto = { path="../common/crypto" } +completions = { path="../common/completions" } mixnet-client = { path="../common/client-libs/mixnet-client" } mixnode-common = { path="../common/mixnode-common" } nonexhaustive-delayqueue = { path="../common/nonexhaustive-delayqueue" } diff --git a/mixnode/src/commands/mod.rs b/mixnode/src/commands/mod.rs index 62468ca809..f716953c11 100644 --- a/mixnode/src/commands/mod.rs +++ b/mixnode/src/commands/mod.rs @@ -4,8 +4,11 @@ use std::process; use crate::{config::Config, Cli}; +use clap::CommandFactory; use clap::Subcommand; use colored::Colorize; +use completions::{fig_generate, ArgShell}; +use config::defaults::mainnet::read_var_if_not_default; use config::{ defaults::var_names::{API_VALIDATOR, BECH32_PREFIX, CONFIGURED}, parse_validators, @@ -38,6 +41,12 @@ pub(crate) enum Commands { /// Show details of this mixnode NodeDetails(node_details::NodeDetails), + + /// Generate shell completions + Completions(ArgShell), + + /// Generate Fig specification + GenerateFigSpec, } // Configuration that can be overridden. @@ -53,6 +62,8 @@ struct OverrideConfig { } pub(crate) async fn execute(args: Cli) { + let bin_name = "nym-mixnode"; + match &args.command { Commands::Describe(m) => describe::execute(m), Commands::Init(m) => init::execute(m), @@ -60,6 +71,8 @@ pub(crate) async fn execute(args: Cli) { Commands::Sign(m) => sign::execute(m), Commands::Upgrade(m) => upgrade::execute(m), Commands::NodeDetails(m) => node_details::execute(m), + Commands::Completions(s) => s.generate(&mut crate::Cli::into_app(), bin_name), + Commands::GenerateFigSpec => fig_generate(&mut crate::Cli::into_app(), bin_name), } } @@ -85,8 +98,9 @@ fn override_config(mut config: Config, args: OverrideConfig) -> Config { if let Some(ref raw_validators) = args.validators { config = config.with_custom_validator_apis(parse_validators(raw_validators)); } else if std::env::var(CONFIGURED).is_ok() { - let raw_validators = std::env::var(API_VALIDATOR).expect("api validator not set"); - config = config.with_custom_validator_apis(parse_validators(&raw_validators)) + if let Some(raw_validators) = read_var_if_not_default(API_VALIDATOR) { + config = config.with_custom_validator_apis(::config::parse_validators(&raw_validators)) + } } if let Some(ref announce_host) = args.announce_host { diff --git a/nym-connect/Cargo.lock b/nym-connect/Cargo.lock index 7b443309c5..2dd92b4fd0 100644 --- a/nym-connect/Cargo.lock +++ b/nym-connect/Cargo.lock @@ -571,6 +571,25 @@ dependencies = [ "textwrap", ] +[[package]] +name = "clap_complete" +version = "3.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f7a2e0a962c45ce25afce14220bc24f9dade0a1787f185cecf96bfba7847cd8" +dependencies = [ + "clap", +] + +[[package]] +name = "clap_complete_fig" +version = "3.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed37b4c0c1214673eba6ad8ea31666626bf72be98ffb323067d973c48b4964b9" +dependencies = [ + "clap", + "clap_complete", +] + [[package]] name = "clap_derive" version = "3.2.18" @@ -708,6 +727,15 @@ dependencies = [ "memchr", ] +[[package]] +name = "completions" +version = "0.1.0" +dependencies = [ + "clap", + "clap_complete", + "clap_complete_fig", +] + [[package]] name = "config" version = "0.1.0" @@ -3347,6 +3375,7 @@ version = "1.0.2" dependencies = [ "clap", "client-core", + "completions", "config", "credential-storage", "crypto", diff --git a/nym-wallet/Cargo.lock b/nym-wallet/Cargo.lock index 629811ab68..a24afac884 100644 --- a/nym-wallet/Cargo.lock +++ b/nym-wallet/Cargo.lock @@ -2785,7 +2785,9 @@ dependencies = [ "config", "cosmrs", "cosmwasm-std", + "hex-literal", "mixnet-contract-common", + "network-defaults", "nym-types", "serde", "serde_json", diff --git a/nym-wallet/nym-wallet-recovery-cli/Cargo.toml b/nym-wallet/nym-wallet-recovery-cli/Cargo.toml index ff74de6709..e4ac1eb284 100644 --- a/nym-wallet/nym-wallet-recovery-cli/Cargo.toml +++ b/nym-wallet/nym-wallet-recovery-cli/Cargo.toml @@ -11,7 +11,7 @@ anyhow = "1.0" argon2 = "0.4" base64 = "0.13" bip39 = "1.0" -clap = { version = "3.2.20", features = ["derive"] } +clap = { version = "3.2", features = ["derive"] } log = "0.4" pretty_env_logger = "0.4" serde_json = "1.0.0" diff --git a/nym-wallet/nym-wallet-types/Cargo.toml b/nym-wallet/nym-wallet-types/Cargo.toml index 56d7f8ae72..322aca5c06 100644 --- a/nym-wallet/nym-wallet-types/Cargo.toml +++ b/nym-wallet/nym-wallet-types/Cargo.toml @@ -5,6 +5,7 @@ edition = "2021" rust-version = "1.58" [dependencies] +hex-literal = "0.3.3" serde = { version = "1.0", features = ["derive"] } serde_json = "1.0" strum = { version = "0.23", features = ["derive"] } @@ -14,6 +15,7 @@ cosmwasm-std = "1.0.0-beta8" cosmrs = { git = "https://github.com/neacsu/cosmos-rust", branch = "neacsu/feegrant_support" } config = { path = "../../common/config" } +network-defaults = { path = "../../common/network-defaults" } mixnet-contract-common = { path = "../../common/cosmwasm-smart-contracts/mixnet-contract" } validator-client = { path = "../../common/client-libs/validator-client", features = [ "nymd-client", diff --git a/nym-wallet/nym-wallet-types/src/network.rs b/nym-wallet/nym-wallet-types/src/network.rs index 8848b41da8..c3712355b1 100644 --- a/nym-wallet/nym-wallet-types/src/network.rs +++ b/nym-wallet/nym-wallet-types/src/network.rs @@ -1,11 +1,10 @@ // Copyright 2021 - Nym Technologies SA // SPDX-License-Identifier: Apache-2.0 -use config::defaults::all::Network as ConfigNetwork; -use config::defaults::{mainnet, qa, sandbox, DenomDetails}; -use nym_types::currency::DecCoin; +use config::defaults::{mainnet, DenomDetails, NymNetworkDetails}; +use nym_types::{currency::DecCoin, error::TypesError}; use serde::{Deserialize, Serialize}; -use std::fmt; +use std::{fmt, ops::Not, str::FromStr}; use strum::EnumIter; #[allow(clippy::upper_case_acronyms)] @@ -67,23 +66,141 @@ impl fmt::Display for Network { } } -impl From for Network { - fn from(network: ConfigNetwork) -> Self { +impl From for NymNetworkDetails { + fn from(network: Network) -> Self { match network { - ConfigNetwork::QA => Network::QA, - ConfigNetwork::SANDBOX => Network::SANDBOX, - ConfigNetwork::MAINNET => Network::MAINNET, - ConfigNetwork::CUSTOM { .. } => panic!("custom network is not supported"), + Network::QA => qa::network_details(), + Network::SANDBOX => sandbox::network_details(), + Network::MAINNET => NymNetworkDetails::new_mainnet(), } } } -impl From for ConfigNetwork { - fn from(network: Network) -> Self { - match network { - Network::QA => ConfigNetwork::QA, - Network::SANDBOX => ConfigNetwork::SANDBOX, - Network::MAINNET => ConfigNetwork::MAINNET, +impl FromStr for Network { + type Err = TypesError; + + fn from_str(s: &str) -> Result { + match s.to_lowercase().as_str() { + "qa" => Ok(Network::QA), + "sandbox" => Ok(Network::SANDBOX), + "mainnet" => Ok(Network::MAINNET), + _ => Err(TypesError::UnknownNetwork(s.to_string())), + } + } +} + +fn parse_optional_str(raw: &str) -> Option { + raw.is_empty().not().then(|| raw.into()) +} + +mod sandbox { + use network_defaults::{ChainDetails, DenomDetails, NymContracts, ValidatorDetails}; + + use super::parse_optional_str; + + pub(crate) const BECH32_PREFIX: &str = "nymt"; + + pub(crate) const MIX_DENOM: DenomDetails = DenomDetails::new("unymt", "nymt", 6); + pub(crate) const STAKE_DENOM: DenomDetails = DenomDetails::new("unyxt", "nyxt", 6); + + pub(crate) const MIXNET_CONTRACT_ADDRESS: &str = "nymt1ghd753shjuwexxywmgs4xz7x2q732vcnstz02j"; + pub(crate) const VESTING_CONTRACT_ADDRESS: &str = "nymt14ejqjyq8um4p3xfqj74yld5waqljf88fn549lh"; + pub(crate) const BANDWIDTH_CLAIM_CONTRACT_ADDRESS: &str = + "nymt17p9rzwnnfxcjp32un9ug7yhhzgtkhvl9f8xzkv"; + pub(crate) const COCONUT_BANDWIDTH_CONTRACT_ADDRESS: &str = + "nymt1nz0r0au8aj6dc00wmm3ufy4g4k86rjzlgq608r"; + pub(crate) const MULTISIG_CONTRACT_ADDRESS: &str = + "nymt1k8re7jwz6rnnwrktnejdwkwnncte7ek7kk6fvg"; + pub(crate) const _ETH_CONTRACT_ADDRESS: [u8; 20] = + hex_literal::hex!("8e0DcFF7F3085235C32E845f3667aEB3f1e83133"); + pub(crate) const _ETH_ERC20_CONTRACT_ADDRESS: [u8; 20] = + hex_literal::hex!("E8883BAeF3869e14E4823F46662e81D4F7d2A81F"); + //pub(crate) const REWARDING_VALIDATOR_ADDRESS: &str = + //"nymt1jh0s6qu6tuw9ut438836mmn7f3f2wencrnmdj4"; + + //pub(crate) const STATISTICS_SERVICE_DOMAIN_ADDRESS: &str = "http://0.0.0.0"; + pub(crate) fn validators() -> Vec { + vec![ValidatorDetails::new( + "https://sandbox-validator.nymtech.net", + Some("https://sandbox-validator.nymtech.net/api"), + )] + } + + pub(crate) fn network_details() -> network_defaults::NymNetworkDetails { + network_defaults::NymNetworkDetails { + chain_details: ChainDetails { + bech32_account_prefix: BECH32_PREFIX.to_string(), + mix_denom: MIX_DENOM.into(), + stake_denom: STAKE_DENOM.into(), + }, + endpoints: validators(), + contracts: NymContracts { + mixnet_contract_address: parse_optional_str(MIXNET_CONTRACT_ADDRESS), + vesting_contract_address: parse_optional_str(VESTING_CONTRACT_ADDRESS), + bandwidth_claim_contract_address: parse_optional_str( + BANDWIDTH_CLAIM_CONTRACT_ADDRESS, + ), + coconut_bandwidth_contract_address: parse_optional_str( + COCONUT_BANDWIDTH_CONTRACT_ADDRESS, + ), + multisig_contract_address: parse_optional_str(MULTISIG_CONTRACT_ADDRESS), + }, + } + } +} + +mod qa { + use network_defaults::{ChainDetails, DenomDetails, NymContracts, ValidatorDetails}; + + use super::parse_optional_str; + + pub(crate) const BECH32_PREFIX: &str = "n"; + + pub(crate) const MIX_DENOM: DenomDetails = DenomDetails::new("unym", "nym", 6); + pub(crate) const STAKE_DENOM: DenomDetails = DenomDetails::new("unyx", "nyx", 6); + + pub(crate) const MIXNET_CONTRACT_ADDRESS: &str = + "n1suhgf5svhu4usrurvxzlgn54ksxmn8gljarjtxqnapv8kjnp4nrsd3qaep"; + pub(crate) const VESTING_CONTRACT_ADDRESS: &str = + "n1xr3rq8yvd7qplsw5yx90ftsr2zdhg4e9z60h5duusgxpv72hud3sjkxkav"; + pub(crate) const BANDWIDTH_CLAIM_CONTRACT_ADDRESS: &str = + "n19lc9u84cz0yz3fww5283nucc9yvr8gsjmgeul0"; + pub(crate) const COCONUT_BANDWIDTH_CONTRACT_ADDRESS: &str = + "n1ghd753shjuwexxywmgs4xz7x2q732vcn7ty4yw"; + pub(crate) const MULTISIG_CONTRACT_ADDRESS: &str = "n17p9rzwnnfxcjp32un9ug7yhhzgtkhvl988qccs"; + pub(crate) const _ETH_CONTRACT_ADDRESS: [u8; 20] = + hex_literal::hex!("0000000000000000000000000000000000000000"); + pub(crate) const _ETH_ERC20_CONTRACT_ADDRESS: [u8; 20] = + hex_literal::hex!("0000000000000000000000000000000000000000"); + //pub(crate) const REWARDING_VALIDATOR_ADDRESS: &str = "n1tfzd4qz3a45u8p4mr5zmzv66457uwjgcl05jdq"; + + //pub(crate) const STATISTICS_SERVICE_DOMAIN_ADDRESS: &str = "http://0.0.0.0"; + pub(crate) fn validators() -> Vec { + vec![ValidatorDetails::new( + "https://qa-validator.nymtech.net", + Some("https://qa-validator-api.nymtech.net/api"), + )] + } + + pub(crate) fn network_details() -> network_defaults::NymNetworkDetails { + network_defaults::NymNetworkDetails { + chain_details: ChainDetails { + bech32_account_prefix: BECH32_PREFIX.to_string(), + mix_denom: MIX_DENOM.into(), + stake_denom: STAKE_DENOM.into(), + }, + endpoints: validators(), + contracts: NymContracts { + mixnet_contract_address: parse_optional_str(MIXNET_CONTRACT_ADDRESS), + vesting_contract_address: parse_optional_str(VESTING_CONTRACT_ADDRESS), + bandwidth_claim_contract_address: parse_optional_str( + BANDWIDTH_CLAIM_CONTRACT_ADDRESS, + ), + coconut_bandwidth_contract_address: parse_optional_str( + COCONUT_BANDWIDTH_CONTRACT_ADDRESS, + ), + multisig_contract_address: parse_optional_str(MULTISIG_CONTRACT_ADDRESS), + }, } } } diff --git a/nym-wallet/src-tauri/src/config/mod.rs b/nym-wallet/src-tauri/src/config/mod.rs index 8fcfc95a6c..9da3a1b73e 100644 --- a/nym-wallet/src-tauri/src/config/mod.rs +++ b/nym-wallet/src-tauri/src/config/mod.rs @@ -13,8 +13,7 @@ use strum::IntoEnumIterator; use url::Url; use validator_client::nymd::AccountId as CosmosAccountId; -use config::defaults::all::Network; -use config::defaults::{all::SupportedNetworks, ValidatorDetails}; +use config::defaults::{DenomDetailsOwned, NymNetworkDetails, ValidatorDetails}; use nym_wallet_types::network::Network as WalletNetwork; use nym_wallet_types::network_config; @@ -138,7 +137,7 @@ impl Config { // One file per network for (network, config) in &self.networks { - let network = match Network::from_str(network).map(Into::into) { + let network = match WalletNetwork::from_str(network) { Ok(network) => network, Err(err) => { log::warn!("Unexpected name for network configuration, not saving: {err}"); @@ -198,7 +197,7 @@ impl Config { &self, network: WalletNetwork, ) -> impl Iterator + '_ { - self.base.networks.validators(network.into()).map(|v| { + self.base.networks.validators(&network).map(|v| { v.clone() .try_into() .expect("The hardcoded validators are assumed to be valid urls") @@ -218,7 +217,7 @@ impl Config { pub fn get_mixnet_contract_address(&self, network: WalletNetwork) -> CosmosAccountId { self.base .networks - .mixnet_contract_address(network.into()) + .mixnet_contract_address(&network) .expect("No mixnet contract address found in config") .parse() .expect("Wrong format for mixnet contract address") @@ -227,7 +226,7 @@ impl Config { pub fn get_vesting_contract_address(&self, network: WalletNetwork) -> CosmosAccountId { self.base .networks - .vesting_contract_address(network.into()) + .vesting_contract_address(&network) .expect("No vesting contract address found in config") .parse() .expect("Wrong format for vesting contract address") @@ -236,7 +235,7 @@ impl Config { pub fn get_bandwidth_claim_contract_address(&self, network: WalletNetwork) -> CosmosAccountId { self.base .networks - .bandwidth_claim_contract_address(network.into()) + .bandwidth_claim_contract_address(&network) .expect("No bandwidth claim contract address found in config") .parse() .expect("Wrong format for bandwidth claim contract address") @@ -421,6 +420,90 @@ impl fmt::Display for OptionalValidators { } } +#[derive(Clone, Debug, Default, Deserialize, Serialize, PartialEq, Eq)] +struct SupportedNetworks { + networks: HashMap, +} + +impl SupportedNetworks { + fn new(support: Vec) -> Self { + SupportedNetworks { + networks: support + .into_iter() + .map(|n| { + let details = NetworkDetails::from(NymNetworkDetails::from(n)); + (n, details) + }) + .collect(), + } + } + + fn mixnet_contract_address(&self, network: &WalletNetwork) -> Option<&str> { + self.networks + .get(network) + .map(|network_details| network_details.mixnet_contract_address.as_str()) + } + + fn vesting_contract_address(&self, network: &WalletNetwork) -> Option<&str> { + self.networks + .get(network) + .map(|network_details| network_details.vesting_contract_address.as_str()) + } + + fn bandwidth_claim_contract_address(&self, network: &WalletNetwork) -> Option<&str> { + self.networks + .get(network) + .map(|network_details| network_details.bandwidth_claim_contract_address.as_str()) + } + + fn validators(&self, network: &WalletNetwork) -> impl Iterator { + self.networks + .get(network) + .map(|network_details| &network_details.validators) + .into_iter() + .flatten() + } +} + +// Simplified variant of NymNetworkDetails for serialization to config file +#[derive(Clone, Debug, Deserialize, Serialize, PartialEq, Eq)] +struct NetworkDetails { + bech32_prefix: String, + mix_denom: DenomDetailsOwned, + stake_denom: DenomDetailsOwned, + mixnet_contract_address: String, + vesting_contract_address: String, + bandwidth_claim_contract_address: String, + statistics_service_url: String, + validators: Vec, +} + +// Possibly a bit naff, but WalletNetwork is converted into the more general NymNetworkDetails, which here +// is converted to the format specific for serialization to config +impl From for NetworkDetails { + fn from(details: NymNetworkDetails) -> Self { + NetworkDetails { + bech32_prefix: details.chain_details.bech32_account_prefix, + mix_denom: details.chain_details.mix_denom, + stake_denom: details.chain_details.stake_denom, + mixnet_contract_address: details + .contracts + .mixnet_contract_address + .unwrap_or_default(), + vesting_contract_address: details + .contracts + .vesting_contract_address + .unwrap_or_default(), + bandwidth_claim_contract_address: details + .contracts + .bandwidth_claim_contract_address + .unwrap_or_default(), + statistics_service_url: "".to_string(), + validators: details.endpoints, + } + } +} + #[cfg(test)] mod tests { use super::*; diff --git a/nym-wallet/src-tauri/src/operations/mixnet/account.rs b/nym-wallet/src-tauri/src/operations/mixnet/account.rs index 5d092e3f44..4fa29620ee 100644 --- a/nym-wallet/src-tauri/src/operations/mixnet/account.rs +++ b/nym-wallet/src-tauri/src/operations/mixnet/account.rs @@ -4,8 +4,7 @@ use crate::network_config; use crate::state::{WalletAccountIds, WalletState}; use crate::wallet_storage::{self, DEFAULT_LOGIN_ID}; use bip39::{Language, Mnemonic}; -use config::defaults::all::Network; -use config::defaults::COSMOS_DERIVATION_PATH; +use config::defaults::{NymNetworkDetails, COSMOS_DERIVATION_PATH}; use cosmrs::bip32::DerivationPath; use itertools::Itertools; use nym_types::account::{Account, AccountEntry, Balance}; @@ -175,8 +174,8 @@ async fn run_connection_test( untested_api_urls: HashMap>, config: &Config, ) -> ( - HashMap>, - HashMap>, + HashMap>, + HashMap>, ) { let mixnet_contract_address = WalletNetwork::iter() .map(|network| (network.into(), config.get_mixnet_contract_address(network))) @@ -199,8 +198,8 @@ async fn run_connection_test( } fn create_clients( - nymd_urls: &HashMap>, - api_urls: &HashMap>, + nymd_urls: &HashMap>, + api_urls: &HashMap>, default_nymd_urls: &HashMap, default_api_urls: &HashMap, config: &Config, @@ -239,8 +238,8 @@ fn create_clients( log::info!("Connecting to: nymd_url: {nymd_url} for {network}"); log::info!("Connecting to: api_url: {api_url} for {network}"); - let network_details = Network::from(network) - .details() + let network_details = NymNetworkDetails::from(network) + .clone() .with_mixnet_contract(Some(config.get_mixnet_contract_address(network).as_ref())) .with_vesting_contract(Some(config.get_vesting_contract_address(network).as_ref())) .with_bandwidth_claim_contract(Some( @@ -260,7 +259,7 @@ fn create_clients( } fn select_random_responding_url( - urls: &HashMap>, + urls: &HashMap>, network: WalletNetwork, ) -> Option { urls.get(&network.into()).and_then(|urls| { @@ -273,7 +272,7 @@ fn select_random_responding_url( } fn select_first_responding_url( - urls: &HashMap>, + urls: &HashMap>, network: WalletNetwork, //config: &Config, ) -> Option { @@ -426,8 +425,8 @@ pub async fn add_account_for_password( let address = { let state = state.read().await; - let network: Network = state.current_network().into(); - derive_address(mnemonic, &network.bech32_prefix())?.to_string() + let network: NymNetworkDetails = state.current_network().into(); + derive_address(mnemonic, &network.chain_details.bech32_account_prefix)?.to_string() }; // Re-read all the acccounts from the wallet to reset the state, rather than updating it @@ -466,10 +465,14 @@ async fn set_state_with_all_accounts( let mnemonic = account.mnemonic(); let addresses: HashMap = WalletNetwork::iter() .map(|network| { - let config_network: Network = network.into(); + let config_network: NymNetworkDetails = network.into(); ( network, - derive_address(mnemonic.clone(), &config_network.bech32_prefix()).unwrap(), + derive_address( + mnemonic.clone(), + &config_network.chain_details.bech32_account_prefix, + ) + .unwrap(), ) }) .collect(); diff --git a/nym-wallet/src-tauri/src/state.rs b/nym-wallet/src-tauri/src/state.rs index 572c0bddf4..e36dfe1b5c 100644 --- a/nym-wallet/src-tauri/src/state.rs +++ b/nym-wallet/src-tauri/src/state.rs @@ -1,6 +1,7 @@ use crate::config; use crate::error::BackendError; use crate::simulate::SimulateResult; +use ::config::defaults::NymNetworkDetails; use cosmwasm_std::Decimal; use itertools::Itertools; use log::warn; @@ -215,8 +216,9 @@ impl WalletStateInner { } pub fn register_default_denoms(&mut self, network: Network) { + let details = NymNetworkDetails::from(network); self.registered_coins - .insert(network, RegisteredCoins::default_denoms(network.into())); + .insert(network, RegisteredCoins::default_denoms(&details)); } pub fn set_network(&mut self, network: Network) { diff --git a/service-providers/network-requester/Cargo.toml b/service-providers/network-requester/Cargo.toml index 1806a75ac4..e4174de920 100644 --- a/service-providers/network-requester/Cargo.toml +++ b/service-providers/network-requester/Cargo.toml @@ -11,7 +11,7 @@ edition = "2021" [dependencies] async-trait = { version = "0.1.51" } -clap = "3.2" +clap = {version = "3.2", features = ["derive"]} dirs = "4.0" futures = "0.3.24" ipnetwork = "0.20.0" @@ -30,6 +30,7 @@ tokio-tungstenite = "0.17.2" # internal network-defaults = { path = "../../common/network-defaults" } nymsphinx = { path = "../../common/nymsphinx" } +completions = { path = "../../common/completions" } ordered-buffer = {path = "../../common/socks5/ordered-buffer"} proxy-helpers = { path = "../../common/socks5/proxy-helpers" } socks5-requests = { path = "../../common/socks5/requests" } diff --git a/service-providers/network-requester/src/main.rs b/service-providers/network-requester/src/main.rs index 4f30fb77e8..ce1762f95b 100644 --- a/service-providers/network-requester/src/main.rs +++ b/service-providers/network-requester/src/main.rs @@ -1,7 +1,10 @@ // Copyright 2020 - Nym Technologies SA // SPDX-License-Identifier: Apache-2.0 -use clap::{App, Arg, ArgMatches}; +use clap::CommandFactory; +use clap::Subcommand; +use clap::{Args, Parser}; +use completions::{fig_generate, ArgShell}; use network_defaults::DEFAULT_WEBSOCKET_LISTENING_PORT; use nymsphinx::addressing::clients::Recipient; @@ -12,75 +15,93 @@ mod core; mod statistics; mod websocket; -const OPEN_PROXY_ARG: &str = "open-proxy"; -const WS_PORT: &str = "websocket-port"; const ENABLE_STATISTICS: &str = "enable-statistics"; -const STATISTICS_RECIPIENT: &str = "statistics-recipient"; -fn parse_args() -> ArgMatches { - App::new("Nym Network Requester") - .version(env!("CARGO_PKG_VERSION")) - .author("Nymtech") - .arg( - Arg::with_name(OPEN_PROXY_ARG) - .help("specifies whether this network requester should run in 'open-proxy' mode") - .long(OPEN_PROXY_ARG) - .short('o'), - ) - .arg( - Arg::with_name(WS_PORT) - .help("websocket port to bind to") - .long(WS_PORT) - .short('p') - .takes_value(true), - ) - .arg( - Arg::with_name(ENABLE_STATISTICS) - .help("enable service anonymized statistics that get sent to a statistics aggregator server") - .long(ENABLE_STATISTICS), - ) - .arg( - Arg::with_name(STATISTICS_RECIPIENT) - .help("mixnet client address where a statistics aggregator is running. The default value is a Nym aggregator client") - .long(STATISTICS_RECIPIENT) - .requires(ENABLE_STATISTICS) - .takes_value(true), - ) - .get_matches() +#[derive(Args)] +struct Run { + /// Specifies whether this network requester should run in 'open-proxy' mode + open_proxy: bool, + + /// Websocket port to bind to + websocket_port: Option, + + /// Enable service anonymized statistics that get sent to a statistics aggregator server + enable_statistics: bool, + + /// Mixnet client address where a statistics aggregator is running. The default value is a Nym aggregator client + statistics_recipient: Option, +} + +impl Run { + async fn execute(&self) { + if self.open_proxy { + println!("\n\nYOU HAVE STARTED IN 'OPEN PROXY' MODE. ANYONE WITH YOUR CLIENT ADDRESS CAN MAKE REQUESTS FROM YOUR MACHINE. PLEASE QUIT IF YOU DON'T UNDERSTAND WHAT YOU'RE DOING.\n\n"); + } + + if self.enable_statistics { + println!("\n\nTHE NETWORK REQUESTER STATISTICS ARE ENABLED. IT WILL COLLECT AND SEND ANONYMIZED STATISTICS TO A CENTRAL SERVER. PLEASE QUIT IF YOU DON'T WANT THIS TO HAPPEN AND START WITHOUT THE {} FLAG .\n\n", ENABLE_STATISTICS); + } + + let stats_provider_addr = self + .statistics_recipient + .as_ref() + .map(Recipient::try_from_base58_string) + .transpose() + .unwrap_or(None); + + let uri = format!( + "ws://localhost:{}", + self.websocket_port + .as_ref() + .unwrap_or(&DEFAULT_WEBSOCKET_LISTENING_PORT.to_string()) + ); + + println!("Starting socks5 service provider:"); + let mut server = core::ServiceProvider::new( + uri, + self.open_proxy, + self.enable_statistics, + stats_provider_addr, + ); + server.run().await; + } +} + +#[derive(Subcommand)] +pub(crate) enum Commands { + /// Run network requester + Run(Run), + + /// Generate shell completions + Completions(ArgShell), + + /// Generate Fig specification + GenerateFigSpec, +} + +#[derive(Parser)] +#[clap(author = "Nymtech", version, about)] +struct Cli { + #[clap(subcommand)] + command: Commands, +} + +pub(crate) async fn execute(args: Cli) { + let bin_name = "nym-network-requester"; + + match &args.command { + Commands::Run(r) => r.execute().await, + Commands::Completions(s) => s.generate(&mut crate::Cli::into_app(), bin_name), + Commands::GenerateFigSpec => fig_generate(&mut crate::Cli::into_app(), bin_name), + } } #[tokio::main] async fn main() { setup_logging(); - let matches = parse_args(); + let args = Cli::parse(); - let open_proxy = matches.is_present(OPEN_PROXY_ARG); - if open_proxy { - println!("\n\nYOU HAVE STARTED IN 'OPEN PROXY' MODE. ANYONE WITH YOUR CLIENT ADDRESS CAN MAKE REQUESTS FROM YOUR MACHINE. PLEASE QUIT IF YOU DON'T UNDERSTAND WHAT YOU'RE DOING.\n\n"); - } - - let enable_statistics = matches.is_present(ENABLE_STATISTICS); - if enable_statistics { - println!("\n\nTHE NETWORK REQUESTER STATISTICS ARE ENABLED. IT WILL COLLECT AND SEND ANONYMIZED STATISTICS TO A CENTRAL SERVER. PLEASE QUIT IF YOU DON'T WANT THIS TO HAPPEN AND START WITHOUT THE {} FLAG .\n\n", ENABLE_STATISTICS); - } - - let stats_provider_addr = matches - .value_of(STATISTICS_RECIPIENT) - .map(Recipient::try_from_base58_string) - .transpose() - .unwrap_or(None); - - let uri = format!( - "ws://localhost:{}", - matches - .value_of(WS_PORT) - .unwrap_or(&DEFAULT_WEBSOCKET_LISTENING_PORT.to_string()) - ); - - println!("Starting socks5 service provider:"); - let mut server = - core::ServiceProvider::new(uri, open_proxy, enable_statistics, stats_provider_addr); - server.run().await; + execute(args).await; } fn setup_logging() { diff --git a/tools/nym-cli/Cargo.toml b/tools/nym-cli/Cargo.toml index fe7a243f85..dcc92dfc36 100644 --- a/tools/nym-cli/Cargo.toml +++ b/tools/nym-cli/Cargo.toml @@ -7,9 +7,9 @@ edition = "2021" [dependencies] base64 = "0.13.0" bs58 = "0.4" -clap = { version = "3.2.8", features = ["derive"] } -clap_complete = "3.2.4" -clap_complete_fig = "3.2.4" +clap = { version = "3.2", features = ["derive"] } +clap_complete = "3.2" +clap_complete_fig = "3.2" dotenv = "0.15.0" log = "0.4" pretty_env_logger = "0.4" diff --git a/validator-api/src/contract_cache/mod.rs b/validator-api/src/contract_cache/mod.rs index 486bf05d8e..851986ff97 100644 --- a/validator-api/src/contract_cache/mod.rs +++ b/validator-api/src/contract_cache/mod.rs @@ -119,11 +119,11 @@ impl ValidatorCacheRefresher { } } - async fn get_performance(&self, node_id: NodeId, epoch: Interval) -> Option { + async fn get_performance(&self, mix_id: NodeId, epoch: Interval) -> Option { self.storage .as_ref()? .get_average_mixnode_uptime_in_the_last_24hrs( - node_id, + mix_id, epoch.current_epoch_end_unix_timestamp(), ) .await diff --git a/validator-api/src/main.rs b/validator-api/src/main.rs index 1d5f2c28a7..68befdf169 100644 --- a/validator-api/src/main.rs +++ b/validator-api/src/main.rs @@ -10,6 +10,7 @@ use crate::network_monitor::NetworkMonitorBuilder; use crate::node_status_api::uptime_updater::HistoricalUptimeUpdater; use crate::nymd_client::Client; use crate::storage::ValidatorApiStorage; +use ::config::defaults::mainnet::read_var_if_not_default; use ::config::defaults::setup_env; #[cfg(feature = "coconut")] use ::config::defaults::var_names::API_VALIDATOR; @@ -294,8 +295,9 @@ fn override_config(mut config: Config, matches: &ArgMatches) -> Config { if let Some(raw_validators) = matches.value_of(API_VALIDATORS_ARG) { config = config.with_custom_validator_apis(::config::parse_validators(raw_validators)); } else if std::env::var(CONFIGURED).is_ok() { - let raw_validators = std::env::var(API_VALIDATOR).expect("api validator not set"); - config = config.with_custom_validator_apis(::config::parse_validators(&raw_validators)) + if let Some(raw_validators) = read_var_if_not_default(API_VALIDATOR) { + config = config.with_custom_validator_apis(::config::parse_validators(&raw_validators)) + } } if let Some(raw_validator) = matches.value_of(NYMD_VALIDATOR_ARG) { @@ -312,9 +314,9 @@ fn override_config(mut config: Config, matches: &ArgMatches) -> Config { if let Some(mixnet_contract) = matches.value_of(MIXNET_CONTRACT_ARG) { config = config.with_custom_mixnet_contract(mixnet_contract) } else if std::env::var(CONFIGURED).is_ok() { - let mixnet_contract = - std::env::var(MIXNET_CONTRACT_ADDRESS).expect("mixnet contract not set"); - config = config.with_custom_mixnet_contract(mixnet_contract) + if let Some(mixnet_contract) = read_var_if_not_default(MIXNET_CONTRACT_ADDRESS) { + config = config.with_custom_mixnet_contract(mixnet_contract) + } } if let Some(mnemonic) = matches.value_of(MNEMONIC_ARG) {