From 18f2fe45722466b897f1f98010783b6ff0a4ddbc Mon Sep 17 00:00:00 2001 From: edouardparis Date: Mon, 29 Jan 2024 14:28:51 +0100 Subject: [PATCH 1/2] gui: display error for rescan past pruned height close #900 --- gui/src/app/state/settings/bitcoind.rs | 145 +++++++++++-------------- gui/src/app/state/settings/mod.rs | 11 -- gui/src/app/view/message.rs | 3 +- gui/src/app/view/settings.rs | 11 +- 4 files changed, 78 insertions(+), 92 deletions(-) diff --git a/gui/src/app/state/settings/bitcoind.rs b/gui/src/app/state/settings/bitcoind.rs index 966c6a1a..eeeb3409 100644 --- a/gui/src/app/state/settings/bitcoind.rs +++ b/gui/src/app/state/settings/bitcoind.rs @@ -13,7 +13,7 @@ use liana::config::{BitcoinConfig, BitcoindConfig, BitcoindRpcAuth, Config}; use liana_ui::{component::form, widget::Element}; use crate::{ - app::{cache::Cache, error::Error, message::Message, state::settings::Setting, view, State}, + app::{cache::Cache, error::Error, message::Message, view, State}, bitcoind::{RpcAuthType, RpcAuthValues}, daemon::Daemon, }; @@ -23,8 +23,8 @@ pub struct BitcoindSettingsState { warning: Option, config_updated: bool, - settings: Vec>, - current: Option, + node_settings: Option, + rescan_settings: RescanSetting, } impl BitcoindSettingsState { @@ -34,27 +34,18 @@ impl BitcoindSettingsState { daemon_is_external: bool, bitcoind_is_internal: bool, ) -> Self { - let settings = if let Some(config) = &config { - vec![ + BitcoindSettingsState { + warning: None, + config_updated: false, + node_settings: config.map(|config| { BitcoindSettings::new( config.bitcoin_config.clone(), config.bitcoind_config.clone().unwrap(), daemon_is_external, bitcoind_is_internal, ) - .into(), - RescanSetting::new(cache.rescan_progress).into(), - ] - } else { - vec![RescanSetting::new(cache.rescan_progress).into()] - }; - - BitcoindSettingsState { - warning: None, - config_updated: false, - settings, - // If a scan is running, the current setting edited is the Rescan panel. - current: cache.rescan_progress.map(|_| 1), + }), + rescan_settings: RescanSetting::new(cache.rescan_progress), } } } @@ -71,25 +62,20 @@ impl State for BitcoindSettingsState { Ok(()) => { self.config_updated = true; self.warning = None; - if let Some(current) = self.current { - if let Some(setting) = self.settings.get_mut(current) { - setting.edited(true); - return Command::perform(async {}, |_| { - Message::View(view::Message::Settings( - view::SettingsMessage::EditBitcoindSettings, - )) - }); - } + if let Some(settings) = &mut self.node_settings { + settings.edited(true); + return Command::perform(async {}, |_| { + Message::View(view::Message::Settings( + view::SettingsMessage::EditBitcoindSettings, + )) + }); } - self.current = None; } Err(e) => { self.config_updated = false; self.warning = Some(e); - if let Some(current) = self.current { - if let Some(setting) = self.settings.get_mut(current) { - setting.edited(false); - } + if let Some(settings) = &mut self.node_settings { + settings.edited(false); } } }, @@ -97,39 +83,57 @@ impl State for BitcoindSettingsState { Err(e) => self.warning = Some(e), Ok(info) => { if info.rescan_progress == Some(1.0) { - self.settings[1].edited(true); + self.rescan_settings.edited(true); } } }, - Message::View(view::Message::Settings(view::SettingsMessage::Edit(i, msg))) => { - if let Some(setting) = self.settings.get_mut(i) { - match msg { - view::SettingsEditMessage::Select => self.current = Some(i), - view::SettingsEditMessage::Cancel => self.current = None, - _ => {} - }; - return setting.update(daemon, cache, msg); + Message::StartRescan(Err(_)) => { + self.rescan_settings.past_possible_height = true; + self.rescan_settings.processing = false; + } + Message::View(view::Message::Settings(view::SettingsMessage::BitcoindSettings( + msg, + ))) => { + if let Some(settings) = &mut self.node_settings { + return settings.update(daemon, cache, msg); } } + Message::View(view::Message::Settings(view::SettingsMessage::RescanSettings(msg))) => { + return self.rescan_settings.update(daemon, cache, msg); + } _ => {} }; Command::none() } fn view<'a>(&'a self, cache: &'a Cache) -> Element<'a, view::Message> { - let can_edit = self.current.is_none(); + let can_edit_bitcoind_settings = !self.rescan_settings.processing; + let can_do_rescan = !self.rescan_settings.processing + && self.node_settings.as_ref().map(|settings| settings.edit) != Some(true); view::settings::bitcoind_settings( cache, self.warning.as_ref(), - self.settings - .iter() - .enumerate() - .map(|(i, setting)| { - setting.view(cache, can_edit).map(move |msg| { - view::Message::Settings(view::SettingsMessage::Edit(i, msg)) - }) - }) - .collect(), + if let Some(settings) = &self.node_settings { + vec![ + settings + .view(cache, can_edit_bitcoind_settings) + .map(move |msg| { + view::Message::Settings(view::SettingsMessage::BitcoindSettings(msg)) + }), + self.rescan_settings + .view(cache, can_do_rescan) + .map(move |msg| { + view::Message::Settings(view::SettingsMessage::RescanSettings(msg)) + }), + ] + } else { + vec![self + .rescan_settings + .view(cache, can_do_rescan) + .map(move |msg| { + view::Message::Settings(view::SettingsMessage::RescanSettings(msg)) + })] + }, ) } } @@ -153,12 +157,6 @@ pub struct BitcoindSettings { bitcoind_is_internal: bool, } -impl From for Box { - fn from(s: BitcoindSettings) -> Box { - Box::new(s) - } -} - impl BitcoindSettings { fn new( bitcoin_config: BitcoinConfig, @@ -211,7 +209,7 @@ impl BitcoindSettings { } } -impl Setting for BitcoindSettings { +impl BitcoindSettings { fn edited(&mut self, success: bool) { self.processing = false; if success { @@ -311,31 +309,29 @@ impl Setting for BitcoindSettings { #[derive(Debug, Default)] pub struct RescanSetting { - edit: bool, processing: bool, success: bool, year: form::Value, month: form::Value, day: form::Value, invalid_date: bool, -} - -impl From for Box { - fn from(s: RescanSetting) -> Box { - Box::new(s) - } + past_possible_height: bool, } impl RescanSetting { pub fn new(rescan_progress: Option) -> Self { Self { - processing: rescan_progress.is_some(), + processing: if let Some(progress) = rescan_progress { + progress < 1.0 + } else { + false + }, ..Default::default() } } } -impl Setting for RescanSetting { +impl RescanSetting { fn edited(&mut self, success: bool) { self.processing = false; self.success = success; @@ -348,16 +344,6 @@ impl Setting for RescanSetting { message: view::SettingsEditMessage, ) -> Command { match message { - view::SettingsEditMessage::Select => { - if !self.processing { - self.edit = true; - } - } - view::SettingsEditMessage::Cancel => { - if !self.processing { - self.edit = false; - } - } view::SettingsEditMessage::FieldEdited(field, value) => { self.invalid_date = false; if !self.processing && (value.is_empty() || u32::from_str(&value).is_ok()) { @@ -376,10 +362,10 @@ impl Setting for RescanSetting { u32::from_str(&self.day.value).unwrap_or(1), ) { if date < NaiveDate::from_str("2009-01-03").unwrap() { - self.invalid_date = true; + self.past_possible_height = true; return Command::none(); } else { - self.invalid_date = false; + self.past_possible_height = false; date } } else { @@ -409,6 +395,7 @@ impl Setting for RescanSetting { self.processing, can_edit, self.invalid_date, + self.past_possible_height, ) } } diff --git a/gui/src/app/state/settings/mod.rs b/gui/src/app/state/settings/mod.rs index ad88a406..21cf2820 100644 --- a/gui/src/app/state/settings/mod.rs +++ b/gui/src/app/state/settings/mod.rs @@ -17,17 +17,6 @@ use crate::{ daemon::Daemon, }; -trait Setting: std::fmt::Debug { - fn edited(&mut self, success: bool); - fn update( - &mut self, - daemon: Arc, - cache: &Cache, - message: view::SettingsEditMessage, - ) -> Command; - fn view<'a>(&self, cache: &'a Cache, can_edit: bool) -> Element<'a, view::SettingsEditMessage>; -} - pub struct SettingsState { data_dir: PathBuf, wallet: Arc, diff --git a/gui/src/app/view/message.rs b/gui/src/app/view/message.rs index 44cd6043..8cbfb5f2 100644 --- a/gui/src/app/view/message.rs +++ b/gui/src/app/view/message.rs @@ -63,12 +63,13 @@ pub enum SpendTxMessage { #[derive(Debug, Clone)] pub enum SettingsMessage { EditBitcoindSettings, + BitcoindSettings(SettingsEditMessage), + RescanSettings(SettingsEditMessage), EditWalletSettings, AboutSection, RegisterWallet, FingerprintAliasEdited(Fingerprint, String), Save, - Edit(usize, SettingsEditMessage), } #[derive(Debug, Clone)] diff --git a/gui/src/app/view/settings.rs b/gui/src/app/view/settings.rs index 4618ad4b..e44bc7f8 100644 --- a/gui/src/app/view/settings.rs +++ b/gui/src/app/view/settings.rs @@ -475,6 +475,7 @@ pub fn rescan<'a>( processing: bool, can_edit: bool, invalid_date: bool, + past_possible_height: bool, ) -> Element<'a, SettingsEditMessage> { card::simple(Container::new( Column::new() @@ -534,7 +535,15 @@ pub fn rescan<'a>( ) .push_maybe(if invalid_date { Some( - p1_regular("Provided date is either invalid or anterior to the genesis block's timestamp.") + p1_regular("Provided date is invalid") + .style(color::RED), + ) + } else { + None + }) + .push_maybe(if past_possible_height { + Some( + p1_regular("Provided date earlier than the genesis block's timestamp or the node prune height") .style(color::RED), ) } else { From 69fd1891f3d4c0c1d9fa3ec807a7a09bd8468a5c Mon Sep 17 00:00:00 2001 From: edouardparis Date: Tue, 30 Jan 2024 11:58:43 +0100 Subject: [PATCH 2/2] If date prior to genesis block timestamp, rescan from genesis --- gui/src/app/state/settings/bitcoind.rs | 79 +++++++++++++++++++++----- gui/src/app/view/settings.rs | 13 +++-- 2 files changed, 73 insertions(+), 19 deletions(-) diff --git a/gui/src/app/state/settings/bitcoind.rs b/gui/src/app/state/settings/bitcoind.rs index eeeb3409..cf944660 100644 --- a/gui/src/app/state/settings/bitcoind.rs +++ b/gui/src/app/state/settings/bitcoind.rs @@ -1,4 +1,4 @@ -use std::convert::From; +use std::convert::{From, TryInto}; use std::net::SocketAddr; use std::path::PathBuf; use std::str::FromStr; @@ -8,7 +8,10 @@ use chrono::prelude::*; use iced::Command; use tracing::info; -use liana::config::{BitcoinConfig, BitcoindConfig, BitcoindRpcAuth, Config}; +use liana::{ + config::{BitcoinConfig, BitcoindConfig, BitcoindRpcAuth, Config}, + miniscript::bitcoin::Network, +}; use liana_ui::{component::form, widget::Element}; @@ -40,7 +43,7 @@ impl BitcoindSettingsState { node_settings: config.map(|config| { BitcoindSettings::new( config.bitcoin_config.clone(), - config.bitcoind_config.clone().unwrap(), + config.bitcoind_config.unwrap(), daemon_is_external, bitcoind_is_internal, ) @@ -315,6 +318,7 @@ pub struct RescanSetting { month: form::Value, day: form::Value, invalid_date: bool, + future_date: bool, past_possible_height: bool, } @@ -340,12 +344,14 @@ impl RescanSetting { fn update( &mut self, daemon: Arc, - _cache: &Cache, + cache: &Cache, message: view::SettingsEditMessage, ) -> Command { match message { view::SettingsEditMessage::FieldEdited(field, value) => { self.invalid_date = false; + self.future_date = false; + self.past_possible_height = false; if !self.processing && (value.is_empty() || u32::from_str(&value).is_ok()) { match field { "rescan_year" => self.year.value = value, @@ -356,27 +362,66 @@ impl RescanSetting { } } view::SettingsEditMessage::Confirm => { - let date_time = if let Some(date) = NaiveDate::from_ymd_opt( + let t = if let Some(date) = NaiveDate::from_ymd_opt( i32::from_str(&self.year.value).unwrap_or(1), u32::from_str(&self.month.value).unwrap_or(1), u32::from_str(&self.day.value).unwrap_or(1), - ) { - if date < NaiveDate::from_str("2009-01-03").unwrap() { - self.past_possible_height = true; - return Command::none(); - } else { - self.past_possible_height = false; - date + ) + .and_then(|d| d.and_hms_opt(0, 0, 0)) + .map(|d| d.timestamp()) + { + match cache.network { + Network::Bitcoin => { + if date < MAINNET_GENESIS_BLOCK_TIMESTAMP { + info!("Date {} prior to genesis block, using genesis block timestamp {}", date, MAINNET_GENESIS_BLOCK_TIMESTAMP); + + MAINNET_GENESIS_BLOCK_TIMESTAMP + } else { + date + } + } + Network::Testnet => { + if date < TESTNET3_GENESIS_BLOCK_TIMESTAMP { + info!("Date {} prior to genesis block, using genesis block timestamp {}", date, TESTNET3_GENESIS_BLOCK_TIMESTAMP); + TESTNET3_GENESIS_BLOCK_TIMESTAMP + } else { + date + } + } + Network::Signet => { + if date < SIGNET_GENESIS_BLOCK_TIMESTAMP { + info!("Date {} prior to genesis block, using genesis block timestamp {}", date, SIGNET_GENESIS_BLOCK_TIMESTAMP); + SIGNET_GENESIS_BLOCK_TIMESTAMP + } else { + date + } + } + // We expect regtest user to not use genesis block timestamp inferior to + // the mainnet one. + // Network is a non exhaustive enum, that is why the _. + _ => { + if date < MAINNET_GENESIS_BLOCK_TIMESTAMP { + info!("Date {} prior to genesis block, using genesis block timestamp {}", date, MAINNET_GENESIS_BLOCK_TIMESTAMP); + MAINNET_GENESIS_BLOCK_TIMESTAMP + } else { + date + } + } } } else { self.invalid_date = true; return Command::none(); }; - let t = date_time.and_hms_opt(0, 0, 0).unwrap().timestamp() as u32; + if t > Utc::now().timestamp() { + self.future_date = true; + return Command::none(); + } self.processing = true; info!("Asking deamon to rescan with timestamp: {}", t); return Command::perform( - async move { daemon.start_rescan(t).map_err(|e| e.into()) }, + async move { + daemon.start_rescan(t.try_into().expect("t cannot be inferior to 0 otherwise genesis block timestam is chosen")).map_err(|e| e.into()) + }, Message::StartRescan, ); } @@ -396,6 +441,12 @@ impl RescanSetting { can_edit, self.invalid_date, self.past_possible_height, + self.future_date, ) } } + +/// Use bitcoin-cli getblock $(bitcoin-cli getblockhash 0) | jq .time +const MAINNET_GENESIS_BLOCK_TIMESTAMP: i64 = 1231006505; +const TESTNET3_GENESIS_BLOCK_TIMESTAMP: i64 = 1296688602; +const SIGNET_GENESIS_BLOCK_TIMESTAMP: i64 = 1598918400; diff --git a/gui/src/app/view/settings.rs b/gui/src/app/view/settings.rs index e44bc7f8..73658922 100644 --- a/gui/src/app/view/settings.rs +++ b/gui/src/app/view/settings.rs @@ -476,6 +476,7 @@ pub fn rescan<'a>( can_edit: bool, invalid_date: bool, past_possible_height: bool, + future_date: bool, ) -> Element<'a, SettingsEditMessage> { card::simple(Container::new( Column::new() @@ -534,21 +535,23 @@ pub fn rescan<'a>( .spacing(10), ) .push_maybe(if invalid_date { - Some( - p1_regular("Provided date is invalid") - .style(color::RED), - ) + Some(p1_regular("Provided date is invalid").style(color::RED)) } else { None }) .push_maybe(if past_possible_height { Some( - p1_regular("Provided date earlier than the genesis block's timestamp or the node prune height") + p1_regular("Provided date earlier than the node prune height") .style(color::RED), ) } else { None }) + .push_maybe(if future_date { + Some(p1_regular("Provided date is in the future").style(color::RED)) + } else { + None + }) .push( if can_edit && !invalid_date