Merge #1661: fix: cache coins when first loading GUI with Liana Connect

417a949037b7c33895e34081d9acd23ddd4a10f8 fix: cache coins for remote backend when loading gui (Michael Mallan)
e03d4e17be937cf49f32a9f3d9d80351d7652a9a refactor: add function to get coins to cache (Michael Mallan)

Pull request description:

  With this PR, coins will be cached when first loading a GUI that is using Liana Connect as the backend.

  The cache refreshes less often for Liana Connect (every 2 mins), which means there can be a discrepancy between the coins loaded on a page and those in the cache, especially when first loading the GUI with no coins cached.

  This can be seen when choosing to refresh a coin in the Coins panel. After selecting the coin and the refresh option, the Self-Send page is displayed with coins listed, but the coin that was previously selected in the Coins panel won't be selected here unless the cache has already refreshed. By caching coins at the start, it makes such a scenario less likely to occur when using Liana Connect.

ACKs for top commit:
  edouardparis:
    ACK 417a949037b7c33895e34081d9acd23ddd4a10f8

Tree-SHA512: 1c64fbc7100685575a4e7e20ac95065c0dbaa981f38953659985dcfacc628ebb2870c34e6a82ee8da781485858ce9d6c7414f62fcb61786e50037088e9903212
This commit is contained in:
edouardparis 2025-04-17 16:55:04 +02:00
commit b4883415f3
No known key found for this signature in database
GPG Key ID: E65F7A089C20DC8F
5 changed files with 52 additions and 25 deletions

View File

@ -1,6 +1,11 @@
use crate::daemon::model::Coin;
use crate::daemon::{
model::{Coin, ListCoinsResult},
Daemon, DaemonError,
};
use liana::miniscript::bitcoin::Network;
use lianad::commands::CoinStatus;
use std::path::PathBuf;
use std::sync::Arc;
#[derive(Debug, Clone)]
pub struct Cache {
@ -31,3 +36,12 @@ impl std::default::Default for Cache {
}
}
}
/// Get the coins that should be cached.
pub async fn coins_to_cache(
daemon: Arc<dyn Daemon + Sync + Send>,
) -> Result<ListCoinsResult, DaemonError> {
daemon
.list_coins(&[CoinStatus::Unconfirmed, CoinStatus::Confirmed], &[])
.await
}

View File

