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.
This commit is contained in:
Michael Mallan 2024-10-28 12:05:44 +00:00
parent 82f01fc9ba
commit f3a136c30b
No known key found for this signature in database
GPG Key ID: 5177CDCEDB0EABEB
3 changed files with 83 additions and 55 deletions

View File

@ -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<Message> {
})
}
fn wallet_is_syncing(
fn sync_status(
daemon_backend: DaemonBackend,
blockheight: i32,
last_poll: Option<u32>,
last_poll_at_startup: Option<u32>,
) -> 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>,
wallet_is_syncing: bool,
blockheight: i32,
sync_status: SyncStatus,
last_poll_at_startup: Option<u32>,
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<u32>) -> bool {
wallet_is_syncing(
fn sync_status(
&self,
daemon_backend: DaemonBackend,
blockheight: i32,
last_poll: Option<u32>,
) -> 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<Wallet>,
) -> Command<Message> {
// 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;

View File

@ -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| {

View File

@ -187,3 +187,20 @@ impl From<settings::SettingsError> 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
}
}