gui: check connection to bitcoind in installer

close #115
This commit is contained in:
edouard 2023-04-05 11:31:50 +02:00
parent a402f85101
commit ec228485e5
6 changed files with 103 additions and 5 deletions

1
gui/Cargo.lock generated
View File

@ -1757,6 +1757,7 @@ dependencies = [
"iced",
"iced_lazy",
"iced_native",
"jsonrpc",
"liana",
"liana_ui",
"log",

View File

@ -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"

View File

@ -38,6 +38,8 @@ pub enum Message {
pub enum DefineBitcoind {
CookiePathEdited(String),
AddressEdited(String),
PingBitcoindResult(Result<(), Error>),
PingBitcoind,
}
#[derive(Debug, Clone)]

View File

@ -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<jsonrpc::simple_http::Error> for Error {
fn from(error: jsonrpc::simple_http::Error) -> Self {
Error::Bitcoind(error.to_string())
}
}
impl From<jsonrpc::Error> for Error {
fn from(error: jsonrpc::Error) -> Self {
Error::Bitcoind(error.to_string())
}
}
impl From<async_hwi::Error> for Error {
fn from(error: async_hwi::Error) -> Self {
Error::HardwareWallet(error)
@ -310,6 +323,7 @@ impl From<async_hwi::Error> 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),

View File

@ -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<Welcome> for Box<dyn Step> {
pub struct DefineBitcoind {
cookie_path: form::Value<String>,
address: form::Value<String>,
is_running: Option<Result<(), Error>>,
}
fn bitcoind_default_cookie_path(network: &bitcoin::Network) -> Option<String> {
@ -106,8 +109,30 @@ impl DefineBitcoind {
Self {
cookie_path: form::Value::default(),
address: form::Value::default(),
is_running: None,
}
}
pub fn ping(&self) -> Command<Message> {
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<Message> {
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<Message> {
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<Message> {
self.ping()
}
}

View File

@ -801,6 +801,7 @@ pub fn define_bitcoin<'a>(
progress: (usize, usize),
address: &form::Value<String>,
cookie_path: &form::Value<String>,
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)