@ -318,9 +318,7 @@ impl App {
daemon.is_alive(&datadir_path, network).await?;
let info = daemon.get_info().await?;
let coins = daemon
.list_coins(&[CoinStatus::Unconfirmed, CoinStatus::Confirmed], &[])
.await?;
let coins = cache::coins_to_cache(daemon).await?;
Ok(Cache {
datadir_path,
coins: coins.coins,

View File

@ -8,9 +8,13 @@ use liana_ui::{
icon, theme,
widget::*,
};
use lianad::commands::ListCoinsResult;
use crate::{
app::settings::{AuthConfig, Settings, SettingsError, WalletSetting},
app::{
cache::coins_to_cache,
settings::{AuthConfig, Settings, SettingsError, WalletSetting},
},
daemon::DaemonError,
};
@ -68,7 +72,16 @@ pub enum Message {
// redirect to the installer with the remote backend connection.
Install(Option<BackendClient>),
// redirect to the app runner with the remote backend connection.
Run(Result<(BackendWalletClient, api::Wallet), Error>),
Run(
Result<
(
BackendWalletClient,
api::Wallet,
/* coins to cache */ ListCoinsResult,
),
Error,
>,
),
}
#[derive(Debug, Clone)]
@ -83,7 +96,7 @@ pub enum ViewMessage {
#[derive(Debug, Clone)]
pub enum BackendState {
NoWallet(BackendClient),
WalletExists(BackendWalletClient, api::Wallet),
WalletExists(BackendWalletClient, api::Wallet, ListCoinsResult),
}
pub struct LianaLiteLogin {
@ -182,10 +195,11 @@ impl LianaLiteLogin {
Ok(BackendState::NoWallet(_)) => {
self.auth_error = Some("No wallet found for the given email");
}
Ok(BackendState::WalletExists(client, wallet)) => {
return Task::perform(async move { (client, wallet) }, |(c, w)| {
Message::Run(Ok((c, w)))
});
Ok(BackendState::WalletExists(client, wallet, coins)) => {
return Task::perform(
async move { (client, wallet, coins) },
|(c, w, coins)| Message::Run(Ok((c, w, coins))),
);
}
Err(e) => {
self.connection_error = Some(e);
@ -323,7 +337,7 @@ impl LianaLiteLogin {
Ok(BackendState::NoWallet(client)) => {
return Task::perform(async move { Some(client) }, Message::Install);
}
Ok(BackendState::WalletExists(client, wallet)) => {
Ok(BackendState::WalletExists(client, wallet, coins)) => {
let datadir = self.datadir.clone();
let network = self.network;
return Task::perform(
@ -337,7 +351,7 @@ impl LianaLiteLogin {
)
.await?;
Ok((client, wallet))
Ok((client, wallet, coins))
},
Message::Run,
);
@ -545,10 +559,12 @@ pub async fn connect(
if wallet_id.is_empty() {
let first = wallets.first().cloned().ok_or(DaemonError::NoAnswer)?;
let (wallet_client, wallet) = client.connect_wallet(first);
Ok(BackendState::WalletExists(wallet_client, wallet))
let coins = coins_to_cache(Arc::new(wallet_client.clone())).await?;
Ok(BackendState::WalletExists(wallet_client, wallet, coins))
} else if let Some(wallet) = wallets.into_iter().find(|w| w.id == wallet_id) {
let (wallet_client, wallet) = client.connect_wallet(wallet);
Ok(BackendState::WalletExists(wallet_client, wallet))
let coins = coins_to_cache(Arc::new(wallet_client.clone())).await?;
Ok(BackendState::WalletExists(wallet_client, wallet, coins))
} else {
Ok(BackendState::NoWallet(client))
}
@ -571,7 +587,8 @@ pub async fn connect_with_refresh_token(
.find(|w| w.id == wallet_id)
{
let (wallet_client, wallet) = client.connect_wallet(wallet);
Ok(BackendState::WalletExists(wallet_client, wallet))
let coins = coins_to_cache(Arc::new(wallet_client.clone())).await?;
Ok(BackendState::WalletExists(wallet_client, wallet, coins))
} else {
Ok(BackendState::NoWallet(client))
}

View File

@ -18,7 +18,6 @@ use liana_ui::{
widget::*,
};
use lianad::{
commands::CoinStatus,
config::{BitcoinBackend, Config, ConfigError},
StartupError,
};
@ -28,7 +27,7 @@ use crate::backup::Backup;
use crate::export::RestoreBackupError;
use crate::{
app::{
cache::Cache,
cache::{coins_to_cache, Cache},
config::Config as GUIConfig,
wallet::{Wallet, WalletError},
},
@ -429,10 +428,7 @@ pub async fn load_application(
.load_from_settings(&datadir_path, network)?
.load_hotsigners(&datadir_path, network)?;
let coins = daemon
.list_coins(&[CoinStatus::Unconfirmed, CoinStatus::Confirmed], &[])
.await
.map(|res| res.coins)?;
let coins = coins_to_cache(daemon.clone()).await.map(|res| res.coins)?;
let cache = Cache {
datadir_path,

View File

@ -19,7 +19,7 @@ extern crate serde_json;
use liana::miniscript::bitcoin;
use liana_ui::{component::text, font, image, theme, widget::Element};
use lianad::config::Config as DaemonConfig;
use lianad::{commands::ListCoinsResult, config::Config as DaemonConfig};
use liana_gui::{
app::{self, cache::Cache, config::default_datadir, wallet::Wallet, App},
@ -272,7 +272,7 @@ impl GUI {
self.state = State::Installer(Box::new(install));
command.map(|msg| Message::Install(Box::new(msg)))
}
login::Message::Run(Ok((backend_client, wallet))) => {
login::Message::Run(Ok((backend_client, wallet, coins))) => {
let config = app::Config::from_file(
&l.datadir
.join(l.network.to_string())
@ -288,6 +288,7 @@ impl GUI {
let (app, command) = create_app_with_remote_backend(
backend_client,
wallet,
coins,
l.datadir.clone(),
l.network,
config,
@ -463,6 +464,7 @@ impl GUI {
pub fn create_app_with_remote_backend(
remote_backend: BackendWalletClient,
wallet: api::Wallet,
coins: ListCoinsResult,
datadir: PathBuf,
network: bitcoin::Network,
config: app::Config,
@ -498,7 +500,7 @@ pub fn create_app_with_remote_backend(
App::new(
Cache {
network,
coins: Vec::new(),
coins: coins.coins,
rescan_progress: None,
sync_progress: 1.0, // Remote backend is always synced
datadir_path: datadir.clone(),