Use async-trait for daemon

This commit is contained in:
edouardparis 2024-04-18 18:57:58 +02:00
parent f9ea8ec0b6
commit 7b70c8434a
20 changed files with 198 additions and 127 deletions

1
gui/Cargo.lock generated
View File

@ -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",

View File

@ -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" }

View File

@ -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<Message> {
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);

View File

@ -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::<LabelItem>::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,
),

View File

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

View File

@ -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())
},

View File

@ -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,
);
}

View File

@ -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,
);
}

View File

@ -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())
},

View File

@ -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())
},

View File

@ -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,
);

View File

@ -164,7 +164,7 @@ impl State for AboutSettingsState {
_wallet: Arc<Wallet>,
) -> Command<Message> {
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,
)
}

View File

@ -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,
)
}

View File

@ -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::<LabelItem>::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,
),

View File

@ -307,13 +307,16 @@ impl DefineSpend {
};
let feerate_vb = self.feerate.value.parse::<u64>().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 } => {

View File

@ -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<u64>,
) -> Result<Txid, Error> {
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())
}

View File

@ -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<C: Client> Lianad<C> {
}
}
impl<C: Client + Debug> Daemon for Lianad<C> {
#[async_trait]
impl<C: Client + Send + Sync + Debug> Daemon for Lianad<C> {
fn is_external(&self) -> bool {
true
}
@ -61,23 +63,25 @@ impl<C: Client + Debug> Daemon for Lianad<C> {
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<GetInfoResult, DaemonError> {
async fn get_info(&self) -> Result<GetInfoResult, DaemonError> {
self.call("getinfo", Option::<Request>::None)
}
fn get_new_address(&self) -> Result<GetAddressResult, DaemonError> {
async fn get_new_address(&self) -> Result<GetAddressResult, DaemonError> {
self.call("getnewaddress", Option::<Request>::None)
}
fn list_coins(
async fn list_coins(
&self,
statuses: &[CoinStatus],
outpoints: &[OutPoint],
@ -91,11 +95,11 @@ impl<C: Client + Debug> Daemon for Lianad<C> {
)
}
fn list_spend_txs(&self) -> Result<ListSpendResult, DaemonError> {
async fn list_spend_txs(&self) -> Result<ListSpendResult, DaemonError> {
self.call("listspendtxs", Option::<Request>::None)
}
fn create_spend_tx(
async fn create_spend_tx(
&self,
coins_outpoints: &[OutPoint],
destinations: &HashMap<Address<address::NetworkUnchecked>, u64>,
@ -113,7 +117,7 @@ impl<C: Client + Debug> Daemon for Lianad<C> {
self.call("createspend", Some(input))
}
fn rbf_psbt(
async fn rbf_psbt(
&self,
txid: &Txid,
is_cancel: bool,
@ -126,30 +130,30 @@ impl<C: Client + Debug> Daemon for Lianad<C> {
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<C: Client + Debug> Daemon for Lianad<C> {
)
}
fn list_txs(&self, txids: &[Txid]) -> Result<ListTransactionsResult, DaemonError> {
async fn list_txs(&self, txids: &[Txid]) -> Result<ListTransactionsResult, DaemonError> {
self.call("listtransactions", Some(vec![txids]))
}
fn create_recovery(
async fn create_recovery(
&self,
address: Address<address::NetworkUnchecked>,
feerate_vb: u64,
@ -178,7 +182,7 @@ impl<C: Client + Debug> Daemon for Lianad<C> {
Ok(res.psbt)
}
fn get_labels(
async fn get_labels(
&self,
items: &HashSet<LabelItem>,
) -> Result<HashMap<String, String>, DaemonError> {
@ -187,7 +191,10 @@ impl<C: Client + Debug> Daemon for Lianad<C> {
Ok(res.labels)
}
fn update_labels(&self, items: &HashMap<LabelItem, Option<String>>) -> Result<(), DaemonError> {
async fn update_labels(
&self,
items: &HashMap<LabelItem, Option<String>>,
) -> Result<(), DaemonError> {
let labels: HashMap<String, Option<String>> =
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]))?;

View File

@ -2,6 +2,7 @@ use std::collections::{HashMap, HashSet};
use std::sync::Mutex;
use super::{model::*, Daemon, DaemonError};
use async_trait::async_trait;
use liana::{
commands::{CoinStatus, LabelItem},
config::Config,
@ -46,6 +47,7 @@ impl std::fmt::Debug for EmbeddedDaemon {
}
}
#[async_trait]
impl Daemon for EmbeddedDaemon {
fn is_external(&self) -> bool {
false
@ -55,7 +57,7 @@ impl Daemon for EmbeddedDaemon {
Some(&self.config)
}
fn is_alive(&self) -> Result<(), DaemonError> {
async fn is_alive(&self) -> Result<(), DaemonError> {
let mut handle = self.handle.lock()?;
if let Some(h) = handle.as_ref() {
if h.is_alive() {
@ -70,7 +72,7 @@ impl Daemon for EmbeddedDaemon {
Ok(())
}
fn stop(&self) -> Result<(), DaemonError> {
async fn stop(&self) -> Result<(), DaemonError> {
let mut handle = self.handle.lock()?;
if let Some(h) = handle.take() {
h.stop()
@ -79,15 +81,15 @@ impl Daemon for EmbeddedDaemon {
Ok(())
}
fn get_info(&self) -> Result<GetInfoResult, DaemonError> {
async fn get_info(&self) -> Result<GetInfoResult, DaemonError> {
self.command(|daemon| Ok(daemon.get_info()))
}
fn get_new_address(&self) -> Result<GetAddressResult, DaemonError> {
async fn get_new_address(&self) -> Result<GetAddressResult, DaemonError> {
self.command(|daemon| Ok(daemon.get_new_address()))
}
fn list_coins(
async fn list_coins(
&self,
statuses: &[CoinStatus],
outpoints: &[OutPoint],
@ -95,7 +97,7 @@ impl Daemon for EmbeddedDaemon {
self.command(|daemon| Ok(daemon.list_coins(statuses, outpoints)))
}
fn list_spend_txs(&self) -> Result<ListSpendResult, DaemonError> {
async fn list_spend_txs(&self) -> Result<ListSpendResult, DaemonError> {
self.command(|daemon| {
daemon
.list_spend(None)
@ -103,7 +105,7 @@ impl Daemon for EmbeddedDaemon {
})
}
fn list_confirmed_txs(
async fn list_confirmed_txs(
&self,
start: u32,
end: u32,
@ -112,11 +114,11 @@ impl Daemon for EmbeddedDaemon {
self.command(|daemon| Ok(daemon.list_confirmed_transactions(start, end, limit)))
}
fn list_txs(&self, txids: &[Txid]) -> Result<ListTransactionsResult, DaemonError> {
async fn list_txs(&self, txids: &[Txid]) -> Result<ListTransactionsResult, DaemonError> {
self.command(|daemon| Ok(daemon.list_transactions(txids)))
}
fn create_spend_tx(
async fn create_spend_tx(
&self,
coins_outpoints: &[OutPoint],
destinations: &HashMap<Address<address::NetworkUnchecked>, u64>,
@ -130,7 +132,7 @@ impl Daemon for EmbeddedDaemon {
})
}
fn rbf_psbt(
async fn rbf_psbt(
&self,
txid: &Txid,
is_cancel: bool,
@ -143,7 +145,7 @@ impl Daemon for EmbeddedDaemon {
})
}
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())
@ -151,14 +153,14 @@ impl Daemon for EmbeddedDaemon {
})
}
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(())
})
}
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)
@ -166,7 +168,7 @@ impl Daemon for EmbeddedDaemon {
})
}
fn start_rescan(&self, t: u32) -> Result<(), DaemonError> {
async fn start_rescan(&self, t: u32) -> Result<(), DaemonError> {
self.command(|daemon| {
daemon
.start_rescan(t)
@ -174,7 +176,7 @@ impl Daemon for EmbeddedDaemon {
})
}
fn create_recovery(
async fn create_recovery(
&self,
address: Address<address::NetworkUnchecked>,
feerate_vb: u64,
@ -188,14 +190,17 @@ impl Daemon for EmbeddedDaemon {
})
}
fn get_labels(
async fn get_labels(
&self,
items: &HashSet<LabelItem>,
) -> Result<HashMap<String, String>, DaemonError> {
self.command(|daemon| Ok(daemon.get_labels(items).labels))
}
fn update_labels(&self, items: &HashMap<LabelItem, Option<String>>) -> Result<(), DaemonError> {
async fn update_labels(
&self,
items: &HashMap<LabelItem, Option<String>>,
) -> Result<(), DaemonError> {
self.command(|daemon| {
daemon.update_labels(items);
Ok(())

View File

@ -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<model::GetInfoResult, DaemonError>;
fn get_new_address(&self) -> Result<model::GetAddressResult, DaemonError>;
fn list_coins(
async fn is_alive(&self) -> Result<(), DaemonError>;
async fn stop(&self) -> Result<(), DaemonError>;
async fn get_info(&self) -> Result<model::GetInfoResult, DaemonError>;
async fn get_new_address(&self) -> Result<model::GetAddressResult, DaemonError>;
async fn list_coins(
&self,
statuses: &[CoinStatus],
outpoints: &[OutPoint],
) -> Result<model::ListCoinsResult, DaemonError>;
fn list_spend_txs(&self) -> Result<model::ListSpendResult, DaemonError>;
fn create_spend_tx(
async fn list_spend_txs(&self) -> Result<model::ListSpendResult, DaemonError>;
async fn create_spend_tx(
&self,
coins_outpoints: &[OutPoint],
destinations: &HashMap<Address<address::NetworkUnchecked>, u64>,
feerate_vb: u64,
change_address: Option<Address<address::NetworkUnchecked>>,
) -> Result<model::CreateSpendResult, DaemonError>;
fn rbf_psbt(
async fn rbf_psbt(
&self,
txid: &Txid,
is_cancel: bool,
feerate_vb: Option<u64>,
) -> Result<model::CreateSpendResult, DaemonError>;
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<model::ListTransactionsResult, DaemonError>;
fn create_recovery(
async fn create_recovery(
&self,
address: Address<address::NetworkUnchecked>,
feerate_vb: u64,
sequence: Option<u16>,
) -> Result<Psbt, DaemonError>;
fn list_txs(&self, txid: &[Txid]) -> Result<model::ListTransactionsResult, DaemonError>;
fn get_labels(
async fn list_txs(&self, txid: &[Txid]) -> Result<model::ListTransactionsResult, DaemonError>;
async fn get_labels(
&self,
labels: &HashSet<LabelItem>,
) -> Result<HashMap<String, String>, DaemonError>;
fn update_labels(&self, labels: &HashMap<LabelItem, Option<String>>)
-> Result<(), DaemonError>;
async fn update_labels(
&self,
labels: &HashMap<LabelItem, Option<String>>,
) -> 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<Vec<model::SpendTx>, 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::<Vec<_>>()
})
.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<TransactionInfo>,
) -> Result<Vec<model::HistoryTransaction>, 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<Vec<model::HistoryTransaction>, 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<Vec<model::HistoryTransaction>, 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<Vec<model::HistoryTransaction>, DaemonError> {
let info = self.get_info()?;
async fn list_pending_txs(&self) -> Result<Vec<model::HistoryTransaction>, 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<Txid> = 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<T: model::Labelled, D: Daemon + ?Sized>(
async fn load_labels<T: model::Labelled, D: Daemon + ?Sized>(
daemon: &D,
targets: &mut Vec<T>,
) -> Result<(), DaemonError> {
@ -303,7 +312,8 @@ fn load_labels<T: model::Labelled, D: Daemon + ?Sized>(
}
let labels = HashMap::from_iter(
daemon
.get_labels(&items)?
.get_labels(&items)
.await?
.into_iter()
.map(|(k, v)| (k, Some(v))),
);

View File

@ -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<Arc<dyn Daemon + Sync + Send>,
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)]