Merge #1614: show rescan warning after restoring wallet from backup

d812ad63a1f367bb304d2edf60103e62effca0bc show rescan warning after restoring wallet from backup (Michael Mallan)

Pull request description:

  This adds a warning on the home page if the wallet has been restored from a backup and the user is using a local bitcoind node.

  This warning has a button to jump to the rescan page and another button to dismiss the warning, which is the only way to remove it (rescanning does not automatically remove it).

  Whether the wallet was restored from a backup is not saved after closing the GUI, so this warning will only be shown in the same user session as that in which the wallet was restored.

  The warning will not currently be shown when importing a descriptor.

ACKs for top commit:
  edouardparis:
    ACK d812ad63a1f367bb304d2edf60103e62effca0bc

Tree-SHA512: 55c8becd30f231a9c9ea1a8818a3df23b98c5f69206e522ae1fee7e5e0679b200e5f1728e67e538ed613236e7925b025286b0e3f4719abcfad3d1ff2a1968df6
This commit is contained in:
edouardparis 2025-03-26 17:48:35 +01:00
commit d4448b6479
No known key found for this signature in database
GPG Key ID: E65F7A089C20DC8F
8 changed files with 114 additions and 10 deletions

View File

@ -7,9 +7,16 @@ pub enum Menu {
Transactions,
TransactionPreSelected(Txid),
Settings,
SettingsPreSelected(SettingsOption),
Coins,
CreateSpendTx,
Recovery,
RefreshCoins(Vec<OutPoint>),
PsbtPreSelected(Txid),
}
/// Pre-selectable settings options.
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum SettingsOption {
Node,
}

View File

