From ec228485e558ff2e04052e3a6fbc81c3fc55997b Mon Sep 17 00:00:00 2001 From: edouard Date: Wed, 5 Apr 2023 11:31:50 +0200 Subject: [PATCH] gui: check connection to bitcoind in installer close #115 --- gui/Cargo.lock | 1 + gui/Cargo.toml | 3 +++ gui/src/installer/message.rs | 2 ++ gui/src/installer/mod.rs | 14 +++++++++++ gui/src/installer/step/mod.rs | 45 +++++++++++++++++++++++++++++++++-- gui/src/installer/view.rs | 43 ++++++++++++++++++++++++++++++--- 6 files changed, 103 insertions(+), 5 deletions(-) diff --git a/gui/Cargo.lock b/gui/Cargo.lock index 68451450..e5fd2535 100644 --- a/gui/Cargo.lock +++ b/gui/Cargo.lock @@ -1757,6 +1757,7 @@ dependencies = [ "iced", "iced_lazy", "iced_native", + "jsonrpc", "liana", "liana_ui", "log", diff --git a/gui/Cargo.toml b/gui/Cargo.toml index 084f5668..ebee0052 100644 --- a/gui/Cargo.toml +++ b/gui/Cargo.toml @@ -28,6 +28,9 @@ tokio = {version = "1.21.0", features = ["signal"]} serde = { version = "1.0", features = ["derive"] } serde_json = "1.0" +# Used to ping bitcoind node +jsonrpc = "0.12" + # Logging stuff tracing = "0.1.37" tracing-subscriber = "0.3.16" diff --git a/gui/src/installer/message.rs b/gui/src/installer/message.rs index e80856cd..e62d8e57 100644 --- a/gui/src/installer/message.rs +++ b/gui/src/installer/message.rs @@ -38,6 +38,8 @@ pub enum Message { pub enum DefineBitcoind { CookiePathEdited(String), AddressEdited(String), + PingBitcoindResult(Result<(), Error>), + PingBitcoind, } #[derive(Debug, Clone)] diff --git a/gui/src/installer/mod.rs b/gui/src/installer/mod.rs index 153a43bc..8d388e59 100644 --- a/gui/src/installer/mod.rs +++ b/gui/src/installer/mod.rs @@ -294,6 +294,7 @@ pub fn create_and_write_file( #[derive(Debug, Clone)] pub enum Error { + Bitcoind(String), CannotCreateDatadir(String), CannotCreateFile(String), CannotWriteToFile(String), @@ -301,6 +302,18 @@ pub enum Error { HardwareWallet(async_hwi::Error), } +impl From for Error { + fn from(error: jsonrpc::simple_http::Error) -> Self { + Error::Bitcoind(error.to_string()) + } +} + +impl From for Error { + fn from(error: jsonrpc::Error) -> Self { + Error::Bitcoind(error.to_string()) + } +} + impl From for Error { fn from(error: async_hwi::Error) -> Self { Error::HardwareWallet(error) @@ -310,6 +323,7 @@ impl From for Error { impl std::fmt::Display for Error { fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { match self { + Self::Bitcoind(e) => write!(f, "Failed to ping bitcoind: {}", e), Self::CannotCreateDatadir(e) => write!(f, "Failed to create datadir: {}", e), Self::CannotWriteToFile(e) => write!(f, "Failed to write to file: {}", e), Self::CannotCreateFile(e) => write!(f, "Failed to create file: {}", e), diff --git a/gui/src/installer/step/mod.rs b/gui/src/installer/step/mod.rs index 560ba16e..7a3bb93e 100644 --- a/gui/src/installer/step/mod.rs +++ b/gui/src/installer/step/mod.rs @@ -13,12 +13,14 @@ use std::str::FromStr; use iced::Command; use liana::{config::BitcoindConfig, miniscript::bitcoin}; +use jsonrpc::{client::Client, simple_http::SimpleHttpTransport}; + use liana_ui::{component::form, widget::*}; use crate::installer::{ context::Context, message::{self, Message}, - view, + view, Error, }; pub trait Step { @@ -56,6 +58,7 @@ impl From for Box { pub struct DefineBitcoind { cookie_path: form::Value, address: form::Value, + is_running: Option>, } fn bitcoind_default_cookie_path(network: &bitcoin::Network) -> Option { @@ -106,8 +109,30 @@ impl DefineBitcoind { Self { cookie_path: form::Value::default(), address: form::Value::default(), + is_running: None, } } + + pub fn ping(&self) -> Command { + let address = self.address.value.to_owned(); + let cookie_path = self.cookie_path.value.to_owned(); + Command::perform( + async move { + let cookie = std::fs::read_to_string(&cookie_path) + .map_err(|e| Error::Bitcoind(format!("Failed to read cookie file: {}", e)))?; + let client = Client::with_transport( + SimpleHttpTransport::builder() + .url(&address)? + .timeout(std::time::Duration::from_secs(3)) + .cookie_auth(cookie) + .build(), + ); + client.send_request(client.build_request("echo", &[]))?; + Ok(()) + }, + |res| Message::DefineBitcoind(message::DefineBitcoind::PingBitcoindResult(res)), + ) + } } impl Step for DefineBitcoind { @@ -123,11 +148,18 @@ impl Step for DefineBitcoind { fn update(&mut self, message: Message) -> Command { if let Message::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::AddressEdited(address) => { + self.is_running = None; self.address.value = address; self.address.valid = true; } message::DefineBitcoind::CookiePathEdited(path) => { + self.is_running = None; self.cookie_path.value = path; self.address.valid = true; } @@ -165,7 +197,16 @@ impl Step for DefineBitcoind { } fn view(&self, progress: (usize, usize)) -> Element { - view::define_bitcoin(progress, &self.address, &self.cookie_path) + view::define_bitcoin( + progress, + &self.address, + &self.cookie_path, + self.is_running.as_ref(), + ) + } + + fn load(&self) -> Command { + self.ping() } } diff --git a/gui/src/installer/view.rs b/gui/src/installer/view.rs index 2fe03071..a2a4c55d 100644 --- a/gui/src/installer/view.rs +++ b/gui/src/installer/view.rs @@ -801,6 +801,7 @@ pub fn define_bitcoin<'a>( progress: (usize, usize), address: &form::Value, cookie_path: &form::Value, + is_running: Option<&Result<(), Error>>, ) -> Element<'a, Message> { let col_address = Column::new() .push(text("Address:").bold()) @@ -836,10 +837,46 @@ pub fn define_bitcoin<'a>( ) .push(col_address) .push(col_cookie) + .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::legacy::SUCCESS)) + .push(text("Connection checked").style(color::legacy::SUCCESS)), + ) + } else { + Container::new( + Row::new() + .spacing(10) + .align_items(Alignment::Center) + .push(icon::circle_cross_icon().style(color::legacy::ALERT)) + .push(text("Connection failed").style(color::legacy::ALERT)), + ) + } + }) + } else { + Some(Container::new(Space::with_height(Length::Units(25)))) + }) .push( - button::primary(None, "Next") - .on_press(Message::Next) - .width(Length::Units(200)), + Row::new() + .spacing(10) + .push(Container::new( + button::border(None, "Check connection") + .on_press(Message::DefineBitcoind( + message::DefineBitcoind::PingBitcoind, + )) + .width(Length::Units(200)), + )) + .push(if is_running.map(|res| res.is_ok()).unwrap_or(false) { + button::primary(None, "Next") + .on_press(Message::Next) + .width(Length::Units(200)) + } else { + button::primary(None, "Next").width(Length::Units(200)) + }), ) .width(Length::Fill) .height(Length::Fill)