diff --git a/locales/en.yml b/locales/en.yml index 7bb951d..976e601 100644 --- a/locales/en.yml +++ b/locales/en.yml @@ -114,7 +114,6 @@ wallets: send: Send receive: Receive settings: Wallet settings - change_server_confirmation: To apply change of connection settings, you need to re-open your wallet. Reopen it now? tx_send_cancel_conf: 'Are you sure you want to cancel sending of %{amount} ツ?' tx_receive_cancel_conf: 'Are you sure you want to cancel receiving of %{amount} ツ?' rec_phrase_not_found: Recovery phrase not found. diff --git a/locales/ru.yml b/locales/ru.yml index 92ee079..09ad7ec 100644 --- a/locales/ru.yml +++ b/locales/ru.yml @@ -114,7 +114,6 @@ wallets: send: Отправить receive: Получить settings: Настройки кошелька - change_server_confirmation: Для применения изменения настроек соединения необходимо переоткрыть кошелёк. Переоткрыть его сейчас? tx_send_cancel_conf: 'Вы действительно хотите отменить отправку %{amount} ツ?' tx_receive_cancel_conf: 'Вы действительно хотите отменить получение %{amount} ツ?' rec_phrase_not_found: Фраза восстановления не найдена. diff --git a/src/gui/views/network/connections.rs b/src/gui/views/network/connections.rs index ed2131f..bc6a72d 100644 --- a/src/gui/views/network/connections.rs +++ b/src/gui/views/network/connections.rs @@ -319,6 +319,9 @@ impl ConnectionsContent { columns[1].vertical_centered_justified(|ui| { // Add connection button callback. let mut on_add = || { + if !self.ext_node_url_edit.starts_with("http") { + self.ext_node_url_edit = format!("http://{}", self.ext_node_url_edit) + } let error = Url::parse(self.ext_node_url_edit.as_str()).is_err(); self.ext_node_url_error = error; if !error { diff --git a/src/gui/views/wallets/content.rs b/src/gui/views/wallets/content.rs index d794d4f..29f67fd 100644 --- a/src/gui/views/wallets/content.rs +++ b/src/gui/views/wallets/content.rs @@ -25,7 +25,7 @@ use crate::gui::views::types::{ModalContainer, ModalPosition, TextEditOptions, T use crate::gui::views::wallets::creation::WalletCreation; use crate::gui::views::wallets::types::WalletTabType; use crate::gui::views::wallets::WalletContent; -use crate::wallet::{ConnectionsConfig, ExternalConnection, Wallet, WalletList}; +use crate::wallet::{Wallet, WalletList}; /// Wallets content. pub struct WalletsContent { @@ -438,12 +438,8 @@ impl WalletsContent { View::ellipsize_text(ui, config.name, 18.0, name_color); // Setup wallet connection text. - let conn_text = if let Some(id) = wallet.get_current_ext_conn_id() { - let ext_conn_url = match ConnectionsConfig::ext_conn(id) { - None => ExternalConnection::DEFAULT_MAIN_URL.to_string(), - Some(ext_conn) => ext_conn.url - }; - format!("{} {}", GLOBE_SIMPLE, ext_conn_url) + let conn_text = if let Some(conn) = wallet.get_current_ext_conn() { + format!("{} {}", GLOBE_SIMPLE, conn.url) } else { format!("{} {}", COMPUTER_TOWER, t!("network.node")) }; diff --git a/src/gui/views/wallets/setup/connection.rs b/src/gui/views/wallets/setup/connection.rs index c6e5ce0..0ab89d9 100644 --- a/src/gui/views/wallets/setup/connection.rs +++ b/src/gui/views/wallets/setup/connection.rs @@ -38,6 +38,9 @@ pub struct ConnectionSetup { /// Flag to show URL format error. ext_node_url_error: bool, + /// Current wallet external connection. + curr_ext_conn: Option, + /// [`Modal`] identifiers allowed at this ui container. modal_ids: Vec<&'static str> } @@ -45,9 +48,6 @@ pub struct ConnectionSetup { /// Identifier for [`Modal`] to add external connection. pub const ADD_EXT_CONNECTION_MODAL: &'static str = "add_ext_connection_modal"; -/// Identifier for [`Modal`] to confirm wallet reopening after connection change. -pub const REOPEN_WALLET_CONFIRMATION_MODAL: &'static str = "change_conn_reopen_wallet_modal"; - impl Default for ConnectionSetup { fn default() -> Self { Self { @@ -56,6 +56,7 @@ impl Default for ConnectionSetup { ext_node_url_edit: "".to_string(), ext_node_secret_edit: "".to_string(), ext_node_url_error: false, + curr_ext_conn: None, modal_ids: vec![ ADD_EXT_CONNECTION_MODAL ] @@ -86,7 +87,7 @@ impl ConnectionSetup { ui: &mut egui::Ui, frame: &mut eframe::Frame, cb: &dyn PlatformCallbacks) { - self.ui(ui, frame, cb); + self.ui(ui, frame, None, cb); } /// Draw existing wallet connection setup content. @@ -95,13 +96,6 @@ impl ConnectionSetup { frame: &mut eframe::Frame, wallet: &mut Wallet, cb: &dyn PlatformCallbacks) { - // Show modal content to reopen the wallet. - if Modal::opened() == Some(REOPEN_WALLET_CONFIRMATION_MODAL) { - Modal::ui(ui.ctx(), |ui, modal| { - self.reopen_modal_content(ui, wallet, modal, cb); - }); - } - // Setup connection value from provided wallet. match wallet.get_config().ext_conn_id { None => self.method = ConnectionMethod::Integrated, @@ -109,7 +103,7 @@ impl ConnectionSetup { } // Draw setup content. - self.ui(ui, frame, cb); + self.ui(ui, frame, Some(wallet), cb); // Setup wallet connection value after change. let config = wallet.get_config(); @@ -130,12 +124,12 @@ impl ConnectionSetup { } }; + // Reopen wallet if connection changed. if changed { - // Show reopen confirmation modal. - Modal::new(REOPEN_WALLET_CONFIRMATION_MODAL) - .position(ModalPosition::Center) - .title(t!("modal.confirmation")) - .show(); + if !wallet.reopen_needed() { + wallet.set_reopen(true); + wallet.close(); + } } } @@ -143,6 +137,7 @@ impl ConnectionSetup { fn ui(&mut self, ui: &mut egui::Ui, frame: &mut eframe::Frame, + wallet: Option<&Wallet>, cb: &dyn PlatformCallbacks) { // Draw modal content for current ui container. self.current_modal_ui(ui, frame, cb); @@ -169,13 +164,27 @@ impl ConnectionSetup { }); ui.add_space(4.0); - let ext_conn_list = ConnectionsConfig::ext_conn_list(); + let mut ext_conn_list = ConnectionsConfig::ext_conn_list(); + // Check if current external connection was deleted to show at list. + if let Some(wallet) = wallet { + if let Some(conn) = wallet.get_current_ext_conn() { + if ext_conn_list.iter() + .filter(|c| c.id == conn.id) + .collect::>().is_empty() { + if self.curr_ext_conn.is_none() { + conn.check_conn_availability(); + self.curr_ext_conn = Some(conn); + } + ext_conn_list.insert(0, self.curr_ext_conn.as_ref().unwrap().clone()); + } + } + } if !ext_conn_list.is_empty() { ui.add_space(8.0); for (index, conn) in ext_conn_list.iter().enumerate() { ui.horizontal_wrapped(|ui| { // Draw connection list item. - self.ext_conn_item_ui(ui, conn, index, ext_conn_list.len()); + self.ext_conn_item_ui(ui, wallet, conn, index, ext_conn_list.len()); }); } } @@ -246,6 +255,7 @@ impl ConnectionSetup { /// Draw external connection item content. fn ext_conn_item_ui(&mut self, ui: &mut egui::Ui, + wallet: Option<&Wallet>, conn: &ExternalConnection, index: usize, len: usize) { @@ -261,7 +271,15 @@ impl ConnectionSetup { ui.vertical(|ui| { ui.allocate_ui_with_layout(rect.size(), Layout::right_to_left(Align::Center), |ui| { // Draw button to select connection. - let is_current_method = self.method == ConnectionMethod::External(conn.id); + let is_current_method = if let Some(wallet) = wallet { + if let Some(cur) = wallet.get_current_ext_conn() { + &cur == conn + } else { + false + } + } else { + self.method == ConnectionMethod::External(conn.id) + }; if !is_current_method { let button_rounding = View::item_rounding(index, len, true); View::item_button(ui, button_rounding, CHECK, None, || { @@ -372,6 +390,9 @@ impl ConnectionSetup { columns[1].vertical_centered_justified(|ui| { // Add connection button callback. let mut on_add = || { + if !self.ext_node_url_edit.starts_with("http") { + self.ext_node_url_edit = format!("http://{}", self.ext_node_url_edit) + } let error = Url::parse(self.ext_node_url_edit.as_str()).is_err(); self.ext_node_url_error = error; if !error { @@ -385,10 +406,8 @@ impl ConnectionSetup { let ext_conn = ExternalConnection::new(url.clone(), secret); ext_conn.check_conn_availability(); ConnectionsConfig::add_ext_conn(ext_conn.clone()); - // Set added connection as current. self.method = ConnectionMethod::External(ext_conn.id); - // Close modal. cb.hide_keyboard(); modal.close(); @@ -406,42 +425,4 @@ impl ConnectionSetup { ui.add_space(6.0); }); } - - /// Draw confirmation modal content to reopen the [`Wallet`]. - fn reopen_modal_content(&mut self, - ui: &mut egui::Ui, - wallet: &Wallet, - modal: &Modal, - _: &dyn PlatformCallbacks) { - ui.add_space(8.0); - ui.vertical_centered(|ui| { - ui.label(RichText::new(t!("wallets.change_server_confirmation")) - .size(17.0) - .color(Colors::TEXT)); - }); - ui.add_space(10.0); - - // Show modal buttons. - ui.scope(|ui| { - // Setup spacing between buttons. - ui.spacing_mut().item_spacing = egui::Vec2::new(6.0, 0.0); - - ui.columns(2, |columns| { - columns[0].vertical_centered_justified(|ui| { - View::button(ui, t!("modal.cancel"), Colors::WHITE, || { - modal.close(); - }); - }); - columns[1].vertical_centered_justified(|ui| { - View::button(ui, "OK".to_owned(), Colors::WHITE, || { - modal.disable_closing(); - wallet.set_reopen(true); - wallet.close(); - modal.close() - }); - }); - }); - ui.add_space(6.0); - }); - } } \ No newline at end of file diff --git a/src/gui/views/wallets/setup/recovery.rs b/src/gui/views/wallets/setup/recovery.rs index 14be7ee..c6c843a 100644 --- a/src/gui/views/wallets/setup/recovery.rs +++ b/src/gui/views/wallets/setup/recovery.rs @@ -67,7 +67,7 @@ impl RecoverySetup { ui.add_space(4.0); ui.vertical_centered(|ui| { - let integrated_node = wallet.get_current_ext_conn_id().is_none(); + let integrated_node = wallet.get_current_ext_conn().is_none(); let integrated_node_ready = Node::get_sync_status() == Some(SyncStatus::NoSync); if wallet.sync_error() || (integrated_node && !integrated_node_ready) { ui.add_space(6.0); diff --git a/src/gui/views/wallets/wallet/content.rs b/src/gui/views/wallets/wallet/content.rs index bd05586..10b0b18 100644 --- a/src/gui/views/wallets/wallet/content.rs +++ b/src/gui/views/wallets/wallet/content.rs @@ -520,7 +520,7 @@ impl WalletContent { } else if wallet.is_closing() { Self::sync_progress_ui(ui, wallet); return true; - } else if wallet.get_current_ext_conn_id().is_none() { + } else if wallet.get_current_ext_conn().is_none() { if !Node::is_running() || Node::is_stopping() { View::center_content(ui, 108.0, |ui| { View::max_width_ui(ui, Root::SIDE_PANEL_WIDTH * 1.5, |ui| { @@ -574,7 +574,7 @@ impl WalletContent { /// Check when to block tabs navigation on sync progress. pub fn block_navigation_on_sync(wallet: &Wallet) -> bool { let sync_error = wallet.sync_error(); - let integrated_node = wallet.get_current_ext_conn_id().is_none(); + let integrated_node = wallet.get_current_ext_conn().is_none(); let integrated_node_ready = Node::get_sync_status() == Some(SyncStatus::NoSync); let sync_after_opening = wallet.get_data().is_none() && !wallet.sync_error(); // Block navigation if wallet is repairing and integrated node is not launching @@ -591,7 +591,7 @@ impl WalletContent { ui.add_space(18.0); // Setup sync progress text. let text = { - let integrated_node = wallet.get_current_ext_conn_id().is_none(); + let integrated_node = wallet.get_current_ext_conn().is_none(); let integrated_node_ready = Node::get_sync_status() == Some(SyncStatus::NoSync); let info_progress = wallet.info_sync_progress(); diff --git a/src/wallet/connections/external.rs b/src/wallet/connections/external.rs index 74be341..3f9cbe7 100644 --- a/src/wallet/connections/external.rs +++ b/src/wallet/connections/external.rs @@ -14,13 +14,11 @@ use grin_util::to_base64; use serde_derive::{Deserialize, Serialize}; -use tor_rtcompat::BlockOn; -use tor_rtcompat::tokio::TokioNativeTlsRuntime; use crate::wallet::ConnectionsConfig; /// External connection for the wallet. -#[derive(Serialize, Deserialize, Clone)] +#[derive(Serialize, Deserialize, Clone, PartialEq)] pub struct ExternalConnection { /// Connection identifier. pub id: i64, @@ -54,45 +52,52 @@ impl ExternalConnection { // Check every connection at separate thread. let conn = self.clone(); std::thread::spawn(move || { - let runtime = TokioNativeTlsRuntime::create().unwrap(); - runtime.block_on(async { - let url = url::Url::parse(conn.url.as_str()).unwrap(); - if let Ok(_) = url.socket_addrs(|| None) { - let addr = format!("{}v2/foreign", url.to_string()); - // Setup http client. - let client = hyper::Client::builder() - .build::<_, hyper::Body>(hyper_tls::HttpsConnector::new()); - let mut req_setup = hyper::Request::builder() - .method(hyper::Method::POST) - .uri(addr.clone()); - // Setup secret key auth. - if let Some(key) = conn.secret { - let basic_auth = format!("Basic {}", to_base64(&format!("grin:{}", key))); - req_setup = req_setup - .header(hyper::header::AUTHORIZATION, basic_auth.clone()); - } - let req = req_setup.body(hyper::Body::from( - r#"{"id":1,"jsonrpc":"2.0","method":"get_version","params":{} }"#) - ).unwrap(); - // Send request. - match client.request(req).await { - Ok(res) => { - let status = res.status().as_u16(); - // Available on 200 HTTP status code. - if status == 200 { - ConnectionsConfig::update_ext_conn_availability(conn.id, true); - } else { + tokio::runtime::Builder::new_multi_thread() + .enable_all() + .build() + .unwrap() + .block_on(async { + println!("check {}", conn.url); + let url = url::Url::parse(conn.url.as_str()).unwrap(); + if let Ok(_) = url.socket_addrs(|| None) { + let addr = format!("{}v2/foreign", url.to_string()); + // Setup http client. + let client = hyper::Client::builder() + .build::<_, hyper::Body>(hyper_tls::HttpsConnector::new()); + let mut req_setup = hyper::Request::builder() + .method(hyper::Method::POST) + .uri(addr.clone()); + // Setup secret key auth. + if let Some(key) = conn.secret { + let basic_auth = format!( + "Basic {}", + to_base64(&format!("grin:{}", key)) + ); + req_setup = req_setup + .header(hyper::header::AUTHORIZATION, basic_auth.clone()); + } + let req = req_setup.body(hyper::Body::from( + r#"{"id":1,"jsonrpc":"2.0","method":"get_version","params":{} }"#) + ).unwrap(); + // Send request. + match client.request(req).await { + Ok(res) => { + let status = res.status().as_u16(); + // Available on 200 HTTP status code. + if status == 200 { + ConnectionsConfig::update_ext_conn_availability(conn.id, true); + } else { + ConnectionsConfig::update_ext_conn_availability(conn.id, false); + } + } + Err(_) => { ConnectionsConfig::update_ext_conn_availability(conn.id, false); } } - Err(_) => { - ConnectionsConfig::update_ext_conn_availability(conn.id, false); - } + } else { + ConnectionsConfig::update_ext_conn_availability(conn.id, false); } - } else { - ConnectionsConfig::update_ext_conn_availability(conn.id, false); - } - }); + }); }); } diff --git a/src/wallet/wallet.rs b/src/wallet/wallet.rs index e8ba71f..8ef52c6 100644 --- a/src/wallet/wallet.rs +++ b/src/wallet/wallet.rs @@ -19,7 +19,7 @@ use std::net::{SocketAddr, TcpListener}; use std::path::PathBuf; use std::sync::{Arc, mpsc}; use parking_lot::RwLock; -use std::sync::atomic::{AtomicBool, AtomicI64, AtomicU8, Ordering}; +use std::sync::atomic::{AtomicBool, AtomicU8, Ordering}; use std::thread::Thread; use std::time::Duration; use futures::channel::oneshot; @@ -55,8 +55,8 @@ pub struct Wallet { config: Arc>, /// Wallet instance, initializing on wallet opening and clearing on wallet closing. instance: Option, - /// [`WalletInstance`] external connection id applied after opening. - instance_ext_conn_id: Arc, + /// [`WalletInstance`] external connection URL. + external_connection: Arc>>, /// Wallet Slatepack address to receive txs at transport. slatepack_address: Arc>>, @@ -106,7 +106,7 @@ impl Wallet { Self { config: Arc::new(RwLock::new(config)), instance: None, - instance_ext_conn_id: Arc::new(AtomicI64::new(0)), + external_connection: Arc::new(RwLock::new(None)), slatepack_address: Arc::new(RwLock::new(None)), sync_thread: Arc::from(RwLock::new(None)), foreign_api_server: Arc::new(RwLock::new(None)), @@ -133,10 +133,10 @@ impl Wallet { mnemonic: String, conn_method: &ConnectionMethod ) -> Result { - let config = WalletConfig::create(name, conn_method); + let mut config = WalletConfig::create(name, conn_method); let w = Wallet::new(config.clone()); { - let instance = Self::create_wallet_instance(config)?; + let instance = Self::create_wallet_instance(&mut config)?; let mut w_lock = instance.lock(); let p = w_lock.lc_provider()?; p.create_wallet(None, @@ -159,7 +159,7 @@ impl Wallet { } /// Create [`WalletInstance`] from provided [`WalletConfig`]. - fn create_wallet_instance(config: WalletConfig) -> Result { + fn create_wallet_instance(config: &mut WalletConfig) -> Result { // Assume global chain type has already been initialized. let chain_type = config.chain_type; if !global::GLOBAL_CHAIN_TYPE.is_init() { @@ -170,16 +170,28 @@ impl Wallet { } // Setup node client. + let integrated = || { + let api_url = format!("http://{}", NodeConfig::get_api_address()); + let api_secret = NodeConfig::get_foreign_api_secret(); + (api_url, api_secret) + }; let (node_api_url, node_secret) = if let Some(id) = config.ext_conn_id { if let Some(conn) = ConnectionsConfig::ext_conn(id) { (conn.url, conn.secret) } else { - (ExternalConnection::DEFAULT_MAIN_URL.to_string(), None) + if chain_type == ChainTypes::Mainnet { + // Save default external connection if previous was deleted. + let default = ExternalConnection::default_main(); + config.ext_conn_id = Some(default.id); + config.save(); + + (default.url, default.secret) + } else { + integrated() + } } } else { - let api_url = format!("http://{}", NodeConfig::get_api_address()); - let api_secret = NodeConfig::get_foreign_api_secret(); - (api_url, api_secret) + integrated() }; let node_client = HTTPNodeClient::new(&node_api_url, node_secret)?; @@ -194,7 +206,7 @@ impl Wallet { /// Instantiate [`WalletInstance`] from provided node client and [`WalletConfig`]. fn inst_wallet( - config: WalletConfig, + config: &mut WalletConfig, node_client: C, ) -> Result>>>, Error> where @@ -308,13 +320,14 @@ impl Wallet { // Create new wallet instance if sync thread was stopped or instance was not created. if self.sync_thread.read().is_none() || self.instance.is_none() { - let config = self.get_config(); - let new_instance = Self::create_wallet_instance(config.clone())?; + let mut config = self.get_config(); + let new_instance = Self::create_wallet_instance(&mut config)?; self.instance = Some(new_instance); - self.instance_ext_conn_id.store(match config.ext_conn_id { - None => 0, - Some(conn_id) => conn_id - }, Ordering::Relaxed); + // Setup current external connection. + { + let mut w_conn = self.external_connection.write(); + *w_conn = self.get_current_ext_conn(); + } } // Open the wallet. @@ -361,18 +374,27 @@ impl Wallet { Ok(()) } - /// Get external connection id applied to [`WalletInstance`] + /// Get external connection URL applied to [`WalletInstance`] /// after wallet opening if sync is running or get it from config. - pub fn get_current_ext_conn_id(&self) -> Option { + pub fn get_current_ext_conn(&self) -> Option { if self.sync_thread.read().is_some() { - let ext_conn_id = self.instance_ext_conn_id.load(Ordering::Relaxed); - if ext_conn_id == 0 { - None - } else { - Some(ext_conn_id) - } + let r_conn = self.external_connection.read(); + r_conn.clone() } else { - self.get_config().ext_conn_id + let config = self.get_config(); + if let Some(id) = config.ext_conn_id { + return match ConnectionsConfig::ext_conn(id) { + None => { + if config.chain_type == ChainTypes::Mainnet { + Some(ExternalConnection::default_main()) + } else { + None + } + }, + Some(ext_conn) => Some(ext_conn) + } + } + None } } @@ -1067,7 +1089,7 @@ fn start_sync(wallet: Wallet) -> Thread { } // Check integrated node state. - if wallet.get_current_ext_conn_id().is_none() { + if wallet.get_current_ext_conn().is_none() { let not_enabled = !Node::is_running() || Node::is_stopping(); if not_enabled { // Reset loading progress.