@ -38,7 +38,7 @@ use wallet::{sync_status, SyncStatus};
use crate::{
app::{cache::Cache, error::Error, menu::Menu, wallet::Wallet},
daemon::{embedded::EmbeddedDaemon, Daemon, DaemonBackend},
node::bitcoind::Bitcoind,
node::{bitcoind::Bitcoind, NodeType},
};
use self::state::SettingsState;
@ -63,7 +63,15 @@ impl Panels {
daemon_backend: DaemonBackend,
internal_bitcoind: Option<&Bitcoind>,
config: Arc<Config>,
restored_from_backup: bool,
) -> Panels {
let show_rescan_warning = restored_from_backup
&& daemon_backend.is_lianad()
&& daemon_backend
.node_type()
.map(|nt| nt == NodeType::Bitcoind)
// We don't know the node type for external lianad so assume it's bitcoind.
.unwrap_or(true);
Self {
current: Menu::Home,
home: Home::new(
@ -77,6 +85,7 @@ impl Panels {
cache.last_poll_at_startup,
),
cache.blockheight,
show_rescan_warning,
),
coins: CoinsPanel::new(&cache.coins, wallet.main_descriptor.first_timelock_value()),
transactions: TransactionsPanel::new(wallet.clone()),
@ -106,7 +115,7 @@ impl Panels {
Menu::PSBTs => &self.psbts,
Menu::Transactions => &self.transactions,
Menu::TransactionPreSelected(_) => &self.transactions,
Menu::Settings => &self.settings,
Menu::Settings | Menu::SettingsPreSelected(_) => &self.settings,
Menu::Coins => &self.coins,
Menu::CreateSpendTx => &self.create_spend,
Menu::Recovery => &self.recovery,
@ -122,7 +131,7 @@ impl Panels {
Menu::PSBTs => &mut self.psbts,
Menu::Transactions => &mut self.transactions,
Menu::TransactionPreSelected(_) => &mut self.transactions,
Menu::Settings => &mut self.settings,
Menu::Settings | Menu::SettingsPreSelected(_) => &mut self.settings,
Menu::Coins => &mut self.coins,
Menu::CreateSpendTx => &mut self.create_spend,
Menu::Recovery => &mut self.recovery,
@ -150,6 +159,7 @@ impl App {
daemon: Arc<dyn Daemon + Sync + Send>,
data_dir: PathBuf,
internal_bitcoind: Option<Bitcoind>,
restored_from_backup: bool,
) -> (App, Task<Message>) {
let config = Arc::new(config);
let mut panels = Panels::new(
@ -159,6 +169,7 @@ impl App {
daemon.backend(),
internal_bitcoind.as_ref(),
config.clone(),
restored_from_backup,
);
let cmd = panels.home.reload(daemon.clone(), wallet.clone());
(
@ -205,6 +216,16 @@ impl App {
return Task::none();
};
}
menu::Menu::SettingsPreSelected(setting) => {
self.panels.current = menu.clone();
return self.panels.current_mut().update(
self.daemon.clone(),
&self.cache,
Message::View(view::Message::Settings(match setting {
&menu::SettingsOption::Node => view::SettingsMessage::EditBitcoindSettings,
})),
);
}
menu::Menu::RefreshCoins(preselected) => {
self.panels.create_spend = CreateSpendPanel::new_self_send(
self.wallet.clone(),

View File

@ -135,6 +135,7 @@ pub struct Home {
selected_event: Option<(HistoryTransaction, usize)>,
labels_edited: LabelsEdited,
warning: Option<Error>,
show_rescan_warning: bool,
}
impl Home {
@ -143,6 +144,7 @@ impl Home {
coins: &[Coin],
sync_status: SyncStatus,
tip_height: i32,
show_rescan_warning: bool,
) -> Self {
let (balance, unconfirmed_balance, expiring_coins, remaining_seq) = coins_summary(
coins,
@ -163,6 +165,7 @@ impl Home {
warning: None,
is_last_page: false,
processing: false,
show_rescan_warning,
}
}
}
@ -191,6 +194,7 @@ impl State for Home {
self.is_last_page,
self.processing,
&self.sync_status,
self.show_rescan_warning,
),
)
}
@ -276,6 +280,9 @@ impl State for Home {
self.warning = Some(e);
}
},
Message::View(view::Message::HideRescanWarning) => {
self.show_rescan_warning = false;
}
Message::View(view::Message::SelectPayment(outpoint)) => {
return Task::perform(
async move {

View File

@ -4,14 +4,16 @@ use std::{collections::HashMap, time::Duration, vec};
use iced::{
alignment,
widget::{Container, Row, Space},
Alignment, Length,
Alignment::{self, Center},
Length,
};
use liana::miniscript::bitcoin;
use liana_ui::{
color,
component::{amount::*, button, card, event, form, spinner, text::*},
icon, theme,
icon::{self, cross_icon},
theme,
widget::*,
};
@ -19,13 +21,46 @@ use crate::{
app::{
cache::Cache,
error::Error,
menu::Menu,
menu::{self, Menu},
view::{coins, dashboard, label, message::Message},
wallet::SyncStatus,
},
daemon::model::{HistoryTransaction, Payment, PaymentKind, TransactionKind},
};
const RESCAN_WARNING: &str = "As this wallet was restored from a backup, you may need to rescan the blockchain to see past transactions.";
fn rescan_warning<'a>() -> Element<'a, Message> {
Container::new(
Column::new()
.spacing(10)
.push(
Row::new()
.spacing(5)
.push(icon::warning_icon().style(theme::text::warning))
.push(text(RESCAN_WARNING).style(theme::text::warning))
.align_y(Center),
)
.push(
Row::new()
.spacing(5)
.push(Space::with_width(Length::Fill))
.push(
button::secondary(None, "Go to rescan").on_press(Message::Menu(
Menu::SettingsPreSelected(menu::SettingsOption::Node),
)),
)
.push(
button::secondary(Some(cross_icon()), "Dismiss")
.on_press(Message::HideRescanWarning),
),
),
)
.padding(25)
.style(theme::card::border)
.into()
}
#[allow(clippy::too_many_arguments)]
pub fn home_view<'a>(
balance: &'a bitcoin::Amount,
@ -36,6 +71,7 @@ pub fn home_view<'a>(
is_last_page: bool,
processing: bool,
sync_status: &SyncStatus,
show_rescan_warning: bool,
) -> Element<'a, Message> {
Column::new()
.push(h3("Balance"))
@ -99,6 +135,7 @@ pub fn home_view<'a>(
},
),
)
.push_maybe(show_rescan_warning.then_some(rescan_warning()))
.push_maybe(if expiring_coins.is_empty() {
remaining_sequence.map(|sequence| {
Container::new(

View File

@ -24,6 +24,7 @@ pub enum Message {
CreateRbf(CreateRbfMessage),
ShowQrCode(usize),
ImportExport(ImportExportMessage),
HideRescanWarning,
}
impl Close for Message {

View File

@ -76,6 +76,21 @@ impl DaemonBackend {
pub fn is_embedded(&self) -> bool {
matches!(self, DaemonBackend::EmbeddedLianad(_))
}
pub fn is_lianad(&self) -> bool {
matches!(
self,
DaemonBackend::EmbeddedLianad(_) | DaemonBackend::ExternalLianad
)
}
pub fn node_type(&self) -> Option<node::NodeType> {
if let DaemonBackend::EmbeddedLianad(node_type) = self {
*node_type
} else {
None
}
}
}
#[async_trait]

View File

@ -103,6 +103,7 @@ pub enum Message {
),
Error,
>,
/* restored_from_backup */ bool,
),
Started(StartedResult),
Loaded(Result<(Arc<dyn Daemon + Sync + Send>, GetInfoResult), Error>),

View File

@ -369,7 +369,9 @@ impl GUI {
},
|r| {
let r = r.map_err(loader::Error::RestoreBackup);
Message::Load(Box::new(loader::Message::App(r)))
Message::Load(Box::new(loader::Message::App(
r, /* restored_from_backup */ true,
)))
},
)
} else {
@ -380,17 +382,29 @@ impl GUI {
daemon,
loader.datadir_path.clone(),
bitcoind,
false,
);
self.state = State::App(app);
command.map(|msg| Message::Run(Box::new(msg)))
}
}
loader::Message::App(Ok((cache, wallet, config, daemon, datadir, bitcoind))) => {
let (app, command) = App::new(cache, wallet, config, daemon, datadir, bitcoind);
loader::Message::App(
Ok((cache, wallet, config, daemon, datadir, bitcoind)),
restored_from_backup,
) => {
let (app, command) = App::new(
cache,
wallet,
config,
daemon,
datadir,
bitcoind,
restored_from_backup,
);
self.state = State::App(app);
command.map(|msg| Message::Run(Box::new(msg)))
}
loader::Message::App(Err(e)) => {
loader::Message::App(Err(e), _) => {
tracing::error!("Fail to import backup: {e}");
Task::none()
}
@ -506,6 +520,7 @@ pub fn create_app_with_remote_backend(
Arc::new(remote_backend),
datadir,
None,
false,
)
}