diff --git a/gui/src/app/config.rs b/gui/src/app/config.rs index 1dfd52d9..041a05d5 100644 --- a/gui/src/app/config.rs +++ b/gui/src/app/config.rs @@ -3,15 +3,6 @@ use serde::{Deserialize, Serialize}; use std::path::{Path, PathBuf}; use tracing_subscriber::filter; -/// Config required to start internal bitcoind. -#[derive(Debug, Clone, Deserialize, Serialize)] -pub struct InternalBitcoindExeConfig { - /// Internal bitcoind executable path. - pub exe_path: PathBuf, - /// Internal bitcoind data dir. - pub data_dir: PathBuf, -} - #[derive(Debug, Clone, Deserialize, Serialize)] pub struct Config { /// Path to lianad configuration file. @@ -25,24 +16,22 @@ pub struct Config { /// hardware wallets config. /// LEGACY: Use Settings module instead. pub hardware_wallets: Option>, - /// Internal bitcoind executable config. - pub internal_bitcoind_exe_config: Option, + /// Start internal bitcoind executable. + #[serde(default)] + pub start_internal_bitcoind: bool, } pub const DEFAULT_FILE_NAME: &str = "gui.toml"; impl Config { - pub fn new( - daemon_config_path: PathBuf, - internal_bitcoind_exe_config: Option, - ) -> Self { + pub fn new(daemon_config_path: PathBuf, start_internal_bitcoind: bool) -> Self { Self { daemon_config_path: Some(daemon_config_path), daemon_rpc_path: None, log_level: None, debug: None, hardware_wallets: None, - internal_bitcoind_exe_config, + start_internal_bitcoind, } } diff --git a/gui/src/bitcoind.rs b/gui/src/bitcoind.rs index 57f597a3..ef3acc5a 100644 --- a/gui/src/bitcoind.rs +++ b/gui/src/bitcoind.rs @@ -1,5 +1,8 @@ -use liana::{config::BitcoindConfig, miniscript::bitcoin}; -use std::path::Path; +use liana::{ + config::BitcoindConfig, + miniscript::bitcoin::{self, Network}, +}; +use std::path::{Path, PathBuf}; use std::sync::Arc; use tokio::sync::Mutex; @@ -11,6 +14,95 @@ use std::os::windows::process::CommandExt; #[cfg(target_os = "windows")] const CREATE_NO_WINDOW: u32 = 0x08000000; +pub const VERSION: &str = "25.0"; + +#[cfg(all(target_os = "macos", target_arch = "x86_64"))] +pub const SHA256SUM: &str = "5708fc639cdfc27347cccfd50db9b73b53647b36fb5f3a4a93537cbe8828c27f"; + +#[cfg(all(target_os = "linux", target_arch = "x86_64"))] +pub const SHA256SUM: &str = "33930d432593e49d58a9bff4c30078823e9af5d98594d2935862788ce8a20aec"; + +#[cfg(all(target_os = "windows", target_arch = "x86_64"))] +pub const SHA256SUM: &str = "7154b35ecc8247589070ae739b7c73c4dee4794bea49eb18dc66faed65b819e7"; + +#[cfg(all(target_os = "macos", target_arch = "x86_64"))] +pub fn download_filename() -> String { + format!("bitcoin-{}-x86_64-apple-darwin.tar.gz", &VERSION) +} + +#[cfg(all(target_os = "linux", target_arch = "x86_64"))] +fn download_filename() -> String { + format!("bitcoin-{}-x86_64-linux-gnu.tar.gz", &VERSION) +} + +#[cfg(all(target_os = "windows", target_arch = "x86_64"))] +fn download_filename() -> String { + format!("bitcoin-{}-win64.zip", &VERSION) +} + +pub fn download_url() -> String { + format!( + "https://bitcoincore.org/bin/bitcoin-core-{}/{}", + &VERSION, + download_filename() + ) +} + +pub fn internal_bitcoind_directory(liana_datadir: &PathBuf) -> PathBuf { + let mut datadir = PathBuf::from(liana_datadir); + datadir.push("bitcoind"); + datadir +} + +/// Data directory used by internal bitcoind. +pub fn internal_bitcoind_datadir(liana_datadir: &PathBuf) -> PathBuf { + let mut datadir = internal_bitcoind_directory(liana_datadir); + datadir.push("datadir"); + datadir +} + +/// Internal bitcoind executable path. +pub fn internal_bitcoind_exe_path(liana_datadir: &PathBuf) -> PathBuf { + internal_bitcoind_directory(liana_datadir) + .join(format!("bitcoin-{}", &VERSION)) + .join("bin") + .join(if cfg!(target_os = "windows") { + "bitcoind.exe" + } else { + "bitcoind" + }) +} + +/// Path of the `bitcoin.conf` file used by internal bitcoind. +pub fn internal_bitcoind_config_path(bitcoind_datadir: &PathBuf) -> PathBuf { + let mut config_path = PathBuf::from(bitcoind_datadir); + config_path.push("bitcoin.conf"); + config_path +} + +/// Path of the cookie file used by internal bitcoind on a given network. +pub fn internal_bitcoind_cookie_path(bitcoind_datadir: &Path, network: &Network) -> PathBuf { + let mut cookie_path = bitcoind_datadir.to_path_buf(); + if let Some(dir) = bitcoind_network_dir(network) { + cookie_path.push(dir); + } + cookie_path.push(".cookie"); + cookie_path +} + +pub fn bitcoind_network_dir(network: &Network) -> Option { + let dir = match network { + Network::Bitcoin => { + return None; + } + Network::Testnet => "testnet3", + Network::Regtest => "regtest", + Network::Signet => "signet", + _ => panic!("Directory required for this network is unknown."), + }; + Some(dir.to_string()) +} + /// Possible errors when starting bitcoind. #[derive(PartialEq, Eq, Debug, Clone)] pub enum StartInternalBitcoindError { @@ -60,9 +152,10 @@ impl Bitcoind { pub fn start( network: &bitcoin::Network, mut config: BitcoindConfig, - bitcoind_datadir: &Path, - exe_path: &Path, + liana_datadir: &PathBuf, ) -> Result { + let bitcoind_datadir = internal_bitcoind_datadir(liana_datadir); + let bitcoind_exe_path = internal_bitcoind_exe_path(liana_datadir); let datadir_path_str = bitcoind_datadir .canonicalize() .map_err(|e| StartInternalBitcoindError::CouldNotCanonicalizeDataDir(e.to_string()))? @@ -82,14 +175,14 @@ impl Bitcoind { format!("-chain={}", network.to_core_arg()), format!("-datadir={}", datadir_path_str), ]; - let mut command = std::process::Command::new(exe_path); + let mut command = std::process::Command::new(bitcoind_exe_path); #[cfg(target_os = "windows")] let command = command.creation_flags(CREATE_NO_WINDOW); let mut process = command .args(&args) - .stdout(std::process::Stdio::piped()) // We still get bitcoind's logs in debug.log. + .stdout(std::process::Stdio::piped()) .stderr(std::process::Stdio::piped()) .spawn() .map_err(|e| StartInternalBitcoindError::CommandError(e.to_string()))?; diff --git a/gui/src/installer/context.rs b/gui/src/installer/context.rs index 6bca248b..3409ad9c 100644 --- a/gui/src/installer/context.rs +++ b/gui/src/installer/context.rs @@ -4,7 +4,6 @@ use std::time::Duration; use crate::{ app::{ - config::InternalBitcoindExeConfig, settings::{KeySetting, Settings, WalletSetting}, wallet::DEFAULT_WALLET_NAME, }, @@ -36,7 +35,6 @@ pub struct Context { pub recovered_signer: Option>, pub bitcoind_is_external: bool, pub internal_bitcoind_config: Option, - pub internal_bitcoind_exe_config: Option, pub internal_bitcoind: Option, } @@ -56,7 +54,6 @@ impl Context { recovered_signer: None, bitcoind_is_external: true, internal_bitcoind_config: None, - internal_bitcoind_exe_config: None, internal_bitcoind: None, } } diff --git a/gui/src/installer/mod.rs b/gui/src/installer/mod.rs index b581c075..128ac34c 100644 --- a/gui/src/installer/mod.rs +++ b/gui/src/installer/mod.rs @@ -16,7 +16,6 @@ use std::path::PathBuf; use std::sync::{Arc, Mutex}; use crate::{ - app::config::InternalBitcoindExeConfig, app::{config as gui_config, settings as gui_settings}, signer::Signer, }; @@ -252,13 +251,6 @@ pub fn daemon_check(cfg: liana::config::Config) -> Result<(), Error> { } } -/// Data directory used by internal bitcoind. -pub fn internal_bitcoind_datadir(liana_datadir: &PathBuf) -> PathBuf { - let mut datadir = PathBuf::from(liana_datadir); - datadir.push("bitcoind_datadir"); - datadir -} - pub async fn install(ctx: Context, signer: Arc>) -> Result { let mut cfg: liana::config::Config = ctx.extract_daemon_config(); let data_dir = cfg.data_dir.unwrap(); @@ -324,7 +316,8 @@ pub async fn install(ctx: Context, signer: Arc>) -> Result Subscription { match self.state { - DownloadState::Downloading { .. } => { - download::file(self.id, download_url()).map(|(_, progress)| { + DownloadState::Downloading { .. } => download::file(self.id, bitcoind::download_url()) + .map(|(_, progress)| { Message::InternalBitcoind(message::InternalBitcoindMsg::DownloadProgressed( progress, )) - }) - } + }), _ => Subscription::none(), } } } -const VERSION: &str = "25.0"; - -#[cfg(all(target_os = "macos", target_arch = "x86_64"))] -const SHA256SUM: &str = "5708fc639cdfc27347cccfd50db9b73b53647b36fb5f3a4a93537cbe8828c27f"; - -#[cfg(all(target_os = "linux", target_arch = "x86_64"))] -const SHA256SUM: &str = "33930d432593e49d58a9bff4c30078823e9af5d98594d2935862788ce8a20aec"; - -#[cfg(all(target_os = "windows", target_arch = "x86_64"))] -const SHA256SUM: &str = "7154b35ecc8247589070ae739b7c73c4dee4794bea49eb18dc66faed65b819e7"; - -#[cfg(all(target_os = "macos", target_arch = "x86_64"))] -fn download_filename() -> String { - format!("bitcoin-{}-x86_64-apple-darwin.tar.gz", &VERSION) -} - -#[cfg(all(target_os = "linux", target_arch = "x86_64"))] -fn download_filename() -> String { - format!("bitcoin-{}-x86_64-linux-gnu.tar.gz", &VERSION) -} - -#[cfg(all(target_os = "windows", target_arch = "x86_64"))] -fn download_filename() -> String { - format!("bitcoin-{}-win64.zip", &VERSION) -} - -fn download_url() -> String { - format!( - "https://bitcoincore.org/bin/bitcoin-core-{}/{}", - &VERSION, - download_filename() - ) -} - /// Default prune value used by internal bitcoind. pub const PRUNE_DEFAULT: u32 = 15_000; /// Default ports used by bitcoind across all networks. @@ -361,7 +328,7 @@ fn unpack_bitcoind(install_dir: &PathBuf, bytes: &[u8]) -> Result<(), InstallBit fn verify_hash(bytes: &[u8]) -> bool { let bytes_hash = sha256::Hash::hash(bytes); info!("Download hash: '{}'.", bytes_hash); - let expected_hash = sha256::Hash::from_str(SHA256SUM).expect("This cannot fail."); + let expected_hash = sha256::Hash::from_str(bitcoind::SHA256SUM).expect("This cannot fail."); expected_hash == bytes_hash } @@ -373,35 +340,6 @@ fn install_bitcoind(install_dir: &PathBuf, bytes: &[u8]) -> Result<(), InstallBi unpack_bitcoind(install_dir, bytes) } -/// Internal bitcoind executable path. -fn internal_bitcoind_exe_path(liana_datadir: &PathBuf) -> PathBuf { - PathBuf::from(liana_datadir) - .join(format!("bitcoin-{}", &VERSION)) - .join("bin") - .join(if cfg!(target_os = "windows") { - "bitcoind.exe" - } else { - "bitcoind" - }) -} - -/// Path of the `bitcoin.conf` file used by internal bitcoind. -fn internal_bitcoind_config_path(bitcoind_datadir: &PathBuf) -> PathBuf { - let mut config_path = PathBuf::from(bitcoind_datadir); - config_path.push("bitcoin.conf"); - config_path -} - -/// Path of the cookie file used by internal bitcoind on a given network. -fn internal_bitcoind_cookie_path(bitcoind_datadir: &Path, network: &Network) -> PathBuf { - let mut cookie_path = bitcoind_datadir.to_path_buf(); - if let Some(dir) = bitcoind_network_dir(network) { - cookie_path.push(dir); - } - cookie_path.push(".cookie"); - cookie_path -} - /// RPC address for internal bitcoind. fn internal_bitcoind_address(rpc_port: u16) -> SocketAddr { SocketAddr::new(IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1)), rpc_port) @@ -426,19 +364,6 @@ fn bitcoind_default_datadir() -> Option { None } -fn bitcoind_network_dir(network: &Network) -> Option { - let dir = match network { - Network::Bitcoin => { - return None; - } - Network::Testnet => "testnet3", - Network::Regtest => "regtest", - Network::Signet => "signet", - _ => panic!("Directory required for this network is unknown."), - }; - Some(dir.to_string()) -} - fn bitcoind_default_cookie_path(network: &Network) -> Option { if let Some(mut path) = bitcoind_default_datadir() { if let Some(dir) = bitcoind_network_dir(network) { @@ -528,7 +453,6 @@ impl Step for SelectBitcoindTypeStep { } } else { ctx.internal_bitcoind_config = None; - ctx.internal_bitcoind_exe_config = None; } ctx.bitcoind_is_external = self.use_external; true @@ -674,7 +598,6 @@ pub struct InternalBitcoindStep { started: Option>, exe_path: Option, bitcoind_config: Option, - exe_config: Option, internal_bitcoind_config: Option, error: Option, exe_download: Option, @@ -697,7 +620,6 @@ impl InternalBitcoindStep { started: None, exe_path: None, bitcoind_config: None, - exe_config: None, internal_bitcoind_config: None, error: None, exe_download: None, @@ -710,8 +632,8 @@ impl InternalBitcoindStep { impl Step for InternalBitcoindStep { fn load_context(&mut self, ctx: &Context) { if self.exe_path.is_none() { - if internal_bitcoind_exe_path(&ctx.data_dir).exists() { - self.exe_path = Some(internal_bitcoind_exe_path(&ctx.data_dir)) + if bitcoind::internal_bitcoind_exe_path(&ctx.data_dir).exists() { + self.exe_path = Some(bitcoind::internal_bitcoind_exe_path(&ctx.data_dir)) } else if self.exe_download.is_none() { self.exe_download = Some(Download::new(0)); }; @@ -732,6 +654,7 @@ impl Step for InternalBitcoindStep { bitcoind.stop(); self.started = None; } + self.internal_bitcoind = None; return Command::perform(async {}, |_| Message::Previous); } message::InternalBitcoindMsg::Reload => { @@ -739,7 +662,7 @@ impl Step for InternalBitcoindStep { } message::InternalBitcoindMsg::DefineConfig => { let mut conf = match InternalBitcoindConfig::from_file( - &internal_bitcoind_config_path(&self.bitcoind_datadir), + &bitcoind::internal_bitcoind_config_path(&self.bitcoind_datadir), ) { Ok(conf) => conf, Err(InternalBitcoindConfigError::FileNotFound) => { @@ -780,9 +703,9 @@ impl Step for InternalBitcoindStep { }; conf.networks.insert(self.network, network_conf); } - if let Err(e) = - conf.to_file(&internal_bitcoind_config_path(&self.bitcoind_datadir)) - { + if let Err(e) = conf.to_file(&bitcoind::internal_bitcoind_config_path( + &self.bitcoind_datadir, + )) { self.error = Some(e.to_string()); return Command::none(); }; @@ -795,7 +718,7 @@ impl Step for InternalBitcoindStep { message::InternalBitcoindMsg::Download => { if let Some(download) = &mut self.exe_download { if let DownloadState::Idle = download.state { - info!("Downloading bitcoind version {}...", &VERSION); + info!("Downloading bitcoind version {}...", &bitcoind::VERSION); download.start(); } } @@ -816,12 +739,16 @@ impl Step for InternalBitcoindStep { if let DownloadState::Finished(bytes) = &download.state { info!("Installing bitcoind..."); self.install_state = Some(InstallState::InProgress); - match install_bitcoind(&self.liana_datadir, bytes) { + match install_bitcoind( + &internal_bitcoind_directory(&self.liana_datadir), + bytes, + ) { Ok(_) => { info!("Installation of bitcoind complete."); self.install_state = Some(InstallState::Finished); - self.exe_path = - Some(internal_bitcoind_exe_path(&self.liana_datadir)); + self.exe_path = Some(bitcoind::internal_bitcoind_exe_path( + &self.liana_datadir, + )); return Command::perform(async {}, |_| { Message::InternalBitcoind( message::InternalBitcoindMsg::Start, @@ -839,68 +766,48 @@ impl Step for InternalBitcoindStep { } } message::InternalBitcoindMsg::Start => { - if let Some(exe_path) = &self.exe_path { - let exe_config = match ( - exe_path.canonicalize(), - self.bitcoind_datadir.canonicalize(), - ) { - (Ok(exe_path), Ok(data_dir)) => { - InternalBitcoindExeConfig { exe_path, data_dir } - } - (Err(e), Ok(_)) | (Err(e), Err(_)) => { - self.started = Some(Err( - StartInternalBitcoindError::CouldNotCanonicalizeExePath( - e.to_string(), - ), - )); - return Command::none(); - } - (Ok(_), Err(e)) => { - self.started = Some(Err( - StartInternalBitcoindError::CouldNotCanonicalizeDataDir( - e.to_string(), - ), - )); - return Command::none(); - } - }; - let cookie_path = - internal_bitcoind_cookie_path(&self.bitcoind_datadir, &self.network); - - let rpc_port = self - .internal_bitcoind_config - .as_ref() - .expect("Already added") - .clone() - .networks - .get(&self.network) - .expect("Already added") - .rpc_port; - - match Bitcoind::start( - &self.network, - BitcoindConfig { - cookie_path, - addr: internal_bitcoind_address(rpc_port), - }, - &exe_config.data_dir, - &exe_config.exe_path, - ) { - Err(e) => { - self.started = Some(Err(StartInternalBitcoindError::CommandError( - e.to_string(), - ))); - return Command::none(); - } - Ok(bitcoind) => { - self.error = None; - self.bitcoind_config = Some(bitcoind.config.clone()); - self.exe_config = Some(exe_config); - self.started = Some(Ok(())); - self.internal_bitcoind = Some(bitcoind); - } - }; + if let Err(e) = self.bitcoind_datadir.canonicalize() { + self.started = Some(Err( + StartInternalBitcoindError::CouldNotCanonicalizeDataDir(e.to_string()), + )); + return Command::none(); } + + let cookie_path = bitcoind::internal_bitcoind_cookie_path( + &self.bitcoind_datadir, + &self.network, + ); + + let rpc_port = self + .internal_bitcoind_config + .as_ref() + .expect("Already added") + .clone() + .networks + .get(&self.network) + .expect("Already added") + .rpc_port; + + match Bitcoind::start( + &self.network, + BitcoindConfig { + cookie_path, + addr: internal_bitcoind_address(rpc_port), + }, + &self.liana_datadir, + ) { + Err(e) => { + self.started = + Some(Err(StartInternalBitcoindError::CommandError(e.to_string()))); + return Command::none(); + } + Ok(bitcoind) => { + self.error = None; + self.bitcoind_config = Some(bitcoind.config.clone()); + self.started = Some(Ok(())); + self.internal_bitcoind = Some(bitcoind); + } + }; } }; }; @@ -940,7 +847,6 @@ impl Step for InternalBitcoindStep { if let Some(Ok(_)) = self.started { ctx.bitcoind_config = self.bitcoind_config.clone(); ctx.internal_bitcoind_config = self.internal_bitcoind_config.clone(); - ctx.internal_bitcoind_exe_config = self.exe_config.clone(); ctx.internal_bitcoind = self.internal_bitcoind.clone(); self.error = None; return true; diff --git a/gui/src/loader.rs b/gui/src/loader.rs index e6523935..c4845290 100644 --- a/gui/src/loader.rs +++ b/gui/src/loader.rs @@ -24,7 +24,7 @@ use liana_ui::{ use crate::{ app::{ cache::Cache, - config::{Config as GUIConfig, InternalBitcoindExeConfig}, + config::Config as GUIConfig, wallet::{Wallet, WalletError}, }, bitcoind::{Bitcoind, StartInternalBitcoindError}, @@ -109,8 +109,8 @@ impl Loader { progress: 0.0, bitcoind_logs: String::new(), }; - if self.gui_config.internal_bitcoind_exe_config.is_some() { - warn!("Ignoring internal bitcoind config because Liana daemon is external."); + if self.gui_config.start_internal_bitcoind { + warn!("Lianad is external, gui will not start internal bitcoind"); } return Command::perform(sync(daemon, false), Message::Syncing); } @@ -127,7 +127,8 @@ impl Loader { return Command::perform( start_bitcoind_and_daemon( daemon_config_path, - self.gui_config.internal_bitcoind_exe_config.clone(), + self.datadir_path.clone(), + self.gui_config.start_internal_bitcoind, ), Message::Started, ); @@ -411,11 +412,12 @@ async fn connect(socket_path: PathBuf) -> Result, // Daemon can start only if a config path is given. pub async fn start_bitcoind_and_daemon( config_path: PathBuf, - bitcoind_exe_config: Option, + liana_datadir_path: PathBuf, + start_internal_bitcoind: bool, ) -> Result<(Arc, Option), Error> { let config = Config::from_file(Some(config_path)).map_err(Error::Config)?; let mut bitcoind: Option = None; - if let Some(exe_config) = bitcoind_exe_config { + if start_internal_bitcoind { if let Some(bitcoind_config) = &config.bitcoind_config { // Check if bitcoind is already running before trying to start it. if liana::BitcoinD::new(bitcoind_config, "internal_bitcoind_start".to_string()).is_ok() @@ -427,8 +429,7 @@ pub async fn start_bitcoind_and_daemon( Bitcoind::start( &config.bitcoin_config.network, bitcoind_config.clone(), - &exe_config.data_dir, - &exe_config.exe_path, + &liana_datadir_path, ) .map_err(Error::Bitcoind)?, );