gui(installer): define bitcoind from general node struct

This commit is contained in:
Michael Mallan 2024-08-28 11:53:20 +01:00
parent c5d9d007fb
commit 046b54e6a9
No known key found for this signature in database
GPG Key ID: 5177CDCEDB0EABEB
6 changed files with 233 additions and 159 deletions

View File

@ -33,7 +33,7 @@ pub enum Message {
ImportRemoteWallet(ImportRemoteWallet),
SelectBitcoindType(SelectBitcoindTypeMsg),
InternalBitcoind(InternalBitcoindMsg),
DefineBitcoind(DefineBitcoind),
DefineNode(DefineNode),
DefineDescriptor(DefineDescriptor),
ImportXpub(Fingerprint, Result<DescriptorPublicKey, Error>),
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)]

View File

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

View File

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

View File

@ -339,11 +339,11 @@ impl Step for SelectBitcoindTypeStep {
}
}
#[derive(Clone)]
pub struct DefineBitcoind {
rpc_auth_vals: RpcAuthValues,
selected_auth_type: RpcAuthType,
address: form::Value<String>,
is_running: Option<Result<(), Error>>,
// Internal cache to detect network change.
network: Option<Network>,
@ -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<Message> {
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<Message> {
if let Message::DefineBitcoind(msg) = message {
pub fn update(&mut self, message: message::DefineNode) -> Command<Message> {
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<Message> {
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<Message> {
self.ping()
}
fn skip(&self, ctx: &Context) -> bool {
!ctx.bitcoind_is_external || ctx.remote_backend.is_some()
pub fn view(&self) -> Element<Message> {
view::define_bitcoind(&self.address, &self.rpc_auth_vals, &self.selected_auth_type)
}
}
@ -532,12 +492,6 @@ impl Default for DefineBitcoind {
}
}
impl From<DefineBitcoind> for Box<dyn Step> {
fn from(s: DefineBitcoind) -> Box<dyn Step> {
Box::new(s)
}
}
pub struct InternalBitcoindStep {
liana_datadir: PathBuf,
bitcoind_datadir: PathBuf,

View File

@ -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<Result<(), Error>>,
}
impl Node {
fn new() -> Self {
Node {
definition: DefineBitcoind::new(),
is_running: None,
}
}
}
pub struct DefineNode {
node: Node,
}
impl From<DefineNode> for Box<dyn Step> {
fn from(s: DefineNode) -> Box<dyn Step> {
Box::new(s)
}
}
impl DefineNode {
pub fn new() -> Self {
Self { node: Node::new() }
}
fn ping(&self) -> Command<Message> {
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<Message> {
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<Message> {
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<Message> {
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<Message> {
self.ping()
}
fn skip(&self, ctx: &Context) -> bool {
!ctx.bitcoind_is_external || ctx.remote_backend.is_some()
}
}

View File

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