From f3a136c30be0e443cebae85785f70999dfaf8485 Mon Sep 17 00:00:00 2001 From: Michael Mallan Date: Mon, 28 Oct 2024 12:05:44 +0000 Subject: [PATCH] gui(home): refactor sync status logic Use an enum to clearly enumerate the different cases we are considering and avoid the need to repeat logic relating to blockheight in the view. --- gui/src/app/state/mod.rs | 87 ++++++++++++++++++++++------------------ gui/src/app/view/home.rs | 34 ++++++++-------- gui/src/app/wallet.rs | 17 ++++++++ 3 files changed, 83 insertions(+), 55 deletions(-) diff --git a/gui/src/app/state/mod.rs b/gui/src/app/state/mod.rs index 0a78d24b..57841da1 100644 --- a/gui/src/app/state/mod.rs +++ b/gui/src/app/state/mod.rs @@ -19,7 +19,14 @@ use liana::{ }; use liana_ui::widget::*; -use super::{cache::Cache, error::Error, menu::Menu, message::Message, view, wallet::Wallet}; +use super::{ + cache::Cache, + error::Error, + menu::Menu, + message::Message, + view, + wallet::{SyncStatus, Wallet}, +}; pub const HISTORY_EVENT_PAGE_SIZE: u64 = 20; @@ -69,16 +76,13 @@ pub fn redirect(menu: Menu) -> Command { }) } -fn wallet_is_syncing( +fn sync_status( daemon_backend: DaemonBackend, blockheight: i32, last_poll: Option, last_poll_at_startup: Option, -) -> bool { - match daemon_backend { - // If remote, the wallet is always synced except before the first scan - // after creation. - DaemonBackend::RemoteBackend => blockheight <= 0, +) -> SyncStatus { + if blockheight <= 0 { // If blockheight <= 0, then this is a newly created wallet. // If user imported descriptor and is using a local bitcoind, a rescan // will need to be performed in order to see past transactions and so the @@ -86,30 +90,31 @@ fn wallet_is_syncing( // being performed. // For external daemon or if we otherwise don't know the node type, // treat it the same as bitcoind to be sure we don't mislead the user. - DaemonBackend::EmbeddedLianad(Some(NodeType::Bitcoind)) - | DaemonBackend::EmbeddedLianad(None) - | DaemonBackend::ExternalLianad - if blockheight <= 0 => + if daemon_backend == DaemonBackend::RemoteBackend + || daemon_backend == DaemonBackend::EmbeddedLianad(Some(NodeType::Electrum)) { - false + return SyncStatus::WalletFullScan; } - // If external daemon, we cannot be sure it will return last poll - // as it depends on the version, so assume it won't unless the - // last poll at startup is set. - // TODO: should we check the daemon version at GUI startup? - DaemonBackend::ExternalLianad if last_poll_at_startup.is_none() => false, - // For an existing wallet with any local node type, the first poll - // completing means the wallet has caught up with the tip. - // For a new wallet with a non-bitcoind local node, the first poll - // completing also means that the initial rescan has completed. - _ => last_poll <= last_poll_at_startup, } + // For an existing wallet with any local node type, if the first poll has + // not completed, then the wallet has not yet caught up with the tip. + // An existing wallet with remote backend remains synced so we can ignore it. + // If external daemon, we cannot be sure it will return last poll as it + // depends on the version, so assume it won't unless the last poll at + // startup is set. + // TODO: should we check the daemon version at GUI startup? + else if last_poll <= last_poll_at_startup + && (daemon_backend.is_embedded() + || (daemon_backend == DaemonBackend::ExternalLianad && last_poll_at_startup.is_some())) + { + return SyncStatus::LatestWalletSync; + } + SyncStatus::Synced } pub struct Home { wallet: Arc, - wallet_is_syncing: bool, - blockheight: i32, + sync_status: SyncStatus, last_poll_at_startup: Option, balance: Amount, unconfirmed_balance: Amount, @@ -145,14 +150,12 @@ impl Home { }, ); - let wallet_is_syncing = - wallet_is_syncing(daemon_backend, blockheight, last_poll, last_poll); + let sync_status = sync_status(daemon_backend, blockheight, last_poll, last_poll); Self { wallet, - wallet_is_syncing, + sync_status, last_poll_at_startup: last_poll, - blockheight, balance, unconfirmed_balance, remaining_sequence: None, @@ -167,10 +170,15 @@ impl Home { } } - fn wallet_is_syncing(&self, daemon_backend: DaemonBackend, last_poll: Option) -> bool { - wallet_is_syncing( + fn sync_status( + &self, + daemon_backend: DaemonBackend, + blockheight: i32, + last_poll: Option, + ) -> SyncStatus { + sync_status( daemon_backend, - self.blockheight, + blockheight, last_poll, self.last_poll_at_startup, ) @@ -206,8 +214,7 @@ impl State for Home { &self.events, self.is_last_page, self.processing, - self.wallet_is_syncing, - self.blockheight, + &self.sync_status, ), ) } @@ -285,12 +292,14 @@ impl State for Home { } }, Message::UpdatePanelCache(is_current, Ok(cache)) => { - let wallet_was_syncing = self.wallet_is_syncing; - self.blockheight = cache.blockheight; - self.wallet_is_syncing = - self.wallet_is_syncing(daemon.backend(), cache.last_poll_timestamp); + let wallet_was_syncing = !self.sync_status.is_synced(); + self.sync_status = self.sync_status( + daemon.backend(), + cache.blockheight, + cache.last_poll_timestamp, + ); // If this is the current panel, reload it if wallet is no longer syncing. - if is_current && wallet_was_syncing && !self.wallet_is_syncing { + if is_current && wallet_was_syncing && self.sync_status.is_synced() { return self.reload(daemon, self.wallet.clone()); } } @@ -371,7 +380,7 @@ impl State for Home { wallet: Arc, ) -> Command { // Wait for wallet to finish syncing before reloading data. - if self.wallet_is_syncing { + if !self.sync_status.is_synced() { return Command::none(); } self.selected_event = None; diff --git a/gui/src/app/view/home.rs b/gui/src/app/view/home.rs index 731d04d2..6da3bce2 100644 --- a/gui/src/app/view/home.rs +++ b/gui/src/app/view/home.rs @@ -21,6 +21,7 @@ use crate::{ error::Error, menu::Menu, view::{coins, dashboard, label, message::Message}, + wallet::SyncStatus, }, daemon::model::{HistoryTransaction, TransactionKind}, }; @@ -35,14 +36,13 @@ pub fn home_view<'a>( events: &'a [HistoryTransaction], is_last_page: bool, processing: bool, - wallet_is_syncing: bool, - blockheight: i32, + sync_status: &SyncStatus, ) -> Element<'a, Message> { Column::new() .push(h3("Balance")) .push( Column::new() - .push(if !wallet_is_syncing { + .push(if sync_status.is_synced() { amount_with_size(balance, H1_SIZE) } else { Row::new().push(spinner::Carousel::new( @@ -58,11 +58,11 @@ pub fn home_view<'a>( ], )) }) - .push_maybe(if wallet_is_syncing { + .push_maybe(if !sync_status.is_synced() { Some( Row::new() .push( - text(if blockheight <= 0 { + text(if *sync_status == SyncStatus::WalletFullScan { "Syncing" } else { "Checking for new transactions" @@ -79,17 +79,19 @@ pub fn home_view<'a>( } else { None }) - .push_maybe(if unconfirmed_balance.to_sat() != 0 && !wallet_is_syncing { - Some( - Row::new() - .spacing(10) - .push(text("+").size(H3_SIZE).style(color::GREY_3)) - .push(unconfirmed_amount_with_size(unconfirmed_balance, H3_SIZE)) - .push(text("unconfirmed").size(H3_SIZE).style(color::GREY_3)), - ) - } else { - None - }), + .push_maybe( + if unconfirmed_balance.to_sat() != 0 && sync_status.is_synced() { + Some( + Row::new() + .spacing(10) + .push(text("+").size(H3_SIZE).style(color::GREY_3)) + .push(unconfirmed_amount_with_size(unconfirmed_balance, H3_SIZE)) + .push(text("unconfirmed").size(H3_SIZE).style(color::GREY_3)), + ) + } else { + None + }, + ), ) .push_maybe(if expiring_coins.is_empty() { remaining_sequence.map(|sequence| { diff --git a/gui/src/app/wallet.rs b/gui/src/app/wallet.rs index bea37a16..9dfade00 100644 --- a/gui/src/app/wallet.rs +++ b/gui/src/app/wallet.rs @@ -187,3 +187,20 @@ impl From for WalletError { WalletError::Settings(error) } } + +/// The sync status of a wallet with respect to the blockchain. +#[derive(Debug, Clone, PartialEq)] +pub enum SyncStatus { + /// Wallet and blockchain are fully synced. + Synced, + /// Wallet is performing a full scan of the blockchain. + WalletFullScan, + /// Wallet is syncing with latest transactions. + LatestWalletSync, +} + +impl SyncStatus { + pub fn is_synced(&self) -> bool { + self == &SyncStatus::Synced + } +}