Merge #657: cleanup bitcoind process management
6d8c9e437f6cc36d479f4be33779834b2180507d gui: forbid edit bitcoind client config if internal (edouard)
bfe73fad18ef3980af5821b6c899e6f3b7ac1918 Add bitcoind logs to loader (edouard)
4c4efebe5bc6560a82bfc4f348ed2663e726a66d gui: encapsulate bitcoind process management (edouard)
d4cf9e3466c40ae74a8099eb4b7b59474ccf2936 fix: move structs next to their methods (edouard)
c756969e7293585471cef64ae4bbe8f203c4866c cleanup bitcoind module dep to app config (edouard)
Pull request description:
- Small rearrangements of the bitcoind module.
- Add a wrapper around the bitcoind child process and share it to the states:
Installer -> loader -> app
- It makes it more easy to share the information if the gui started a bitcoind process, no need anymore to check multiple configuration files, just answer the question: do we have a child process started ?
ACKs for top commit:
darosior:
ACK 6d8c9e437f6cc36d479f4be33779834b2180507d
Tree-SHA512: dd3612d40d15ebde094150abc1cfbf4a54dd1f517fbdeed51934280b0b36e4a4871d3bfe9f5b98617746b042352a570b4be237c3a2b71f74bb636ece4a027208
This commit is contained in:
commit
1bdc6e9fb4
@ -31,7 +31,7 @@ use state::{
|
||||
|
||||
use crate::{
|
||||
app::{cache::Cache, error::Error, menu::Menu, wallet::Wallet},
|
||||
bitcoind::stop_internal_bitcoind,
|
||||
bitcoind::Bitcoind,
|
||||
daemon::{embedded::EmbeddedDaemon, Daemon},
|
||||
};
|
||||
|
||||
@ -42,6 +42,7 @@ pub struct App {
|
||||
config: Config,
|
||||
wallet: Arc<Wallet>,
|
||||
daemon: Arc<dyn Daemon + Sync + Send>,
|
||||
internal_bitcoind: Option<Bitcoind>,
|
||||
}
|
||||
|
||||
impl App {
|
||||
@ -51,6 +52,7 @@ impl App {
|
||||
config: Config,
|
||||
daemon: Arc<dyn Daemon + Sync + Send>,
|
||||
data_dir: PathBuf,
|
||||
internal_bitcoind: Option<Bitcoind>,
|
||||
) -> (App, Command<Message>) {
|
||||
let state: Box<dyn State> = Home::new(wallet.clone(), &cache.coins).into();
|
||||
let cmd = state.load(daemon.clone());
|
||||
@ -62,6 +64,7 @@ impl App {
|
||||
config,
|
||||
daemon,
|
||||
wallet,
|
||||
internal_bitcoind,
|
||||
},
|
||||
cmd,
|
||||
)
|
||||
@ -69,9 +72,12 @@ impl App {
|
||||
|
||||
fn load_state(&mut self, menu: &Menu) -> Command<Message> {
|
||||
self.state = match menu {
|
||||
menu::Menu::Settings => {
|
||||
state::SettingsState::new(self.data_dir.clone(), self.wallet.clone()).into()
|
||||
}
|
||||
menu::Menu::Settings => state::SettingsState::new(
|
||||
self.data_dir.clone(),
|
||||
self.wallet.clone(),
|
||||
self.internal_bitcoind.is_some(),
|
||||
)
|
||||
.into(),
|
||||
menu::Menu::Home => Home::new(self.wallet.clone(), &self.cache.coins).into(),
|
||||
menu::Menu::Coins => CoinsPanel::new(
|
||||
&self.cache.coins,
|
||||
@ -116,12 +122,8 @@ impl App {
|
||||
if !self.daemon.is_external() {
|
||||
self.daemon.stop();
|
||||
info!("Internal daemon stopped");
|
||||
if self.config.internal_bitcoind_exe_config.is_some() {
|
||||
if let Some(daemon_config) = self.daemon.config() {
|
||||
if let Some(bitcoind_config) = &daemon_config.bitcoind_config {
|
||||
stop_internal_bitcoind(bitcoind_config);
|
||||
}
|
||||
}
|
||||
if let Some(bitcoind) = &self.internal_bitcoind {
|
||||
bitcoind.stop();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -22,13 +22,19 @@ pub struct BitcoindSettingsState {
|
||||
warning: Option<Error>,
|
||||
config_updated: bool,
|
||||
daemon_is_external: bool,
|
||||
bitcoind_is_internal: bool,
|
||||
|
||||
settings: Vec<Box<dyn Setting>>,
|
||||
current: Option<usize>,
|
||||
}
|
||||
|
||||
impl BitcoindSettingsState {
|
||||
pub fn new(config: Option<Config>, cache: &Cache, daemon_is_external: bool) -> Self {
|
||||
pub fn new(
|
||||
config: Option<Config>,
|
||||
cache: &Cache,
|
||||
daemon_is_external: bool,
|
||||
bitcoind_is_internal: bool,
|
||||
) -> Self {
|
||||
let settings = if let Some(config) = &config {
|
||||
vec![
|
||||
BitcoindSettings::new(
|
||||
@ -44,6 +50,7 @@ impl BitcoindSettingsState {
|
||||
|
||||
BitcoindSettingsState {
|
||||
daemon_is_external,
|
||||
bitcoind_is_internal,
|
||||
warning: None,
|
||||
config_updated: false,
|
||||
settings,
|
||||
@ -106,7 +113,8 @@ impl State for BitcoindSettingsState {
|
||||
}
|
||||
|
||||
fn view<'a>(&'a self, cache: &'a Cache) -> Element<'a, view::Message> {
|
||||
let can_edit = self.current.is_none() && !self.daemon_is_external;
|
||||
let can_edit =
|
||||
self.current.is_none() && !self.daemon_is_external && !self.bitcoind_is_internal;
|
||||
view::settings::bitcoind_settings(
|
||||
cache,
|
||||
self.warning.as_ref(),
|
||||
|
||||
@ -32,14 +32,16 @@ pub struct SettingsState {
|
||||
data_dir: PathBuf,
|
||||
wallet: Arc<Wallet>,
|
||||
setting: Option<Box<dyn State>>,
|
||||
internal_bitcoind: bool,
|
||||
}
|
||||
|
||||
impl SettingsState {
|
||||
pub fn new(data_dir: PathBuf, wallet: Arc<Wallet>) -> Self {
|
||||
pub fn new(data_dir: PathBuf, wallet: Arc<Wallet>, internal_bitcoind: bool) -> Self {
|
||||
Self {
|
||||
data_dir,
|
||||
wallet,
|
||||
setting: None,
|
||||
internal_bitcoind,
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -58,6 +60,7 @@ impl State for SettingsState {
|
||||
daemon.config().cloned(),
|
||||
cache,
|
||||
daemon.is_external(),
|
||||
self.internal_bitcoind,
|
||||
)
|
||||
.into(),
|
||||
);
|
||||
|
||||
@ -1,9 +1,10 @@
|
||||
use liana::{config::BitcoindConfig, miniscript::bitcoin};
|
||||
use std::path::Path;
|
||||
use std::sync::Arc;
|
||||
use tokio::sync::Mutex;
|
||||
|
||||
use tracing::{info, warn};
|
||||
|
||||
use crate::app::config::InternalBitcoindExeConfig;
|
||||
|
||||
#[cfg(target_os = "windows")]
|
||||
use std::os::windows::process::CommandExt;
|
||||
|
||||
@ -47,51 +48,91 @@ impl std::fmt::Display for StartInternalBitcoindError {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Start internal bitcoind for the given network.
|
||||
pub fn start_internal_bitcoind(
|
||||
network: &bitcoin::Network,
|
||||
exe_config: InternalBitcoindExeConfig,
|
||||
) -> Result<std::process::Child, StartInternalBitcoindError> {
|
||||
let datadir_path_str = exe_config
|
||||
.data_dir
|
||||
.canonicalize()
|
||||
.map_err(|e| StartInternalBitcoindError::CouldNotCanonicalizeDataDir(e.to_string()))?
|
||||
.to_str()
|
||||
.ok_or_else(|| {
|
||||
StartInternalBitcoindError::CouldNotCanonicalizeDataDir(
|
||||
"Couldn't convert path to str.".to_string(),
|
||||
)
|
||||
})?
|
||||
.to_string();
|
||||
#[cfg(target_os = "windows")]
|
||||
// See https://github.com/rust-lang/rust/issues/42869.
|
||||
let datadir_path_str = datadir_path_str.replace("\\\\?\\", "").replace("\\\\?", "");
|
||||
let args = vec![
|
||||
format!("-chain={}", network.to_core_arg()),
|
||||
format!("-datadir={}", datadir_path_str),
|
||||
];
|
||||
let mut command = std::process::Command::new(exe_config.exe_path);
|
||||
#[cfg(target_os = "windows")]
|
||||
let command = command.creation_flags(CREATE_NO_WINDOW);
|
||||
command
|
||||
.args(&args)
|
||||
.stdout(std::process::Stdio::null()) // We still get bitcoind's logs in debug.log.
|
||||
.stderr(std::process::Stdio::piped())
|
||||
.spawn()
|
||||
.map_err(|e| StartInternalBitcoindError::CommandError(e.to_string()))
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct Bitcoind {
|
||||
_process: Arc<std::process::Child>,
|
||||
pub config: BitcoindConfig,
|
||||
pub stdout: Option<Arc<Mutex<std::process::ChildStdout>>>,
|
||||
}
|
||||
|
||||
/// Stop (internal) bitcoind.
|
||||
pub fn stop_internal_bitcoind(bitcoind_config: &BitcoindConfig) {
|
||||
match liana::BitcoinD::new(bitcoind_config, "internal_bitcoind_stop".to_string()) {
|
||||
Ok(bitcoind) => {
|
||||
info!("Stopping internal bitcoind...");
|
||||
bitcoind.stop();
|
||||
info!("Stopped liana managed bitcoind");
|
||||
impl Bitcoind {
|
||||
/// Start internal bitcoind for the given network.
|
||||
pub fn start(
|
||||
network: &bitcoin::Network,
|
||||
mut config: BitcoindConfig,
|
||||
bitcoind_datadir: &Path,
|
||||
exe_path: &Path,
|
||||
) -> Result<Self, StartInternalBitcoindError> {
|
||||
let datadir_path_str = bitcoind_datadir
|
||||
.canonicalize()
|
||||
.map_err(|e| StartInternalBitcoindError::CouldNotCanonicalizeDataDir(e.to_string()))?
|
||||
.to_str()
|
||||
.ok_or_else(|| {
|
||||
StartInternalBitcoindError::CouldNotCanonicalizeDataDir(
|
||||
"Couldn't convert path to str.".to_string(),
|
||||
)
|
||||
})?
|
||||
.to_string();
|
||||
|
||||
// See https://github.com/rust-lang/rust/issues/42869.
|
||||
#[cfg(target_os = "windows")]
|
||||
let datadir_path_str = datadir_path_str.replace("\\\\?\\", "").replace("\\\\?", "");
|
||||
|
||||
let args = vec![
|
||||
format!("-chain={}", network.to_core_arg()),
|
||||
format!("-datadir={}", datadir_path_str),
|
||||
];
|
||||
let mut command = std::process::Command::new(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.
|
||||
.stderr(std::process::Stdio::piped())
|
||||
.spawn()
|
||||
.map_err(|e| StartInternalBitcoindError::CommandError(e.to_string()))?;
|
||||
|
||||
if !crate::utils::poll_for_file(&config.cookie_path, 200, 15) {
|
||||
match process.wait_with_output() {
|
||||
Err(e) => {
|
||||
tracing::error!("Error while waiting for bitcoind to finish: {}", e)
|
||||
}
|
||||
Ok(o) => {
|
||||
tracing::error!("Exit status: {}", o.status);
|
||||
tracing::error!("stderr: {}", String::from_utf8_lossy(&o.stderr));
|
||||
}
|
||||
}
|
||||
return Err(StartInternalBitcoindError::CookieFileNotFound(
|
||||
config.cookie_path.to_string_lossy().into_owned(),
|
||||
));
|
||||
}
|
||||
Err(e) => {
|
||||
warn!("Could not create interface to internal bitcoind: '{}'.", e);
|
||||
config.cookie_path = config.cookie_path.canonicalize().map_err(|e| {
|
||||
StartInternalBitcoindError::CouldNotCanonicalizeCookiePath(e.to_string())
|
||||
})?;
|
||||
|
||||
liana::BitcoinD::new(&config, "internal_bitcoind_start".to_string())
|
||||
.map_err(|e| StartInternalBitcoindError::BitcoinDError(e.to_string()))?;
|
||||
|
||||
Ok(Self {
|
||||
stdout: process.stdout.take().map(|s| Arc::new(Mutex::new(s))),
|
||||
config,
|
||||
_process: Arc::new(process),
|
||||
})
|
||||
}
|
||||
|
||||
/// Stop (internal) bitcoind.
|
||||
pub fn stop(&self) {
|
||||
match liana::BitcoinD::new(&self.config, "internal_bitcoind_stop".to_string()) {
|
||||
Ok(bitcoind) => {
|
||||
info!("Stopping internal bitcoind...");
|
||||
bitcoind.stop();
|
||||
info!("Stopped liana managed bitcoind");
|
||||
}
|
||||
Err(e) => {
|
||||
warn!("Could not create interface to internal bitcoind: '{}'.", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -8,6 +8,7 @@ use crate::{
|
||||
settings::{KeySetting, Settings, WalletSetting},
|
||||
wallet::DEFAULT_WALLET_NAME,
|
||||
},
|
||||
bitcoind::Bitcoind,
|
||||
hw::HardwareWalletConfig,
|
||||
signer::Signer,
|
||||
};
|
||||
@ -36,6 +37,7 @@ pub struct Context {
|
||||
pub bitcoind_is_external: bool,
|
||||
pub internal_bitcoind_config: Option<InternalBitcoindConfig>,
|
||||
pub internal_bitcoind_exe_config: Option<InternalBitcoindExeConfig>,
|
||||
pub internal_bitcoind: Option<Bitcoind>,
|
||||
}
|
||||
|
||||
impl Context {
|
||||
@ -55,6 +57,7 @@ impl Context {
|
||||
bitcoind_is_external: true,
|
||||
internal_bitcoind_config: None,
|
||||
internal_bitcoind_exe_config: None,
|
||||
internal_bitcoind: None,
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -5,7 +5,7 @@ use liana::miniscript::{
|
||||
use std::path::PathBuf;
|
||||
|
||||
use super::Error;
|
||||
use crate::{download::Progress, hw::HardwareWallet};
|
||||
use crate::{bitcoind::Bitcoind, download::Progress, hw::HardwareWallet};
|
||||
use async_hwi::DeviceKind;
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
@ -14,7 +14,7 @@ pub enum Message {
|
||||
ParticipateWallet,
|
||||
ImportWallet,
|
||||
UserActionDone(bool),
|
||||
Exit(PathBuf),
|
||||
Exit(PathBuf, Option<Bitcoind>),
|
||||
Clibpboard(String),
|
||||
Next,
|
||||
Skip,
|
||||
|
||||
@ -18,7 +18,6 @@ use std::sync::{Arc, Mutex};
|
||||
use crate::{
|
||||
app::config::InternalBitcoindExeConfig,
|
||||
app::{config as gui_config, settings as gui_settings},
|
||||
bitcoind::stop_internal_bitcoind,
|
||||
signer::Signer,
|
||||
};
|
||||
|
||||
@ -84,11 +83,10 @@ impl Installer {
|
||||
.expect("There is always a step")
|
||||
.stop();
|
||||
// Now use context to determine what to stop.
|
||||
if self.context.internal_bitcoind_config.is_some() {
|
||||
if let Some(bitcoind_config) = &self.context.bitcoind_config {
|
||||
stop_internal_bitcoind(bitcoind_config);
|
||||
}
|
||||
if let Some(bitcoind) = &self.context.internal_bitcoind {
|
||||
bitcoind.stop();
|
||||
}
|
||||
self.context.internal_bitcoind = None;
|
||||
}
|
||||
|
||||
fn next(&mut self) -> Command<Message> {
|
||||
|
||||
@ -12,14 +12,14 @@ use iced::{Command, Subscription};
|
||||
use liana::{config::BitcoindConfig, miniscript::bitcoin::Network};
|
||||
#[cfg(any(target_os = "macos", target_os = "linux"))]
|
||||
use tar::Archive;
|
||||
use tracing::{error, info};
|
||||
use tracing::info;
|
||||
|
||||
use jsonrpc::{client::Client, simple_http::SimpleHttpTransport};
|
||||
|
||||
use liana_ui::{component::form, widget::*};
|
||||
|
||||
use crate::{
|
||||
bitcoind::{start_internal_bitcoind, stop_internal_bitcoind, StartInternalBitcoindError},
|
||||
bitcoind::{Bitcoind, StartInternalBitcoindError},
|
||||
download,
|
||||
installer::{
|
||||
context::Context,
|
||||
@ -28,7 +28,6 @@ use crate::{
|
||||
step::Step,
|
||||
view, Error, InternalBitcoindExeConfig,
|
||||
},
|
||||
utils::poll_for_file,
|
||||
};
|
||||
|
||||
// The approach for tracking download progress is taken from
|
||||
@ -133,30 +132,6 @@ fn download_url() -> String {
|
||||
)
|
||||
}
|
||||
|
||||
pub struct DefineBitcoind {
|
||||
cookie_path: form::Value<String>,
|
||||
address: form::Value<String>,
|
||||
is_running: Option<Result<(), Error>>,
|
||||
}
|
||||
|
||||
pub struct InternalBitcoindStep {
|
||||
liana_datadir: PathBuf,
|
||||
bitcoind_datadir: PathBuf,
|
||||
network: Network,
|
||||
started: Option<Result<(), StartInternalBitcoindError>>,
|
||||
exe_path: Option<PathBuf>,
|
||||
bitcoind_config: Option<BitcoindConfig>,
|
||||
exe_config: Option<InternalBitcoindExeConfig>,
|
||||
internal_bitcoind_config: Option<InternalBitcoindConfig>,
|
||||
error: Option<String>,
|
||||
exe_download: Option<Download>,
|
||||
install_state: Option<InstallState>,
|
||||
}
|
||||
|
||||
pub struct SelectBitcoindTypeStep {
|
||||
use_external: bool,
|
||||
}
|
||||
|
||||
/// Default prune value used by internal bitcoind.
|
||||
pub const PRUNE_DEFAULT: u32 = 15_000;
|
||||
/// Default ports used by bitcoind across all networks.
|
||||
@ -511,6 +486,10 @@ pub fn port_is_valid(port: &u16) -> bool {
|
||||
!BITCOIND_DEFAULT_PORTS.contains(port)
|
||||
}
|
||||
|
||||
pub struct SelectBitcoindTypeStep {
|
||||
use_external: bool,
|
||||
}
|
||||
|
||||
impl Default for SelectBitcoindTypeStep {
|
||||
fn default() -> Self {
|
||||
Self::new()
|
||||
@ -560,6 +539,12 @@ impl Step for SelectBitcoindTypeStep {
|
||||
}
|
||||
}
|
||||
|
||||
pub struct DefineBitcoind {
|
||||
cookie_path: form::Value<String>,
|
||||
address: form::Value<String>,
|
||||
is_running: Option<Result<(), Error>>,
|
||||
}
|
||||
|
||||
impl DefineBitcoind {
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
@ -682,6 +667,21 @@ impl From<DefineBitcoind> for Box<dyn Step> {
|
||||
}
|
||||
}
|
||||
|
||||
pub struct InternalBitcoindStep {
|
||||
liana_datadir: PathBuf,
|
||||
bitcoind_datadir: PathBuf,
|
||||
network: Network,
|
||||
started: Option<Result<(), StartInternalBitcoindError>>,
|
||||
exe_path: Option<PathBuf>,
|
||||
bitcoind_config: Option<BitcoindConfig>,
|
||||
exe_config: Option<InternalBitcoindExeConfig>,
|
||||
internal_bitcoind_config: Option<InternalBitcoindConfig>,
|
||||
error: Option<String>,
|
||||
exe_download: Option<Download>,
|
||||
install_state: Option<InstallState>,
|
||||
internal_bitcoind: Option<Bitcoind>,
|
||||
}
|
||||
|
||||
impl From<InternalBitcoindStep> for Box<dyn Step> {
|
||||
fn from(s: InternalBitcoindStep) -> Box<dyn Step> {
|
||||
Box::new(s)
|
||||
@ -702,6 +702,7 @@ impl InternalBitcoindStep {
|
||||
error: None,
|
||||
exe_download: None,
|
||||
install_state: None,
|
||||
internal_bitcoind: None,
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -727,11 +728,9 @@ impl Step for InternalBitcoindStep {
|
||||
if let Message::InternalBitcoind(msg) = message {
|
||||
match msg {
|
||||
message::InternalBitcoindMsg::Previous => {
|
||||
if self.internal_bitcoind_config.is_some() {
|
||||
if let Some(bitcoind_config) = &self.bitcoind_config {
|
||||
stop_internal_bitcoind(bitcoind_config);
|
||||
self.started = None;
|
||||
}
|
||||
if let Some(bitcoind) = &self.internal_bitcoind {
|
||||
bitcoind.stop();
|
||||
self.started = None;
|
||||
}
|
||||
return Command::perform(async {}, |_| Message::Previous);
|
||||
}
|
||||
@ -865,36 +864,9 @@ impl Step for InternalBitcoindStep {
|
||||
return Command::none();
|
||||
}
|
||||
};
|
||||
let handle =
|
||||
match start_internal_bitcoind(&self.network, exe_config.clone()) {
|
||||
Err(e) => {
|
||||
self.started = Some(Err(
|
||||
StartInternalBitcoindError::CommandError(e.to_string()),
|
||||
));
|
||||
return Command::none();
|
||||
}
|
||||
Ok(h) => h,
|
||||
};
|
||||
// Need to wait for cookie file to appear.
|
||||
let cookie_path =
|
||||
internal_bitcoind_cookie_path(&self.bitcoind_datadir, &self.network);
|
||||
if !poll_for_file(&cookie_path, 200, 15) {
|
||||
error!("Cookie file still not present after 3 seconds. Waiting for the bitcoind process to finish.");
|
||||
match handle.wait_with_output() {
|
||||
Err(e) => {
|
||||
error!("Error while waiting for bitcoind to finish: {}", e)
|
||||
}
|
||||
Ok(o) => {
|
||||
error!("Exit status: {}", o.status);
|
||||
error!("stderr: {}", String::from_utf8_lossy(&o.stderr));
|
||||
}
|
||||
}
|
||||
self.started =
|
||||
Some(Err(StartInternalBitcoindError::CookieFileNotFound(
|
||||
cookie_path.to_string_lossy().into_owned(),
|
||||
)));
|
||||
return Command::none();
|
||||
}
|
||||
|
||||
let rpc_port = self
|
||||
.internal_bitcoind_config
|
||||
.as_ref()
|
||||
@ -904,36 +876,30 @@ impl Step for InternalBitcoindStep {
|
||||
.get(&self.network)
|
||||
.expect("Already added")
|
||||
.rpc_port;
|
||||
let bitcoind_config = match cookie_path.canonicalize() {
|
||||
Ok(cookie_path) => BitcoindConfig {
|
||||
|
||||
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::CouldNotCanonicalizeCookiePath(
|
||||
e.to_string(),
|
||||
),
|
||||
));
|
||||
self.started = Some(Err(StartInternalBitcoindError::CommandError(
|
||||
e.to_string(),
|
||||
)));
|
||||
return Command::none();
|
||||
}
|
||||
};
|
||||
match liana::BitcoinD::new(
|
||||
&bitcoind_config,
|
||||
"internal_bitcoind_connection_check".to_string(),
|
||||
) {
|
||||
Ok(_) => {
|
||||
Ok(bitcoind) => {
|
||||
self.error = None;
|
||||
self.bitcoind_config = Some(bitcoind_config);
|
||||
self.bitcoind_config = Some(bitcoind.config.clone());
|
||||
self.exe_config = Some(exe_config);
|
||||
self.started = Some(Ok(()));
|
||||
self.internal_bitcoind = Some(bitcoind);
|
||||
}
|
||||
Err(e) => {
|
||||
self.started = Some(Err(
|
||||
StartInternalBitcoindError::BitcoinDError(e.to_string()),
|
||||
));
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
};
|
||||
@ -975,6 +941,7 @@ impl Step for InternalBitcoindStep {
|
||||
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;
|
||||
}
|
||||
@ -994,10 +961,8 @@ impl Step for InternalBitcoindStep {
|
||||
|
||||
fn stop(&self) {
|
||||
// In case the installer is closed before changes written to context, stop bitcoind.
|
||||
if let Some(Ok(_)) = self.started {
|
||||
if let Some(bitcoind_config) = &self.bitcoind_config {
|
||||
stop_internal_bitcoind(bitcoind_config);
|
||||
}
|
||||
if let Some(bitcoind) = &self.internal_bitcoind {
|
||||
bitcoind.stop();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -1248,7 +1248,10 @@ pub fn install<'a>(
|
||||
.push(Container::new(text("Installed !")))
|
||||
.push(Container::new(
|
||||
button::primary(None, "Start")
|
||||
.on_press(Message::Exit(path.clone()))
|
||||
.on_press(Message::Exit(
|
||||
path.clone(),
|
||||
context.internal_bitcoind.clone(),
|
||||
))
|
||||
.width(Length::Fixed(200.0)),
|
||||
))
|
||||
.align_items(Alignment::Center)
|
||||
|
||||
@ -1,5 +1,7 @@
|
||||
use std::convert::From;
|
||||
use std::io::BufRead;
|
||||
use std::io::ErrorKind;
|
||||
use std::ops::DerefMut;
|
||||
use std::path::{Path, PathBuf};
|
||||
use std::sync::Arc;
|
||||
|
||||
@ -12,6 +14,7 @@ use liana::{
|
||||
StartupError,
|
||||
};
|
||||
use liana_ui::{
|
||||
color,
|
||||
component::{button, notification, text::*},
|
||||
icon,
|
||||
util::Collection,
|
||||
@ -24,9 +27,8 @@ use crate::{
|
||||
config::{Config as GUIConfig, InternalBitcoindExeConfig},
|
||||
wallet::{Wallet, WalletError},
|
||||
},
|
||||
bitcoind::{start_internal_bitcoind, stop_internal_bitcoind, StartInternalBitcoindError},
|
||||
bitcoind::{Bitcoind, StartInternalBitcoindError},
|
||||
daemon::{client, embedded::EmbeddedDaemon, model::*, Daemon, DaemonError},
|
||||
utils,
|
||||
};
|
||||
|
||||
type Lianad = client::Lianad<client::jsonrpc::JsonRPCClient>;
|
||||
@ -36,6 +38,7 @@ pub struct Loader {
|
||||
pub network: bitcoin::Network,
|
||||
pub gui_config: GUIConfig,
|
||||
pub daemon_started: bool,
|
||||
pub internal_bitcoind: Option<Bitcoind>,
|
||||
|
||||
step: Step,
|
||||
}
|
||||
@ -46,6 +49,7 @@ pub enum Step {
|
||||
Syncing {
|
||||
daemon: Arc<dyn Daemon + Sync + Send>,
|
||||
progress: f64,
|
||||
bitcoind_logs: String,
|
||||
},
|
||||
Error(Box<Error>),
|
||||
}
|
||||
@ -55,9 +59,20 @@ pub enum Step {
|
||||
pub enum Message {
|
||||
View(ViewMessage),
|
||||
Syncing(Result<GetInfoResult, DaemonError>),
|
||||
Synced(Result<(Arc<Wallet>, Cache, Arc<dyn Daemon + Sync + Send>), Error>),
|
||||
Started(Result<Arc<dyn Daemon + Sync + Send>, Error>),
|
||||
Synced(
|
||||
Result<
|
||||
(
|
||||
Arc<Wallet>,
|
||||
Cache,
|
||||
Arc<dyn Daemon + Sync + Send>,
|
||||
Option<Bitcoind>,
|
||||
),
|
||||
Error,
|
||||
>,
|
||||
),
|
||||
Started(Result<(Arc<dyn Daemon + Sync + Send>, Option<Bitcoind>), Error>),
|
||||
Loaded(Result<Arc<dyn Daemon + Sync + Send>, Error>),
|
||||
BitcoindLog(Option<String>),
|
||||
Failure(DaemonError),
|
||||
}
|
||||
|
||||
@ -66,6 +81,7 @@ impl Loader {
|
||||
datadir_path: PathBuf,
|
||||
gui_config: GUIConfig,
|
||||
network: bitcoin::Network,
|
||||
internal_bitcoind: Option<Bitcoind>,
|
||||
) -> (Self, Command<Message>) {
|
||||
let path = gui_config
|
||||
.daemon_rpc_path
|
||||
@ -79,6 +95,7 @@ impl Loader {
|
||||
gui_config,
|
||||
step: Step::Connecting,
|
||||
daemon_started: false,
|
||||
internal_bitcoind,
|
||||
},
|
||||
Command::perform(connect(path), Message::Loaded),
|
||||
)
|
||||
@ -90,6 +107,7 @@ impl Loader {
|
||||
self.step = Step::Syncing {
|
||||
daemon: daemon.clone(),
|
||||
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.");
|
||||
@ -125,12 +143,30 @@ impl Loader {
|
||||
Command::none()
|
||||
}
|
||||
|
||||
fn on_start(&mut self, res: Result<Arc<dyn Daemon + Sync + Send>, Error>) -> Command<Message> {
|
||||
fn on_log(&mut self, log: Option<String>) -> Command<Message> {
|
||||
if let Step::Syncing { bitcoind_logs, .. } = &mut self.step {
|
||||
if let Some(l) = log {
|
||||
*bitcoind_logs = l;
|
||||
}
|
||||
}
|
||||
Command::none()
|
||||
}
|
||||
|
||||
fn on_start(
|
||||
&mut self,
|
||||
res: Result<(Arc<dyn Daemon + Sync + Send>, Option<Bitcoind>), Error>,
|
||||
) -> Command<Message> {
|
||||
match res {
|
||||
Ok(daemon) => {
|
||||
Ok((daemon, bitcoind)) => {
|
||||
// bitcoind may have been already started and given to the loader
|
||||
// We should not override with None the loader bitcoind field
|
||||
if let Some(bitcoind) = bitcoind {
|
||||
self.internal_bitcoind = Some(bitcoind);
|
||||
}
|
||||
self.step = Step::Syncing {
|
||||
daemon: daemon.clone(),
|
||||
progress: 0.0,
|
||||
bitcoind_logs: String::new(),
|
||||
};
|
||||
Command::perform(sync(daemon, false), Message::Syncing)
|
||||
}
|
||||
@ -156,6 +192,7 @@ impl Loader {
|
||||
self.gui_config.clone(),
|
||||
self.datadir_path.clone(),
|
||||
self.network,
|
||||
self.internal_bitcoind.take(),
|
||||
),
|
||||
Message::Synced,
|
||||
);
|
||||
@ -183,18 +220,9 @@ impl Loader {
|
||||
info!("Internal daemon stopped");
|
||||
}
|
||||
}
|
||||
if self.gui_config.internal_bitcoind_exe_config.is_some() {
|
||||
if let Ok(daemon_config) =
|
||||
Config::from_file(self.gui_config.daemon_config_path.as_ref().cloned())
|
||||
{
|
||||
if let Some(bitcoind_config) = &daemon_config.bitcoind_config {
|
||||
stop_internal_bitcoind(bitcoind_config);
|
||||
} else {
|
||||
warn!("Liana daemon config does not have bitcoind config");
|
||||
}
|
||||
} else {
|
||||
warn!("Liana gui cannot access daemon config to stop bitcoind");
|
||||
}
|
||||
|
||||
if let Some(bitcoind) = &self.internal_bitcoind {
|
||||
bitcoind.stop();
|
||||
}
|
||||
}
|
||||
|
||||
@ -205,6 +233,7 @@ impl Loader {
|
||||
self.datadir_path.clone(),
|
||||
self.gui_config.clone(),
|
||||
self.network,
|
||||
self.internal_bitcoind.clone(),
|
||||
);
|
||||
*self = loader;
|
||||
cmd
|
||||
@ -212,6 +241,7 @@ impl Loader {
|
||||
Message::Started(res) => self.on_start(res),
|
||||
Message::Loaded(res) => self.on_load(res),
|
||||
Message::Syncing(res) => self.on_sync(res),
|
||||
Message::BitcoindLog(log) => self.on_log(log),
|
||||
Message::Synced(Err(e)) => {
|
||||
self.step = Step::Error(Box::new(e));
|
||||
Command::none()
|
||||
@ -225,7 +255,24 @@ impl Loader {
|
||||
}
|
||||
|
||||
pub fn subscription(&self) -> Subscription<Message> {
|
||||
Subscription::none()
|
||||
if let Some(Some(bitcoind_stdout)) =
|
||||
self.internal_bitcoind.as_ref().map(|b| b.stdout.clone())
|
||||
{
|
||||
iced::subscription::unfold(0, bitcoind_stdout, move |stdout| async {
|
||||
let msg = {
|
||||
let mut s = stdout.lock().await;
|
||||
let mut s = std::io::BufReader::new(s.deref_mut());
|
||||
let mut buffer = String::new();
|
||||
match s.read_line(&mut buffer) {
|
||||
Err(e) => Message::BitcoindLog(Some(e.to_string())),
|
||||
Ok(_) => Message::BitcoindLog(Some(buffer)),
|
||||
}
|
||||
};
|
||||
(msg, stdout)
|
||||
})
|
||||
} else {
|
||||
Subscription::none()
|
||||
}
|
||||
}
|
||||
|
||||
pub fn view(&self) -> Element<Message> {
|
||||
@ -239,7 +286,16 @@ pub async fn load_application(
|
||||
gui_config: GUIConfig,
|
||||
datadir_path: PathBuf,
|
||||
network: bitcoin::Network,
|
||||
) -> Result<(Arc<Wallet>, Cache, Arc<dyn Daemon + Sync + Send>), Error> {
|
||||
internal_bitcoind: Option<Bitcoind>,
|
||||
) -> Result<
|
||||
(
|
||||
Arc<Wallet>,
|
||||
Cache,
|
||||
Arc<dyn Daemon + Sync + Send>,
|
||||
Option<Bitcoind>,
|
||||
),
|
||||
Error,
|
||||
> {
|
||||
let coins = daemon.list_coins().map(|res| res.coins)?;
|
||||
let spend_txs = daemon.list_spend_transactions()?;
|
||||
let cache = Cache {
|
||||
@ -253,7 +309,7 @@ pub async fn load_application(
|
||||
let wallet =
|
||||
Wallet::new(info.descriptors.main).load_settings(&gui_config, &datadir_path, network)?;
|
||||
|
||||
Ok((Arc::new(wallet), cache, daemon))
|
||||
Ok((Arc::new(wallet), cache, daemon, internal_bitcoind))
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
@ -278,12 +334,17 @@ pub fn view(step: &Step) -> Element<ViewMessage> {
|
||||
.push(ProgressBar::new(0.0..=1.0, 0.0).width(Length::Fill))
|
||||
.push(text("Connecting to daemon...")),
|
||||
),
|
||||
Step::Syncing { progress, .. } => cover(
|
||||
Step::Syncing {
|
||||
progress,
|
||||
bitcoind_logs,
|
||||
..
|
||||
} => cover(
|
||||
None,
|
||||
Column::new()
|
||||
.width(Length::Fill)
|
||||
.push(ProgressBar::new(0.0..=1.0, *progress as f32).width(Length::Fill))
|
||||
.push(text("Syncing the wallet with the blockchain...")),
|
||||
.push(text("Syncing the wallet with the blockchain..."))
|
||||
.push(p2_regular(bitcoind_logs).style(color::GREY_3)),
|
||||
),
|
||||
Step::Error(error) => cover(
|
||||
Some(("Error while starting the internal daemon", error)),
|
||||
@ -351,8 +412,9 @@ async fn connect(socket_path: PathBuf) -> Result<Arc<dyn Daemon + Sync + Send>,
|
||||
pub async fn start_bitcoind_and_daemon(
|
||||
config_path: PathBuf,
|
||||
bitcoind_exe_config: Option<InternalBitcoindExeConfig>,
|
||||
) -> Result<Arc<dyn Daemon + Sync + Send>, Error> {
|
||||
) -> Result<(Arc<dyn Daemon + Sync + Send>, Option<Bitcoind>), Error> {
|
||||
let config = Config::from_file(Some(config_path)).map_err(Error::Config)?;
|
||||
let mut bitcoind: Option<Bitcoind> = None;
|
||||
if let Some(exe_config) = bitcoind_exe_config {
|
||||
if let Some(bitcoind_config) = &config.bitcoind_config {
|
||||
// Check if bitcoind is already running before trying to start it.
|
||||
@ -361,19 +423,15 @@ pub async fn start_bitcoind_and_daemon(
|
||||
info!("Internal bitcoind is already running");
|
||||
} else {
|
||||
info!("Starting internal bitcoind");
|
||||
start_internal_bitcoind(&config.bitcoin_config.network, exe_config)
|
||||
.map_err(Error::Bitcoind)?;
|
||||
if !utils::poll_for_file(&bitcoind_config.cookie_path, 200, 15) {
|
||||
return Err(Error::Bitcoind(
|
||||
StartInternalBitcoindError::CookieFileNotFound(
|
||||
bitcoind_config.cookie_path.to_string_lossy().into_owned(),
|
||||
),
|
||||
));
|
||||
}
|
||||
liana::BitcoinD::new(bitcoind_config, "internal_bitcoind_start".to_string())
|
||||
.map_err(|e| {
|
||||
Error::Bitcoind(StartInternalBitcoindError::BitcoinDError(e.to_string()))
|
||||
})?;
|
||||
bitcoind = Some(
|
||||
Bitcoind::start(
|
||||
&config.bitcoin_config.network,
|
||||
bitcoind_config.clone(),
|
||||
&exe_config.data_dir,
|
||||
&exe_config.exe_path,
|
||||
)
|
||||
.map_err(Error::Bitcoind)?,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -382,7 +440,7 @@ pub async fn start_bitcoind_and_daemon(
|
||||
|
||||
let daemon = EmbeddedDaemon::start(config)?;
|
||||
|
||||
Ok(Arc::new(daemon))
|
||||
Ok((Arc::new(daemon), bitcoind))
|
||||
}
|
||||
|
||||
async fn sync(
|
||||
|
||||
@ -142,7 +142,7 @@ impl Application for GUI {
|
||||
network,
|
||||
cfg.log_level().unwrap_or(LevelFilter::INFO),
|
||||
);
|
||||
let (loader, command) = Loader::new(datadir_path, cfg, network);
|
||||
let (loader, command) = Loader::new(datadir_path, cfg, network, None);
|
||||
(
|
||||
Self {
|
||||
state: State::Loader(Box::new(loader)),
|
||||
@ -189,14 +189,14 @@ impl Application for GUI {
|
||||
network,
|
||||
cfg.log_level().unwrap_or(LevelFilter::INFO),
|
||||
);
|
||||
let (loader, command) = Loader::new(datadir_path, cfg, network);
|
||||
let (loader, command) = Loader::new(datadir_path, cfg, network, None);
|
||||
self.state = State::Loader(Box::new(loader));
|
||||
command.map(|msg| Message::Load(Box::new(msg)))
|
||||
}
|
||||
_ => l.update(*msg).map(|msg| Message::Launch(Box::new(msg))),
|
||||
},
|
||||
(State::Installer(i), Message::Install(msg)) => {
|
||||
if let installer::Message::Exit(path) = *msg {
|
||||
if let installer::Message::Exit(path, internal_bitcoind) = *msg {
|
||||
let cfg = app::Config::from_file(&path).unwrap();
|
||||
let daemon_cfg =
|
||||
DaemonConfig::from_file(cfg.daemon_config_path.clone()).unwrap();
|
||||
@ -212,8 +212,12 @@ impl Application for GUI {
|
||||
cfg.log_level().unwrap_or(LevelFilter::INFO),
|
||||
);
|
||||
self.logger.remove_install_log_file(datadir_path.clone());
|
||||
let (loader, command) =
|
||||
Loader::new(datadir_path, cfg, daemon_cfg.bitcoin_config.network);
|
||||
let (loader, command) = Loader::new(
|
||||
datadir_path,
|
||||
cfg,
|
||||
daemon_cfg.bitcoin_config.network,
|
||||
internal_bitcoind,
|
||||
);
|
||||
self.state = State::Loader(Box::new(loader));
|
||||
command.map(|msg| Message::Load(Box::new(msg)))
|
||||
} else {
|
||||
@ -226,13 +230,14 @@ impl Application for GUI {
|
||||
State::Launcher(Box::new(Launcher::new(loader.datadir_path.clone())));
|
||||
Command::none()
|
||||
}
|
||||
loader::Message::Synced(Ok((wallet, cache, daemon))) => {
|
||||
loader::Message::Synced(Ok((wallet, cache, daemon, bitcoind))) => {
|
||||
let (app, command) = App::new(
|
||||
cache,
|
||||
wallet,
|
||||
loader.gui_config.clone(),
|
||||
daemon,
|
||||
loader.datadir_path.clone(),
|
||||
bitcoind,
|
||||
);
|
||||
self.state = State::App(app);
|
||||
command.map(|msg| Message::Run(Box::new(msg)))
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user