gui(installer): add electrum node option

This commit is contained in:
Michael Mallan 2024-08-28 15:32:11 +01:00
parent 341e4467db
commit c93aa88d74
No known key found for this signature in database
GPG Key ID: 5177CDCEDB0EABEB
7 changed files with 154 additions and 3 deletions

View File

@ -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,
}

View File

@ -701,6 +701,7 @@ pub enum Error {
Backend(Arc<DaemonError>),
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),

View File

@ -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<String>,
}
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<Message> {
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::<u16>() // 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<Message> {
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(())
}
}

View File

@ -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<Message> {
match self {
NodeDefinition::Bitcoind(def) => def.update(message),
NodeDefinition::Electrum(def) => def.update(message),
}
}
fn view(&self) -> Element<Message> {
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()

View File

@ -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<String>) -> 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,

14
gui/src/node/electrum.rs Normal file
View File

@ -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"),
}
}
}

View File

@ -1,6 +1,8 @@
pub mod bitcoind;
pub mod electrum;
#[derive(Debug, Clone, PartialEq, Eq, Copy)]
pub enum NodeType {
Bitcoind,
Electrum,
}