From f74f071b8a3e4eda77cc5b468f891b21c5e4e2a4 Mon Sep 17 00:00:00 2001 From: jp1ac4 <121959000+jp1ac4@users.noreply.github.com> Date: Thu, 22 Aug 2024 17:42:28 +0100 Subject: [PATCH 01/15] gui: upgrade liana dependency --- gui/Cargo.lock | 69 +++++++++++++++++++++----- gui/src/app/state/settings/bitcoind.rs | 31 ++++++++---- gui/src/daemon/embedded.rs | 4 +- gui/src/installer/context.rs | 6 +-- gui/src/installer/mod.rs | 2 +- gui/src/installer/step/bitcoind.rs | 17 +++++-- gui/src/loader.rs | 6 +-- 7 files changed, 99 insertions(+), 36 deletions(-) diff --git a/gui/Cargo.lock b/gui/Cargo.lock index 3e5014f63..46c168613 100644 --- a/gui/Cargo.lock +++ b/gui/Cargo.lock @@ -308,12 +308,32 @@ version = "1.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8c3c1a368f70d6cf7302d78f8f7093da241fb8e8807c05cc9e51a125895a6d5b" +[[package]] +name = "bdk_chain" +version = "0.15.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c601c4dc7e6c3efa538a0afbb43b964cefab9a9b5e8f352fa0ca38145448a5e7" +dependencies = [ + "bitcoin", + "miniscript", +] + [[package]] name = "bdk_coin_select" version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3c084bf76f0f67546fc814ffa82044144be1bb4618183a15016c162f8b087ad4" +[[package]] +name = "bdk_electrum" +version = "0.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "28906275aeb1f71dc32045670f06c8a26fb17cc62151a99f7425d258f4bda589" +dependencies = [ + "bdk_chain", + "electrum-client", +] + [[package]] name = "bech32" version = "0.10.0-beta" @@ -1143,11 +1163,11 @@ dependencies = [ [[package]] name = "dirs" -version = "5.0.0" +version = "5.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dece029acd3353e3a58ac2e3eb3c8d6c35827a892edc6cc4138ef9c33df46ecd" +checksum = "44c45a9d03d6676652bcb5e724c7e988de1acad23a711b5217ab9cbecbec2225" dependencies = [ - "dirs-sys 0.4.0", + "dirs-sys 0.4.1", ] [[package]] @@ -1163,13 +1183,14 @@ dependencies = [ [[package]] name = "dirs-sys" -version = "0.4.0" +version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "04414300db88f70d74c5ff54e50f9e1d1737d9a5b90f53fcf2e95ca2a9ab554b" +checksum = "520f05a5cbd335fae5a99ff7a6ab8627577660ee5cfd6a94a6a929b52ff0321c" dependencies = [ "libc", + "option-ext", "redox_users", - "windows-sys 0.45.0", + "windows-sys 0.48.0", ] [[package]] @@ -1267,6 +1288,23 @@ version = "1.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7fcaabb2fef8c910e7f4c7ce9f67a1283a1715879a7c230ca9d6d1ae31f16d91" +[[package]] +name = "electrum-client" +version = "0.19.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "89008f106be6f303695522f2f4c1f28b40c3e8367ed8b3bb227f1f882cb52cc2" +dependencies = [ + "bitcoin", + "byteorder", + "libc", + "log", + "rustls", + "serde", + "serde_json", + "webpki-roots", + "winapi", +] + [[package]] name = "elliptic-curve" version = "0.13.8" @@ -2589,12 +2627,13 @@ dependencies = [ [[package]] name = "liana" version = "6.0.0" -source = "git+https://github.com/wizardsardine/liana?branch=master#585bb5b763127f4e0686ce8201fb846fa84137b9" +source = "git+https://github.com/wizardsardine/liana?branch=master#6f7334738360a554d17875b364ccdf2120250315" dependencies = [ "backtrace", "bdk_coin_select", + "bdk_electrum", "bip39", - "dirs 5.0.0", + "dirs 5.0.1", "fern", "getrandom", "jsonrpc 0.17.0", @@ -2936,9 +2975,9 @@ dependencies = [ [[package]] name = "minreq" -version = "2.8.1" +version = "2.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3de406eeb24aba36ed3829532fa01649129677186b44a49debec0ec574ca7da7" +checksum = "763d142cdff44aaadd9268bebddb156ef6c65a0e13486bb81673cf2d8739f9b0" dependencies = [ "log", "serde", @@ -3260,6 +3299,12 @@ version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "624a8340c38c1b80fd549087862da4ba43e08858af025b236e509b6649fc13d5" +[[package]] +name = "option-ext" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "04744f49eae99ab78e0d5c0b603ab218f515ea8cfe5a456d7629ad883a3b6e7d" + [[package]] name = "orbclient" version = "0.3.47" @@ -3818,9 +3863,9 @@ checksum = "3b42e27ef78c35d3998403c1d26f3efd9e135d3e5121b0a4845cc5cc27547f4f" [[package]] name = "rdrand" -version = "0.8.2" +version = "0.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e233b642160555c1aa1ff7a78443c6139342f411b6fa6602af2ebbfee9e166bb" +checksum = "d92195228612ac8eed47adbc2ed0f04e513a4ccb98175b6f2bd04d963b533655" dependencies = [ "rand_core", ] diff --git a/gui/src/app/state/settings/bitcoind.rs b/gui/src/app/state/settings/bitcoind.rs index 430b1fe5f..5d8fc07e3 100644 --- a/gui/src/app/state/settings/bitcoind.rs +++ b/gui/src/app/state/settings/bitcoind.rs @@ -9,7 +9,7 @@ use iced::Command; use tracing::info; use liana::{ - config::{BitcoinConfig, BitcoindConfig, BitcoindRpcAuth, Config}, + config::{BitcoinBackend, BitcoinConfig, BitcoindConfig, BitcoindRpcAuth, Config}, miniscript::bitcoin::Network, }; @@ -37,13 +37,22 @@ impl BitcoindSettingsState { daemon_is_external: bool, bitcoind_is_internal: bool, ) -> Self { + let bitcoind_config = if let Some(BitcoinBackend::Bitcoind(bitcoind_config)) = + config.clone().and_then(|c| c.bitcoin_backend) + { + Some(bitcoind_config) + } else { + None + }; BitcoindSettingsState { warning: None, config_updated: false, - node_settings: config.map(|config| { + node_settings: bitcoind_config.map(|bitcoind_config| { BitcoindSettings::new( - config.bitcoin_config.clone(), - config.bitcoind_config.unwrap(), + config + .expect("config must exist if bitcoind_config exists") + .bitcoin_config, + bitcoind_config, daemon_is_external, bitcoind_is_internal, ) @@ -110,9 +119,10 @@ impl State for BitcoindSettingsState { } fn view<'a>(&'a self, cache: &'a Cache) -> Element<'a, view::Message> { - let can_edit_bitcoind_settings = !self.rescan_settings.processing; + let can_edit_bitcoind_settings = + self.node_settings.is_some() && !self.rescan_settings.processing; let can_do_rescan = !self.rescan_settings.processing - && self.node_settings.as_ref().map(|settings| settings.edit) != Some(true); + && self.node_settings.as_ref().map(|settings| settings.edit) == Some(false); view::settings::bitcoind_settings( cache, self.warning.as_ref(), @@ -274,10 +284,11 @@ impl BitcoindSettings { if let (true, Some(rpc_auth)) = (self.addr.valid, rpc_auth) { let mut daemon_config = daemon.config().cloned().unwrap(); - daemon_config.bitcoind_config = Some(liana::config::BitcoindConfig { - rpc_auth, - addr: new_addr.unwrap(), - }); + daemon_config.bitcoin_backend = + Some(liana::config::BitcoinBackend::Bitcoind(BitcoindConfig { + rpc_auth, + addr: new_addr.unwrap(), + })); self.processing = true; return Command::perform(async move { daemon_config }, |cfg| { Message::LoadDaemonConfig(Box::new(cfg)) diff --git a/gui/src/daemon/embedded.rs b/gui/src/daemon/embedded.rs index 9ee78d80e..06f6203ca 100644 --- a/gui/src/daemon/embedded.rs +++ b/gui/src/daemon/embedded.rs @@ -27,9 +27,9 @@ impl EmbeddedDaemon { pub async fn command(&self, method: F) -> Result where - F: FnOnce(&DaemonControl) -> Result, + F: FnOnce(&mut DaemonControl) -> Result, { - match self.handle.lock().await.as_ref() { + match self.handle.lock().await.as_mut() { Some(DaemonHandle::Controller { control, .. }) => method(control), None => Err(DaemonError::DaemonStopped), } diff --git a/gui/src/installer/context.rs b/gui/src/installer/context.rs index 38215b646..740c55c0d 100644 --- a/gui/src/installer/context.rs +++ b/gui/src/installer/context.rs @@ -10,7 +10,7 @@ use crate::{ }; use async_hwi::DeviceKind; use liana::{ - config::{BitcoinConfig, BitcoindConfig}, + config::{BitcoinBackend, BitcoinConfig}, descriptors::LianaDescriptor, miniscript::bitcoin, }; @@ -48,7 +48,7 @@ impl RemoteBackend { #[derive(Clone)] pub struct Context { pub bitcoin_config: BitcoinConfig, - pub bitcoind_config: Option, + pub bitcoin_backend: Option, pub descriptor: Option, pub keys: Vec, pub hws: Vec<(DeviceKind, bitcoin::bip32::Fingerprint, Option<[u8; 32]>)>, @@ -77,7 +77,7 @@ impl Context { }, hws: Vec::new(), keys: Vec::new(), - bitcoind_config: None, + bitcoin_backend: None, descriptor: None, data_dir, network, diff --git a/gui/src/installer/mod.rs b/gui/src/installer/mod.rs index f9cdb3393..a05f4e0a8 100644 --- a/gui/src/installer/mod.rs +++ b/gui/src/installer/mod.rs @@ -689,7 +689,7 @@ pub fn extract_daemon_config(ctx: &Context) -> Config { .expect("Context must have a descriptor at this point"), data_dir: Some(ctx.data_dir.clone()), bitcoin_config: ctx.bitcoin_config.clone(), - bitcoind_config: ctx.bitcoind_config.clone(), + bitcoin_backend: ctx.bitcoin_backend.clone(), } } diff --git a/gui/src/installer/step/bitcoind.rs b/gui/src/installer/step/bitcoind.rs index e48637b99..a76d8408f 100644 --- a/gui/src/installer/step/bitcoind.rs +++ b/gui/src/installer/step/bitcoind.rs @@ -9,7 +9,7 @@ use bitcoin_hashes::{sha256, Hash}; use flate2::read::GzDecoder; use iced::{Command, Subscription}; use liana::{ - config::{BitcoindConfig, BitcoindRpcAuth}, + config::{BitcoinBackend, BitcoindConfig, BitcoindRpcAuth}, miniscript::bitcoin::Network, }; #[cfg(any(target_os = "macos", target_os = "linux"))] @@ -320,7 +320,7 @@ impl Step for SelectBitcoindTypeStep { fn apply(&mut self, ctx: &mut Context) -> bool { if !self.use_external { if ctx.internal_bitcoind_config.is_none() { - ctx.bitcoind_config = None; // Ensures internal bitcoind can be restarted in case user has switched selection. + ctx.bitcoin_backend = None; // Ensures internal bitcoind can be restarted in case user has switched selection. } } else { ctx.internal_bitcoind_config = None; @@ -481,7 +481,11 @@ impl Step for DefineBitcoind { false } (Some(rpc_auth), Ok(addr)) => { - ctx.bitcoind_config = Some(BitcoindConfig { rpc_auth, addr }); + ctx.bitcoin_backend = + Some(liana::config::BitcoinBackend::Bitcoind(BitcoindConfig { + rpc_auth, + addr, + })); true } } @@ -579,7 +583,7 @@ impl Step for InternalBitcoindStep { } if let Some(Ok(_)) = self.started { // This case can arise if a user switches from internal bitcoind to external and back to internal. - if ctx.bitcoind_config.is_none() { + if ctx.bitcoin_backend.is_none() { self.started = None; // So that internal bitcoind will be restarted. } } @@ -790,7 +794,10 @@ impl Step for InternalBitcoindStep { fn apply(&mut self, ctx: &mut Context) -> bool { // Any errors have been handled as part of `message::InternalBitcoindMsg::Start` if let Some(Ok(_)) = self.started { - ctx.bitcoind_config.clone_from(&self.bitcoind_config); + ctx.bitcoin_backend = self + .bitcoind_config + .as_ref() + .map(|bitcoind_config| BitcoinBackend::Bitcoind(bitcoind_config.clone())); ctx.internal_bitcoind_config .clone_from(&self.internal_bitcoind_config); ctx.internal_bitcoind.clone_from(&self.internal_bitcoind); diff --git a/gui/src/loader.rs b/gui/src/loader.rs index 47e22ac3d..d5f25d19b 100644 --- a/gui/src/loader.rs +++ b/gui/src/loader.rs @@ -11,7 +11,7 @@ use tracing::{debug, info, warn}; use liana::{ commands::CoinStatus, - config::{Config, ConfigError}, + config::{BitcoinBackend, Config, ConfigError}, miniscript::bitcoin, StartupError, }; @@ -245,7 +245,7 @@ impl Loader { log::info!("Managed bitcoind stopped."); } else if self.waiting_daemon_bitcoind && self.gui_config.start_internal_bitcoind { if let Ok(config) = Config::from_file(self.gui_config.daemon_config_path.clone()) { - if let Some(bitcoind_config) = &config.bitcoind_config { + if let Some(BitcoinBackend::Bitcoind(bitcoind_config)) = &config.bitcoin_backend { let mut retry = 0; while !stop_bitcoind(bitcoind_config) && retry < 10 { std::thread::sleep(std::time::Duration::from_millis(500)); @@ -500,7 +500,7 @@ pub async fn start_bitcoind_and_daemon( let config = Config::from_file(Some(config_path)).map_err(Error::Config)?; let mut bitcoind: Option = None; if start_internal_bitcoind { - if let Some(bitcoind_config) = &config.bitcoind_config { + if let Some(BitcoinBackend::Bitcoind(bitcoind_config)) = &config.bitcoin_backend { // Check if bitcoind is already running before trying to start it. if liana::BitcoinD::new(bitcoind_config, "internal_bitcoind_start".to_string()).is_ok() { From ef44cf329adc0eed01451950454edfb7e54be515 Mon Sep 17 00:00:00 2001 From: Michael Mallan Date: Wed, 28 Aug 2024 11:31:12 +0100 Subject: [PATCH 02/15] gui(installer): add module for node step --- gui/src/installer/step/mod.rs | 4 ++-- gui/src/installer/step/{ => node}/bitcoind.rs | 0 gui/src/installer/step/node/mod.rs | 1 + 3 files changed, 3 insertions(+), 2 deletions(-) rename gui/src/installer/step/{ => node}/bitcoind.rs (100%) create mode 100644 gui/src/installer/step/node/mod.rs diff --git a/gui/src/installer/step/mod.rs b/gui/src/installer/step/mod.rs index ac32ed0b1..623bdbf65 100644 --- a/gui/src/installer/step/mod.rs +++ b/gui/src/installer/step/mod.rs @@ -1,10 +1,10 @@ mod backend; -mod bitcoind; mod descriptor; mod mnemonic; +mod node; mod share_xpubs; -pub use bitcoind::{ +pub use node::bitcoind::{ DefineBitcoind, DownloadState, InstallState, InternalBitcoindStep, SelectBitcoindTypeStep, }; diff --git a/gui/src/installer/step/bitcoind.rs b/gui/src/installer/step/node/bitcoind.rs similarity index 100% rename from gui/src/installer/step/bitcoind.rs rename to gui/src/installer/step/node/bitcoind.rs diff --git a/gui/src/installer/step/node/mod.rs b/gui/src/installer/step/node/mod.rs new file mode 100644 index 000000000..ac8d53251 --- /dev/null +++ b/gui/src/installer/step/node/mod.rs @@ -0,0 +1 @@ +pub mod bitcoind; From 4536eff561459648cbf0666ec757db95ac29de4f Mon Sep 17 00:00:00 2001 From: Michael Mallan Date: Wed, 28 Aug 2024 11:34:50 +0100 Subject: [PATCH 03/15] gui(installer): extract logic for try ping bitcoind --- gui/src/installer/step/node/bitcoind.rs | 11 +++++++++++ gui/src/installer/view.rs | 10 ++-------- 2 files changed, 13 insertions(+), 8 deletions(-) diff --git a/gui/src/installer/step/node/bitcoind.rs b/gui/src/installer/step/node/bitcoind.rs index a76d8408f..9a5959e0b 100644 --- a/gui/src/installer/step/node/bitcoind.rs +++ b/gui/src/installer/step/node/bitcoind.rs @@ -392,6 +392,16 @@ impl DefineBitcoind { |res| Message::DefineBitcoind(message::DefineBitcoind::PingBitcoindResult(res)), ) } + + pub fn can_try_ping(&self) -> bool { + if let RpcAuthType::UserPass = self.selected_auth_type { + self.address.valid + && !self.rpc_auth_vals.password.value.is_empty() + && !self.rpc_auth_vals.user.value.is_empty() + } else { + self.address.valid && !self.rpc_auth_vals.cookie_path.value.is_empty() + } + } } impl Step for DefineBitcoind { @@ -503,6 +513,7 @@ impl Step for DefineBitcoind { &self.rpc_auth_vals, &self.selected_auth_type, self.is_running.as_ref(), + self.can_try_ping(), ) } diff --git a/gui/src/installer/view.rs b/gui/src/installer/view.rs index cae776e00..7cac0dfa1 100644 --- a/gui/src/installer/view.rs +++ b/gui/src/installer/view.rs @@ -1165,6 +1165,7 @@ pub fn define_bitcoin<'a>( rpc_auth_vals: &RpcAuthValues, selected_auth_type: &RpcAuthType, is_running: Option<&Result<(), Error>>, + can_try_ping: bool, ) -> Element<'a, Message> { let is_loopback = if let Some((ip, _port)) = address.value.clone().rsplit_once(':') { let (ipv4, ipv6) = (Ipv4Addr::from_str(ip), Ipv6Addr::from_str(ip)); @@ -1268,13 +1269,6 @@ pub fn define_bitcoin<'a>( }) .spacing(10); - let check_connect_enable = if let RpcAuthType::UserPass = selected_auth_type { - address.valid - && !rpc_auth_vals.password.value.is_empty() - && !rpc_auth_vals.user.value.is_empty() - } else { - address.valid && !rpc_auth_vals.cookie_path.value.is_empty() - }; layout( progress, None, @@ -1310,7 +1304,7 @@ pub fn define_bitcoin<'a>( .spacing(10) .push(Container::new( button::secondary(None, "Check connection") - .on_press_maybe(if check_connect_enable { + .on_press_maybe(if can_try_ping { Some(Message::DefineBitcoind( message::DefineBitcoind::PingBitcoind, )) From c5d9d007fb908308592635b8f85236a1b7a4b5ae Mon Sep 17 00:00:00 2001 From: Michael Mallan Date: Wed, 28 Aug 2024 11:42:32 +0100 Subject: [PATCH 04/15] gui: move bitcoind to new node module --- gui/src/app/mod.rs | 2 +- gui/src/app/state/settings/bitcoind.rs | 2 +- gui/src/app/view/message.rs | 2 +- gui/src/app/view/settings.rs | 2 +- gui/src/installer/context.rs | 2 +- gui/src/installer/message.rs | 2 +- gui/src/installer/step/mod.rs | 2 +- gui/src/installer/step/node/bitcoind.rs | 12 ++++++------ gui/src/installer/view.rs | 4 ++-- gui/src/lib.rs | 2 +- gui/src/loader.rs | 4 ++-- gui/src/{ => node}/bitcoind.rs | 0 gui/src/node/mod.rs | 1 + 13 files changed, 19 insertions(+), 18 deletions(-) rename gui/src/{ => node}/bitcoind.rs (100%) create mode 100644 gui/src/node/mod.rs diff --git a/gui/src/app/mod.rs b/gui/src/app/mod.rs index c222211a2..434153843 100644 --- a/gui/src/app/mod.rs +++ b/gui/src/app/mod.rs @@ -35,8 +35,8 @@ use state::{ use crate::{ app::{cache::Cache, error::Error, menu::Menu, wallet::Wallet}, - bitcoind::Bitcoind, daemon::{embedded::EmbeddedDaemon, Daemon, DaemonBackend}, + node::bitcoind::Bitcoind, }; use self::state::SettingsState; diff --git a/gui/src/app/state/settings/bitcoind.rs b/gui/src/app/state/settings/bitcoind.rs index 5d8fc07e3..03f0a2a7b 100644 --- a/gui/src/app/state/settings/bitcoind.rs +++ b/gui/src/app/state/settings/bitcoind.rs @@ -17,8 +17,8 @@ use liana_ui::{component::form, widget::Element}; use crate::{ app::{cache::Cache, error::Error, message::Message, state::settings::State, view}, - bitcoind::{RpcAuthType, RpcAuthValues}, daemon::Daemon, + node::bitcoind::{RpcAuthType, RpcAuthValues}, }; #[derive(Debug)] diff --git a/gui/src/app/view/message.rs b/gui/src/app/view/message.rs index 001e8952a..78118ff85 100644 --- a/gui/src/app/view/message.rs +++ b/gui/src/app/view/message.rs @@ -1,4 +1,4 @@ -use crate::{app::menu::Menu, bitcoind::RpcAuthType}; +use crate::{app::menu::Menu, node::bitcoind::RpcAuthType}; use liana::miniscript::bitcoin::bip32::Fingerprint; #[derive(Debug, Clone)] diff --git a/gui/src/app/view/settings.rs b/gui/src/app/view/settings.rs index a81bbd295..fc22c0221 100644 --- a/gui/src/app/view/settings.rs +++ b/gui/src/app/view/settings.rs @@ -29,8 +29,8 @@ use crate::{ menu::Menu, view::{hw, warning::warn}, }, - bitcoind::{RpcAuthType, RpcAuthValues}, hw::HardwareWallet, + node::bitcoind::{RpcAuthType, RpcAuthValues}, }; pub fn list(cache: &Cache, is_remote_backend: bool) -> Element { diff --git a/gui/src/installer/context.rs b/gui/src/installer/context.rs index 740c55c0d..3f46cbe7a 100644 --- a/gui/src/installer/context.rs +++ b/gui/src/installer/context.rs @@ -4,8 +4,8 @@ use std::time::Duration; use crate::{ app::settings::KeySetting, - bitcoind::{Bitcoind, InternalBitcoindConfig}, lianalite::client::backend::{BackendClient, BackendWalletClient}, + node::bitcoind::{Bitcoind, InternalBitcoindConfig}, signer::Signer, }; use async_hwi::DeviceKind; diff --git a/gui/src/installer/message.rs b/gui/src/installer/message.rs index dfeb516bd..bbf8285d1 100644 --- a/gui/src/installer/message.rs +++ b/gui/src/installer/message.rs @@ -6,10 +6,10 @@ use std::path::PathBuf; use super::{context, Error}; use crate::{ - bitcoind::{Bitcoind, ConfigField, RpcAuthType}, download::Progress, hw::HardwareWalletMessage, lianalite::client::{auth::AuthClient, backend::api}, + node::bitcoind::{Bitcoind, ConfigField, RpcAuthType}, }; use async_hwi::{DeviceKind, Version}; diff --git a/gui/src/installer/step/mod.rs b/gui/src/installer/step/mod.rs index 623bdbf65..76113623b 100644 --- a/gui/src/installer/step/mod.rs +++ b/gui/src/installer/step/mod.rs @@ -21,9 +21,9 @@ use iced::{Command, Subscription}; use liana_ui::widget::*; use crate::{ - bitcoind::Bitcoind, hw::HardwareWallets, installer::{context::Context, message::Message, view}, + node::bitcoind::Bitcoind, }; pub trait Step { diff --git a/gui/src/installer/step/node/bitcoind.rs b/gui/src/installer/step/node/bitcoind.rs index 9a5959e0b..ef22b7cd3 100644 --- a/gui/src/installer/step/node/bitcoind.rs +++ b/gui/src/installer/step/node/bitcoind.rs @@ -21,12 +21,6 @@ use jsonrpc::{client::Client, simple_http::SimpleHttpTransport}; use liana_ui::{component::form, widget::*}; use crate::{ - bitcoind::{ - self, bitcoind_network_dir, internal_bitcoind_datadir, internal_bitcoind_directory, - Bitcoind, ConfigField, InternalBitcoindConfig, InternalBitcoindConfigError, - InternalBitcoindNetworkConfig, RpcAuth, RpcAuthType, RpcAuthValues, - StartInternalBitcoindError, VERSION, - }, download, hw::HardwareWallets, installer::{ @@ -35,6 +29,12 @@ use crate::{ step::Step, view, Error, }, + node::bitcoind::{ + self, bitcoind_network_dir, internal_bitcoind_datadir, internal_bitcoind_directory, + Bitcoind, ConfigField, InternalBitcoindConfig, InternalBitcoindConfigError, + InternalBitcoindNetworkConfig, RpcAuth, RpcAuthType, RpcAuthValues, + StartInternalBitcoindError, VERSION, + }, }; // The approach for tracking download progress is taken from diff --git a/gui/src/installer/view.rs b/gui/src/installer/view.rs index 7cac0dfa1..73f765651 100644 --- a/gui/src/installer/view.rs +++ b/gui/src/installer/view.rs @@ -32,7 +32,6 @@ use liana_ui::{ }; use crate::{ - bitcoind::{ConfigField, RpcAuthType, RpcAuthValues, StartInternalBitcoindError}, hw::{is_compatible_with_tapminiscript, HardwareWallet, UnsupportedReason}, installer::{ message::{self, Message}, @@ -40,6 +39,7 @@ use crate::{ step::{DownloadState, InstallState}, Error, }, + node::bitcoind::{ConfigField, RpcAuthType, RpcAuthValues, StartInternalBitcoindError}, }; #[derive(Debug, Clone, Copy, PartialEq, Eq)] @@ -1430,7 +1430,7 @@ pub fn start_internal_bitcoind<'a>( download_state: Option<&DownloadState>, install_state: Option<&InstallState>, ) -> Element<'a, Message> { - let version = crate::bitcoind::VERSION; + let version = crate::node::bitcoind::VERSION; let mut next_button = button::primary(None, "Next").width(Length::Fixed(200.0)); if let Some(Ok(_)) = started { next_button = next_button.on_press(Message::Next); diff --git a/gui/src/lib.rs b/gui/src/lib.rs index b29f7e7c2..9441c85fd 100644 --- a/gui/src/lib.rs +++ b/gui/src/lib.rs @@ -1,5 +1,4 @@ pub mod app; -pub mod bitcoind; pub mod daemon; pub mod datadir; pub mod download; @@ -9,6 +8,7 @@ pub mod launcher; pub mod lianalite; pub mod loader; pub mod logger; +pub mod node; pub mod signer; pub mod utils; diff --git a/gui/src/loader.rs b/gui/src/loader.rs index d5f25d19b..27283a7fa 100644 --- a/gui/src/loader.rs +++ b/gui/src/loader.rs @@ -29,10 +29,10 @@ use crate::{ config::Config as GUIConfig, wallet::{Wallet, WalletError}, }, - bitcoind::{ + daemon::{client, embedded::EmbeddedDaemon, model::*, Daemon, DaemonError}, + node::bitcoind::{ internal_bitcoind_debug_log_path, stop_bitcoind, Bitcoind, StartInternalBitcoindError, }, - daemon::{client, embedded::EmbeddedDaemon, model::*, Daemon, DaemonError}, }; const SYNCING_PROGRESS_1: &str = "Bitcoin Core is synchronising the blockchain. A full synchronisation typically take a few days, and is resource intensive. Once the initial synchronisation is done, the next ones will be much faster."; diff --git a/gui/src/bitcoind.rs b/gui/src/node/bitcoind.rs similarity index 100% rename from gui/src/bitcoind.rs rename to gui/src/node/bitcoind.rs diff --git a/gui/src/node/mod.rs b/gui/src/node/mod.rs new file mode 100644 index 000000000..ac8d53251 --- /dev/null +++ b/gui/src/node/mod.rs @@ -0,0 +1 @@ +pub mod bitcoind; From 046b54e6a9337aaf478ea6e61f2d28d73f1b15c1 Mon Sep 17 00:00:00 2001 From: Michael Mallan Date: Wed, 28 Aug 2024 11:53:20 +0100 Subject: [PATCH 05/15] gui(installer): define bitcoind from general node struct --- gui/src/installer/message.rs | 11 +- gui/src/installer/mod.rs | 6 +- gui/src/installer/step/mod.rs | 5 +- gui/src/installer/step/node/bitcoind.rs | 106 +++++----------- gui/src/installer/step/node/mod.rs | 109 +++++++++++++++++ gui/src/installer/view.rs | 155 ++++++++++++------------ 6 files changed, 233 insertions(+), 159 deletions(-) diff --git a/gui/src/installer/message.rs b/gui/src/installer/message.rs index bbf8285d1..4a171a193 100644 --- a/gui/src/installer/message.rs +++ b/gui/src/installer/message.rs @@ -33,7 +33,7 @@ pub enum Message { ImportRemoteWallet(ImportRemoteWallet), SelectBitcoindType(SelectBitcoindTypeMsg), InternalBitcoind(InternalBitcoindMsg), - DefineBitcoind(DefineBitcoind), + DefineNode(DefineNode), DefineDescriptor(DefineDescriptor), ImportXpub(Fingerprint, Result), HardwareWallets(HardwareWalletMessage), @@ -72,8 +72,13 @@ pub enum ImportRemoteWallet { pub enum DefineBitcoind { ConfigFieldEdited(ConfigField, String), RpcAuthTypeSelected(RpcAuthType), - PingBitcoindResult(Result<(), Error>), - PingBitcoind, +} + +#[derive(Debug, Clone)] +pub enum DefineNode { + DefineBitcoind(DefineBitcoind), + PingResult(Result<(), Error>), + Ping, } #[derive(Debug, Clone)] diff --git a/gui/src/installer/mod.rs b/gui/src/installer/mod.rs index a05f4e0a8..e6b2687ea 100644 --- a/gui/src/installer/mod.rs +++ b/gui/src/installer/mod.rs @@ -39,7 +39,7 @@ use crate::{ pub use message::Message; use step::{ - BackupDescriptor, BackupMnemonic, ChooseBackend, DefineBitcoind, DefineDescriptor, Final, + BackupDescriptor, BackupMnemonic, ChooseBackend, DefineDescriptor, DefineNode, Final, ImportDescriptor, ImportRemoteWallet, InternalBitcoindStep, RecoverMnemonic, RegisterDescriptor, RemoteBackendLogin, SelectBitcoindTypeStep, ShareXpubs, Step, }; @@ -126,7 +126,7 @@ impl Installer { RemoteBackendLogin::new(network).into(), SelectBitcoindTypeStep::new().into(), InternalBitcoindStep::new(&context.data_dir).into(), - DefineBitcoind::new().into(), + DefineNode::default().into(), Final::new().into(), ], UserFlow::ShareXpubs => vec![ShareXpubs::new(network, signer.clone()).into()], @@ -139,7 +139,7 @@ impl Installer { RegisterDescriptor::new_import_wallet().into(), SelectBitcoindTypeStep::new().into(), InternalBitcoindStep::new(&context.data_dir).into(), - DefineBitcoind::new().into(), + DefineNode::default().into(), Final::new().into(), ], }, diff --git a/gui/src/installer/step/mod.rs b/gui/src/installer/step/mod.rs index 76113623b..c45e56c56 100644 --- a/gui/src/installer/step/mod.rs +++ b/gui/src/installer/step/mod.rs @@ -4,8 +4,9 @@ mod mnemonic; mod node; mod share_xpubs; -pub use node::bitcoind::{ - DefineBitcoind, DownloadState, InstallState, InternalBitcoindStep, SelectBitcoindTypeStep, +pub use node::{ + bitcoind::{DownloadState, InstallState, InternalBitcoindStep, SelectBitcoindTypeStep}, + DefineNode, }; pub use descriptor::{BackupDescriptor, DefineDescriptor, ImportDescriptor, RegisterDescriptor}; diff --git a/gui/src/installer/step/node/bitcoind.rs b/gui/src/installer/step/node/bitcoind.rs index ef22b7cd3..7255cbcd0 100644 --- a/gui/src/installer/step/node/bitcoind.rs +++ b/gui/src/installer/step/node/bitcoind.rs @@ -339,11 +339,11 @@ impl Step for SelectBitcoindTypeStep { } } +#[derive(Clone)] pub struct DefineBitcoind { rpc_auth_vals: RpcAuthValues, selected_auth_type: RpcAuthType, address: form::Value, - is_running: Option>, // Internal cache to detect network change. network: Option, @@ -355,42 +355,33 @@ impl DefineBitcoind { rpc_auth_vals: RpcAuthValues::default(), selected_auth_type: RpcAuthType::CookieFile, address: form::Value::default(), - is_running: None, network: None, } } - pub fn ping(&self) -> Command { - let address = self.address.value.to_owned(); - let selected_auth_type = self.selected_auth_type; + pub fn ping(&self) -> Result<(), Error> { let rpc_auth_vals = self.rpc_auth_vals.clone(); - Command::perform( - async move { - let builder = match selected_auth_type { - RpcAuthType::CookieFile => { - let cookie_path = rpc_auth_vals.cookie_path.value; - let cookie = std::fs::read_to_string(cookie_path).map_err(|e| { - Error::Bitcoind(format!("Failed to read cookie file: {}", e)) - })?; - SimpleHttpTransport::builder().cookie_auth(cookie) - } - RpcAuthType::UserPass => { - let user = rpc_auth_vals.user.value; - let password = rpc_auth_vals.password.value; - SimpleHttpTransport::builder().auth(user, Some(password)) - } - }; - let client = Client::with_transport( - builder - .url(&address)? - .timeout(std::time::Duration::from_secs(3)) - .build(), - ); - client.send_request(client.build_request("echo", &[]))?; - Ok(()) - }, - |res| Message::DefineBitcoind(message::DefineBitcoind::PingBitcoindResult(res)), - ) + let builder = match self.selected_auth_type { + RpcAuthType::CookieFile => { + let cookie_path = rpc_auth_vals.cookie_path.value; + let cookie = std::fs::read_to_string(cookie_path) + .map_err(|e| Error::Bitcoind(format!("Failed to read cookie file: {}", e)))?; + SimpleHttpTransport::builder().cookie_auth(cookie) + } + RpcAuthType::UserPass => { + let user = rpc_auth_vals.user.value; + let password = rpc_auth_vals.password.value; + SimpleHttpTransport::builder().auth(user, Some(password)) + } + }; + let client = Client::with_transport( + builder + .url(&self.address.value.to_owned())? + .timeout(std::time::Duration::from_secs(3)) + .build(), + ); + client.send_request(client.build_request("echo", &[]))?; + Ok(()) } pub fn can_try_ping(&self) -> bool { @@ -402,10 +393,8 @@ impl DefineBitcoind { self.address.valid && !self.rpc_auth_vals.cookie_path.value.is_empty() } } -} -impl Step for DefineBitcoind { - fn load_context(&mut self, ctx: &Context) { + pub fn load_context(&mut self, ctx: &Context) { if self.rpc_auth_vals.cookie_path.value.is_empty() // if network changed then the values must be reset to default. || self.network != Some(ctx.bitcoin_config.network) @@ -422,17 +411,12 @@ impl Step for DefineBitcoind { self.network = Some(ctx.bitcoin_config.network); } - fn update(&mut self, _hws: &mut HardwareWallets, message: Message) -> Command { - if let Message::DefineBitcoind(msg) = message { + + pub fn update(&mut self, message: message::DefineNode) -> Command { + if let message::DefineNode::DefineBitcoind(msg) = message { match msg { - message::DefineBitcoind::PingBitcoind => { - self.is_running = None; - return self.ping(); - } - message::DefineBitcoind::PingBitcoindResult(res) => self.is_running = Some(res), message::DefineBitcoind::ConfigFieldEdited(field, value) => match field { ConfigField::Address => { - self.is_running = None; self.address.value.clone_from(&value); self.address.valid = false; if let Some((ip, port)) = value.rsplit_once(':') { @@ -444,23 +428,19 @@ impl Step for DefineBitcoind { } } ConfigField::CookieFilePath => { - self.is_running = None; self.rpc_auth_vals.cookie_path.value = value; self.rpc_auth_vals.cookie_path.valid = true; } ConfigField::User => { - self.is_running = None; self.rpc_auth_vals.user.value = value; self.rpc_auth_vals.user.valid = true; } ConfigField::Password => { - self.is_running = None; self.rpc_auth_vals.password.value = value; self.rpc_auth_vals.password.valid = true; } }, message::DefineBitcoind::RpcAuthTypeSelected(auth_type) => { - self.is_running = None; self.selected_auth_type = auth_type; } }; @@ -468,7 +448,7 @@ impl Step for DefineBitcoind { Command::none() } - fn apply(&mut self, ctx: &mut Context) -> bool { + pub fn apply(&mut self, ctx: &mut Context) -> bool { let addr = std::net::SocketAddr::from_str(&self.address.value); let rpc_auth = match self.selected_auth_type { RpcAuthType::CookieFile => { @@ -501,28 +481,8 @@ impl Step for DefineBitcoind { } } - fn view( - &self, - _hws: &HardwareWallets, - progress: (usize, usize), - _email: Option<&str>, - ) -> Element { - view::define_bitcoin( - progress, - &self.address, - &self.rpc_auth_vals, - &self.selected_auth_type, - self.is_running.as_ref(), - self.can_try_ping(), - ) - } - - fn load(&self) -> Command { - self.ping() - } - - fn skip(&self, ctx: &Context) -> bool { - !ctx.bitcoind_is_external || ctx.remote_backend.is_some() + pub fn view(&self) -> Element { + view::define_bitcoind(&self.address, &self.rpc_auth_vals, &self.selected_auth_type) } } @@ -532,12 +492,6 @@ impl Default for DefineBitcoind { } } -impl From for Box { - fn from(s: DefineBitcoind) -> Box { - Box::new(s) - } -} - pub struct InternalBitcoindStep { liana_datadir: PathBuf, bitcoind_datadir: PathBuf, diff --git a/gui/src/installer/step/node/mod.rs b/gui/src/installer/step/node/mod.rs index ac8d53251..224800504 100644 --- a/gui/src/installer/step/node/mod.rs +++ b/gui/src/installer/step/node/mod.rs @@ -1 +1,110 @@ pub mod bitcoind; + +use crate::{ + hw::HardwareWallets, + installer::{ + context::Context, + message::{self, Message}, + step::{node::bitcoind::DefineBitcoind, Step}, + view, Error, + }, +}; + +use iced::Command; +use liana_ui::widget::Element; + +pub struct Node { + definition: DefineBitcoind, + is_running: Option>, +} + +impl Node { + fn new() -> Self { + Node { + definition: DefineBitcoind::new(), + is_running: None, + } + } +} + +pub struct DefineNode { + node: Node, +} + +impl From for Box { + fn from(s: DefineNode) -> Box { + Box::new(s) + } +} + +impl DefineNode { + pub fn new() -> Self { + Self { node: Node::new() } + } + + fn ping(&self) -> Command { + let def = self.node.definition.clone(); + Command::perform(async move { def.ping() }, move |res| { + Message::DefineNode(message::DefineNode::PingResult(res)) + }) + } + + fn update_node(&mut self, message: message::DefineNode) -> Command { + self.node.is_running = None; + self.node.definition.update(message) + } +} + +impl Default for DefineNode { + fn default() -> Self { + Self::new() + } +} + +impl Step for DefineNode { + fn load_context(&mut self, ctx: &Context) { + self.node.definition.load_context(ctx); + } + fn update(&mut self, _hws: &mut HardwareWallets, message: Message) -> Command { + if let Message::DefineNode(msg) = message { + match msg { + message::DefineNode::Ping => { + return self.ping(); + } + message::DefineNode::PingResult(res) => { + self.node.is_running = Some(res); + } + msg @ message::DefineNode::DefineBitcoind(_) => { + return self.update_node(msg); + } + } + } + Command::none() + } + + fn apply(&mut self, ctx: &mut Context) -> bool { + self.node.definition.apply(ctx) + } + + fn view( + &self, + _hws: &HardwareWallets, + progress: (usize, usize), + _email: Option<&str>, + ) -> Element { + view::define_bitcoin_node( + progress, + self.node.definition.view(), + self.node.is_running.as_ref(), + self.node.definition.can_try_ping(), + ) + } + + fn load(&self) -> Command { + self.ping() + } + + fn skip(&self, ctx: &Context) -> bool { + !ctx.bitcoind_is_external || ctx.remote_backend.is_some() + } +} diff --git a/gui/src/installer/view.rs b/gui/src/installer/view.rs index 73f765651..bd86a2f7f 100644 --- a/gui/src/installer/view.rs +++ b/gui/src/installer/view.rs @@ -34,7 +34,7 @@ use liana_ui::{ use crate::{ hw::{is_compatible_with_tapminiscript, HardwareWallet, UnsupportedReason}, installer::{ - message::{self, Message}, + message::{self, DefineBitcoind, DefineNode, Message}, prompt, step::{DownloadState, InstallState}, Error, @@ -1159,13 +1159,73 @@ pub fn help_backup<'a>() -> Element<'a, Message> { text(prompt::BACKUP_DESCRIPTOR_HELP).small().into() } -pub fn define_bitcoin<'a>( +pub fn define_bitcoin_node<'a>( progress: (usize, usize), + node_view: Element<'a, Message>, + is_running: Option<&Result<(), Error>>, + can_try_ping: bool, +) -> Element<'a, Message> { + let col = Column::new() + .push(node_view) + .push_maybe(if is_running.is_some() { + is_running.map(|res| { + if res.is_ok() { + Container::new( + Row::new() + .spacing(10) + .align_items(Alignment::Center) + .push(icon::circle_check_icon().style(color::GREEN)) + .push(text("Connection checked").style(color::GREEN)), + ) + } else { + Container::new( + Row::new() + .spacing(10) + .align_items(Alignment::Center) + .push(icon::circle_cross_icon().style(color::RED)) + .push(text("Connection failed").style(color::RED)), + ) + } + }) + } else { + Some(Container::new(Space::with_height(Length::Fixed(25.0)))) + }) + .push( + Row::new() + .spacing(10) + .push(Container::new( + button::secondary(None, "Check connection") + .on_press_maybe(if can_try_ping { + Some(Message::DefineNode(DefineNode::Ping)) + } else { + None + }) + .width(Length::Fixed(200.0)), + )) + .push(if is_running.map(|res| res.is_ok()).unwrap_or(false) { + button::primary(None, "Next") + .on_press(Message::Next) + .width(Length::Fixed(200.0)) + } else { + button::primary(None, "Next").width(Length::Fixed(200.0)) + }), + ) + .spacing(50); + + layout( + progress, + None, + "Set up connection to the Bitcoin full node", + col, + true, + Some(Message::Previous), + ) +} + +pub fn define_bitcoind<'a>( address: &form::Value, rpc_auth_vals: &RpcAuthValues, selected_auth_type: &RpcAuthType, - is_running: Option<&Result<(), Error>>, - can_try_ping: bool, ) -> Element<'a, Message> { let is_loopback = if let Some((ip, _port)) = address.value.clone().rsplit_once(':') { let (ipv4, ipv6) = (Ipv4Addr::from_str(ip), Ipv6Addr::from_str(ip)); @@ -1182,9 +1242,8 @@ pub fn define_bitcoin<'a>( .push(text("Address:").bold()) .push( form::Form::new_trimmed("Address", address, |msg| { - Message::DefineBitcoind(message::DefineBitcoind::ConfigFieldEdited( - ConfigField::Address, - msg, + Message::DefineNode(DefineNode::DefineBitcoind( + DefineBitcoind::ConfigFieldEdited(ConfigField::Address, msg), )) }) .warning("Please enter correct address") @@ -1220,9 +1279,9 @@ pub fn define_bitcoin<'a>( *auth_type, Some(*selected_auth_type), |new_selection| { - Message::DefineBitcoind( - message::DefineBitcoind::RpcAuthTypeSelected(new_selection), - ) + Message::DefineNode(DefineNode::DefineBitcoind( + DefineBitcoind::RpcAuthTypeSelected(new_selection), + )) }, )) .spacing(30) @@ -1233,9 +1292,8 @@ pub fn define_bitcoin<'a>( .push(match selected_auth_type { RpcAuthType::CookieFile => Row::new().push( form::Form::new_trimmed("Cookie path", &rpc_auth_vals.cookie_path, |msg| { - Message::DefineBitcoind(message::DefineBitcoind::ConfigFieldEdited( - ConfigField::CookieFilePath, - msg, + Message::DefineNode(DefineNode::DefineBitcoind( + DefineBitcoind::ConfigFieldEdited(ConfigField::CookieFilePath, msg), )) }) .warning("Please enter correct path") @@ -1245,9 +1303,8 @@ pub fn define_bitcoin<'a>( RpcAuthType::UserPass => Row::new() .push( form::Form::new_trimmed("User", &rpc_auth_vals.user, |msg| { - Message::DefineBitcoind(message::DefineBitcoind::ConfigFieldEdited( - ConfigField::User, - msg, + Message::DefineNode(DefineNode::DefineBitcoind( + DefineBitcoind::ConfigFieldEdited(ConfigField::User, msg), )) }) .warning("Please enter correct user") @@ -1256,9 +1313,8 @@ pub fn define_bitcoin<'a>( ) .push( form::Form::new_trimmed("Password", &rpc_auth_vals.password, |msg| { - Message::DefineBitcoind(message::DefineBitcoind::ConfigFieldEdited( - ConfigField::Password, - msg, + Message::DefineNode(DefineNode::DefineBitcoind( + DefineBitcoind::ConfigFieldEdited(ConfigField::Password, msg), )) }) .warning("Please enter correct password") @@ -1269,62 +1325,11 @@ pub fn define_bitcoin<'a>( }) .spacing(10); - layout( - progress, - None, - "Set up connection to the Bitcoin full node", - Column::new() - .push(col_address) - .push(col_auth) - .push_maybe(if is_running.is_some() { - is_running.map(|res| { - if res.is_ok() { - Container::new( - Row::new() - .spacing(10) - .align_items(Alignment::Center) - .push(icon::circle_check_icon().style(color::GREEN)) - .push(text("Connection checked").style(color::GREEN)), - ) - } else { - Container::new( - Row::new() - .spacing(10) - .align_items(Alignment::Center) - .push(icon::circle_cross_icon().style(color::RED)) - .push(text("Connection failed").style(color::RED)), - ) - } - }) - } else { - Some(Container::new(Space::with_height(Length::Fixed(25.0)))) - }) - .push( - Row::new() - .spacing(10) - .push(Container::new( - button::secondary(None, "Check connection") - .on_press_maybe(if can_try_ping { - Some(Message::DefineBitcoind( - message::DefineBitcoind::PingBitcoind, - )) - } else { - None - }) - .width(Length::Fixed(200.0)), - )) - .push(if is_running.map(|res| res.is_ok()).unwrap_or(false) { - button::primary(None, "Next") - .on_press(Message::Next) - .width(Length::Fixed(200.0)) - } else { - button::primary(None, "Next").width(Length::Fixed(200.0)) - }), - ) - .spacing(50), - true, - Some(Message::Previous), - ) + Column::new() + .push(col_address) + .push(col_auth) + .spacing(50) + .into() } pub fn select_bitcoind_type<'a>(progress: (usize, usize)) -> Element<'a, Message> { From 83172c7bc584283d7dd4b93f3f18ec0f2d9a1ad0 Mon Sep 17 00:00:00 2001 From: Michael Mallan Date: Wed, 28 Aug 2024 12:49:23 +0100 Subject: [PATCH 06/15] gui(installer): add general node definition --- gui/src/installer/step/node/mod.rs | 51 ++++++++++++++++++++++++++++-- 1 file changed, 49 insertions(+), 2 deletions(-) diff --git a/gui/src/installer/step/node/mod.rs b/gui/src/installer/step/node/mod.rs index 224800504..f63f1004e 100644 --- a/gui/src/installer/step/node/mod.rs +++ b/gui/src/installer/step/node/mod.rs @@ -13,15 +13,62 @@ use crate::{ use iced::Command; use liana_ui::widget::Element; +#[derive(Clone)] +pub enum NodeDefinition { + Bitcoind(DefineBitcoind), +} + +impl NodeDefinition { + fn new() -> Self { + NodeDefinition::Bitcoind(DefineBitcoind::new()) + } + + fn apply(&mut self, ctx: &mut Context) -> bool { + match self { + NodeDefinition::Bitcoind(def) => def.apply(ctx), + } + } + + fn can_try_ping(&self) -> bool { + match self { + NodeDefinition::Bitcoind(def) => def.can_try_ping(), + } + } + + fn load_context(&mut self, ctx: &Context) { + match self { + NodeDefinition::Bitcoind(def) => def.load_context(ctx), + } + } + + fn update(&mut self, message: message::DefineNode) -> Command { + match self { + NodeDefinition::Bitcoind(def) => def.update(message), + } + } + + fn view(&self) -> Element { + match self { + NodeDefinition::Bitcoind(def) => def.view(), + } + } + + fn ping(&self) -> Result<(), Error> { + match self { + NodeDefinition::Bitcoind(def) => def.ping(), + } + } +} + pub struct Node { - definition: DefineBitcoind, + definition: NodeDefinition, is_running: Option>, } impl Node { fn new() -> Self { Node { - definition: DefineBitcoind::new(), + definition: NodeDefinition::new(), is_running: None, } } From 341e4467dbf727d78f31690b8dbc405995ab4088 Mon Sep 17 00:00:00 2001 From: Michael Mallan Date: Wed, 28 Aug 2024 14:37:54 +0100 Subject: [PATCH 07/15] gui(installer): allow for different node types --- gui/src/installer/message.rs | 8 +- gui/src/installer/step/node/mod.rs | 115 ++++++++++++++++++++++------- gui/src/installer/view.rs | 30 +++++++- gui/src/node/mod.rs | 5 ++ 4 files changed, 130 insertions(+), 28 deletions(-) diff --git a/gui/src/installer/message.rs b/gui/src/installer/message.rs index 4a171a193..fc4c919e1 100644 --- a/gui/src/installer/message.rs +++ b/gui/src/installer/message.rs @@ -9,7 +9,10 @@ use crate::{ download::Progress, hw::HardwareWalletMessage, lianalite::client::{auth::AuthClient, backend::api}, - node::bitcoind::{Bitcoind, ConfigField, RpcAuthType}, + node::{ + bitcoind::{Bitcoind, ConfigField, RpcAuthType}, + NodeType, + }, }; use async_hwi::{DeviceKind, Version}; @@ -76,8 +79,9 @@ pub enum DefineBitcoind { #[derive(Debug, Clone)] pub enum DefineNode { + NodeTypeSelected(NodeType), DefineBitcoind(DefineBitcoind), - PingResult(Result<(), Error>), + PingResult((NodeType, Result<(), Error>)), Ping, } diff --git a/gui/src/installer/step/node/mod.rs b/gui/src/installer/step/node/mod.rs index f63f1004e..b2b5e5494 100644 --- a/gui/src/installer/step/node/mod.rs +++ b/gui/src/installer/step/node/mod.rs @@ -8,6 +8,7 @@ use crate::{ step::{node::bitcoind::DefineBitcoind, Step}, view, Error, }, + node::NodeType, }; use iced::Command; @@ -19,8 +20,16 @@ pub enum NodeDefinition { } impl NodeDefinition { - fn new() -> Self { - NodeDefinition::Bitcoind(DefineBitcoind::new()) + fn new(node_type: NodeType) -> Self { + match node_type { + NodeType::Bitcoind => NodeDefinition::Bitcoind(DefineBitcoind::new()), + } + } + + fn node_type(&self) -> NodeType { + match self { + NodeDefinition::Bitcoind(_) => NodeType::Bitcoind, + } } fn apply(&mut self, ctx: &mut Context) -> bool { @@ -66,16 +75,17 @@ pub struct Node { } impl Node { - fn new() -> Self { + fn new(node_type: NodeType) -> Self { Node { - definition: NodeDefinition::new(), + definition: NodeDefinition::new(node_type), is_running: None, } } } pub struct DefineNode { - node: Node, + nodes: Vec, + selected_node_type: NodeType, } impl From for Box { @@ -85,44 +95,97 @@ impl From for Box { } impl DefineNode { - pub fn new() -> Self { - Self { node: Node::new() } + pub fn new(selected_node_type: NodeType) -> Self { + let available_node_types = [ + // This is the order in which the available node types will be shown to the user. + NodeType::Bitcoind, + ]; + assert!(available_node_types.contains(&selected_node_type)); + + let nodes = available_node_types + .iter() + .copied() + .map(Node::new) + .collect(); + + Self { + nodes, + selected_node_type, + } + } + + pub fn selected_mut(&mut self) -> &mut Node { + self.get_mut(self.selected_node_type) + .expect("selected type must be present") + } + + pub fn selected(&self) -> &Node { + self.get(self.selected_node_type) + .expect("selected type must be present") + } + + pub fn get_mut(&mut self, node_type: NodeType) -> Option<&mut Node> { + self.nodes + .iter_mut() + .find(|node| node.definition.node_type() == node_type) + } + + pub fn get(&self, node_type: NodeType) -> Option<&Node> { + self.nodes + .iter() + .find(|node| node.definition.node_type() == node_type) } - fn ping(&self) -> Command { - let def = self.node.definition.clone(); - Command::perform(async move { def.ping() }, move |res| { - Message::DefineNode(message::DefineNode::PingResult(res)) + fn ping_selected(&self) -> Command { + let selected = self.selected().definition.clone(); + let node_type = selected.node_type(); + Command::perform(async move { selected.ping() }, move |res| { + Message::DefineNode(message::DefineNode::PingResult((node_type, res))) }) } - fn update_node(&mut self, message: message::DefineNode) -> Command { - self.node.is_running = None; - self.node.definition.update(message) + fn update_node( + &mut self, + node_type: NodeType, + message: message::DefineNode, + ) -> Command { + if let Some(node) = self.get_mut(node_type) { + node.is_running = None; + return node.definition.update(message); + } + Command::none() } } impl Default for DefineNode { fn default() -> Self { - Self::new() + Self::new(NodeType::Bitcoind) } } impl Step for DefineNode { fn load_context(&mut self, ctx: &Context) { - self.node.definition.load_context(ctx); + for node in self.nodes.iter_mut() { + node.definition.load_context(ctx); + } } fn update(&mut self, _hws: &mut HardwareWallets, message: Message) -> Command { if let Message::DefineNode(msg) = message { match msg { + message::DefineNode::NodeTypeSelected(node_type) => { + self.selected_node_type = node_type; + } message::DefineNode::Ping => { - return self.ping(); + return self.ping_selected(); } - message::DefineNode::PingResult(res) => { - self.node.is_running = Some(res); + message::DefineNode::PingResult((node_type, res)) => { + // Result may not be for the selected node type. + if let Some(node) = self.get_mut(node_type) { + node.is_running = Some(res); + } } msg @ message::DefineNode::DefineBitcoind(_) => { - return self.update_node(msg); + return self.update_node(NodeType::Bitcoind, msg); } } } @@ -130,7 +193,7 @@ impl Step for DefineNode { } fn apply(&mut self, ctx: &mut Context) -> bool { - self.node.definition.apply(ctx) + self.selected_mut().definition.apply(ctx) } fn view( @@ -141,14 +204,16 @@ impl Step for DefineNode { ) -> Element { view::define_bitcoin_node( progress, - self.node.definition.view(), - self.node.is_running.as_ref(), - self.node.definition.can_try_ping(), + self.nodes.iter().map(|node| node.definition.node_type()), + self.selected_node_type, + self.selected().definition.view(), + self.selected().is_running.as_ref(), + self.selected().definition.can_try_ping(), ) } fn load(&self) -> Command { - self.ping() + self.ping_selected() } fn skip(&self, ctx: &Context) -> bool { diff --git a/gui/src/installer/view.rs b/gui/src/installer/view.rs index bd86a2f7f..ec5c4de41 100644 --- a/gui/src/installer/view.rs +++ b/gui/src/installer/view.rs @@ -39,7 +39,10 @@ use crate::{ step::{DownloadState, InstallState}, Error, }, - node::bitcoind::{ConfigField, RpcAuthType, RpcAuthValues, StartInternalBitcoindError}, + node::{ + bitcoind::{ConfigField, RpcAuthType, RpcAuthValues, StartInternalBitcoindError}, + NodeType, + }, }; #[derive(Debug, Clone, Copy, PartialEq, Eq)] @@ -1161,11 +1164,36 @@ pub fn help_backup<'a>() -> Element<'a, Message> { pub fn define_bitcoin_node<'a>( progress: (usize, usize), + available_node_types: impl Iterator, + selected_node_type: NodeType, node_view: Element<'a, Message>, is_running: Option<&Result<(), Error>>, can_try_ping: bool, ) -> Element<'a, Message> { let col = Column::new() + .push( + available_node_types.fold( + Row::new() + .push(text("Node type:").small().bold()) + .spacing(10), + |row, node_type| { + row.push(radio( + match node_type { + NodeType::Bitcoind => "Bitcoin Core", + }, + node_type, + Some(selected_node_type), + |new_selection| { + Message::DefineNode(message::DefineNode::NodeTypeSelected( + new_selection, + )) + }, + )) + .spacing(30) + .align_items(Alignment::Center) + }, + ), + ) .push(node_view) .push_maybe(if is_running.is_some() { is_running.map(|res| { diff --git a/gui/src/node/mod.rs b/gui/src/node/mod.rs index ac8d53251..9f91cba04 100644 --- a/gui/src/node/mod.rs +++ b/gui/src/node/mod.rs @@ -1 +1,6 @@ pub mod bitcoind; + +#[derive(Debug, Clone, PartialEq, Eq, Copy)] +pub enum NodeType { + Bitcoind, +} From c93aa88d74620a555bd442bc3504e12af198f00a Mon Sep 17 00:00:00 2001 From: Michael Mallan Date: Wed, 28 Aug 2024 15:32:11 +0100 Subject: [PATCH 08/15] gui(installer): add electrum node option --- gui/src/installer/message.rs | 8 ++- gui/src/installer/mod.rs | 2 + gui/src/installer/step/node/electrum.rs | 86 +++++++++++++++++++++++++ gui/src/installer/step/node/mod.rs | 21 +++++- gui/src/installer/view.rs | 24 ++++++- gui/src/node/electrum.rs | 14 ++++ gui/src/node/mod.rs | 2 + 7 files changed, 154 insertions(+), 3 deletions(-) create mode 100644 gui/src/installer/step/node/electrum.rs create mode 100644 gui/src/node/electrum.rs diff --git a/gui/src/installer/message.rs b/gui/src/installer/message.rs index fc4c919e1..690f58dcd 100644 --- a/gui/src/installer/message.rs +++ b/gui/src/installer/message.rs @@ -11,7 +11,7 @@ use crate::{ lianalite::client::{auth::AuthClient, backend::api}, node::{ bitcoind::{Bitcoind, ConfigField, RpcAuthType}, - NodeType, + electrum, NodeType, }, }; use async_hwi::{DeviceKind, Version}; @@ -77,10 +77,16 @@ pub enum DefineBitcoind { RpcAuthTypeSelected(RpcAuthType), } +#[derive(Debug, Clone)] +pub enum DefineElectrum { + ConfigFieldEdited(electrum::ConfigField, String), +} + #[derive(Debug, Clone)] pub enum DefineNode { NodeTypeSelected(NodeType), DefineBitcoind(DefineBitcoind), + DefineElectrum(DefineElectrum), PingResult((NodeType, Result<(), Error>)), Ping, } diff --git a/gui/src/installer/mod.rs b/gui/src/installer/mod.rs index e6b2687ea..6464bae7b 100644 --- a/gui/src/installer/mod.rs +++ b/gui/src/installer/mod.rs @@ -701,6 +701,7 @@ pub enum Error { Backend(Arc), Settings(SettingsError), Bitcoind(String), + Electrum(String), CannotCreateDatadir(String), CannotCreateFile(String), CannotWriteToFile(String), @@ -752,6 +753,7 @@ impl std::fmt::Display for Error { Self::Backend(e) => write!(f, "Remote backend error: {}", e), Self::Settings(e) => write!(f, "Settings file error: {}", e), Self::Bitcoind(e) => write!(f, "Failed to ping bitcoind: {}", e), + Self::Electrum(e) => write!(f, "Failed to ping Electrum: {}", e), Self::CannotCreateDatadir(e) => write!(f, "Failed to create datadir: {}", e), Self::CannotGetAvailablePort(e) => write!(f, "Failed to get available port: {}", e), Self::CannotWriteToFile(e) => write!(f, "Failed to write to file: {}", e), diff --git a/gui/src/installer/step/node/electrum.rs b/gui/src/installer/step/node/electrum.rs new file mode 100644 index 000000000..8adfbd5a8 --- /dev/null +++ b/gui/src/installer/step/node/electrum.rs @@ -0,0 +1,86 @@ +use iced::Command; +use liana::{ + config::ElectrumConfig, + electrum_client::{self, ElectrumApi}, +}; +use liana_ui::{component::form, widget::*}; + +use crate::{ + installer::{ + context::Context, + message::{self, Message}, + view, Error, + }, + node::electrum::ConfigField, +}; + +#[derive(Clone)] +pub struct DefineElectrum { + address: form::Value, +} + +impl DefineElectrum { + pub fn new() -> Self { + Self { + address: form::Value::default(), + } + } + + pub fn can_try_ping(&self) -> bool { + !self.address.value.is_empty() && self.address.valid + } + + pub fn update(&mut self, message: message::DefineNode) -> Command { + if let message::DefineNode::DefineElectrum(msg) = message { + match msg { + message::DefineElectrum::ConfigFieldEdited(field, value) => match field { + ConfigField::Address => { + let value_noprefix = if value.starts_with("ssl://") { + value.replacen("ssl://", "", 1) + } else { + value.replacen("tcp://", "", 1) + }; + let noprefix_parts: Vec<_> = value_noprefix.split(':').collect(); + self.address.value.clone_from(&value); // save the value including any prefix + self.address.valid = noprefix_parts.len() == 2 + && !noprefix_parts + .first() + .expect("there are two parts") + .is_empty() + && noprefix_parts + .last() + .expect("there are two parts") + .parse::() // check it is a port + .is_ok(); + } + }, + }; + }; + Command::none() + } + + pub fn apply(&mut self, ctx: &mut Context) -> bool { + if self.can_try_ping() { + ctx.bitcoin_backend = Some(liana::config::BitcoinBackend::Electrum(ElectrumConfig { + addr: self.address.value.clone(), + })); + return true; + } + false + } + + pub fn view(&self) -> Element { + view::define_electrum(&self.address) + } + + pub fn ping(&self) -> Result<(), Error> { + let builder = electrum_client::Config::builder(); + let config = builder.timeout(Some(3)).build(); + let client = electrum_client::Client::from_config(&self.address.value, config) + .map_err(|e| Error::Electrum(e.to_string()))?; + client + .raw_call("server.ping", []) + .map_err(|e| Error::Electrum(e.to_string()))?; + Ok(()) + } +} diff --git a/gui/src/installer/step/node/mod.rs b/gui/src/installer/step/node/mod.rs index b2b5e5494..984c01380 100644 --- a/gui/src/installer/step/node/mod.rs +++ b/gui/src/installer/step/node/mod.rs @@ -1,11 +1,15 @@ pub mod bitcoind; +pub mod electrum; use crate::{ hw::HardwareWallets, installer::{ context::Context, message::{self, Message}, - step::{node::bitcoind::DefineBitcoind, Step}, + step::{ + node::{bitcoind::DefineBitcoind, electrum::DefineElectrum}, + Step, + }, view, Error, }, node::NodeType, @@ -17,54 +21,65 @@ use liana_ui::widget::Element; #[derive(Clone)] pub enum NodeDefinition { Bitcoind(DefineBitcoind), + Electrum(DefineElectrum), } impl NodeDefinition { fn new(node_type: NodeType) -> Self { match node_type { NodeType::Bitcoind => NodeDefinition::Bitcoind(DefineBitcoind::new()), + NodeType::Electrum => NodeDefinition::Electrum(DefineElectrum::new()), } } fn node_type(&self) -> NodeType { match self { NodeDefinition::Bitcoind(_) => NodeType::Bitcoind, + NodeDefinition::Electrum(_) => NodeType::Electrum, } } fn apply(&mut self, ctx: &mut Context) -> bool { match self { NodeDefinition::Bitcoind(def) => def.apply(ctx), + NodeDefinition::Electrum(def) => def.apply(ctx), } } fn can_try_ping(&self) -> bool { match self { NodeDefinition::Bitcoind(def) => def.can_try_ping(), + NodeDefinition::Electrum(def) => def.can_try_ping(), } } fn load_context(&mut self, ctx: &Context) { match self { NodeDefinition::Bitcoind(def) => def.load_context(ctx), + NodeDefinition::Electrum(_) => { + // noop for now + } } } fn update(&mut self, message: message::DefineNode) -> Command { match self { NodeDefinition::Bitcoind(def) => def.update(message), + NodeDefinition::Electrum(def) => def.update(message), } } fn view(&self) -> Element { match self { NodeDefinition::Bitcoind(def) => def.view(), + NodeDefinition::Electrum(def) => def.view(), } } fn ping(&self) -> Result<(), Error> { match self { NodeDefinition::Bitcoind(def) => def.ping(), + NodeDefinition::Electrum(def) => def.ping(), } } } @@ -99,6 +114,7 @@ impl DefineNode { let available_node_types = [ // This is the order in which the available node types will be shown to the user. NodeType::Bitcoind, + NodeType::Electrum, ]; assert!(available_node_types.contains(&selected_node_type)); @@ -187,6 +203,9 @@ impl Step for DefineNode { msg @ message::DefineNode::DefineBitcoind(_) => { return self.update_node(NodeType::Bitcoind, msg); } + msg @ message::DefineNode::DefineElectrum(_) => { + return self.update_node(NodeType::Electrum, msg); + } } } Command::none() diff --git a/gui/src/installer/view.rs b/gui/src/installer/view.rs index ec5c4de41..59ed48e4a 100644 --- a/gui/src/installer/view.rs +++ b/gui/src/installer/view.rs @@ -41,7 +41,7 @@ use crate::{ }, node::{ bitcoind::{ConfigField, RpcAuthType, RpcAuthValues, StartInternalBitcoindError}, - NodeType, + electrum, NodeType, }, }; @@ -1180,6 +1180,7 @@ pub fn define_bitcoin_node<'a>( row.push(radio( match node_type { NodeType::Bitcoind => "Bitcoin Core", + NodeType::Electrum => "Electrum", }, node_type, Some(selected_node_type), @@ -1360,6 +1361,27 @@ pub fn define_bitcoind<'a>( .into() } +pub fn define_electrum<'a>(address: &form::Value) -> Element<'a, Message> { + let col_address = Column::new() + .push(text("Address:").bold()) + .push( + form::Form::new_trimmed("127.0.0.1:50001", address, |msg| { + Message::DefineNode(DefineNode::DefineElectrum( + message::DefineElectrum::ConfigFieldEdited(electrum::ConfigField::Address, msg), + )) + }) + .warning( + "Please enter correct address (including port), \ + optionally prefixed with tcp:// or ssl://", + ) + .size(text::P1_SIZE) + .padding(10), + ) + .spacing(10); + + Column::new().push(col_address).spacing(50).into() +} + pub fn select_bitcoind_type<'a>(progress: (usize, usize)) -> Element<'a, Message> { layout( progress, diff --git a/gui/src/node/electrum.rs b/gui/src/node/electrum.rs new file mode 100644 index 000000000..97c563a84 --- /dev/null +++ b/gui/src/node/electrum.rs @@ -0,0 +1,14 @@ +use std::fmt; + +#[derive(Debug, PartialEq, Eq, Clone, Copy)] +pub enum ConfigField { + Address, +} + +impl fmt::Display for ConfigField { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + match self { + ConfigField::Address => write!(f, "RPC address"), + } + } +} diff --git a/gui/src/node/mod.rs b/gui/src/node/mod.rs index 9f91cba04..293a65268 100644 --- a/gui/src/node/mod.rs +++ b/gui/src/node/mod.rs @@ -1,6 +1,8 @@ pub mod bitcoind; +pub mod electrum; #[derive(Debug, Clone, PartialEq, Eq, Copy)] pub enum NodeType { Bitcoind, + Electrum, } From 0f09be151ca3ba1353d23b170805c845fc8ab5cc Mon Sep 17 00:00:00 2001 From: Michael Mallan Date: Wed, 28 Aug 2024 16:18:20 +0100 Subject: [PATCH 09/15] gui(installer): don't change values while waiting for ping result --- gui/src/installer/step/node/mod.rs | 44 +++++++++++++++++++----------- gui/src/installer/view.rs | 12 ++++++-- 2 files changed, 38 insertions(+), 18 deletions(-) diff --git a/gui/src/installer/step/node/mod.rs b/gui/src/installer/step/node/mod.rs index 984c01380..cb8b350dc 100644 --- a/gui/src/installer/step/node/mod.rs +++ b/gui/src/installer/step/node/mod.rs @@ -87,6 +87,7 @@ impl NodeDefinition { pub struct Node { definition: NodeDefinition, is_running: Option>, + waiting_for_ping_result: bool, } impl Node { @@ -94,6 +95,7 @@ impl Node { Node { definition: NodeDefinition::new(node_type), is_running: None, + waiting_for_ping_result: false, } } } @@ -152,22 +154,18 @@ impl DefineNode { .find(|node| node.definition.node_type() == node_type) } - fn ping_selected(&self) -> Command { - let selected = self.selected().definition.clone(); - let node_type = selected.node_type(); - Command::perform(async move { selected.ping() }, move |res| { - Message::DefineNode(message::DefineNode::PingResult((node_type, res))) - }) - } - fn update_node( &mut self, node_type: NodeType, message: message::DefineNode, ) -> Command { if let Some(node) = self.get_mut(node_type) { - node.is_running = None; - return node.definition.update(message); + // Don't make changes while waiting for a ping result so that we + // know which values the ping result applies to. + if !node.waiting_for_ping_result { + node.is_running = None; + return node.definition.update(message); + } } Command::none() } @@ -192,12 +190,28 @@ impl Step for DefineNode { self.selected_node_type = node_type; } message::DefineNode::Ping => { - return self.ping_selected(); + let selected = self.selected_mut(); + // Make sure we don't send more than one ping request at a time + // so that we know which values the result applies to. + if !selected.waiting_for_ping_result { + selected.waiting_for_ping_result = true; + selected.is_running = None; + let def = selected.definition.clone(); + let node_type = def.node_type(); + return Command::perform(async move { def.ping() }, move |res| { + Message::DefineNode(message::DefineNode::PingResult((node_type, res))) + }); + } } message::DefineNode::PingResult((node_type, res)) => { // Result may not be for the selected node type. if let Some(node) = self.get_mut(node_type) { - node.is_running = Some(res); + // Make sure we're expecting the ping result. Otherwise, the user may have changed values + // and so the ping result may not apply to the current values. + if node.waiting_for_ping_result { + node.waiting_for_ping_result = false; + node.is_running = Some(res); + } } } msg @ message::DefineNode::DefineBitcoind(_) => { @@ -221,6 +235,7 @@ impl Step for DefineNode { progress: (usize, usize), _email: Option<&str>, ) -> Element { + // TODO: Make input fields read-only while waiting for a ping result. view::define_bitcoin_node( progress, self.nodes.iter().map(|node| node.definition.node_type()), @@ -228,13 +243,10 @@ impl Step for DefineNode { self.selected().definition.view(), self.selected().is_running.as_ref(), self.selected().definition.can_try_ping(), + self.selected().waiting_for_ping_result, ) } - fn load(&self) -> Command { - self.ping_selected() - } - fn skip(&self, ctx: &Context) -> bool { !ctx.bitcoind_is_external || ctx.remote_backend.is_some() } diff --git a/gui/src/installer/view.rs b/gui/src/installer/view.rs index 59ed48e4a..da783daaa 100644 --- a/gui/src/installer/view.rs +++ b/gui/src/installer/view.rs @@ -1169,6 +1169,7 @@ pub fn define_bitcoin_node<'a>( node_view: Element<'a, Message>, is_running: Option<&Result<(), Error>>, can_try_ping: bool, + waiting_for_ping_result: bool, ) -> Element<'a, Message> { let col = Column::new() .push( @@ -1196,7 +1197,14 @@ pub fn define_bitcoin_node<'a>( ), ) .push(node_view) - .push_maybe(if is_running.is_some() { + .push_maybe(if waiting_for_ping_result { + Some(Container::new( + Row::new() + .spacing(10) + .align_items(Alignment::Center) + .push(text("Checking connection...")), + )) + } else if is_running.is_some() { is_running.map(|res| { if res.is_ok() { Container::new( @@ -1224,7 +1232,7 @@ pub fn define_bitcoin_node<'a>( .spacing(10) .push(Container::new( button::secondary(None, "Check connection") - .on_press_maybe(if can_try_ping { + .on_press_maybe(if can_try_ping && !waiting_for_ping_result { Some(Message::DefineNode(DefineNode::Ping)) } else { None From f40af570bccecf9361410df334ad30e45f57659b Mon Sep 17 00:00:00 2001 From: Michael Mallan Date: Wed, 28 Aug 2024 16:26:07 +0100 Subject: [PATCH 10/15] gui(installer): split long string and run cargo fmt --- gui/src/installer/view.rs | 179 +++++++++++++++++++++----------------- 1 file changed, 98 insertions(+), 81 deletions(-) diff --git a/gui/src/installer/view.rs b/gui/src/installer/view.rs index da783daaa..406908ad0 100644 --- a/gui/src/installer/view.rs +++ b/gui/src/installer/view.rs @@ -1395,91 +1395,108 @@ pub fn select_bitcoind_type<'a>(progress: (usize, usize)) -> Element<'a, Message progress, None, "Bitcoin node management", - Column::new().push( - Row::new() - .align_items(Alignment::Start) - .spacing(20) - .push( - Container::new( - Column::new() - .spacing(20) - .width(Length::Fixed(300.0)) - .push(text("Manage your own Bitcoin node").bold()) - ) - .padding(20), - ) - .push( - Container::new( - Column::new() - .spacing(20) - .width(Length::Fixed(300.0)) - .push(text("Have Liana manage and run a dedicated Bitcoin node").bold()) - ) - .padding(20), - ), - ) - .push( - Row::new() - .align_items(Alignment::Start) - .spacing(20) - .push( - Container::new( - Column::new() - .spacing(20) - .width(Length::Fixed(300.0)) - .align_items(Alignment::Start) - .push(text("Liana will connect to your existing instance of Bitcoin Core. You will have to make sure Bitcoin Core is running when you use Liana.\n\n(Use this if you already have a full node on your machine, and don't need a new instance)")) - ) - .padding(20), - ) - .push( - Container::new( - Column::new() - .spacing(20) - .width(Length::Fixed(300.0)) - .align_items(Alignment::Start) - .push(text("Liana will run its own instance of Bitcoin Core. This will use a pruned node, and perform the synchronization in the Liana folder.\n\nIf you select this option, Bitcoin Core will be downloaded, installed and started on the next step.\n\n(Use this if you don't want to deal with Bitcoin Core yourself, or need a new, dedicated instance for Liana)")) + Column::new() + .push( + Row::new() + .align_items(Alignment::Start) + .spacing(20) + .push( + Container::new( + Column::new() + .spacing(20) + .width(Length::Fixed(300.0)) + .push(text("Manage your own Bitcoin node").bold()), + ) + .padding(20), ) - .padding(20), - ), - ) - .push( - Row::new() - .align_items(Alignment::End) - .spacing(20) - .push( - Container::new( - Column::new() - .spacing(20) - .width(Length::Fixed(300.0)) - .align_items(Alignment::Center) - .push( - button::primary(None, "Select") + .push( + Container::new(Column::new().spacing(20).width(Length::Fixed(300.0)).push( + text("Have Liana manage and run a dedicated Bitcoin node").bold(), + )) + .padding(20), + ), + ) + .push( + Row::new() + .align_items(Alignment::Start) + .spacing(20) + .push( + Container::new( + Column::new() + .spacing(20) .width(Length::Fixed(300.0)) - .on_press(Message::SelectBitcoindType( - message::SelectBitcoindTypeMsg::UseExternal(true), - )), - ) + .align_items(Alignment::Start) + .push(text( + "Liana will connect to your \ + existing instance of Bitcoin Core. \ + You will have to make sure Bitcoin Core \ + is running when you use Liana.\n\n\ + (Use this if you already have a \ + full node on your machine, \ + and don't need a new instance)", + )), + ) + .padding(20), ) - .padding(20), - ) - .push( - Container::new( - Column::new() - .spacing(20) - .width(Length::Fixed(300.0)) - .align_items(Alignment::Center) - .push( - button::primary(None, "Select") - .width(Length::Fixed(300.0)) - .on_press(Message::SelectBitcoindType( - message::SelectBitcoindTypeMsg::UseExternal(false), - )), - ) + .push( + Container::new( + Column::new() + .spacing(20) + .width(Length::Fixed(300.0)) + .align_items(Alignment::Start) + .push(text( + "Liana will run its own instance \ + of Bitcoin Core. This will use a pruned node, \ + and perform the synchronization in the \ + Liana folder.\n\n\ + If you select this option, Bitcoin Core will \ + be downloaded, installed and started \ + on the next step.\n\n\ + (Use this if you don't want to deal with \ + Bitcoin Core yourself, \ + or need a new, dedicated instance for Liana)", + )), + ) + .padding(20), + ), + ) + .push( + Row::new() + .align_items(Alignment::End) + .spacing(20) + .push( + Container::new( + Column::new() + .spacing(20) + .width(Length::Fixed(300.0)) + .align_items(Alignment::Center) + .push( + button::primary(None, "Select") + .width(Length::Fixed(300.0)) + .on_press(Message::SelectBitcoindType( + message::SelectBitcoindTypeMsg::UseExternal(true), + )), + ), + ) + .padding(20), ) - .padding(20), - ), - ), + .push( + Container::new( + Column::new() + .spacing(20) + .width(Length::Fixed(300.0)) + .align_items(Alignment::Center) + .push( + button::primary(None, "Select") + .width(Length::Fixed(300.0)) + .on_press(Message::SelectBitcoindType( + message::SelectBitcoindTypeMsg::UseExternal(false), + )), + ), + ) + .padding(20), + ), + ), true, Some(Message::Previous), ) From 0993905879629c58da24a1145acbb49faf31da3a Mon Sep 17 00:00:00 2001 From: Michael Mallan Date: Wed, 28 Aug 2024 16:36:11 +0100 Subject: [PATCH 11/15] gui(installer): update wording to include Electrum --- gui/src/installer/view.rs | 43 +++++++++++++++++++-------------------- 1 file changed, 21 insertions(+), 22 deletions(-) diff --git a/gui/src/installer/view.rs b/gui/src/installer/view.rs index 406908ad0..31c57e196 100644 --- a/gui/src/installer/view.rs +++ b/gui/src/installer/view.rs @@ -1252,7 +1252,7 @@ pub fn define_bitcoin_node<'a>( layout( progress, None, - "Set up connection to the Bitcoin full node", + "Set up connection to the Bitcoin node", col, true, Some(Message::Previous), @@ -1405,14 +1405,20 @@ pub fn select_bitcoind_type<'a>(progress: (usize, usize)) -> Element<'a, Message Column::new() .spacing(20) .width(Length::Fixed(300.0)) - .push(text("Manage your own Bitcoin node").bold()), + .push(text("I already have a node").bold()), ) .padding(20), ) .push( - Container::new(Column::new().spacing(20).width(Length::Fixed(300.0)).push( - text("Have Liana manage and run a dedicated Bitcoin node").bold(), - )) + Container::new( + Column::new().spacing(20).width(Length::Fixed(300.0)).push( + text( + "I want Liana to automatically install \ + a Bitcoin node on my device", + ) + .bold(), + ), + ) .padding(20), ), ) @@ -1427,13 +1433,9 @@ pub fn select_bitcoind_type<'a>(progress: (usize, usize)) -> Element<'a, Message .width(Length::Fixed(300.0)) .align_items(Alignment::Start) .push(text( - "Liana will connect to your \ - existing instance of Bitcoin Core. \ - You will have to make sure Bitcoin Core \ - is running when you use Liana.\n\n\ - (Use this if you already have a \ - full node on your machine, \ - and don't need a new instance)", + "Select this option if you already have \ + a Bitcoin node running locally or remotely. \ + Liana will connect to it.", )), ) .padding(20), @@ -1445,16 +1447,13 @@ pub fn select_bitcoind_type<'a>(progress: (usize, usize)) -> Element<'a, Message .width(Length::Fixed(300.0)) .align_items(Alignment::Start) .push(text( - "Liana will run its own instance \ - of Bitcoin Core. This will use a pruned node, \ - and perform the synchronization in the \ - Liana folder.\n\n\ - If you select this option, Bitcoin Core will \ - be downloaded, installed and started \ - on the next step.\n\n\ - (Use this if you don't want to deal with \ - Bitcoin Core yourself, \ - or need a new, dedicated instance for Liana)", + "Liana will install a pruned node \ + on your computer. You won't need to do anything \ + except have some disk space available \ + (~30GB required on mainnet) and \ + wait for the initial synchronization with the \ + network (it can take some days depending on \ + your internet connection speed).", )), ) .padding(20), From db20ae4b677cf4ffdf19ad0df6f50ea983b34a02 Mon Sep 17 00:00:00 2001 From: Michael Mallan Date: Fri, 30 Aug 2024 12:12:07 +0100 Subject: [PATCH 12/15] gui(installer): reduce empty space height --- gui/src/installer/view.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gui/src/installer/view.rs b/gui/src/installer/view.rs index 31c57e196..70e22a362 100644 --- a/gui/src/installer/view.rs +++ b/gui/src/installer/view.rs @@ -1225,7 +1225,7 @@ pub fn define_bitcoin_node<'a>( } }) } else { - Some(Container::new(Space::with_height(Length::Fixed(25.0)))) + Some(Container::new(Space::with_height(Length::Fixed(21.0)))) }) .push( Row::new() From b570039ff8e740be43c6aebdc62910f4ab4eac05 Mon Sep 17 00:00:00 2001 From: Michael Mallan Date: Tue, 3 Sep 2024 09:03:40 +0100 Subject: [PATCH 13/15] gui(settings): rename Bitcoin Core to Node --- gui/src/app/view/settings.rs | 4 ++-- gui/src/installer/view.rs | 11 ++++++----- 2 files changed, 8 insertions(+), 7 deletions(-) diff --git a/gui/src/app/view/settings.rs b/gui/src/app/view/settings.rs index fc22c0221..893d4077a 100644 --- a/gui/src/app/view/settings.rs +++ b/gui/src/app/view/settings.rs @@ -51,7 +51,7 @@ pub fn list(cache: &Cache, is_remote_backend: bool) -> Element { Button::new( Row::new() .push(badge::Badge::new(icon::bitcoin_icon())) - .push(text("Bitcoin Core").bold()) + .push(text("Node").bold()) .padding(10) .spacing(20) .align_items(Alignment::Center) @@ -161,7 +161,7 @@ pub fn bitcoind_settings<'a>( ) .push(icon::chevron_right().size(30)) .push( - Button::new(text("Bitcoin Core").size(30).bold()) + Button::new(text("Node").size(30).bold()) .style(theme::Button::Transparent) .on_press(Message::Settings(SettingsMessage::EditBitcoindSettings)), ), diff --git a/gui/src/installer/view.rs b/gui/src/installer/view.rs index 70e22a362..142c9d480 100644 --- a/gui/src/installer/view.rs +++ b/gui/src/installer/view.rs @@ -527,11 +527,12 @@ pub fn import_descriptor<'a>( "Import the wallet", Column::new() .push(Column::new().spacing(20).push(col_descriptor).push(text( - "After creating the wallet, \ - you will need to perform a rescan of \ - the blockchain in order to see your \ - coins and past transactions. This can \ - be done in Settings > Bitcoin Core.", + "If you are using a Bitcoin Core node, \ + you will need to perform a rescan of \ + the blockchain after creating the wallet \ + in order to see your coins and past \ + transactions. This can be done in \ + Settings > Node.", ))) .push( if imported_descriptor.value.is_empty() || !imported_descriptor.valid { From 2381227216663a6a2336ee79905646fb2a3aadca Mon Sep 17 00:00:00 2001 From: Michael Mallan Date: Tue, 3 Sep 2024 09:02:35 +0100 Subject: [PATCH 14/15] gui(settings): view & edit Electrum settings --- gui/src/app/state/settings/bitcoind.rs | 168 +++++++++++++++++++++++-- gui/src/app/view/message.rs | 1 + gui/src/app/view/settings.rs | 167 ++++++++++++++++++++++++ 3 files changed, 326 insertions(+), 10 deletions(-) diff --git a/gui/src/app/state/settings/bitcoind.rs b/gui/src/app/state/settings/bitcoind.rs index 03f0a2a7b..b2f02c9c2 100644 --- a/gui/src/app/state/settings/bitcoind.rs +++ b/gui/src/app/state/settings/bitcoind.rs @@ -9,7 +9,9 @@ use iced::Command; use tracing::info; use liana::{ - config::{BitcoinBackend, BitcoinConfig, BitcoindConfig, BitcoindRpcAuth, Config}, + config::{ + BitcoinBackend, BitcoinConfig, BitcoindConfig, BitcoindRpcAuth, Config, ElectrumConfig, + }, miniscript::bitcoin::Network, }; @@ -27,6 +29,7 @@ pub struct BitcoindSettingsState { config_updated: bool, node_settings: Option, + electrum_settings: Option, rescan_settings: RescanSetting, } @@ -37,19 +40,19 @@ impl BitcoindSettingsState { daemon_is_external: bool, bitcoind_is_internal: bool, ) -> Self { - let bitcoind_config = if let Some(BitcoinBackend::Bitcoind(bitcoind_config)) = - config.clone().and_then(|c| c.bitcoin_backend) - { - Some(bitcoind_config) - } else { - None - }; + let (bitcoind_config, electrum_config) = + match config.clone().and_then(|c| c.bitcoin_backend) { + Some(BitcoinBackend::Bitcoind(bitcoind_config)) => (Some(bitcoind_config), None), + Some(BitcoinBackend::Electrum(electrum_config)) => (None, Some(electrum_config)), + _ => (None, None), + }; BitcoindSettingsState { warning: None, config_updated: false, node_settings: bitcoind_config.map(|bitcoind_config| { BitcoindSettings::new( config + .clone() .expect("config must exist if bitcoind_config exists") .bitcoin_config, bitcoind_config, @@ -57,6 +60,15 @@ impl BitcoindSettingsState { bitcoind_is_internal, ) }), + electrum_settings: electrum_config.map(|electrum_config| { + ElectrumSettings::new( + config + .expect("config must exist if electrum_config exists") + .bitcoin_config, + electrum_config, + daemon_is_external, + ) + }), rescan_settings: RescanSetting::new(cache.rescan_progress), } } @@ -82,6 +94,14 @@ impl State for BitcoindSettingsState { )) }); } + if let Some(settings) = &mut self.electrum_settings { + settings.edited(true); + return Command::perform(async {}, |_| { + Message::View(view::Message::Settings( + view::SettingsMessage::EditBitcoindSettings, + )) + }); + } } Err(e) => { self.config_updated = false; @@ -110,6 +130,13 @@ impl State for BitcoindSettingsState { return settings.update(daemon, cache, msg); } } + Message::View(view::Message::Settings(view::SettingsMessage::ElectrumSettings( + msg, + ))) => { + if let Some(settings) = &mut self.electrum_settings { + return settings.update(daemon, cache, msg); + } + } Message::View(view::Message::Settings(view::SettingsMessage::RescanSettings(msg))) => { return self.rescan_settings.update(daemon, cache, msg); } @@ -121,8 +148,17 @@ impl State for BitcoindSettingsState { fn view<'a>(&'a self, cache: &'a Cache) -> Element<'a, view::Message> { let can_edit_bitcoind_settings = self.node_settings.is_some() && !self.rescan_settings.processing; - let can_do_rescan = !self.rescan_settings.processing - && self.node_settings.as_ref().map(|settings| settings.edit) == Some(false); + let can_edit_electrum_settings = + self.electrum_settings.is_some() && !self.rescan_settings.processing; + let settings_edit = self + .node_settings + .as_ref() + .map(|settings| settings.edit) + .or(self + .electrum_settings + .as_ref() + .map(|settings| settings.edit)); + let can_do_rescan = !self.rescan_settings.processing && settings_edit == Some(false); view::settings::bitcoind_settings( cache, self.warning.as_ref(), @@ -139,6 +175,19 @@ impl State for BitcoindSettingsState { view::Message::Settings(view::SettingsMessage::RescanSettings(msg)) }), ] + } else if let Some(settings) = &self.electrum_settings { + vec![ + settings + .view(cache, can_edit_electrum_settings) + .map(move |msg| { + view::Message::Settings(view::SettingsMessage::ElectrumSettings(msg)) + }), + self.rescan_settings + .view(cache, can_do_rescan) + .map(move |msg| { + view::Message::Settings(view::SettingsMessage::RescanSettings(msg)) + }), + ] } else { vec![self .rescan_settings @@ -321,6 +370,105 @@ impl BitcoindSettings { } } +#[derive(Debug)] +pub struct ElectrumSettings { + electrum_config: ElectrumConfig, + bitcoin_config: BitcoinConfig, + edit: bool, + processing: bool, + addr: form::Value, + daemon_is_external: bool, +} + +impl ElectrumSettings { + fn new( + bitcoin_config: BitcoinConfig, + electrum_config: ElectrumConfig, + daemon_is_external: bool, + ) -> ElectrumSettings { + let addr = electrum_config.addr.to_string(); + ElectrumSettings { + daemon_is_external, + electrum_config, + bitcoin_config, + edit: false, + processing: false, + addr: form::Value { + valid: true, + value: addr, + }, + } + } +} + +impl ElectrumSettings { + fn edited(&mut self, success: bool) { + self.processing = false; + if success { + self.edit = false; + } + } + + fn update( + &mut self, + daemon: Arc, + _cache: &Cache, + message: view::SettingsEditMessage, + ) -> Command { + match message { + view::SettingsEditMessage::Select => { + if !self.processing { + self.edit = true; + } + } + view::SettingsEditMessage::Cancel => { + if !self.processing { + self.edit = false; + } + } + view::SettingsEditMessage::FieldEdited(field, value) => { + if !self.processing && field == "address" { + self.addr.value = value + } + } + view::SettingsEditMessage::Confirm => { + if self.addr.valid { + let mut daemon_config = daemon.config().cloned().unwrap(); + daemon_config.bitcoin_backend = + Some(liana::config::BitcoinBackend::Electrum(ElectrumConfig { + addr: self.addr.value.clone(), + })); + self.processing = true; + return Command::perform(async move { daemon_config }, |cfg| { + Message::LoadDaemonConfig(Box::new(cfg)) + }); + } + } + _ => {} + }; + Command::none() + } + + fn view<'a>(&self, cache: &'a Cache, can_edit: bool) -> Element<'a, view::SettingsEditMessage> { + if self.edit { + view::settings::electrum_edit( + self.bitcoin_config.network, + cache.blockheight, + &self.addr, + self.processing, + ) + } else { + view::settings::electrum( + self.bitcoin_config.network, + &self.electrum_config, + cache.blockheight, + Some(cache.blockheight != 0), + can_edit && !self.daemon_is_external, + ) + } + } +} + #[derive(Debug, Default)] pub struct RescanSetting { processing: bool, diff --git a/gui/src/app/view/message.rs b/gui/src/app/view/message.rs index 78118ff85..c726b5fc5 100644 --- a/gui/src/app/view/message.rs +++ b/gui/src/app/view/message.rs @@ -67,6 +67,7 @@ pub enum SpendTxMessage { pub enum SettingsMessage { EditBitcoindSettings, BitcoindSettings(SettingsEditMessage), + ElectrumSettings(SettingsEditMessage), RescanSettings(SettingsEditMessage), EditRemoteBackendSettings, RemoteBackendSettings(RemoteBackendSettingsMessage), diff --git a/gui/src/app/view/settings.rs b/gui/src/app/view/settings.rs index 893d4077a..b6b310b55 100644 --- a/gui/src/app/view/settings.rs +++ b/gui/src/app/view/settings.rs @@ -532,6 +532,173 @@ pub fn bitcoind<'a>( .into() } +pub fn electrum_edit<'a>( + network: Network, + blockheight: i32, + addr: &form::Value, + processing: bool, +) -> Element<'a, SettingsEditMessage> { + let mut col = Column::new().spacing(20); + if blockheight != 0 { + col = col + .push( + Row::new() + .push( + Row::new() + .push(badge::Badge::new(icon::network_icon())) + .push( + Column::new() + .push(text("Network:")) + .push(text(network.to_string()).bold()), + ) + .spacing(10) + .width(Length::FillPortion(1)), + ) + .push( + Row::new() + .push(badge::Badge::new(icon::block_icon())) + .push( + Column::new() + .push(text("Block Height:")) + .push(text(blockheight.to_string()).bold()), + ) + .spacing(10) + .width(Length::FillPortion(1)), + ), + ) + .push(separation().width(Length::Fill)); + } + + col = col.push( + Column::new() + .push(text("Address:").bold().small()) + .push( + form::Form::new_trimmed("127:0.0.1:50001", addr, |value| { + SettingsEditMessage::FieldEdited("address", value) + }) + .warning("Please enter a valid address") + .size(P1_SIZE) + .padding(5), + ) + .spacing(5), + ); + + let mut cancel_button = button::transparent(None, " Cancel ").padding(5); + let mut confirm_button = button::primary(None, " Save ").padding(5); + if !processing { + cancel_button = cancel_button.on_press(SettingsEditMessage::Cancel); + confirm_button = confirm_button.on_press(SettingsEditMessage::Confirm); + } + + card::simple(Container::new( + Column::new() + .push( + Row::new() + .push(badge::Badge::new(icon::bitcoin_icon())) + .push(text("Electrum").bold()) + .padding(10) + .spacing(20) + .align_items(Alignment::Center) + .width(Length::Fill), + ) + .push(separation().width(Length::Fill)) + .push(col) + .push( + Container::new( + Row::new() + .push(cancel_button) + .push(confirm_button) + .spacing(10) + .align_items(Alignment::Center), + ) + .width(Length::Fill) + .align_x(alignment::Horizontal::Right), + ) + .spacing(20), + )) + .width(Length::Fill) + .into() +} + +pub fn electrum<'a>( + network: Network, + config: &liana::config::ElectrumConfig, + blockheight: i32, + is_running: Option, + can_edit: bool, +) -> Element<'a, SettingsEditMessage> { + let mut col = Column::new().spacing(20); + if blockheight != 0 { + col = col + .push( + Row::new() + .push( + Row::new() + .push(badge::Badge::new(icon::network_icon())) + .push( + Column::new() + .push(text("Network:")) + .push(text(network.to_string()).bold()), + ) + .spacing(10) + .width(Length::FillPortion(1)), + ) + .push( + Row::new() + .push(badge::Badge::new(icon::block_icon())) + .push( + Column::new() + .push(text("Block Height:")) + .push(text(blockheight.to_string()).bold()), + ) + .spacing(10) + .width(Length::FillPortion(1)), + ), + ) + .push(separation().width(Length::Fill)); + } + + let rows = vec![("Address:", config.addr.to_string())]; + + let mut col_fields = Column::new(); + for (k, v) in rows { + col_fields = col_fields.push( + Row::new() + .push(Container::new(text(k).bold().small()).width(Length::Fill)) + .push(text(v).small()), + ); + } + + card::simple(Container::new( + Column::new() + .push( + Row::new() + .push( + Row::new() + .push(badge::Badge::new(icon::bitcoin_icon())) + .push(text("Electrum").bold()) + .push(is_running_label(is_running)) + .spacing(20) + .align_items(Alignment::Center) + .width(Length::Fill), + ) + .push(if can_edit { + Button::new(icon::pencil_icon()) + .style(theme::Button::TransparentBorder) + .on_press(SettingsEditMessage::Select) + } else { + Button::new(icon::pencil_icon()).style(theme::Button::TransparentBorder) + }) + .align_items(Alignment::Center), + ) + .push(separation().width(Length::Fill)) + .push(col.push(col_fields)) + .spacing(20), + )) + .width(Length::Fill) + .into() +} + pub fn is_running_label<'a, T: 'a>(is_running: Option) -> Container<'a, T> { if let Some(running) = is_running { if running { From 819eb920c0210b9d95689f40cfbeba65c03280af Mon Sep 17 00:00:00 2001 From: Michael Mallan Date: Thu, 5 Sep 2024 07:58:22 +0100 Subject: [PATCH 15/15] gui(settings): allow to change node type --- gui/src/app/state/settings/bitcoind.rs | 70 ++++++++++++++++++-------- gui/src/app/view/settings.rs | 48 ++++++++++++------ 2 files changed, 82 insertions(+), 36 deletions(-) diff --git a/gui/src/app/state/settings/bitcoind.rs b/gui/src/app/state/settings/bitcoind.rs index b2f02c9c2..35d36ebc0 100644 --- a/gui/src/app/state/settings/bitcoind.rs +++ b/gui/src/app/state/settings/bitcoind.rs @@ -1,5 +1,5 @@ use std::convert::{From, TryInto}; -use std::net::SocketAddr; +use std::net::{SocketAddr, SocketAddrV4}; use std::path::PathBuf; use std::str::FromStr; use std::sync::Arc; @@ -20,7 +20,10 @@ use liana_ui::{component::form, widget::Element}; use crate::{ app::{cache::Cache, error::Error, message::Message, state::settings::State, view}, daemon::Daemon, - node::bitcoind::{RpcAuthType, RpcAuthValues}, + node::{ + bitcoind::{RpcAuthType, RpcAuthValues}, + NodeType, + }, }; #[derive(Debug)] @@ -40,10 +43,25 @@ impl BitcoindSettingsState { daemon_is_external: bool, bitcoind_is_internal: bool, ) -> Self { + let mut configured_node_type = None; let (bitcoind_config, electrum_config) = match config.clone().and_then(|c| c.bitcoin_backend) { - Some(BitcoinBackend::Bitcoind(bitcoind_config)) => (Some(bitcoind_config), None), - Some(BitcoinBackend::Electrum(electrum_config)) => (None, Some(electrum_config)), + Some(BitcoinBackend::Bitcoind(bitcoind_config)) => { + configured_node_type = Some(NodeType::Bitcoind); + let dummy_electrum = ElectrumConfig { + addr: String::default(), + }; + (Some(bitcoind_config), Some(dummy_electrum)) + } + Some(BitcoinBackend::Electrum(electrum_config)) => { + configured_node_type = Some(NodeType::Electrum); + // The dummy values will be ignored. + let dummy_bitcoind = BitcoindConfig { + addr: SocketAddr::V4(SocketAddrV4::from_str("127.0.0.1:10000").unwrap()), + rpc_auth: BitcoindRpcAuth::CookieFile(PathBuf::from_str("").unwrap()), + }; + (Some(dummy_bitcoind), Some(electrum_config)) + } _ => (None, None), }; BitcoindSettingsState { @@ -51,6 +69,7 @@ impl BitcoindSettingsState { config_updated: false, node_settings: bitcoind_config.map(|bitcoind_config| { BitcoindSettings::new( + configured_node_type, config .clone() .expect("config must exist if bitcoind_config exists") @@ -62,6 +81,7 @@ impl BitcoindSettingsState { }), electrum_settings: electrum_config.map(|electrum_config| { ElectrumSettings::new( + configured_node_type, config .expect("config must exist if electrum_config exists") .bitcoin_config, @@ -150,15 +170,13 @@ impl State for BitcoindSettingsState { self.node_settings.is_some() && !self.rescan_settings.processing; let can_edit_electrum_settings = self.electrum_settings.is_some() && !self.rescan_settings.processing; - let settings_edit = self - .node_settings - .as_ref() - .map(|settings| settings.edit) - .or(self + let settings_edit = self.node_settings.as_ref().map(|settings| settings.edit) == Some(true) + || self .electrum_settings .as_ref() - .map(|settings| settings.edit)); - let can_do_rescan = !self.rescan_settings.processing && settings_edit == Some(false); + .map(|settings| settings.edit) + == Some(true); + let can_do_rescan = !self.rescan_settings.processing && !settings_edit; view::settings::bitcoind_settings( cache, self.warning.as_ref(), @@ -169,15 +187,9 @@ impl State for BitcoindSettingsState { .map(move |msg| { view::Message::Settings(view::SettingsMessage::BitcoindSettings(msg)) }), - self.rescan_settings - .view(cache, can_do_rescan) - .map(move |msg| { - view::Message::Settings(view::SettingsMessage::RescanSettings(msg)) - }), - ] - } else if let Some(settings) = &self.electrum_settings { - vec![ - settings + self.electrum_settings + .as_ref() + .expect("If we have bitcoind, we must also have electrum") .view(cache, can_edit_electrum_settings) .map(move |msg| { view::Message::Settings(view::SettingsMessage::ElectrumSettings(msg)) @@ -208,6 +220,7 @@ impl From for Box { #[derive(Debug)] pub struct BitcoindSettings { + configured_node_type: Option, bitcoind_config: BitcoindConfig, bitcoin_config: BitcoinConfig, edit: bool, @@ -221,6 +234,7 @@ pub struct BitcoindSettings { impl BitcoindSettings { fn new( + configured_node_type: Option, bitcoin_config: BitcoinConfig, bitcoind_config: BitcoindConfig, daemon_is_external: bool, @@ -253,8 +267,13 @@ impl BitcoindSettings { RpcAuthType::UserPass, ), }; - let addr = bitcoind_config.addr.to_string(); + let addr = if configured_node_type == Some(NodeType::Bitcoind) { + bitcoind_config.addr.to_string() + } else { + String::default() + }; BitcoindSettings { + configured_node_type, daemon_is_external, bitcoind_is_internal, bitcoind_config, @@ -349,8 +368,10 @@ impl BitcoindSettings { } fn view<'a>(&self, cache: &'a Cache, can_edit: bool) -> Element<'a, view::SettingsEditMessage> { + let is_configured_node_type = self.configured_node_type == Some(NodeType::Bitcoind); if self.edit { view::settings::bitcoind_edit( + is_configured_node_type, self.bitcoin_config.network, cache.blockheight, &self.addr, @@ -360,6 +381,7 @@ impl BitcoindSettings { ) } else { view::settings::bitcoind( + is_configured_node_type, self.bitcoin_config.network, &self.bitcoind_config, cache.blockheight, @@ -372,6 +394,7 @@ impl BitcoindSettings { #[derive(Debug)] pub struct ElectrumSettings { + configured_node_type: Option, electrum_config: ElectrumConfig, bitcoin_config: BitcoinConfig, edit: bool, @@ -382,12 +405,14 @@ pub struct ElectrumSettings { impl ElectrumSettings { fn new( + configured_node_type: Option, bitcoin_config: BitcoinConfig, electrum_config: ElectrumConfig, daemon_is_external: bool, ) -> ElectrumSettings { let addr = electrum_config.addr.to_string(); ElectrumSettings { + configured_node_type, daemon_is_external, electrum_config, bitcoin_config, @@ -450,8 +475,10 @@ impl ElectrumSettings { } fn view<'a>(&self, cache: &'a Cache, can_edit: bool) -> Element<'a, view::SettingsEditMessage> { + let is_configured_node_type = self.configured_node_type == Some(NodeType::Electrum); if self.edit { view::settings::electrum_edit( + is_configured_node_type, self.bitcoin_config.network, cache.blockheight, &self.addr, @@ -459,6 +486,7 @@ impl ElectrumSettings { ) } else { view::settings::electrum( + is_configured_node_type, self.bitcoin_config.network, &self.electrum_config, cache.blockheight, diff --git a/gui/src/app/view/settings.rs b/gui/src/app/view/settings.rs index b6b310b55..95782c51a 100644 --- a/gui/src/app/view/settings.rs +++ b/gui/src/app/view/settings.rs @@ -298,6 +298,7 @@ pub fn remote_backend_section<'a>( } pub fn bitcoind_edit<'a>( + is_configured_node_type: bool, network: Network, blockheight: i32, addr: &form::Value, @@ -306,7 +307,7 @@ pub fn bitcoind_edit<'a>( processing: bool, ) -> Element<'a, SettingsEditMessage> { let mut col = Column::new().spacing(20); - if blockheight != 0 { + if is_configured_node_type && blockheight != 0 { col = col .push( Row::new() @@ -444,6 +445,7 @@ pub fn bitcoind_edit<'a>( } pub fn bitcoind<'a>( + is_configured_node_type: bool, network: Network, config: &liana::config::BitcoindConfig, blockheight: i32, @@ -451,7 +453,7 @@ pub fn bitcoind<'a>( can_edit: bool, ) -> Element<'a, SettingsEditMessage> { let mut col = Column::new().spacing(20); - if blockheight != 0 { + if is_configured_node_type && blockheight != 0 { col = col .push( Row::new() @@ -482,16 +484,18 @@ pub fn bitcoind<'a>( } let mut rows = vec![]; - match &config.rpc_auth { - BitcoindRpcAuth::CookieFile(path) => { - rows.push(("Cookie file path:", path.to_str().unwrap().to_string())); - } - BitcoindRpcAuth::UserPass(user, password) => { - rows.push(("User:", user.clone())); - rows.push(("Password:", password.clone())); + if is_configured_node_type { + match &config.rpc_auth { + BitcoindRpcAuth::CookieFile(path) => { + rows.push(("Cookie file path:", path.to_str().unwrap().to_string())); + } + BitcoindRpcAuth::UserPass(user, password) => { + rows.push(("User:", user.clone())); + rows.push(("Password:", password.clone())); + } } + rows.push(("Socket address:", config.addr.to_string())); } - rows.push(("Socket address:", config.addr.to_string())); let mut col_fields = Column::new(); for (k, v) in rows { @@ -510,7 +514,11 @@ pub fn bitcoind<'a>( Row::new() .push(badge::Badge::new(icon::bitcoin_icon())) .push(text("Bitcoin Core").bold()) - .push(is_running_label(is_running)) + .push_maybe(if is_configured_node_type { + Some(is_running_label(is_running)) + } else { + None + }) .spacing(20) .align_items(Alignment::Center) .width(Length::Fill), @@ -533,13 +541,14 @@ pub fn bitcoind<'a>( } pub fn electrum_edit<'a>( + is_configured_node_type: bool, network: Network, blockheight: i32, addr: &form::Value, processing: bool, ) -> Element<'a, SettingsEditMessage> { let mut col = Column::new().spacing(20); - if blockheight != 0 { + if is_configured_node_type && blockheight != 0 { col = col .push( Row::new() @@ -621,6 +630,7 @@ pub fn electrum_edit<'a>( } pub fn electrum<'a>( + is_configured_node_type: bool, network: Network, config: &liana::config::ElectrumConfig, blockheight: i32, @@ -628,7 +638,7 @@ pub fn electrum<'a>( can_edit: bool, ) -> Element<'a, SettingsEditMessage> { let mut col = Column::new().spacing(20); - if blockheight != 0 { + if is_configured_node_type && blockheight != 0 { col = col .push( Row::new() @@ -658,7 +668,11 @@ pub fn electrum<'a>( .push(separation().width(Length::Fill)); } - let rows = vec![("Address:", config.addr.to_string())]; + let rows = if is_configured_node_type { + vec![("Address:", config.addr.to_string())] + } else { + vec![] + }; let mut col_fields = Column::new(); for (k, v) in rows { @@ -677,7 +691,11 @@ pub fn electrum<'a>( Row::new() .push(badge::Badge::new(icon::bitcoin_icon())) .push(text("Electrum").bold()) - .push(is_running_label(is_running)) + .push_maybe(if is_configured_node_type { + Some(is_running_label(is_running)) + } else { + None + }) .spacing(20) .align_items(Alignment::Center) .width(Length::Fill),