From c93aa88d74620a555bd442bc3504e12af198f00a Mon Sep 17 00:00:00 2001 From: Michael Mallan Date: Wed, 28 Aug 2024 15:32:11 +0100 Subject: [PATCH] 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 fc4c919e..690f58dc 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 e6b2687e..6464bae7 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 00000000..8adfbd5a --- /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 b2b5e549..984c0138 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 ec5c4de4..59ed48e4 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 00000000..97c563a8 --- /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 9f91cba0..293a6526 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, }