From e172ecd2f1e910a5ba16ea4debb0091b70c2cb17 Mon Sep 17 00:00:00 2001 From: jp1ac4 <121959000+jp1ac4@users.noreply.github.com> Date: Fri, 23 Feb 2024 17:18:33 +0000 Subject: [PATCH] gui: move internal bitcoind config to bitcoind module As part of this change, the struct fields were made public. --- gui/src/bitcoind.rs | 209 ++++++++++++++++++++++++++++ gui/src/installer/context.rs | 4 +- gui/src/installer/step/bitcoind.rs | 212 +---------------------------- gui/src/installer/step/mod.rs | 3 +- 4 files changed, 215 insertions(+), 213 deletions(-) diff --git a/gui/src/bitcoind.rs b/gui/src/bitcoind.rs index 69aaa5dc..83653dba 100644 --- a/gui/src/bitcoind.rs +++ b/gui/src/bitcoind.rs @@ -3,6 +3,7 @@ use liana::{ miniscript::bitcoin::{self, Network}, }; use liana_ui::component::form; +use std::collections::BTreeMap; use std::fmt; use std::path::{Path, PathBuf}; use std::sync::Arc; @@ -120,6 +121,143 @@ pub fn bitcoind_network_dir(network: &Network) -> Option { Some(dir.to_string()) } +/// Represents section for a single network in `bitcoin.conf` file. +#[derive(PartialEq, Eq, Debug, Clone)] +pub struct InternalBitcoindNetworkConfig { + pub rpc_port: u16, + pub p2p_port: u16, + pub prune: u32, +} + +/// Represents the `bitcoin.conf` file to be used by internal bitcoind. +#[derive(Debug, Clone)] +pub struct InternalBitcoindConfig { + pub networks: BTreeMap, +} + +#[derive(PartialEq, Eq, Debug, Clone)] +pub enum InternalBitcoindConfigError { + KeyNotFound(String), + CouldNotParseValue(String), + UnexpectedSection(String), + TooManyElements(String), + FileNotFound, + ReadingFile(String), + WritingFile(String), + Unexpected(String), +} + +impl std::fmt::Display for InternalBitcoindConfigError { + fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { + match self { + Self::KeyNotFound(e) => write!(f, "Config file does not contain expected key: {}", e), + Self::CouldNotParseValue(e) => write!(f, "Value could not be parsed: {}", e), + Self::UnexpectedSection(e) => write!(f, "Unexpected section in file: {}", e), + Self::TooManyElements(section) => { + write!(f, "Section in file contains too many elements: {}", section) + } + Self::FileNotFound => write!(f, "File not found"), + Self::ReadingFile(e) => write!(f, "Error while reading file: {}", e), + Self::WritingFile(e) => write!(f, "Error while writing file: {}", e), + Self::Unexpected(e) => write!(f, "Unexpected error: {}", e), + } + } +} + +impl Default for InternalBitcoindConfig { + fn default() -> Self { + Self::new() + } +} + +impl InternalBitcoindConfig { + pub fn new() -> Self { + Self { + networks: BTreeMap::new(), + } + } + + pub fn from_ini(ini: &ini::Ini) -> Result { + let mut networks = BTreeMap::new(); + for (maybe_sec, prop) in ini { + if let Some(sec) = maybe_sec { + let network = Network::from_core_arg(sec) + .map_err(|e| InternalBitcoindConfigError::UnexpectedSection(e.to_string()))?; + if prop.len() > 3 { + return Err(InternalBitcoindConfigError::TooManyElements( + sec.to_string(), + )); + } + let rpc_port = prop + .get("rpcport") + .ok_or_else(|| InternalBitcoindConfigError::KeyNotFound("rpcport".to_string()))? + .parse::() + .map_err(|e| InternalBitcoindConfigError::CouldNotParseValue(e.to_string()))?; + let p2p_port = prop + .get("port") + .ok_or_else(|| InternalBitcoindConfigError::KeyNotFound("port".to_string()))? + .parse::() + .map_err(|e| InternalBitcoindConfigError::CouldNotParseValue(e.to_string()))?; + let prune = prop + .get("prune") + .ok_or_else(|| InternalBitcoindConfigError::KeyNotFound("prune".to_string()))? + .parse::() + .map_err(|e| InternalBitcoindConfigError::CouldNotParseValue(e.to_string()))?; + networks.insert( + network, + InternalBitcoindNetworkConfig { + rpc_port, + p2p_port, + prune, + }, + ); + } else if !prop.is_empty() { + return Err(InternalBitcoindConfigError::UnexpectedSection( + "General section should be empty".to_string(), + )); + } + } + Ok(Self { networks }) + } + + pub fn from_file(path: &PathBuf) -> Result { + if !path.exists() { + return Err(InternalBitcoindConfigError::FileNotFound); + } + let conf_ini = ini::Ini::load_from_file(path) + .map_err(|e| InternalBitcoindConfigError::ReadingFile(e.to_string()))?; + + Self::from_ini(&conf_ini) + } + + pub fn to_ini(&self) -> ini::Ini { + let mut conf_ini = ini::Ini::new(); + + for (network, network_conf) in &self.networks { + conf_ini + .with_section(Some(network.to_core_arg())) + .set("rpcport", network_conf.rpc_port.to_string()) + .set("port", network_conf.p2p_port.to_string()) + .set("prune", network_conf.prune.to_string()); + } + conf_ini + } + + pub fn to_file(&self, path: &PathBuf) -> Result<(), InternalBitcoindConfigError> { + std::fs::create_dir_all( + path.parent() + .ok_or_else(|| InternalBitcoindConfigError::Unexpected("No parent".to_string()))?, + ) + .map_err(|e| InternalBitcoindConfigError::Unexpected(e.to_string()))?; + info!("Writing to file {}", path.to_string_lossy()); + self.to_ini() + .write_to_file(path) + .map_err(|e| InternalBitcoindConfigError::WritingFile(e.to_string()))?; + + Ok(()) + } +} + /// Possible errors when starting bitcoind. #[derive(PartialEq, Eq, Debug, Clone)] pub enum StartInternalBitcoindError { @@ -318,3 +456,74 @@ impl fmt::Display for ConfigField { } } } + +#[cfg(test)] +mod tests { + use super::*; + use ini::Ini; + use liana::miniscript::bitcoin::Network; + + // Test the format of the internal bitcoind configuration file. + #[test] + fn internal_bitcoind_config() { + // A valid config + let mut conf_ini = Ini::new(); + conf_ini + .with_section(Some("main")) + .set("rpcport", "43345") + .set("port", "42355") + .set("prune", "15246"); + conf_ini + .with_section(Some("regtest")) + .set("rpcport", "34067") + .set("port", "45175") + .set("prune", "2043"); + let conf = InternalBitcoindConfig::from_ini(&conf_ini).expect("Loading conf from ini"); + let main_conf = InternalBitcoindNetworkConfig { + rpc_port: 43345, + p2p_port: 42355, + prune: 15246, + }; + let regtest_conf = InternalBitcoindNetworkConfig { + rpc_port: 34067, + p2p_port: 45175, + prune: 2043, + }; + assert_eq!(conf.networks.len(), 2); + assert_eq!( + conf.networks.get(&Network::Bitcoin).expect("Missing main"), + &main_conf + ); + assert_eq!( + conf.networks + .get(&Network::Regtest) + .expect("Missing regtest"), + ®test_conf + ); + + let mut conf = InternalBitcoindConfig::new(); + conf.networks.insert(Network::Bitcoin, main_conf); + conf.networks.insert(Network::Regtest, regtest_conf); + for (sec, prop) in &conf.to_ini() { + if let Some(sec) = sec { + assert_eq!(prop.len(), 3); + let rpc_port = prop.get("rpcport").expect("rpcport"); + let p2p_port = prop.get("port").expect("port"); + let prune = prop.get("prune").expect("prune"); + if sec == "main" { + assert_eq!(rpc_port, "43345"); + assert_eq!(p2p_port, "42355"); + assert_eq!(prune, "15246"); + } else if sec == "regtest" { + assert_eq!(rpc_port, "34067"); + assert_eq!(p2p_port, "45175"); + assert_eq!(prune, "2043"); + } else { + panic!("Unexpected section"); + } + } else { + assert!(prop.is_empty()) + } + } + } +} diff --git a/gui/src/installer/context.rs b/gui/src/installer/context.rs index 7acf49d3..6413626c 100644 --- a/gui/src/installer/context.rs +++ b/gui/src/installer/context.rs @@ -7,7 +7,7 @@ use crate::{ settings::{KeySetting, Settings, WalletSetting}, wallet::wallet_name, }, - bitcoind::Bitcoind, + bitcoind::{Bitcoind, InternalBitcoindConfig}, hw::HardwareWalletConfig, signer::Signer, }; @@ -19,8 +19,6 @@ use liana::{ miniscript::bitcoin, }; -use super::step::InternalBitcoindConfig; - #[derive(Clone)] pub struct Context { pub bitcoin_config: BitcoinConfig, diff --git a/gui/src/installer/step/bitcoind.rs b/gui/src/installer/step/bitcoind.rs index 25b70b50..00ec02ee 100644 --- a/gui/src/installer/step/bitcoind.rs +++ b/gui/src/installer/step/bitcoind.rs @@ -1,4 +1,3 @@ -use std::collections::BTreeMap; #[cfg(target_os = "windows")] use std::io::{self, Cursor}; use std::net::{IpAddr, Ipv4Addr, SocketAddr, TcpListener}; @@ -24,7 +23,9 @@ use liana_ui::{component::form, widget::*}; use crate::{ bitcoind::{ self, bitcoind_network_dir, internal_bitcoind_datadir, internal_bitcoind_directory, - Bitcoind, ConfigField, RpcAuthType, RpcAuthValues, StartInternalBitcoindError, VERSION, + Bitcoind, ConfigField, InternalBitcoindConfig, InternalBitcoindConfigError, + InternalBitcoindNetworkConfig, RpcAuthType, RpcAuthValues, StartInternalBitcoindError, + VERSION, }, download, hw::HardwareWallets, @@ -108,143 +109,6 @@ pub const PRUNE_DEFAULT: u32 = 15_000; /// Default ports used by bitcoind across all networks. pub const BITCOIND_DEFAULT_PORTS: [u16; 8] = [8332, 8333, 18332, 18333, 18443, 18444, 38332, 38333]; -/// Represents section for a single network in `bitcoin.conf` file. -#[derive(PartialEq, Eq, Debug, Clone)] -pub struct InternalBitcoindNetworkConfig { - rpc_port: u16, - p2p_port: u16, - prune: u32, -} - -/// Represents the `bitcoin.conf` file to be used by internal bitcoind. -#[derive(Debug, Clone)] -pub struct InternalBitcoindConfig { - networks: BTreeMap, -} - -#[derive(PartialEq, Eq, Debug, Clone)] -pub enum InternalBitcoindConfigError { - KeyNotFound(String), - CouldNotParseValue(String), - UnexpectedSection(String), - TooManyElements(String), - FileNotFound, - ReadingFile(String), - WritingFile(String), - Unexpected(String), -} - -impl std::fmt::Display for InternalBitcoindConfigError { - fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { - match self { - Self::KeyNotFound(e) => write!(f, "Config file does not contain expected key: {}", e), - Self::CouldNotParseValue(e) => write!(f, "Value could not be parsed: {}", e), - Self::UnexpectedSection(e) => write!(f, "Unexpected section in file: {}", e), - Self::TooManyElements(section) => { - write!(f, "Section in file contains too many elements: {}", section) - } - Self::FileNotFound => write!(f, "File not found"), - Self::ReadingFile(e) => write!(f, "Error while reading file: {}", e), - Self::WritingFile(e) => write!(f, "Error while writing file: {}", e), - Self::Unexpected(e) => write!(f, "Unexpected error: {}", e), - } - } -} - -impl Default for InternalBitcoindConfig { - fn default() -> Self { - Self::new() - } -} - -impl InternalBitcoindConfig { - pub fn new() -> Self { - Self { - networks: BTreeMap::new(), - } - } - - pub fn from_ini(ini: &ini::Ini) -> Result { - let mut networks = BTreeMap::new(); - for (maybe_sec, prop) in ini { - if let Some(sec) = maybe_sec { - let network = Network::from_core_arg(sec) - .map_err(|e| InternalBitcoindConfigError::UnexpectedSection(e.to_string()))?; - if prop.len() > 3 { - return Err(InternalBitcoindConfigError::TooManyElements( - sec.to_string(), - )); - } - let rpc_port = prop - .get("rpcport") - .ok_or_else(|| InternalBitcoindConfigError::KeyNotFound("rpcport".to_string()))? - .parse::() - .map_err(|e| InternalBitcoindConfigError::CouldNotParseValue(e.to_string()))?; - let p2p_port = prop - .get("port") - .ok_or_else(|| InternalBitcoindConfigError::KeyNotFound("port".to_string()))? - .parse::() - .map_err(|e| InternalBitcoindConfigError::CouldNotParseValue(e.to_string()))?; - let prune = prop - .get("prune") - .ok_or_else(|| InternalBitcoindConfigError::KeyNotFound("prune".to_string()))? - .parse::() - .map_err(|e| InternalBitcoindConfigError::CouldNotParseValue(e.to_string()))?; - networks.insert( - network, - InternalBitcoindNetworkConfig { - rpc_port, - p2p_port, - prune, - }, - ); - } else if !prop.is_empty() { - return Err(InternalBitcoindConfigError::UnexpectedSection( - "General section should be empty".to_string(), - )); - } - } - Ok(Self { networks }) - } - - pub fn from_file(path: &PathBuf) -> Result { - if !path.exists() { - return Err(InternalBitcoindConfigError::FileNotFound); - } - let conf_ini = ini::Ini::load_from_file(path) - .map_err(|e| InternalBitcoindConfigError::ReadingFile(e.to_string()))?; - - Self::from_ini(&conf_ini) - } - - pub fn to_ini(&self) -> ini::Ini { - let mut conf_ini = ini::Ini::new(); - - for (network, network_conf) in &self.networks { - conf_ini - .with_section(Some(network.to_core_arg())) - .set("rpcport", network_conf.rpc_port.to_string()) - .set("port", network_conf.p2p_port.to_string()) - .set("prune", network_conf.prune.to_string()); - } - conf_ini - } - - pub fn to_file(&self, path: &PathBuf) -> Result<(), InternalBitcoindConfigError> { - std::fs::create_dir_all( - path.parent() - .ok_or_else(|| InternalBitcoindConfigError::Unexpected("No parent".to_string()))?, - ) - .map_err(|e| InternalBitcoindConfigError::Unexpected(e.to_string()))?; - info!("Writing to file {}", path.to_string_lossy()); - self.to_ini() - .write_to_file(path) - .map_err(|e| InternalBitcoindConfigError::WritingFile(e.to_string()))?; - - Ok(()) - } -} - #[derive(Debug)] pub enum InstallState { InProgress, @@ -941,75 +805,7 @@ impl Step for InternalBitcoindStep { #[cfg(test)] mod tests { - use crate::installer::step::bitcoind::{ - verify_hash, InternalBitcoindConfig, InternalBitcoindNetworkConfig, - }; - use ini::Ini; - use liana::miniscript::bitcoin::Network; - - // Test the format of the internal bitcoind configuration file. - #[test] - fn internal_bitcoind_config() { - // A valid config - let mut conf_ini = Ini::new(); - conf_ini - .with_section(Some("main")) - .set("rpcport", "43345") - .set("port", "42355") - .set("prune", "15246"); - conf_ini - .with_section(Some("regtest")) - .set("rpcport", "34067") - .set("port", "45175") - .set("prune", "2043"); - let conf = InternalBitcoindConfig::from_ini(&conf_ini).expect("Loading conf from ini"); - let main_conf = InternalBitcoindNetworkConfig { - rpc_port: 43345, - p2p_port: 42355, - prune: 15246, - }; - let regtest_conf = InternalBitcoindNetworkConfig { - rpc_port: 34067, - p2p_port: 45175, - prune: 2043, - }; - assert_eq!(conf.networks.len(), 2); - assert_eq!( - conf.networks.get(&Network::Bitcoin).expect("Missing main"), - &main_conf - ); - assert_eq!( - conf.networks - .get(&Network::Regtest) - .expect("Missing regtest"), - ®test_conf - ); - - let mut conf = InternalBitcoindConfig::new(); - conf.networks.insert(Network::Bitcoin, main_conf); - conf.networks.insert(Network::Regtest, regtest_conf); - for (sec, prop) in &conf.to_ini() { - if let Some(sec) = sec { - assert_eq!(prop.len(), 3); - let rpc_port = prop.get("rpcport").expect("rpcport"); - let p2p_port = prop.get("port").expect("port"); - let prune = prop.get("prune").expect("prune"); - if sec == "main" { - assert_eq!(rpc_port, "43345"); - assert_eq!(p2p_port, "42355"); - assert_eq!(prune, "15246"); - } else if sec == "regtest" { - assert_eq!(rpc_port, "34067"); - assert_eq!(p2p_port, "45175"); - assert_eq!(prune, "2043"); - } else { - panic!("Unexpected section"); - } - } else { - assert!(prop.is_empty()) - } - } - } + use super::verify_hash; #[test] fn hash() { diff --git a/gui/src/installer/step/mod.rs b/gui/src/installer/step/mod.rs index 3583dbca..15529629 100644 --- a/gui/src/installer/step/mod.rs +++ b/gui/src/installer/step/mod.rs @@ -3,8 +3,7 @@ mod descriptor; mod mnemonic; pub use bitcoind::{ - DefineBitcoind, DownloadState, InstallState, InternalBitcoindConfig, InternalBitcoindStep, - SelectBitcoindTypeStep, + DefineBitcoind, DownloadState, InstallState, InternalBitcoindStep, SelectBitcoindTypeStep, }; pub use descriptor::{