diff --git a/nym-wallet/src-tauri/src/config/mod.rs b/nym-wallet/src-tauri/src/config/mod.rs index c0972a77fa..7510d80612 100644 --- a/nym-wallet/src-tauri/src/config/mod.rs +++ b/nym-wallet/src-tauri/src/config/mod.rs @@ -1,6 +1,7 @@ // Copyright 2021 - Nym Technologies SA // SPDX-License-Identifier: Apache-2.0 +use crate::network_config; use crate::platform_constants::{CONFIG_DIR_NAME, CONFIG_FILENAME}; use crate::{error::BackendError, network::Network as WalletNetwork}; use config::defaults::all::Network; @@ -18,6 +19,9 @@ use url::Url; pub const REMOTE_SOURCE_OF_VALIDATOR_URLS: &str = "https://nymtech.net/.wellknown/wallet/validators.json"; +const CURRENT_GLOBAL_CONFIG_VERSION: u32 = 1; +const CURRENT_NETWORK_CONFIG_VERSION: u32 = 1; + #[derive(Debug, Default, Clone, PartialEq, Eq)] pub struct Config { // Base configuration is not part of the configuration file as it's not intended to be changed. @@ -36,27 +40,25 @@ struct Base { networks: SupportedNetworks, } -#[derive(Debug, Default, Deserialize, Serialize, Clone, PartialEq, Eq)] +#[derive(Debug, Deserialize, Serialize, Clone, PartialEq, Eq)] pub struct GlobalConfig { + version: Option, // TODO: there are no global settings (yet) } -#[derive(Debug, Default, Deserialize, Serialize, Clone, PartialEq, Eq)] +#[derive(Debug, Deserialize, Serialize, Clone, PartialEq, Eq)] pub struct NetworkConfig { + version: Option, + // User selected urls selected_nymd_url: Option, selected_api_url: Option, - // Additional user provided validators + // Additional user provided validators. + // It is an option for the purpuse of file serialization. validator_urls: Option>, } -impl NetworkConfig { - fn validators(&self) -> impl Iterator { - self.validator_urls.iter().flat_map(|v| v.iter()) - } -} - impl Default for Base { fn default() -> Self { let networks = WalletNetwork::iter().map(Into::into).collect(); @@ -66,6 +68,31 @@ impl Default for Base { } } +impl Default for GlobalConfig { + fn default() -> Self { + Self { + version: Some(CURRENT_GLOBAL_CONFIG_VERSION), + } + } +} + +impl Default for NetworkConfig { + fn default() -> Self { + Self { + version: Some(CURRENT_NETWORK_CONFIG_VERSION), + selected_nymd_url: None, + selected_api_url: None, + validator_urls: None, + } + } +} + +impl NetworkConfig { + fn validators(&self) -> impl Iterator { + self.validator_urls.iter().flat_map(|v| v.iter()) + } +} + impl Config { fn root_directory() -> PathBuf { tauri::api::path::config_dir().expect("Failed to get config directory") @@ -259,11 +286,11 @@ impl Config { } pub fn add_validator_url(&mut self, url: ValidatorUrl, network: WalletNetwork) { - if let Some(net) = self.networks.get_mut(&network.as_key()) { - if let Some(ref mut urls) = net.validator_urls { + if let Some(network_config) = self.networks.get_mut(&network.as_key()) { + if let Some(ref mut urls) = network_config.validator_urls { urls.push(url); } else { - net.validator_urls = Some(vec![url]); + network_config.validator_urls = Some(vec![url]); } } else { self.networks.insert( @@ -276,9 +303,13 @@ impl Config { } } - #[allow(unused)] - pub fn remove_validator_url(&mut self, _url: ValidatorUrl, _network: WalletNetwork) { - todo!(); + pub fn remove_validator_url(&mut self, url: ValidatorUrl, network: WalletNetwork) { + if let Some(network_config) = self.networks.get_mut(&network.as_key()) { + if let Some(ref mut urls) = network_config.validator_urls { + // Removes duplicates too if there are any + urls.retain(|existing_url| existing_url != &url); + } + } } } @@ -312,6 +343,20 @@ impl TryFrom for ValidatorUrl { } } +impl TryFrom for ValidatorUrl { + type Error = BackendError; + + fn try_from(validator: network_config::Validator) -> Result { + Ok(ValidatorUrl { + nymd_url: validator.nymd_url.parse()?, + api_url: match &validator.api_url { + Some(url) => Some(url.parse()?), + None => None, + }, + }) + } +} + impl fmt::Display for ValidatorUrl { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { let s1 = format!("nymd_url: {}", self.nymd_url); @@ -389,6 +434,7 @@ mod tests { api_url: Some("https://baz/api".parse().unwrap()), }, ]), + ..NetworkConfig::default() }; Config { @@ -406,7 +452,8 @@ mod tests { let netconfig = &config.networks[&WalletNetwork::MAINNET.as_key()]; assert_eq!( toml::to_string_pretty(netconfig).unwrap(), - r#"selected_api_url = 'https://my_api_url.com/' + r#"version = 1 +selected_api_url = 'https://my_api_url.com/' [[validator_urls]] nymd_url = 'https://foo/' diff --git a/nym-wallet/src-tauri/src/main.rs b/nym-wallet/src-tauri/src/main.rs index 61346a19cf..75ee64719c 100644 --- a/nym-wallet/src-tauri/src/main.rs +++ b/nym-wallet/src-tauri/src/main.rs @@ -14,6 +14,7 @@ mod config; mod error; mod menu; mod network; +mod network_config; mod operations; mod platform_constants; mod state; @@ -35,19 +36,15 @@ fn main() { .manage(Arc::new(RwLock::new(State::default()))) .invoke_handler(tauri::generate_handler![ mixnet::account::connect_with_mnemonic, - mixnet::account::validate_mnemonic, mixnet::account::create_new_account, mixnet::account::create_new_mnemonic, mixnet::account::create_password, mixnet::account::does_password_file_exist, mixnet::account::get_balance, - mixnet::account::get_validator_nymd_urls, - mixnet::account::get_validator_api_urls, mixnet::account::logout, mixnet::account::remove_password, mixnet::account::sign_in_with_password, mixnet::account::switch_network, - mixnet::account::update_validator_urls, mixnet::account::validate_mnemonic, mixnet::admin::get_contract_settings, mixnet::admin::update_contract_settings, @@ -61,11 +58,20 @@ fn main() { mixnet::bond::update_mixnode, mixnet::delegate::delegate_to_mixnode, mixnet::delegate::get_delegator_rewards, + mixnet::delegate::get_pending_delegation_events, mixnet::delegate::get_reverse_mix_delegations_paged, mixnet::delegate::undelegate_from_mixnode, - mixnet::delegate::get_pending_delegation_events, mixnet::epoch::get_current_epoch, mixnet::send::send, + network_config::add_validator, + network_config::get_validator_api_urls, + network_config::get_validator_nymd_urls, + network_config::remove_validator, + network_config::select_validator_api_url, + network_config::select_validator_nymd_url, + network_config::update_validator_urls, + state::load_config_from_files, + state::save_config_to_files, utils::major_to_minor, utils::minor_to_major, utils::outdated_get_approximate_fee, @@ -81,8 +87,8 @@ fn main() { vesting::bond::vesting_bond_mixnode, vesting::bond::vesting_unbond_gateway, vesting::bond::vesting_unbond_mixnode, - vesting::bond::withdraw_vested_coins, vesting::bond::vesting_update_mixnode, + vesting::bond::withdraw_vested_coins, vesting::delegate::vesting_delegate_to_mixnode, vesting::delegate::vesting_undelegate_from_mixnode, vesting::queries::delegated_free, diff --git a/nym-wallet/src-tauri/src/network_config.rs b/nym-wallet/src-tauri/src/network_config.rs new file mode 100644 index 0000000000..df80e299a7 --- /dev/null +++ b/nym-wallet/src-tauri/src/network_config.rs @@ -0,0 +1,122 @@ +// Copyright 2021 - Nym Technologies SA +// SPDX-License-Identifier: Apache-2.0 + +use crate::error::BackendError; +use crate::network::Network as WalletNetwork; +use crate::state::State; + +use serde::{Deserialize, Serialize}; +use std::{fmt, sync::Arc}; +use tokio::sync::RwLock; + +#[cfg_attr(test, derive(ts_rs::TS))] +#[cfg_attr(test, ts(export, export_to = "../src/types/rust/validatorurls.ts"))] +#[derive(Serialize, Deserialize)] +pub struct ValidatorUrls { + pub urls: Vec, +} + +#[cfg_attr(test, derive(ts_rs::TS))] +#[cfg_attr(test, ts(export, export_to = "../src/types/rust/validatorurls.ts"))] +#[derive(Debug, Serialize, Deserialize)] +pub struct Validator { + pub nymd_url: String, + pub api_url: Option, +} + +impl fmt::Display for Validator { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + let nymd_url = format!("nymd_url: {}", self.nymd_url); + let api_url = self + .api_url + .as_ref() + .map(|api_url| format!(", api_url: {}", api_url)) + .unwrap_or_default(); + write!(f, "{nymd_url}{api_url}") + } +} + +#[tauri::command] +pub async fn get_validator_nymd_urls( + network: WalletNetwork, + state: tauri::State<'_, Arc>>, +) -> Result { + let state = state.read().await; + let urls: Vec = state + .get_nymd_urls(network) + .map(|url| url.to_string()) + .collect(); + Ok(ValidatorUrls { urls }) +} + +#[tauri::command] +pub async fn get_validator_api_urls( + network: WalletNetwork, + state: tauri::State<'_, Arc>>, +) -> Result { + let state = state.read().await; + let urls: Vec = state + .get_api_urls(network) + .map(|url| url.to_string()) + .collect(); + Ok(ValidatorUrls { urls }) +} + +#[tauri::command] +pub async fn select_validator_nymd_url( + url: &str, + network: WalletNetwork, + state: tauri::State<'_, Arc>>, +) -> Result<(), BackendError> { + log::debug!("Selecting new validator nymd_url for {network}: {url}"); + state + .write() + .await + .select_validator_nymd_url(url, network)?; + Ok(()) +} + +#[tauri::command] +pub async fn select_validator_api_url( + url: &str, + network: WalletNetwork, + state: tauri::State<'_, Arc>>, +) -> Result<(), BackendError> { + log::debug!("Selecting new validator api_url for {network}: {url}"); + state.write().await.select_validator_api_url(url, network)?; + Ok(()) +} + +#[tauri::command] +pub async fn add_validator( + validator: Validator, + network: WalletNetwork, + state: tauri::State<'_, Arc>>, +) -> Result<(), BackendError> { + log::debug!("Add validator for {network}: {validator}"); + let url = validator.try_into()?; + state.write().await.add_validator_url(url, network); + Ok(()) +} + +#[tauri::command] +pub async fn remove_validator( + validator: Validator, + network: WalletNetwork, + state: tauri::State<'_, Arc>>, +) -> Result<(), BackendError> { + log::debug!("Remove validator for {network}: {validator}"); + let url = validator.try_into()?; + state.write().await.remove_validator_url(url, network); + Ok(()) +} + +// Update the list of validators by fecthing additional ones remotely. If it fails, just ignore. +#[tauri::command] +pub async fn update_validator_urls( + state: tauri::State<'_, Arc>>, +) -> Result<(), BackendError> { + let mut w_state = state.write().await; + let _r = w_state.fetch_updated_validator_urls().await; + Ok(()) +} diff --git a/nym-wallet/src-tauri/src/operations/mixnet/account.rs b/nym-wallet/src-tauri/src/operations/mixnet/account.rs index 12901cc046..a79a6c92c3 100644 --- a/nym-wallet/src-tauri/src/operations/mixnet/account.rs +++ b/nym-wallet/src-tauri/src/operations/mixnet/account.rs @@ -2,6 +2,7 @@ use crate::coin::{Coin, Denom}; use crate::config::Config; use crate::error::BackendError; use crate::network::Network as WalletNetwork; +use crate::network_config; use crate::nymd_client; use crate::state::State; use crate::wallet_storage::{self, DEFAULT_WALLET_ACCOUNT_ID}; @@ -58,13 +59,6 @@ pub struct Balance { printable_balance: String, } -#[cfg_attr(test, derive(ts_rs::TS))] -#[cfg_attr(test, ts(export, export_to = "../src/types/rust/validatorurls.ts"))] -#[derive(Serialize, Deserialize)] -pub struct ValidatorUrls { - urls: Vec, -} - #[tauri::command] pub async fn connect_with_mnemonic( mnemonic: String, @@ -113,7 +107,7 @@ pub async fn create_new_account( } #[tauri::command] -pub async fn create_new_mnemonic() -> Result { +pub fn create_new_mnemonic() -> Result { let rand_mnemonic = random_mnemonic(); Ok(rand_mnemonic.to_string()) } @@ -157,42 +151,6 @@ fn random_mnemonic() -> Mnemonic { Mnemonic::generate_in_with(&mut rng, Language::English, 24).unwrap() } -#[tauri::command] -pub async fn update_validator_urls( - state: tauri::State<'_, Arc>>, -) -> Result<(), BackendError> { - // Update the list of validators by fecthing additional ones remotely. If it fails, just ignore. - let mut w_state = state.write().await; - let _r = w_state.fetch_updated_validator_urls().await; - Ok(()) -} - -#[tauri::command] -pub async fn get_validator_nymd_urls( - network: WalletNetwork, - state: tauri::State<'_, Arc>>, -) -> Result { - let state = state.read().await; - let urls: Vec = state - .get_nymd_urls(network) - .map(|url| url.to_string()) - .collect(); - Ok(ValidatorUrls { urls }) -} - -#[tauri::command] -pub async fn get_validator_api_urls( - network: WalletNetwork, - state: tauri::State<'_, Arc>>, -) -> Result { - let state = state.read().await; - let urls: Vec = state - .get_api_urls(network) - .map(|url| url.to_string()) - .collect(); - Ok(ValidatorUrls { urls }) -} - async fn _connect_with_mnemonic( mnemonic: Mnemonic, state: tauri::State<'_, Arc>>, @@ -202,7 +160,7 @@ async fn _connect_with_mnemonic( w_state.load_config_files(); } - update_validator_urls(state.clone()).await?; + network_config::update_validator_urls(state.clone()).await?; let config = { let state = state.read().await; diff --git a/nym-wallet/src-tauri/src/state.rs b/nym-wallet/src-tauri/src/state.rs index 63e5a961c5..2a3242d3d7 100644 --- a/nym-wallet/src-tauri/src/state.rs +++ b/nym-wallet/src-tauri/src/state.rs @@ -7,11 +7,28 @@ use validator_client::nymd::SigningNymdClient; use validator_client::Client; use itertools::Itertools; +use tokio::sync::RwLock; use url::Url; use std::collections::HashMap; +use std::sync::Arc; use std::time::Duration; +#[tauri::command] +pub async fn load_config_from_files( + state: tauri::State<'_, Arc>>, +) -> Result<(), BackendError> { + state.write().await.load_config_files(); + Ok(()) +} + +#[tauri::command] +pub async fn save_config_to_files( + state: tauri::State<'_, Arc>>, +) -> Result<(), BackendError> { + state.read().await.save_config_files() +} + #[derive(Default)] pub struct State { config: Config, @@ -30,6 +47,16 @@ impl State { .ok_or(BackendError::ClientNotInitialized) } + pub fn client_mut( + &mut self, + network: Network, + ) -> Result<&mut Client, BackendError> { + self + .signing_clients + .get_mut(&network) + .ok_or(BackendError::ClientNotInitialized) + } + pub fn current_client(&self) -> Result<&Client, BackendError> { self .signing_clients @@ -37,6 +64,14 @@ impl State { .ok_or(BackendError::ClientNotInitialized) } + #[allow(unused)] + pub fn current_client_mut(&mut self) -> Result<&mut Client, BackendError> { + self + .signing_clients + .get_mut(&self.current_network) + .ok_or(BackendError::ClientNotInitialized) + } + pub fn config(&self) -> &Config { &self.config } @@ -124,32 +159,34 @@ impl State { Ok(()) } - #[allow(unused)] pub fn select_validator_nymd_url( &mut self, url: &str, network: Network, ) -> Result<(), BackendError> { self.config.select_validator_nymd_url(url.parse()?, network); + if let Ok(client) = self.client_mut(network) { + client.change_nymd(url.parse()?)?; + } Ok(()) } - #[allow(unused)] pub fn select_validator_api_url( &mut self, url: &str, network: Network, ) -> Result<(), BackendError> { self.config.select_validator_api_url(url.parse()?, network); + if let Ok(client) = self.client_mut(network) { + client.change_validator_api(url.parse()?); + } Ok(()) } - #[allow(unused)] pub fn add_validator_url(&mut self, url: ValidatorUrl, network: Network) { self.config.add_validator_url(url, network); } - #[allow(unused)] pub fn remove_validator_url(&mut self, url: ValidatorUrl, network: Network) { self.config.remove_validator_url(url, network) } diff --git a/nym-wallet/src/types/rust/validatorurls.ts b/nym-wallet/src/types/rust/validatorurls.ts index c4b4a7624e..c3c6bbe580 100644 --- a/nym-wallet/src/types/rust/validatorurls.ts +++ b/nym-wallet/src/types/rust/validatorurls.ts @@ -1,3 +1,8 @@ export interface ValidatorUrls { urls: Array; } + +export interface Validator { + nymd_url: string; + api_url: string | null; +}