gui: move internal bitcoind config to bitcoind module

As part of this change, the struct fields were made public.
This commit is contained in:
jp1ac4 2024-02-23 17:18:33 +00:00
parent 16afa3e992
commit e172ecd2f1
No known key found for this signature in database
GPG Key ID: A7ACD32423568D7B
4 changed files with 215 additions and 213 deletions

View File

@ -3,6 +3,7 @@ use liana::{
miniscript::bitcoin::{self, Network}, miniscript::bitcoin::{self, Network},
}; };
use liana_ui::component::form; use liana_ui::component::form;
use std::collections::BTreeMap;
use std::fmt; use std::fmt;
use std::path::{Path, PathBuf}; use std::path::{Path, PathBuf};
use std::sync::Arc; use std::sync::Arc;
@ -120,6 +121,143 @@ pub fn bitcoind_network_dir(network: &Network) -> Option<String> {
Some(dir.to_string()) 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<Network, InternalBitcoindNetworkConfig>,
}
#[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<Self, InternalBitcoindConfigError> {
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::<u16>()
.map_err(|e| InternalBitcoindConfigError::CouldNotParseValue(e.to_string()))?;
let p2p_port = prop
.get("port")
.ok_or_else(|| InternalBitcoindConfigError::KeyNotFound("port".to_string()))?
.parse::<u16>()
.map_err(|e| InternalBitcoindConfigError::CouldNotParseValue(e.to_string()))?;
let prune = prop
.get("prune")
.ok_or_else(|| InternalBitcoindConfigError::KeyNotFound("prune".to_string()))?
.parse::<u32>()
.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<Self, InternalBitcoindConfigError> {
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. /// Possible errors when starting bitcoind.
#[derive(PartialEq, Eq, Debug, Clone)] #[derive(PartialEq, Eq, Debug, Clone)]
pub enum StartInternalBitcoindError { 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"),
&regtest_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())
}
}
}
}

View File

@ -7,7 +7,7 @@ use crate::{
settings::{KeySetting, Settings, WalletSetting}, settings::{KeySetting, Settings, WalletSetting},
wallet::wallet_name, wallet::wallet_name,
}, },
bitcoind::Bitcoind, bitcoind::{Bitcoind, InternalBitcoindConfig},
hw::HardwareWalletConfig, hw::HardwareWalletConfig,
signer::Signer, signer::Signer,
}; };
@ -19,8 +19,6 @@ use liana::{
miniscript::bitcoin, miniscript::bitcoin,
}; };
use super::step::InternalBitcoindConfig;
#[derive(Clone)] #[derive(Clone)]
pub struct Context { pub struct Context {
pub bitcoin_config: BitcoinConfig, pub bitcoin_config: BitcoinConfig,

View File

@ -1,4 +1,3 @@
use std::collections::BTreeMap;
#[cfg(target_os = "windows")] #[cfg(target_os = "windows")]
use std::io::{self, Cursor}; use std::io::{self, Cursor};
use std::net::{IpAddr, Ipv4Addr, SocketAddr, TcpListener}; use std::net::{IpAddr, Ipv4Addr, SocketAddr, TcpListener};
@ -24,7 +23,9 @@ use liana_ui::{component::form, widget::*};
use crate::{ use crate::{
bitcoind::{ bitcoind::{
self, bitcoind_network_dir, internal_bitcoind_datadir, internal_bitcoind_directory, 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, download,
hw::HardwareWallets, hw::HardwareWallets,
@ -108,143 +109,6 @@ pub const PRUNE_DEFAULT: u32 = 15_000;
/// Default ports used by bitcoind across all networks. /// Default ports used by bitcoind across all networks.
pub const BITCOIND_DEFAULT_PORTS: [u16; 8] = [8332, 8333, 18332, 18333, 18443, 18444, 38332, 38333]; 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<Network, InternalBitcoindNetworkConfig>,
}
#[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<Self, InternalBitcoindConfigError> {
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::<u16>()
.map_err(|e| InternalBitcoindConfigError::CouldNotParseValue(e.to_string()))?;
let p2p_port = prop
.get("port")
.ok_or_else(|| InternalBitcoindConfigError::KeyNotFound("port".to_string()))?
.parse::<u16>()
.map_err(|e| InternalBitcoindConfigError::CouldNotParseValue(e.to_string()))?;
let prune = prop
.get("prune")
.ok_or_else(|| InternalBitcoindConfigError::KeyNotFound("prune".to_string()))?
.parse::<u32>()
.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<Self, InternalBitcoindConfigError> {
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)] #[derive(Debug)]
pub enum InstallState { pub enum InstallState {
InProgress, InProgress,
@ -941,75 +805,7 @@ impl Step for InternalBitcoindStep {
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use crate::installer::step::bitcoind::{ use super::verify_hash;
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"),
&regtest_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())
}
}
}
#[test] #[test]
fn hash() { fn hash() {

View File

@ -3,8 +3,7 @@ mod descriptor;
mod mnemonic; mod mnemonic;
pub use bitcoind::{ pub use bitcoind::{
DefineBitcoind, DownloadState, InstallState, InternalBitcoindConfig, InternalBitcoindStep, DefineBitcoind, DownloadState, InstallState, InternalBitcoindStep, SelectBitcoindTypeStep,
SelectBitcoindTypeStep,
}; };
pub use descriptor::{ pub use descriptor::{