diff --git a/gui/Cargo.lock b/gui/Cargo.lock index 7e18ddd9..3d56cf81 100644 --- a/gui/Cargo.lock +++ b/gui/Cargo.lock @@ -2643,6 +2643,7 @@ name = "liana_gui" version = "5.0.0" dependencies = [ "async-hwi", + "async-trait", "backtrace", "base64 0.21.6", "bitcoin_hashes 0.12.0", diff --git a/gui/Cargo.toml b/gui/Cargo.toml index a9e75330..9229176d 100644 --- a/gui/Cargo.toml +++ b/gui/Cargo.toml @@ -14,6 +14,7 @@ name = "liana-gui" path = "src/main.rs" [dependencies] +async-trait = "0.1" async-hwi = "0.0.16" liana = { git = "https://github.com/wizardsardine/liana", branch = "master", default-features = false, features = ["nonblocking_shutdown"] } liana_ui = { path = "ui" } diff --git a/gui/src/app/mod.rs b/gui/src/app/mod.rs index a2aa1717..6049530a 100644 --- a/gui/src/app/mod.rs +++ b/gui/src/app/mod.rs @@ -16,6 +16,7 @@ use std::sync::Arc; use std::time::Duration; use iced::{clipboard, time, Command, Subscription}; +use tokio::runtime::Handle; use tracing::{error, info, warn}; pub use liana::{commands::CoinStatus, config::Config as DaemonConfig, miniscript::bitcoin}; @@ -155,11 +156,12 @@ impl App { fn set_current_panel(&mut self, menu: Menu) -> Command { match &menu { menu::Menu::TransactionPreSelected(txid) => { - if let Ok(Some(tx)) = self - .daemon - .get_history_txs(&[*txid]) - .map(|txs| txs.first().cloned()) - { + if let Ok(Some(tx)) = Handle::current().block_on(async { + self.daemon + .get_history_txs(&[*txid]) + .await + .map(|txs| txs.first().cloned()) + }) { self.panels.transactions.preselect(tx); self.panels.current = menu; return Command::none(); @@ -169,11 +171,12 @@ impl App { // Get preselected spend from DB in case it's not yet in the cache. // We only need this single spend as we will go straight to its view and not show the PSBTs list. // In case of any error loading the spend or if it doesn't exist, load PSBTs list in usual way. - if let Ok(Some(spend_tx)) = self - .daemon - .list_spend_transactions(Some(&[*txid])) - .map(|txs| txs.first().cloned()) - { + if let Ok(Some(spend_tx)) = Handle::current().block_on(async { + self.daemon + .list_spend_transactions(Some(&[*txid])) + .await + .map(|txs| txs.first().cloned()) + }) { self.panels.psbts.preselect(spend_tx); self.panels.current = menu; return Command::none(); @@ -201,6 +204,7 @@ impl App { } _ => {} }; + self.panels.current = menu; self.panels .current_mut() @@ -217,7 +221,7 @@ impl App { pub fn stop(&mut self) { info!("Close requested"); if !self.daemon.is_external() { - if let Err(e) = self.daemon.stop() { + if let Err(e) = Handle::current().block_on(async { self.daemon.stop().await }) { error!("{}", e); } else { info!("Internal daemon stopped"); @@ -236,11 +240,12 @@ impl App { Command::perform( async move { // we check every 10 second if the daemon poller is alive - daemon.is_alive()?; + daemon.is_alive().await?; - let info = daemon.get_info()?; + let info = daemon.get_info().await?; let coins = daemon - .list_coins(&[CoinStatus::Unconfirmed, CoinStatus::Confirmed], &[])?; + .list_coins(&[CoinStatus::Unconfirmed, CoinStatus::Confirmed], &[]) + .await?; Ok(Cache { datadir_path, coins: coins.coins, @@ -284,7 +289,7 @@ impl App { daemon_config_path: &PathBuf, cfg: DaemonConfig, ) -> Result<(), Error> { - self.daemon.stop()?; + Handle::current().block_on(async { self.daemon.stop().await })?; let daemon = EmbeddedDaemon::start(cfg)?; self.daemon = Arc::new(daemon); diff --git a/gui/src/app/state/coins.rs b/gui/src/app/state/coins.rs index 999e667e..173c866c 100644 --- a/gui/src/app/state/coins.rs +++ b/gui/src/app/state/coins.rs @@ -164,6 +164,7 @@ impl State for CoinsPanel { async move { daemon1 .list_coins(&[CoinStatus::Unconfirmed, CoinStatus::Confirmed], &[]) + .await .map(|res| res.coins) .map_err(|e| e.into()) }, @@ -173,6 +174,7 @@ impl State for CoinsPanel { async move { let coins = daemon2 .list_coins(&[CoinStatus::Unconfirmed, CoinStatus::Confirmed], &[]) + .await .map(|res| res.coins) .map_err(Error::from)?; let mut targets = HashSet::::new(); @@ -181,7 +183,7 @@ impl State for CoinsPanel { targets.insert(LabelItem::Txid(coin.outpoint.txid)); targets.insert(LabelItem::Address(coin.address)); } - daemon2.get_labels(&targets).map_err(|e| e.into()) + daemon2.get_labels(&targets).await.map_err(|e| e.into()) }, Message::Labels, ), diff --git a/gui/src/app/state/label.rs b/gui/src/app/state/label.rs index faf4da46..0668f1d3 100644 --- a/gui/src/app/state/label.rs +++ b/gui/src/app/state/label.rs @@ -66,7 +66,7 @@ impl LabelsEdited { } return Ok(Command::perform( async move { - daemon.update_labels(&updated_labels)?; + daemon.update_labels(&updated_labels).await?; Ok(updated_labels_str) }, Message::LabelsUpdated, diff --git a/gui/src/app/state/mod.rs b/gui/src/app/state/mod.rs index 2a32f9d9..8033243e 100644 --- a/gui/src/app/state/mod.rs +++ b/gui/src/app/state/mod.rs @@ -230,8 +230,9 @@ impl State for Home { return Command::perform( async move { let mut limit = view::home::HISTORY_EVENT_PAGE_SIZE; - let mut events = - daemon.list_history_txs(0_u32, last_event_date, limit)?; + let mut events = daemon + .list_history_txs(0_u32, last_event_date, limit) + .await?; // because gethistory cursor is inclusive and use blocktime // multiple events can occur in the same block. @@ -253,7 +254,7 @@ impl State for Home { { // increments of the equivalent of one page more. limit += view::home::HISTORY_EVENT_PAGE_SIZE; - events = daemon.list_history_txs(0, last_event_date, limit)?; + events = daemon.list_history_txs(0, last_event_date, limit).await?; } Ok(events) }, @@ -284,13 +285,14 @@ impl State for Home { .unwrap(); Command::batch(vec![ Command::perform( - async move { daemon3.list_pending_txs().map_err(|e| e.into()) }, + async move { daemon3.list_pending_txs().await.map_err(|e| e.into()) }, Message::PendingTransactions, ), Command::perform( async move { daemon1 .list_history_txs(0, now, view::home::HISTORY_EVENT_PAGE_SIZE) + .await .map_err(|e| e.into()) }, Message::HistoryTransactions, @@ -299,6 +301,7 @@ impl State for Home { async move { daemon2 .list_coins(&[CoinStatus::Unconfirmed, CoinStatus::Confirmed], &[]) + .await .map(|res| res.coins) .map_err(|e| e.into()) }, diff --git a/gui/src/app/state/psbt.rs b/gui/src/app/state/psbt.rs index 4bc53d48..50363b31 100644 --- a/gui/src/app/state/psbt.rs +++ b/gui/src/app/state/psbt.rs @@ -173,6 +173,7 @@ impl PsbtState { async move { daemon .list_coins(&[CoinStatus::Spending], &outpoints) + .await .map(|res| { res.coins .iter() @@ -275,8 +276,8 @@ impl Action for SaveAction { } return Command::perform( async move { - daemon.update_spend_tx(&psbt)?; - daemon.update_labels(&labels).map_err(|e| e.into()) + daemon.update_spend_tx(&psbt).await?; + daemon.update_labels(&labels).await.map_err(|e| e.into()) }, Message::Updated, ); @@ -323,6 +324,7 @@ impl Action for BroadcastAction { async move { daemon .broadcast_spend_tx(&psbt.unsigned_tx.txid()) + .await .map_err(|e| e.into()) }, Message::Updated, @@ -375,6 +377,7 @@ impl Action for DeleteAction { async move { daemon .delete_spend_tx(&psbt.unsigned_tx.txid()) + .await .map_err(|e| e.into()) }, Message::Updated, @@ -482,7 +485,7 @@ impl Action for SignAction { merge_signatures(&mut tx.psbt, &psbt); if self.is_saved { return Command::perform( - async move { daemon.update_spend_tx(&psbt).map_err(|e| e.into()) }, + async move { daemon.update_spend_tx(&psbt).await.map_err(|e| e.into()) }, Message::Updated, ); // If the spend transaction was never saved before, then both the psbt and @@ -496,8 +499,8 @@ impl Action for SignAction { } return Command::perform( async move { - daemon.update_spend_tx(&psbt)?; - daemon.update_labels(&labels).map_err(|e| e.into()) + daemon.update_spend_tx(&psbt).await?; + daemon.update_labels(&labels).await.map_err(|e| e.into()) }, Message::Updated, ); @@ -727,7 +730,7 @@ impl Action for UpdateAction { self.error = None; let updated = Psbt::from_str(&self.updated.value).expect("Already checked"); return Command::perform( - async move { daemon.update_spend_tx(&updated).map_err(|e| e.into()) }, + async move { daemon.update_spend_tx(&updated).await.map_err(|e| e.into()) }, Message::Updated, ); } diff --git a/gui/src/app/state/psbts.rs b/gui/src/app/state/psbts.rs index b29565ba..9b7a54fe 100644 --- a/gui/src/app/state/psbts.rs +++ b/gui/src/app/state/psbts.rs @@ -128,7 +128,12 @@ impl State for PsbtsPanel { self.import_tx = None; let daemon = daemon.clone(); Command::perform( - async move { daemon.list_spend_transactions(None).map_err(|e| e.into()) }, + async move { + daemon + .list_spend_transactions(None) + .await + .map_err(|e| e.into()) + }, Message::SpendTxs, ) } @@ -194,7 +199,12 @@ impl ImportPsbtModal { self.error = None; let imported = Psbt::from_str(&self.imported.value).expect("Already checked"); return Command::perform( - async move { daemon.update_spend_tx(&imported).map_err(|e| e.into()) }, + async move { + daemon + .update_spend_tx(&imported) + .await + .map_err(|e| e.into()) + }, Message::Updated, ); } diff --git a/gui/src/app/state/receive.rs b/gui/src/app/state/receive.rs index 84b43b1c..4387937d 100644 --- a/gui/src/app/state/receive.rs +++ b/gui/src/app/state/receive.rs @@ -161,6 +161,7 @@ impl State for ReceivePanel { async move { daemon .get_new_address() + .await .map(|res| (res.address, res.derivation_index)) .map_err(|e| e.into()) }, @@ -200,6 +201,7 @@ impl State for ReceivePanel { async move { daemon .get_new_address() + .await .map(|res| (res.address, res.derivation_index)) .map_err(|e| e.into()) }, diff --git a/gui/src/app/state/recovery.rs b/gui/src/app/state/recovery.rs index 392ac180..09298aaf 100644 --- a/gui/src/app/state/recovery.rs +++ b/gui/src/app/state/recovery.rs @@ -157,14 +157,19 @@ impl State for RecoveryPanel { let network = cache.network; return Command::perform( async move { - let psbt = daemon.create_recovery(address, feerate_vb, sequence)?; + let psbt = daemon + .create_recovery(address, feerate_vb, sequence) + .await?; let outpoints: Vec<_> = psbt .unsigned_tx .input .iter() .map(|txin| txin.previous_output) .collect(); - let coins = daemon.list_coins(&[], &outpoints).map(|res| res.coins)?; + let coins = daemon + .list_coins(&[], &outpoints) + .await + .map(|res| res.coins)?; Ok(SpendTx::new( None, psbt, @@ -208,6 +213,7 @@ impl State for RecoveryPanel { async move { daemon .list_coins(&[CoinStatus::Unconfirmed, CoinStatus::Confirmed], &[]) + .await .map(|res| res.coins) .map_err(|e| e.into()) }, diff --git a/gui/src/app/state/settings/bitcoind.rs b/gui/src/app/state/settings/bitcoind.rs index 83b13b9b..caf683bb 100644 --- a/gui/src/app/state/settings/bitcoind.rs +++ b/gui/src/app/state/settings/bitcoind.rs @@ -420,7 +420,9 @@ impl RescanSetting { info!("Asking deamon to rescan with timestamp: {}", t); return Command::perform( 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()) + daemon.start_rescan(t.try_into().expect("t cannot be inferior to 0 otherwise genesis block timestamp is chosen")) + .await + .map_err(|e| e.into()) }, Message::StartRescan, ); diff --git a/gui/src/app/state/settings/mod.rs b/gui/src/app/state/settings/mod.rs index 204dc087..ff325e27 100644 --- a/gui/src/app/state/settings/mod.rs +++ b/gui/src/app/state/settings/mod.rs @@ -164,7 +164,7 @@ impl State for AboutSettingsState { _wallet: Arc, ) -> Command { Command::perform( - async move { daemon.get_info().map_err(|e| e.into()) }, + async move { daemon.get_info().await.map_err(|e| e.into()) }, Message::Info, ) } diff --git a/gui/src/app/state/settings/wallet.rs b/gui/src/app/state/settings/wallet.rs index fa776508..f736267c 100644 --- a/gui/src/app/state/settings/wallet.rs +++ b/gui/src/app/state/settings/wallet.rs @@ -189,7 +189,7 @@ impl State for WalletSettingsState { self.keys_aliases = Self::keys_aliases(&wallet); self.wallet = wallet; Command::perform( - async move { daemon.get_info().map_err(|e| e.into()) }, + async move { daemon.get_info().await.map_err(|e| e.into()) }, Message::Info, ) } diff --git a/gui/src/app/state/spend/mod.rs b/gui/src/app/state/spend/mod.rs index 60e8d933..0c8aa579 100644 --- a/gui/src/app/state/spend/mod.rs +++ b/gui/src/app/state/spend/mod.rs @@ -127,6 +127,7 @@ impl State for CreateSpendPanel { async move { daemon1 .list_coins(&[CoinStatus::Unconfirmed, CoinStatus::Confirmed], &[]) + .await .map(|res| res.coins) .map_err(|e| e.into()) }, @@ -136,6 +137,7 @@ impl State for CreateSpendPanel { async move { let coins = daemon .list_coins(&[CoinStatus::Unconfirmed, CoinStatus::Confirmed], &[]) + .await .map(|res| res.coins) .map_err(Error::from)?; let mut targets = HashSet::::new(); @@ -143,7 +145,7 @@ impl State for CreateSpendPanel { targets.insert(LabelItem::OutPoint(coin.outpoint)); targets.insert(LabelItem::Txid(coin.outpoint.txid)); } - daemon2.get_labels(&targets).map_err(|e| e.into()) + daemon2.get_labels(&targets).await.map_err(|e| e.into()) }, Message::Labels, ), diff --git a/gui/src/app/state/spend/step.rs b/gui/src/app/state/spend/step.rs index 0cd1f2bd..4453b54f 100644 --- a/gui/src/app/state/spend/step.rs +++ b/gui/src/app/state/spend/step.rs @@ -307,13 +307,16 @@ impl DefineSpend { }; let feerate_vb = self.feerate.value.parse::().expect("Checked before"); - - match daemon.create_spend_tx( - &outpoints, - &destinations, - feerate_vb, - Some(change_address.clone()), - ) { + match tokio::runtime::Handle::current().block_on(async { + daemon + .create_spend_tx( + &outpoints, + &destinations, + feerate_vb, + Some(change_address.clone()), + ) + .await + }) { Ok(CreateSpendResult::Success { psbt, .. }) => { self.warning = None; if !self.is_user_coin_selection { @@ -483,6 +486,7 @@ impl Step for DefineSpend { async move { daemon .create_spend_tx(&inputs, &outputs, feerate_vb, None) + .await .map_err(|e| e.into()) .and_then(|res| match res { CreateSpendResult::Success { psbt, warnings } => { diff --git a/gui/src/app/state/transactions.rs b/gui/src/app/state/transactions.rs index d88d5338..23ad26ec 100644 --- a/gui/src/app/state/transactions.rs +++ b/gui/src/app/state/transactions.rs @@ -160,6 +160,7 @@ impl State for TransactionsPanel { async move { daemon .list_coins(&[CoinStatus::Spending], &outpoints) + .await .map(|res| { res.coins .iter() @@ -202,7 +203,8 @@ impl State for TransactionsPanel { return Command::perform( async move { let mut limit = view::home::HISTORY_EVENT_PAGE_SIZE; - let mut txs = daemon.list_history_txs(0_u32, last_tx_date, limit)?; + let mut txs = + daemon.list_history_txs(0_u32, last_tx_date, limit).await?; // because gethistory cursor is inclusive and use blocktime // multiple txs can occur in the same block. @@ -224,7 +226,7 @@ impl State for TransactionsPanel { { // increments of the equivalent of one page more. limit += view::home::HISTORY_EVENT_PAGE_SIZE; - txs = daemon.list_history_txs(0, last_tx_date, limit)?; + txs = daemon.list_history_txs(0, last_tx_date, limit).await?; } Ok(txs) }, @@ -257,13 +259,14 @@ impl State for TransactionsPanel { .unwrap(); Command::batch(vec![ Command::perform( - async move { daemon2.list_pending_txs().map_err(|e| e.into()) }, + async move { daemon2.list_pending_txs().await.map_err(|e| e.into()) }, Message::PendingTransactions, ), Command::perform( async move { daemon1 .list_history_txs(0, now, view::home::HISTORY_EVENT_PAGE_SIZE) + .await .map_err(|e| e.into()) }, Message::HistoryTransactions, @@ -405,7 +408,10 @@ async fn rbf( feerate_vb: Option, ) -> Result { let previous_txid = previous_tx.tx.txid(); - let psbt = match daemon.rbf_psbt(&previous_txid, is_cancel, feerate_vb)? { + let psbt = match daemon + .rbf_psbt(&previous_txid, is_cancel, feerate_vb) + .await? + { CreateSpendResult::Success { psbt, .. } => psbt, CreateSpendResult::InsufficientFunds { missing } => { return Err( @@ -445,9 +451,9 @@ async fn rbf( } } - daemon.update_labels(&labels)?; + daemon.update_labels(&labels).await?; } - daemon.update_spend_tx(&psbt)?; + daemon.update_spend_tx(&psbt).await?; Ok(psbt.unsigned_tx.txid()) } diff --git a/gui/src/daemon/client/mod.rs b/gui/src/daemon/client/mod.rs index 7f354a28..00cc8227 100644 --- a/gui/src/daemon/client/mod.rs +++ b/gui/src/daemon/client/mod.rs @@ -2,6 +2,7 @@ use std::collections::{HashMap, HashSet}; use std::fmt::Debug; use std::iter::FromIterator; +use async_trait::async_trait; use liana::commands::{CoinStatus, CreateRecoveryResult}; use serde::de::DeserializeOwned; use serde::{Deserialize, Serialize}; @@ -52,7 +53,8 @@ impl Lianad { } } -impl Daemon for Lianad { +#[async_trait] +impl Daemon for Lianad { fn is_external(&self) -> bool { true } @@ -61,23 +63,25 @@ impl Daemon for Lianad { None } - fn is_alive(&self) -> Result<(), DaemonError> { + async fn is_alive(&self) -> Result<(), DaemonError> { Ok(()) } - fn stop(&self) -> Result<(), DaemonError> { - unreachable!("GUI should not ask external client to stop") + async fn stop(&self) -> Result<(), DaemonError> { + Err(DaemonError::Unexpected( + "GUI should not ask external client to stop".to_string(), + )) } - fn get_info(&self) -> Result { + async fn get_info(&self) -> Result { self.call("getinfo", Option::::None) } - fn get_new_address(&self) -> Result { + async fn get_new_address(&self) -> Result { self.call("getnewaddress", Option::::None) } - fn list_coins( + async fn list_coins( &self, statuses: &[CoinStatus], outpoints: &[OutPoint], @@ -91,11 +95,11 @@ impl Daemon for Lianad { ) } - fn list_spend_txs(&self) -> Result { + async fn list_spend_txs(&self) -> Result { self.call("listspendtxs", Option::::None) } - fn create_spend_tx( + async fn create_spend_tx( &self, coins_outpoints: &[OutPoint], destinations: &HashMap, u64>, @@ -113,7 +117,7 @@ impl Daemon for Lianad { self.call("createspend", Some(input)) } - fn rbf_psbt( + async fn rbf_psbt( &self, txid: &Txid, is_cancel: bool, @@ -126,30 +130,30 @@ impl Daemon for Lianad { self.call("rbfpsbt", Some(input)) } - fn update_spend_tx(&self, psbt: &Psbt) -> Result<(), DaemonError> { + async fn update_spend_tx(&self, psbt: &Psbt) -> Result<(), DaemonError> { let spend_tx = psbt.to_string(); let _res: serde_json::value::Value = self.call("updatespend", Some(vec![spend_tx]))?; Ok(()) } - fn delete_spend_tx(&self, txid: &Txid) -> Result<(), DaemonError> { + async fn delete_spend_tx(&self, txid: &Txid) -> Result<(), DaemonError> { let _res: serde_json::value::Value = self.call("delspendtx", Some(vec![txid.to_string()]))?; Ok(()) } - fn broadcast_spend_tx(&self, txid: &Txid) -> Result<(), DaemonError> { + async fn broadcast_spend_tx(&self, txid: &Txid) -> Result<(), DaemonError> { let _res: serde_json::value::Value = self.call("broadcastspend", Some(vec![txid.to_string()]))?; Ok(()) } - fn start_rescan(&self, t: u32) -> Result<(), DaemonError> { + async fn start_rescan(&self, t: u32) -> Result<(), DaemonError> { let _res: serde_json::value::Value = self.call("startrescan", Some(vec![t]))?; Ok(()) } - fn list_confirmed_txs( + async fn list_confirmed_txs( &self, start: u32, end: u32, @@ -161,11 +165,11 @@ impl Daemon for Lianad { ) } - fn list_txs(&self, txids: &[Txid]) -> Result { + async fn list_txs(&self, txids: &[Txid]) -> Result { self.call("listtransactions", Some(vec![txids])) } - fn create_recovery( + async fn create_recovery( &self, address: Address, feerate_vb: u64, @@ -178,7 +182,7 @@ impl Daemon for Lianad { Ok(res.psbt) } - fn get_labels( + async fn get_labels( &self, items: &HashSet, ) -> Result, DaemonError> { @@ -187,7 +191,10 @@ impl Daemon for Lianad { Ok(res.labels) } - fn update_labels(&self, items: &HashMap>) -> Result<(), DaemonError> { + async fn update_labels( + &self, + items: &HashMap>, + ) -> Result<(), DaemonError> { let labels: HashMap> = HashMap::from_iter(items.iter().map(|(a, l)| (a.to_string(), l.clone()))); let _res: serde_json::value::Value = self.call("updatelabels", Some(vec![labels]))?; diff --git a/gui/src/daemon/embedded.rs b/gui/src/daemon/embedded.rs index ec4942f7..98e63037 100644 --- a/gui/src/daemon/embedded.rs +++ b/gui/src/daemon/embedded.rs @@ -1,7 +1,8 @@ use std::collections::{HashMap, HashSet}; -use std::sync::Mutex; +use tokio::sync::Mutex; use super::{model::*, Daemon, DaemonError}; +use async_trait::async_trait; use liana::{ commands::{CoinStatus, LabelItem}, config::Config, @@ -23,11 +24,11 @@ impl EmbeddedDaemon { }) } - pub fn command(&self, method: F) -> Result + pub async fn command(&self, method: F) -> Result where F: FnOnce(&DaemonControl) -> Result, { - match self.handle.lock()?.as_ref() { + match self.handle.lock().await.as_ref() { Some(DaemonHandle::Controller { control, .. }) => method(control), None => Err(DaemonError::DaemonStopped), } @@ -46,6 +47,7 @@ impl std::fmt::Debug for EmbeddedDaemon { } } +#[async_trait] impl Daemon for EmbeddedDaemon { fn is_external(&self) -> bool { false @@ -55,8 +57,8 @@ impl Daemon for EmbeddedDaemon { Some(&self.config) } - fn is_alive(&self) -> Result<(), DaemonError> { - let mut handle = self.handle.lock()?; + async fn is_alive(&self) -> Result<(), DaemonError> { + let mut handle = self.handle.lock().await; if let Some(h) = handle.as_ref() { if h.is_alive() { return Ok(()); @@ -70,8 +72,8 @@ impl Daemon for EmbeddedDaemon { Ok(()) } - fn stop(&self) -> Result<(), DaemonError> { - let mut handle = self.handle.lock()?; + async fn stop(&self) -> Result<(), DaemonError> { + let mut handle = self.handle.lock().await; if let Some(h) = handle.take() { h.stop() .map_err(|e| DaemonError::Unexpected(e.to_string()))?; @@ -79,44 +81,48 @@ impl Daemon for EmbeddedDaemon { Ok(()) } - fn get_info(&self) -> Result { - self.command(|daemon| Ok(daemon.get_info())) + async fn get_info(&self) -> Result { + self.command(|daemon| Ok(daemon.get_info())).await } - fn get_new_address(&self) -> Result { - self.command(|daemon| Ok(daemon.get_new_address())) + async fn get_new_address(&self) -> Result { + self.command(|daemon| Ok(daemon.get_new_address())).await } - fn list_coins( + async fn list_coins( &self, statuses: &[CoinStatus], outpoints: &[OutPoint], ) -> Result { self.command(|daemon| Ok(daemon.list_coins(statuses, outpoints))) + .await } - fn list_spend_txs(&self) -> Result { + async fn list_spend_txs(&self) -> Result { self.command(|daemon| { daemon .list_spend(None) .map_err(|e| DaemonError::Unexpected(e.to_string())) }) + .await } - fn list_confirmed_txs( + async fn list_confirmed_txs( &self, start: u32, end: u32, limit: u64, ) -> Result { self.command(|daemon| Ok(daemon.list_confirmed_transactions(start, end, limit))) + .await } - fn list_txs(&self, txids: &[Txid]) -> Result { + async fn list_txs(&self, txids: &[Txid]) -> Result { self.command(|daemon| Ok(daemon.list_transactions(txids))) + .await } - fn create_spend_tx( + async fn create_spend_tx( &self, coins_outpoints: &[OutPoint], destinations: &HashMap, u64>, @@ -128,9 +134,10 @@ impl Daemon for EmbeddedDaemon { .create_spend(destinations, coins_outpoints, feerate_vb, change_address) .map_err(|e| DaemonError::Unexpected(e.to_string())) }) + .await } - fn rbf_psbt( + async fn rbf_psbt( &self, txid: &Txid, is_cancel: bool, @@ -141,40 +148,45 @@ impl Daemon for EmbeddedDaemon { .rbf_psbt(txid, is_cancel, feerate_vb) .map_err(|e| DaemonError::Unexpected(e.to_string())) }) + .await } - fn update_spend_tx(&self, psbt: &Psbt) -> Result<(), DaemonError> { + async fn update_spend_tx(&self, psbt: &Psbt) -> Result<(), DaemonError> { self.command(|daemon| { daemon .update_spend(psbt.clone()) .map_err(|e| DaemonError::Unexpected(e.to_string())) }) + .await } - fn delete_spend_tx(&self, txid: &Txid) -> Result<(), DaemonError> { + async fn delete_spend_tx(&self, txid: &Txid) -> Result<(), DaemonError> { self.command(|daemon| { daemon.delete_spend(txid); Ok(()) }) + .await } - fn broadcast_spend_tx(&self, txid: &Txid) -> Result<(), DaemonError> { + async fn broadcast_spend_tx(&self, txid: &Txid) -> Result<(), DaemonError> { self.command(|daemon| { daemon .broadcast_spend(txid) .map_err(|e| DaemonError::Unexpected(e.to_string())) }) + .await } - fn start_rescan(&self, t: u32) -> Result<(), DaemonError> { + async fn start_rescan(&self, t: u32) -> Result<(), DaemonError> { self.command(|daemon| { daemon .start_rescan(t) .map_err(|e| DaemonError::Unexpected(e.to_string())) }) + .await } - fn create_recovery( + async fn create_recovery( &self, address: Address, feerate_vb: u64, @@ -186,19 +198,25 @@ impl Daemon for EmbeddedDaemon { .map(|res| res.psbt) .map_err(|e| DaemonError::Unexpected(e.to_string())) }) + .await } - fn get_labels( + async fn get_labels( &self, items: &HashSet, ) -> Result, DaemonError> { self.command(|daemon| Ok(daemon.get_labels(items).labels)) + .await } - fn update_labels(&self, items: &HashMap>) -> Result<(), DaemonError> { + async fn update_labels( + &self, + items: &HashMap>, + ) -> Result<(), DaemonError> { self.command(|daemon| { daemon.update_labels(items); Ok(()) }) + .await } } diff --git a/gui/src/daemon/mod.rs b/gui/src/daemon/mod.rs index 477e68b3..0a462a6c 100644 --- a/gui/src/daemon/mod.rs +++ b/gui/src/daemon/mod.rs @@ -8,6 +8,8 @@ use std::fmt::Debug; use std::io::ErrorKind; use std::iter::FromIterator; +use async_trait::async_trait; + use liana::{ commands::{CoinStatus, LabelItem, TransactionInfo}, config::Config, @@ -50,67 +52,70 @@ impl std::fmt::Display for DaemonError { } } +#[async_trait] pub trait Daemon: Debug { fn is_external(&self) -> bool; fn config(&self) -> Option<&Config>; - fn is_alive(&self) -> Result<(), DaemonError>; - fn stop(&self) -> Result<(), DaemonError>; - fn get_info(&self) -> Result; - fn get_new_address(&self) -> Result; - fn list_coins( + async fn is_alive(&self) -> Result<(), DaemonError>; + async fn stop(&self) -> Result<(), DaemonError>; + async fn get_info(&self) -> Result; + async fn get_new_address(&self) -> Result; + async fn list_coins( &self, statuses: &[CoinStatus], outpoints: &[OutPoint], ) -> Result; - fn list_spend_txs(&self) -> Result; - fn create_spend_tx( + async fn list_spend_txs(&self) -> Result; + async fn create_spend_tx( &self, coins_outpoints: &[OutPoint], destinations: &HashMap, u64>, feerate_vb: u64, change_address: Option>, ) -> Result; - fn rbf_psbt( + async fn rbf_psbt( &self, txid: &Txid, is_cancel: bool, feerate_vb: Option, ) -> Result; - fn update_spend_tx(&self, psbt: &Psbt) -> Result<(), DaemonError>; - fn delete_spend_tx(&self, txid: &Txid) -> Result<(), DaemonError>; - fn broadcast_spend_tx(&self, txid: &Txid) -> Result<(), DaemonError>; - fn start_rescan(&self, t: u32) -> Result<(), DaemonError>; - fn list_confirmed_txs( + async fn update_spend_tx(&self, psbt: &Psbt) -> Result<(), DaemonError>; + async fn delete_spend_tx(&self, txid: &Txid) -> Result<(), DaemonError>; + async fn broadcast_spend_tx(&self, txid: &Txid) -> Result<(), DaemonError>; + async fn start_rescan(&self, t: u32) -> Result<(), DaemonError>; + async fn list_confirmed_txs( &self, _start: u32, _end: u32, _limit: u64, ) -> Result; - fn create_recovery( + async fn create_recovery( &self, address: Address, feerate_vb: u64, sequence: Option, ) -> Result; - fn list_txs(&self, txid: &[Txid]) -> Result; - fn get_labels( + async fn list_txs(&self, txid: &[Txid]) -> Result; + async fn get_labels( &self, labels: &HashSet, ) -> Result, DaemonError>; - fn update_labels(&self, labels: &HashMap>) - -> Result<(), DaemonError>; + async fn update_labels( + &self, + labels: &HashMap>, + ) -> Result<(), DaemonError>; // List spend transactions, optionally filtered to the specified `txids`. // Set `txids` to `None` for no filter (passing an empty slice returns no transactions). - fn list_spend_transactions( + async fn list_spend_transactions( &self, txids: Option<&[Txid]>, ) -> Result, DaemonError> { - let info = self.get_info()?; + let info = self.get_info().await?; let mut spend_txs = Vec::new(); let curve = secp256k1::Secp256k1::verification_only(); // TODO: Use filters in `list_spend_txs` command. - let mut txs = self.list_spend_txs()?.spend_txs; + let mut txs = self.list_spend_txs().await?.spend_txs; if let Some(txids) = txids { txs.retain(|tx| txids.contains(&tx.psbt.unsigned_tx.txid())); } @@ -125,7 +130,7 @@ pub trait Daemon: Debug { .collect::>() }) .collect(); - let coins = self.list_coins(&[], &outpoints)?.coins; + let coins = self.list_coins(&[], &outpoints).await?.coins; for tx in txs { let coins = coins .iter() @@ -148,7 +153,7 @@ pub trait Daemon: Debug { info.network, )); } - load_labels(self, &mut spend_txs)?; + load_labels(self, &mut spend_txs).await?; spend_txs.sort_by(|a, b| { if a.status == b.status { // last updated first @@ -161,11 +166,11 @@ pub trait Daemon: Debug { Ok(spend_txs) } - fn txs_to_historytxs( + async fn txs_to_historytxs( &self, txs: Vec, ) -> Result, DaemonError> { - let info = self.get_info()?; + let info = self.get_info().await?; let outpoints: Vec<_> = txs .iter() .flat_map(|tx| { @@ -184,7 +189,7 @@ pub trait Daemon: Debug { .iter() .cloned() .collect(); - let coins = self.list_coins(&[], &outpoints)?.coins; + let coins = self.list_coins(&[], &outpoints).await?.coins; let mut txs = txs .into_iter() .map(|tx| { @@ -212,34 +217,38 @@ pub trait Daemon: Debug { ) }) .collect(); - load_labels(self, &mut txs)?; + load_labels(self, &mut txs).await?; Ok(txs) } - fn list_history_txs( + async fn list_history_txs( &self, start: u32, end: u32, limit: u64, ) -> Result, DaemonError> { - let txs = self.list_confirmed_txs(start, end, limit)?.transactions; - self.txs_to_historytxs(txs) + let txs = self + .list_confirmed_txs(start, end, limit) + .await? + .transactions; + self.txs_to_historytxs(txs).await } - fn get_history_txs( + async fn get_history_txs( &self, txids: &[Txid], ) -> Result, DaemonError> { - let txs = self.list_txs(txids)?.transactions; - self.txs_to_historytxs(txs) + let txs = self.list_txs(txids).await?.transactions; + self.txs_to_historytxs(txs).await } - fn list_pending_txs(&self) -> Result, DaemonError> { - let info = self.get_info()?; + async fn list_pending_txs(&self) -> Result, DaemonError> { + let info = self.get_info().await?; // We want coins that are inputs to and/or outputs of a pending tx, // which can only be unconfirmed and spending coins. let coins = self - .list_coins(&[CoinStatus::Unconfirmed, CoinStatus::Spending], &[])? + .list_coins(&[CoinStatus::Unconfirmed, CoinStatus::Spending], &[]) + .await? .coins; let mut txids: Vec = Vec::new(); for coin in &coins { @@ -254,7 +263,7 @@ pub trait Daemon: Debug { } } - let txs = self.list_txs(&txids)?.transactions; + let txs = self.list_txs(&txids).await?.transactions; let mut txs = txs .into_iter() .map(|tx| { @@ -283,12 +292,12 @@ pub trait Daemon: Debug { }) .collect(); - load_labels(self, &mut txs)?; + load_labels(self, &mut txs).await?; Ok(txs) } } -fn load_labels( +async fn load_labels( daemon: &D, targets: &mut Vec, ) -> Result<(), DaemonError> { @@ -303,7 +312,8 @@ fn load_labels( } let labels = HashMap::from_iter( daemon - .get_labels(&items)? + .get_labels(&items) + .await? .into_iter() .map(|(k, v)| (k, Some(v))), ); diff --git a/gui/src/loader.rs b/gui/src/loader.rs index 84cd19ee..8efcd107 100644 --- a/gui/src/loader.rs +++ b/gui/src/loader.rs @@ -6,6 +6,7 @@ use std::sync::Arc; use std::time::Duration; use iced::{Alignment, Command, Length, Subscription}; +use tokio::runtime::Handle; use tracing::{debug, info, warn}; use liana::{ @@ -229,7 +230,7 @@ impl Loader { if let Step::Syncing { daemon, .. } = &mut self.step { if !daemon.is_external() { info!("Stopping internal daemon..."); - if let Err(e) = daemon.stop() { + if let Err(e) = Handle::current().block_on(async { daemon.stop().await }) { warn!("Internal daemon failed to stop: {}", e); } else { info!("Internal daemon stopped"); @@ -373,6 +374,7 @@ pub async fn load_application( let coins = daemon .list_coins(&[CoinStatus::Unconfirmed, CoinStatus::Confirmed], &[]) + .await .map(|res| res.coins)?; let cache = Cache { @@ -484,7 +486,7 @@ async fn connect(socket_path: PathBuf) -> Result, let daemon = Lianad::new(client); debug!("Searching for external daemon"); - daemon.get_info()?; + daemon.get_info().await?; info!("Connected to external daemon"); Ok(Arc::new(daemon)) @@ -532,7 +534,7 @@ async fn sync( if sleep { std::thread::sleep(std::time::Duration::from_secs(1)); } - daemon.get_info() + daemon.get_info().await } #[allow(clippy::large_enum_variant)]