From 0628b6d1eaa02cd043cde5e5f376de076f3e4838 Mon Sep 17 00:00:00 2001 From: Elias Rohrer Date: Mon, 8 Jun 2026 17:06:44 +0200 Subject: [PATCH 1/5] Expose async APIs for store-backed persistence Avoid nested runtime waits when peer, payment, and node metrics operations persist through async storage. This lets callers already in async contexts await those operations directly while keeping existing sync boundaries explicit. Co-Authored-By: HAL 9000 --- bindings/ldk_node.udl | 34 +- src/builder.rs | 35 +- src/chain/bitcoind.rs | 18 +- src/chain/electrum.rs | 11 +- src/chain/esplora.rs | 11 +- src/chain/mod.rs | 11 +- src/data_store.rs | 121 ++--- src/event.rs | 42 +- src/io/utils.rs | 14 +- src/lib.rs | 259 +++++------ src/payment/bolt11.rs | 151 +++--- src/payment/bolt12.rs | 45 +- src/payment/onchain.rs | 4 +- src/payment/spontaneous.rs | 29 +- src/payment/unified.rs | 41 +- src/peer_store.rs | 64 +-- src/scoring.rs | 1 + src/wallet/mod.rs | 607 +++++++++++++------------ tests/common/mod.rs | 336 +++++++++----- tests/common/scenarios/channel.rs | 4 +- tests/common/scenarios/connectivity.rs | 8 +- tests/common/scenarios/mod.rs | 4 +- tests/common/scenarios/payment.rs | 5 +- tests/integration_tests_hrn.rs | 11 +- tests/integration_tests_postgres.rs | 4 +- tests/integration_tests_rust.rs | 486 +++++++++++--------- tests/integration_tests_vss.rs | 4 +- tests/integration_tests_vss_no_auth.rs | 4 +- tests/reorg_test.rs | 15 +- 29 files changed, 1257 insertions(+), 1122 deletions(-) diff --git a/bindings/ldk_node.udl b/bindings/ldk_node.udl index 7e9e61f5d5..40119eb5cc 100644 --- a/bindings/ldk_node.udl +++ b/bindings/ldk_node.udl @@ -82,6 +82,7 @@ interface Node { void start(); [Throws=NodeError] void stop(); + [Async] NodeStatus status(); Config config(); Event? next_event(); @@ -102,47 +103,50 @@ interface Node { LSPS1Liquidity lsps1_liquidity(); [Throws=NodeError] void lnurl_auth(string lnurl); - [Throws=NodeError] + [Async, Throws=NodeError] void connect(PublicKey node_id, SocketAddress address, boolean persist); - [Throws=NodeError] + [Async, Throws=NodeError] void disconnect(PublicKey node_id); - [Throws=NodeError] + [Async, Throws=NodeError] UserChannelId open_channel(PublicKey node_id, SocketAddress address, u64 channel_amount_sats, u64? push_to_counterparty_msat, ChannelConfig? channel_config); - [Throws=NodeError] + [Async, Throws=NodeError] UserChannelId open_announced_channel(PublicKey node_id, SocketAddress address, u64 channel_amount_sats, u64? push_to_counterparty_msat, ChannelConfig? channel_config); - [Throws=NodeError] + [Async, Throws=NodeError] UserChannelId open_channel_with_all(PublicKey node_id, SocketAddress address, u64? push_to_counterparty_msat, ChannelConfig? channel_config); - [Throws=NodeError] + [Async, Throws=NodeError] UserChannelId open_announced_channel_with_all(PublicKey node_id, SocketAddress address, u64? push_to_counterparty_msat, ChannelConfig? channel_config); - [Throws=NodeError] + [Async, Throws=NodeError] UserChannelId open_0reserve_channel(PublicKey node_id, SocketAddress address, u64 channel_amount_sats, u64? push_to_counterparty_msat, ChannelConfig? channel_config); - [Throws=NodeError] + [Async, Throws=NodeError] UserChannelId open_0reserve_channel_with_all(PublicKey node_id, SocketAddress address, u64? push_to_counterparty_msat, ChannelConfig? channel_config); - [Throws=NodeError] + [Async, Throws=NodeError] void splice_in([ByRef]UserChannelId user_channel_id, PublicKey counterparty_node_id, u64 splice_amount_sats); - [Throws=NodeError] + [Async, Throws=NodeError] void splice_in_with_all([ByRef]UserChannelId user_channel_id, PublicKey counterparty_node_id); [Throws=NodeError] void splice_out([ByRef]UserChannelId user_channel_id, PublicKey counterparty_node_id, [ByRef]Address address, u64 splice_amount_sats); - [Throws=NodeError] + [Async, Throws=NodeError] void close_channel([ByRef]UserChannelId user_channel_id, PublicKey counterparty_node_id); - [Throws=NodeError] + [Async, Throws=NodeError] void force_close_channel([ByRef]UserChannelId user_channel_id, PublicKey counterparty_node_id, string? reason); [Throws=NodeError] void update_channel_config([ByRef]UserChannelId user_channel_id, PublicKey counterparty_node_id, ChannelConfig channel_config); - [Throws=NodeError] + [Async, Throws=NodeError] void sync_wallets(); + [Async] PaymentDetails? payment([ByRef]PaymentId payment_id); - [Throws=NodeError] + [Async, Throws=NodeError] void remove_payment([ByRef]PaymentId payment_id); BalanceDetails list_balances(); + [Async] sequence list_payments(); + [Async] sequence list_peers(); sequence list_channels(); NetworkGraph network_graph(); string sign_message([ByRef]sequence msg); boolean verify_signature([ByRef]sequence msg, [ByRef]string sig, [ByRef]PublicKey pkey); - [Throws=NodeError] + [Async, Throws=NodeError] bytes export_pathfinding_scores(); }; diff --git a/src/builder.rs b/src/builder.rs index c88c867cc1..dc73915bd3 100644 --- a/src/builder.rs +++ b/src/builder.rs @@ -43,6 +43,7 @@ use lightning::util::persist::{ use lightning::util::ser::ReadableArgs; use lightning::util::sweep::OutputSweeper; use lightning_dns_resolver::OMDomainResolver; +use tokio::sync::RwLock as AsyncRwLock; use vss_client::headers::VssHeaderProvider; use crate::chain::ChainSource; @@ -85,7 +86,7 @@ use crate::types::{ }; use crate::wallet::persist::KVStoreWalletPersister; use crate::wallet::Wallet; -use crate::{Node, NodeMetrics, PersistedNodeMetrics}; +use crate::{Node, NodeMetrics}; const LSPS_HARDENED_CHILD_INDEX: u32 = 577; const PERSISTER_MAX_PENDING_UPDATES: u64 = 100; @@ -727,13 +728,18 @@ impl NodeBuilder { fixed_headers: HashMap, ) -> Result { let logger = setup_logger(&self.log_writer_config, &self.config)?; + let runtime = self.setup_runtime(&logger)?; let builder = VssStoreBuilder::new(node_entropy, vss_url, store_id, self.config.network); let vss_store = builder.build_with_sigs_auth(fixed_headers).map_err(|e| { log_error!(logger, "Failed to setup VSS store: {}", e); BuildError::KVStoreSetupFailed })?; + runtime.block_on(vss_store.setup_schema_version()).map_err(|e| { + log_error!(logger, "Failed to setup VSS store: {}", e); + BuildError::KVStoreSetupFailed + })?; - self.build_with_store_and_logger(node_entropy, vss_store, logger) + self.build_with_store_runtime_and_logger(node_entropy, vss_store, runtime, logger) } /// Builds a [`Node`] instance with a [VSS] backend and according to the options @@ -763,14 +769,19 @@ impl NodeBuilder { lnurl_auth_server_url: String, fixed_headers: HashMap, ) -> Result { let logger = setup_logger(&self.log_writer_config, &self.config)?; + let runtime = self.setup_runtime(&logger)?; let builder = VssStoreBuilder::new(node_entropy, vss_url, store_id, self.config.network); let vss_store = builder.build_with_lnurl(lnurl_auth_server_url, fixed_headers).map_err(|e| { log_error!(logger, "Failed to setup VSS store: {}", e); BuildError::KVStoreSetupFailed })?; + runtime.block_on(vss_store.setup_schema_version()).map_err(|e| { + log_error!(logger, "Failed to setup VSS store: {}", e); + BuildError::KVStoreSetupFailed + })?; - self.build_with_store_and_logger(node_entropy, vss_store, logger) + self.build_with_store_runtime_and_logger(node_entropy, vss_store, runtime, logger) } /// Builds a [`Node`] instance with a [VSS] backend and according to the options @@ -791,13 +802,18 @@ impl NodeBuilder { fixed_headers: HashMap, ) -> Result { let logger = setup_logger(&self.log_writer_config, &self.config)?; + let runtime = self.setup_runtime(&logger)?; let builder = VssStoreBuilder::new(node_entropy, vss_url, store_id, self.config.network); let vss_store = builder.build_with_fixed_headers(fixed_headers).map_err(|e| { log_error!(logger, "Failed to setup VSS store: {}", e); BuildError::KVStoreSetupFailed })?; + runtime.block_on(vss_store.setup_schema_version()).map_err(|e| { + log_error!(logger, "Failed to setup VSS store: {}", e); + BuildError::KVStoreSetupFailed + })?; - self.build_with_store_and_logger(node_entropy, vss_store, logger) + self.build_with_store_runtime_and_logger(node_entropy, vss_store, runtime, logger) } /// Builds a [`Node`] instance with a [VSS] backend and according to the options @@ -816,13 +832,18 @@ impl NodeBuilder { header_provider: Arc, ) -> Result { let logger = setup_logger(&self.log_writer_config, &self.config)?; + let runtime = self.setup_runtime(&logger)?; let builder = VssStoreBuilder::new(node_entropy, vss_url, store_id, self.config.network); let vss_store = builder.build_with_header_provider(header_provider).map_err(|e| { log_error!(logger, "Failed to setup VSS store: {}", e); BuildError::KVStoreSetupFailed })?; + runtime.block_on(vss_store.setup_schema_version()).map_err(|e| { + log_error!(logger, "Failed to setup VSS store: {}", e); + BuildError::KVStoreSetupFailed + })?; - self.build_with_store_and_logger(node_entropy, vss_store, logger) + self.build_with_store_runtime_and_logger(node_entropy, vss_store, runtime, logger) } /// Builds a [`Node`] instance according to the options previously configured. @@ -1416,10 +1437,10 @@ fn build_with_store_internal( // Initialize the status fields. let node_metrics = match node_metris_res { - Ok(metrics) => Arc::new(PersistedNodeMetrics::new(metrics)), + Ok(metrics) => Arc::new(AsyncRwLock::new(metrics)), Err(e) => { if e.kind() == std::io::ErrorKind::NotFound { - Arc::new(PersistedNodeMetrics::new(NodeMetrics::default())) + Arc::new(AsyncRwLock::new(NodeMetrics::default())) } else { log_error!(logger, "Failed to read node metrics from store: {}", e); return Err(BuildError::ReadFailed); diff --git a/src/chain/bitcoind.rs b/src/chain/bitcoind.rs index 6bfa8ffd27..a044cecf05 100644 --- a/src/chain/bitcoind.rs +++ b/src/chain/bitcoind.rs @@ -29,6 +29,7 @@ use lightning_block_sync::{ SpvClient, }; use serde::Serialize; +use tokio::sync::RwLock as AsyncRwLock; use super::WalletSyncStatus; use crate::config::{ @@ -42,7 +43,7 @@ use crate::fee_estimator::{ use crate::io::utils::update_and_persist_node_metrics; use crate::logger::{log_bytes, log_debug, log_error, log_info, log_trace, LdkLogger, Logger}; use crate::types::{ChainMonitor, ChannelManager, DynStore, Sweeper, Wallet}; -use crate::{Error, PersistedNodeMetrics}; +use crate::{Error, NodeMetrics}; const CHAIN_POLLING_INTERVAL_SECS: u64 = 2; const CHAIN_POLLING_TIMEOUT_SECS: u64 = 10; @@ -55,14 +56,14 @@ pub(super) struct BitcoindChainSource { kv_store: Arc, config: Arc, logger: Arc, - node_metrics: Arc, + node_metrics: Arc>, } impl BitcoindChainSource { pub(crate) fn new_rpc( rpc_host: String, rpc_port: u16, rpc_user: String, rpc_password: String, fee_estimator: Arc, kv_store: Arc, config: Arc, - logger: Arc, node_metrics: Arc, + logger: Arc, node_metrics: Arc>, ) -> Self { let api_client = Arc::new(BitcoindClient::new_rpc( rpc_host.clone(), @@ -89,7 +90,7 @@ impl BitcoindChainSource { rpc_host: String, rpc_port: u16, rpc_user: String, rpc_password: String, fee_estimator: Arc, kv_store: Arc, config: Arc, rest_client_config: BitcoindRestClientConfig, logger: Arc, - node_metrics: Arc, + node_metrics: Arc>, ) -> Self { let api_client = Arc::new(BitcoindClient::new_rest( rest_client_config.rest_host, @@ -435,11 +436,12 @@ impl BitcoindChainSource { evicted_txids.len(), elapsed_ms, ); - onchain_wallet.apply_mempool_txs(unconfirmed_txs, evicted_txids).unwrap_or_else( - |e| { + onchain_wallet + .apply_mempool_txs(unconfirmed_txs, evicted_txids) + .await + .unwrap_or_else(|e| { log_error!(self.logger, "Failed to apply mempool transactions: {:?}", e); - }, - ); + }); }, Err(e) => { log_error!(self.logger, "Failed to poll for mempool transactions: {:?}", e); diff --git a/src/chain/electrum.rs b/src/chain/electrum.rs index ad0ef1b7ba..01c0780eca 100644 --- a/src/chain/electrum.rs +++ b/src/chain/electrum.rs @@ -22,6 +22,7 @@ use electrum_client::{ use lightning::chain::{Confirm, Filter, WatchedOutput}; use lightning::util::ser::Writeable; use lightning_transaction_sync::ElectrumSyncClient; +use tokio::sync::RwLock as AsyncRwLock; use super::WalletSyncStatus; use crate::config::{Config, ElectrumSyncConfig, BDK_CLIENT_STOP_GAP}; @@ -34,7 +35,7 @@ use crate::io::utils::update_and_persist_node_metrics; use crate::logger::{log_bytes, log_debug, log_error, log_trace, LdkLogger, Logger}; use crate::runtime::Runtime; use crate::types::{ChainMonitor, ChannelManager, DynStore, Sweeper, Wallet}; -use crate::PersistedNodeMetrics; +use crate::NodeMetrics; const BDK_ELECTRUM_CLIENT_BATCH_SIZE: usize = 5; const ELECTRUM_CLIENT_NUM_RETRIES: u8 = 3; @@ -49,14 +50,14 @@ pub(super) struct ElectrumChainSource { kv_store: Arc, config: Arc, logger: Arc, - node_metrics: Arc, + node_metrics: Arc>, } impl ElectrumChainSource { pub(super) fn new( server_url: String, sync_config: ElectrumSyncConfig, fee_estimator: Arc, kv_store: Arc, config: Arc, - logger: Arc, node_metrics: Arc, + logger: Arc, node_metrics: Arc>, ) -> Self { let electrum_runtime_status = RwLock::new(ElectrumRuntimeStatus::new()); let onchain_wallet_sync_status = Mutex::new(WalletSyncStatus::Completed); @@ -127,7 +128,7 @@ impl ElectrumChainSource { // If this is our first sync, do a full scan with the configured gap limit. // Otherwise just do an incremental sync. let incremental_sync = - self.node_metrics.read().expect("lock").latest_onchain_wallet_sync_timestamp.is_some(); + self.node_metrics.read().await.latest_onchain_wallet_sync_timestamp.is_some(); let cached_txs = onchain_wallet.get_cached_txs(); @@ -168,7 +169,7 @@ impl ElectrumChainSource { update_res: Result, now: Instant, ) -> Result<(), Error> { match update_res { - Ok(update) => match onchain_wallet.apply_update(update) { + Ok(update) => match onchain_wallet.apply_update(update).await { Ok(()) => { log_debug!( self.logger, diff --git a/src/chain/esplora.rs b/src/chain/esplora.rs index eb23a395d3..8f99d2b58e 100644 --- a/src/chain/esplora.rs +++ b/src/chain/esplora.rs @@ -15,6 +15,7 @@ use esplora_client::AsyncClient as EsploraAsyncClient; use lightning::chain::{Confirm, Filter, WatchedOutput}; use lightning::util::ser::Writeable; use lightning_transaction_sync::EsploraSyncClient; +use tokio::sync::RwLock as AsyncRwLock; use super::WalletSyncStatus; use crate::config::{Config, EsploraSyncConfig, BDK_CLIENT_CONCURRENCY, BDK_CLIENT_STOP_GAP}; @@ -25,7 +26,7 @@ use crate::fee_estimator::{ use crate::io::utils::update_and_persist_node_metrics; use crate::logger::{log_bytes, log_debug, log_error, log_trace, LdkLogger, Logger}; use crate::types::{ChainMonitor, ChannelManager, DynStore, Sweeper, Wallet}; -use crate::{Error, PersistedNodeMetrics}; +use crate::{Error, NodeMetrics}; pub(super) struct EsploraChainSource { pub(super) sync_config: EsploraSyncConfig, @@ -37,14 +38,14 @@ pub(super) struct EsploraChainSource { kv_store: Arc, config: Arc, logger: Arc, - node_metrics: Arc, + node_metrics: Arc>, } impl EsploraChainSource { pub(crate) fn new( server_url: String, headers: HashMap, sync_config: EsploraSyncConfig, fee_estimator: Arc, kv_store: Arc, config: Arc, - logger: Arc, node_metrics: Arc, + logger: Arc, node_metrics: Arc>, ) -> Result { let mut client_builder = esplora_client::Builder::new(&server_url); client_builder = @@ -103,14 +104,14 @@ impl EsploraChainSource { // If this is our first sync, do a full scan with the configured gap limit. // Otherwise just do an incremental sync. let incremental_sync = - self.node_metrics.read().expect("lock").latest_onchain_wallet_sync_timestamp.is_some(); + self.node_metrics.read().await.latest_onchain_wallet_sync_timestamp.is_some(); macro_rules! get_and_apply_wallet_update { ($sync_future: expr) => {{ let now = Instant::now(); match $sync_future.await { Ok(res) => match res { - Ok(update) => match onchain_wallet.apply_update(update) { + Ok(update) => match onchain_wallet.apply_update(update).await { Ok(()) => { log_debug!( self.logger, diff --git a/src/chain/mod.rs b/src/chain/mod.rs index 92c4bdb641..b152abd9e8 100644 --- a/src/chain/mod.rs +++ b/src/chain/mod.rs @@ -15,6 +15,7 @@ use std::time::Duration; use bitcoin::{Script, Txid}; use lightning::chain::{BlockLocator, Filter}; +use tokio::sync::RwLock as AsyncRwLock; use crate::chain::bitcoind::{BitcoindChainSource, UtxoSourceClient}; use crate::chain::electrum::ElectrumChainSource; @@ -27,7 +28,7 @@ use crate::fee_estimator::OnchainFeeEstimator; use crate::logger::{log_debug, log_info, log_trace, LdkLogger, Logger}; use crate::runtime::Runtime; use crate::types::{Broadcaster, ChainMonitor, ChannelManager, DynStore, Sweeper, Wallet}; -use crate::{Error, PersistedNodeMetrics}; +use crate::{Error, NodeMetrics}; pub(crate) enum WalletSyncStatus { Completed, @@ -100,7 +101,7 @@ impl ChainSource { server_url: String, headers: HashMap, sync_config: EsploraSyncConfig, fee_estimator: Arc, tx_broadcaster: Arc, kv_store: Arc, config: Arc, logger: Arc, - node_metrics: Arc, + node_metrics: Arc>, ) -> Result<(Self, Option), ()> { let esplora_chain_source = EsploraChainSource::new( server_url, @@ -121,7 +122,7 @@ impl ChainSource { server_url: String, sync_config: ElectrumSyncConfig, fee_estimator: Arc, tx_broadcaster: Arc, kv_store: Arc, config: Arc, logger: Arc, - node_metrics: Arc, + node_metrics: Arc>, ) -> (Self, Option) { let electrum_chain_source = ElectrumChainSource::new( server_url, @@ -141,7 +142,7 @@ impl ChainSource { rpc_host: String, rpc_port: u16, rpc_user: String, rpc_password: String, fee_estimator: Arc, tx_broadcaster: Arc, kv_store: Arc, config: Arc, logger: Arc, - node_metrics: Arc, + node_metrics: Arc>, ) -> (Self, Option) { let bitcoind_chain_source = BitcoindChainSource::new_rpc( rpc_host, @@ -164,7 +165,7 @@ impl ChainSource { rpc_host: String, rpc_port: u16, rpc_user: String, rpc_password: String, fee_estimator: Arc, tx_broadcaster: Arc, kv_store: Arc, config: Arc, rest_client_config: BitcoindRestClientConfig, - logger: Arc, node_metrics: Arc, + logger: Arc, node_metrics: Arc>, ) -> (Self, Option) { let bitcoind_chain_source = BitcoindChainSource::new_rest( rpc_host, diff --git a/src/data_store.rs b/src/data_store.rs index 70abfcc3fd..f7248118c1 100644 --- a/src/data_store.rs +++ b/src/data_store.rs @@ -7,10 +7,11 @@ use std::collections::{hash_map, HashMap}; use std::ops::Deref; -use std::sync::{Arc, Mutex}; +use std::sync::Arc; use lightning::util::persist::KVStore; use lightning::util::ser::{Readable, Writeable}; +use tokio::sync::RwLock; use crate::logger::{log_error, LdkLogger}; use crate::types::DynStore; @@ -44,8 +45,7 @@ pub(crate) struct DataStore where L::Target: LdkLogger, { - objects: Mutex>, - mutation_lock: tokio::sync::Mutex<()>, + objects: RwLock>, primary_namespace: String, secondary_namespace: String, kv_store: Arc, @@ -61,55 +61,42 @@ where kv_store: Arc, logger: L, ) -> Self { let objects = - Mutex::new(HashMap::from_iter(objects.into_iter().map(|obj| (obj.id(), obj)))); - Self { - objects, - mutation_lock: tokio::sync::Mutex::new(()), - primary_namespace, - secondary_namespace, - kv_store, - logger, - } + RwLock::new(HashMap::from_iter(objects.into_iter().map(|obj| (obj.id(), obj)))); + Self { objects, primary_namespace, secondary_namespace, kv_store, logger } } pub(crate) async fn insert(&self, object: SO) -> Result { - let _guard = self.mutation_lock.lock().await; - + let mut locked_objects = self.objects.write().await; self.persist(&object).await?; - let mut locked_objects = self.objects.lock().expect("lock"); let updated = locked_objects.insert(object.id(), object).is_some(); Ok(updated) } pub(crate) async fn insert_or_update(&self, object: SO) -> Result { - let _guard = self.mutation_lock.lock().await; - let (updated, data_to_persist) = { - let mut locked_objects = self.objects.lock().expect("lock"); - match locked_objects.entry(object.id()) { - hash_map::Entry::Occupied(mut e) => { - let update = object.to_update(); - let updated = e.get_mut().update(update); - let data_to_persist = - if updated { Some(Self::encode_object(e.get())) } else { None }; - (updated, data_to_persist) - }, - hash_map::Entry::Vacant(e) => { - let data_to_persist = Self::encode_object(&object); - e.insert(object); - (true, Some(data_to_persist)) - }, - } - }; + let mut locked_objects = self.objects.write().await; - if let Some((store_key, data)) = data_to_persist { - self.persist_encoded(store_key, data).await?; + let updated; + match locked_objects.entry(object.id()) { + hash_map::Entry::Occupied(mut e) => { + let update = object.to_update(); + updated = e.get_mut().update(update); + if updated { + self.persist(&e.get()).await?; + } + }, + hash_map::Entry::Vacant(e) => { + e.insert(object.clone()); + self.persist(&object).await?; + updated = true; + }, } + Ok(updated) } pub(crate) async fn remove(&self, id: &SO::Id) -> Result<(), Error> { - let _guard = self.mutation_lock.lock().await; - let removed = { self.objects.lock().expect("lock").remove(id).is_some() }; + let mut locked_objects = self.objects.write().await; + let removed = locked_objects.remove(id).is_some(); if removed { let store_key = id.encode_to_hex_str(); KVStore::remove( @@ -135,43 +122,28 @@ where Ok(()) } - /// Returns the current in-memory object for `id`. - /// - /// The async mutation lock serializes writers, but this synchronous reader cannot wait on it. - /// Until store reads are async, callers may temporarily see in-memory state that is either - /// still being persisted or has not yet caught up to a write in progress. - pub(crate) fn get(&self, id: &SO::Id) -> Option { - self.objects.lock().expect("lock").get(id).cloned() + pub(crate) async fn get(&self, id: &SO::Id) -> Option { + self.objects.read().await.get(id).cloned() } pub(crate) async fn update(&self, update: SO::Update) -> Result { - let _guard = self.mutation_lock.lock().await; - let (res, data_to_persist) = { - let mut locked_objects = self.objects.lock().expect("lock"); - if let Some(object) = locked_objects.get_mut(&update.id()) { - let updated = object.update(update); - if updated { - (DataStoreUpdateResult::Updated, Some(Self::encode_object(object))) - } else { - (DataStoreUpdateResult::Unchanged, None) - } + let mut locked_objects = self.objects.write().await; + + if let Some(object) = locked_objects.get_mut(&update.id()) { + let updated = object.update(update); + if updated { + self.persist(&object).await?; + Ok(DataStoreUpdateResult::Updated) } else { - (DataStoreUpdateResult::NotFound, None) + Ok(DataStoreUpdateResult::Unchanged) } - }; - if let Some((store_key, data)) = data_to_persist { - self.persist_encoded(store_key, data).await?; + } else { + Ok(DataStoreUpdateResult::NotFound) } - Ok(res) } - /// Returns in-memory objects matching `f`. - /// - /// The async mutation lock serializes writers, but this synchronous reader cannot wait on it. - /// Until store reads are async, callers may temporarily see in-memory state that is either - /// still being persisted or has not yet caught up to a write in progress. - pub(crate) fn list_filter bool>(&self, f: F) -> Vec { - self.objects.lock().expect("lock").values().filter(f).cloned().collect::>() + pub(crate) async fn list_filter bool>(&self, f: F) -> Vec { + self.objects.read().await.values().filter(f).cloned().collect::>() } async fn persist(&self, object: &SO) -> Result<(), Error> { @@ -206,13 +178,8 @@ where Ok(()) } - /// Returns whether the in-memory store contains `id`. - /// - /// The async mutation lock serializes writers, but this synchronous reader cannot wait on it. - /// Until store reads are async, callers may temporarily see in-memory state that is either - /// still being persisted or has not yet caught up to a write in progress. - pub(crate) fn contains_key(&self, id: &SO::Id) -> bool { - self.objects.lock().expect("lock").contains_key(id) + pub(crate) async fn contains_key(&self, id: &SO::Id) -> bool { + self.objects.read().await.contains_key(id) } } @@ -296,7 +263,7 @@ mod tests { ); let id = TestObjectId { id: [42u8; 4] }; - assert!(data_store.get(&id).is_none()); + assert!(data_store.get(&id).await.is_none()); let store_key = id.encode_to_hex_str(); @@ -308,7 +275,7 @@ mod tests { // Check we successfully store an object and return `false` let object = TestObject { id, data: [23u8; 3] }; assert_eq!(Ok(false), data_store.insert(object.clone()).await); - assert_eq!(Some(object), data_store.get(&id)); + assert_eq!(Some(object), data_store.get(&id).await); assert!(KVStore::read(&*store, &primary_namespace, &secondary_namespace, &store_key) .await .is_ok()); @@ -317,12 +284,12 @@ mod tests { let mut override_object = object.clone(); override_object.data = [24u8; 3]; assert_eq!(Ok(true), data_store.insert(override_object).await); - assert_eq!(Some(override_object), data_store.get(&id)); + assert_eq!(Some(override_object), data_store.get(&id).await); // Check update returns `Updated` let update = TestObjectUpdate { id, data: [25u8; 3] }; assert_eq!(Ok(DataStoreUpdateResult::Updated), data_store.update(update).await); - assert_eq!(data_store.get(&id).unwrap().data, [25u8; 3]); + assert_eq!(data_store.get(&id).await.unwrap().data, [25u8; 3]); // Check no-op update yields `Unchanged` let update = TestObjectUpdate { id, data: [25u8; 3] }; diff --git a/src/event.rs b/src/event.rs index 86ee7bb05a..d5621521ac 100644 --- a/src/event.rs +++ b/src/event.rs @@ -724,7 +724,7 @@ where .. } => { let payment_id = PaymentId(payment_hash.0); - let payment_info = self.payment_store.get(&payment_id); + let payment_info = self.payment_store.get(&payment_id).await; if let Some(info) = payment_info.as_ref() { if info.direction == PaymentDirection::Outbound { log_info!( @@ -1142,7 +1142,7 @@ where }, }; - self.payment_store.get(&payment_id).map(|payment| { + self.payment_store.get(&payment_id).await.map(|payment| { let amount_msat = payment.amount_msat.expect( "outbound payments should record their amount before they can succeed", ); @@ -1524,28 +1524,26 @@ where }, }; - let peer_to_store = { + let is_new_inbound_pending_channel = self + .channel_manager + .list_channels_with_counterparty(&counterparty_node_id) + .into_iter() + .any(|c| c.channel_id == channel_id && !c.is_outbound); + let peer_to_store = if is_new_inbound_pending_channel + && self.peer_store.get_peer(&counterparty_node_id).await.is_none() + { let network_graph = self.network_graph.read_only(); - let channels = - self.channel_manager.list_channels_with_counterparty(&counterparty_node_id); - channels - .into_iter() - .find(|c| c.channel_id == channel_id) - .filter(|pending_channel| { - !pending_channel.is_outbound - && self.peer_store.get_peer(&counterparty_node_id).is_none() - }) - .and_then(|_| { - network_graph - .nodes() - .get(&NodeId::from_pubkey(&counterparty_node_id)) - .and_then(|node_info| node_info.announcement_info.as_ref()) - .and_then(|ann_info| ann_info.addresses().first()) - .map(|address| PeerInfo { - node_id: counterparty_node_id, - address: address.clone(), - }) + network_graph + .nodes() + .get(&NodeId::from_pubkey(&counterparty_node_id)) + .and_then(|node_info| node_info.announcement_info.as_ref()) + .and_then(|ann_info| ann_info.addresses().first()) + .map(|address| PeerInfo { + node_id: counterparty_node_id, + address: address.clone(), }) + } else { + None }; if let Some(peer) = peer_to_store { self.peer_store.add_peer(peer).await.unwrap_or_else(|e| { diff --git a/src/io/utils.rs b/src/io/utils.rs index 4657688f51..b07bf14a5b 100644 --- a/src/io/utils.rs +++ b/src/io/utils.rs @@ -37,6 +37,7 @@ use lightning::util::ser::{Readable, ReadableArgs, Writeable}; use lightning_persister::fs_store::v1::FilesystemStore; use lightning_persister::fs_store::v2::{FilesystemStoreV2, FilesystemStoreV2Error}; use lightning_types::string::PrintableString; +use tokio::sync::RwLock; use super::*; use crate::chain::ChainSource; @@ -49,7 +50,7 @@ use crate::logger::{log_error, LdkLogger, Logger}; use crate::peer_store::PeerStore; use crate::types::{Broadcaster, DynStore, KeysManager, Sweeper}; use crate::wallet::ser::{ChangeSetDeserWrapper, ChangeSetSerWrapper}; -use crate::{BuildError, Error, EventQueue, NodeMetrics, PersistedNodeMetrics}; +use crate::{BuildError, Error, EventQueue, NodeMetrics}; pub const EXTERNAL_PATHFINDING_SCORES_CACHE_KEY: &str = "external_pathfinding_scores_cache"; @@ -337,18 +338,15 @@ where /// Take a write lock on `node_metrics`, apply `update`, and persist the result to `kv_store`. pub(crate) async fn update_and_persist_node_metrics( - node_metrics: &PersistedNodeMetrics, kv_store: &DynStore, logger: L, + node_metrics: &RwLock, kv_store: &DynStore, logger: L, update: impl FnOnce(&mut NodeMetrics), ) -> Result<(), Error> where L::Target: LdkLogger, { - let _guard = node_metrics.lock_mutation().await; - let data = { - let mut locked_node_metrics = node_metrics.write().expect("lock"); - update(&mut *locked_node_metrics); - locked_node_metrics.encode() - }; + let mut locked_node_metrics = node_metrics.write().await; + update(&mut *locked_node_metrics); + let data = locked_node_metrics.encode(); KVStore::write( &*kv_store, NODE_METRICS_PRIMARY_NAMESPACE, diff --git a/src/lib.rs b/src/lib.rs index 7465dfabf5..0d5f6b9bb1 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -175,6 +175,7 @@ use payment::{ use peer_store::{PeerInfo, PeerStore}; use runtime::Runtime; pub use tokio; +use tokio::sync::RwLock as AsyncRwLock; use types::{ Broadcaster, BumpTransactionEventHandler, ChainMonitor, ChannelManager, DynStore, Graph, HRNResolver, KeysManager, OnionMessenger, PaymentStore, PeerManager, Router, Scorer, Sweeper, @@ -243,7 +244,7 @@ pub struct Node { payment_store: Arc, lnurl_auth: Arc, is_running: Arc>, - node_metrics: Arc, + node_metrics: Arc>, om_mailbox: Option>, async_payments_role: Option, hrn_resolver: HRNResolver, @@ -459,27 +460,32 @@ impl Node { interval.set_missed_tick_behavior(tokio::time::MissedTickBehavior::Skip); loop { tokio::select! { - _ = stop_connect.changed() => { - log_debug!( - connect_logger, - "Stopping reconnecting known peers." - ); - return; - } - _ = interval.tick() => { - let pm_peers = connect_pm - .list_peers() - .iter() - .map(|peer| peer.counterparty_node_id) - .collect::>(); - - for peer_info in connect_peer_store.list_peers().iter().filter(|info| !pm_peers.contains(&info.node_id)) { - let _ = connect_cm.do_connect_peer( - peer_info.node_id, - peer_info.address.clone(), - ).await; - } + _ = stop_connect.changed() => { + log_debug!( + connect_logger, + "Stopping reconnecting known peers." + ); + return; + } + _ = interval.tick() => { + let pm_peers = connect_pm + .list_peers() + .iter() + .map(|peer| peer.counterparty_node_id) + .collect::>(); + + for peer_info in connect_peer_store + .list_peers() + .await + .iter() + .filter(|info| !pm_peers.contains(&info.node_id)) + { + let _ = connect_cm.do_connect_peer( + peer_info.node_id, + peer_info.address.clone(), + ).await; } + } } } }); @@ -506,11 +512,11 @@ impl Node { log_debug!( bcast_logger, "Stopping broadcasting node announcements.", - ); + ); return; } _ = interval.tick() => { - let skip_broadcast = match bcast_node_metrics.read().expect("lock").latest_node_announcement_broadcast_timestamp { + let skip_broadcast = match bcast_node_metrics.read().await.latest_node_announcement_broadcast_timestamp { Some(latest_bcast_time_secs) => { // Skip if the time hasn't elapsed yet. let next_bcast_unix_time = SystemTime::UNIX_EPOCH + Duration::from_secs(latest_bcast_time_secs) + NODE_ANN_BCAST_INTERVAL; @@ -767,11 +773,11 @@ impl Node { } /// Returns the status of the [`Node`]. - pub fn status(&self) -> NodeStatus { + pub async fn status(&self) -> NodeStatus { let is_running = *self.is_running.read().expect("lock"); let network = self.config.network; let current_best_block = self.channel_manager.current_best_block().into(); - let locked_node_metrics = self.node_metrics.read().expect("lock"); + let locked_node_metrics = self.node_metrics.read().await; let latest_lightning_wallet_sync_timestamp = locked_node_metrics.latest_lightning_wallet_sync_timestamp; let latest_onchain_wallet_sync_timestamp = @@ -1104,7 +1110,7 @@ impl Node { /// Connect to a node on the peer-to-peer network. /// /// If `persist` is set to `true`, we'll remember the peer and reconnect to it on restart. - pub fn connect( + pub async fn connect( &self, node_id: PublicKey, address: SocketAddress, persist: bool, ) -> Result<(), Error> { if !*self.is_running.read().expect("lock") { @@ -1117,16 +1123,12 @@ impl Node { let con_addr = peer_info.address.clone(); let con_cm = Arc::clone(&self.connection_manager); - // We need to use our main runtime here as a local runtime might not be around to poll - // connection futures going forward. - self.runtime.block_on(async move { - con_cm.connect_peer_if_necessary(con_node_id, con_addr).await - })?; + con_cm.connect_peer_if_necessary(con_node_id, con_addr).await?; log_info!(self.logger, "Connected to peer {}@{}. ", peer_info.node_id, peer_info.address); if persist { - self.runtime.block_on(self.peer_store.add_peer(peer_info))?; + self.peer_store.add_peer(peer_info).await?; } Ok(()) @@ -1136,14 +1138,14 @@ impl Node { /// /// Will also remove the peer from the peer store, i.e., after this has been called we won't /// try to reconnect on restart. - pub fn disconnect(&self, counterparty_node_id: PublicKey) -> Result<(), Error> { + pub async fn disconnect(&self, counterparty_node_id: PublicKey) -> Result<(), Error> { if !*self.is_running.read().expect("lock") { return Err(Error::NotRunning); } log_info!(self.logger, "Disconnecting peer {}..", counterparty_node_id); - match self.runtime.block_on(self.peer_store.remove_peer(&counterparty_node_id)) { + match self.peer_store.remove_peer(&counterparty_node_id).await { Ok(()) => {}, Err(e) => { log_error!(self.logger, "Failed to remove peer {}: {}", counterparty_node_id, e) @@ -1154,7 +1156,7 @@ impl Node { Ok(()) } - fn open_channel_inner( + async fn open_channel_inner( &self, node_id: PublicKey, address: SocketAddress, channel_amount_sats: FundingAmount, push_to_counterparty_msat: Option, channel_config: Option, announce_for_forwarding: bool, disable_counterparty_reserve: bool, @@ -1169,11 +1171,7 @@ impl Node { let con_addr = peer_info.address.clone(); let con_cm = Arc::clone(&self.connection_manager); - // We need to use our main runtime here as a local runtime might not be around to poll - // connection futures going forward. - self.runtime.block_on(async move { - con_cm.connect_peer_if_necessary(con_node_id, con_addr).await - })?; + con_cm.connect_peer_if_necessary(con_node_id, con_addr).await?; let channel_amount_sats = match channel_amount_sats { FundingAmount::Exact { amount_sats } => { @@ -1260,7 +1258,7 @@ impl Node { zero_reserve_string, peer_info.node_id ); - self.runtime.block_on(self.peer_store.add_peer(peer_info))?; + self.peer_store.add_peer(peer_info).await?; Ok(UserChannelId(user_channel_id)) }, Err(e) => { @@ -1334,7 +1332,7 @@ impl Node { /// Returns a [`UserChannelId`] allowing to locally keep track of the channel. /// /// [`AnchorChannelsConfig::per_channel_reserve_sats`]: crate::config::AnchorChannelsConfig::per_channel_reserve_sats - pub fn open_channel( + pub async fn open_channel( &self, node_id: PublicKey, address: SocketAddress, channel_amount_sats: u64, push_to_counterparty_msat: Option, channel_config: Option, ) -> Result { @@ -1347,6 +1345,7 @@ impl Node { false, false, ) + .await } /// Connect to a node and open a new announced channel. @@ -1370,7 +1369,7 @@ impl Node { /// Returns a [`UserChannelId`] allowing to locally keep track of the channel. /// /// [`AnchorChannelsConfig::per_channel_reserve_sats`]: crate::config::AnchorChannelsConfig::per_channel_reserve_sats - pub fn open_announced_channel( + pub async fn open_announced_channel( &self, node_id: PublicKey, address: SocketAddress, channel_amount_sats: u64, push_to_counterparty_msat: Option, channel_config: Option, ) -> Result { @@ -1388,6 +1387,7 @@ impl Node { true, false, ) + .await } /// Connect to a node and open a new unannounced channel, using all available on-chain funds @@ -1404,7 +1404,7 @@ impl Node { /// Returns a [`UserChannelId`] allowing to locally keep track of the channel. /// /// [`AnchorChannelsConfig::per_channel_reserve_sats`]: crate::config::AnchorChannelsConfig::per_channel_reserve_sats - pub fn open_channel_with_all( + pub async fn open_channel_with_all( &self, node_id: PublicKey, address: SocketAddress, push_to_counterparty_msat: Option, channel_config: Option, ) -> Result { @@ -1417,6 +1417,7 @@ impl Node { false, false, ) + .await } /// Connect to a node and open a new announced channel, using all available on-chain funds @@ -1437,7 +1438,7 @@ impl Node { /// Returns a [`UserChannelId`] allowing to locally keep track of the channel. /// /// [`AnchorChannelsConfig::per_channel_reserve_sats`]: crate::config::AnchorChannelsConfig::per_channel_reserve_sats - pub fn open_announced_channel_with_all( + pub async fn open_announced_channel_with_all( &self, node_id: PublicKey, address: SocketAddress, push_to_counterparty_msat: Option, channel_config: Option, ) -> Result { @@ -1455,6 +1456,7 @@ impl Node { true, false, ) + .await } /// Connect to a node and open a new unannounced channel, in which the target node can @@ -1476,7 +1478,7 @@ impl Node { /// Returns a [`UserChannelId`] allowing to locally keep track of the channel. /// /// [`AnchorChannelsConfig::per_channel_reserve_sats`]: crate::config::AnchorChannelsConfig::per_channel_reserve_sats - pub fn open_0reserve_channel( + pub async fn open_0reserve_channel( &self, node_id: PublicKey, address: SocketAddress, channel_amount_sats: u64, push_to_counterparty_msat: Option, channel_config: Option, ) -> Result { @@ -1489,6 +1491,7 @@ impl Node { false, true, ) + .await } /// Connect to a node and open a new unannounced channel, using all available on-chain funds @@ -1505,7 +1508,7 @@ impl Node { /// entirely shifted to one side, therefore allowing to receive payments from the getgo. /// /// Returns a [`UserChannelId`] allowing to locally keep track of the channel. - pub fn open_0reserve_channel_with_all( + pub async fn open_0reserve_channel_with_all( &self, node_id: PublicKey, address: SocketAddress, push_to_counterparty_msat: Option, channel_config: Option, ) -> Result { @@ -1518,9 +1521,10 @@ impl Node { false, true, ) + .await } - fn splice_in_inner( + async fn splice_in_inner( &self, user_channel_id: &UserChannelId, counterparty_node_id: PublicKey, splice_amount_sats: FundingAmount, ) -> Result<(), Error> { @@ -1605,14 +1609,14 @@ impl Node { return Err(Error::ChannelSplicingFailed); } - let contribution = self - .runtime - .block_on(funding_template.splice_in( + let contribution = funding_template + .splice_in( Amount::from_sat(splice_amount_sats), min_feerate, max_feerate, Arc::clone(&self.wallet), - )) + ) + .await .map_err(|e| { log_error!(self.logger, "Failed to splice channel: {}", e); Error::ChannelSplicingFailed @@ -1650,7 +1654,7 @@ impl Node { /// /// This API is experimental. Currently, a splice-in will be marked as an outbound payment, but /// this classification may change in the future. - pub fn splice_in( + pub async fn splice_in( &self, user_channel_id: &UserChannelId, counterparty_node_id: PublicKey, splice_amount_sats: u64, ) -> Result<(), Error> { @@ -1659,6 +1663,7 @@ impl Node { counterparty_node_id, FundingAmount::Exact { amount_sats: splice_amount_sats }, ) + .await } /// Add all available on-chain funds into an existing channel. @@ -1674,10 +1679,10 @@ impl Node { /// /// This API is experimental. Currently, a splice-in will be marked as an outbound payment, but /// this classification may change in the future. - pub fn splice_in_with_all( + pub async fn splice_in_with_all( &self, user_channel_id: &UserChannelId, counterparty_node_id: PublicKey, ) -> Result<(), Error> { - self.splice_in_inner(user_channel_id, counterparty_node_id, FundingAmount::Max) + self.splice_in_inner(user_channel_id, counterparty_node_id, FundingAmount::Max).await } /// Remove funds from an existing channel, sending them to an on-chain address. @@ -1768,7 +1773,7 @@ impl Node { /// this method must be called manually to keep wallets in sync with the chain state. /// /// [`EsploraSyncConfig::background_sync_config`]: crate::config::EsploraSyncConfig::background_sync_config - pub fn sync_wallets(&self) -> Result<(), Error> { + pub async fn sync_wallets(&self) -> Result<(), Error> { if !*self.is_running.read().expect("lock") { return Err(Error::NotRunning); } @@ -1778,37 +1783,35 @@ impl Node { let sync_cman = Arc::clone(&self.channel_manager); let sync_cmon = Arc::clone(&self.chain_monitor); let sync_sweeper = Arc::clone(&self.output_sweeper); - self.runtime.block_on(async move { - if chain_source.is_transaction_based() { - chain_source.update_fee_rate_estimates().await?; - chain_source - .sync_lightning_wallet(sync_cman, sync_cmon, Arc::clone(&sync_sweeper)) - .await?; - chain_source.sync_onchain_wallet(sync_wallet).await?; - } else { - chain_source.update_fee_rate_estimates().await?; - chain_source - .poll_and_update_listeners( - sync_wallet, - sync_cman, - sync_cmon, - Arc::clone(&sync_sweeper), - ) - .await?; - } - let _ = sync_sweeper.regenerate_and_broadcast_spend_if_necessary().await; - Ok(()) - }) + if chain_source.is_transaction_based() { + chain_source.update_fee_rate_estimates().await?; + chain_source + .sync_lightning_wallet(sync_cman, sync_cmon, Arc::clone(&sync_sweeper)) + .await?; + chain_source.sync_onchain_wallet(sync_wallet).await?; + } else { + chain_source.update_fee_rate_estimates().await?; + chain_source + .poll_and_update_listeners( + sync_wallet, + sync_cman, + sync_cmon, + Arc::clone(&sync_sweeper), + ) + .await?; + } + let _ = sync_sweeper.regenerate_and_broadcast_spend_if_necessary().await; + Ok(()) } /// Close a previously opened channel. /// /// Will attempt to close a channel coopertively. If this fails, users might need to resort to /// [`Node::force_close_channel`]. - pub fn close_channel( + pub async fn close_channel( &self, user_channel_id: &UserChannelId, counterparty_node_id: PublicKey, ) -> Result<(), Error> { - self.close_channel_internal(user_channel_id, counterparty_node_id, false, None) + self.close_channel_internal(user_channel_id, counterparty_node_id, false, None).await } /// Force-close a previously opened channel. @@ -1824,14 +1827,14 @@ impl Node { /// for more information). /// /// [`AnchorChannelsConfig::trusted_peers_no_reserve`]: crate::config::AnchorChannelsConfig::trusted_peers_no_reserve - pub fn force_close_channel( + pub async fn force_close_channel( &self, user_channel_id: &UserChannelId, counterparty_node_id: PublicKey, reason: Option, ) -> Result<(), Error> { - self.close_channel_internal(user_channel_id, counterparty_node_id, true, reason) + self.close_channel_internal(user_channel_id, counterparty_node_id, true, reason).await } - fn close_channel_internal( + async fn close_channel_internal( &self, user_channel_id: &UserChannelId, counterparty_node_id: PublicKey, force: bool, force_close_reason: Option, ) -> Result<(), Error> { @@ -1866,7 +1869,7 @@ impl Node { // Check if this was the last open channel, if so, forget the peer. if open_channels.len() == 1 { - self.runtime.block_on(self.peer_store.remove_peer(&counterparty_node_id))?; + self.peer_store.remove_peer(&counterparty_node_id).await?; } } @@ -1898,13 +1901,13 @@ impl Node { /// Retrieve the details of a specific payment with the given id. /// /// Returns `Some` if the payment was known and `None` otherwise. - pub fn payment(&self, payment_id: &PaymentId) -> Option { - self.payment_store.get(payment_id) + pub async fn payment(&self, payment_id: &PaymentId) -> Option { + self.payment_store.get(payment_id).await } /// Remove the payment with the given id from the store. - pub fn remove_payment(&self, payment_id: &PaymentId) -> Result<(), Error> { - self.runtime.block_on(self.payment_store.remove(&payment_id)) + pub async fn remove_payment(&self, payment_id: &PaymentId) -> Result<(), Error> { + self.payment_store.remove(&payment_id).await } /// Retrieves an overview of all known balances. @@ -1978,19 +1981,19 @@ impl Node { /// # let node = builder.build(node_entropy.into()).unwrap(); /// node.list_payments_with_filter(|p| p.direction == PaymentDirection::Outbound); /// ``` - pub fn list_payments_with_filter bool>( + pub async fn list_payments_with_filter bool>( &self, f: F, ) -> Vec { - self.payment_store.list_filter(f) + self.payment_store.list_filter(f).await } /// Retrieves all payments. - pub fn list_payments(&self) -> Vec { - self.payment_store.list_filter(|_| true) + pub async fn list_payments(&self) -> Vec { + self.payment_store.list_filter(|_| true).await } /// Retrieves a list of known peers. - pub fn list_peers(&self) -> Vec { + pub async fn list_peers(&self) -> Vec { let mut peers = Vec::new(); // First add all connected peers, preferring to list the connected address if available. @@ -1998,7 +2001,7 @@ impl Node { let connected_peers_len = connected_peers.len(); for connected_peer in connected_peers { let node_id = connected_peer.counterparty_node_id; - let stored_peer = self.peer_store.get_peer(&node_id); + let stored_peer = self.peer_store.get_peer(&node_id).await; let stored_addr_opt = stored_peer.as_ref().map(|p| p.address.clone()); let address = match (connected_peer.socket_address, stored_addr_opt) { (Some(con_addr), _) => con_addr, @@ -2013,7 +2016,7 @@ impl Node { } // Now add all known-but-offline peers, too. - for p in self.peer_store.list_peers() { + for p in self.peer_store.list_peers().await { if peers.iter().take(connected_peers_len).any(|d| d.node_id == p.node_id) { continue; } @@ -2061,22 +2064,22 @@ impl Node { /// Exports the current state of the scorer. The result can be shared with and merged by light nodes that only have /// a limited view of the network. - pub fn export_pathfinding_scores(&self) -> Result, Error> { - self.runtime - .block_on(KVStore::read( - &*self.kv_store, - lightning::util::persist::SCORER_PERSISTENCE_PRIMARY_NAMESPACE, - lightning::util::persist::SCORER_PERSISTENCE_SECONDARY_NAMESPACE, - lightning::util::persist::SCORER_PERSISTENCE_KEY, - )) - .map_err(|e| { - log_error!( - self.logger, - "Failed to access store while exporting pathfinding scores: {}", - e - ); - Error::PersistenceFailed - }) + pub async fn export_pathfinding_scores(&self) -> Result, Error> { + KVStore::read( + &*self.kv_store, + lightning::util::persist::SCORER_PERSISTENCE_PRIMARY_NAMESPACE, + lightning::util::persist::SCORER_PERSISTENCE_SECONDARY_NAMESPACE, + lightning::util::persist::SCORER_PERSISTENCE_KEY, + ) + .await + .map_err(|e| { + log_error!( + self.logger, + "Failed to access store while exporting pathfinding scores: {}", + e + ); + Error::PersistenceFailed + }) } /// Return the features used in node announcement. @@ -2187,42 +2190,6 @@ impl Default for NodeMetrics { } } -pub(crate) struct PersistedNodeMetrics { - metrics: RwLock, - mutation_lock: tokio::sync::Mutex<()>, -} - -impl PersistedNodeMetrics { - pub(crate) fn new(metrics: NodeMetrics) -> Self { - Self { metrics: RwLock::new(metrics), mutation_lock: tokio::sync::Mutex::new(()) } - } - - /// Returns the current in-memory metrics. - /// - /// The async mutation lock serializes persistence updates, but this synchronous reader cannot - /// wait on it. Until metrics reads are async, callers may observe metrics changes that are - /// still being persisted. - pub(crate) fn read( - &self, - ) -> std::sync::LockResult> { - self.metrics.read() - } - - /// Returns the in-memory metrics write lock. - /// - /// Persistence updates should go through `update_and_persist_node_metrics` so writers are - /// serialized by the async mutation lock. - pub(crate) fn write( - &self, - ) -> std::sync::LockResult> { - self.metrics.write() - } - - pub(crate) async fn lock_mutation(&self) -> tokio::sync::MutexGuard<'_, ()> { - self.mutation_lock.lock().await - } -} - impl_writeable_tlv_based!(NodeMetrics, { (0, latest_lightning_wallet_sync_timestamp, option), (1, latest_pathfinding_scores_sync_timestamp, option), diff --git a/src/payment/bolt11.rs b/src/payment/bolt11.rs index 068269997f..a4c0eb510b 100644 --- a/src/payment/bolt11.rs +++ b/src/payment/bolt11.rs @@ -36,7 +36,6 @@ use crate::payment::store::{ PaymentStatus, }; use crate::peer_store::{PeerInfo, PeerStore}; -use crate::runtime::Runtime; use crate::types::{ChannelManager, PaymentStore}; #[cfg(not(feature = "uniffi"))] @@ -67,7 +66,6 @@ impl_writeable_tlv_based!(PaymentMetadata, { /// [`Node::bolt11_payment`]: crate::Node::bolt11_payment #[cfg_attr(feature = "uniffi", derive(uniffi::Object))] pub struct Bolt11Payment { - runtime: Arc, channel_manager: Arc, connection_manager: Arc>>, liquidity_source: Option>>>, @@ -80,14 +78,13 @@ pub struct Bolt11Payment { impl Bolt11Payment { pub(crate) fn new( - runtime: Arc, channel_manager: Arc, + _runtime: Arc, channel_manager: Arc, connection_manager: Arc>>, liquidity_source: Option>>>, payment_store: Arc, peer_store: Arc>>, config: Arc, is_running: Arc>, logger: Arc, ) -> Self { Self { - runtime, channel_manager, connection_manager, liquidity_source, @@ -99,7 +96,7 @@ impl Bolt11Payment { } } - pub(crate) fn receive_inner( + pub(crate) async fn receive_inner( &self, amount_msat: Option, invoice_description: &LdkBolt11InvoiceDescription, expiry_secs: u32, manual_claim_payment_hash: Option, ) -> Result { @@ -158,12 +155,12 @@ impl Bolt11Payment { PaymentDirection::Inbound, PaymentStatus::Pending, ); - self.runtime.block_on(self.payment_store.insert(payment))?; + self.payment_store.insert(payment).await?; Ok(invoice) } - fn receive_via_jit_channel_inner( + async fn receive_via_jit_channel_inner( &self, amount_msat: Option, description: &LdkBolt11InvoiceDescription, expiry_secs: u32, max_total_lsp_fee_limit_msat: Option, max_proportional_lsp_fee_limit_ppm_msat: Option, payment_hash: Option, @@ -180,16 +177,12 @@ impl Bolt11Payment { let con_addr = peer_info.address.clone(); let con_cm = Arc::clone(&self.connection_manager); - // We need to use our main runtime here as a local runtime might not be around to poll - // connection futures going forward. - self.runtime.block_on(async move { - con_cm.connect_peer_if_necessary(con_node_id, con_addr).await - })?; + con_cm.connect_peer_if_necessary(con_node_id, con_addr).await?; log_info!(self.logger, "Connected to LSP {}@{}. ", peer_info.node_id, peer_info.address); let liquidity_source = Arc::clone(&liquidity_source); - let invoice = self.runtime.block_on(async move { + let invoice = async move { if let Some(amount_msat) = amount_msat { liquidity_source .lsps2_receive_to_jit_channel( @@ -210,7 +203,8 @@ impl Bolt11Payment { ) .await } - })?; + } + .await?; // Register payment in payment store. let payment_hash = invoice.payment_hash(); @@ -239,10 +233,10 @@ impl Bolt11Payment { PaymentDirection::Inbound, PaymentStatus::Pending, ); - self.runtime.block_on(self.payment_store.insert(payment))?; + self.payment_store.insert(payment).await?; // Persist LSP peer to make sure we reconnect on restart. - self.runtime.block_on(self.peer_store.add_peer(peer_info))?; + self.peer_store.add_peer(peer_info).await?; Ok(invoice) } @@ -285,7 +279,7 @@ impl Bolt11Payment { /// /// If `route_parameters` are provided they will override the default as well as the /// node-wide parameters configured via [`Config::route_parameters`] on a per-field basis. - pub fn send( + pub async fn send( &self, invoice: &Bolt11Invoice, route_parameters: Option, ) -> Result { if !*self.is_running.read().expect("lock") { @@ -295,7 +289,7 @@ impl Bolt11Payment { let invoice = maybe_deref(invoice); let payment_hash = invoice.payment_hash(); let payment_id = PaymentId(invoice.payment_hash().0); - if let Some(payment) = self.payment_store.get(&payment_id) { + if let Some(payment) = self.payment_store.get(&payment_id).await { if payment.status == PaymentStatus::Pending || payment.status == PaymentStatus::Succeeded { @@ -341,7 +335,7 @@ impl Bolt11Payment { PaymentStatus::Pending, ); - self.runtime.block_on(self.payment_store.insert(payment))?; + self.payment_store.insert(payment).await?; Ok(payment_id) }, @@ -371,7 +365,7 @@ impl Bolt11Payment { PaymentStatus::Failed, ); - self.runtime.block_on(self.payment_store.insert(payment))?; + self.payment_store.insert(payment).await?; Err(Error::PaymentSendingFailed) }, } @@ -388,7 +382,7 @@ impl Bolt11Payment { /// /// If `route_parameters` are provided they will override the default as well as the /// node-wide parameters configured via [`Config::route_parameters`] on a per-field basis. - pub fn send_using_amount( + pub async fn send_using_amount( &self, invoice: &Bolt11Invoice, amount_msat: u64, route_parameters: Option, ) -> Result { @@ -408,7 +402,7 @@ impl Bolt11Payment { let payment_hash = invoice.payment_hash(); let payment_id = PaymentId(invoice.payment_hash().0); - if let Some(payment) = self.payment_store.get(&payment_id) { + if let Some(payment) = self.payment_store.get(&payment_id).await { if payment.status == PaymentStatus::Pending || payment.status == PaymentStatus::Succeeded { @@ -457,7 +451,7 @@ impl Bolt11Payment { PaymentDirection::Outbound, PaymentStatus::Pending, ); - self.runtime.block_on(self.payment_store.insert(payment))?; + self.payment_store.insert(payment).await?; Ok(payment_id) }, @@ -488,7 +482,7 @@ impl Bolt11Payment { PaymentStatus::Failed, ); - self.runtime.block_on(self.payment_store.insert(payment))?; + self.payment_store.insert(payment).await?; Err(Error::PaymentSendingFailed) }, } @@ -512,7 +506,7 @@ impl Bolt11Payment { /// [`receive_variable_amount_for_hash`]: Self::receive_variable_amount_for_hash /// [`PaymentClaimable`]: crate::Event::PaymentClaimable /// [`PaymentReceived`]: crate::Event::PaymentReceived - pub fn claim_for_hash( + pub async fn claim_for_hash( &self, payment_hash: PaymentHash, claimable_amount_msat: u64, preimage: PaymentPreimage, ) -> Result<(), Error> { let payment_id = PaymentId(payment_hash.0); @@ -528,7 +522,7 @@ impl Bolt11Payment { return Err(Error::InvalidPaymentPreimage); } - if let Some(details) = self.payment_store.get(&payment_id) { + if let Some(details) = self.payment_store.get(&payment_id).await { // For payments requested via `receive*_via_jit_channel_for_hash()` // `skimmed_fee_msat` held by LSP must be taken into account. let skimmed_fee_msat = match details.kind { @@ -574,7 +568,7 @@ impl Bolt11Payment { /// [`receive_for_hash`]: Self::receive_for_hash /// [`receive_variable_amount_for_hash`]: Self::receive_variable_amount_for_hash /// [`PaymentClaimable`]: crate::Event::PaymentClaimable - pub fn fail_for_hash(&self, payment_hash: PaymentHash) -> Result<(), Error> { + pub async fn fail_for_hash(&self, payment_hash: PaymentHash) -> Result<(), Error> { let payment_id = PaymentId(payment_hash.0); let update = PaymentDetailsUpdate { @@ -582,7 +576,7 @@ impl Bolt11Payment { ..PaymentDetailsUpdate::new(payment_id) }; - match self.runtime.block_on(self.payment_store.update(update)) { + match self.payment_store.update(update).await { Ok(DataStoreUpdateResult::Updated) | Ok(DataStoreUpdateResult::Unchanged) => (), Ok(DataStoreUpdateResult::NotFound) => { log_error!( @@ -611,11 +605,12 @@ impl Bolt11Payment { /// given. /// /// The inbound payment will be automatically claimed upon arrival. - pub fn receive( + pub async fn receive( &self, amount_msat: u64, description: &Bolt11InvoiceDescription, expiry_secs: u32, ) -> Result { let description = maybe_try_convert_enum(description)?; - let invoice = self.receive_inner(Some(amount_msat), &description, expiry_secs, None)?; + let invoice = + self.receive_inner(Some(amount_msat), &description, expiry_secs, None).await?; Ok(maybe_wrap(invoice)) } @@ -633,13 +628,14 @@ impl Bolt11Payment { /// [`PaymentClaimable`]: crate::Event::PaymentClaimable /// [`claim_for_hash`]: Self::claim_for_hash /// [`fail_for_hash`]: Self::fail_for_hash - pub fn receive_for_hash( + pub async fn receive_for_hash( &self, amount_msat: u64, description: &Bolt11InvoiceDescription, expiry_secs: u32, payment_hash: PaymentHash, ) -> Result { let description = maybe_try_convert_enum(description)?; - let invoice = - self.receive_inner(Some(amount_msat), &description, expiry_secs, Some(payment_hash))?; + let invoice = self + .receive_inner(Some(amount_msat), &description, expiry_secs, Some(payment_hash)) + .await?; Ok(maybe_wrap(invoice)) } @@ -647,11 +643,11 @@ impl Bolt11Payment { /// amount is to be determined by the user, also known as a "zero-amount" invoice. /// /// The inbound payment will be automatically claimed upon arrival. - pub fn receive_variable_amount( + pub async fn receive_variable_amount( &self, description: &Bolt11InvoiceDescription, expiry_secs: u32, ) -> Result { let description = maybe_try_convert_enum(description)?; - let invoice = self.receive_inner(None, &description, expiry_secs, None)?; + let invoice = self.receive_inner(None, &description, expiry_secs, None).await?; Ok(maybe_wrap(invoice)) } @@ -669,11 +665,12 @@ impl Bolt11Payment { /// [`PaymentClaimable`]: crate::Event::PaymentClaimable /// [`claim_for_hash`]: Self::claim_for_hash /// [`fail_for_hash`]: Self::fail_for_hash - pub fn receive_variable_amount_for_hash( + pub async fn receive_variable_amount_for_hash( &self, description: &Bolt11InvoiceDescription, expiry_secs: u32, payment_hash: PaymentHash, ) -> Result { let description = maybe_try_convert_enum(description)?; - let invoice = self.receive_inner(None, &description, expiry_secs, Some(payment_hash))?; + let invoice = + self.receive_inner(None, &description, expiry_secs, Some(payment_hash)).await?; Ok(maybe_wrap(invoice)) } @@ -687,19 +684,21 @@ impl Bolt11Payment { /// channel to us. We'll use its cheapest offer otherwise. /// /// [LSPS2]: https://github.com/BitcoinAndLightningLayerSpecs/lsp/blob/main/LSPS2/README.md - pub fn receive_via_jit_channel( + pub async fn receive_via_jit_channel( &self, amount_msat: u64, description: &Bolt11InvoiceDescription, expiry_secs: u32, max_total_lsp_fee_limit_msat: Option, ) -> Result { let description = maybe_try_convert_enum(description)?; - let invoice = self.receive_via_jit_channel_inner( - Some(amount_msat), - &description, - expiry_secs, - max_total_lsp_fee_limit_msat, - None, - None, - )?; + let invoice = self + .receive_via_jit_channel_inner( + Some(amount_msat), + &description, + expiry_secs, + max_total_lsp_fee_limit_msat, + None, + None, + ) + .await?; Ok(maybe_wrap(invoice)) } @@ -726,19 +725,21 @@ impl Bolt11Payment { /// [`claim_for_hash`]: Self::claim_for_hash /// [`fail_for_hash`]: Self::fail_for_hash /// [`counterparty_skimmed_fee_msat`]: crate::payment::PaymentKind::Bolt11::counterparty_skimmed_fee_msat - pub fn receive_via_jit_channel_for_hash( + pub async fn receive_via_jit_channel_for_hash( &self, amount_msat: u64, description: &Bolt11InvoiceDescription, expiry_secs: u32, max_total_lsp_fee_limit_msat: Option, payment_hash: PaymentHash, ) -> Result { let description = maybe_try_convert_enum(description)?; - let invoice = self.receive_via_jit_channel_inner( - Some(amount_msat), - &description, - expiry_secs, - max_total_lsp_fee_limit_msat, - None, - Some(payment_hash), - )?; + let invoice = self + .receive_via_jit_channel_inner( + Some(amount_msat), + &description, + expiry_secs, + max_total_lsp_fee_limit_msat, + None, + Some(payment_hash), + ) + .await?; Ok(maybe_wrap(invoice)) } @@ -753,19 +754,21 @@ impl Bolt11Payment { /// We'll use its cheapest offer otherwise. /// /// [LSPS2]: https://github.com/BitcoinAndLightningLayerSpecs/lsp/blob/main/LSPS2/README.md - pub fn receive_variable_amount_via_jit_channel( + pub async fn receive_variable_amount_via_jit_channel( &self, description: &Bolt11InvoiceDescription, expiry_secs: u32, max_proportional_lsp_fee_limit_ppm_msat: Option, ) -> Result { let description = maybe_try_convert_enum(description)?; - let invoice = self.receive_via_jit_channel_inner( - None, - &description, - expiry_secs, - None, - max_proportional_lsp_fee_limit_ppm_msat, - None, - )?; + let invoice = self + .receive_via_jit_channel_inner( + None, + &description, + expiry_secs, + None, + max_proportional_lsp_fee_limit_ppm_msat, + None, + ) + .await?; Ok(maybe_wrap(invoice)) } @@ -793,19 +796,21 @@ impl Bolt11Payment { /// [`claim_for_hash`]: Self::claim_for_hash /// [`fail_for_hash`]: Self::fail_for_hash /// [`counterparty_skimmed_fee_msat`]: crate::payment::PaymentKind::Bolt11::counterparty_skimmed_fee_msat - pub fn receive_variable_amount_via_jit_channel_for_hash( + pub async fn receive_variable_amount_via_jit_channel_for_hash( &self, description: &Bolt11InvoiceDescription, expiry_secs: u32, max_proportional_lsp_fee_limit_ppm_msat: Option, payment_hash: PaymentHash, ) -> Result { let description = maybe_try_convert_enum(description)?; - let invoice = self.receive_via_jit_channel_inner( - None, - &description, - expiry_secs, - None, - max_proportional_lsp_fee_limit_ppm_msat, - Some(payment_hash), - )?; + let invoice = self + .receive_via_jit_channel_inner( + None, + &description, + expiry_secs, + None, + max_proportional_lsp_fee_limit_ppm_msat, + Some(payment_hash), + ) + .await?; Ok(maybe_wrap(invoice)) } diff --git a/src/payment/bolt12.rs b/src/payment/bolt12.rs index d79aca6c24..6658a18c30 100644 --- a/src/payment/bolt12.rs +++ b/src/payment/bolt12.rs @@ -29,7 +29,6 @@ use crate::error::Error; use crate::ffi::{maybe_deref, maybe_wrap}; use crate::logger::{log_error, log_info, LdkLogger, Logger}; use crate::payment::store::{PaymentDetails, PaymentDirection, PaymentKind, PaymentStatus}; -use crate::runtime::Runtime; use crate::types::{ChannelManager, KeysManager, PaymentStore}; #[cfg(not(feature = "uniffi"))] @@ -60,7 +59,6 @@ type HumanReadableName = Arc; /// [`Node::bolt12_payment`]: crate::Node::bolt12_payment #[cfg_attr(feature = "uniffi", derive(uniffi::Object))] pub struct Bolt12Payment { - runtime: Arc, channel_manager: Arc, keys_manager: Arc, payment_store: Arc, @@ -72,13 +70,12 @@ pub struct Bolt12Payment { impl Bolt12Payment { pub(crate) fn new( - runtime: Arc, channel_manager: Arc, + _runtime: Arc, channel_manager: Arc, keys_manager: Arc, payment_store: Arc, config: Arc, is_running: Arc>, logger: Arc, async_payments_role: Option, ) -> Self { Self { - runtime, channel_manager, keys_manager, payment_store, @@ -89,7 +86,7 @@ impl Bolt12Payment { } } - pub(crate) fn send_using_amount_inner( + pub(crate) async fn send_using_amount_inner( &self, offer: &Offer, amount_msat: u64, quantity: Option, payer_note: Option, route_parameters: Option, hrn: Option, ) -> Result { @@ -167,7 +164,7 @@ impl Bolt12Payment { PaymentDirection::Outbound, PaymentStatus::Pending, ); - self.runtime.block_on(self.payment_store.insert(payment))?; + self.payment_store.insert(payment).await?; Ok(payment_id) }, @@ -192,7 +189,7 @@ impl Bolt12Payment { PaymentDirection::Outbound, PaymentStatus::Failed, ); - self.runtime.block_on(self.payment_store.insert(payment))?; + self.payment_store.insert(payment).await?; Err(Error::PaymentSendingFailed) }, } @@ -264,7 +261,7 @@ impl Bolt12Payment { /// /// If `route_parameters` are provided they will override the default as well as the /// node-wide parameters configured via [`Config::route_parameters`] on a per-field basis. - pub fn send( + pub async fn send( &self, offer: &Offer, quantity: Option, payer_note: Option, route_parameters: Option, ) -> Result { @@ -329,7 +326,7 @@ impl Bolt12Payment { PaymentDirection::Outbound, PaymentStatus::Pending, ); - self.runtime.block_on(self.payment_store.insert(payment))?; + self.payment_store.insert(payment).await?; Ok(payment_id) }, @@ -354,7 +351,7 @@ impl Bolt12Payment { PaymentDirection::Outbound, PaymentStatus::Failed, ); - self.runtime.block_on(self.payment_store.insert(payment))?; + self.payment_store.insert(payment).await?; Err(Error::InvoiceRequestCreationFailed) }, } @@ -374,18 +371,20 @@ impl Bolt12Payment { /// /// If `route_parameters` are provided they will override the default as well as the /// node-wide parameters configured via [`Config::route_parameters`] on a per-field basis. - pub fn send_using_amount( + pub async fn send_using_amount( &self, offer: &Offer, amount_msat: u64, quantity: Option, payer_note: Option, route_parameters: Option, ) -> Result { - let payment_id = self.send_using_amount_inner( - offer, - amount_msat, - quantity, - payer_note, - route_parameters, - None, - )?; + let payment_id = self + .send_using_amount_inner( + offer, + amount_msat, + quantity, + payer_note, + route_parameters, + None, + ) + .await?; Ok(payment_id) } @@ -430,7 +429,7 @@ impl Bolt12Payment { /// /// [`Refund`]: lightning::offers::refund::Refund /// [`Bolt12Invoice`]: lightning::offers::invoice::Bolt12Invoice - pub fn request_refund_payment(&self, refund: &Refund) -> Result { + pub async fn request_refund_payment(&self, refund: &Refund) -> Result { if !*self.is_running.read().expect("lock") { return Err(Error::NotRunning); } @@ -461,7 +460,7 @@ impl Bolt12Payment { PaymentStatus::Pending, ); - self.runtime.block_on(self.payment_store.insert(payment))?; + self.payment_store.insert(payment).await?; Ok(maybe_wrap(invoice)) } @@ -472,7 +471,7 @@ impl Bolt12Payment { /// node-wide parameters configured via [`Config::route_parameters`] on a per-field basis. /// /// [`Refund`]: lightning::offers::refund::Refund - pub fn initiate_refund( + pub async fn initiate_refund( &self, amount_msat: u64, expiry_secs: u32, quantity: Option, payer_note: Option, route_parameters: Option, ) -> Result { @@ -530,7 +529,7 @@ impl Bolt12Payment { PaymentStatus::Pending, ); - self.runtime.block_on(self.payment_store.insert(payment))?; + self.payment_store.insert(payment).await?; Ok(maybe_wrap(refund)) } diff --git a/src/payment/onchain.rs b/src/payment/onchain.rs index 9d00968fcc..7c8334d7bb 100644 --- a/src/payment/onchain.rs +++ b/src/payment/onchain.rs @@ -135,10 +135,10 @@ impl OnchainPayment { /// higher fee, resulting in faster confirmation potential. /// /// Returns the [`Txid`] of the new replacement transaction if successful. - pub fn bump_fee_rbf( + pub async fn bump_fee_rbf( &self, payment_id: PaymentId, fee_rate: Option, ) -> Result { let fee_rate_opt = maybe_map_fee_rate_opt!(fee_rate); - self.wallet.bump_fee_rbf(payment_id, fee_rate_opt) + self.wallet.bump_fee_rbf(payment_id, fee_rate_opt).await } } diff --git a/src/payment/spontaneous.rs b/src/payment/spontaneous.rs index 45dab644d4..3e62ac591e 100644 --- a/src/payment/spontaneous.rs +++ b/src/payment/spontaneous.rs @@ -22,7 +22,6 @@ use crate::config::{Config, LDK_PAYMENT_RETRY_TIMEOUT}; use crate::error::Error; use crate::logger::{log_error, log_info, LdkLogger, Logger}; use crate::payment::store::{PaymentDetails, PaymentDirection, PaymentKind, PaymentStatus}; -use crate::runtime::Runtime; use crate::types::{ChannelManager, CustomTlvRecord, KeysManager, PaymentStore}; // The default `final_cltv_expiry_delta` we apply when not set. @@ -35,7 +34,6 @@ const LDK_DEFAULT_FINAL_CLTV_EXPIRY_DELTA: u32 = 144; /// [`Node::spontaneous_payment`]: crate::Node::spontaneous_payment #[cfg_attr(feature = "uniffi", derive(uniffi::Object))] pub struct SpontaneousPayment { - runtime: Arc, channel_manager: Arc, keys_manager: Arc, payment_store: Arc, @@ -46,14 +44,14 @@ pub struct SpontaneousPayment { impl SpontaneousPayment { pub(crate) fn new( - runtime: Arc, channel_manager: Arc, + _runtime: Arc, channel_manager: Arc, keys_manager: Arc, payment_store: Arc, config: Arc, is_running: Arc>, logger: Arc, ) -> Self { - Self { runtime, channel_manager, keys_manager, payment_store, config, is_running, logger } + Self { channel_manager, keys_manager, payment_store, config, is_running, logger } } - fn send_inner( + async fn send_inner( &self, amount_msat: u64, node_id: PublicKey, route_parameters: Option, custom_tlvs: Option>, preimage: Option, @@ -68,7 +66,7 @@ impl SpontaneousPayment { let payment_hash = PaymentHash::from(payment_preimage); let payment_id = PaymentId(payment_hash.0); - if let Some(payment) = self.payment_store.get(&payment_id) { + if let Some(payment) = self.payment_store.get(&payment_id).await { if payment.status == PaymentStatus::Pending || payment.status == PaymentStatus::Succeeded { @@ -132,7 +130,7 @@ impl SpontaneousPayment { PaymentDirection::Outbound, PaymentStatus::Pending, ); - self.runtime.block_on(self.payment_store.insert(payment))?; + self.payment_store.insert(payment).await?; Ok(payment_id) }, @@ -155,7 +153,7 @@ impl SpontaneousPayment { PaymentStatus::Failed, ); - self.runtime.block_on(self.payment_store.insert(payment))?; + self.payment_store.insert(payment).await?; Err(Error::PaymentSendingFailed) }, } @@ -170,35 +168,36 @@ impl SpontaneousPayment { /// /// If `route_parameters` are provided they will override the default as well as the /// node-wide parameters configured via [`Config::route_parameters`] on a per-field basis. - pub fn send( + pub async fn send( &self, amount_msat: u64, node_id: PublicKey, route_parameters: Option, ) -> Result { - self.send_inner(amount_msat, node_id, route_parameters, None, None) + self.send_inner(amount_msat, node_id, route_parameters, None, None).await } /// Send a spontaneous payment including a list of custom TLVs. - pub fn send_with_custom_tlvs( + pub async fn send_with_custom_tlvs( &self, amount_msat: u64, node_id: PublicKey, route_parameters: Option, custom_tlvs: Vec, ) -> Result { - self.send_inner(amount_msat, node_id, route_parameters, Some(custom_tlvs), None) + self.send_inner(amount_msat, node_id, route_parameters, Some(custom_tlvs), None).await } /// Send a spontaneous payment with custom preimage - pub fn send_with_preimage( + pub async fn send_with_preimage( &self, amount_msat: u64, node_id: PublicKey, preimage: PaymentPreimage, route_parameters: Option, ) -> Result { - self.send_inner(amount_msat, node_id, route_parameters, None, Some(preimage)) + self.send_inner(amount_msat, node_id, route_parameters, None, Some(preimage)).await } /// Send a spontaneous payment with custom preimage including a list of custom TLVs. - pub fn send_with_preimage_and_custom_tlvs( + pub async fn send_with_preimage_and_custom_tlvs( &self, amount_msat: u64, node_id: PublicKey, custom_tlvs: Vec, preimage: PaymentPreimage, route_parameters: Option, ) -> Result { self.send_inner(amount_msat, node_id, route_parameters, Some(custom_tlvs), Some(preimage)) + .await } /// Sends payment probes over all paths of a route that would be used to pay the given diff --git a/src/payment/unified.rs b/src/payment/unified.rs index 3708afe8e6..92b2858ff0 100644 --- a/src/payment/unified.rs +++ b/src/payment/unified.rs @@ -126,7 +126,7 @@ impl UnifiedPayment { /// /// [BOLT 11]: https://github.com/lightning/bolts/blob/master/11-payment-encoding.md /// [BOLT 12]: https://github.com/lightning/bolts/blob/master/12-offer-encoding.md - pub fn receive( + pub async fn receive( &self, amount_sats: u64, description: &str, expiry_sec: u32, ) -> Result { let onchain_address = self.onchain_payment.new_address()?; @@ -145,12 +145,11 @@ impl UnifiedPayment { let invoice_description = Bolt11InvoiceDescription::Direct( Description::new(description.to_string()).map_err(|_| Error::InvoiceCreationFailed)?, ); - let bolt11_invoice = match self.bolt11_invoice.receive_inner( - Some(amount_msats), - &invoice_description, - expiry_sec, - None, - ) { + let bolt11_invoice = match self + .bolt11_invoice + .receive_inner(Some(amount_msats), &invoice_description, expiry_sec, None) + .await + { Ok(invoice) => Some(invoice), Err(e) => { log_error!(self.logger, "Failed to create invoice {}", e); @@ -286,15 +285,15 @@ impl UnifiedPayment { }; let payment_result = if let Ok(hrn) = HumanReadableName::from_encoded(uri_str) { - let hrn = maybe_wrap(hrn.clone()); - self.bolt12_payment.send_using_amount_inner(&offer, amount_msat.unwrap_or(0), None, None, route_parameters, Some(hrn)) - } else if let Some(amount_msat) = amount_msat { - self.bolt12_payment.send_using_amount(&offer, amount_msat, None, None, route_parameters) - } else { - self.bolt12_payment.send(&offer, None, None, route_parameters) - } - .map_err(|e| { - log_error!(self.logger, "Failed to send BOLT12 offer: {:?}. This is part of a unified payment. Falling back to the BOLT11 invoice.", e); + let hrn = maybe_wrap(hrn.clone()); + self.bolt12_payment.send_using_amount_inner(&offer, amount_msat.unwrap_or(0), None, None, route_parameters, Some(hrn)).await + } else if let Some(amount_msat) = amount_msat { + self.bolt12_payment.send_using_amount(&offer, amount_msat, None, None, route_parameters).await + } else { + self.bolt12_payment.send(&offer, None, None, route_parameters).await + } + .map_err(|e| { + log_error!(self.logger, "Failed to send BOLT12 offer: {:?}. This is part of a unified payment. Falling back to the BOLT11 invoice.", e); e }); @@ -304,11 +303,11 @@ impl UnifiedPayment { }, PaymentMethod::LightningBolt11(invoice) => { let invoice = maybe_wrap(invoice.clone()); - let payment_result = self.bolt11_invoice.send(&invoice, route_parameters) - .map_err(|e| { - log_error!(self.logger, "Failed to send BOLT11 invoice: {:?}. This is part of a unified payment. Falling back to the on-chain transaction.", e); - e - }); + let payment_result = + self.bolt11_invoice.send(&invoice, route_parameters).await.map_err(|e| { + log_error!(self.logger, "Failed to send BOLT11 invoice: {:?}. This is part of a unified payment. Falling back to the on-chain transaction.", e); + e + }); if let Ok(payment_id) = payment_result { return Ok(UnifiedPaymentResult::Bolt11 { payment_id }); diff --git a/src/peer_store.rs b/src/peer_store.rs index 8037f93471..cccf2d3b50 100644 --- a/src/peer_store.rs +++ b/src/peer_store.rs @@ -7,12 +7,13 @@ use std::collections::HashMap; use std::ops::Deref; -use std::sync::{Arc, RwLock}; +use std::sync::Arc; use bitcoin::secp256k1::PublicKey; use lightning::impl_writeable_tlv_based; use lightning::util::persist::KVStore; use lightning::util::ser::{Readable, ReadableArgs, Writeable, Writer}; +use tokio::sync::RwLock; use crate::io::{ PEER_INFO_PERSISTENCE_KEY, PEER_INFO_PERSISTENCE_PRIMARY_NAMESPACE, @@ -27,7 +28,6 @@ where L::Target: LdkLogger, { peers: RwLock>, - mutation_lock: tokio::sync::Mutex<()>, kv_store: Arc, logger: L, } @@ -38,52 +38,37 @@ where { pub(crate) fn new(kv_store: Arc, logger: L) -> Self { let peers = RwLock::new(HashMap::new()); - let mutation_lock = tokio::sync::Mutex::new(()); - Self { peers, mutation_lock, kv_store, logger } + Self { peers, kv_store, logger } } pub(crate) async fn add_peer(&self, peer_info: PeerInfo) -> Result<(), Error> { - let _guard = self.mutation_lock.lock().await; - let data = { - let mut locked_peers = self.peers.write().expect("lock"); - if locked_peers.contains_key(&peer_info.node_id) { - return Ok(()); - } - locked_peers.insert(peer_info.node_id, peer_info); - PeerStoreSerWrapper(&locked_peers).encode() - }; - self.persist_peers(data).await + let mut locked_peers = self.peers.write().await; + if locked_peers.contains_key(&peer_info.node_id) { + return Ok(()); + } + + locked_peers.insert(peer_info.node_id, peer_info); + self.persist_peers(&*locked_peers).await } pub(crate) async fn remove_peer(&self, node_id: &PublicKey) -> Result<(), Error> { - let _guard = self.mutation_lock.lock().await; - let data = { - let mut locked_peers = self.peers.write().expect("lock"); - locked_peers.remove(node_id); - PeerStoreSerWrapper(&locked_peers).encode() - }; - self.persist_peers(data).await + let mut locked_peers = self.peers.write().await; + locked_peers.remove(node_id); + self.persist_peers(&*locked_peers).await } - /// Returns the current in-memory peer set. - /// - /// The async mutation lock serializes `add_peer` and `remove_peer`, but this synchronous - /// reader cannot wait on it. Until peer-store reads are async, callers may observe peer - /// changes that are still being persisted. - pub(crate) fn list_peers(&self) -> Vec { - self.peers.read().expect("lock").values().cloned().collect() + pub(crate) async fn list_peers(&self) -> Vec { + self.peers.read().await.values().cloned().collect() } - /// Returns the current in-memory peer info for `node_id`. - /// - /// The async mutation lock serializes `add_peer` and `remove_peer`, but this synchronous - /// reader cannot wait on it. Until peer-store reads are async, callers may observe peer - /// changes that are still being persisted. - pub(crate) fn get_peer(&self, node_id: &PublicKey) -> Option { - self.peers.read().expect("lock").get(node_id).cloned() + pub(crate) async fn get_peer(&self, node_id: &PublicKey) -> Option { + self.peers.read().await.get(node_id).cloned() } - async fn persist_peers(&self, data: Vec) -> Result<(), Error> { + async fn persist_peers( + &self, locked_peers: &HashMap, + ) -> Result<(), Error> { + let data = PeerStoreSerWrapper(&*locked_peers).encode(); KVStore::write( &*self.kv_store, PEER_INFO_PERSISTENCE_PRIMARY_NAMESPACE, @@ -118,8 +103,7 @@ where let (kv_store, logger) = args; let read_peers: PeerStoreDeserWrapper = Readable::read(reader)?; let peers: RwLock> = RwLock::new(read_peers.0); - let mutation_lock = tokio::sync::Mutex::new(()); - Ok(Self { peers, mutation_lock, kv_store, logger }) + Ok(Self { peers, kv_store, logger }) } } @@ -210,9 +194,9 @@ mod tests { let deser_peer_store = PeerStore::read(&mut &persisted_bytes[..], (Arc::clone(&store), logger)).unwrap(); - let peers = deser_peer_store.list_peers(); + let peers = deser_peer_store.list_peers().await; assert_eq!(peers.len(), 1); assert_eq!(peers[0], expected_peer_info); - assert_eq!(deser_peer_store.get_peer(&node_id), Some(expected_peer_info)); + assert_eq!(deser_peer_store.get_peer(&node_id).await, Some(expected_peer_info)); } } diff --git a/src/scoring.rs b/src/scoring.rs index 401d9c3f1f..8056d3f056 100644 --- a/src/scoring.rs +++ b/src/scoring.rs @@ -4,6 +4,7 @@ use std::time::SystemTime; use lightning::routing::scoring::ChannelLiquidities; use lightning::util::ser::Readable; use lightning::{log_error, log_info, log_trace}; +use tokio::sync::RwLock; use crate::config::{ EXTERNAL_PATHFINDING_SCORES_MAX_SIZE, EXTERNAL_PATHFINDING_SCORES_SYNC_INTERVAL, diff --git a/src/wallet/mod.rs b/src/wallet/mod.rs index 76f2aa9ce6..c3e77dcc78 100644 --- a/src/wallet/mod.rs +++ b/src/wallet/mod.rs @@ -151,70 +151,73 @@ impl Wallet { BlockLocator { block_hash: checkpoint.hash(), height: checkpoint.height(), previous_blocks } } - pub(crate) fn apply_update(&self, update: impl Into) -> Result<(), Error> { - let mut locked_wallet = self.inner.lock().expect("lock"); - match locked_wallet.apply_update_events(update) { - Ok(events) => { - self.update_payment_store(&mut *locked_wallet, events).map_err(|e| { - log_error!(self.logger, "Failed to update payment store: {}", e); - Error::PersistenceFailed - })?; + pub(crate) async fn apply_update(&self, update: impl Into) -> Result<(), Error> { + let events = { + let mut locked_wallet = self.inner.lock().expect("lock"); + match locked_wallet.apply_update_events(update) { + Ok(events) => events, + Err(e) => { + log_error!(self.logger, "Sync failed due to chain connection error: {}", e); + return Err(Error::WalletOperationFailed); + }, + } + }; - let mut locked_persister = self.persister.lock().expect("lock"); - self.runtime.block_on(locked_wallet.persist_async(&mut locked_persister)).map_err( - |e| { - log_error!(self.logger, "Failed to persist wallet: {}", e); - Error::PersistenceFailed - }, - )?; + self.update_payment_store(events).await.map_err(|e| { + log_error!(self.logger, "Failed to update payment store: {}", e); + Error::PersistenceFailed + })?; - Ok(()) - }, - Err(e) => { - log_error!(self.logger, "Sync failed due to chain connection error: {}", e); - Err(Error::WalletOperationFailed) - }, - } + let mut locked_wallet = self.inner.lock().expect("lock"); + let mut locked_persister = self.persister.lock().expect("lock"); + self.runtime.block_on(locked_wallet.persist_async(&mut locked_persister)).map_err(|e| { + log_error!(self.logger, "Failed to persist wallet: {}", e); + Error::PersistenceFailed + })?; + + Ok(()) } - pub(crate) fn apply_mempool_txs( + pub(crate) async fn apply_mempool_txs( &self, unconfirmed_txs: Vec<(Transaction, u64)>, evicted_txids: Vec<(Txid, u64)>, ) -> Result<(), Error> { if unconfirmed_txs.is_empty() && evicted_txids.is_empty() { return Ok(()); } - let mut locked_wallet = self.inner.lock().expect("lock"); - - let chain_tip1 = locked_wallet.latest_checkpoint().block_id(); - let wallet_txs1 = locked_wallet - .transactions() - .map(|wtx| (wtx.tx_node.txid, (wtx.tx_node.tx.clone(), wtx.chain_position))) - .collect::, bdk_chain::ChainPosition), - >>(); - - locked_wallet.apply_unconfirmed_txs(unconfirmed_txs); - locked_wallet.apply_evicted_txs(evicted_txids); - - let chain_tip2 = locked_wallet.latest_checkpoint().block_id(); - let wallet_txs2 = locked_wallet - .transactions() - .map(|wtx| (wtx.tx_node.txid, (wtx.tx_node.tx.clone(), wtx.chain_position))) - .collect::, bdk_chain::ChainPosition), - >>(); + let events = { + let mut locked_wallet = self.inner.lock().expect("lock"); - let events = - wallet_events(&mut *locked_wallet, chain_tip1, chain_tip2, wallet_txs1, wallet_txs2); + let chain_tip1 = locked_wallet.latest_checkpoint().block_id(); + let wallet_txs1 = locked_wallet + .transactions() + .map(|wtx| (wtx.tx_node.txid, (wtx.tx_node.tx.clone(), wtx.chain_position))) + .collect::, bdk_chain::ChainPosition), + >>(); + + locked_wallet.apply_unconfirmed_txs(unconfirmed_txs); + locked_wallet.apply_evicted_txs(evicted_txids); + + let chain_tip2 = locked_wallet.latest_checkpoint().block_id(); + let wallet_txs2 = locked_wallet + .transactions() + .map(|wtx| (wtx.tx_node.txid, (wtx.tx_node.tx.clone(), wtx.chain_position))) + .collect::, bdk_chain::ChainPosition), + >>(); + + wallet_events(&mut *locked_wallet, chain_tip1, chain_tip2, wallet_txs1, wallet_txs2) + }; - self.update_payment_store(&mut *locked_wallet, events).map_err(|e| { + self.update_payment_store(events).await.map_err(|e| { log_error!(self.logger, "Failed to update payment store: {}", e); Error::PersistenceFailed })?; + let mut locked_wallet = self.inner.lock().expect("lock"); let mut locked_persister = self.persister.lock().expect("lock"); self.runtime.block_on(locked_wallet.persist_async(&mut locked_persister)).map_err(|e| { log_error!(self.logger, "Failed to persist wallet: {}", e); @@ -224,10 +227,7 @@ impl Wallet { Ok(()) } - fn update_payment_store<'a>( - &self, locked_wallet: &'a mut PersistedWallet, - mut events: Vec, - ) -> Result<(), Error> { + async fn update_payment_store(&self, mut events: Vec) -> Result<(), Error> { if events.is_empty() { return Ok(()); } @@ -255,14 +255,7 @@ impl Wallet { for event in events { match event { WalletEvent::TxConfirmed { txid, tx, block_time, .. } => { - let cur_height = locked_wallet.latest_checkpoint().height(); let confirmation_height = block_time.block_id.height; - let payment_status = if cur_height >= confirmation_height + ANTI_REORG_DELAY - 1 - { - PaymentStatus::Succeeded - } else { - PaymentStatus::Pending - }; let confirmation_status = ConfirmationStatus::Confirmed { block_hash: block_time.block_id.hash, @@ -272,31 +265,42 @@ impl Wallet { let payment_id = self .find_payment_by_txid(txid) + .await .unwrap_or_else(|| PaymentId(txid.to_byte_array())); - let payment = self.create_payment_from_tx( - locked_wallet, - txid, - payment_id, - &tx, - payment_status, - confirmation_status, - ); + let payment = { + let locked_wallet = self.inner.lock().expect("lock"); + let cur_height = locked_wallet.latest_checkpoint().height(); + let payment_status = + if cur_height >= confirmation_height + ANTI_REORG_DELAY - 1 { + PaymentStatus::Succeeded + } else { + PaymentStatus::Pending + }; + + self.create_payment_from_tx( + &locked_wallet, + txid, + payment_id, + &tx, + payment_status, + confirmation_status, + ) + }; - self.runtime.block_on(self.payment_store.insert_or_update(payment.clone()))?; + self.payment_store.insert_or_update(payment.clone()).await?; - if payment_status == PaymentStatus::Pending { + if payment.status == PaymentStatus::Pending { let pending_payment = self.create_pending_payment_from_tx(payment, Vec::new()); - self.runtime.block_on( - self.pending_payment_store.insert_or_update(pending_payment), - )?; + self.pending_payment_store.insert_or_update(pending_payment).await?; } }, WalletEvent::ChainTipChanged { new_tip, .. } => { - let pending_payments: Vec = - self.pending_payment_store.list_filter(|p| { + let pending_payments: Vec = self + .pending_payment_store + .list_filter(|p| { debug_assert!( p.details.status == PaymentStatus::Pending, "Non-pending payment {:?} found in pending store", @@ -304,7 +308,8 @@ impl Wallet { ); p.details.status == PaymentStatus::Pending && matches!(p.details.kind, PaymentKind::Onchain { .. }) - }); + }) + .await; let mut unconfirmed_outbound_txids: Vec = Vec::new(); @@ -317,11 +322,8 @@ impl Wallet { let payment_id = payment.details.id; if new_tip.height >= height + ANTI_REORG_DELAY - 1 { payment.details.status = PaymentStatus::Succeeded; - self.runtime.block_on( - self.payment_store.insert_or_update(payment.details), - )?; - self.runtime - .block_on(self.pending_payment_store.remove(&payment_id))?; + self.payment_store.insert_or_update(payment.details).await?; + self.pending_payment_store.remove(&payment_id).await?; } }, PaymentKind::Onchain { @@ -338,6 +340,7 @@ impl Wallet { let txs_to_broadcast: Vec = unconfirmed_outbound_txids .iter() .filter_map(|txid| { + let locked_wallet = self.inner.lock().expect("lock"); locked_wallet.tx_details(*txid).map(|d| (*d.tx).clone()) }) .collect(); @@ -365,24 +368,27 @@ impl Wallet { WalletEvent::TxUnconfirmed { txid, tx, old_block_time: None } => { let payment_id = self .find_payment_by_txid(txid) + .await .unwrap_or_else(|| PaymentId(txid.to_byte_array())); - let payment = self.create_payment_from_tx( - locked_wallet, - txid, - payment_id, - &tx, - PaymentStatus::Pending, - ConfirmationStatus::Unconfirmed, - ); + let payment = { + let locked_wallet = self.inner.lock().expect("lock"); + self.create_payment_from_tx( + &locked_wallet, + txid, + payment_id, + &tx, + PaymentStatus::Pending, + ConfirmationStatus::Unconfirmed, + ) + }; let pending_payment = self.create_pending_payment_from_tx(payment.clone(), Vec::new()); - self.runtime.block_on(self.payment_store.insert_or_update(payment))?; - self.runtime - .block_on(self.pending_payment_store.insert_or_update(pending_payment))?; + self.payment_store.insert_or_update(payment).await?; + self.pending_payment_store.insert_or_update(pending_payment).await?; }, WalletEvent::TxReplaced { txid, conflicts, .. } => { - let Some(payment_id) = self.find_payment_by_txid(txid) else { + let Some(payment_id) = self.find_payment_by_txid(txid).await else { log_error!( self.logger, "Could not find payment for replaced transaction {}. Skipping.", @@ -400,36 +406,37 @@ impl Wallet { // the payment store with the replacement txid before the next sync cycle, so we // can safely fetch it here. debug_assert!( - self.payment_store.get(&payment_id).is_some(), + self.payment_store.get(&payment_id).await.is_some(), "Payment {:?} expected in store during WalletEvent::TxReplaced but not found", payment_id, ); let payment = - self.payment_store.get(&payment_id).ok_or(Error::InvalidPaymentId)?; + self.payment_store.get(&payment_id).await.ok_or(Error::InvalidPaymentId)?; let pending_payment_details = self.create_pending_payment_from_tx(payment, conflict_txids.clone()); - self.runtime.block_on( - self.pending_payment_store.insert_or_update(pending_payment_details), - )?; + self.pending_payment_store.insert_or_update(pending_payment_details).await?; }, WalletEvent::TxDropped { txid, tx } => { let payment_id = self .find_payment_by_txid(txid) + .await .unwrap_or_else(|| PaymentId(txid.to_byte_array())); - let payment = self.create_payment_from_tx( - locked_wallet, - txid, - payment_id, - &tx, - PaymentStatus::Pending, - ConfirmationStatus::Unconfirmed, - ); + let payment = { + let locked_wallet = self.inner.lock().expect("lock"); + self.create_payment_from_tx( + &locked_wallet, + txid, + payment_id, + &tx, + PaymentStatus::Pending, + ConfirmationStatus::Unconfirmed, + ) + }; let pending_payment = self.create_pending_payment_from_tx(payment.clone(), Vec::new()); - self.runtime.block_on(self.payment_store.insert_or_update(payment))?; - self.runtime - .block_on(self.pending_payment_store.insert_or_update(pending_payment))?; + self.payment_store.insert_or_update(payment).await?; + self.pending_payment_store.insert_or_update(pending_payment).await?; }, _ => { continue; @@ -1216,9 +1223,9 @@ impl Wallet { PendingPaymentDetails::new(payment, conflicting_txids) } - fn find_payment_by_txid(&self, target_txid: Txid) -> Option { + async fn find_payment_by_txid(&self, target_txid: Txid) -> Option { let direct_payment_id = PaymentId(target_txid.to_byte_array()); - if self.pending_payment_store.contains_key(&direct_payment_id) { + if self.pending_payment_store.contains_key(&direct_payment_id).await { return Some(direct_payment_id); } @@ -1228,6 +1235,7 @@ impl Wallet { matches!(p.details.kind, PaymentKind::Onchain { txid, .. } if txid == target_txid) || p.conflicting_txids.contains(&target_txid) }) + .await .first() { return Some(replaced_details.details.id); @@ -1237,10 +1245,10 @@ impl Wallet { } #[allow(deprecated)] - pub(crate) fn bump_fee_rbf( + pub(crate) async fn bump_fee_rbf( &self, payment_id: PaymentId, fee_rate: Option, ) -> Result { - let payment = self.payment_store.get(&payment_id).ok_or_else(|| { + let payment = self.payment_store.get(&payment_id).await.ok_or_else(|| { log_error!(self.logger, "Payment {} not found in payment store", payment_id); Error::InvalidPaymentId })?; @@ -1280,163 +1288,195 @@ impl Wallet { }, }; - let mut locked_wallet = self.inner.lock().expect("lock"); - - debug_assert!( - locked_wallet.tx_details(txid).is_some(), - "Transaction {} expected in wallet but not found", - txid, - ); - let old_tx = locked_wallet - .tx_details(txid) - .ok_or_else(|| { - log_error!(self.logger, "Transaction {} not found in wallet", txid); - Error::InvalidPaymentId - })? - .tx - .deref() - .clone(); + let (fee_bumped_tx, new_txid, new_payment, pending_payment_store) = { + let mut locked_wallet = self.inner.lock().expect("lock"); - let old_fee_rate = locked_wallet.calculate_fee_rate(&old_tx).map_err(|e| { - log_error!(self.logger, "Failed to calculate fee rate of transaction {}: {}", txid, e); - Error::WalletOperationFailed - })?; + debug_assert!( + locked_wallet.tx_details(txid).is_some(), + "Transaction {} expected in wallet but not found", + txid, + ); + let old_tx = locked_wallet + .tx_details(txid) + .ok_or_else(|| { + log_error!(self.logger, "Transaction {} not found in wallet", txid); + Error::InvalidPaymentId + })? + .tx + .deref() + .clone(); + + let old_fee_rate = locked_wallet.calculate_fee_rate(&old_tx).map_err(|e| { + log_error!( + self.logger, + "Failed to calculate fee rate of transaction {}: {}", + txid, + e + ); + Error::WalletOperationFailed + })?; - // BIP 125 requires the replacement to pay a higher fee rate than the original. - // The minimum increase is the incremental relay fee. - let min_required_fee_rate_sat_per_kwu = - old_fee_rate.to_sat_per_kwu() + INCREMENTAL_RELAY_FEE_SAT_PER_1000_WEIGHT as u64; + // BIP 125 requires the replacement to pay a higher fee rate than the original. + // The minimum increase is the incremental relay fee. + let min_required_fee_rate_sat_per_kwu = + old_fee_rate.to_sat_per_kwu() + INCREMENTAL_RELAY_FEE_SAT_PER_1000_WEIGHT as u64; + + let confirmation_target = ConfirmationTarget::OnchainPayment; + let estimated_fee_rate = self.fee_estimator.estimate_fee_rate(confirmation_target); + + // Use the higher of minimum RBF requirement or current network estimate + let final_fee_rate_sat_per_kwu = + min_required_fee_rate_sat_per_kwu.max(estimated_fee_rate.to_sat_per_kwu()); + let final_fee_rate = + fee_rate.unwrap_or_else(|| FeeRate::from_sat_per_kwu(final_fee_rate_sat_per_kwu)); + + let mut psbt = { + let mut builder = locked_wallet.build_fee_bump(txid).map_err(|e| { + log_error!(self.logger, "BDK fee bump failed for {}: {:?}", txid, e); + match e { + BuildFeeBumpError::TransactionNotFound(_) => Error::InvalidPaymentId, + BuildFeeBumpError::TransactionConfirmed(_) => { + log_error!(self.logger, "Payment {} is already confirmed", payment_id); + Error::InvalidPaymentId + }, + BuildFeeBumpError::IrreplaceableTransaction(_) => { + Error::OnchainTxCreationFailed + }, + BuildFeeBumpError::FeeRateUnavailable => { + Error::FeerateEstimationUpdateFailed + }, + BuildFeeBumpError::UnknownUtxo(_) => Error::OnchainTxCreationFailed, + BuildFeeBumpError::InvalidOutputIndex(_) => Error::OnchainTxCreationFailed, + } + })?; - let confirmation_target = ConfirmationTarget::OnchainPayment; - let estimated_fee_rate = self.fee_estimator.estimate_fee_rate(confirmation_target); - - // Use the higher of minimum RBF requirement or current network estimate - let final_fee_rate_sat_per_kwu = - min_required_fee_rate_sat_per_kwu.max(estimated_fee_rate.to_sat_per_kwu()); - let final_fee_rate = - fee_rate.unwrap_or_else(|| FeeRate::from_sat_per_kwu(final_fee_rate_sat_per_kwu)); - - let mut psbt = { - let mut builder = locked_wallet.build_fee_bump(txid).map_err(|e| { - log_error!(self.logger, "BDK fee bump failed for {}: {:?}", txid, e); - match e { - BuildFeeBumpError::TransactionNotFound(_) => Error::InvalidPaymentId, - BuildFeeBumpError::TransactionConfirmed(_) => { - log_error!(self.logger, "Payment {} is already confirmed", payment_id); - Error::InvalidPaymentId - }, - BuildFeeBumpError::IrreplaceableTransaction(_) => { - Error::OnchainTxCreationFailed - }, - BuildFeeBumpError::FeeRateUnavailable => Error::FeerateEstimationUpdateFailed, - BuildFeeBumpError::UnknownUtxo(_) => Error::OnchainTxCreationFailed, - BuildFeeBumpError::InvalidOutputIndex(_) => Error::OnchainTxCreationFailed, - } - })?; + builder.fee_rate(final_fee_rate); - builder.fee_rate(final_fee_rate); + match builder.finish() { + Ok(psbt) => Ok(psbt), + Err(CreateTxError::FeeRateTooLow { required: required_fee_rate }) => { + if fee_rate.is_some() { + log_error!( + self.logger, + "Provided fee rate {} is too low for RBF fee bump of txid {}, required minimum fee rate: {}", + fee_rate.expect("fee rate is set"), + txid, + required_fee_rate + ); + return Err(Error::InvalidFeeRate); + } - match builder.finish() { - Ok(psbt) => Ok(psbt), - Err(CreateTxError::FeeRateTooLow { required: required_fee_rate }) => { - if fee_rate.is_some() { - log_error!( + log_info!( self.logger, - "Provided fee rate {} is too low for RBF fee bump of txid {}, required minimum fee rate: {}", - fee_rate.expect("fee rate is set"), - txid, + "BDK requires higher fee rate: {}", required_fee_rate ); - return Err(Error::InvalidFeeRate); - } - log_info!(self.logger, "BDK requires higher fee rate: {}", required_fee_rate); + // BDK may require a higher fee rate than our estimate due to + // differences in UTXO selection or transaction weight calculations. + // We cap the retry at 1.5x our target fee rate as a safety bound + // to avoid overpaying. + let max_allowed_fee_rate = FeeRate::from_sat_per_kwu( + final_fee_rate_sat_per_kwu.saturating_mul(3).saturating_div(2), + ); + if required_fee_rate > max_allowed_fee_rate { + log_error!( self.logger, "BDK required fee rate {} exceeds sanity cap {} (1.5x our estimate) for tx {}", required_fee_rate, max_allowed_fee_rate, txid ); + return Err(Error::InvalidFeeRate); + } - // BDK may require a higher fee rate than our estimate due to - // differences in UTXO selection or transaction weight calculations. - // We cap the retry at 1.5x our target fee rate as a safety bound - // to avoid overpaying. - let max_allowed_fee_rate = FeeRate::from_sat_per_kwu( - final_fee_rate_sat_per_kwu.saturating_mul(3).saturating_div(2), - ); - if required_fee_rate > max_allowed_fee_rate { - log_error!( self.logger, "BDK required fee rate {} exceeds sanity cap {} (1.5x our estimate) for tx {}", required_fee_rate, max_allowed_fee_rate, txid ); - return Err(Error::InvalidFeeRate); - } + let mut builder = locked_wallet.build_fee_bump(txid).map_err(|e| { + log_error!( + self.logger, + "BDK fee bump retry failed for {}: {:?}", + txid, + e + ); + Error::InvalidFeeRate + })?; - let mut builder = locked_wallet.build_fee_bump(txid).map_err(|e| { - log_error!(self.logger, "BDK fee bump retry failed for {}: {:?}", txid, e); - Error::InvalidFeeRate - })?; + builder.fee_rate(required_fee_rate); + builder.finish().map_err(|e| { + log_error!( + self.logger, + "Failed to finish PSBT with required fee rate: {:?}", + e + ); + Error::InvalidFeeRate + }) + }, + Err(e) => { + log_error!(self.logger, "Failed to create fee bump PSBT: {:?}", e); + Err(Error::InvalidFeeRate) + }, + }? + }; - builder.fee_rate(required_fee_rate); - builder.finish().map_err(|e| { + match locked_wallet.sign(&mut psbt, SignOptions::default()) { + Ok(finalized) => { + if !finalized { log_error!( self.logger, - "Failed to finish PSBT with required fee rate: {:?}", - e + "Failed to finalize signing for fee bump of {}", + txid ); - Error::InvalidFeeRate - }) + return Err(Error::OnchainTxCreationFailed); + } }, - Err(e) => { - log_error!(self.logger, "Failed to create fee bump PSBT: {:?}", e); - Err(Error::InvalidFeeRate) + Err(err) => { + log_error!( + self.logger, + "Failed to sign fee bump transaction for {}: {}", + txid, + err + ); + return Err(err.into()); }, - }? - }; + } - match locked_wallet.sign(&mut psbt, SignOptions::default()) { - Ok(finalized) => { - if !finalized { - log_error!(self.logger, "Failed to finalize signing for fee bump of {}", txid); - return Err(Error::OnchainTxCreationFailed); - } - }, - Err(err) => { + let mut locked_persister = self.persister.lock().expect("lock"); + self.runtime.block_on(locked_wallet.persist_async(&mut locked_persister)).map_err( + |e| { + log_error!( + self.logger, + "Failed to persist wallet after fee bump of {}: {}", + txid, + e + ); + Error::PersistenceFailed + }, + )?; + + let fee_bumped_tx = psbt.extract_tx().map_err(|e| { log_error!( self.logger, - "Failed to sign fee bump transaction for {}: {}", + "Failed to extract fee bump transaction for {}: {}", txid, - err + e ); - return Err(err.into()); - }, - } - - let mut locked_persister = self.persister.lock().expect("lock"); - self.runtime.block_on(locked_wallet.persist_async(&mut locked_persister)).map_err(|e| { - log_error!(self.logger, "Failed to persist wallet after fee bump of {}: {}", txid, e); - Error::PersistenceFailed - })?; - - let fee_bumped_tx = psbt.extract_tx().map_err(|e| { - log_error!(self.logger, "Failed to extract fee bump transaction for {}: {}", txid, e); - e - })?; + e + })?; - let new_txid = fee_bumped_tx.compute_txid(); + let new_txid = fee_bumped_tx.compute_txid(); + let new_payment = self.create_payment_from_tx( + &locked_wallet, + new_txid, + payment.id, + &fee_bumped_tx, + PaymentStatus::Pending, + ConfirmationStatus::Unconfirmed, + ); + let pending_payment_store = + self.create_pending_payment_from_tx(new_payment.clone(), Vec::new()); + (fee_bumped_tx, new_txid, new_payment, pending_payment_store) + }; self.broadcaster.broadcast_transactions(&[( &fee_bumped_tx, lightning::chain::chaininterface::TransactionType::Sweep { channels: vec![] }, )]); - let new_payment = self.create_payment_from_tx( - &locked_wallet, - new_txid, - payment.id, - &fee_bumped_tx, - PaymentStatus::Pending, - ConfirmationStatus::Unconfirmed, - ); - - let pending_payment_store = - self.create_pending_payment_from_tx(new_payment.clone(), Vec::new()); - - self.runtime - .block_on(self.pending_payment_store.insert_or_update(pending_payment_store))?; - self.runtime.block_on(self.payment_store.insert_or_update(new_payment))?; + self.pending_payment_store.insert_or_update(pending_payment_store).await?; + self.payment_store.insert_or_update(new_payment).await?; log_info!(self.logger, "RBF successful: replaced {} with {}", txid, new_txid); @@ -1456,57 +1496,60 @@ impl Listen for Wallet { } fn block_connected(&self, block: &bitcoin::Block, height: u32) { - let mut locked_wallet = self.inner.lock().expect("lock"); + let events = { + let mut locked_wallet = self.inner.lock().expect("lock"); - let pre_checkpoint = locked_wallet.latest_checkpoint(); - if pre_checkpoint.height() != height - 1 - || pre_checkpoint.hash() != block.header.prev_blockhash - { - log_debug!( - self.logger, - "Detected reorg while applying a connected block to on-chain wallet: new block with hash {} at height {}", - block.header.block_hash(), - height - ); - } + let pre_checkpoint = locked_wallet.latest_checkpoint(); + if pre_checkpoint.height() != height - 1 + || pre_checkpoint.hash() != block.header.prev_blockhash + { + log_debug!( + self.logger, + "Detected reorg while applying a connected block to on-chain wallet: new block with hash {} at height {}", + block.header.block_hash(), + height + ); + } - // In order to be able to reliably calculate fees the `Wallet` needs access to the previous - // ouput data. To this end, we here insert any ouputs of transactions that LDK is intersted - // in (e.g., funding transaction ouputs) into the wallet's transaction graph when we see - // them, so it is reliably able to calculate fees for subsequent spends. - // - // FIXME: technically, we should also do this for mempool transactions. However, at the - // current time fixing the edge case doesn't seem worth the additional conplexity / - // additional overhead.. - let registered_txids = self.chain_source.registered_txids(); - for tx in &block.txdata { - let txid = tx.compute_txid(); - if registered_txids.contains(&txid) { - for (vout, txout) in tx.output.iter().enumerate() { - let outpoint = OutPoint { txid, vout: vout as u32 }; - locked_wallet.insert_txout(outpoint, txout.clone()); + // In order to be able to reliably calculate fees the `Wallet` needs access to the previous + // ouput data. To this end, we here insert any ouputs of transactions that LDK is intersted + // in (e.g., funding transaction ouputs) into the wallet's transaction graph when we see + // them, so it is reliably able to calculate fees for subsequent spends. + // + // FIXME: technically, we should also do this for mempool transactions. However, at the + // current time fixing the edge case doesn't seem worth the additional conplexity / + // additional overhead.. + let registered_txids = self.chain_source.registered_txids(); + for tx in &block.txdata { + let txid = tx.compute_txid(); + if registered_txids.contains(&txid) { + for (vout, txout) in tx.output.iter().enumerate() { + let outpoint = OutPoint { txid, vout: vout as u32 }; + locked_wallet.insert_txout(outpoint, txout.clone()); + } } } - } - match locked_wallet.apply_block_events(block, height) { - Ok(events) => { - if let Err(e) = self.update_payment_store(&mut *locked_wallet, events) { - log_error!(self.logger, "Failed to update payment store: {}", e); + match locked_wallet.apply_block_events(block, height) { + Ok(events) => events, + Err(e) => { + log_error!( + self.logger, + "Failed to apply connected block to on-chain wallet: {}", + e + ); return; - } - }, - Err(e) => { - log_error!( - self.logger, - "Failed to apply connected block to on-chain wallet: {}", - e - ); - return; - }, + }, + } }; + if let Err(e) = self.runtime.block_on(self.update_payment_store(events)) { + log_error!(self.logger, "Failed to update payment store: {}", e); + return; + } + let mut locked_persister = self.persister.lock().expect("lock"); + let mut locked_wallet = self.inner.lock().expect("lock"); match self.runtime.block_on(locked_wallet.persist_async(&mut locked_persister)) { Ok(_) => (), Err(e) => { diff --git a/tests/common/mod.rs b/tests/common/mod.rs index d7775e67b3..1131ec8c7c 100644 --- a/tests/common/mod.rs +++ b/tests/common/mod.rs @@ -222,7 +222,7 @@ macro_rules! expect_payment_received_event { ref e @ Event::PaymentReceived { payment_id, amount_msat, .. } => { println!("{} got event {:?}", $node.node_id(), e); assert_eq!(amount_msat, $amount_msat); - let payment = $node.payment(&payment_id.unwrap()).unwrap(); + let payment = $node.payment(&payment_id.unwrap()).await.unwrap(); if !matches!(payment.kind, ldk_node::payment::PaymentKind::Onchain { .. }) { assert_eq!(payment.fee_paid_msat, None); } @@ -290,7 +290,7 @@ macro_rules! expect_payment_successful_event { if let Some(fee_msat) = $fee_paid_msat { assert_eq!(fee_paid_msat, fee_msat); } - let payment = $node.payment(&$payment_id.unwrap()).unwrap(); + let payment = $node.payment(&$payment_id.unwrap()).await.unwrap(); assert_eq!(payment.fee_paid_msat, fee_paid_msat); assert_eq!(payment_id, $payment_id); $node.event_handled().unwrap(); @@ -606,8 +606,6 @@ pub(crate) fn setup_node(chain_source: &TestChainSource, config: TestConfig) -> } node.start().unwrap(); - assert!(node.status().is_running); - assert!(node.status().latest_fee_rate_cache_update_timestamp.is_some()); node } @@ -836,6 +834,7 @@ pub async fn open_channel_push_amt( push_amount_msat, None, ) + .await .unwrap(); } else { node_a @@ -846,9 +845,10 @@ pub async fn open_channel_push_amt( push_amount_msat, None, ) + .await .unwrap(); } - assert!(node_a.list_peers().iter().find(|c| { c.node_id == node_b.node_id() }).is_some()); + assert!(node_a.list_peers().await.iter().find(|c| { c.node_id == node_b.node_id() }).is_some()); let funding_txo_a = expect_channel_pending_event!(node_a, node_b.node_id()); let funding_txo_b = expect_channel_pending_event!(node_b, node_a.node_id()); @@ -869,6 +869,7 @@ pub async fn open_channel_with_all( None, None, ) + .await .unwrap(); } else { node_a @@ -878,9 +879,10 @@ pub async fn open_channel_with_all( None, None, ) + .await .unwrap(); } - assert!(node_a.list_peers().iter().find(|c| { c.node_id == node_b.node_id() }).is_some()); + assert!(node_a.list_peers().await.iter().find(|c| { c.node_id == node_b.node_id() }).is_some()); let funding_txo_a = expect_channel_pending_event!(node_a, node_b.node_id()); let funding_txo_b = expect_channel_pending_event!(node_b, node_a.node_id()); @@ -893,7 +895,7 @@ pub async fn open_channel_with_all( pub async fn splice_in_with_all( node_a: &TestNode, node_b: &TestNode, user_channel_id: &UserChannelId, electrsd: &ElectrsD, ) { - node_a.splice_in_with_all(user_channel_id, node_b.node_id()).unwrap(); + node_a.splice_in_with_all(user_channel_id, node_b.node_id()).await.unwrap(); let splice_txo = expect_splice_negotiated_event!(node_a, node_b.node_id()); expect_splice_negotiated_event!(node_b, node_a.node_id()); @@ -916,8 +918,8 @@ pub(crate) async fn do_channel_full_cycle( Amount::from_sat(premine_amount_sat), ) .await; - node_a.sync_wallets().unwrap(); - node_b.sync_wallets().unwrap(); + node_a.sync_wallets().await.unwrap(); + node_b.sync_wallets().await.unwrap(); assert_eq!(node_a.list_balances().spendable_onchain_balance_sats, premine_amount_sat); assert_eq!(node_b.list_balances().spendable_onchain_balance_sats, premine_amount_sat); @@ -926,6 +928,7 @@ pub(crate) async fn do_channel_full_cycle( node_a .list_payments_with_filter(|p| p.direction == PaymentDirection::Inbound && matches!(p.kind, PaymentKind::Onchain { .. })) + .await .len(), 1 ); @@ -933,6 +936,7 @@ pub(crate) async fn do_channel_full_cycle( node_a .list_payments_with_filter(|p| p.direction == PaymentDirection::Outbound && matches!(p.kind, PaymentKind::Onchain { .. })) + .await .len(), 0 ); @@ -940,6 +944,7 @@ pub(crate) async fn do_channel_full_cycle( node_b .list_payments_with_filter(|p| p.direction == PaymentDirection::Inbound && matches!(p.kind, PaymentKind::Onchain { .. })) + .await .len(), 1 ); @@ -947,6 +952,7 @@ pub(crate) async fn do_channel_full_cycle( node_b .list_payments_with_filter(|p| p.direction == PaymentDirection::Outbound && matches!(p.kind, PaymentKind::Onchain { .. })) + .await .len(), 0 ); @@ -967,6 +973,7 @@ pub(crate) async fn do_channel_full_cycle( Some(push_msat), None, ) + .await .unwrap(); } else { node_a @@ -977,11 +984,12 @@ pub(crate) async fn do_channel_full_cycle( Some(push_msat), None, ) + .await .unwrap(); } - assert_eq!(node_a.list_peers().first().unwrap().node_id, node_b.node_id()); - assert!(node_a.list_peers().first().unwrap().is_persisted); + assert_eq!(node_a.list_peers().await.first().unwrap().node_id, node_b.node_id()); + assert!(node_a.list_peers().await.first().unwrap().is_persisted); let funding_txo_a = expect_channel_pending_event!(node_a, node_b.node_id()); let funding_txo_b = expect_channel_pending_event!(node_b, node_a.node_id()); assert_eq!(funding_txo_a, funding_txo_b); @@ -992,14 +1000,15 @@ pub(crate) async fn do_channel_full_cycle( generate_blocks_and_wait(&bitcoind, electrsd, 6).await; } - node_a.sync_wallets().unwrap(); - node_b.sync_wallets().unwrap(); + node_a.sync_wallets().await.unwrap(); + node_b.sync_wallets().await.unwrap(); // Check we now see the channel funding transaction as outbound. assert_eq!( node_a .list_payments_with_filter(|p| p.direction == PaymentDirection::Outbound && matches!(p.kind, PaymentKind::Onchain { .. })) + .await .len(), 1 ); @@ -1069,32 +1078,46 @@ pub(crate) async fn do_channel_full_cycle( let invoice = node_b .bolt11_payment() .receive(invoice_amount_1_msat, &invoice_description.clone().into(), 9217) + .await .unwrap(); println!("\nA send"); - let payment_id = node_a.bolt11_payment().send(&invoice, None).unwrap(); - assert_eq!(node_a.bolt11_payment().send(&invoice, None), Err(NodeError::DuplicatePayment)); + let payment_id = node_a.bolt11_payment().send(&invoice, None).await.unwrap(); + assert_eq!( + node_a.bolt11_payment().send(&invoice, None).await, + Err(NodeError::DuplicatePayment) + ); - assert!(!node_a.list_payments_with_filter(|p| p.id == payment_id).is_empty()); + assert!(!node_a.list_payments_with_filter(|p| p.id == payment_id).await.is_empty()); - let outbound_payments_a = node_a.list_payments_with_filter(|p| { - p.direction == PaymentDirection::Outbound && matches!(p.kind, PaymentKind::Bolt11 { .. }) - }); + let outbound_payments_a = node_a + .list_payments_with_filter(|p| { + p.direction == PaymentDirection::Outbound + && matches!(p.kind, PaymentKind::Bolt11 { .. }) + }) + .await; assert_eq!(outbound_payments_a.len(), 1); - let inbound_payments_a = node_a.list_payments_with_filter(|p| { - p.direction == PaymentDirection::Inbound && matches!(p.kind, PaymentKind::Bolt11 { .. }) - }); + let inbound_payments_a = node_a + .list_payments_with_filter(|p| { + p.direction == PaymentDirection::Inbound && matches!(p.kind, PaymentKind::Bolt11 { .. }) + }) + .await; assert_eq!(inbound_payments_a.len(), 0); - let outbound_payments_b = node_b.list_payments_with_filter(|p| { - p.direction == PaymentDirection::Outbound && matches!(p.kind, PaymentKind::Bolt11 { .. }) - }); + let outbound_payments_b = node_b + .list_payments_with_filter(|p| { + p.direction == PaymentDirection::Outbound + && matches!(p.kind, PaymentKind::Bolt11 { .. }) + }) + .await; assert_eq!(outbound_payments_b.len(), 0); - let inbound_payments_b = node_b.list_payments_with_filter(|p| { - p.direction == PaymentDirection::Inbound && matches!(p.kind, PaymentKind::Bolt11 { .. }) - }); + let inbound_payments_b = node_b + .list_payments_with_filter(|p| { + p.direction == PaymentDirection::Inbound && matches!(p.kind, PaymentKind::Bolt11 { .. }) + }) + .await; assert_eq!(inbound_payments_b.len(), 1); // Verify bolt12_invoice is None for BOLT11 payments @@ -1109,47 +1132,55 @@ pub(crate) async fn do_channel_full_cycle( }, } expect_event!(node_b, PaymentReceived); - assert_eq!(node_a.payment(&payment_id).unwrap().status, PaymentStatus::Succeeded); - assert_eq!(node_a.payment(&payment_id).unwrap().direction, PaymentDirection::Outbound); - assert_eq!(node_a.payment(&payment_id).unwrap().amount_msat, Some(invoice_amount_1_msat)); - assert!(matches!(node_a.payment(&payment_id).unwrap().kind, PaymentKind::Bolt11 { .. })); - assert_eq!(node_b.payment(&payment_id).unwrap().status, PaymentStatus::Succeeded); - assert_eq!(node_b.payment(&payment_id).unwrap().direction, PaymentDirection::Inbound); - assert_eq!(node_b.payment(&payment_id).unwrap().amount_msat, Some(invoice_amount_1_msat)); - assert!(matches!(node_b.payment(&payment_id).unwrap().kind, PaymentKind::Bolt11 { .. })); + assert_eq!(node_a.payment(&payment_id).await.unwrap().status, PaymentStatus::Succeeded); + assert_eq!(node_a.payment(&payment_id).await.unwrap().direction, PaymentDirection::Outbound); + assert_eq!(node_a.payment(&payment_id).await.unwrap().amount_msat, Some(invoice_amount_1_msat)); + assert!(matches!(node_a.payment(&payment_id).await.unwrap().kind, PaymentKind::Bolt11 { .. })); + assert_eq!(node_b.payment(&payment_id).await.unwrap().status, PaymentStatus::Succeeded); + assert_eq!(node_b.payment(&payment_id).await.unwrap().direction, PaymentDirection::Inbound); + assert_eq!(node_b.payment(&payment_id).await.unwrap().amount_msat, Some(invoice_amount_1_msat)); + assert!(matches!(node_b.payment(&payment_id).await.unwrap().kind, PaymentKind::Bolt11 { .. })); // Assert we fail duplicate outbound payments and check the status hasn't changed. - assert_eq!(Err(NodeError::DuplicatePayment), node_a.bolt11_payment().send(&invoice, None)); - assert_eq!(node_a.payment(&payment_id).unwrap().status, PaymentStatus::Succeeded); - assert_eq!(node_a.payment(&payment_id).unwrap().direction, PaymentDirection::Outbound); - assert_eq!(node_a.payment(&payment_id).unwrap().amount_msat, Some(invoice_amount_1_msat)); - assert_eq!(node_b.payment(&payment_id).unwrap().status, PaymentStatus::Succeeded); - assert_eq!(node_b.payment(&payment_id).unwrap().direction, PaymentDirection::Inbound); - assert_eq!(node_b.payment(&payment_id).unwrap().amount_msat, Some(invoice_amount_1_msat)); + assert_eq!( + Err(NodeError::DuplicatePayment), + node_a.bolt11_payment().send(&invoice, None).await + ); + assert_eq!(node_a.payment(&payment_id).await.unwrap().status, PaymentStatus::Succeeded); + assert_eq!(node_a.payment(&payment_id).await.unwrap().direction, PaymentDirection::Outbound); + assert_eq!(node_a.payment(&payment_id).await.unwrap().amount_msat, Some(invoice_amount_1_msat)); + assert_eq!(node_b.payment(&payment_id).await.unwrap().status, PaymentStatus::Succeeded); + assert_eq!(node_b.payment(&payment_id).await.unwrap().direction, PaymentDirection::Inbound); + assert_eq!(node_b.payment(&payment_id).await.unwrap().amount_msat, Some(invoice_amount_1_msat)); // Test under-/overpayment let invoice_amount_2_msat = 2500_000; let invoice = node_b .bolt11_payment() .receive(invoice_amount_2_msat, &invoice_description.clone().into(), 9217) + .await .unwrap(); let underpaid_amount = invoice_amount_2_msat - 1; assert_eq!( Err(NodeError::InvalidAmount), - node_a.bolt11_payment().send_using_amount(&invoice, underpaid_amount, None) + node_a.bolt11_payment().send_using_amount(&invoice, underpaid_amount, None).await ); println!("\nB overpaid receive"); let invoice = node_b .bolt11_payment() .receive(invoice_amount_2_msat, &invoice_description.clone().into(), 9217) + .await .unwrap(); let overpaid_amount_msat = invoice_amount_2_msat + 100; println!("\nA overpaid send"); - let payment_id = - node_a.bolt11_payment().send_using_amount(&invoice, overpaid_amount_msat, None).unwrap(); + let payment_id = node_a + .bolt11_payment() + .send_using_amount(&invoice, overpaid_amount_msat, None) + .await + .unwrap(); expect_event!(node_a, PaymentSuccessful); let received_amount = match node_b.next_event_async().await { ref e @ Event::PaymentReceived { amount_msat, .. } => { @@ -1162,30 +1193,32 @@ pub(crate) async fn do_channel_full_cycle( }, }; assert_eq!(received_amount, overpaid_amount_msat); - assert_eq!(node_a.payment(&payment_id).unwrap().status, PaymentStatus::Succeeded); - assert_eq!(node_a.payment(&payment_id).unwrap().direction, PaymentDirection::Outbound); - assert_eq!(node_a.payment(&payment_id).unwrap().amount_msat, Some(overpaid_amount_msat)); - assert!(matches!(node_a.payment(&payment_id).unwrap().kind, PaymentKind::Bolt11 { .. })); - assert_eq!(node_b.payment(&payment_id).unwrap().status, PaymentStatus::Succeeded); - assert_eq!(node_b.payment(&payment_id).unwrap().direction, PaymentDirection::Inbound); - assert_eq!(node_b.payment(&payment_id).unwrap().amount_msat, Some(overpaid_amount_msat)); - assert!(matches!(node_b.payment(&payment_id).unwrap().kind, PaymentKind::Bolt11 { .. })); + assert_eq!(node_a.payment(&payment_id).await.unwrap().status, PaymentStatus::Succeeded); + assert_eq!(node_a.payment(&payment_id).await.unwrap().direction, PaymentDirection::Outbound); + assert_eq!(node_a.payment(&payment_id).await.unwrap().amount_msat, Some(overpaid_amount_msat)); + assert!(matches!(node_a.payment(&payment_id).await.unwrap().kind, PaymentKind::Bolt11 { .. })); + assert_eq!(node_b.payment(&payment_id).await.unwrap().status, PaymentStatus::Succeeded); + assert_eq!(node_b.payment(&payment_id).await.unwrap().direction, PaymentDirection::Inbound); + assert_eq!(node_b.payment(&payment_id).await.unwrap().amount_msat, Some(overpaid_amount_msat)); + assert!(matches!(node_b.payment(&payment_id).await.unwrap().kind, PaymentKind::Bolt11 { .. })); // Test "zero-amount" invoice payment println!("\nB receive_variable_amount_payment"); let variable_amount_invoice = node_b .bolt11_payment() .receive_variable_amount(&invoice_description.clone().into(), 9217) + .await .unwrap(); let determined_amount_msat = 2345_678; assert_eq!( Err(NodeError::InvalidInvoice), - node_a.bolt11_payment().send(&variable_amount_invoice, None) + node_a.bolt11_payment().send(&variable_amount_invoice, None).await ); println!("\nA send_using_amount"); let payment_id = node_a .bolt11_payment() .send_using_amount(&variable_amount_invoice, determined_amount_msat, None) + .await .unwrap(); expect_event!(node_a, PaymentSuccessful); @@ -1200,14 +1233,20 @@ pub(crate) async fn do_channel_full_cycle( }, }; assert_eq!(received_amount, determined_amount_msat); - assert_eq!(node_a.payment(&payment_id).unwrap().status, PaymentStatus::Succeeded); - assert_eq!(node_a.payment(&payment_id).unwrap().direction, PaymentDirection::Outbound); - assert_eq!(node_a.payment(&payment_id).unwrap().amount_msat, Some(determined_amount_msat)); - assert!(matches!(node_a.payment(&payment_id).unwrap().kind, PaymentKind::Bolt11 { .. })); - assert_eq!(node_b.payment(&payment_id).unwrap().status, PaymentStatus::Succeeded); - assert_eq!(node_b.payment(&payment_id).unwrap().direction, PaymentDirection::Inbound); - assert_eq!(node_b.payment(&payment_id).unwrap().amount_msat, Some(determined_amount_msat)); - assert!(matches!(node_b.payment(&payment_id).unwrap().kind, PaymentKind::Bolt11 { .. })); + assert_eq!(node_a.payment(&payment_id).await.unwrap().status, PaymentStatus::Succeeded); + assert_eq!(node_a.payment(&payment_id).await.unwrap().direction, PaymentDirection::Outbound); + assert_eq!( + node_a.payment(&payment_id).await.unwrap().amount_msat, + Some(determined_amount_msat) + ); + assert!(matches!(node_a.payment(&payment_id).await.unwrap().kind, PaymentKind::Bolt11 { .. })); + assert_eq!(node_b.payment(&payment_id).await.unwrap().status, PaymentStatus::Succeeded); + assert_eq!(node_b.payment(&payment_id).await.unwrap().direction, PaymentDirection::Inbound); + assert_eq!( + node_b.payment(&payment_id).await.unwrap().amount_msat, + Some(determined_amount_msat) + ); + assert!(matches!(node_b.payment(&payment_id).await.unwrap().kind, PaymentKind::Bolt11 { .. })); // Test claiming manually registered payments. let invoice_amount_3_msat = 5_532_000; @@ -1221,8 +1260,9 @@ pub(crate) async fn do_channel_full_cycle( 9217, manual_payment_hash, ) + .await .unwrap(); - let manual_payment_id = node_a.bolt11_payment().send(&manual_invoice, None).unwrap(); + let manual_payment_id = node_a.bolt11_payment().send(&manual_invoice, None).await.unwrap(); let claimable_amount_msat = expect_payment_claimable_event!( node_b, @@ -1233,23 +1273,36 @@ pub(crate) async fn do_channel_full_cycle( node_b .bolt11_payment() .claim_for_hash(manual_payment_hash, claimable_amount_msat, manual_preimage) + .await .unwrap(); expect_payment_received_event!(node_b, claimable_amount_msat); expect_payment_successful_event!(node_a, Some(manual_payment_id), None); - assert_eq!(node_a.payment(&manual_payment_id).unwrap().status, PaymentStatus::Succeeded); - assert_eq!(node_a.payment(&manual_payment_id).unwrap().direction, PaymentDirection::Outbound); + assert_eq!(node_a.payment(&manual_payment_id).await.unwrap().status, PaymentStatus::Succeeded); assert_eq!( - node_a.payment(&manual_payment_id).unwrap().amount_msat, + node_a.payment(&manual_payment_id).await.unwrap().direction, + PaymentDirection::Outbound + ); + assert_eq!( + node_a.payment(&manual_payment_id).await.unwrap().amount_msat, Some(invoice_amount_3_msat) ); - assert!(matches!(node_a.payment(&manual_payment_id).unwrap().kind, PaymentKind::Bolt11 { .. })); - assert_eq!(node_b.payment(&manual_payment_id).unwrap().status, PaymentStatus::Succeeded); - assert_eq!(node_b.payment(&manual_payment_id).unwrap().direction, PaymentDirection::Inbound); + assert!(matches!( + node_a.payment(&manual_payment_id).await.unwrap().kind, + PaymentKind::Bolt11 { .. } + )); + assert_eq!(node_b.payment(&manual_payment_id).await.unwrap().status, PaymentStatus::Succeeded); + assert_eq!( + node_b.payment(&manual_payment_id).await.unwrap().direction, + PaymentDirection::Inbound + ); assert_eq!( - node_b.payment(&manual_payment_id).unwrap().amount_msat, + node_b.payment(&manual_payment_id).await.unwrap().amount_msat, Some(invoice_amount_3_msat) ); - assert!(matches!(node_b.payment(&manual_payment_id).unwrap().kind, PaymentKind::Bolt11 { .. })); + assert!(matches!( + node_b.payment(&manual_payment_id).await.unwrap().kind, + PaymentKind::Bolt11 { .. } + )); // Test failing manually registered payments. let invoice_amount_4_msat = 5_532_000; @@ -1264,8 +1317,10 @@ pub(crate) async fn do_channel_full_cycle( 9217, manual_fail_payment_hash, ) + .await .unwrap(); - let manual_fail_payment_id = node_a.bolt11_payment().send(&manual_fail_invoice, None).unwrap(); + let manual_fail_payment_id = + node_a.bolt11_payment().send(&manual_fail_invoice, None).await.unwrap(); expect_payment_claimable_event!( node_b, @@ -1273,32 +1328,38 @@ pub(crate) async fn do_channel_full_cycle( manual_fail_payment_hash, invoice_amount_4_msat ); - node_b.bolt11_payment().fail_for_hash(manual_fail_payment_hash).unwrap(); + node_b.bolt11_payment().fail_for_hash(manual_fail_payment_hash).await.unwrap(); expect_event!(node_a, PaymentFailed); - assert_eq!(node_a.payment(&manual_fail_payment_id).unwrap().status, PaymentStatus::Failed); assert_eq!( - node_a.payment(&manual_fail_payment_id).unwrap().direction, + node_a.payment(&manual_fail_payment_id).await.unwrap().status, + PaymentStatus::Failed + ); + assert_eq!( + node_a.payment(&manual_fail_payment_id).await.unwrap().direction, PaymentDirection::Outbound ); assert_eq!( - node_a.payment(&manual_fail_payment_id).unwrap().amount_msat, + node_a.payment(&manual_fail_payment_id).await.unwrap().amount_msat, Some(invoice_amount_4_msat) ); assert!(matches!( - node_a.payment(&manual_fail_payment_id).unwrap().kind, + node_a.payment(&manual_fail_payment_id).await.unwrap().kind, PaymentKind::Bolt11 { .. } )); - assert_eq!(node_b.payment(&manual_fail_payment_id).unwrap().status, PaymentStatus::Failed); assert_eq!( - node_b.payment(&manual_fail_payment_id).unwrap().direction, + node_b.payment(&manual_fail_payment_id).await.unwrap().status, + PaymentStatus::Failed + ); + assert_eq!( + node_b.payment(&manual_fail_payment_id).await.unwrap().direction, PaymentDirection::Inbound ); assert_eq!( - node_b.payment(&manual_fail_payment_id).unwrap().amount_msat, + node_b.payment(&manual_fail_payment_id).await.unwrap().amount_msat, Some(invoice_amount_4_msat) ); assert!(matches!( - node_b.payment(&manual_fail_payment_id).unwrap().kind, + node_b.payment(&manual_fail_payment_id).await.unwrap().kind, PaymentKind::Bolt11 { .. } )); @@ -1309,6 +1370,7 @@ pub(crate) async fn do_channel_full_cycle( let keysend_payment_id = node_a .spontaneous_payment() .send_with_custom_tlvs(keysend_amount_msat, node_b.node_id(), None, custom_tlvs.clone()) + .await .unwrap(); expect_event!(node_a, PaymentSuccessful); let next_event = node_b.next_event_async().await; @@ -1323,38 +1385,58 @@ pub(crate) async fn do_channel_full_cycle( }, }; assert_eq!(received_keysend_amount, keysend_amount_msat); - assert_eq!(node_a.payment(&keysend_payment_id).unwrap().status, PaymentStatus::Succeeded); - assert_eq!(node_a.payment(&keysend_payment_id).unwrap().direction, PaymentDirection::Outbound); - assert_eq!(node_a.payment(&keysend_payment_id).unwrap().amount_msat, Some(keysend_amount_msat)); + assert_eq!(node_a.payment(&keysend_payment_id).await.unwrap().status, PaymentStatus::Succeeded); + assert_eq!( + node_a.payment(&keysend_payment_id).await.unwrap().direction, + PaymentDirection::Outbound + ); + assert_eq!( + node_a.payment(&keysend_payment_id).await.unwrap().amount_msat, + Some(keysend_amount_msat) + ); assert!(matches!( - node_a.payment(&keysend_payment_id).unwrap().kind, + node_a.payment(&keysend_payment_id).await.unwrap().kind, PaymentKind::Spontaneous { .. } )); assert_eq!(received_custom_records, &custom_tlvs); - assert_eq!(node_b.payment(&keysend_payment_id).unwrap().status, PaymentStatus::Succeeded); - assert_eq!(node_b.payment(&keysend_payment_id).unwrap().direction, PaymentDirection::Inbound); - assert_eq!(node_b.payment(&keysend_payment_id).unwrap().amount_msat, Some(keysend_amount_msat)); + assert_eq!(node_b.payment(&keysend_payment_id).await.unwrap().status, PaymentStatus::Succeeded); + assert_eq!( + node_b.payment(&keysend_payment_id).await.unwrap().direction, + PaymentDirection::Inbound + ); + assert_eq!( + node_b.payment(&keysend_payment_id).await.unwrap().amount_msat, + Some(keysend_amount_msat) + ); assert!(matches!( - node_b.payment(&keysend_payment_id).unwrap().kind, + node_b.payment(&keysend_payment_id).await.unwrap().kind, PaymentKind::Spontaneous { .. } )); assert_eq!( - node_a.list_payments_with_filter(|p| matches!(p.kind, PaymentKind::Bolt11 { .. })).len(), + node_a + .list_payments_with_filter(|p| matches!(p.kind, PaymentKind::Bolt11 { .. })) + .await + .len(), 5 ); assert_eq!( - node_b.list_payments_with_filter(|p| matches!(p.kind, PaymentKind::Bolt11 { .. })).len(), + node_b + .list_payments_with_filter(|p| matches!(p.kind, PaymentKind::Bolt11 { .. })) + .await + .len(), 6 ); assert_eq!( node_a .list_payments_with_filter(|p| matches!(p.kind, PaymentKind::Spontaneous { .. })) + .await .len(), 1 ); assert_eq!( node_b .list_payments_with_filter(|p| matches!(p.kind, PaymentKind::Spontaneous { .. })) + .await .len(), 1 ); @@ -1371,8 +1453,8 @@ pub(crate) async fn do_channel_full_cycle( expect_splice_negotiated_event!(node_b, node_a.node_id()); generate_blocks_and_wait(&bitcoind, electrsd, 6).await; - node_a.sync_wallets().unwrap(); - node_b.sync_wallets().unwrap(); + node_a.sync_wallets().await.unwrap(); + node_b.sync_wallets().await.unwrap(); expect_channel_ready_event!(node_a, node_b.node_id()); expect_channel_ready_event!(node_b, node_a.node_id()); @@ -1381,20 +1463,21 @@ pub(crate) async fn do_channel_full_cycle( node_a .list_payments_with_filter(|p| p.direction == PaymentDirection::Inbound && matches!(p.kind, PaymentKind::Onchain { .. })) + .await .len(), 2 ); println!("\nA splices in the splice-out payment from B"); let splice_in_sat = splice_out_sat; - node_a.splice_in(&user_channel_id_a, node_b.node_id(), splice_in_sat).unwrap(); + node_a.splice_in(&user_channel_id_a, node_b.node_id(), splice_in_sat).await.unwrap(); expect_splice_negotiated_event!(node_a, node_b.node_id()); expect_splice_negotiated_event!(node_b, node_a.node_id()); generate_blocks_and_wait(&bitcoind, electrsd, 6).await; - node_a.sync_wallets().unwrap(); - node_b.sync_wallets().unwrap(); + node_a.sync_wallets().await.unwrap(); + node_b.sync_wallets().await.unwrap(); expect_channel_ready_event!(node_a, node_b.node_id()); expect_channel_ready_event!(node_b, node_a.node_id()); @@ -1403,6 +1486,7 @@ pub(crate) async fn do_channel_full_cycle( node_a .list_payments_with_filter(|p| p.direction == PaymentDirection::Outbound && matches!(p.kind, PaymentKind::Onchain { .. })) + .await .len(), 2 ); @@ -1431,11 +1515,19 @@ pub(crate) async fn do_channel_full_cycle( // This is a private channel, so node B can send 100% of the value over assert_eq!(node_b.list_channels()[0].next_outbound_htlc_limit_msat, node_b_capacity_msat); - node_b.spontaneous_payment().send(node_b_capacity_msat, node_a.node_id(), None).unwrap(); + node_b + .spontaneous_payment() + .send(node_b_capacity_msat, node_a.node_id(), None) + .await + .unwrap(); expect_event!(node_b, PaymentSuccessful); expect_event!(node_a, PaymentReceived); - node_a.spontaneous_payment().send(node_b_capacity_msat, node_b.node_id(), None).unwrap(); + node_a + .spontaneous_payment() + .send(node_b_capacity_msat, node_b.node_id(), None) + .await + .unwrap(); expect_event!(node_a, PaymentSuccessful); expect_event!(node_b, PaymentReceived); } @@ -1443,9 +1535,9 @@ pub(crate) async fn do_channel_full_cycle( println!("\nB close_channel (force: {})", force_close); tokio::time::sleep(Duration::from_secs(1)).await; if force_close { - node_a.force_close_channel(&user_channel_id_a, node_b.node_id(), None).unwrap(); + node_a.force_close_channel(&user_channel_id_a, node_b.node_id(), None).await.unwrap(); } else { - node_a.close_channel(&user_channel_id_a, node_b.node_id()).unwrap(); + node_a.close_channel(&user_channel_id_a, node_b.node_id()).await.unwrap(); // The cooperative shutdown may complete before we get to check, but if the channel // is still visible it must already be in a shutdown state. if let Some(channel) = @@ -1468,8 +1560,8 @@ pub(crate) async fn do_channel_full_cycle( wait_for_outpoint_spend(electrsd, funding_txo_b).await; generate_blocks_and_wait(&bitcoind, electrsd, 1).await; - node_a.sync_wallets().unwrap(); - node_b.sync_wallets().unwrap(); + node_a.sync_wallets().await.unwrap(); + node_b.sync_wallets().await.unwrap(); if force_close { // Check node_b properly sees all balances and sweeps them. @@ -1481,11 +1573,11 @@ pub(crate) async fn do_channel_full_cycle( .. } => { assert_eq!(counterparty_node_id, node_a.node_id()); - let cur_height = node_b.status().current_best_block.height; + let cur_height = node_b.status().await.current_best_block.height; let blocks_to_go = confirmation_height - cur_height; generate_blocks_and_wait(&bitcoind, electrsd, blocks_to_go as usize).await; - node_b.sync_wallets().unwrap(); - node_a.sync_wallets().unwrap(); + node_b.sync_wallets().await.unwrap(); + node_a.sync_wallets().await.unwrap(); }, _ => panic!("Unexpected balance state!"), } @@ -1497,8 +1589,8 @@ pub(crate) async fn do_channel_full_cycle( _ => panic!("Unexpected balance state!"), } generate_blocks_and_wait(&bitcoind, electrsd, 1).await; - node_b.sync_wallets().unwrap(); - node_a.sync_wallets().unwrap(); + node_b.sync_wallets().await.unwrap(); + node_a.sync_wallets().await.unwrap(); assert!(node_b.list_balances().lightning_balances.is_empty()); assert_eq!(node_b.list_balances().pending_balances_from_channel_closures.len(), 1); @@ -1507,8 +1599,8 @@ pub(crate) async fn do_channel_full_cycle( _ => panic!("Unexpected balance state!"), } generate_blocks_and_wait(&bitcoind, electrsd, 5).await; - node_b.sync_wallets().unwrap(); - node_a.sync_wallets().unwrap(); + node_b.sync_wallets().await.unwrap(); + node_a.sync_wallets().await.unwrap(); assert!(node_b.list_balances().lightning_balances.is_empty()); assert_eq!(node_b.list_balances().pending_balances_from_channel_closures.len(), 1); @@ -1522,11 +1614,11 @@ pub(crate) async fn do_channel_full_cycle( .. } => { assert_eq!(counterparty_node_id, node_b.node_id()); - let cur_height = node_a.status().current_best_block.height; + let cur_height = node_a.status().await.current_best_block.height; let blocks_to_go = confirmation_height - cur_height; generate_blocks_and_wait(&bitcoind, electrsd, blocks_to_go as usize).await; - node_a.sync_wallets().unwrap(); - node_b.sync_wallets().unwrap(); + node_a.sync_wallets().await.unwrap(); + node_b.sync_wallets().await.unwrap(); }, _ => panic!("Unexpected balance state!"), } @@ -1538,8 +1630,8 @@ pub(crate) async fn do_channel_full_cycle( _ => panic!("Unexpected balance state!"), } generate_blocks_and_wait(&bitcoind, electrsd, 1).await; - node_a.sync_wallets().unwrap(); - node_b.sync_wallets().unwrap(); + node_a.sync_wallets().await.unwrap(); + node_b.sync_wallets().await.unwrap(); assert!(node_a.list_balances().lightning_balances.is_empty()); assert_eq!(node_a.list_balances().pending_balances_from_channel_closures.len(), 1); @@ -1548,8 +1640,8 @@ pub(crate) async fn do_channel_full_cycle( _ => panic!("Unexpected balance state!"), } generate_blocks_and_wait(&bitcoind, electrsd, 5).await; - node_a.sync_wallets().unwrap(); - node_b.sync_wallets().unwrap(); + node_a.sync_wallets().await.unwrap(); + node_b.sync_wallets().await.unwrap(); } else { assert_eq!(node_a.list_balances().lightning_balances.len(), 1); assert!(node_a.list_balances().pending_balances_from_channel_closures.is_empty()); @@ -1560,7 +1652,7 @@ pub(crate) async fn do_channel_full_cycle( .. } => { assert_eq!(counterparty_node_id, node_b.node_id()); - let cur_height = node_a.status().current_best_block.height; + let cur_height = node_a.status().await.current_best_block.height; let blocks_to_go = confirmation_height - cur_height; blocks_to_go }, @@ -1576,7 +1668,7 @@ pub(crate) async fn do_channel_full_cycle( .. } => { assert_eq!(counterparty_node_id, node_a.node_id()); - let cur_height = node_b.status().current_best_block.height; + let cur_height = node_b.status().await.current_best_block.height; let blocks_to_go = confirmation_height - cur_height; blocks_to_go }, @@ -1586,8 +1678,8 @@ pub(crate) async fn do_channel_full_cycle( assert_eq!(node_a_blocks_to_go, node_b_blocks_to_go); generate_blocks_and_wait(&bitcoind, electrsd, node_a_blocks_to_go as usize).await; - node_a.sync_wallets().unwrap(); - node_b.sync_wallets().unwrap(); + node_a.sync_wallets().await.unwrap(); + node_b.sync_wallets().await.unwrap(); assert!(node_a.list_balances().lightning_balances.is_empty()); assert!(node_a.list_balances().pending_balances_from_channel_closures.is_empty()); @@ -1621,6 +1713,7 @@ pub(crate) async fn do_channel_full_cycle( node_a .list_payments_with_filter(|p| p.direction == PaymentDirection::Inbound && matches!(p.kind, PaymentKind::Onchain { .. })) + .await .len(), 3 ); @@ -1628,6 +1721,7 @@ pub(crate) async fn do_channel_full_cycle( node_b .list_payments_with_filter(|p| p.direction == PaymentDirection::Inbound && matches!(p.kind, PaymentKind::Onchain { .. })) + .await .len(), 2 ); diff --git a/tests/common/scenarios/channel.rs b/tests/common/scenarios/channel.rs index 74e04127c6..d2b618828c 100644 --- a/tests/common/scenarios/channel.rs +++ b/tests/common/scenarios/channel.rs @@ -53,7 +53,7 @@ pub(crate) async fn cooperative_close( match initiator { Side::Ldk => { let ext_node_id = peer.get_node_id().await.unwrap(); - node.close_channel(user_channel_id, ext_node_id).unwrap(); + node.close_channel(user_channel_id, ext_node_id).await.unwrap(); }, Side::External => { peer.close_channel(ext_channel_id).await.unwrap(); @@ -75,7 +75,7 @@ pub(crate) async fn force_close( match initiator { Side::Ldk => { let ext_node_id = peer.get_node_id().await.unwrap(); - node.force_close_channel(user_channel_id, ext_node_id, None).unwrap(); + node.force_close_channel(user_channel_id, ext_node_id, None).await.unwrap(); expect_event!(node, ChannelClosed); generate_blocks_and_wait(bitcoind, electrs, 6).await; super::sync_wallets_with_retry(node).await; diff --git a/tests/common/scenarios/connectivity.rs b/tests/common/scenarios/connectivity.rs index e24419c76a..516d1fe8b0 100644 --- a/tests/common/scenarios/connectivity.rs +++ b/tests/common/scenarios/connectivity.rs @@ -23,7 +23,7 @@ pub(crate) async fn disconnect_by_side( ) -> Result<(), String> { let ext_node_id = peer.get_node_id().await.unwrap(); match side { - Side::Ldk => node.disconnect(ext_node_id).map_err(|e| format!("{:?}", e)), + Side::Ldk => node.disconnect(ext_node_id).await.map_err(|e| format!("{:?}", e)), Side::External => { peer.disconnect_peer(node.node_id()).await.map_err(|e| format!("{:?}", e)) }, @@ -34,10 +34,10 @@ pub(crate) async fn disconnect_by_side( pub(crate) async fn reconnect_and_wait( node: &Node, peer_id: PublicKey, addr: SocketAddress, context: &str, ) { - node.connect(peer_id, addr, true).unwrap(); + node.connect(peer_id, addr, true).await.unwrap(); let max_attempts = super::super::INTEROP_TIMEOUT_SECS; for i in 0..max_attempts { - if node.list_peers().iter().any(|p| p.node_id == peer_id && p.is_connected) { + if node.list_peers().await.iter().any(|p| p.node_id == peer_id && p.is_connected) { tokio::time::sleep(Duration::from_secs(2)).await; return; } @@ -59,7 +59,7 @@ pub(crate) async fn disconnect_during_payment( let parsed_invoice = Bolt11Invoice::from_str(&invoice_str).unwrap(); // If send() fails immediately, no event will arrive, so skip event wait below. - let send_ok = node.bolt11_payment().send(&parsed_invoice, None).is_ok(); + let send_ok = node.bolt11_payment().send(&parsed_invoice, None).await.is_ok(); // Disconnect may race with payment delivery; tolerate failure. let _ = disconnect_by_side(node, peer, disconnect_side).await; diff --git a/tests/common/scenarios/mod.rs b/tests/common/scenarios/mod.rs index 7cbf56b8e1..cf1ecc7bb1 100644 --- a/tests/common/scenarios/mod.rs +++ b/tests/common/scenarios/mod.rs @@ -130,7 +130,7 @@ pub(crate) async fn setup_interop_test( let ext_node_id = peer.get_node_id().await.unwrap(); let ext_addr = peer.get_listening_address().await.unwrap(); - node.connect(ext_node_id, ext_addr, true).unwrap(); + node.connect(ext_node_id, ext_addr, true).await.unwrap(); } /// Drive a scenario end-to-end: fund LDK + peer, run the scenario, stop the node. @@ -239,7 +239,7 @@ pub(crate) async fn splice_in_scenario( ) .await; let ext_node_id = peer.get_node_id().await.unwrap(); - node.splice_in(&user_ch, ext_node_id, 500_000).unwrap(); + node.splice_in(&user_ch, ext_node_id, 500_000).await.unwrap(); expect_splice_negotiated_event!(node, ext_node_id); generate_blocks_and_wait(bitcoind, electrs, 6).await; sync_wallets_with_retry(node).await; diff --git a/tests/common/scenarios/payment.rs b/tests/common/scenarios/payment.rs index 191f60abc1..2ff5065dc0 100644 --- a/tests/common/scenarios/payment.rs +++ b/tests/common/scenarios/payment.rs @@ -19,7 +19,7 @@ pub(crate) async fn send_bolt11_to_peer( ) { let invoice_str = peer.create_invoice(amount_msat, label).await.unwrap(); let parsed = Bolt11Invoice::from_str(&invoice_str).unwrap(); - node.bolt11_payment().send(&parsed, None).unwrap(); + node.bolt11_payment().send(&parsed, None).await.unwrap(); expect_event!(node, PaymentSuccessful); } @@ -37,6 +37,7 @@ pub(crate) async fn receive_bolt11_payment( ), 3600, ) + .await .unwrap(); let invoice_str = invoice.to_string(); retry_until_ok(10, "receive_bolt11_payment", || peer.pay_invoice(&invoice_str)).await; @@ -48,7 +49,7 @@ pub(crate) async fn send_keysend_to_peer( node: &Node, peer: &(impl ExternalNode + ?Sized), amount_msat: u64, ) { let peer_id = peer.get_node_id().await.unwrap(); - node.spontaneous_payment().send(amount_msat, peer_id, None).unwrap(); + node.spontaneous_payment().send(amount_msat, peer_id, None).await.unwrap(); expect_event!(node, PaymentSuccessful); } diff --git a/tests/integration_tests_hrn.rs b/tests/integration_tests_hrn.rs index 9102400398..99e500c8f3 100644 --- a/tests/integration_tests_hrn.rs +++ b/tests/integration_tests_hrn.rs @@ -37,25 +37,26 @@ async fn unified_send_to_hrn() { ) .await; - node_a.sync_wallets().unwrap(); + node_a.sync_wallets().await.unwrap(); open_channel(&node_a, &node_b, 4_000_000, true, &electrsd).await; generate_blocks_and_wait(&bitcoind.client, &electrsd.client, 6).await; - node_a.sync_wallets().unwrap(); - node_b.sync_wallets().unwrap(); + node_a.sync_wallets().await.unwrap(); + node_b.sync_wallets().await.unwrap(); expect_channel_ready_event!(node_a, node_b.node_id()); expect_channel_ready_event!(node_b, node_a.node_id()); // Wait until node_b broadcasts a node announcement - while node_b.status().latest_node_announcement_broadcast_timestamp.is_none() { + while node_b.status().await.latest_node_announcement_broadcast_timestamp.is_none() { tokio::time::sleep(std::time::Duration::from_millis(10)).await; } // Sleep to make sure the node announcement propagates tokio::time::sleep(std::time::Duration::from_secs(1)).await; - let test_offer = node_b.bolt12_payment().receive(1000000, "test offer", None, None).unwrap(); + let test_offer = + node_b.bolt12_payment().receive(1000000, "test offer", None, None).await.unwrap(); let hrn_str = "matt@mattcorallo.com"; diff --git a/tests/integration_tests_postgres.rs b/tests/integration_tests_postgres.rs index b96b0c277c..8939134568 100644 --- a/tests/integration_tests_postgres.rs +++ b/tests/integration_tests_postgres.rs @@ -121,7 +121,7 @@ async fn postgres_node_restart() { bitcoin::Amount::from_sat(100_000), ) .await; - node.sync_wallets().unwrap(); + node.sync_wallets().await.unwrap(); let balance = node.list_balances().spendable_onchain_balance_sats; assert!(balance > 0); @@ -148,7 +148,7 @@ async fn postgres_node_restart() { .unwrap(); node.start().unwrap(); - node.sync_wallets().unwrap(); + node.sync_wallets().await.unwrap(); assert_eq!(expected_node_id, node.node_id()); assert_eq!(expected_balance_sats, node.list_balances().spendable_onchain_balance_sats); diff --git a/tests/integration_tests_rust.rs b/tests/integration_tests_rust.rs index 1ea6c45845..55c7f83905 100644 --- a/tests/integration_tests_rust.rs +++ b/tests/integration_tests_rust.rs @@ -187,21 +187,23 @@ async fn channel_open_fails_when_funds_insufficient() { Amount::from_sat(premine_amount_sat), ) .await; - node_a.sync_wallets().unwrap(); - node_b.sync_wallets().unwrap(); + node_a.sync_wallets().await.unwrap(); + node_b.sync_wallets().await.unwrap(); assert_eq!(node_a.list_balances().spendable_onchain_balance_sats, premine_amount_sat); assert_eq!(node_b.list_balances().spendable_onchain_balance_sats, premine_amount_sat); println!("\nA -- open_channel -> B"); assert_eq!( Err(NodeError::InsufficientFunds), - node_a.open_channel( - node_b.node_id(), - node_b.listening_addresses().unwrap().first().unwrap().clone(), - 120000, - None, - None, - ) + node_a + .open_channel( + node_b.node_id(), + node_b.listening_addresses().unwrap().first().unwrap().clone(), + 120000, + None, + None, + ) + .await ); } @@ -234,7 +236,7 @@ async fn multi_hop_sending() { .await; for n in &nodes { - n.sync_wallets().unwrap(); + n.sync_wallets().await.unwrap(); assert_eq!(n.list_balances().spendable_onchain_balance_sats, premine_amount_sat); assert_eq!(n.next_event(), None); } @@ -252,7 +254,7 @@ async fn multi_hop_sending() { // wallet picks up on the broadcast funding tx and doesn't double-spend itself. // // TODO: Remove once fixed in BDK. - nodes[1].sync_wallets().unwrap(); + nodes[1].sync_wallets().await.unwrap(); open_channel(&nodes[1], &nodes[3], 1_000_000, true, &electrsd).await; open_channel(&nodes[2], &nodes[4], 1_000_000, true, &electrsd).await; open_channel(&nodes[3], &nodes[4], 1_000_000, true, &electrsd).await; @@ -260,7 +262,7 @@ async fn multi_hop_sending() { generate_blocks_and_wait(&bitcoind.client, &electrsd.client, 6).await; for n in &nodes { - n.sync_wallets().unwrap(); + n.sync_wallets().await.unwrap(); } expect_event!(nodes[0], ChannelReady); @@ -289,8 +291,9 @@ async fn multi_hop_sending() { let invoice = nodes[4] .bolt11_payment() .receive(2_500_000, &invoice_description.clone().into(), 9217) + .await .unwrap(); - nodes[0].bolt11_payment().send(&invoice, Some(route_params)).unwrap(); + nodes[0].bolt11_payment().send(&invoice, Some(route_params)).await.unwrap(); expect_event!(nodes[1], PaymentForwarded); @@ -338,7 +341,7 @@ async fn start_stop_reinit() { ) .await; - node.sync_wallets().unwrap(); + node.sync_wallets().await.unwrap(); assert_eq!(node.list_balances().spendable_onchain_balance_sats, expected_amount.to_sat()); let log_file = format!("{}/ldk_node.log", config.node_config.clone().storage_dir_path); @@ -367,7 +370,7 @@ async fn start_stop_reinit() { expected_amount.to_sat() ); - reinitialized_node.sync_wallets().unwrap(); + reinitialized_node.sync_wallets().await.unwrap(); assert_eq!( reinitialized_node.list_balances().spendable_onchain_balance_sats, expected_amount.to_sat() @@ -398,13 +401,13 @@ async fn onchain_send_receive() { ) .await; - node_a.sync_wallets().unwrap(); - node_b.sync_wallets().unwrap(); + node_a.sync_wallets().await.unwrap(); + node_b.sync_wallets().await.unwrap(); assert_eq!(node_a.list_balances().spendable_onchain_balance_sats, premine_amount_sat); assert_eq!(node_b.list_balances().spendable_onchain_balance_sats, premine_amount_sat); - let node_a_payments = node_a.list_payments(); - let node_b_payments = node_b.list_payments(); + let node_a_payments = node_a.list_payments().await; + let node_b_payments = node_b.list_payments().await; for payments in [&node_a_payments, &node_b_payments] { assert_eq!(payments.len(), 1) } @@ -426,16 +429,16 @@ async fn onchain_send_receive() { open_channel(&node_b, &node_a, channel_amount_sat, true, &electrsd).await; generate_blocks_and_wait(&bitcoind.client, &electrsd.client, 6).await; - node_a.sync_wallets().unwrap(); - node_b.sync_wallets().unwrap(); + node_a.sync_wallets().await.unwrap(); + node_b.sync_wallets().await.unwrap(); expect_channel_ready_event!(node_a, node_b.node_id()); expect_channel_ready_event!(node_b, node_a.node_id()); let node_a_payments = - node_a.list_payments_with_filter(|p| matches!(p.kind, PaymentKind::Onchain { .. })); + node_a.list_payments_with_filter(|p| matches!(p.kind, PaymentKind::Onchain { .. })).await; assert_eq!(node_a_payments.len(), 1); let node_b_payments = - node_b.list_payments_with_filter(|p| matches!(p.kind, PaymentKind::Onchain { .. })); + node_b.list_payments_with_filter(|p| matches!(p.kind, PaymentKind::Onchain { .. })).await; assert_eq!(node_b_payments.len(), 2); let onchain_fee_buffer_sat = 1000; @@ -467,11 +470,11 @@ async fn onchain_send_receive() { let txid = node_b.onchain_payment().send_to_address(&addr_a, amount_to_send_sats, None).unwrap(); wait_for_tx(&electrsd.client, txid).await; - node_a.sync_wallets().unwrap(); - node_b.sync_wallets().unwrap(); + node_a.sync_wallets().await.unwrap(); + node_b.sync_wallets().await.unwrap(); let payment_id = PaymentId(txid.to_byte_array()); - let payment_a = node_a.payment(&payment_id).unwrap(); + let payment_a = node_a.payment(&payment_id).await.unwrap(); assert_eq!(payment_a.status, PaymentStatus::Pending); match payment_a.kind { PaymentKind::Onchain { status, .. } => { @@ -480,7 +483,7 @@ async fn onchain_send_receive() { _ => panic!("Unexpected payment kind"), } assert!(payment_a.fee_paid_msat > Some(0)); - let payment_b = node_b.payment(&payment_id).unwrap(); + let payment_b = node_b.payment(&payment_id).await.unwrap(); assert_eq!(payment_b.status, PaymentStatus::Pending); match payment_a.kind { PaymentKind::Onchain { status, .. } => { @@ -494,8 +497,8 @@ async fn onchain_send_receive() { assert_eq!(payment_a.fee_paid_msat, payment_b.fee_paid_msat); generate_blocks_and_wait(&bitcoind.client, &electrsd.client, 6).await; - node_a.sync_wallets().unwrap(); - node_b.sync_wallets().unwrap(); + node_a.sync_wallets().await.unwrap(); + node_b.sync_wallets().await.unwrap(); let expected_node_a_balance = expected_node_a_balance + amount_to_send_sats; let expected_node_b_balance_lower = expected_node_b_balance_lower - amount_to_send_sats; @@ -505,13 +508,13 @@ async fn onchain_send_receive() { assert!(node_b.list_balances().spendable_onchain_balance_sats < expected_node_b_balance_upper); let node_a_payments = - node_a.list_payments_with_filter(|p| matches!(p.kind, PaymentKind::Onchain { .. })); + node_a.list_payments_with_filter(|p| matches!(p.kind, PaymentKind::Onchain { .. })).await; assert_eq!(node_a_payments.len(), 2); let node_b_payments = - node_b.list_payments_with_filter(|p| matches!(p.kind, PaymentKind::Onchain { .. })); + node_b.list_payments_with_filter(|p| matches!(p.kind, PaymentKind::Onchain { .. })).await; assert_eq!(node_b_payments.len(), 3); - let payment_a = node_a.payment(&payment_id).unwrap(); + let payment_a = node_a.payment(&payment_id).await.unwrap(); match payment_a.kind { PaymentKind::Onchain { txid: _txid, status } => { assert_eq!(_txid, txid); @@ -520,7 +523,7 @@ async fn onchain_send_receive() { _ => panic!("Unexpected payment kind"), } - let payment_b = node_a.payment(&payment_id).unwrap(); + let payment_b = node_a.payment(&payment_id).await.unwrap(); match payment_b.kind { PaymentKind::Onchain { txid: _txid, status } => { assert_eq!(_txid, txid); @@ -534,8 +537,8 @@ async fn onchain_send_receive() { generate_blocks_and_wait(&bitcoind.client, &electrsd.client, 6).await; wait_for_tx(&electrsd.client, txid).await; - node_a.sync_wallets().unwrap(); - node_b.sync_wallets().unwrap(); + node_a.sync_wallets().await.unwrap(); + node_b.sync_wallets().await.unwrap(); let expected_node_b_balance_lower = expected_node_b_balance_lower + expected_node_a_balance; let expected_node_b_balance_upper = expected_node_b_balance_upper + expected_node_a_balance; @@ -546,10 +549,10 @@ async fn onchain_send_receive() { assert!(node_b.list_balances().spendable_onchain_balance_sats < expected_node_b_balance_upper); let node_a_payments = - node_a.list_payments_with_filter(|p| matches!(p.kind, PaymentKind::Onchain { .. })); + node_a.list_payments_with_filter(|p| matches!(p.kind, PaymentKind::Onchain { .. })).await; assert_eq!(node_a_payments.len(), 3); let node_b_payments = - node_b.list_payments_with_filter(|p| matches!(p.kind, PaymentKind::Onchain { .. })); + node_b.list_payments_with_filter(|p| matches!(p.kind, PaymentKind::Onchain { .. })).await; assert_eq!(node_b_payments.len(), 4); let addr_b = node_b.onchain_payment().new_address().unwrap(); @@ -557,8 +560,8 @@ async fn onchain_send_receive() { generate_blocks_and_wait(&bitcoind.client, &electrsd.client, 6).await; wait_for_tx(&electrsd.client, txid).await; - node_a.sync_wallets().unwrap(); - node_b.sync_wallets().unwrap(); + node_a.sync_wallets().await.unwrap(); + node_b.sync_wallets().await.unwrap(); let expected_node_b_balance_lower = expected_node_b_balance_lower + reserve_amount_sat; let expected_node_b_balance_upper = expected_node_b_balance_upper + reserve_amount_sat; @@ -570,10 +573,10 @@ async fn onchain_send_receive() { assert!(node_b.list_balances().spendable_onchain_balance_sats < expected_node_b_balance_upper); let node_a_payments = - node_a.list_payments_with_filter(|p| matches!(p.kind, PaymentKind::Onchain { .. })); + node_a.list_payments_with_filter(|p| matches!(p.kind, PaymentKind::Onchain { .. })).await; assert_eq!(node_a_payments.len(), 4); let node_b_payments = - node_b.list_payments_with_filter(|p| matches!(p.kind, PaymentKind::Onchain { .. })); + node_b.list_payments_with_filter(|p| matches!(p.kind, PaymentKind::Onchain { .. })).await; assert_eq!(node_b_payments.len(), 5); } @@ -598,8 +601,8 @@ async fn onchain_send_all_retains_reserve() { ) .await; - node_a.sync_wallets().unwrap(); - node_b.sync_wallets().unwrap(); + node_a.sync_wallets().await.unwrap(); + node_b.sync_wallets().await.unwrap(); assert_eq!(node_a.list_balances().spendable_onchain_balance_sats, premine_amount_sat); assert_eq!(node_b.list_balances().spendable_onchain_balance_sats, premine_amount_sat); @@ -609,8 +612,8 @@ async fn onchain_send_all_retains_reserve() { wait_for_tx(&electrsd.client, txid).await; generate_blocks_and_wait(&bitcoind.client, &electrsd.client, 6).await; - node_a.sync_wallets().unwrap(); - node_b.sync_wallets().unwrap(); + node_a.sync_wallets().await.unwrap(); + node_b.sync_wallets().await.unwrap(); // Check node a sent all and node b received it assert_eq!(node_a.list_balances().spendable_onchain_balance_sats, 0); assert!(((premine_amount_sat * 2 - onchain_fee_buffer_sat)..=(premine_amount_sat * 2)) @@ -626,15 +629,15 @@ async fn onchain_send_all_retains_reserve() { .unwrap(); wait_for_tx(&electrsd.client, txid).await; generate_blocks_and_wait(&bitcoind.client, &electrsd.client, 6).await; - node_a.sync_wallets().unwrap(); - node_b.sync_wallets().unwrap(); + node_a.sync_wallets().await.unwrap(); + node_b.sync_wallets().await.unwrap(); assert_eq!(node_a.list_balances().spendable_onchain_balance_sats, reserve_amount_sat); // Open a channel. open_channel(&node_b, &node_a, premine_amount_sat, false, &electrsd).await; generate_blocks_and_wait(&bitcoind.client, &electrsd.client, 6).await; - node_a.sync_wallets().unwrap(); - node_b.sync_wallets().unwrap(); + node_a.sync_wallets().await.unwrap(); + node_b.sync_wallets().await.unwrap(); expect_channel_ready_event!(node_a, node_b.node_id()); expect_channel_ready_event!(node_b, node_a.node_id()); @@ -650,8 +653,8 @@ async fn onchain_send_all_retains_reserve() { wait_for_tx(&electrsd.client, txid).await; generate_blocks_and_wait(&bitcoind.client, &electrsd.client, 6).await; - node_a.sync_wallets().unwrap(); - node_b.sync_wallets().unwrap(); + node_a.sync_wallets().await.unwrap(); + node_b.sync_wallets().await.unwrap(); // Check node b sent all and node a received it assert_eq!(node_b.list_balances().total_onchain_balance_sats, reserve_amount_sat); @@ -682,7 +685,7 @@ async fn onchain_wallet_recovery() { Amount::from_sat(premine_amount_sat), ) .await; - original_node.sync_wallets().unwrap(); + original_node.sync_wallets().await.unwrap(); assert_eq!(original_node.list_balances().spendable_onchain_balance_sats, premine_amount_sat); let addr_2 = original_node.onchain_payment().new_address().unwrap(); @@ -698,7 +701,7 @@ async fn onchain_wallet_recovery() { generate_blocks_and_wait(&bitcoind.client, &electrsd.client, 1).await; - original_node.sync_wallets().unwrap(); + original_node.sync_wallets().await.unwrap(); assert_eq!( original_node.list_balances().spendable_onchain_balance_sats, premine_amount_sat * 2 @@ -713,7 +716,7 @@ async fn onchain_wallet_recovery() { recovered_config.recovery_mode = true; let recovered_node = setup_node(&chain_source, recovered_config); - recovered_node.sync_wallets().unwrap(); + recovered_node.sync_wallets().await.unwrap(); assert_eq!( recovered_node.list_balances().spendable_onchain_balance_sats, premine_amount_sat * 2 @@ -736,7 +739,7 @@ async fn onchain_wallet_recovery() { generate_blocks_and_wait(&bitcoind.client, &electrsd.client, 1).await; - recovered_node.sync_wallets().unwrap(); + recovered_node.sync_wallets().await.unwrap(); assert_eq!( recovered_node.list_balances().spendable_onchain_balance_sats, premine_amount_sat * 3 @@ -799,7 +802,7 @@ async fn run_rbf_test(is_insert_block: bool) { ($expected_balance_sat:expr, $is_spendable:expr) => { let spend_balance = if $is_spendable { $expected_balance_sat } else { 0 }; for node in &nodes { - node.sync_wallets().unwrap(); + node.sync_wallets().await.unwrap(); let balances = node.list_balances(); assert_eq!(balances.spendable_onchain_balance_sats, spend_balance); assert_eq!(balances.total_onchain_balance_sats, $expected_balance_sat); @@ -903,8 +906,8 @@ async fn connection_multi_listen() { let node_addrs_b = node_b.listening_addresses().unwrap(); for node_addr_b in &node_addrs_b { - node_a.connect(node_id_b, node_addr_b.clone(), false).unwrap(); - node_a.disconnect(node_id_b).unwrap(); + node_a.connect(node_id_b, node_addr_b.clone(), false).await.unwrap(); + node_a.disconnect(node_id_b).await.unwrap(); } } @@ -923,14 +926,14 @@ async fn do_connection_restart_behavior(persist: bool) { let node_id_b = node_b.node_id(); let node_addr_b = node_b.listening_addresses().unwrap().first().unwrap().clone(); - node_a.connect(node_id_b, node_addr_b, persist).unwrap(); + node_a.connect(node_id_b, node_addr_b, persist).await.unwrap(); - let peer_details_a = node_a.list_peers().first().unwrap().clone(); + let peer_details_a = node_a.list_peers().await.first().unwrap().clone(); assert_eq!(peer_details_a.node_id, node_id_b); assert_eq!(peer_details_a.is_persisted, persist); assert!(peer_details_a.is_connected); - let peer_details_b = node_b.list_peers().first().unwrap().clone(); + let peer_details_b = node_b.list_peers().await.first().unwrap().clone(); assert_eq!(peer_details_b.node_id, node_id_a); assert_eq!(peer_details_b.is_persisted, false); assert!(peer_details_a.is_connected); @@ -945,18 +948,18 @@ async fn do_connection_restart_behavior(persist: bool) { tokio::time::sleep(std::time::Duration::from_secs(5)).await; if persist { - let peer_details_a = node_a.list_peers().first().unwrap().clone(); + let peer_details_a = node_a.list_peers().await.first().unwrap().clone(); assert_eq!(peer_details_a.node_id, node_id_b); assert_eq!(peer_details_a.is_persisted, persist); assert!(peer_details_a.is_connected); - let peer_details_b = node_b.list_peers().first().unwrap().clone(); + let peer_details_b = node_b.list_peers().await.first().unwrap().clone(); assert_eq!(peer_details_b.node_id, node_id_a); assert_eq!(peer_details_b.is_persisted, false); assert!(peer_details_a.is_connected); } else { - assert!(node_a.list_peers().is_empty()); - assert!(node_b.list_peers().is_empty()); + assert!(node_a.list_peers().await.is_empty()); + assert!(node_b.list_peers().await.is_empty()); } } @@ -977,7 +980,10 @@ async fn concurrent_connections_succeed() { let thread_node = Arc::clone(&node_a); let thread_addr = node_addr_b.clone(); let handle = std::thread::spawn(move || { - thread_node.connect(node_id_b, thread_addr, false).unwrap(); + tokio::runtime::Runtime::new() + .unwrap() + .block_on(async move { thread_node.connect(node_id_b, thread_addr, false).await }) + .unwrap(); }); handles.push(handle); } @@ -1005,8 +1011,8 @@ async fn splice_channel() { ) .await; - node_a.sync_wallets().unwrap(); - node_b.sync_wallets().unwrap(); + node_a.sync_wallets().await.unwrap(); + node_b.sync_wallets().await.unwrap(); assert_eq!(node_a.list_balances().total_onchain_balance_sats, premine_amount_sat); assert_eq!(node_b.list_balances().total_onchain_balance_sats, premine_amount_sat); @@ -1016,8 +1022,8 @@ async fn splice_channel() { // Open a channel with Node A contributing the funding generate_blocks_and_wait(&bitcoind.client, &electrsd.client, 6).await; - node_a.sync_wallets().unwrap(); - node_b.sync_wallets().unwrap(); + node_a.sync_wallets().await.unwrap(); + node_b.sync_wallets().await.unwrap(); let user_channel_id_a = expect_channel_ready_event!(node_a, node_b.node_id()); let user_channel_id_b = expect_channel_ready_event!(node_b, node_a.node_id()); @@ -1041,7 +1047,7 @@ async fn splice_channel() { let amount_msat = 400_000_000; assert_eq!( - node_b.splice_in(&user_channel_id_b, node_b.node_id(), 5_000_000), + node_b.splice_in(&user_channel_id_b, node_b.node_id(), 5_000_000).await, Err(NodeError::ChannelSplicingFailed), ); assert_eq!( @@ -1049,20 +1055,20 @@ async fn splice_channel() { Err(NodeError::ChannelSplicingFailed), ); assert_eq!( - node_b.spontaneous_payment().send(amount_msat, node_a.node_id(), None), + node_b.spontaneous_payment().send(amount_msat, node_a.node_id(), None).await, Err(NodeError::PaymentSendingFailed) ); // Splice-in funds for Node B so that it has outbound liquidity to make a payment - node_b.splice_in(&user_channel_id_b, node_a.node_id(), 4_000_000).unwrap(); + node_b.splice_in(&user_channel_id_b, node_a.node_id(), 4_000_000).await.unwrap(); let txo = expect_splice_negotiated_event!(node_a, node_b.node_id()); expect_splice_negotiated_event!(node_b, node_a.node_id()); generate_blocks_and_wait(&bitcoind.client, &electrsd.client, 6).await; - node_a.sync_wallets().unwrap(); - node_b.sync_wallets().unwrap(); + node_a.sync_wallets().await.unwrap(); + node_b.sync_wallets().await.unwrap(); expect_channel_ready_event!(node_a, node_b.node_id()); expect_channel_ready_event!(node_b, node_a.node_id()); @@ -1078,7 +1084,7 @@ async fn splice_channel() { // updating the BDK wallet dependency. See: https://github.com/bitcoindevkit/bdk_wallet/pull/479 let expected_splice_in_lightning_balance_sat = 4_000_003; - let payments = node_b.list_payments(); + let payments = node_b.list_payments().await; let payment = payments.into_iter().find(|p| p.id == PaymentId(txo.txid.to_byte_array())).unwrap(); assert_eq!(payment.fee_paid_msat, Some(expected_splice_in_fee_sat * 1_000)); @@ -1093,7 +1099,7 @@ async fn splice_channel() { ); let payment_id = - node_b.spontaneous_payment().send(amount_msat, node_a.node_id(), None).unwrap(); + node_b.spontaneous_payment().send(amount_msat, node_a.node_id(), None).await.unwrap(); expect_payment_successful_event!(node_b, Some(payment_id), None); expect_payment_received_event!(node_a, amount_msat); @@ -1119,15 +1125,15 @@ async fn splice_channel() { generate_blocks_and_wait(&bitcoind.client, &electrsd.client, 6).await; - node_a.sync_wallets().unwrap(); - node_b.sync_wallets().unwrap(); + node_a.sync_wallets().await.unwrap(); + node_b.sync_wallets().await.unwrap(); expect_channel_ready_event!(node_a, node_b.node_id()); expect_channel_ready_event!(node_b, node_a.node_id()); let expected_splice_out_fee_sat = 183; - let payments = node_a.list_payments(); + let payments = node_a.list_payments().await; let payment = payments.into_iter().find(|p| p.id == PaymentId(txo.txid.to_byte_array())).unwrap(); assert_eq!(payment.fee_paid_msat, Some(expected_splice_out_fee_sat * 1_000)); @@ -1158,19 +1164,19 @@ async fn simple_bolt12_send_receive() { ) .await; - node_a.sync_wallets().unwrap(); + node_a.sync_wallets().await.unwrap(); open_channel(&node_a, &node_b, 4_000_000, true, &electrsd).await; generate_blocks_and_wait(&bitcoind.client, &electrsd.client, 6).await; - node_a.sync_wallets().unwrap(); - node_b.sync_wallets().unwrap(); + node_a.sync_wallets().await.unwrap(); + node_b.sync_wallets().await.unwrap(); expect_channel_ready_event!(node_a, node_b.node_id()); expect_channel_ready_event!(node_b, node_a.node_id()); // Sleep until we broadcasted a node announcement. - while node_b.status().latest_node_announcement_broadcast_timestamp.is_none() { + while node_b.status().await.latest_node_announcement_broadcast_timestamp.is_none() { tokio::time::sleep(std::time::Duration::from_millis(10)).await; } @@ -1185,6 +1191,7 @@ async fn simple_bolt12_send_receive() { let payment_id = node_a .bolt12_payment() .send(&offer, expected_quantity, expected_payer_note.clone(), None) + .await .unwrap(); let event = node_a.next_event_async().await; @@ -1200,8 +1207,9 @@ async fn simple_bolt12_send_receive() { }, ref e => panic!("{} got unexpected event!: {:?}", "node_a", e), } - let node_a_payments = - node_a.list_payments_with_filter(|p| matches!(p.kind, PaymentKind::Bolt12Offer { .. })); + let node_a_payments = node_a + .list_payments_with_filter(|p| matches!(p.kind, PaymentKind::Bolt12Offer { .. })) + .await; assert_eq!(node_a_payments.len(), 1); match node_a_payments.first().unwrap().kind { PaymentKind::Bolt12Offer { @@ -1227,8 +1235,9 @@ async fn simple_bolt12_send_receive() { assert_eq!(node_a_payments.first().unwrap().amount_msat, Some(expected_amount_msat)); expect_payment_received_event!(node_b, expected_amount_msat); - let node_b_payments = - node_b.list_payments_with_filter(|p| matches!(p.kind, PaymentKind::Bolt12Offer { .. })); + let node_b_payments = node_b + .list_payments_with_filter(|p| matches!(p.kind, PaymentKind::Bolt12Offer { .. })) + .await; assert_eq!(node_b_payments.len(), 1); match node_b_payments.first().unwrap().kind { PaymentKind::Bolt12Offer { hash, preimage, secret, offer_id, .. } => { @@ -1253,6 +1262,7 @@ async fn simple_bolt12_send_receive() { assert!(node_a .bolt12_payment() .send_using_amount(&offer, less_than_offer_amount, None, None, None) + .await .is_err()); let payment_id = node_a .bolt12_payment() @@ -1263,12 +1273,15 @@ async fn simple_bolt12_send_receive() { expected_payer_note.clone(), None, ) + .await .unwrap(); expect_payment_successful_event!(node_a, Some(payment_id), None); - let node_a_payments = node_a.list_payments_with_filter(|p| { - matches!(p.kind, PaymentKind::Bolt12Offer { .. }) && p.id == payment_id - }); + let node_a_payments = node_a + .list_payments_with_filter(|p| { + matches!(p.kind, PaymentKind::Bolt12Offer { .. }) && p.id == payment_id + }) + .await; assert_eq!(node_a_payments.len(), 1); let payment_hash = match node_a_payments.first().unwrap().kind { PaymentKind::Bolt12Offer { @@ -1296,9 +1309,11 @@ async fn simple_bolt12_send_receive() { expect_payment_received_event!(node_b, expected_amount_msat); let node_b_payment_id = PaymentId(payment_hash.0); - let node_b_payments = node_b.list_payments_with_filter(|p| { - matches!(p.kind, PaymentKind::Bolt12Offer { .. }) && p.id == node_b_payment_id - }); + let node_b_payments = node_b + .list_payments_with_filter(|p| { + matches!(p.kind, PaymentKind::Bolt12Offer { .. }) && p.id == node_b_payment_id + }) + .await; assert_eq!(node_b_payments.len(), 1); match node_b_payments.first().unwrap().kind { PaymentKind::Bolt12Offer { hash, preimage, secret, offer_id, .. } => { @@ -1326,8 +1341,9 @@ async fn simple_bolt12_send_receive() { expected_payer_note.clone(), None, ) + .await .unwrap(); - let invoice = node_a.bolt12_payment().request_refund_payment(&refund).unwrap(); + let invoice = node_a.bolt12_payment().request_refund_payment(&refund).await.unwrap(); expect_payment_received_event!(node_a, overpaid_amount); let node_b_payment_id = node_b @@ -1335,14 +1351,17 @@ async fn simple_bolt12_send_receive() { matches!(p.kind, PaymentKind::Bolt12Refund { .. }) && p.amount_msat == Some(overpaid_amount) }) + .await .first() .unwrap() .id; expect_payment_successful_event!(node_b, Some(node_b_payment_id), None); - let node_b_payments = node_b.list_payments_with_filter(|p| { - matches!(p.kind, PaymentKind::Bolt12Refund { .. }) && p.id == node_b_payment_id - }); + let node_b_payments = node_b + .list_payments_with_filter(|p| { + matches!(p.kind, PaymentKind::Bolt12Refund { .. }) && p.id == node_b_payment_id + }) + .await; assert_eq!(node_b_payments.len(), 1); match node_b_payments.first().unwrap().kind { PaymentKind::Bolt12Refund { @@ -1366,9 +1385,11 @@ async fn simple_bolt12_send_receive() { assert_eq!(node_b_payments.first().unwrap().amount_msat, Some(overpaid_amount)); let node_a_payment_id = PaymentId(invoice.payment_hash().0); - let node_a_payments = node_a.list_payments_with_filter(|p| { - matches!(p.kind, PaymentKind::Bolt12Refund { .. }) && p.id == node_a_payment_id - }); + let node_a_payments = node_a + .list_payments_with_filter(|p| { + matches!(p.kind, PaymentKind::Bolt12Refund { .. }) && p.id == node_a_payment_id + }) + .await; assert_eq!(node_a_payments.len(), 1); match node_a_payments.first().unwrap().kind { PaymentKind::Bolt12Refund { hash, preimage, secret, .. } => { @@ -1429,10 +1450,10 @@ async fn async_payment() { ) .await; - node_sender.sync_wallets().unwrap(); - node_sender_lsp.sync_wallets().unwrap(); - node_receiver_lsp.sync_wallets().unwrap(); - node_receiver.sync_wallets().unwrap(); + node_sender.sync_wallets().await.unwrap(); + node_sender_lsp.sync_wallets().await.unwrap(); + node_receiver_lsp.sync_wallets().await.unwrap(); + node_receiver.sync_wallets().await.unwrap(); open_channel(&node_sender, &node_sender_lsp, 400_000, false, &electrsd).await; open_channel(&node_sender_lsp, &node_receiver_lsp, 400_000, true, &electrsd).await; @@ -1448,10 +1469,10 @@ async fn async_payment() { generate_blocks_and_wait(&bitcoind.client, &electrsd.client, 6).await; - node_sender.sync_wallets().unwrap(); - node_sender_lsp.sync_wallets().unwrap(); - node_receiver_lsp.sync_wallets().unwrap(); - node_receiver.sync_wallets().unwrap(); + node_sender.sync_wallets().await.unwrap(); + node_sender_lsp.sync_wallets().await.unwrap(); + node_receiver_lsp.sync_wallets().await.unwrap(); + node_receiver.sync_wallets().await.unwrap(); expect_channel_ready_event!(node_sender, node_sender_lsp.node_id()); expect_channel_ready_events!( @@ -1504,8 +1525,11 @@ async fn async_payment() { node_receiver.stop().unwrap(); - let payment_id = - node_sender.bolt12_payment().send_using_amount(&offer, 5_000, None, None, None).unwrap(); + let payment_id = node_sender + .bolt12_payment() + .send_using_amount(&offer, 5_000, None, None, None) + .await + .unwrap(); // Sleep to allow the payment reach a state where the htlc is held and waiting for the receiver to come online. tokio::time::sleep(std::time::Duration::from_millis(3000)).await; @@ -1557,21 +1581,21 @@ async fn test_node_announcement_propagation() { ) .await; - node_a.sync_wallets().unwrap(); + node_a.sync_wallets().await.unwrap(); // Open an announced channel from node_a to node_b open_channel(&node_a, &node_b, 4_000_000, true, &electrsd).await; generate_blocks_and_wait(&bitcoind.client, &electrsd.client, 6).await; - node_a.sync_wallets().unwrap(); - node_b.sync_wallets().unwrap(); + node_a.sync_wallets().await.unwrap(); + node_b.sync_wallets().await.unwrap(); expect_channel_ready_event!(node_a, node_b.node_id()); expect_channel_ready_event!(node_b, node_a.node_id()); // Wait until node_b broadcasts a node announcement - while node_b.status().latest_node_announcement_broadcast_timestamp.is_none() { + while node_b.status().await.latest_node_announcement_broadcast_timestamp.is_none() { tokio::time::sleep(std::time::Duration::from_millis(10)).await; } @@ -1625,6 +1649,7 @@ async fn generate_bip21_uri() { let initial_uni_payment = node_b .unified_payment() .receive(expected_amount_sats, "asdf", expiry_sec) + .await .expect("Failed to generate URI"); println!("Initial URI (no channels): {}", initial_uni_payment); @@ -1640,12 +1665,12 @@ async fn generate_bip21_uri() { ) .await; - node_a.sync_wallets().unwrap(); + node_a.sync_wallets().await.unwrap(); open_channel(&node_a, &node_b, 4_000_000, true, &electrsd).await; generate_blocks_and_wait(&bitcoind.client, &electrsd.client, 6).await; - node_a.sync_wallets().unwrap(); - node_b.sync_wallets().unwrap(); + node_a.sync_wallets().await.unwrap(); + node_b.sync_wallets().await.unwrap(); expect_channel_ready_event!(node_a, node_b.node_id()); expect_channel_ready_event!(node_b, node_a.node_id()); @@ -1654,6 +1679,7 @@ async fn generate_bip21_uri() { let uni_payment = node_b .unified_payment() .receive(expected_amount_sats, "asdf", expiry_sec) + .await .expect("Failed to generate URI"); println!("Generated URI: {}", uni_payment); @@ -1680,18 +1706,18 @@ async fn unified_send_receive_bip21_uri() { ) .await; - node_a.sync_wallets().unwrap(); + node_a.sync_wallets().await.unwrap(); open_channel(&node_a, &node_b, 4_000_000, true, &electrsd).await; generate_blocks_and_wait(&bitcoind.client, &electrsd.client, 6).await; - node_a.sync_wallets().unwrap(); - node_b.sync_wallets().unwrap(); + node_a.sync_wallets().await.unwrap(); + node_b.sync_wallets().await.unwrap(); expect_channel_ready_event!(node_a, node_b.node_id()); expect_channel_ready_event!(node_b, node_a.node_id()); // Sleep until we broadcast a node announcement. - while node_b.status().latest_node_announcement_broadcast_timestamp.is_none() { + while node_b.status().await.latest_node_announcement_broadcast_timestamp.is_none() { tokio::time::sleep(std::time::Duration::from_millis(10)).await; } @@ -1701,7 +1727,8 @@ async fn unified_send_receive_bip21_uri() { let expected_amount_sats = 100_000; let expiry_sec = 4_000; - let uni_payment = node_b.unified_payment().receive(expected_amount_sats, "asdf", expiry_sec); + let uni_payment = + node_b.unified_payment().receive(expected_amount_sats, "asdf", expiry_sec).await; let uri_str = uni_payment.clone().unwrap(); let offer_payment_id: PaymentId = match node_a.unified_payment().send(&uri_str, None, None).await { @@ -1744,7 +1771,7 @@ async fn unified_send_receive_bip21_uri() { let expect_onchain_amount_sats = 800_000; let onchain_uni_payment = - node_b.unified_payment().receive(expect_onchain_amount_sats, "asdf", 4_000).unwrap(); + node_b.unified_payment().receive(expect_onchain_amount_sats, "asdf", 4_000).await.unwrap(); // Cut off any lightning part to fallback to on-chain only. let uri_str_without_lightning = onchain_uni_payment.split("&lightning=").next().unwrap(); @@ -1767,8 +1794,8 @@ async fn unified_send_receive_bip21_uri() { generate_blocks_and_wait(&bitcoind.client, &electrsd.client, 6).await; wait_for_tx(&electrsd.client, txid).await; - node_a.sync_wallets().unwrap(); - node_b.sync_wallets().unwrap(); + node_a.sync_wallets().await.unwrap(); + node_b.sync_wallets().await.unwrap(); assert_eq!(node_b.list_balances().total_onchain_balance_sats, 800_000); assert_eq!(node_b.list_balances().total_lightning_balance_sats, 200_000); @@ -1840,17 +1867,17 @@ async fn do_lsps2_client_service_integration(client_trusts_lsp: bool) { Amount::from_sat(premine_amount_sat), ) .await; - service_node.sync_wallets().unwrap(); - client_node.sync_wallets().unwrap(); - payer_node.sync_wallets().unwrap(); + service_node.sync_wallets().await.unwrap(); + client_node.sync_wallets().await.unwrap(); + payer_node.sync_wallets().await.unwrap(); // Open a channel payer -> service that will allow paying the JIT invoice println!("Opening channel payer_node -> service_node!"); open_channel(&payer_node, &service_node, 5_000_000, false, &electrsd).await; generate_blocks_and_wait(&bitcoind.client, &electrsd.client, 6).await; - service_node.sync_wallets().unwrap(); - payer_node.sync_wallets().unwrap(); + service_node.sync_wallets().await.unwrap(); + payer_node.sync_wallets().await.unwrap(); expect_channel_ready_event!(payer_node, service_node.node_id()); expect_channel_ready_event!(service_node, payer_node.node_id()); @@ -1862,11 +1889,12 @@ async fn do_lsps2_client_service_integration(client_trusts_lsp: bool) { let jit_invoice = client_node .bolt11_payment() .receive_via_jit_channel(jit_amount_msat, &invoice_description.into(), 1024, None) + .await .unwrap(); // Have the payer_node pay the invoice, therby triggering channel open service_node -> client_node. println!("Paying JIT invoice!"); - let payment_id = payer_node.bolt11_payment().send(&jit_invoice, None).unwrap(); + let payment_id = payer_node.bolt11_payment().send(&jit_invoice, None).await.unwrap(); expect_channel_pending_event!(service_node, client_node.node_id()); expect_channel_ready_event!(service_node, client_node.node_id()); expect_event!(service_node, PaymentForwarded); @@ -1878,7 +1906,7 @@ async fn do_lsps2_client_service_integration(client_trusts_lsp: bool) { expect_payment_successful_event!(payer_node, Some(payment_id), None); let client_payment_id = expect_payment_received_event!(client_node, expected_received_amount_msat).unwrap(); - let client_payment = client_node.payment(&client_payment_id).unwrap(); + let client_payment = client_node.payment(&client_payment_id).await.unwrap(); match client_payment.kind { PaymentKind::Bolt11 { counterparty_skimmed_fee_msat, .. } => { assert_eq!(counterparty_skimmed_fee_msat, Some(service_fee_msat)); @@ -1897,13 +1925,16 @@ async fn do_lsps2_client_service_integration(client_trusts_lsp: bool) { let invoice_description = Bolt11InvoiceDescription::Direct(Description::new(String::from("asdf")).unwrap()).into(); let amount_msat = 5_000_000; - let invoice = - client_node.bolt11_payment().receive(amount_msat, &invoice_description, 1024).unwrap(); + let invoice = client_node + .bolt11_payment() + .receive(amount_msat, &invoice_description, 1024) + .await + .unwrap(); // Have the payer_node pay the invoice, to check regular forwards service_node -> client_node // are working as expected. println!("Paying regular invoice!"); - let payment_id = payer_node.bolt11_payment().send(&invoice, None).unwrap(); + let payment_id = payer_node.bolt11_payment().send(&invoice, None).await.unwrap(); expect_payment_successful_event!(payer_node, Some(payment_id), None); expect_event!(service_node, PaymentForwarded); expect_payment_received_event!(client_node, amount_msat); @@ -1925,11 +1956,12 @@ async fn do_lsps2_client_service_integration(client_trusts_lsp: bool) { None, manual_payment_hash, ) + .await .unwrap(); // Have the payer_node pay the invoice, therby triggering channel open service_node -> client_node. println!("Paying JIT invoice!"); - let payment_id = payer_node.bolt11_payment().send(&jit_invoice, None).unwrap(); + let payment_id = payer_node.bolt11_payment().send(&jit_invoice, None).await.unwrap(); expect_channel_pending_event!(service_node, client_node.node_id()); expect_channel_ready_event!(service_node, client_node.node_id()); expect_channel_pending_event!(client_node, service_node.node_id()); @@ -1947,13 +1979,14 @@ async fn do_lsps2_client_service_integration(client_trusts_lsp: bool) { client_node .bolt11_payment() .claim_for_hash(manual_payment_hash, claimable_amount_msat, manual_preimage) + .await .unwrap(); expect_event!(service_node, PaymentForwarded); expect_payment_successful_event!(payer_node, Some(payment_id), None); let client_payment_id = expect_payment_received_event!(client_node, expected_received_amount_msat).unwrap(); - let client_payment = client_node.payment(&client_payment_id).unwrap(); + let client_payment = client_node.payment(&client_payment_id).await.unwrap(); match client_payment.kind { PaymentKind::Bolt11 { counterparty_skimmed_fee_msat, .. } => { assert_eq!(counterparty_skimmed_fee_msat, Some(service_fee_msat)); @@ -1978,11 +2011,12 @@ async fn do_lsps2_client_service_integration(client_trusts_lsp: bool) { None, manual_payment_hash, ) + .await .unwrap(); // Have the payer_node pay the invoice, therby triggering channel open service_node -> client_node. println!("Paying JIT invoice!"); - let payment_id = payer_node.bolt11_payment().send(&jit_invoice, None).unwrap(); + let payment_id = payer_node.bolt11_payment().send(&jit_invoice, None).await.unwrap(); expect_channel_pending_event!(service_node, client_node.node_id()); expect_channel_ready_event!(service_node, client_node.node_id()); expect_channel_pending_event!(client_node, service_node.node_id()); @@ -1997,10 +2031,10 @@ async fn do_lsps2_client_service_integration(client_trusts_lsp: bool) { expected_received_amount_msat ); println!("Failing payment!"); - client_node.bolt11_payment().fail_for_hash(manual_payment_hash).unwrap(); + client_node.bolt11_payment().fail_for_hash(manual_payment_hash).await.unwrap(); expect_event!(payer_node, PaymentFailed); - assert_eq!(client_node.payment(&payment_id).unwrap().status, PaymentStatus::Failed); + assert_eq!(client_node.payment(&payment_id).await.unwrap().status, PaymentStatus::Failed); } #[tokio::test(flavor = "multi_thread", worker_threads = 1)] @@ -2036,12 +2070,12 @@ async fn spontaneous_send_with_custom_preimage() { Amount::from_sat(premine_sat), ) .await; - node_a.sync_wallets().unwrap(); - node_b.sync_wallets().unwrap(); + node_a.sync_wallets().await.unwrap(); + node_b.sync_wallets().await.unwrap(); open_channel(&node_a, &node_b, 500_000, true, &electrsd).await; generate_blocks_and_wait(&bitcoind.client, &electrsd.client, 6).await; - node_a.sync_wallets().unwrap(); - node_b.sync_wallets().unwrap(); + node_a.sync_wallets().await.unwrap(); + node_b.sync_wallets().await.unwrap(); expect_channel_ready_event!(node_a, node_b.node_id()); expect_channel_ready_event!(node_b, node_a.node_id()); @@ -2054,12 +2088,13 @@ async fn spontaneous_send_with_custom_preimage() { let payment_id = node_a .spontaneous_payment() .send_with_preimage(amount_msat, node_b.node_id(), custom_preimage, None) + .await .unwrap(); // check payment status and verify stored preimage expect_payment_successful_event!(node_a, Some(payment_id), None); let details: PaymentDetails = - node_a.list_payments_with_filter(|p| p.id == payment_id).first().unwrap().clone(); + node_a.list_payments_with_filter(|p| p.id == payment_id).await.first().unwrap().clone(); assert_eq!(details.status, PaymentStatus::Succeeded); if let PaymentKind::Spontaneous { preimage: Some(pi), .. } = details.kind { assert_eq!(pi.0, custom_bytes); @@ -2069,10 +2104,12 @@ async fn spontaneous_send_with_custom_preimage() { // Verify receiver side (node_b) expect_payment_received_event!(node_b, amount_msat); - let receiver_payments: Vec = node_b.list_payments_with_filter(|p| { - p.direction == PaymentDirection::Inbound - && matches!(p.kind, PaymentKind::Spontaneous { .. }) - }); + let receiver_payments: Vec = node_b + .list_payments_with_filter(|p| { + p.direction == PaymentDirection::Inbound + && matches!(p.kind, PaymentKind::Spontaneous { .. }) + }) + .await; assert_eq!(receiver_payments.len(), 1); let receiver_details = &receiver_payments[0]; @@ -2159,16 +2196,16 @@ async fn lsps2_client_trusts_lsp() { Amount::from_sat(premine_amount_sat), ) .await; - service_node.sync_wallets().unwrap(); - client_node.sync_wallets().unwrap(); - payer_node.sync_wallets().unwrap(); + service_node.sync_wallets().await.unwrap(); + client_node.sync_wallets().await.unwrap(); + payer_node.sync_wallets().await.unwrap(); println!("Premine complete!"); // Open a channel payer -> service that will allow paying the JIT invoice open_channel(&payer_node, &service_node, 5_000_000, false, &electrsd).await; generate_blocks_and_wait(&bitcoind.client, &electrsd.client, 6).await; - service_node.sync_wallets().unwrap(); - payer_node.sync_wallets().unwrap(); + service_node.sync_wallets().await.unwrap(); + payer_node.sync_wallets().await.unwrap(); expect_channel_ready_event!(payer_node, service_node.node_id()); expect_channel_ready_event!(service_node, payer_node.node_id()); @@ -2188,11 +2225,12 @@ async fn lsps2_client_trusts_lsp() { None, manual_payment_hash, ) + .await .unwrap(); // Have the payer_node pay the invoice, therby triggering channel open service_node -> client_node. println!("Paying JIT invoice!"); - let payment_id = payer_node.bolt11_payment().send(&res, None).unwrap(); + let payment_id = payer_node.bolt11_payment().send(&res, None).await.unwrap(); println!("Payment ID: {:?}", payment_id); let funding_txo = expect_channel_pending_event!(service_node, client_node.node_id()); expect_channel_ready_event!(service_node, client_node.node_id()); @@ -2206,8 +2244,8 @@ async fn lsps2_client_trusts_lsp() { let funding_tx_found = mempool.0.iter().any(|txid| *txid == funding_txo.txid); assert!(!funding_tx_found, "Funding transaction should NOT be broadcast yet"); - service_node.sync_wallets().unwrap(); - client_node.sync_wallets().unwrap(); + service_node.sync_wallets().await.unwrap(); + client_node.sync_wallets().await.unwrap(); assert_eq!( client_node .list_channels() @@ -2241,6 +2279,7 @@ async fn lsps2_client_trusts_lsp() { client_node .bolt11_payment() .claim_for_hash(manual_payment_hash, jit_amount_msat, manual_preimage) + .await .unwrap(); expect_payment_successful_event!(payer_node, Some(payment_id), None); @@ -2250,8 +2289,8 @@ async fn lsps2_client_trusts_lsp() { // Check the nodes pick up on the confirmed funding tx now. wait_for_tx(&electrsd.client, funding_txo.txid).await; generate_blocks_and_wait(&bitcoind.client, &electrsd.client, 6).await; - service_node.sync_wallets().unwrap(); - client_node.sync_wallets().unwrap(); + service_node.sync_wallets().await.unwrap(); + client_node.sync_wallets().await.unwrap(); assert_eq!( client_node .list_channels() @@ -2336,16 +2375,16 @@ async fn lsps2_lsp_trusts_client_but_client_does_not_claim() { Amount::from_sat(premine_amount_sat), ) .await; - service_node.sync_wallets().unwrap(); - client_node.sync_wallets().unwrap(); - payer_node.sync_wallets().unwrap(); + service_node.sync_wallets().await.unwrap(); + client_node.sync_wallets().await.unwrap(); + payer_node.sync_wallets().await.unwrap(); println!("Premine complete!"); // Open a channel payer -> service that will allow paying the JIT invoice open_channel(&payer_node, &service_node, 5_000_000, false, &electrsd).await; generate_blocks_and_wait(&bitcoind.client, &electrsd.client, 6).await; - service_node.sync_wallets().unwrap(); - payer_node.sync_wallets().unwrap(); + service_node.sync_wallets().await.unwrap(); + payer_node.sync_wallets().await.unwrap(); expect_channel_ready_event!(payer_node, service_node.node_id()); expect_channel_ready_event!(service_node, payer_node.node_id()); @@ -2365,11 +2404,12 @@ async fn lsps2_lsp_trusts_client_but_client_does_not_claim() { None, manual_payment_hash, ) + .await .unwrap(); // Have the payer_node pay the invoice, therby triggering channel open service_node -> client_node. println!("Paying JIT invoice!"); - let _payment_id = payer_node.bolt11_payment().send(&res, None).unwrap(); + let _payment_id = payer_node.bolt11_payment().send(&res, None).await.unwrap(); let funding_txo = expect_channel_pending_event!(service_node, client_node.node_id()); expect_channel_ready_event!(service_node, client_node.node_id()); expect_channel_pending_event!(client_node, service_node.node_id()); @@ -2379,8 +2419,8 @@ async fn lsps2_lsp_trusts_client_but_client_does_not_claim() { // Check the nodes pick up on the confirmed funding tx now. wait_for_tx(&electrsd.client, funding_txo.txid).await; generate_blocks_and_wait(&bitcoind.client, &electrsd.client, 6).await; - service_node.sync_wallets().unwrap(); - client_node.sync_wallets().unwrap(); + service_node.sync_wallets().await.unwrap(); + client_node.sync_wallets().await.unwrap(); assert_eq!( client_node .list_channels() @@ -2433,8 +2473,8 @@ async fn payment_persistence_after_restart() { Amount::from_sat(premine_amount_sat), ) .await; - node_a.sync_wallets().unwrap(); - node_b.sync_wallets().unwrap(); + node_a.sync_wallets().await.unwrap(); + node_b.sync_wallets().await.unwrap(); assert_eq!(node_a.list_balances().spendable_onchain_balance_sats, premine_amount_sat); assert_eq!(node_b.list_balances().spendable_onchain_balance_sats, premine_amount_sat); @@ -2442,8 +2482,8 @@ async fn payment_persistence_after_restart() { let channel_amount_sat = 5_000_000; open_channel(&node_a, &node_b, channel_amount_sat, true, &electrsd).await; generate_blocks_and_wait(&bitcoind.client, &electrsd.client, 6).await; - node_a.sync_wallets().unwrap(); - node_b.sync_wallets().unwrap(); + node_a.sync_wallets().await.unwrap(); + node_b.sync_wallets().await.unwrap(); expect_channel_ready_event!(node_a, node_b.node_id()); expect_channel_ready_event!(node_b, node_a.node_id()); @@ -2456,8 +2496,9 @@ async fn payment_persistence_after_restart() { let invoice = node_b .bolt11_payment() .receive(payment_amount_msat, &invoice_description.clone().into(), 3600) + .await .unwrap(); - let payment_id = node_a.bolt11_payment().send(&invoice, None).unwrap(); + let payment_id = node_a.bolt11_payment().send(&invoice, None).await.unwrap(); expect_event!(node_a, PaymentSuccessful); expect_event!(node_b, PaymentReceived); @@ -2466,15 +2507,17 @@ async fn payment_persistence_after_restart() { } // Verify payment succeeded - assert_eq!(node_a.payment(&payment_id).unwrap().status, PaymentStatus::Succeeded); + assert_eq!(node_a.payment(&payment_id).await.unwrap().status, PaymentStatus::Succeeded); } println!("All {} payments completed successfully", num_payments); // Verify node_a has 200 outbound Bolt11 payments before shutdown - let outbound_payments_before = node_a.list_payments_with_filter(|p| { - p.direction == PaymentDirection::Outbound - && matches!(p.kind, PaymentKind::Bolt11 { .. }) - }); + let outbound_payments_before = node_a + .list_payments_with_filter(|p| { + p.direction == PaymentDirection::Outbound + && matches!(p.kind, PaymentKind::Bolt11 { .. }) + }) + .await; assert_eq!(outbound_payments_before.len(), num_payments); // Shut down both nodes @@ -2488,9 +2531,12 @@ async fn payment_persistence_after_restart() { let restarted_node_a = setup_node(&chain_source, config_a); // Assert all 200 payments are still in the store - let outbound_payments_after = restarted_node_a.list_payments_with_filter(|p| { - p.direction == PaymentDirection::Outbound && matches!(p.kind, PaymentKind::Bolt11 { .. }) - }); + let outbound_payments_after = restarted_node_a + .list_payments_with_filter(|p| { + p.direction == PaymentDirection::Outbound + && matches!(p.kind, PaymentKind::Bolt11 { .. }) + }) + .await; assert_eq!( outbound_payments_after.len(), num_payments, @@ -2632,7 +2678,7 @@ async fn do_persistence_backwards_compatibility(version: OldLdkVersion) { let node_new = builder_new.build(node_entropy.into()).unwrap(); node_new.start().unwrap(); - node_new.sync_wallets().unwrap(); + node_new.sync_wallets().await.unwrap(); let new_balance = node_new.list_balances().spendable_onchain_balance_sats; let new_node_id = node_new.node_id(); @@ -2681,7 +2727,7 @@ async fn fs_store_persistence_backwards_compatibility() { let node_new = builder_new.build_with_fs_store(node_entropy.into()).unwrap(); node_new.start().unwrap(); - node_new.sync_wallets().unwrap(); + node_new.sync_wallets().await.unwrap(); let new_balance = node_new.list_balances().spendable_onchain_balance_sats; let new_node_id = node_new.node_id(); @@ -2711,8 +2757,8 @@ async fn onchain_fee_bump_rbf() { ) .await; - node_a.sync_wallets().unwrap(); - node_b.sync_wallets().unwrap(); + node_a.sync_wallets().await.unwrap(); + node_b.sync_wallets().await.unwrap(); // Send a transaction from node_b to node_a that we'll later bump let amount_to_send_sats = 100_000; @@ -2723,11 +2769,11 @@ async fn onchain_fee_bump_rbf() { // Without this, Esplora may not yet have the tx, causing sync to miss it and // leaving the BDK wallet graph empty. tokio::time::sleep(std::time::Duration::from_secs(5)).await; - node_a.sync_wallets().unwrap(); - node_b.sync_wallets().unwrap(); + node_a.sync_wallets().await.unwrap(); + node_b.sync_wallets().await.unwrap(); let payment_id = PaymentId(txid.to_byte_array()); - let original_payment = node_b.payment(&payment_id).unwrap(); + let original_payment = node_b.payment(&payment_id).await.unwrap(); let original_fee = original_payment.fee_paid_msat.unwrap(); // Non-existent payment id @@ -2736,27 +2782,27 @@ async fn onchain_fee_bump_rbf() { let invalid_payment_id = PaymentId(fake_txid.to_byte_array()); assert_eq!( Err(NodeError::InvalidPaymentId), - node_b.onchain_payment().bump_fee_rbf(invalid_payment_id, None) + node_b.onchain_payment().bump_fee_rbf(invalid_payment_id, None).await ); // Bump an inbound payment assert_eq!( Err(NodeError::InvalidPaymentId), - node_a.onchain_payment().bump_fee_rbf(payment_id, None) + node_a.onchain_payment().bump_fee_rbf(payment_id, None).await ); // Successful fee bump - let new_txid = node_b.onchain_payment().bump_fee_rbf(payment_id, None).unwrap(); + let new_txid = node_b.onchain_payment().bump_fee_rbf(payment_id, None).await.unwrap(); wait_for_tx(&electrsd.client, new_txid).await; // Give the chain source time to index the unconfirmed transaction before syncing. // Without this, Esplora may not yet have the tx, causing sync to miss it and // leaving the BDK wallet graph empty. tokio::time::sleep(std::time::Duration::from_secs(5)).await; - node_a.sync_wallets().unwrap(); - node_b.sync_wallets().unwrap(); + node_a.sync_wallets().await.unwrap(); + node_b.sync_wallets().await.unwrap(); // Verify fee increased and txid updated for node_b - let new_payment = node_b.payment(&payment_id).unwrap(); + let new_payment = node_b.payment(&payment_id).await.unwrap(); assert!( new_payment.fee_paid_msat > Some(original_fee), "Fee should increase after RBF bump. Original: {}, New: {}", @@ -2774,13 +2820,13 @@ async fn onchain_fee_bump_rbf() { } // Multiple consecutive bumps - let second_bump_txid = node_b.onchain_payment().bump_fee_rbf(payment_id, None).unwrap(); + let second_bump_txid = node_b.onchain_payment().bump_fee_rbf(payment_id, None).await.unwrap(); wait_for_tx(&electrsd.client, second_bump_txid).await; - node_a.sync_wallets().unwrap(); - node_b.sync_wallets().unwrap(); + node_a.sync_wallets().await.unwrap(); + node_b.sync_wallets().await.unwrap(); // Verify second bump payment exists and txid updated for node_b - let second_payment = node_b.payment(&payment_id).unwrap(); + let second_payment = node_b.payment(&payment_id).await.unwrap(); assert!( second_payment.fee_paid_msat > new_payment.fee_paid_msat, "Second bump should have higher fee than first bump" @@ -2797,16 +2843,16 @@ async fn onchain_fee_bump_rbf() { // Confirm the transaction and try to bump again (should fail) generate_blocks_and_wait(&bitcoind.client, &electrsd.client, 6).await; - node_a.sync_wallets().unwrap(); - node_b.sync_wallets().unwrap(); + node_a.sync_wallets().await.unwrap(); + node_b.sync_wallets().await.unwrap(); assert_eq!( Err(NodeError::InvalidPaymentId), - node_b.onchain_payment().bump_fee_rbf(payment_id, None) + node_b.onchain_payment().bump_fee_rbf(payment_id, None).await ); // Verify final payment is confirmed - let final_payment = node_b.payment(&payment_id).unwrap(); + let final_payment = node_b.payment(&payment_id).await.unwrap(); assert_eq!(final_payment.status, PaymentStatus::Succeeded); match final_payment.kind { PaymentKind::Onchain { status, .. } => { @@ -2816,9 +2862,11 @@ async fn onchain_fee_bump_rbf() { } // Verify node A received the funds correctly - let node_a_received_payment = node_a.list_payments_with_filter(|p| { - p.id == payment_id && matches!(p.kind, PaymentKind::Onchain { .. }) - }); + let node_a_received_payment = node_a + .list_payments_with_filter(|p| { + p.id == payment_id && matches!(p.kind, PaymentKind::Onchain { .. }) + }) + .await; assert_eq!(node_a_received_payment.len(), 1); match &node_a_received_payment[0].kind { @@ -2852,16 +2900,16 @@ async fn open_channel_with_all_with_anchors() { Amount::from_sat(premine_amount_sat), ) .await; - node_a.sync_wallets().unwrap(); - node_b.sync_wallets().unwrap(); + node_a.sync_wallets().await.unwrap(); + node_b.sync_wallets().await.unwrap(); assert_eq!(node_a.list_balances().spendable_onchain_balance_sats, premine_amount_sat); let funding_txo = open_channel_with_all(&node_a, &node_b, false, &electrsd).await; generate_blocks_and_wait(&bitcoind.client, &electrsd.client, 6).await; - node_a.sync_wallets().unwrap(); - node_b.sync_wallets().unwrap(); + node_a.sync_wallets().await.unwrap(); + node_b.sync_wallets().await.unwrap(); let _user_channel_id_a = expect_channel_ready_event!(node_a, node_b.node_id()); let _user_channel_id_b = expect_channel_ready_event!(node_b, node_a.node_id()); @@ -2905,16 +2953,16 @@ async fn open_channel_with_all_without_anchors() { Amount::from_sat(premine_amount_sat), ) .await; - node_a.sync_wallets().unwrap(); - node_b.sync_wallets().unwrap(); + node_a.sync_wallets().await.unwrap(); + node_b.sync_wallets().await.unwrap(); assert_eq!(node_a.list_balances().spendable_onchain_balance_sats, premine_amount_sat); let funding_txo = open_channel_with_all(&node_a, &node_b, false, &electrsd).await; generate_blocks_and_wait(&bitcoind.client, &electrsd.client, 6).await; - node_a.sync_wallets().unwrap(); - node_b.sync_wallets().unwrap(); + node_a.sync_wallets().await.unwrap(); + node_b.sync_wallets().await.unwrap(); let _user_channel_id_a = expect_channel_ready_event!(node_a, node_b.node_id()); let _user_channel_id_b = expect_channel_ready_event!(node_b, node_a.node_id()); @@ -2957,8 +3005,8 @@ async fn splice_in_with_all_balance() { Amount::from_sat(premine_amount_sat), ) .await; - node_a.sync_wallets().unwrap(); - node_b.sync_wallets().unwrap(); + node_a.sync_wallets().await.unwrap(); + node_b.sync_wallets().await.unwrap(); assert_eq!(node_a.list_balances().spendable_onchain_balance_sats, premine_amount_sat); // Open a channel with a fixed amount first @@ -2966,8 +3014,8 @@ async fn splice_in_with_all_balance() { generate_blocks_and_wait(&bitcoind.client, &electrsd.client, 6).await; - node_a.sync_wallets().unwrap(); - node_b.sync_wallets().unwrap(); + node_a.sync_wallets().await.unwrap(); + node_b.sync_wallets().await.unwrap(); let user_channel_id_a = expect_channel_ready_event!(node_a, node_b.node_id()); let _user_channel_id_b = expect_channel_ready_event!(node_b, node_a.node_id()); @@ -2985,8 +3033,8 @@ async fn splice_in_with_all_balance() { generate_blocks_and_wait(&bitcoind.client, &electrsd.client, 6).await; - node_a.sync_wallets().unwrap(); - node_b.sync_wallets().unwrap(); + node_a.sync_wallets().await.unwrap(); + node_b.sync_wallets().await.unwrap(); let _user_channel_id_a2 = expect_channel_ready_event!(node_a, node_b.node_id()); let _user_channel_id_b2 = expect_channel_ready_event!(node_b, node_a.node_id()); diff --git a/tests/integration_tests_vss.rs b/tests/integration_tests_vss.rs index 210e9a8b25..2ee5ae1ca0 100644 --- a/tests/integration_tests_vss.rs +++ b/tests/integration_tests_vss.rs @@ -96,7 +96,7 @@ async fn vss_node_restart() { bitcoin::Amount::from_sat(100_000), ) .await; - node.sync_wallets().unwrap(); + node.sync_wallets().await.unwrap(); let balance = node.list_balances().spendable_onchain_balance_sats; assert!(balance > 0); @@ -117,7 +117,7 @@ async fn vss_node_restart() { .unwrap(); node.start().unwrap(); - node.sync_wallets().unwrap(); + node.sync_wallets().await.unwrap(); assert_eq!(expected_node_id, node.node_id()); assert_eq!(expected_balance_sats, node.list_balances().spendable_onchain_balance_sats); diff --git a/tests/integration_tests_vss_no_auth.rs b/tests/integration_tests_vss_no_auth.rs index e4ad9cacba..f1bd3a24d2 100644 --- a/tests/integration_tests_vss_no_auth.rs +++ b/tests/integration_tests_vss_no_auth.rs @@ -53,7 +53,7 @@ async fn vss_v0_schema_backwards_compatibility() { bitcoin::Amount::from_sat(100_000), ) .await; - node_old.sync_wallets().unwrap(); + node_old.sync_wallets().await.unwrap(); let balance = node_old.list_balances().spendable_onchain_balance_sats; assert!(balance > 0); @@ -84,7 +84,7 @@ async fn vss_v0_schema_backwards_compatibility() { .unwrap(); node_new.start().unwrap(); - node_new.sync_wallets().unwrap(); + node_new.sync_wallets().await.unwrap(); let new_balance = node_new.list_balances().spendable_onchain_balance_sats; let new_node_id = node_new.node_id(); diff --git a/tests/reorg_test.rs b/tests/reorg_test.rs index 295d9fdd24..e177808a88 100644 --- a/tests/reorg_test.rs +++ b/tests/reorg_test.rs @@ -58,7 +58,7 @@ proptest! { macro_rules! sync_wallets { () => { for node in &nodes { - node.sync_wallets().unwrap(); + node.sync_wallets().await.unwrap(); } }; } @@ -104,6 +104,7 @@ proptest! { node .list_payments_with_filter(|p| p.direction == PaymentDirection::Outbound && matches!(p.kind, PaymentKind::Onchain { .. })) + .await .len(), 1 ); @@ -126,9 +127,9 @@ proptest! { let funding = nodes_funding_tx.get(&node.node_id()).expect("Funding tx not exist"); if force_close { - node.force_close_channel(&user_channel_id, next_node.node_id(), None).unwrap(); + node.force_close_channel(&user_channel_id, next_node.node_id(), None).await.unwrap(); } else { - node.close_channel(&user_channel_id, next_node.node_id()).unwrap(); + node.close_channel(&user_channel_id, next_node.node_id()).await.unwrap(); } expect_event!(node, ChannelClosed); @@ -145,7 +146,7 @@ proptest! { if force_close { for node in &nodes { - node.sync_wallets().unwrap(); + node.sync_wallets().await.unwrap(); // If there is no more balance, there is nothing to process here. if node.list_balances().lightning_balances.len() < 1 { return; @@ -155,10 +156,10 @@ proptest! { confirmation_height, .. } => { - let cur_height = node.status().current_best_block.height; + let cur_height = node.status().await.current_best_block.height; let blocks_to_go = confirmation_height - cur_height; generate_blocks_and_wait(bitcoind, electrs, blocks_to_go as usize).await; - node.sync_wallets().unwrap(); + node.sync_wallets().await.unwrap(); }, _ => panic!("Unexpected balance state for node_hub!"), } @@ -171,7 +172,7 @@ proptest! { } generate_blocks_and_wait(&bitcoind, electrs, 1).await; - node.sync_wallets().unwrap(); + node.sync_wallets().await.unwrap(); assert!(node.list_balances().lightning_balances.len() < 2); assert!(node.list_balances().pending_balances_from_channel_closures.len() > 0); match node.list_balances().pending_balances_from_channel_closures[0] { From 16fd81d9441aefd983f9ae99e91ef4214a53e5fa Mon Sep 17 00:00:00 2001 From: Elias Rohrer Date: Mon, 8 Jun 2026 17:07:41 +0200 Subject: [PATCH 2/5] Stop retaining a VSS runtime Resolve VSS schema setup through async initialization on the caller runtime so VSS stores no longer create or retain a Tokio runtime. Co-Authored-By: HAL 9000 --- src/io/vss_store.rs | 223 ++++++++++++++++++++------------------------ 1 file changed, 101 insertions(+), 122 deletions(-) diff --git a/src/io/vss_store.rs b/src/io/vss_store.rs index 6c3535627a..5541e6c257 100644 --- a/src/io/vss_store.rs +++ b/src/io/vss_store.rs @@ -45,7 +45,6 @@ use vss_client::util::storable_builder::{EntropySource, StorableBuilder}; use crate::entropy::NodeEntropy; use crate::io::utils::check_namespace_key_validity; use crate::lnurl_auth::LNURL_AUTH_HARDENED_CHILD_INDEX; -use crate::runtime::StoreRuntime; type CustomRetryPolicy = FilteredRetryPolicy< JitteredRetryPolicy< @@ -54,7 +53,7 @@ type CustomRetryPolicy = FilteredRetryPolicy< Box bool + 'static + Send + Sync>, >; -#[derive(Debug, PartialEq)] +#[derive(Clone, Copy, Debug, PartialEq)] enum VssSchemaVersion { // The initial schema version. // This used an empty `aad` and unobfuscated `primary_namespace`/`secondary_namespace`s in the @@ -74,10 +73,6 @@ const VSS_HARDENED_CHILD_INDEX: u32 = 877; const VSS_SIGS_AUTH_HARDENED_CHILD_INDEX: u32 = 139; const VSS_SCHEMA_VERSION_KEY: &str = "vss_schema_version"; -// We set this to a small number of threads that would still allow to make some progress if one -// would hit a blocking case -const INTERNAL_RUNTIME_WORKERS: usize = 2; - /// A [`KVStore`] implementation that writes to and reads from a [VSS] backend. /// /// [VSS]: https://github.com/lightningdevkit/vss-server/blob/main/README.md @@ -86,8 +81,6 @@ pub struct VssStore { // Version counter to ensure that writes are applied in the correct order. It is assumed that read and list // operations aren't sensitive to the order of execution. next_version: AtomicU64, - // A VSS-internal runtime that drives VSS I/O independently from the node runtime. - internal_runtime: Option>, } impl VssStore { @@ -96,49 +89,20 @@ impl VssStore { header_provider: Arc, ) -> io::Result { let next_version = AtomicU64::new(1); - let internal_runtime = - Arc::new(StoreRuntime::new("ldk-node-vss-runtime", INTERNAL_RUNTIME_WORKERS, "VSS")?); let (data_encryption_key, obfuscation_master_key) = derive_data_encryption_and_obfuscation_keys(&vss_seed); let key_obfuscator = KeyObfuscator::new(obfuscation_master_key); - let setup_key_obfuscator = KeyObfuscator::new(obfuscation_master_key); let mut entropy_seed = [0u8; 32]; getrandom::fill(&mut entropy_seed).expect("Failed to generate random bytes"); let entropy_source = RandomBytes::new(entropy_seed); - let setup_entropy_source = RandomBytes::new(entropy_seed); - - let setup_retry_policy = retry_policy(); - let setup_client = VssClient::new_with_headers( - base_url.clone(), - setup_retry_policy, - Arc::clone(&header_provider), - ); let async_retry_policy = retry_policy(); let async_client = VssClient::new_with_headers(base_url, async_retry_policy, header_provider); - let setup_store_id = store_id.clone(); - let runtime_handle = internal_runtime.handle().clone(); - let schema_version = std::thread::spawn(move || { - runtime_handle.block_on(async { - determine_and_write_schema_version( - &setup_client, - &setup_store_id, - data_encryption_key, - &setup_key_obfuscator, - &setup_entropy_source, - ) - .await - }) - }) - .join() - .map_err(|_| io::Error::new(io::ErrorKind::Other, "VSS schema setup task panicked"))??; - let inner = Arc::new(VssStoreInner::new( - schema_version, async_client, store_id, data_encryption_key, @@ -146,12 +110,13 @@ impl VssStore { entropy_source, )); - Ok(Self { inner, next_version, internal_runtime: Some(internal_runtime) }) + Ok(Self { inner, next_version }) } - fn internal_runtime(&self) -> Arc { - Arc::clone(self.internal_runtime.as_ref().expect("VSS runtime must be available")) + pub(crate) async fn setup_schema_version(&self) -> io::Result<()> { + self.inner.schema_version().await.map(|_| ()) } + /// Returns a [`VssStoreBuilder`] allowing to build a [`VssStore`]. pub fn builder( node_entropy: NodeEntropy, vss_url: String, store_id: String, network: Network, @@ -194,16 +159,10 @@ impl KVStore for VssStore { let secondary_namespace = secondary_namespace.to_string(); let key = key.to_string(); let inner = Arc::clone(&self.inner); - let runtime = self.internal_runtime(); async move { - let task = runtime.spawn(async move { - inner - .read_internal(&inner.async_client, primary_namespace, secondary_namespace, key) - .await - }); - task.await.map_err(|e| { - io::Error::new(io::ErrorKind::Other, format!("VSS runtime task failed: {}", e)) - })? + inner + .read_internal(&inner.async_client, primary_namespace, secondary_namespace, key) + .await } } fn write( @@ -215,25 +174,19 @@ impl KVStore for VssStore { let secondary_namespace = secondary_namespace.to_string(); let key = key.to_string(); let inner = Arc::clone(&self.inner); - let runtime = self.internal_runtime(); async move { - let task = runtime.spawn(async move { - inner - .write_internal( - &inner.async_client, - inner_lock_ref, - locking_key, - version, - primary_namespace, - secondary_namespace, - key, - buf, - ) - .await - }); - task.await.map_err(|e| { - io::Error::new(io::ErrorKind::Other, format!("VSS runtime task failed: {}", e)) - })? + inner + .write_internal( + &inner.async_client, + inner_lock_ref, + locking_key, + version, + primary_namespace, + secondary_namespace, + key, + buf, + ) + .await } } fn remove( @@ -245,7 +198,6 @@ impl KVStore for VssStore { let secondary_namespace = secondary_namespace.to_string(); let key = key.to_string(); let inner = Arc::clone(&self.inner); - let runtime = self.internal_runtime(); let fut = async move { inner .remove_internal( @@ -261,15 +213,10 @@ impl KVStore for VssStore { }; async move { if lazy { - runtime.spawn(async move { - let _ = fut.await; - }); + tokio::task::spawn(async move { fut.await }); Ok(()) } else { - let task = runtime.spawn(fut); - task.await.map_err(|e| { - io::Error::new(io::ErrorKind::Other, format!("VSS runtime task failed: {}", e)) - })? + fut.await } } } @@ -279,34 +226,15 @@ impl KVStore for VssStore { let primary_namespace = primary_namespace.to_string(); let secondary_namespace = secondary_namespace.to_string(); let inner = Arc::clone(&self.inner); - let runtime = self.internal_runtime(); async move { - let task = runtime.spawn(async move { - inner - .list_internal(&inner.async_client, primary_namespace, secondary_namespace) - .await - }); - task.await.map_err(|e| { - io::Error::new(io::ErrorKind::Other, format!("VSS runtime task failed: {}", e)) - })? - } - } -} - -impl Drop for VssStore { - fn drop(&mut self) { - if let Some(runtime) = self.internal_runtime.take() { - if let Ok(runtime) = Arc::try_unwrap(runtime) { - runtime.shutdown_background(); - } + inner.list_internal(&inner.async_client, primary_namespace, secondary_namespace).await } } } struct VssStoreInner { - schema_version: VssSchemaVersion, - // A secondary client that will only be used for async persistence via `KVStore`, to ensure TCP - // connections aren't shared between our outer and the internal runtime. + schema_version: tokio::sync::OnceCell, + // Client used for async persistence via `KVStore`. async_client: VssClient, store_id: String, data_encryption_key: [u8; 32], @@ -319,13 +247,12 @@ struct VssStoreInner { impl VssStoreInner { pub(crate) fn new( - schema_version: VssSchemaVersion, async_client: VssClient, - store_id: String, data_encryption_key: [u8; 32], key_obfuscator: KeyObfuscator, - entropy_source: RandomBytes, + async_client: VssClient, store_id: String, + data_encryption_key: [u8; 32], key_obfuscator: KeyObfuscator, entropy_source: RandomBytes, ) -> Self { let locks = Mutex::new(HashMap::new()); Self { - schema_version, + schema_version: tokio::sync::OnceCell::new(), async_client, store_id, data_encryption_key, @@ -340,12 +267,33 @@ impl VssStoreInner { Arc::clone(&outer_lock.entry(locking_key).or_default()) } + async fn schema_version(&self) -> io::Result { + let schema_version = self + .schema_version + .get_or_try_init(|| async { + determine_and_write_schema_version( + &self.async_client, + &self.store_id, + self.data_encryption_key, + &self.key_obfuscator, + &self.entropy_source, + ) + .await + }) + .await?; + Ok(*schema_version) + } + fn build_obfuscated_key( - &self, primary_namespace: &str, secondary_namespace: &str, key: &str, + &self, schema_version: VssSchemaVersion, primary_namespace: &str, + secondary_namespace: &str, key: &str, ) -> String { - if self.schema_version == VssSchemaVersion::V1 { - let obfuscated_prefix = - self.build_obfuscated_prefix(primary_namespace, secondary_namespace); + if schema_version == VssSchemaVersion::V1 { + let obfuscated_prefix = self.build_obfuscated_prefix( + schema_version, + primary_namespace, + secondary_namespace, + ); let obfuscated_key = self.key_obfuscator.obfuscate(key); format!("{}#{}", obfuscated_prefix, obfuscated_key) } else { @@ -360,9 +308,9 @@ impl VssStoreInner { } fn build_obfuscated_prefix( - &self, primary_namespace: &str, secondary_namespace: &str, + &self, schema_version: VssSchemaVersion, primary_namespace: &str, secondary_namespace: &str, ) -> String { - if self.schema_version == VssSchemaVersion::V1 { + if schema_version == VssSchemaVersion::V1 { let prefix = format!("{}#{}", primary_namespace, secondary_namespace); self.key_obfuscator.obfuscate(&prefix) } else { @@ -371,8 +319,10 @@ impl VssStoreInner { } } - fn extract_key(&self, unified_key: &str) -> io::Result { - let mut parts = if self.schema_version == VssSchemaVersion::V1 { + fn extract_key( + &self, schema_version: VssSchemaVersion, unified_key: &str, + ) -> io::Result { + let mut parts = if schema_version == VssSchemaVersion::V1 { let mut parts = unified_key.splitn(2, '#'); let _obfuscated_namespace = parts.next(); parts @@ -392,12 +342,13 @@ impl VssStoreInner { } async fn list_all_keys( - &self, client: &VssClient, primary_namespace: &str, - secondary_namespace: &str, + &self, client: &VssClient, schema_version: VssSchemaVersion, + primary_namespace: &str, secondary_namespace: &str, ) -> io::Result> { let mut page_token = None; let mut keys = vec![]; - let key_prefix = self.build_obfuscated_prefix(primary_namespace, secondary_namespace); + let key_prefix = + self.build_obfuscated_prefix(schema_version, primary_namespace, secondary_namespace); while page_token != Some("".to_string()) { let request = ListKeyVersionsRequest { store_id: self.store_id.clone(), @@ -415,7 +366,7 @@ impl VssStoreInner { })?; for kv in response.key_versions { - keys.push(self.extract_key(&kv.key)?); + keys.push(self.extract_key(schema_version, &kv.key)?); } page_token = response.next_page_token; } @@ -428,7 +379,13 @@ impl VssStoreInner { ) -> io::Result> { check_namespace_key_validity(&primary_namespace, &secondary_namespace, Some(&key), "read")?; - let store_key = self.build_obfuscated_key(&primary_namespace, &secondary_namespace, &key); + let schema_version = self.schema_version().await?; + let store_key = self.build_obfuscated_key( + schema_version, + &primary_namespace, + &secondary_namespace, + &key, + ); let request = GetObjectRequest { store_id: self.store_id.clone(), key: store_key.clone() }; let resp = client.get_object(&request).await.map_err(|e| { let msg = format!( @@ -454,8 +411,7 @@ impl VssStoreInner { })?; let storable_builder = StorableBuilder::new(VssEntropySource(&self.entropy_source)); - let aad = - if self.schema_version == VssSchemaVersion::V1 { store_key.as_bytes() } else { &[] }; + let aad = if schema_version == VssSchemaVersion::V1 { store_key.as_bytes() } else { &[] }; let decrypted = storable_builder.deconstruct(storable, &self.data_encryption_key, aad)?.0; Ok(decrypted) } @@ -472,11 +428,22 @@ impl VssStoreInner { "write", )?; - let store_key = self.build_obfuscated_key(&primary_namespace, &secondary_namespace, &key); + let schema_version = match self.schema_version().await { + Ok(schema_version) => schema_version, + Err(e) => { + self.clean_locks(&inner_lock_ref, locking_key); + return Err(e); + }, + }; + let store_key = self.build_obfuscated_key( + schema_version, + &primary_namespace, + &secondary_namespace, + &key, + ); let vss_version = -1; let storable_builder = StorableBuilder::new(VssEntropySource(&self.entropy_source)); - let aad = - if self.schema_version == VssSchemaVersion::V1 { store_key.as_bytes() } else { &[] }; + let aad = if schema_version == VssSchemaVersion::V1 { store_key.as_bytes() } else { &[] }; let storable = storable_builder.build(buf.to_vec(), vss_version, &self.data_encryption_key, aad); let request = PutObjectRequest { @@ -516,8 +483,19 @@ impl VssStoreInner { "remove", )?; - let obfuscated_key = - self.build_obfuscated_key(&primary_namespace, &secondary_namespace, &key); + let schema_version = match self.schema_version().await { + Ok(schema_version) => schema_version, + Err(e) => { + self.clean_locks(&inner_lock_ref, locking_key); + return Err(e); + }, + }; + let obfuscated_key = self.build_obfuscated_key( + schema_version, + &primary_namespace, + &secondary_namespace, + &key, + ); let key_value = KeyValue { key: obfuscated_key, version: -1, value: vec![] }; self.execute_locked_write(inner_lock_ref, locking_key, version, async move || { @@ -543,8 +521,9 @@ impl VssStoreInner { ) -> io::Result> { check_namespace_key_validity(&primary_namespace, &secondary_namespace, None, "list")?; + let schema_version = self.schema_version().await?; let keys = self - .list_all_keys(client, &primary_namespace, &secondary_namespace) + .list_all_keys(client, schema_version, &primary_namespace, &secondary_namespace) .await .map_err(|e| { let msg = format!( From 20f3f28299d9c14cdbda0de624459aad5b6d5c72 Mon Sep 17 00:00:00 2001 From: Elias Rohrer Date: Mon, 8 Jun 2026 17:08:18 +0200 Subject: [PATCH 3/5] Stop retaining a PostgreSQL runtime Construct PostgreSQL storage on the caller runtime now that store-backed APIs are async, removing the store-owned runtime and shutdown path. Co-Authored-By: HAL 9000 --- src/io/postgres_store/mod.rs | 135 +++++++---------------------------- 1 file changed, 27 insertions(+), 108 deletions(-) diff --git a/src/io/postgres_store/mod.rs b/src/io/postgres_store/mod.rs index c0770de5f0..694ba59fb3 100644 --- a/src/io/postgres_store/mod.rs +++ b/src/io/postgres_store/mod.rs @@ -22,7 +22,6 @@ use tokio_postgres::{Config, Error as PgError}; use self::pool::{make_config_connection, ClientConnection, PgTlsConnector, SmallPool}; use crate::io::utils::check_namespace_key_validity; use crate::logger::{log_debug, log_info, LdkLogger, Logger}; -use crate::runtime::StoreRuntime; mod migrations; mod pool; @@ -39,9 +38,6 @@ const SCHEMA_VERSION: u16 = 1; // The number of entries returned per page in paginated list operations. const PAGE_SIZE: usize = 50; -// Keep this small while still allowing progress if one runtime worker blocks on sync store access. -const INTERNAL_RUNTIME_WORKERS: usize = 2; - fn sql_identifier(identifier: &str) -> io::Result { if identifier.is_empty() || identifier.contains('\0') { return Err(io::Error::new( @@ -92,8 +88,6 @@ macro_rules! query_with_retry { /// A [`KVStore`] implementation that writes to and reads from a [PostgreSQL] database. /// -/// Maintains an internal runtime for the underlying tokio-postgres connection drivers. -/// /// [PostgreSQL]: https://www.postgresql.org pub struct PostgresStore { inner: Arc, @@ -101,9 +95,6 @@ pub struct PostgresStore { // Version counter to ensure that writes are applied in the correct order. It is assumed that read and list // operations aren't sensitive to the order of execution. next_write_version: AtomicU64, - - // A store-internal runtime that drives PostgreSQL I/O independently from the node runtime. - internal_runtime: Option>, } // tokio::sync::Mutex (used for the DB client) contains UnsafeCell which opts out of @@ -146,21 +137,12 @@ impl PostgresStore { connection_string: String, db_name: Option, kv_table_name: Option, certificate_pem: Option, logger: Option>, ) -> io::Result { - let internal_runtime = Arc::new(StoreRuntime::new( - "ldk-node-postgres-runtime", - INTERNAL_RUNTIME_WORKERS, - "PostgreSQL", - )?); let tls = Self::build_tls_connector(certificate_pem)?; - let task = internal_runtime.spawn(async move { - PostgresStoreInner::new(connection_string, db_name, kv_table_name, tls, logger).await - }); - let inner = task.await.map_err(|e| { - io::Error::new(io::ErrorKind::Other, format!("PostgreSQL runtime task failed: {}", e)) - })??; + let inner = + PostgresStoreInner::new(connection_string, db_name, kv_table_name, tls, logger).await?; let inner = Arc::new(inner); let next_write_version = AtomicU64::new(1); - Ok(Self { inner, next_write_version, internal_runtime: Some(internal_runtime) }) + Ok(Self { inner, next_write_version }) } fn build_tls_connector(certificate_pem: Option) -> io::Result { @@ -203,20 +185,6 @@ impl PostgresStore { (inner_lock_ref, version) } - - fn internal_runtime(&self) -> Arc { - Arc::clone(self.internal_runtime.as_ref().expect("PostgreSQL runtime must be available")) - } -} - -impl Drop for PostgresStore { - fn drop(&mut self) { - if let Some(internal_runtime) = self.internal_runtime.take() { - if let Ok(internal_runtime) = Arc::try_unwrap(internal_runtime) { - internal_runtime.shutdown_background(); - } - } - } } impl KVStore for PostgresStore { @@ -227,18 +195,7 @@ impl KVStore for PostgresStore { let secondary_namespace = secondary_namespace.to_string(); let key = key.to_string(); let inner = Arc::clone(&self.inner); - let runtime = self.internal_runtime(); - async move { - let task = runtime.spawn(async move { - inner.read_internal(&primary_namespace, &secondary_namespace, &key).await - }); - task.await.map_err(|e| { - io::Error::new( - io::ErrorKind::Other, - format!("PostgreSQL runtime task failed: {}", e), - ) - })? - } + async move { inner.read_internal(&primary_namespace, &secondary_namespace, &key).await } } fn write( @@ -250,27 +207,18 @@ impl KVStore for PostgresStore { let secondary_namespace = secondary_namespace.to_string(); let key = key.to_string(); let inner = Arc::clone(&self.inner); - let runtime = self.internal_runtime(); async move { - let task = runtime.spawn(async move { - inner - .write_internal( - inner_lock_ref, - locking_key, - version, - &primary_namespace, - &secondary_namespace, - &key, - buf, - ) - .await - }); - task.await.map_err(|e| { - io::Error::new( - io::ErrorKind::Other, - format!("PostgreSQL runtime task failed: {}", e), + inner + .write_internal( + inner_lock_ref, + locking_key, + version, + &primary_namespace, + &secondary_namespace, + &key, + buf, ) - })? + .await } } @@ -283,26 +231,17 @@ impl KVStore for PostgresStore { let secondary_namespace = secondary_namespace.to_string(); let key = key.to_string(); let inner = Arc::clone(&self.inner); - let runtime = self.internal_runtime(); async move { - let task = runtime.spawn(async move { - inner - .remove_internal( - inner_lock_ref, - locking_key, - version, - &primary_namespace, - &secondary_namespace, - &key, - ) - .await - }); - task.await.map_err(|e| { - io::Error::new( - io::ErrorKind::Other, - format!("PostgreSQL runtime task failed: {}", e), + inner + .remove_internal( + inner_lock_ref, + locking_key, + version, + &primary_namespace, + &secondary_namespace, + &key, ) - })? + .await } } @@ -312,18 +251,7 @@ impl KVStore for PostgresStore { let primary_namespace = primary_namespace.to_string(); let secondary_namespace = secondary_namespace.to_string(); let inner = Arc::clone(&self.inner); - let runtime = self.internal_runtime(); - async move { - let task = runtime.spawn(async move { - inner.list_internal(&primary_namespace, &secondary_namespace).await - }); - task.await.map_err(|e| { - io::Error::new( - io::ErrorKind::Other, - format!("PostgreSQL runtime task failed: {}", e), - ) - })? - } + async move { inner.list_internal(&primary_namespace, &secondary_namespace).await } } } @@ -334,19 +262,10 @@ impl PaginatedKVStore for PostgresStore { let primary_namespace = primary_namespace.to_string(); let secondary_namespace = secondary_namespace.to_string(); let inner = Arc::clone(&self.inner); - let runtime = self.internal_runtime(); async move { - let task = runtime.spawn(async move { - inner - .list_paginated_internal(&primary_namespace, &secondary_namespace, page_token) - .await - }); - task.await.map_err(|e| { - io::Error::new( - io::ErrorKind::Other, - format!("PostgreSQL runtime task failed: {}", e), - ) - })? + inner + .list_paginated_internal(&primary_namespace, &secondary_namespace, page_token) + .await } } } From 2f72a98ddc3c89a76ab8eab6ef56c0a9cd3fb41a Mon Sep 17 00:00:00 2001 From: Elias Rohrer Date: Mon, 8 Jun 2026 17:45:57 +0200 Subject: [PATCH 4/5] Finish async node API migration Expose startup, shutdown, builder, wallet, liquidity, and bindings entrypoints as async so store-backed persistence no longer needs local synchronous future driving. Update tests and benches to await the async surface and keep store persistence helpers on the async storage path. Co-Authored-By: HAL 9000 --- benches/payments.rs | 71 ++-- bindings/ldk_node.udl | 23 +- src/builder.rs | 244 +++++++------- src/event.rs | 18 +- src/io/postgres_store/mod.rs | 2 +- src/io/sqlite_store/mod.rs | 2 +- src/io/test_utils.rs | 203 +++++++----- src/lib.rs | 170 +++++----- src/liquidity.rs | 51 ++- src/payment/onchain.rs | 12 +- src/payment/unified.rs | 5 +- src/runtime.rs | 188 +++-------- src/scoring.rs | 12 +- src/wallet/mod.rs | 429 +++++++++++++++---------- tests/common/mod.rs | 57 ++-- tests/integration_tests_hrn.rs | 4 +- tests/integration_tests_rust.rs | 332 +++++++++---------- tests/integration_tests_vss_no_auth.rs | 11 +- tests/reorg_test.rs | 40 ++- 19 files changed, 943 insertions(+), 931 deletions(-) diff --git a/benches/payments.rs b/benches/payments.rs index 52769d7949..ee886a4c62 100644 --- a/benches/payments.rs +++ b/benches/payments.rs @@ -35,14 +35,15 @@ fn spawn_payment(node_a: Arc, node_b: Arc, amount_msat: u64) { tokio::time::sleep(std::time::Duration::from_millis(100)).await; } - let payment_id = node_a.spontaneous_payment().send_with_preimage( + let spontaneous_payment = node_a.spontaneous_payment(); + let payment_id = spontaneous_payment.send_with_preimage( amount_msat, node_b.node_id(), preimage, None, ); - match payment_id { + match payment_id.await { Ok(payment_id) => { println!( "{}: Awaiting payment with id {}", @@ -93,7 +94,7 @@ async fn send_payments(node_a: Arc, node_b: Arc) -> std::time::Durat }, } - node_a.event_handled().unwrap(); + node_a.event_handled().await.unwrap(); } let duration = start.elapsed(); @@ -110,6 +111,7 @@ async fn send_payments(node_a: Arc, node_b: Arc) -> std::time::Durat PaymentPreimage(preimage_bytes), None, ) + .await .ok() .unwrap(); @@ -117,30 +119,28 @@ async fn send_payments(node_a: Arc, node_b: Arc) -> std::time::Durat } fn payment_benchmark(c: &mut Criterion) { - // Set up two nodes. Because this is slow, we reuse the same nodes for each sample. - let (bitcoind, electrsd) = setup_bitcoind_and_electrsd(); - let chain_source = random_chain_source(&bitcoind, &electrsd); - - let (node_a, node_b) = setup_two_nodes_with_store( - &chain_source, - false, - true, - false, - common::TestStoreType::Sqlite, - ); - let runtime = tokio::runtime::Builder::new_multi_thread().worker_threads(4).enable_all().build().unwrap(); - let node_a = Arc::new(node_a); - let node_b = Arc::new(node_b); + // Set up two nodes. Because this is slow, we reuse the same nodes for each sample. + let (setup_done, setup_result) = std::sync::mpsc::channel(); + runtime.spawn(async move { + let (bitcoind, electrsd) = setup_bitcoind_and_electrsd(); + let chain_source = random_chain_source(&bitcoind, &electrsd); + let (node_a, node_b) = setup_two_nodes_with_store( + &chain_source, + false, + true, + false, + common::TestStoreType::Sqlite, + ) + .await; + + let node_a = Arc::new(node_a); + let node_b = Arc::new(node_b); - // Fund the nodes and setup a channel between them. The criterion function cannot be async, so we need to execute - // the setup using a runtime. - let node_a_cloned = Arc::clone(&node_a); - let node_b_cloned = Arc::clone(&node_b); - runtime.block_on(async move { - let address_a = node_a_cloned.onchain_payment().new_address().unwrap(); + // Fund the nodes and setup a channel between them. + let address_a = node_a.onchain_payment().new_address().await.unwrap(); let premine_sat = 25_000_000; premine_and_distribute_funds( &bitcoind.client, @@ -149,23 +149,18 @@ fn payment_benchmark(c: &mut Criterion) { Amount::from_sat(premine_sat), ) .await; - node_a_cloned.sync_wallets().unwrap(); - node_b_cloned.sync_wallets().unwrap(); - open_channel_push_amt( - &node_a_cloned, - &node_b_cloned, - 16_000_000, - Some(1_000_000_000), - false, - &electrsd, - ) - .await; + node_a.sync_wallets().await.unwrap(); + node_b.sync_wallets().await.unwrap(); + open_channel_push_amt(&node_a, &node_b, 16_000_000, Some(1_000_000_000), false, &electrsd) + .await; generate_blocks_and_wait(&bitcoind.client, &electrsd.client, 6).await; - node_a_cloned.sync_wallets().unwrap(); - node_b_cloned.sync_wallets().unwrap(); - expect_channel_ready_event!(node_a_cloned, node_b_cloned.node_id()); - expect_channel_ready_event!(node_b_cloned, node_a_cloned.node_id()); + node_a.sync_wallets().await.unwrap(); + node_b.sync_wallets().await.unwrap(); + expect_channel_ready_event!(node_a, node_b.node_id()); + expect_channel_ready_event!(node_b, node_a.node_id()); + setup_done.send((node_a, node_b)).unwrap(); }); + let (node_a, node_b) = setup_result.recv().unwrap(); let mut group = c.benchmark_group("payments"); group.sample_size(10); diff --git a/bindings/ldk_node.udl b/bindings/ldk_node.udl index 40119eb5cc..af189c3ec7 100644 --- a/bindings/ldk_node.udl +++ b/bindings/ldk_node.udl @@ -61,35 +61,36 @@ interface Builder { [Throws=BuildError] void set_async_payments_role(AsyncPaymentsRole? role); void set_wallet_recovery_mode(); - [Throws=BuildError] + [Async, Throws=BuildError] Node build(NodeEntropy node_entropy); - [Throws=BuildError] + [Async, Throws=BuildError] Node build_with_postgres_store(NodeEntropy node_entropy, string connection_string, string? db_name, string? kv_table_name, string? certificate_pem); - [Throws=BuildError] + [Async, Throws=BuildError] Node build_with_fs_store(NodeEntropy node_entropy); - [Throws=BuildError] + [Async, Throws=BuildError] Node build_with_vss_store(NodeEntropy node_entropy, string vss_url, string store_id, record fixed_headers); - [Throws=BuildError] + [Async, Throws=BuildError] Node build_with_vss_store_and_lnurl_auth(NodeEntropy node_entropy, string vss_url, string store_id, string lnurl_auth_server_url, record fixed_headers); - [Throws=BuildError] + [Async, Throws=BuildError] Node build_with_vss_store_and_fixed_headers(NodeEntropy node_entropy, string vss_url, string store_id, record fixed_headers); - [Throws=BuildError] + [Async, Throws=BuildError] Node build_with_vss_store_and_header_provider(NodeEntropy node_entropy, string vss_url, string store_id, VssHeaderProvider header_provider); }; interface Node { - [Throws=NodeError] + [Async, Throws=NodeError] void start(); - [Throws=NodeError] + [Async, Throws=NodeError] void stop(); [Async] NodeStatus status(); Config config(); Event? next_event(); + [Async] Event wait_next_event(); [Async] Event next_event_async(); - [Throws=NodeError] + [Async, Throws=NodeError] void event_handled(); PublicKey node_id(); sequence? listening_addresses(); @@ -101,7 +102,7 @@ interface Node { OnchainPayment onchain_payment(); UnifiedPayment unified_payment(); LSPS1Liquidity lsps1_liquidity(); - [Throws=NodeError] + [Async, Throws=NodeError] void lnurl_auth(string lnurl); [Async, Throws=NodeError] void connect(PublicKey node_id, SocketAddress address, boolean persist); diff --git a/src/builder.rs b/src/builder.rs index dc73915bd3..c190fcb108 100644 --- a/src/builder.rs +++ b/src/builder.rs @@ -282,7 +282,7 @@ impl std::error::Error for BuildError {} /// [`set_custom_logger`]: Self::set_custom_logger /// [`log`]: https://crates.io/crates/log /// [Logging]: #logging -#[derive(Debug)] +#[derive(Debug, Clone)] pub struct NodeBuilder { config: Config, chain_data_source_config: Option, @@ -628,7 +628,7 @@ impl NodeBuilder { /// Builds a [`Node`] instance with a [`SqliteStore`] backend and according to the options /// previously configured. - pub fn build(&self, node_entropy: NodeEntropy) -> Result { + pub async fn build(&self, node_entropy: NodeEntropy) -> Result { let logger = setup_logger(&self.log_writer_config, &self.config)?; let storage_dir_path = self.config.storage_dir_path.clone(); fs::create_dir_all(storage_dir_path.clone()) @@ -642,7 +642,7 @@ impl NodeBuilder { log_error!(logger, "Failed to setup Sqlite store: {}", e); BuildError::KVStoreSetupFailed })?; - self.build_with_store_and_logger(node_entropy, kv_store, logger) + self.build_with_store_and_logger(node_entropy, kv_store, logger).await } /// Builds a [`Node`] instance with a [PostgreSQL] backend and according to the options @@ -668,25 +668,25 @@ impl NodeBuilder { /// /// [PostgreSQL]: https://www.postgresql.org #[cfg(feature = "postgres")] - pub fn build_with_postgres_store( + pub async fn build_with_postgres_store( &self, node_entropy: NodeEntropy, connection_string: String, db_name: Option, kv_table_name: Option, certificate_pem: Option, ) -> Result { let logger = setup_logger(&self.log_writer_config, &self.config)?; let runtime = self.setup_runtime(&logger)?; - let kv_store = runtime - .block_on(io::postgres_store::PostgresStore::new_with_logger( - connection_string, - db_name, - kv_table_name, - certificate_pem, - Some(Arc::clone(&logger)), - )) - .map_err(|e| { - log_error!(logger, "Failed to set up Postgres store: {e}"); - BuildError::KVStoreSetupFailed - })?; - self.build_with_store_runtime_and_logger(node_entropy, kv_store, runtime, logger) + let kv_store = io::postgres_store::PostgresStore::new_with_logger( + connection_string, + db_name, + kv_table_name, + certificate_pem, + Some(Arc::clone(&logger)), + ) + .await + .map_err(|e| { + log_error!(logger, "Failed to set up Postgres store: {e}"); + BuildError::KVStoreSetupFailed + })?; + self.build_with_store_runtime_and_logger(node_entropy, kv_store, runtime, logger).await } /// Builds a [`Node`] instance with a [`FilesystemStoreV2`] backend and according to the options @@ -696,14 +696,14 @@ impl NodeBuilder { /// automatically migrated to the v2 format. /// /// [`FilesystemStoreV2`]: lightning_persister::fs_store::v2::FilesystemStoreV2 - pub fn build_with_fs_store(&self, node_entropy: NodeEntropy) -> Result { + pub async fn build_with_fs_store(&self, node_entropy: NodeEntropy) -> Result { let logger = setup_logger(&self.log_writer_config, &self.config)?; let runtime = self.setup_runtime(&logger)?; let mut storage_dir_path: PathBuf = self.config.storage_dir_path.clone().into(); storage_dir_path.push("fs_store"); - let kv_store = runtime.block_on(open_or_migrate_fs_store(storage_dir_path))?; - self.build_with_store_runtime_and_logger(node_entropy, kv_store, runtime, logger) + let kv_store = open_or_migrate_fs_store(storage_dir_path).await?; + self.build_with_store_runtime_and_logger(node_entropy, kv_store, runtime, logger).await } /// Builds a [`Node`] instance with a [VSS] backend and according to the options @@ -723,7 +723,7 @@ impl NodeBuilder { /// unrecoverable, i.e., if they remain unresolved after internal retries are exhausted. /// /// [VSS]: https://github.com/lightningdevkit/vss-server/blob/main/README.md - pub fn build_with_vss_store( + pub async fn build_with_vss_store( &self, node_entropy: NodeEntropy, vss_url: String, store_id: String, fixed_headers: HashMap, ) -> Result { @@ -734,12 +734,12 @@ impl NodeBuilder { log_error!(logger, "Failed to setup VSS store: {}", e); BuildError::KVStoreSetupFailed })?; - runtime.block_on(vss_store.setup_schema_version()).map_err(|e| { + vss_store.setup_schema_version().await.map_err(|e| { log_error!(logger, "Failed to setup VSS store: {}", e); BuildError::KVStoreSetupFailed })?; - self.build_with_store_runtime_and_logger(node_entropy, vss_store, runtime, logger) + self.build_with_store_runtime_and_logger(node_entropy, vss_store, runtime, logger).await } /// Builds a [`Node`] instance with a [VSS] backend and according to the options @@ -764,7 +764,7 @@ impl NodeBuilder { /// /// [VSS]: https://github.com/lightningdevkit/vss-server/blob/main/README.md /// [LNURL-auth]: https://github.com/lnurl/luds/blob/luds/04.md - pub fn build_with_vss_store_and_lnurl_auth( + pub async fn build_with_vss_store_and_lnurl_auth( &self, node_entropy: NodeEntropy, vss_url: String, store_id: String, lnurl_auth_server_url: String, fixed_headers: HashMap, ) -> Result { @@ -776,12 +776,12 @@ impl NodeBuilder { log_error!(logger, "Failed to setup VSS store: {}", e); BuildError::KVStoreSetupFailed })?; - runtime.block_on(vss_store.setup_schema_version()).map_err(|e| { + vss_store.setup_schema_version().await.map_err(|e| { log_error!(logger, "Failed to setup VSS store: {}", e); BuildError::KVStoreSetupFailed })?; - self.build_with_store_runtime_and_logger(node_entropy, vss_store, runtime, logger) + self.build_with_store_runtime_and_logger(node_entropy, vss_store, runtime, logger).await } /// Builds a [`Node`] instance with a [VSS] backend and according to the options @@ -797,7 +797,7 @@ impl NodeBuilder { /// /// [VSS]: https://github.com/lightningdevkit/vss-server/blob/main/README.md /// [`FixedHeaders`]: vss_client::headers::FixedHeaders - pub fn build_with_vss_store_and_fixed_headers( + pub async fn build_with_vss_store_and_fixed_headers( &self, node_entropy: NodeEntropy, vss_url: String, store_id: String, fixed_headers: HashMap, ) -> Result { @@ -808,12 +808,12 @@ impl NodeBuilder { log_error!(logger, "Failed to setup VSS store: {}", e); BuildError::KVStoreSetupFailed })?; - runtime.block_on(vss_store.setup_schema_version()).map_err(|e| { + vss_store.setup_schema_version().await.map_err(|e| { log_error!(logger, "Failed to setup VSS store: {}", e); BuildError::KVStoreSetupFailed })?; - self.build_with_store_runtime_and_logger(node_entropy, vss_store, runtime, logger) + self.build_with_store_runtime_and_logger(node_entropy, vss_store, runtime, logger).await } /// Builds a [`Node`] instance with a [VSS] backend and according to the options @@ -827,7 +827,7 @@ impl NodeBuilder { /// unrecoverable, i.e., if they remain unresolved after internal retries are exhausted. /// /// [VSS]: https://github.com/lightningdevkit/vss-server/blob/main/README.md - pub fn build_with_vss_store_and_header_provider( + pub async fn build_with_vss_store_and_header_provider( &self, node_entropy: NodeEntropy, vss_url: String, store_id: String, header_provider: Arc, ) -> Result { @@ -838,21 +838,21 @@ impl NodeBuilder { log_error!(logger, "Failed to setup VSS store: {}", e); BuildError::KVStoreSetupFailed })?; - runtime.block_on(vss_store.setup_schema_version()).map_err(|e| { + vss_store.setup_schema_version().await.map_err(|e| { log_error!(logger, "Failed to setup VSS store: {}", e); BuildError::KVStoreSetupFailed })?; - self.build_with_store_runtime_and_logger(node_entropy, vss_store, runtime, logger) + self.build_with_store_runtime_and_logger(node_entropy, vss_store, runtime, logger).await } /// Builds a [`Node`] instance according to the options previously configured. - pub fn build_with_store( + pub async fn build_with_store( &self, node_entropy: NodeEntropy, kv_store: S, ) -> Result { let logger = setup_logger(&self.log_writer_config, &self.config)?; - self.build_with_store_and_logger(node_entropy, kv_store, logger) + self.build_with_store_and_logger(node_entropy, kv_store, logger).await } fn setup_runtime(&self, logger: &Arc) -> Result, BuildError> { @@ -866,14 +866,14 @@ impl NodeBuilder { } } - fn build_with_store_and_logger( + async fn build_with_store_and_logger( &self, node_entropy: NodeEntropy, kv_store: S, logger: Arc, ) -> Result { let runtime = self.setup_runtime(&logger)?; - self.build_with_store_runtime_and_logger(node_entropy, kv_store, runtime, logger) + self.build_with_store_runtime_and_logger(node_entropy, kv_store, runtime, logger).await } - fn build_with_store_runtime_and_logger( + async fn build_with_store_runtime_and_logger( &self, node_entropy: NodeEntropy, kv_store: S, runtime: Arc, logger: Arc, ) -> Result { let seed_bytes = node_entropy.to_seed_bytes(); @@ -892,6 +892,7 @@ impl NodeBuilder { logger, Arc::new(DynStoreWrapper(kv_store)), ) + .await } } @@ -1187,8 +1188,9 @@ impl ArcedNodeBuilder { /// Builds a [`Node`] instance with a [`SqliteStore`] backend and according to the options /// previously configured. - pub fn build(&self, node_entropy: Arc) -> Result, BuildError> { - self.inner.read().expect("lock").build(*node_entropy).map(Arc::new) + pub async fn build(&self, node_entropy: Arc) -> Result, BuildError> { + let builder = self.inner.read().expect("lock").clone(); + builder.build(*node_entropy).await.map(Arc::new) } /// Builds a [`Node`] instance with a [PostgreSQL] backend and according to the options @@ -1214,13 +1216,12 @@ impl ArcedNodeBuilder { /// /// [PostgreSQL]: https://www.postgresql.org #[cfg(feature = "postgres")] - pub fn build_with_postgres_store( + pub async fn build_with_postgres_store( &self, node_entropy: Arc, connection_string: String, db_name: Option, kv_table_name: Option, certificate_pem: Option, ) -> Result, BuildError> { - self.inner - .read() - .unwrap() + let builder = self.inner.read().expect("lock").clone(); + builder .build_with_postgres_store( *node_entropy, connection_string, @@ -1228,6 +1229,7 @@ impl ArcedNodeBuilder { kv_table_name, certificate_pem, ) + .await .map(Arc::new) } @@ -1236,7 +1238,7 @@ impl ArcedNodeBuilder { /// /// This requires the `postgres` crate feature. #[cfg(not(feature = "postgres"))] - pub fn build_with_postgres_store( + pub async fn build_with_postgres_store( &self, _node_entropy: Arc, _connection_string: String, _db_name: Option, _kv_table_name: Option, _certificate_pem: Option, ) -> Result, BuildError> { @@ -1245,10 +1247,11 @@ impl ArcedNodeBuilder { /// Builds a [`Node`] instance with a [`FilesystemStoreV2`] backend and according to the options /// previously configured. - pub fn build_with_fs_store( + pub async fn build_with_fs_store( &self, node_entropy: Arc, ) -> Result, BuildError> { - self.inner.read().expect("lock").build_with_fs_store(*node_entropy).map(Arc::new) + let builder = self.inner.read().expect("lock").clone(); + builder.build_with_fs_store(*node_entropy).await.map(Arc::new) } /// Builds a [`Node`] instance with a [VSS] backend and according to the options @@ -1268,14 +1271,14 @@ impl ArcedNodeBuilder { /// unrecoverable, i.e., if they remain unresolved after internal retries are exhausted. /// /// [VSS]: https://github.com/lightningdevkit/vss-server/blob/main/README.md - pub fn build_with_vss_store( + pub async fn build_with_vss_store( &self, node_entropy: Arc, vss_url: String, store_id: String, fixed_headers: HashMap, ) -> Result, BuildError> { - self.inner - .read() - .expect("lock") + let builder = self.inner.read().expect("lock").clone(); + builder .build_with_vss_store(*node_entropy, vss_url, store_id, fixed_headers) + .await .map(Arc::new) } @@ -1301,13 +1304,12 @@ impl ArcedNodeBuilder { /// /// [VSS]: https://github.com/lightningdevkit/vss-server/blob/main/README.md /// [LNURL-auth]: https://github.com/lnurl/luds/blob/luds/04.md - pub fn build_with_vss_store_and_lnurl_auth( + pub async fn build_with_vss_store_and_lnurl_auth( &self, node_entropy: Arc, vss_url: String, store_id: String, lnurl_auth_server_url: String, fixed_headers: HashMap, ) -> Result, BuildError> { - self.inner - .read() - .expect("lock") + let builder = self.inner.read().expect("lock").clone(); + builder .build_with_vss_store_and_lnurl_auth( *node_entropy, vss_url, @@ -1315,6 +1317,7 @@ impl ArcedNodeBuilder { lnurl_auth_server_url, fixed_headers, ) + .await .map(Arc::new) } @@ -1330,14 +1333,14 @@ impl ArcedNodeBuilder { /// unrecoverable, i.e., if they remain unresolved after internal retries are exhausted. /// /// [VSS]: https://github.com/lightningdevkit/vss-server/blob/main/README.md - pub fn build_with_vss_store_and_fixed_headers( + pub async fn build_with_vss_store_and_fixed_headers( &self, node_entropy: Arc, vss_url: String, store_id: String, fixed_headers: HashMap, ) -> Result, BuildError> { - self.inner - .read() - .expect("lock") + let builder = self.inner.read().expect("lock").clone(); + builder .build_with_vss_store_and_fixed_headers(*node_entropy, vss_url, store_id, fixed_headers) + .await .map(Arc::new) } @@ -1352,30 +1355,31 @@ impl ArcedNodeBuilder { /// unrecoverable, i.e., if they remain unresolved after internal retries are exhausted. /// /// [VSS]: https://github.com/lightningdevkit/vss-server/blob/main/README.md - pub fn build_with_vss_store_and_header_provider( + pub async fn build_with_vss_store_and_header_provider( &self, node_entropy: Arc, vss_url: String, store_id: String, header_provider: Arc, ) -> Result, BuildError> { let adapter = Arc::new(crate::ffi::VssHeaderProviderAdapter::new(header_provider)); - self.inner - .read() - .expect("lock") + let builder = self.inner.read().expect("lock").clone(); + builder .build_with_vss_store_and_header_provider(*node_entropy, vss_url, store_id, adapter) + .await .map(Arc::new) } /// Builds a [`Node`] instance according to the options previously configured. // Note that the generics here don't actually work for Uniffi, but we don't currently expose // this so its not needed. - pub fn build_with_store( + pub async fn build_with_store( &self, node_entropy: Arc, kv_store: S, ) -> Result, BuildError> { - self.inner.read().expect("lock").build_with_store(*node_entropy, kv_store).map(Arc::new) + let builder = self.inner.read().expect("lock").clone(); + builder.build_with_store(*node_entropy, kv_store).await.map(Arc::new) } } /// Builds a [`Node`] instance according to the options previously configured. -fn build_with_store_internal( +async fn build_with_store_internal( config: Arc, chain_data_source_config: Option<&ChainDataSourceConfig>, gossip_source_config: Option<&GossipSourceConfig>, liquidity_source_config: Option<&LiquiditySourceConfig>, @@ -1416,24 +1420,21 @@ fn build_with_store_internal( let kv_store_ref = Arc::clone(&kv_store); let logger_ref = Arc::clone(&logger); - let (payment_store_res, node_metris_res, pending_payment_store_res) = - runtime.block_on(async move { - tokio::join!( - read_all_objects( - &*kv_store_ref, - PAYMENT_INFO_PERSISTENCE_PRIMARY_NAMESPACE, - PAYMENT_INFO_PERSISTENCE_SECONDARY_NAMESPACE, - Arc::clone(&logger_ref), - ), - read_node_metrics(&*kv_store_ref, Arc::clone(&logger_ref)), - read_all_objects( - &*kv_store_ref, - PENDING_PAYMENT_INFO_PERSISTENCE_PRIMARY_NAMESPACE, - PENDING_PAYMENT_INFO_PERSISTENCE_SECONDARY_NAMESPACE, - Arc::clone(&logger_ref), - ) - ) - }); + let (payment_store_res, node_metris_res, pending_payment_store_res) = tokio::join!( + read_all_objects( + &*kv_store_ref, + PAYMENT_INFO_PERSISTENCE_PRIMARY_NAMESPACE, + PAYMENT_INFO_PERSISTENCE_SECONDARY_NAMESPACE, + Arc::clone(&logger_ref), + ), + read_node_metrics(&*kv_store_ref, Arc::clone(&logger_ref)), + read_all_objects( + &*kv_store_ref, + PENDING_PAYMENT_INFO_PERSISTENCE_PRIMARY_NAMESPACE, + PENDING_PAYMENT_INFO_PERSISTENCE_SECONDARY_NAMESPACE, + Arc::clone(&logger_ref), + ) + ); // Initialize the status fields. let node_metrics = match node_metris_res { @@ -1498,7 +1499,7 @@ fn build_with_store_internal( rpc_password, rest_client_config, }) => match rest_client_config { - Some(rest_client_config) => runtime.block_on(async { + Some(rest_client_config) => { ChainSource::new_bitcoind_rest( rpc_host.clone(), *rpc_port, @@ -1513,8 +1514,8 @@ fn build_with_store_internal( Arc::clone(&node_metrics), ) .await - }), - None => runtime.block_on(async { + }, + None => { ChainSource::new_bitcoind_rpc( rpc_host.clone(), *rpc_port, @@ -1528,7 +1529,7 @@ fn build_with_store_internal( Arc::clone(&node_metrics), ) .await - }), + }, }, None => { @@ -1561,16 +1562,13 @@ fn build_with_store_internal( let change_descriptor = Bip84(xprv, KeychainKind::Internal); let mut wallet_persister = KVStoreWalletPersister::new(Arc::clone(&kv_store), Arc::clone(&logger)); - let wallet_opt = runtime - .block_on(async { - BdkWallet::load() - .descriptor(KeychainKind::External, Some(descriptor.clone())) - .descriptor(KeychainKind::Internal, Some(change_descriptor.clone())) - .extract_keys() - .check_network(config.network) - .load_wallet_async(&mut wallet_persister) - .await - }) + let wallet_opt = BdkWallet::load() + .descriptor(KeychainKind::External, Some(descriptor.clone())) + .descriptor(KeychainKind::Internal, Some(change_descriptor.clone())) + .extract_keys() + .check_network(config.network) + .load_wallet_async(&mut wallet_persister) + .await .map_err(|e| match e { bdk_wallet::LoadWithPersistError::InvalidChangeSet( bdk_wallet::LoadError::Mismatch(bdk_wallet::LoadMismatch::Network { @@ -1594,13 +1592,10 @@ fn build_with_store_internal( let bdk_wallet = match wallet_opt { Some(wallet) => wallet, None => { - let mut wallet = runtime - .block_on(async { - BdkWallet::create(descriptor, change_descriptor) - .network(config.network) - .create_wallet_async(&mut wallet_persister) - .await - }) + let mut wallet = BdkWallet::create(descriptor, change_descriptor) + .network(config.network) + .create_wallet_async(&mut wallet_persister) + .await .map_err(|e| { log_error!(logger, "Failed to set up wallet: {}", e); BuildError::WalletSetupFailed @@ -1685,12 +1680,10 @@ fn build_with_store_internal( // Read ChannelMonitors and the NetworkGraph let kv_store_ref = Arc::clone(&kv_store); let logger_ref = Arc::clone(&logger); - let (monitor_read_res, network_graph_res) = runtime.block_on(async { - tokio::join!( - monitor_reader.read_all_channel_monitors_with_updates_parallel(), - read_network_graph(&*kv_store_ref, logger_ref), - ) - }); + let (monitor_read_res, network_graph_res) = tokio::join!( + monitor_reader.read_all_channel_monitors_with_updates_parallel(), + read_network_graph(&*kv_store_ref, logger_ref), + ); // Read ChannelMonitor state from store let channel_monitors = match monitor_read_res { @@ -1753,21 +1746,19 @@ fn build_with_store_internal( sweeper_bytes_res, event_queue_res, peer_info_res, - ) = runtime.block_on(async move { - tokio::join!( - read_scorer(&*kv_store_ref, network_graph_ref, Arc::clone(&logger_ref)), - read_external_pathfinding_scores_from_cache(&*kv_store_ref, Arc::clone(&logger_ref)), - KVStore::read( - &*kv_store_ref, - CHANNEL_MANAGER_PERSISTENCE_PRIMARY_NAMESPACE, - CHANNEL_MANAGER_PERSISTENCE_SECONDARY_NAMESPACE, - CHANNEL_MANAGER_PERSISTENCE_KEY, - ), - output_sweeper_future, - read_event_queue(Arc::clone(&kv_store_ref), Arc::clone(&logger_ref)), - read_peer_info(Arc::clone(&kv_store_ref), Arc::clone(&logger_ref)), - ) - }); + ) = tokio::join!( + read_scorer(&*kv_store_ref, network_graph_ref, Arc::clone(&logger_ref)), + read_external_pathfinding_scores_from_cache(&*kv_store_ref, Arc::clone(&logger_ref)), + KVStore::read( + &*kv_store_ref, + CHANNEL_MANAGER_PERSISTENCE_PRIMARY_NAMESPACE, + CHANNEL_MANAGER_PERSISTENCE_SECONDARY_NAMESPACE, + CHANNEL_MANAGER_PERSISTENCE_KEY, + ), + output_sweeper_future, + read_event_queue(Arc::clone(&kv_store_ref), Arc::clone(&logger_ref)), + read_peer_info(Arc::clone(&kv_store_ref), Arc::clone(&logger_ref)), + ); let local_scorer = match scorer_res { Ok(scorer) => scorer, @@ -2037,8 +2028,7 @@ fn build_with_store_internal( liquidity_source_builder.lsps2_service(promise_secret, config.clone()) }); - let liquidity_source = runtime - .block_on(async move { liquidity_source_builder.build().await.map(Arc::new) })?; + let liquidity_source = liquidity_source_builder.build().await.map(Arc::new)?; let custom_message_handler = Arc::new(NodeCustomMessageHandler::new_liquidity(Arc::clone(&liquidity_source))); (Some(liquidity_source), custom_message_handler) diff --git a/src/event.rs b/src/event.rs index d5621521ac..f345c38883 100644 --- a/src/event.rs +++ b/src/event.rs @@ -630,12 +630,16 @@ where // Sign the final funding transaction and broadcast it. let channel_amount = Amount::from_sat(channel_value_satoshis); - match self.wallet.create_funding_transaction( - output_script, - channel_amount, - confirmation_target, - locktime, - ) { + match self + .wallet + .create_funding_transaction( + output_script, + channel_amount, + confirmation_target, + locktime, + ) + .await + { Ok(final_tx) => { let needs_manual_broadcast = self.liquidity_source.as_ref().map_or(false, |ls| { @@ -1644,7 +1648,7 @@ where }) .collect(), }; - if let Err(e) = self.wallet.cancel_tx(&tx) { + if let Err(e) = self.wallet.cancel_tx(&tx).await { log_error!(self.logger, "Failed reclaiming unused addresses: {}", e); return Err(ReplayEvent()); } diff --git a/src/io/postgres_store/mod.rs b/src/io/postgres_store/mod.rs index 694ba59fb3..486f985c7b 100644 --- a/src/io/postgres_store/mod.rs +++ b/src/io/postgres_store/mod.rs @@ -818,7 +818,7 @@ mod tests { async fn test_postgres_store() { let store_0 = create_test_store("test_pg_store_0").await; let store_1 = create_test_store("test_pg_store_1").await; - do_test_store(&store_0, &store_1); + do_test_store(&store_0, &store_1).await; cleanup_store(&store_0).await; cleanup_store(&store_1).await; } diff --git a/src/io/sqlite_store/mod.rs b/src/io/sqlite_store/mod.rs index 076aeef9bd..cc28ccb28b 100644 --- a/src/io/sqlite_store/mod.rs +++ b/src/io/sqlite_store/mod.rs @@ -676,7 +676,7 @@ mod tests { Some("test_table".to_string()), ) .unwrap(); - do_test_store(&store_0, &store_1) + do_test_store(&store_0, &store_1).await } #[tokio::test] diff --git a/src/io/test_utils.rs b/src/io/test_utils.rs index aadb4b79a8..4e8e90deef 100644 --- a/src/io/test_utils.rs +++ b/src/io/test_utils.rs @@ -8,7 +8,7 @@ use std::future::Future; use std::panic::RefUnwindSafe; use std::path::PathBuf; -use std::sync::Arc; +use std::sync::Mutex; use lightning::chain::channelmonitor::{ChannelMonitor, ChannelMonitorUpdate}; use lightning::chain::{chainmonitor, BlockLocator, ChannelMonitorUpdateStatus}; @@ -35,71 +35,128 @@ use rand::{rng, Rng}; #[path = "in_memory_store.rs"] mod in_memory_store; -use crate::logger::Logger; -use crate::runtime::Runtime; +enum PendingMonitorPersistence { + Write { monitor_name: MonitorName, monitor: Vec }, + Archive { monitor_name: MonitorName }, +} pub(crate) struct TestMonitorUpdatePersister<'a, K> { store: &'a K, - runtime: Runtime, + pending_updates: Mutex>, entropy_source: &'a test_utils::TestKeysInterface, signer_provider: &'a test_utils::TestKeysInterface, } impl TestMonitorUpdatePersister<'_, K> { - pub(crate) fn read_all_channel_monitors_with_updates( + async fn flush_pending_monitor_updates(&self) -> Result<(), io::Error> { + loop { + let pending_updates = { + let mut pending_updates = self.pending_updates.lock().unwrap(); + if pending_updates.is_empty() { + return Ok(()); + } + std::mem::take(&mut *pending_updates) + }; + + for update in pending_updates { + match update { + PendingMonitorPersistence::Write { monitor_name, monitor } => { + KVStore::write( + self.store, + CHANNEL_MONITOR_PERSISTENCE_PRIMARY_NAMESPACE, + CHANNEL_MONITOR_PERSISTENCE_SECONDARY_NAMESPACE, + &monitor_name.to_string(), + monitor, + ) + .await?; + }, + PendingMonitorPersistence::Archive { monitor_name } => { + let key = monitor_name.to_string(); + let monitor = match KVStore::read( + self.store, + CHANNEL_MONITOR_PERSISTENCE_PRIMARY_NAMESPACE, + CHANNEL_MONITOR_PERSISTENCE_SECONDARY_NAMESPACE, + &key, + ) + .await + { + Ok(monitor) => monitor, + Err(_) => continue, + }; + + if KVStore::write( + self.store, + ARCHIVED_CHANNEL_MONITOR_PERSISTENCE_PRIMARY_NAMESPACE, + ARCHIVED_CHANNEL_MONITOR_PERSISTENCE_SECONDARY_NAMESPACE, + &key, + monitor, + ) + .await + .is_ok() + { + let _ = KVStore::remove( + self.store, + CHANNEL_MONITOR_PERSISTENCE_PRIMARY_NAMESPACE, + CHANNEL_MONITOR_PERSISTENCE_SECONDARY_NAMESPACE, + &key, + true, + ) + .await; + } + }, + } + } + } + } + + pub(crate) async fn read_all_channel_monitors_with_updates( &self, ) -> Result)>, io::Error> { - self.runtime.block_on(async { - let stored_keys = KVStore::list( + self.flush_pending_monitor_updates().await?; + + let stored_keys = KVStore::list( + self.store, + CHANNEL_MONITOR_PERSISTENCE_PRIMARY_NAMESPACE, + CHANNEL_MONITOR_PERSISTENCE_SECONDARY_NAMESPACE, + ) + .await?; + + let mut res = Vec::with_capacity(stored_keys.len()); + for stored_key in stored_keys { + let data = KVStore::read( self.store, CHANNEL_MONITOR_PERSISTENCE_PRIMARY_NAMESPACE, CHANNEL_MONITOR_PERSISTENCE_SECONDARY_NAMESPACE, + &stored_key, ) .await?; - - let mut res = Vec::with_capacity(stored_keys.len()); - for stored_key in stored_keys { - let data = KVStore::read( - self.store, - CHANNEL_MONITOR_PERSISTENCE_PRIMARY_NAMESPACE, - CHANNEL_MONITOR_PERSISTENCE_SECONDARY_NAMESPACE, - &stored_key, - ) - .await?; - match )>>::read( - &mut io::Cursor::new(data), - (self.entropy_source, self.signer_provider), - ) { - Ok(Some((best_block, channel_monitor))) => { - res.push((best_block, channel_monitor)); - }, - Ok(None) => {}, - Err(_) => { - return Err(io::Error::new( - io::ErrorKind::InvalidData, - "Failed to read ChannelMonitor", - )); - }, - } + match )>>::read( + &mut io::Cursor::new(data), + (self.entropy_source, self.signer_provider), + ) { + Ok(Some((best_block, channel_monitor))) => { + res.push((best_block, channel_monitor)); + }, + Ok(None) => {}, + Err(_) => { + return Err(io::Error::new( + io::ErrorKind::InvalidData, + "Failed to read ChannelMonitor", + )); + }, } - Ok(res) - }) + } + Ok(res) } fn write_monitor( &self, monitor_name: MonitorName, monitor: &ChannelMonitor, ) -> ChannelMonitorUpdateStatus { - let write_res = self.runtime.block_on(KVStore::write( - self.store, - CHANNEL_MONITOR_PERSISTENCE_PRIMARY_NAMESPACE, - CHANNEL_MONITOR_PERSISTENCE_SECONDARY_NAMESPACE, - &monitor_name.to_string(), - monitor.encode(), - )); - match write_res { - Ok(()) => ChannelMonitorUpdateStatus::Completed, - Err(_) => ChannelMonitorUpdateStatus::UnrecoverableError, - } + self.pending_updates + .lock() + .unwrap() + .push(PendingMonitorPersistence::Write { monitor_name, monitor: monitor.encode() }); + ChannelMonitorUpdateStatus::Completed } } @@ -120,40 +177,10 @@ impl chainmonitor::Persist } fn archive_persisted_channel(&self, monitor_name: MonitorName) { - let key = monitor_name.to_string(); - self.runtime.block_on(async { - let monitor = match KVStore::read( - self.store, - CHANNEL_MONITOR_PERSISTENCE_PRIMARY_NAMESPACE, - CHANNEL_MONITOR_PERSISTENCE_SECONDARY_NAMESPACE, - &key, - ) - .await - { - Ok(monitor) => monitor, - Err(_) => return, - }; - - if KVStore::write( - self.store, - ARCHIVED_CHANNEL_MONITOR_PERSISTENCE_PRIMARY_NAMESPACE, - ARCHIVED_CHANNEL_MONITOR_PERSISTENCE_SECONDARY_NAMESPACE, - &key, - monitor, - ) - .await - .is_ok() - { - let _ = KVStore::remove( - self.store, - CHANNEL_MONITOR_PERSISTENCE_PRIMARY_NAMESPACE, - CHANNEL_MONITOR_PERSISTENCE_SECONDARY_NAMESPACE, - &key, - true, - ) - .await; - } - }); + self.pending_updates + .lock() + .unwrap() + .push(PendingMonitorPersistence::Archive { monitor_name }); } } @@ -248,11 +275,9 @@ pub(crate) async fn do_read_write_remove_list_persist( store: &'a K, chanmon_cfg: &'a TestChanMonCfg, _max_pending_updates: u64, ) -> TestMonitorUpdatePersister<'a, K> { - let runtime = - Runtime::new(Arc::new(Logger::new_log_facade())).expect("Failed to setup runtime"); TestMonitorUpdatePersister { store, - runtime, + pending_updates: Mutex::new(Vec::new()), entropy_source: &chanmon_cfg.keys_manager, signer_provider: &chanmon_cfg.keys_manager, } @@ -273,7 +298,7 @@ pub(crate) fn create_chain_monitor<'a, K: KVStore + Sync>( // Integration-test the given KVStore implementation. Test relaying a few payments and check that // the persisted data is updated the appropriate number of times. -pub(crate) fn do_test_store(store_0: &K, store_1: &K) { +pub(crate) async fn do_test_store(store_0: &K, store_1: &K) { // This value is used later to limit how many iterations we perform. let persister_0_max_pending_updates = 7; // Intentionally set this to a smaller value to test a different alignment. @@ -297,20 +322,24 @@ pub(crate) fn do_test_store(store_0: &K, store_1: &K) { // Check that the persisted channel data is empty before any channels are // open. - let mut persisted_chan_data_0 = persister_0.read_all_channel_monitors_with_updates().unwrap(); + let mut persisted_chan_data_0 = + persister_0.read_all_channel_monitors_with_updates().await.unwrap(); assert_eq!(persisted_chan_data_0.len(), 0); - let mut persisted_chan_data_1 = persister_1.read_all_channel_monitors_with_updates().unwrap(); + let mut persisted_chan_data_1 = + persister_1.read_all_channel_monitors_with_updates().await.unwrap(); assert_eq!(persisted_chan_data_1.len(), 0); // Helper to make sure the channel is on the expected update ID. macro_rules! check_persisted_data { ($expected_update_id:expr) => { - persisted_chan_data_0 = persister_0.read_all_channel_monitors_with_updates().unwrap(); + persisted_chan_data_0 = + persister_0.read_all_channel_monitors_with_updates().await.unwrap(); assert_eq!(persisted_chan_data_0.len(), 1); for (_, mon) in persisted_chan_data_0.iter() { assert_eq!(mon.get_latest_update_id(), $expected_update_id); } - persisted_chan_data_1 = persister_1.read_all_channel_monitors_with_updates().unwrap(); + persisted_chan_data_1 = + persister_1.read_all_channel_monitors_with_updates().await.unwrap(); assert_eq!(persisted_chan_data_1.len(), 1); for (_, mon) in persisted_chan_data_1.iter() { assert_eq!(mon.get_latest_update_id(), $expected_update_id); diff --git a/src/lib.rs b/src/lib.rs index 0d5f6b9bb1..65bf822b87 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -34,7 +34,8 @@ //! use ldk_node::lightning_invoice::Bolt11Invoice; //! use ldk_node::Builder; //! -//! fn main() { +//! #[tokio::main] +//! async fn main() { //! let mut builder = Builder::new(); //! builder.set_network(Network::Testnet); //! builder.set_chain_source_esplora("https://blockstream.info/testnet/api".to_string(), None); @@ -44,26 +45,26 @@ //! //! let mnemonic = generate_entropy_mnemonic(None); //! let node_entropy = NodeEntropy::from_bip39_mnemonic(mnemonic, None); -//! let node = builder.build(node_entropy).unwrap(); +//! let node = builder.build(node_entropy).await.unwrap(); //! -//! node.start().unwrap(); +//! node.start().await.unwrap(); //! -//! let funding_address = node.onchain_payment().new_address(); +//! let funding_address = node.onchain_payment().new_address().await.unwrap(); //! //! // .. fund address .. //! //! let node_id = PublicKey::from_str("NODE_ID").unwrap(); //! let node_addr = SocketAddress::from_str("IP_ADDR:PORT").unwrap(); -//! node.open_channel(node_id, node_addr, 10000, None, None).unwrap(); +//! node.open_channel(node_id, node_addr, 10000, None, None).await.unwrap(); //! -//! let event = node.wait_next_event(); +//! let event = node.wait_next_event().await; //! println!("EVENT: {:?}", event); -//! node.event_handled(); +//! node.event_handled().await.unwrap(); //! //! let invoice = Bolt11Invoice::from_str("INVOICE_STR").unwrap(); -//! node.bolt11_payment().send(&invoice, None).unwrap(); +//! node.bolt11_payment().send(&invoice, None).await.unwrap(); //! -//! node.stop().unwrap(); +//! node.stop().await.unwrap(); //! } //! # } //! ``` @@ -261,11 +262,14 @@ impl Node { /// /// After this returns, the [`Node`] instance can be controlled via the provided API methods in /// a thread-safe manner. - pub fn start(&self) -> Result<(), Error> { + pub async fn start(&self) -> Result<(), Error> { // Acquire a run lock and hold it until we're setup. - let mut is_running_lock = self.is_running.write().expect("lock"); - if *is_running_lock { - return Err(Error::AlreadyRunning); + { + let mut is_running_lock = self.is_running.write().expect("lock"); + if *is_running_lock { + return Err(Error::AlreadyRunning); + } + *is_running_lock = true; } log_info!( @@ -275,34 +279,35 @@ impl Node { self.config.network ); - // Start up any runtime-dependant chain sources (e.g. Electrum) - self.chain_source.start(Arc::clone(&self.runtime)).map_err(|e| { - log_error!(self.logger, "Failed to start chain syncing: {}", e); - e - })?; + let startup_res = async { + // Start up any runtime-dependant chain sources (e.g. Electrum) + self.chain_source.start(Arc::clone(&self.runtime)).map_err(|e| { + log_error!(self.logger, "Failed to start chain syncing: {}", e); + e + })?; - // Block to ensure we update our fee rate cache once on startup - let chain_source = Arc::clone(&self.chain_source); - self.runtime.block_on(async move { chain_source.update_fee_rate_estimates().await })?; + // Ensure we update our fee rate cache once on startup. + let chain_source = Arc::clone(&self.chain_source); + chain_source.update_fee_rate_estimates().await?; - // Spawn background task continuously syncing onchain, lightning, and fee rate cache. - let stop_sync_receiver = self.stop_sender.subscribe(); - let chain_source = Arc::clone(&self.chain_source); - let sync_wallet = Arc::clone(&self.wallet); - let sync_cman = Arc::clone(&self.channel_manager); - let sync_cmon = Arc::clone(&self.chain_monitor); - let sync_sweeper = Arc::clone(&self.output_sweeper); - self.runtime.spawn_background_task(async move { - chain_source - .continuously_sync_wallets( - stop_sync_receiver, - sync_wallet, - sync_cman, - sync_cmon, - sync_sweeper, - ) - .await; - }); + // Spawn background task continuously syncing onchain, lightning, and fee rate cache. + let stop_sync_receiver = self.stop_sender.subscribe(); + let chain_source = Arc::clone(&self.chain_source); + let sync_wallet = Arc::clone(&self.wallet); + let sync_cman = Arc::clone(&self.channel_manager); + let sync_cmon = Arc::clone(&self.chain_monitor); + let sync_sweeper = Arc::clone(&self.output_sweeper); + self.runtime.spawn_background_task(async move { + chain_source + .continuously_sync_wallets( + stop_sync_receiver, + sync_wallet, + sync_cman, + sync_cmon, + sync_sweeper, + ) + .await; + }); if self.gossip_source.is_rgs() { let gossip_source = Arc::clone(&self.gossip_source); @@ -362,7 +367,7 @@ impl Node { let logger = Arc::clone(&listening_logger); let listening_addrs = listening_addresses.clone(); - let listeners = self.runtime.block_on(async move { + let listeners = async move { let mut bind_addrs = Vec::with_capacity(listening_addrs.len()); for listening_addr in &listening_addrs { @@ -401,7 +406,8 @@ impl Node { } Ok(listeners) - })?; + } + .await?; for listener in listeners { let logger = Arc::clone(&listening_logger); @@ -701,18 +707,27 @@ impl Node { }); } - log_info!(self.logger, "Startup complete."); - *is_running_lock = true; - Ok(()) + log_info!(self.logger, "Startup complete."); + Ok(()) + } + .await; + + if startup_res.is_err() { + *self.is_running.write().expect("lock") = false; + } + startup_res } /// Disconnects all peers, stops all running background tasks, and shuts down [`Node`]. /// /// After this returns most API methods will return [`Error::NotRunning`]. - pub fn stop(&self) -> Result<(), Error> { - let mut is_running_lock = self.is_running.write().expect("lock"); - if !*is_running_lock { - return Err(Error::NotRunning); + pub async fn stop(&self) -> Result<(), Error> { + { + let mut is_running_lock = self.is_running.write().expect("lock"); + if !*is_running_lock { + return Err(Error::NotRunning); + } + *is_running_lock = false; } log_info!(self.logger, "Shutting down LDK Node with node ID {}...", self.node_id()); @@ -733,14 +748,14 @@ impl Node { }); // Cancel cancellable background tasks - self.runtime.abort_cancellable_background_tasks(); + self.runtime.abort_cancellable_background_tasks().await; // Disconnect all peers. self.peer_manager.disconnect_all_peers(); log_debug!(self.logger, "Disconnected all network peers."); // Wait until non-cancellable background tasks (mod LDK's background processor) are done. - self.runtime.wait_on_background_tasks(); + self.runtime.wait_on_background_tasks().await; // Stop any runtime-dependant chain sources. self.chain_source.stop(); @@ -762,13 +777,12 @@ impl Node { }); // Finally, wait until background processing stopped, at least until a timeout is reached. - self.runtime.wait_on_background_processor_task(); + self.runtime.wait_on_background_processor_task().await; #[cfg(tokio_unstable)] self.runtime.log_metrics(); log_info!(self.logger, "Shutdown complete."); - *is_running_lock = false; Ok(()) } @@ -837,27 +851,21 @@ impl Node { /// Returns the next event in the event queue. /// - /// Will block the current thread until the next event is available. + /// Will asynchronously wait until the next event is available. /// /// **Note:** this will always return the same event until handling is confirmed via [`Node::event_handled`]. /// /// **Caution:** Users must handle events as quickly as possible to prevent a large event backlog, /// which can increase the memory footprint of [`Node`]. - pub fn wait_next_event(&self) -> Event { - let fut = self.event_queue.next_event_async(); - // We use our runtime for the sync variant to ensure `tokio::task::block_in_place` is - // always called if we'd ever hit this in an outer runtime context. - self.runtime.block_on(fut) + pub async fn wait_next_event(&self) -> Event { + self.event_queue.next_event_async().await } /// Confirm the last retrieved event handled. /// /// **Note:** This **MUST** be called after each event has been handled. - pub fn event_handled(&self) -> Result<(), Error> { - // We use our runtime for the sync variant to ensure `tokio::task::block_in_place` is - // always called if we'd ever hit this in an outer runtime context. - let fut = self.event_queue.event_handled(); - self.runtime.block_on(fut).map_err(|e| { + pub async fn event_handled(&self) -> Result<(), Error> { + self.event_queue.event_handled().await.map_err(|e| { log_error!( self.logger, "Couldn't mark event handled due to persistence failure: {}", @@ -1057,21 +1065,19 @@ impl Node { /// Authenticates the user via [LNURL-auth] for the given LNURL string. /// /// [LNURL-auth]: https://github.com/lnurl/luds/blob/luds/04.md - pub fn lnurl_auth(&self, lnurl: String) -> Result<(), Error> { + pub async fn lnurl_auth(&self, lnurl: String) -> Result<(), Error> { let auth = Arc::clone(&self.lnurl_auth); - self.runtime.block_on(async move { - let res = tokio::time::timeout( - Duration::from_secs(LNURL_AUTH_TIMEOUT_SECS), - auth.authenticate(&lnurl), - ) - .await; + let res = tokio::time::timeout( + Duration::from_secs(LNURL_AUTH_TIMEOUT_SECS), + auth.authenticate(&lnurl), + ) + .await; - match res { - Ok(Ok(())) => Ok(()), - Ok(Err(e)) => Err(e), - Err(_) => Err(Error::LnurlAuthTimeout), - } - }) + match res { + Ok(Ok(())) => Ok(()), + Ok(Err(e)) => Err(e), + Err(_) => Err(Error::LnurlAuthTimeout), + } } /// Returns a liquidity handler allowing to request channels via the [bLIP-51 / LSPS1] protocol. @@ -1080,7 +1086,6 @@ impl Node { #[cfg(not(feature = "uniffi"))] pub fn lsps1_liquidity(&self) -> LSPS1Liquidity { LSPS1Liquidity::new( - Arc::clone(&self.runtime), Arc::clone(&self.wallet), Arc::clone(&self.connection_manager), self.liquidity_source.clone(), @@ -1094,7 +1099,6 @@ impl Node { #[cfg(feature = "uniffi")] pub fn lsps1_liquidity(&self) -> Arc { Arc::new(LSPS1Liquidity::new( - Arc::clone(&self.runtime), Arc::clone(&self.wallet), Arc::clone(&self.connection_manager), self.liquidity_source.clone(), @@ -2107,7 +2111,15 @@ impl Node { impl Drop for Node { fn drop(&mut self) { - let _ = self.stop(); + if let Ok(mut is_running_lock) = self.is_running.write() { + if *is_running_lock { + *is_running_lock = false; + let _ = self.stop_sender.send(()); + let _ = self.background_processor_stop_sender.send(()); + self.peer_manager.disconnect_all_peers(); + self.chain_source.stop(); + } + } } } diff --git a/src/liquidity.rs b/src/liquidity.rs index 3cd6d110da..00f71d1a7f 100644 --- a/src/liquidity.rs +++ b/src/liquidity.rs @@ -44,7 +44,6 @@ use crate::connection::ConnectionManager; use crate::logger::{log_debug, log_error, log_info, LdkLogger, Logger}; use crate::payment::store::LSPS2Parameters; use crate::payment::PaymentMetadata; -use crate::runtime::Runtime; use crate::types::{ Broadcaster, ChannelManager, DynStore, KeysManager, LiquidityManager, PeerManager, Wallet, }; @@ -1510,7 +1509,6 @@ pub(crate) struct LSPS2BuyResponse { #[derive(Clone)] #[cfg_attr(feature = "uniffi", derive(uniffi::Object))] pub struct LSPS1Liquidity { - runtime: Arc, wallet: Arc, connection_manager: Arc>>, liquidity_source: Option>>>, @@ -1519,11 +1517,10 @@ pub struct LSPS1Liquidity { impl LSPS1Liquidity { pub(crate) fn new( - runtime: Arc, wallet: Arc, - connection_manager: Arc>>, + wallet: Arc, connection_manager: Arc>>, liquidity_source: Option>>>, logger: Arc, ) -> Self { - Self { runtime, wallet, connection_manager, liquidity_source, logger } + Self { wallet, connection_manager, liquidity_source, logger } } } @@ -1533,7 +1530,7 @@ impl LSPS1Liquidity { /// /// The channel will be opened after one of the returned payment options has successfully been /// paid. - pub fn request_channel( + pub async fn request_channel( &self, lsp_balance_sat: u64, client_balance_sat: u64, channel_expiry_blocks: u32, announce_channel: bool, ) -> Result { @@ -1547,34 +1544,30 @@ impl LSPS1Liquidity { let con_addr = lsp_address.clone(); let con_cm = Arc::clone(&self.connection_manager); - // We need to use our main runtime here as a local runtime might not be around to poll - // connection futures going forward. - self.runtime.block_on(async move { - con_cm.connect_peer_if_necessary(con_node_id, con_addr).await - })?; + con_cm.connect_peer_if_necessary(con_node_id, con_addr).await?; log_info!(self.logger, "Connected to LSP {}@{}. ", lsp_node_id, lsp_address); - let refund_address = self.wallet.get_new_address()?; + let refund_address = self.wallet.get_new_address().await?; let liquidity_source = Arc::clone(&liquidity_source); - let response = self.runtime.block_on(async move { - liquidity_source - .lsps1_request_channel( - lsp_balance_sat, - client_balance_sat, - channel_expiry_blocks, - announce_channel, - refund_address, - ) - .await - })?; + let response = liquidity_source + .lsps1_request_channel( + lsp_balance_sat, + client_balance_sat, + channel_expiry_blocks, + announce_channel, + refund_address, + ) + .await?; Ok(response) } /// Connects to the configured LSP and checks for the status of a previously-placed order. - pub fn check_order_status(&self, order_id: LSPS1OrderId) -> Result { + pub async fn check_order_status( + &self, order_id: LSPS1OrderId, + ) -> Result { let liquidity_source = self.liquidity_source.as_ref().ok_or(Error::LiquiditySourceUnavailable)?; @@ -1585,16 +1578,10 @@ impl LSPS1Liquidity { let con_addr = lsp_address.clone(); let con_cm = Arc::clone(&self.connection_manager); - // We need to use our main runtime here as a local runtime might not be around to poll - // connection futures going forward. - self.runtime.block_on(async move { - con_cm.connect_peer_if_necessary(con_node_id, con_addr).await - })?; + con_cm.connect_peer_if_necessary(con_node_id, con_addr).await?; let liquidity_source = Arc::clone(&liquidity_source); - let response = self - .runtime - .block_on(async move { liquidity_source.lsps1_check_order_status(order_id).await })?; + let response = liquidity_source.lsps1_check_order_status(order_id).await?; Ok(response) } } diff --git a/src/payment/onchain.rs b/src/payment/onchain.rs index 7c8334d7bb..93a12754fd 100644 --- a/src/payment/onchain.rs +++ b/src/payment/onchain.rs @@ -62,8 +62,8 @@ impl OnchainPayment { #[cfg_attr(feature = "uniffi", uniffi::export)] impl OnchainPayment { /// Retrieve a new on-chain/funding address. - pub fn new_address(&self) -> Result { - let funding_address = self.wallet.get_new_address()?; + pub async fn new_address(&self) -> Result { + let funding_address = self.wallet.get_new_address().await?; log_info!(self.logger, "Generated new funding address: {}", funding_address); Ok(funding_address) } @@ -77,7 +77,7 @@ impl OnchainPayment { /// a reasonable estimate from the configured chain source. /// /// [`BalanceDetails::total_anchor_channels_reserve_sats`]: crate::BalanceDetails::total_anchor_channels_reserve_sats - pub fn send_to_address( + pub async fn send_to_address( &self, address: &bitcoin::Address, amount_sats: u64, fee_rate: Option, ) -> Result { if !*self.is_running.read().expect("lock") { @@ -89,7 +89,7 @@ impl OnchainPayment { let send_amount = OnchainSendAmount::ExactRetainingReserve { amount_sats, cur_anchor_reserve_sats }; let fee_rate_opt = maybe_map_fee_rate_opt!(fee_rate); - self.wallet.send_to_address(address, send_amount, fee_rate_opt) + self.wallet.send_to_address(address, send_amount, fee_rate_opt).await } /// Send an on-chain payment to the given address, draining the available funds. @@ -107,7 +107,7 @@ impl OnchainPayment { /// we'll retrieve an estimate from the configured chain source. /// /// [`BalanceDetails::spendable_onchain_balance_sats`]: crate::balance::BalanceDetails::spendable_onchain_balance_sats - pub fn send_all_to_address( + pub async fn send_all_to_address( &self, address: &bitcoin::Address, retain_reserves: bool, fee_rate: Option, ) -> Result { if !*self.is_running.read().expect("lock") { @@ -123,7 +123,7 @@ impl OnchainPayment { }; let fee_rate_opt = maybe_map_fee_rate_opt!(fee_rate); - self.wallet.send_to_address(address, send_amount, fee_rate_opt) + self.wallet.send_to_address(address, send_amount, fee_rate_opt).await } /// Attempt to bump the fee of an unconfirmed transaction using Replace-by-Fee (RBF). diff --git a/src/payment/unified.rs b/src/payment/unified.rs index 92b2858ff0..73a70a569c 100644 --- a/src/payment/unified.rs +++ b/src/payment/unified.rs @@ -129,7 +129,7 @@ impl UnifiedPayment { pub async fn receive( &self, amount_sats: u64, description: &str, expiry_sec: u32, ) -> Result { - let onchain_address = self.onchain_payment.new_address()?; + let onchain_address = self.onchain_payment.new_address().await?; let amount_msats = amount_sats * 1_000; @@ -327,7 +327,8 @@ impl UnifiedPayment { Error::InvalidAmount })?; - let txid = self.onchain_payment.send_to_address(&address, amt_sats, None)?; + let txid = + self.onchain_payment.send_to_address(&address, amt_sats, None).await?; return Ok(UnifiedPaymentResult::Onchain { txid }); }, } diff --git a/src/runtime.rs b/src/runtime.rs index 9673d0eb7a..a72dc0a274 100644 --- a/src/runtime.rs +++ b/src/runtime.rs @@ -6,7 +6,6 @@ // accordance with one or both of these licenses. use std::future::Future; -use std::io; use std::sync::atomic::{AtomicUsize, Ordering}; use std::sync::{Arc, Mutex}; use std::time::Duration; @@ -39,15 +38,11 @@ impl Runtime { let id = ATOMIC_ID.fetch_add(1, Ordering::SeqCst); format!("ldk-node-runtime-{}", id) }); - // Eager driver handoff lets Tokio move the I/O driver to another worker sooner - // when this runtime's current worker enters `block_in_place` via `block_on`. - // That marginally reduces the chance that a synchronous caller blocks the same - // worker that would otherwise drive the I/O resource it is waiting on. It does - // not solve the issue completely: it only applies to node runtimes we build - // ourselves under `tokio_unstable`, does not affect externally supplied runtime - // handles, and cannot guarantee that every persistence driver task needed by the - // blocked future is already polling elsewhere. See the `StoreRuntime` docs below - // for the full deadlock scenario and the temporary store-runtime isolation. + // Eager driver handoff lets Tokio move the I/O driver to another worker sooner when + // a worker enters Tokio-managed blocking sections in dependencies. This is a + // low-risk scheduler hint for runtimes we build ourselves under `tokio_unstable`, + // but it does not affect externally supplied runtime handles and is not a substitute + // for exposing async APIs all the way to callers. #[cfg(tokio_unstable)] runtime_builder.enable_eager_driver_handoff(); let rt = runtime_builder.build()?; @@ -128,74 +123,57 @@ impl Runtime { handle.spawn_blocking(func) } - pub fn block_on(&self, future: F) -> F::Output { - // While we generally decided not to overthink via which call graph users would enter our - // runtime context, we'd still try to reuse whatever current context would be present - // during `block_on`, as this is the context `block_in_place` would operate on. So we try - // to detect the outer context here, and otherwise use whatever was set during - // initialization. - let handle = tokio::runtime::Handle::try_current().unwrap_or(self.handle().clone()); - // Since it seems to make a difference to `tokio` (see - // https://docs.rs/tokio/latest/tokio/time/fn.timeout.html#panics) we make sure the futures - // are always put in an `async` / `.await` closure. - tokio::task::block_in_place(move || handle.block_on(async { future.await })) - } - - pub fn abort_cancellable_background_tasks(&self) { + pub async fn abort_cancellable_background_tasks(&self) { let mut tasks = core::mem::take(&mut *self.cancellable_background_tasks.lock().expect("lock")); debug_assert!(tasks.len() > 0, "Expected some cancellable background_tasks"); tasks.abort_all(); - self.block_on(async { while let Some(_) = tasks.join_next().await {} }) + while let Some(_) = tasks.join_next().await {} } - pub fn wait_on_background_tasks(&self) { + pub async fn wait_on_background_tasks(&self) { let mut tasks = core::mem::take(&mut *self.background_tasks.lock().expect("lock")); debug_assert!(tasks.len() > 0, "Expected some background_tasks"); - self.block_on(async { - loop { - let timeout_fut = tokio::time::timeout( - Duration::from_secs(BACKGROUND_TASK_SHUTDOWN_TIMEOUT_SECS), - tasks.join_next_with_id(), - ); - match timeout_fut.await { - Ok(Some(Ok((id, _)))) => { - log_trace!(self.logger, "Stopped background task with id {}", id); - }, - Ok(Some(Err(e))) => { - tasks.abort_all(); - log_trace!(self.logger, "Stopping background task failed: {}", e); - break; - }, - Ok(None) => { - log_debug!(self.logger, "Stopped all background tasks"); - break; - }, - Err(e) => { - tasks.abort_all(); - log_error!(self.logger, "Stopping background task timed out: {}", e); - break; - }, - } + loop { + let timeout_fut = tokio::time::timeout( + Duration::from_secs(BACKGROUND_TASK_SHUTDOWN_TIMEOUT_SECS), + tasks.join_next_with_id(), + ); + match timeout_fut.await { + Ok(Some(Ok((id, _)))) => { + log_trace!(self.logger, "Stopped background task with id {}", id); + }, + Ok(Some(Err(e))) => { + tasks.abort_all(); + log_trace!(self.logger, "Stopping background task failed: {}", e); + break; + }, + Ok(None) => { + log_debug!(self.logger, "Stopped all background tasks"); + break; + }, + Err(e) => { + tasks.abort_all(); + log_error!(self.logger, "Stopping background task timed out: {}", e); + break; + }, } - }) + } } - pub fn wait_on_background_processor_task(&self) { - if let Some(background_processor_task) = - self.background_processor_task.lock().expect("lock").take() - { + pub async fn wait_on_background_processor_task(&self) { + let background_processor_task = + { self.background_processor_task.lock().expect("lock").take() }; + if let Some(background_processor_task) = background_processor_task { let abort_handle = background_processor_task.abort_handle(); // Since it seems to make a difference to `tokio` (see // https://docs.rs/tokio/latest/tokio/time/fn.timeout.html#panics) we make sure the futures // are always put in an `async` / `.await` closure. - let timeout_res = self.block_on(async { - tokio::time::timeout( - Duration::from_secs(LDK_EVENT_HANDLER_SHUTDOWN_TIMEOUT_SECS), - background_processor_task, - ) - .await - }); + let timeout_res = tokio::time::timeout( + Duration::from_secs(LDK_EVENT_HANDLER_SHUTDOWN_TIMEOUT_SECS), + background_processor_task, + ) + .await; match timeout_res { Ok(stop_res) => match stop_res { @@ -243,90 +221,6 @@ enum RuntimeMode { Handle(tokio::runtime::Handle), } -/// Runtime used by async store backends while ldk-node still exposes synchronous APIs. -/// -/// This is a temporary bridge for store implementations that need Tokio-driven I/O, such as VSS -/// and PostgreSQL. Many public ldk-node methods are still synchronous, so they call -/// [`Runtime::block_on`] when they need to wait for async persistence. If that persistence work is -/// driven by the same Tokio runtime as the synchronous caller, a blocking call can deadlock in a -/// narrow but realistic scheduler state. -/// -/// The failure mode is that `block_on` parks the current worker with `block_in_place` while it -/// waits for an async store operation. Suppose that store operation is waiting for an I/O future, -/// and the connection driver or I/O driver task that can make the future progress is assigned to -/// the same worker thread that just entered `block_in_place`. The blocked sync caller is waiting -/// for the persistence future to complete, while the persistence future is waiting for an I/O task -/// that cannot be polled because its worker is occupied by the blocking caller. With no worker -/// driving that I/O resource, neither side can make progress. -/// -/// A simple example is a synchronous node API calling `block_on(store.write(...))` for a -/// tokio-postgres-backed store. The write future may wait for the postgres connection task or -/// socket readiness. If the runtime worker that should poll that connection task is also the -/// worker currently blocked in the synchronous API, the write cannot complete, and the synchronous -/// API cannot unblock. -/// -/// `StoreRuntime` gives each such store backend its own small runtime, workers, and I/O driver. -/// Synchronous node APIs may still block the node runtime while waiting for persistence, but the -/// persistence tasks they wait on are driven independently and can continue polling sockets and -/// connection drivers. -/// -/// Once ldk-node switches the remaining store-backed APIs to be fully async, callers will await -/// persistence directly and these `block_on` bridges will be disallowed. At that point the store -/// runtimes should be removed again and store I/O can run on the node runtime directly. -pub(crate) struct StoreRuntime { - runtime: Option, -} - -impl StoreRuntime { - pub(crate) fn new( - thread_name_prefix: &'static str, worker_threads: usize, runtime_name: &'static str, - ) -> io::Result { - let runtime = tokio::runtime::Builder::new_multi_thread() - .enable_all() - .thread_name_fn(move || { - static ATOMIC_ID: AtomicUsize = AtomicUsize::new(0); - let id = ATOMIC_ID.fetch_add(1, Ordering::SeqCst); - format!("{}-{}", thread_name_prefix, id) - }) - .worker_threads(worker_threads) - .max_blocking_threads(worker_threads) - .build() - .map_err(|e| { - io::Error::new( - io::ErrorKind::Other, - format!("Failed to build {runtime_name} runtime: {e}"), - ) - })?; - Ok(Self { runtime: Some(runtime) }) - } - - pub(crate) fn handle(&self) -> &tokio::runtime::Handle { - self.runtime.as_ref().expect("store runtime must be available").handle() - } - - pub(crate) fn spawn(&self, future: F) -> JoinHandle - where - F: Future + Send + 'static, - F::Output: Send + 'static, - { - self.handle().spawn(future) - } - - pub(crate) fn shutdown_background(mut self) { - if let Some(runtime) = self.runtime.take() { - runtime.shutdown_background(); - } - } -} - -impl Drop for StoreRuntime { - fn drop(&mut self) { - if let Some(runtime) = self.runtime.take() { - runtime.shutdown_background(); - } - } -} - pub(crate) struct RuntimeSpawner { runtime: Arc, } diff --git a/src/scoring.rs b/src/scoring.rs index 8056d3f056..266ca01b03 100644 --- a/src/scoring.rs +++ b/src/scoring.rs @@ -4,7 +4,7 @@ use std::time::SystemTime; use lightning::routing::scoring::ChannelLiquidities; use lightning::util::ser::Readable; use lightning::{log_error, log_info, log_trace}; -use tokio::sync::RwLock; +use tokio::sync::RwLock as AsyncRwLock; use crate::config::{ EXTERNAL_PATHFINDING_SCORES_MAX_SIZE, EXTERNAL_PATHFINDING_SCORES_SYNC_INTERVAL, @@ -14,14 +14,14 @@ use crate::io::utils::write_external_pathfinding_scores_to_cache; use crate::logger::LdkLogger; use crate::runtime::Runtime; use crate::types::DynStore; -use crate::{update_and_persist_node_metrics, Logger, PersistedNodeMetrics, Scorer}; +use crate::{update_and_persist_node_metrics, Logger, NodeMetrics, Scorer}; /// Start a background task that periodically downloads scores via an external url and merges them into the local /// pathfinding scores. pub fn setup_background_pathfinding_scores_sync( - url: String, scorer: Arc>, node_metrics: Arc, - kv_store: Arc, logger: Arc, runtime: Arc, - mut stop_receiver: tokio::sync::watch::Receiver<()>, + url: String, scorer: Arc>, + node_metrics: Arc>, kv_store: Arc, logger: Arc, + runtime: Arc, mut stop_receiver: tokio::sync::watch::Receiver<()>, ) { log_info!(logger, "External scores background syncing enabled from {}", url); @@ -52,7 +52,7 @@ pub fn setup_background_pathfinding_scores_sync( } async fn sync_external_scores( - logger: &Logger, scorer: &Mutex, node_metrics: &PersistedNodeMetrics, + logger: &Logger, scorer: &Mutex, node_metrics: &AsyncRwLock, kv_store: Arc, url: &String, ) -> () { let request = bitreq::get(url) diff --git a/src/wallet/mod.rs b/src/wallet/mod.rs index c3e77dcc78..bec06474f5 100644 --- a/src/wallet/mod.rs +++ b/src/wallet/mod.rs @@ -11,12 +11,13 @@ use std::str::FromStr; use std::sync::{Arc, Mutex}; use bdk_chain::spk_client::{FullScanRequest, SyncRequest}; +use bdk_chain::Merge; use bdk_wallet::descriptor::ExtendedDescriptor; use bdk_wallet::error::{BuildFeeBumpError, CreateTxError}; use bdk_wallet::event::WalletEvent; #[allow(deprecated)] use bdk_wallet::SignOptions; -use bdk_wallet::{Balance, KeychainKind, PersistedWallet, Update}; +use bdk_wallet::{AsyncWalletPersister, Balance, ChangeSet, KeychainKind, PersistedWallet, Update}; use bitcoin::address::NetworkUnchecked; use bitcoin::blockdata::constants::WITNESS_SCALE_FACTOR; use bitcoin::blockdata::locktime::absolute::LockTime; @@ -50,6 +51,7 @@ use lightning::util::wallet_utils::{ }; use lightning_invoice::RawBolt11Invoice; use persist::KVStoreWalletPersister; +use tokio::sync::Mutex as AsyncMutex; use crate::config::Config; use crate::fee_estimator::{ConfirmationTarget, FeeEstimator, OnchainFeeEstimator}; @@ -78,10 +80,13 @@ pub(crate) mod ser; const DUST_LIMIT_SATS: u64 = 546; +#[derive(Clone)] pub(crate) struct Wallet { // A BDK on-chain wallet. - inner: Mutex>, - persister: Mutex, + inner: Arc>>, + persister: Arc>, + pending_changes: Arc>, + pending_persist_lock: Arc>, broadcaster: Arc, fee_estimator: Arc, chain_source: Arc, @@ -100,11 +105,15 @@ impl Wallet { payment_store: Arc, runtime: Arc, config: Arc, logger: Arc, pending_payment_store: Arc, ) -> Self { - let inner = Mutex::new(wallet); - let persister = Mutex::new(wallet_persister); + let inner = Arc::new(Mutex::new(wallet)); + let persister = Arc::new(AsyncMutex::new(wallet_persister)); + let pending_changes = Arc::new(Mutex::new(ChangeSet::default())); + let pending_persist_lock = Arc::new(AsyncMutex::new(())); Self { inner, persister, + pending_changes, + pending_persist_lock, broadcaster, fee_estimator, chain_source, @@ -116,6 +125,56 @@ impl Wallet { } } + fn queue_staged_wallet_changes_from( + &self, locked_wallet: &mut PersistedWallet, + ) -> bool { + let Some(changes) = locked_wallet.take_staged() else { + return false; + }; + self.pending_changes.lock().expect("lock").merge(changes); + true + } + + fn queue_staged_wallet_changes(&self) -> bool { + let mut locked_wallet = self.inner.lock().expect("lock"); + self.queue_staged_wallet_changes_from(&mut locked_wallet) + } + + async fn persist_queued_wallet_changes(&self) -> Result<(), std::io::Error> { + let _lock = self.pending_persist_lock.lock().await; + + loop { + let changes = { + let mut pending_changes = self.pending_changes.lock().expect("lock"); + if pending_changes.is_empty() { + return Ok(()); + } + core::mem::take(&mut *pending_changes) + }; + + let persist_res = { + let mut locked_persister = self.persister.lock().await; + KVStoreWalletPersister::persist(&mut *locked_persister, &changes).await + }; + + if let Err(e) = persist_res { + self.pending_changes.lock().expect("lock").merge(changes); + return Err(e); + } + } + } + + fn spawn_persist_wallet(&self) { + let wallet = self.clone(); + let runtime = Arc::clone(&self.runtime); + let logger = Arc::clone(&self.logger); + runtime.spawn_background_task(async move { + if let Err(e) = wallet.persist_queued_wallet_changes().await { + log_error!(logger, "Failed to persist on-chain wallet: {}", e); + } + }); + } + pub(crate) fn get_full_scan_request(&self) -> FullScanRequest { self.inner.lock().expect("lock").start_full_scan().build() } @@ -163,14 +222,13 @@ impl Wallet { } }; + self.queue_staged_wallet_changes(); self.update_payment_store(events).await.map_err(|e| { log_error!(self.logger, "Failed to update payment store: {}", e); Error::PersistenceFailed })?; - let mut locked_wallet = self.inner.lock().expect("lock"); - let mut locked_persister = self.persister.lock().expect("lock"); - self.runtime.block_on(locked_wallet.persist_async(&mut locked_persister)).map_err(|e| { + self.persist_queued_wallet_changes().await.map_err(|e| { log_error!(self.logger, "Failed to persist wallet: {}", e); Error::PersistenceFailed })?; @@ -209,7 +267,15 @@ impl Wallet { (Arc, bdk_chain::ChainPosition), >>(); - wallet_events(&mut *locked_wallet, chain_tip1, chain_tip2, wallet_txs1, wallet_txs2) + let events = wallet_events( + &mut *locked_wallet, + chain_tip1, + chain_tip2, + wallet_txs1, + wallet_txs2, + ); + self.queue_staged_wallet_changes_from(&mut locked_wallet); + events }; self.update_payment_store(events).await.map_err(|e| { @@ -217,9 +283,7 @@ impl Wallet { Error::PersistenceFailed })?; - let mut locked_wallet = self.inner.lock().expect("lock"); - let mut locked_persister = self.persister.lock().expect("lock"); - self.runtime.block_on(locked_wallet.persist_async(&mut locked_persister)).map_err(|e| { + self.persist_queued_wallet_changes().await.map_err(|e| { log_error!(self.logger, "Failed to persist wallet: {}", e); Error::PersistenceFailed })?; @@ -448,84 +512,105 @@ impl Wallet { } #[allow(deprecated)] - pub(crate) fn create_funding_transaction( + pub(crate) async fn create_funding_transaction( &self, output_script: ScriptBuf, amount: Amount, confirmation_target: ConfirmationTarget, locktime: LockTime, ) -> Result { let fee_rate = self.fee_estimator.estimate_fee_rate(confirmation_target); - let mut locked_wallet = self.inner.lock().expect("lock"); - let mut tx_builder = locked_wallet.build_tx(); + let tx = { + let mut locked_wallet = self.inner.lock().expect("lock"); + let mut tx_builder = locked_wallet.build_tx(); - tx_builder.add_recipient(output_script, amount).fee_rate(fee_rate).nlocktime(locktime); + tx_builder.add_recipient(output_script, amount).fee_rate(fee_rate).nlocktime(locktime); - let mut psbt = match tx_builder.finish() { - Ok(psbt) => { - log_trace!(self.logger, "Created funding PSBT: {:?}", psbt); - psbt - }, - Err(err) => { - log_error!(self.logger, "Failed to create funding transaction: {}", err); - return Err(err.into()); - }, - }; + let mut psbt = match tx_builder.finish() { + Ok(psbt) => { + log_trace!(self.logger, "Created funding PSBT: {:?}", psbt); + psbt + }, + Err(err) => { + log_error!(self.logger, "Failed to create funding transaction: {}", err); + return Err(err.into()); + }, + }; - match locked_wallet.sign(&mut psbt, SignOptions::default()) { - Ok(finalized) => { - if !finalized { - return Err(Error::OnchainTxCreationFailed); - } - }, - Err(err) => { - log_error!(self.logger, "Failed to create funding transaction: {}", err); - return Err(err.into()); - }, - } + match locked_wallet.sign(&mut psbt, SignOptions::default()) { + Ok(finalized) => { + if !finalized { + return Err(Error::OnchainTxCreationFailed); + } + }, + Err(err) => { + log_error!(self.logger, "Failed to create funding transaction: {}", err); + return Err(err.into()); + }, + } + + self.queue_staged_wallet_changes_from(&mut locked_wallet); + + psbt.extract_tx().map_err(|e| { + log_error!(self.logger, "Failed to extract transaction: {}", e); + e + })? + }; - let mut locked_persister = self.persister.lock().expect("lock"); - self.runtime.block_on(locked_wallet.persist_async(&mut locked_persister)).map_err(|e| { + self.persist_queued_wallet_changes().await.map_err(|e| { log_error!(self.logger, "Failed to persist wallet: {}", e); Error::PersistenceFailed })?; - let tx = psbt.extract_tx().map_err(|e| { - log_error!(self.logger, "Failed to extract transaction: {}", e); - e + Ok(tx) + } + + pub(crate) async fn get_new_address(&self) -> Result { + let address = self.get_new_address_inner(); + self.persist_queued_wallet_changes().await.map_err(|e| { + log_error!(self.logger, "Failed to persist wallet: {}", e); + Error::PersistenceFailed })?; + Ok(address) + } - Ok(tx) + fn get_new_address_sync(&self) -> bitcoin::Address { + let address = self.get_new_address_inner(); + self.spawn_persist_wallet(); + address } - pub(crate) fn get_new_address(&self) -> Result { + fn get_new_address_inner(&self) -> bitcoin::Address { let mut locked_wallet = self.inner.lock().expect("lock"); - let mut locked_persister = self.persister.lock().expect("lock"); let address_info = locked_wallet.reveal_next_address(KeychainKind::External); - self.runtime.block_on(locked_wallet.persist_async(&mut locked_persister)).map_err(|e| { + self.queue_staged_wallet_changes_from(&mut locked_wallet); + address_info.address + } + + pub(crate) async fn get_new_internal_address(&self) -> Result { + let address = self.get_new_internal_address_inner(); + self.persist_queued_wallet_changes().await.map_err(|e| { log_error!(self.logger, "Failed to persist wallet: {}", e); Error::PersistenceFailed })?; - Ok(address_info.address) + Ok(address) } - pub(crate) fn get_new_internal_address(&self) -> Result { + fn get_new_internal_address_inner(&self) -> bitcoin::Address { let mut locked_wallet = self.inner.lock().expect("lock"); - let mut locked_persister = self.persister.lock().expect("lock"); let address_info = locked_wallet.next_unused_address(KeychainKind::Internal); - self.runtime.block_on(locked_wallet.persist_async(&mut locked_persister)).map_err(|e| { - log_error!(self.logger, "Failed to persist wallet: {}", e); - Error::PersistenceFailed - })?; - Ok(address_info.address) + self.queue_staged_wallet_changes_from(&mut locked_wallet); + address_info.address } - pub(crate) fn cancel_tx(&self, tx: &Transaction) -> Result<(), Error> { - let mut locked_wallet = self.inner.lock().expect("lock"); - let mut locked_persister = self.persister.lock().expect("lock"); + pub(crate) async fn cancel_tx(&self, tx: &Transaction) -> Result<(), Error> { + { + let mut locked_wallet = self.inner.lock().expect("lock"); + locked_wallet.cancel_tx(tx); + self.queue_staged_wallet_changes_from(&mut locked_wallet); + } - locked_wallet.cancel_tx(tx); - self.runtime.block_on(locked_wallet.persist_async(&mut locked_persister)).map_err(|e| { + self.persist_queued_wallet_changes().await.map_err(|e| { log_error!(self.logger, "Failed to persist wallet: {}", e); Error::PersistenceFailed })?; @@ -728,7 +813,7 @@ impl Wallet { } #[allow(deprecated)] - pub(crate) fn send_to_address( + pub(crate) async fn send_to_address( &self, address: &bitcoin::Address, send_amount: OnchainSendAmount, fee_rate: Option, ) -> Result { @@ -862,13 +947,7 @@ impl Wallet { }, } - let mut locked_persister = self.persister.lock().expect("lock"); - self.runtime.block_on(locked_wallet.persist_async(&mut locked_persister)).map_err( - |e| { - log_error!(self.logger, "Failed to persist wallet: {}", e); - Error::PersistenceFailed - }, - )?; + self.queue_staged_wallet_changes_from(&mut locked_wallet); psbt.extract_tx().map_err(|e| { log_error!(self.logger, "Failed to extract transaction: {}", e); @@ -876,6 +955,11 @@ impl Wallet { })? }; + self.persist_queued_wallet_changes().await.map_err(|e| { + log_error!(self.logger, "Failed to persist wallet: {}", e); + Error::PersistenceFailed + })?; + self.broadcaster.broadcast_transactions(&[( &tx, lightning::chain::chaininterface::TransactionType::Sweep { channels: vec![] }, @@ -915,83 +999,90 @@ impl Wallet { Ok(txid) } - pub(crate) fn select_confirmed_utxos( + pub(crate) async fn select_confirmed_utxos( &self, must_spend: Vec, must_pay_to: &[TxOut], fee_rate: FeeRate, ) -> Result { - let mut locked_wallet = self.inner.lock().expect("lock"); - let mut locked_persister = self.persister.lock().expect("lock"); + let (coin_selection, should_persist) = { + let mut locked_wallet = self.inner.lock().expect("lock"); - debug_assert!(matches!( - locked_wallet.public_descriptor(KeychainKind::External), - ExtendedDescriptor::Wpkh(_) - )); - debug_assert!(matches!( - locked_wallet.public_descriptor(KeychainKind::Internal), - ExtendedDescriptor::Wpkh(_) - )); + debug_assert!(matches!( + locked_wallet.public_descriptor(KeychainKind::External), + ExtendedDescriptor::Wpkh(_) + )); + debug_assert!(matches!( + locked_wallet.public_descriptor(KeychainKind::Internal), + ExtendedDescriptor::Wpkh(_) + )); + + let mut tx_builder = locked_wallet.build_tx(); + tx_builder.only_witness_utxo(); + + for input in &must_spend { + let psbt_input = psbt::Input { + witness_utxo: Some(input.previous_utxo.clone()), + ..Default::default() + }; + let weight = ldk_to_bdk_satisfaction_weight(input.satisfaction_weight); + tx_builder.add_foreign_utxo(input.outpoint, psbt_input, weight).map_err(|_| ())?; + } - let mut tx_builder = locked_wallet.build_tx(); - tx_builder.only_witness_utxo(); + for output in must_pay_to { + tx_builder.add_recipient(output.script_pubkey.clone(), output.value); + } - for input in &must_spend { - let psbt_input = psbt::Input { - witness_utxo: Some(input.previous_utxo.clone()), - ..Default::default() - }; - let weight = ldk_to_bdk_satisfaction_weight(input.satisfaction_weight); - tx_builder.add_foreign_utxo(input.outpoint, psbt_input, weight).map_err(|_| ())?; - } + tx_builder.fee_rate(fee_rate); + tx_builder.exclude_unconfirmed(); - for output in must_pay_to { - tx_builder.add_recipient(output.script_pubkey.clone(), output.value); - } + let unsigned_tx = tx_builder + .finish() + .map_err(|e| { + log_error!(self.logger, "Failed to select confirmed UTXOs: {}", e); + })? + .unsigned_tx; - tx_builder.fee_rate(fee_rate); - tx_builder.exclude_unconfirmed(); + let confirmed_utxos = unsigned_tx + .input + .iter() + .filter(|txin| { + must_spend.iter().all(|input| input.outpoint != txin.previous_output) + }) + .filter_map(|txin| { + locked_wallet + .tx_details(txin.previous_output.txid) + .map(|tx_details| tx_details.tx.deref().clone()) + .map(|prevtx| ConfirmedUtxo::new_p2wpkh(prevtx, txin.previous_output.vout)) + }) + .collect::, ()>>()?; - let unsigned_tx = tx_builder - .finish() - .map_err(|e| { - log_error!(self.logger, "Failed to select confirmed UTXOs: {}", e); - })? - .unsigned_tx; + if unsigned_tx.output.len() > must_pay_to.len() + 1 { + log_error!( + self.logger, + "Unexpected number of change outputs during coin selection: {}", + unsigned_tx.output.len() - must_pay_to.len(), + ); + return Err(()); + } - let confirmed_utxos = unsigned_tx - .input - .iter() - .filter(|txin| must_spend.iter().all(|input| input.outpoint != txin.previous_output)) - .filter_map(|txin| { - locked_wallet - .tx_details(txin.previous_output.txid) - .map(|tx_details| tx_details.tx.deref().clone()) - .map(|prevtx| ConfirmedUtxo::new_p2wpkh(prevtx, txin.previous_output.vout)) - }) - .collect::, ()>>()?; + let change_output = unsigned_tx + .output + .into_iter() + .find(|txout| must_pay_to.iter().all(|output| output != txout)); + let should_persist = change_output.is_some(); - if unsigned_tx.output.len() > must_pay_to.len() + 1 { - log_error!( - self.logger, - "Unexpected number of change outputs during coin selection: {}", - unsigned_tx.output.len() - must_pay_to.len(), - ); - return Err(()); - } + if should_persist { + self.queue_staged_wallet_changes_from(&mut locked_wallet); + } - let change_output = unsigned_tx - .output - .into_iter() - .find(|txout| must_pay_to.iter().all(|output| output != txout)); - - if change_output.is_some() { - self.runtime.block_on(locked_wallet.persist_async(&mut locked_persister)).map_err( - |e| { - log_error!(self.logger, "Failed to persist wallet: {}", e); - () - }, - )?; + (Ok(CoinSelection { confirmed_utxos, change_output }), should_persist) + }; + + if should_persist { + self.persist_queued_wallet_changes().await.map_err(|e| { + log_error!(self.logger, "Failed to persist wallet: {}", e); + })?; } - Ok(CoinSelection { confirmed_utxos, change_output }) + coin_selection } fn list_confirmed_utxos_inner(&self) -> Result, ()> { @@ -1088,16 +1179,19 @@ impl Wallet { } #[allow(deprecated)] - fn get_change_script_inner(&self) -> Result { - let mut locked_wallet = self.inner.lock().expect("lock"); - let mut locked_persister = self.persister.lock().expect("lock"); + async fn get_change_script_inner(&self) -> Result { + let script_pubkey = { + let mut locked_wallet = self.inner.lock().expect("lock"); + let address_info = locked_wallet.next_unused_address(KeychainKind::Internal); + self.queue_staged_wallet_changes_from(&mut locked_wallet); + address_info.address.script_pubkey() + }; - let address_info = locked_wallet.next_unused_address(KeychainKind::Internal); - self.runtime.block_on(locked_wallet.persist_async(&mut locked_persister)).map_err(|e| { + self.persist_queued_wallet_changes().await.map_err(|e| { log_error!(self.logger, "Failed to persist wallet: {}", e); () })?; - Ok(address_info.address.script_pubkey()) + Ok(script_pubkey) } #[allow(deprecated)] @@ -1433,18 +1527,7 @@ impl Wallet { }, } - let mut locked_persister = self.persister.lock().expect("lock"); - self.runtime.block_on(locked_wallet.persist_async(&mut locked_persister)).map_err( - |e| { - log_error!( - self.logger, - "Failed to persist wallet after fee bump of {}: {}", - txid, - e - ); - Error::PersistenceFailed - }, - )?; + self.queue_staged_wallet_changes_from(&mut locked_wallet); let fee_bumped_tx = psbt.extract_tx().map_err(|e| { log_error!( @@ -1470,6 +1553,11 @@ impl Wallet { (fee_bumped_tx, new_txid, new_payment, pending_payment_store) }; + self.persist_queued_wallet_changes().await.map_err(|e| { + log_error!(self.logger, "Failed to persist wallet after fee bump of {}: {}", txid, e); + Error::PersistenceFailed + })?; + self.broadcaster.broadcast_transactions(&[( &fee_bumped_tx, lightning::chain::chaininterface::TransactionType::Sweep { channels: vec![] }, @@ -1530,7 +1618,7 @@ impl Listen for Wallet { } } - match locked_wallet.apply_block_events(block, height) { + let events = match locked_wallet.apply_block_events(block, height) { Ok(events) => events, Err(e) => { log_error!( @@ -1540,23 +1628,23 @@ impl Listen for Wallet { ); return; }, - } + }; + self.queue_staged_wallet_changes_from(&mut locked_wallet); + events }; - if let Err(e) = self.runtime.block_on(self.update_payment_store(events)) { - log_error!(self.logger, "Failed to update payment store: {}", e); - return; - } - - let mut locked_persister = self.persister.lock().expect("lock"); - let mut locked_wallet = self.inner.lock().expect("lock"); - match self.runtime.block_on(locked_wallet.persist_async(&mut locked_persister)) { - Ok(_) => (), - Err(e) => { - log_error!(self.logger, "Failed to persist on-chain wallet: {}", e); + let wallet = self.clone(); + let runtime = Arc::clone(&self.runtime); + let logger = Arc::clone(&self.logger); + runtime.spawn_background_task(async move { + if let Err(e) = wallet.update_payment_store(events).await { + log_error!(logger, "Failed to update payment store: {}", e); return; - }, - }; + } + if let Err(e) = wallet.persist_queued_wallet_changes().await { + log_error!(logger, "Failed to persist on-chain wallet: {}", e); + } + }); } fn blocks_disconnected(&self, _fork_point_block: BlockLocator) { @@ -1574,7 +1662,7 @@ impl WalletSource for Wallet { } fn get_change_script<'a>(&'a self) -> impl Future> + Send + 'a { - async move { self.get_change_script_inner() } + async move { self.get_change_script_inner().await } } fn get_prevtx<'a>( @@ -1611,7 +1699,7 @@ impl CoinSelectionSource for Wallet { ) -> impl Future> + Send + 'a { debug_assert!(claim_id.is_none()); let fee_rate = FeeRate::from_sat_per_kwu(target_feerate_sat_per_1000_weight as u64); - async move { self.select_confirmed_utxos(must_spend, must_pay_to, fee_rate) } + async move { self.select_confirmed_utxos(must_spend, must_pay_to, fee_rate).await } } fn sign_psbt<'a>( @@ -1735,16 +1823,12 @@ impl SignerProvider for WalletKeysManager { } fn get_destination_script(&self, _channel_keys_id: [u8; 32]) -> Result { - let address = self.wallet.get_new_address().map_err(|e| { - log_error!(self.logger, "Failed to retrieve new address from wallet: {}", e); - })?; + let address = self.wallet.get_new_address_sync(); Ok(address.script_pubkey()) } fn get_shutdown_scriptpubkey(&self) -> Result { - let address = self.wallet.get_new_address().map_err(|e| { - log_error!(self.logger, "Failed to retrieve new address from wallet: {}", e); - })?; + let address = self.wallet.get_new_address_sync(); match address.witness_program() { Some(program) => ShutdownScript::new_witness_program(&program).map_err(|e| { @@ -1768,6 +1852,7 @@ impl ChangeDestinationSource for WalletKeysManager { async move { self.wallet .get_new_internal_address() + .await .map_err(|e| { log_error!(self.logger, "Failed to retrieve new address from wallet: {}", e); }) diff --git a/tests/common/mod.rs b/tests/common/mod.rs index 1131ec8c7c..c8e195e2de 100644 --- a/tests/common/mod.rs +++ b/tests/common/mod.rs @@ -83,7 +83,7 @@ macro_rules! expect_event { match event { ref e @ Event::$event_type { .. } => { println!("{} got event {:?}", $node.node_id(), e); - $node.event_handled().unwrap(); + $node.event_handled().await.unwrap(); }, ref e => { panic!("{} got unexpected event!: {:?}", std::stringify!($node), e); @@ -108,7 +108,7 @@ macro_rules! expect_channel_pending_event { ref e @ Event::ChannelPending { funding_txo, counterparty_node_id, .. } => { println!("{} got event {:?}", $node.node_id(), e); assert_eq!(counterparty_node_id, $counterparty_node_id); - $node.event_handled().unwrap(); + $node.event_handled().await.unwrap(); funding_txo }, ref e => { @@ -134,7 +134,7 @@ macro_rules! expect_channel_ready_event { ref e @ Event::ChannelReady { user_channel_id, counterparty_node_id, .. } => { println!("{} got event {:?}", $node.node_id(), e); assert_eq!(counterparty_node_id, Some($counterparty_node_id)); - $node.event_handled().unwrap(); + $node.event_handled().await.unwrap(); user_channel_id }, ref e => { @@ -162,7 +162,7 @@ macro_rules! expect_channel_ready_events { ref e @ Event::ChannelReady { counterparty_node_id, .. } => { println!("{} got event {:?}", $node.node_id(), e); ids.push(counterparty_node_id); - $node.event_handled().unwrap(); + $node.event_handled().await.unwrap(); }, ref e => { panic!("{} got unexpected event!: {:?}", std::stringify!($node), e); @@ -196,7 +196,7 @@ macro_rules! expect_splice_negotiated_event { ref e @ Event::SpliceNegotiated { new_funding_txo, counterparty_node_id, .. } => { println!("{} got event {:?}", $node.node_id(), e); assert_eq!(counterparty_node_id, $counterparty_node_id); - $node.event_handled().unwrap(); + $node.event_handled().await.unwrap(); new_funding_txo }, ref e => { @@ -226,7 +226,7 @@ macro_rules! expect_payment_received_event { if !matches!(payment.kind, ldk_node::payment::PaymentKind::Onchain { .. }) { assert_eq!(payment.fee_paid_msat, None); } - $node.event_handled().unwrap(); + $node.event_handled().await.unwrap(); payment_id }, ref e => { @@ -262,7 +262,7 @@ macro_rules! expect_payment_claimable_event { assert_eq!(payment_hash, $payment_hash); assert_eq!(payment_id, $payment_id); assert_eq!(claimable_amount_msat, $claimable_amount_msat); - $node.event_handled().unwrap(); + $node.event_handled().await.unwrap(); claimable_amount_msat }, ref e => { @@ -293,7 +293,7 @@ macro_rules! expect_payment_successful_event { let payment = $node.payment(&$payment_id.unwrap()).await.unwrap(); assert_eq!(payment.fee_paid_msat, fee_paid_msat); assert_eq!(payment_id, $payment_id); - $node.event_handled().unwrap(); + $node.event_handled().await.unwrap(); }, ref e => { panic!("{} got unexpected event!: {:?}", std::stringify!(node_b), e); @@ -473,8 +473,8 @@ pub(crate) use setup_builder; #[cfg(any(cln_test, lnd_test, eclair_test))] pub(crate) mod scenarios; -pub(crate) fn setup_two_nodes( - chain_source: &TestChainSource, allow_0conf: bool, anchor_channels: bool, +pub(crate) async fn setup_two_nodes( + chain_source: &TestChainSource<'_>, allow_0conf: bool, anchor_channels: bool, anchors_trusted_no_reserve: bool, ) -> (TestNode, TestNode) { setup_two_nodes_with_store( @@ -484,10 +484,11 @@ pub(crate) fn setup_two_nodes( anchors_trusted_no_reserve, TestStoreType::TestSyncStore, ) + .await } -pub(crate) fn setup_two_nodes_with_store( - chain_source: &TestChainSource, allow_0conf: bool, anchor_channels: bool, +pub(crate) async fn setup_two_nodes_with_store( + chain_source: &TestChainSource<'_>, allow_0conf: bool, anchor_channels: bool, anchors_trusted_no_reserve: bool, store_type: TestStoreType, ) -> (TestNode, TestNode) { println!("== Node A =="); @@ -499,7 +500,7 @@ pub(crate) fn setup_two_nodes_with_store( HumanReadableNamesConfig { resolution_config: HRNResolverConfig::Blip32 }; } - let node_a = setup_node(chain_source, config_a); + let node_a = setup_node(chain_source, config_a).await; println!("\n== Node B =="); let mut config_b = random_config(anchor_channels); @@ -526,11 +527,11 @@ pub(crate) fn setup_two_nodes_with_store( .trusted_peers_no_reserve .push(node_a.node_id()); } - let node_b = setup_node(chain_source, config_b); + let node_b = setup_node(chain_source, config_b).await; (node_a, node_b) } -pub(crate) fn setup_node(chain_source: &TestChainSource, config: TestConfig) -> TestNode { +pub(crate) async fn setup_node(chain_source: &TestChainSource<'_>, config: TestConfig) -> TestNode { setup_builder!(builder, config.node_config); match chain_source { TestChainSource::Esplora(electrsd) => { @@ -593,11 +594,11 @@ pub(crate) fn setup_node(chain_source: &TestChainSource, config: TestConfig) -> let node = match config.store_type { TestStoreType::TestSyncStore => { let kv_store = TestSyncStore::new(config.node_config.storage_dir_path.into()); - builder.build_with_store(config.node_entropy.into(), kv_store).unwrap() + builder.build_with_store(config.node_entropy.into(), kv_store).await.unwrap() }, - TestStoreType::Sqlite => builder.build(config.node_entropy.into()).unwrap(), + TestStoreType::Sqlite => builder.build(config.node_entropy.into()).await.unwrap(), TestStoreType::FilesystemStore => { - builder.build_with_fs_store(config.node_entropy.into()).unwrap() + builder.build_with_fs_store(config.node_entropy.into()).await.unwrap() }, }; @@ -605,7 +606,7 @@ pub(crate) fn setup_node(chain_source: &TestChainSource, config: TestConfig) -> builder.set_wallet_recovery_mode(); } - node.start().unwrap(); + node.start().await.unwrap(); node } @@ -906,8 +907,8 @@ pub(crate) async fn do_channel_full_cycle( node_a: TestNode, node_b: TestNode, bitcoind: &BitcoindClient, electrsd: &E, allow_0conf: bool, disable_node_b_reserve: bool, expect_anchor_channel: bool, force_close: bool, ) { - let addr_a = node_a.onchain_payment().new_address().unwrap(); - let addr_b = node_b.onchain_payment().new_address().unwrap(); + let addr_a = node_a.onchain_payment().new_address().await.unwrap(); + let addr_b = node_b.onchain_payment().new_address().await.unwrap(); let premine_amount_sat = if expect_anchor_channel { 2_125_000 } else { 2_100_000 }; @@ -1125,7 +1126,7 @@ pub(crate) async fn do_channel_full_cycle( ref e @ Event::PaymentSuccessful { ref bolt12_invoice, .. } => { println!("{} got event {:?}", node_a.node_id(), e); assert!(bolt12_invoice.is_none(), "bolt12_invoice should be None for BOLT11 payments"); - node_a.event_handled().unwrap(); + node_a.event_handled().await.unwrap(); }, ref e => { panic!("{} got unexpected event!: {:?}", std::stringify!(node_a), e); @@ -1185,7 +1186,7 @@ pub(crate) async fn do_channel_full_cycle( let received_amount = match node_b.next_event_async().await { ref e @ Event::PaymentReceived { amount_msat, .. } => { println!("{} got event {:?}", std::stringify!(node_b), e); - node_b.event_handled().unwrap(); + node_b.event_handled().await.unwrap(); amount_msat }, ref e => { @@ -1225,7 +1226,7 @@ pub(crate) async fn do_channel_full_cycle( let received_amount = match node_b.next_event_async().await { ref e @ Event::PaymentReceived { amount_msat, .. } => { println!("{} got event {:?}", std::stringify!(node_b), e); - node_b.event_handled().unwrap(); + node_b.event_handled().await.unwrap(); amount_msat }, ref e => { @@ -1377,7 +1378,7 @@ pub(crate) async fn do_channel_full_cycle( let (received_keysend_amount, received_custom_records) = match next_event { ref e @ Event::PaymentReceived { amount_msat, ref custom_records, .. } => { println!("{} got event {:?}", std::stringify!(node_b), e); - node_b.event_handled().unwrap(); + node_b.event_handled().await.unwrap(); (amount_msat, custom_records) }, ref e => { @@ -1445,7 +1446,7 @@ pub(crate) async fn do_channel_full_cycle( generate_blocks_and_wait(&bitcoind, electrsd, 1).await; println!("\nB splices out to pay A"); - let addr_a = node_a.onchain_payment().new_address().unwrap(); + let addr_a = node_a.onchain_payment().new_address().await.unwrap(); let splice_out_sat = funding_amount_sat / 2; node_b.splice_out(&user_channel_id_b, node_a.node_id(), &addr_a, splice_out_sat).unwrap(); @@ -1730,9 +1731,9 @@ pub(crate) async fn do_channel_full_cycle( assert_eq!(node_a.next_event(), None); assert_eq!(node_b.next_event(), None); - node_a.stop().unwrap(); + node_a.stop().await.unwrap(); println!("\nA stopped"); - node_b.stop().unwrap(); + node_b.stop().await.unwrap(); println!("\nB stopped"); } diff --git a/tests/integration_tests_hrn.rs b/tests/integration_tests_hrn.rs index 99e500c8f3..497b9e61a2 100644 --- a/tests/integration_tests_hrn.rs +++ b/tests/integration_tests_hrn.rs @@ -24,9 +24,9 @@ async fn unified_send_to_hrn() { let (bitcoind, electrsd) = setup_bitcoind_and_electrsd(); let chain_source = random_chain_source(&bitcoind, &electrsd); - let (node_a, node_b) = setup_two_nodes(&chain_source, false, true, false); + let (node_a, node_b) = setup_two_nodes(&chain_source, false, true, false).await; - let address_a = node_a.onchain_payment().new_address().unwrap(); + let address_a = node_a.onchain_payment().new_address().await.unwrap(); let premined_sats = 5_000_000; premine_and_distribute_funds( diff --git a/tests/integration_tests_rust.rs b/tests/integration_tests_rust.rs index 55c7f83905..54ad381f3e 100644 --- a/tests/integration_tests_rust.rs +++ b/tests/integration_tests_rust.rs @@ -47,7 +47,7 @@ use log::LevelFilter; async fn channel_full_cycle() { let (bitcoind, electrsd) = setup_bitcoind_and_electrsd(); let chain_source = random_chain_source(&bitcoind, &electrsd); - let (node_a, node_b) = setup_two_nodes(&chain_source, false, true, false); + let (node_a, node_b) = setup_two_nodes(&chain_source, false, true, false).await; do_channel_full_cycle( node_a, node_b, @@ -65,7 +65,7 @@ async fn channel_full_cycle() { async fn channel_full_cycle_force_close() { let (bitcoind, electrsd) = setup_bitcoind_and_electrsd(); let chain_source = random_chain_source(&bitcoind, &electrsd); - let (node_a, node_b) = setup_two_nodes(&chain_source, false, true, false); + let (node_a, node_b) = setup_two_nodes(&chain_source, false, true, false).await; do_channel_full_cycle( node_a, node_b, @@ -83,7 +83,7 @@ async fn channel_full_cycle_force_close() { async fn channel_full_cycle_force_close_trusted_no_reserve() { let (bitcoind, electrsd) = setup_bitcoind_and_electrsd(); let chain_source = random_chain_source(&bitcoind, &electrsd); - let (node_a, node_b) = setup_two_nodes(&chain_source, false, true, true); + let (node_a, node_b) = setup_two_nodes(&chain_source, false, true, true).await; do_channel_full_cycle( node_a, node_b, @@ -101,7 +101,7 @@ async fn channel_full_cycle_force_close_trusted_no_reserve() { async fn channel_full_cycle_0conf() { let (bitcoind, electrsd) = setup_bitcoind_and_electrsd(); let chain_source = random_chain_source(&bitcoind, &electrsd); - let (node_a, node_b) = setup_two_nodes(&chain_source, true, true, false); + let (node_a, node_b) = setup_two_nodes(&chain_source, true, true, false).await; do_channel_full_cycle( node_a, node_b, @@ -119,7 +119,7 @@ async fn channel_full_cycle_0conf() { async fn channel_full_cycle_legacy_staticremotekey() { let (bitcoind, electrsd) = setup_bitcoind_and_electrsd(); let chain_source = random_chain_source(&bitcoind, &electrsd); - let (node_a, node_b) = setup_two_nodes(&chain_source, false, false, false); + let (node_a, node_b) = setup_two_nodes(&chain_source, false, false, false).await; do_channel_full_cycle( node_a, node_b, @@ -137,7 +137,7 @@ async fn channel_full_cycle_legacy_staticremotekey() { async fn channel_full_cycle_0reserve() { let (bitcoind, electrsd) = setup_bitcoind_and_electrsd(); let chain_source = random_chain_source(&bitcoind, &electrsd); - let (node_a, node_b) = setup_two_nodes(&chain_source, false, true, false); + let (node_a, node_b) = setup_two_nodes(&chain_source, false, true, false).await; do_channel_full_cycle( node_a, node_b, @@ -155,7 +155,7 @@ async fn channel_full_cycle_0reserve() { async fn channel_full_cycle_0conf_0reserve() { let (bitcoind, electrsd) = setup_bitcoind_and_electrsd(); let chain_source = random_chain_source(&bitcoind, &electrsd); - let (node_a, node_b) = setup_two_nodes(&chain_source, true, true, false); + let (node_a, node_b) = setup_two_nodes(&chain_source, true, true, false).await; do_channel_full_cycle( node_a, node_b, @@ -173,10 +173,10 @@ async fn channel_full_cycle_0conf_0reserve() { async fn channel_open_fails_when_funds_insufficient() { let (bitcoind, electrsd) = setup_bitcoind_and_electrsd(); let chain_source = random_chain_source(&bitcoind, &electrsd); - let (node_a, node_b) = setup_two_nodes(&chain_source, false, true, false); + let (node_a, node_b) = setup_two_nodes(&chain_source, false, true, false).await; - let addr_a = node_a.onchain_payment().new_address().unwrap(); - let addr_b = node_b.onchain_payment().new_address().unwrap(); + let addr_a = node_a.onchain_payment().new_address().await.unwrap(); + let addr_b = node_b.onchain_payment().new_address().await.unwrap(); let premine_amount_sat = 100_000; @@ -220,12 +220,15 @@ async fn multi_hop_sending() { sync_config.background_sync_config = None; setup_builder!(builder, config.node_config); builder.set_chain_source_esplora(esplora_url.clone(), Some(sync_config)); - let node = builder.build(config.node_entropy.into()).unwrap(); - node.start().unwrap(); + let node = builder.build(config.node_entropy.into()).await.unwrap(); + node.start().await.unwrap(); nodes.push(node); } - let addresses = nodes.iter().map(|n| n.onchain_payment().new_address().unwrap()).collect(); + let mut addresses = Vec::with_capacity(nodes.len()); + for node in &nodes { + addresses.push(node.onchain_payment().new_address().await.unwrap()); + } let premine_amount_sat = 5_000_000; premine_and_distribute_funds( &bitcoind.client, @@ -321,14 +324,16 @@ async fn start_stop_reinit() { setup_builder!(builder, config.node_config); builder.set_chain_source_esplora(esplora_url.clone(), Some(sync_config)); - let node = - builder.build_with_store(config.node_entropy.into(), test_sync_store.clone()).unwrap(); - node.start().unwrap(); + let node = builder + .build_with_store(config.node_entropy.into(), test_sync_store.clone()) + .await + .unwrap(); + node.start().await.unwrap(); let expected_node_id = node.node_id(); - assert_eq!(node.start(), Err(NodeError::AlreadyRunning)); + assert_eq!(node.start().await, Err(NodeError::AlreadyRunning)); - let funding_address = node.onchain_payment().new_address().unwrap(); + let funding_address = node.onchain_payment().new_address().await.unwrap(); assert_eq!(node.list_balances().total_onchain_balance_sats, 0); @@ -347,22 +352,22 @@ async fn start_stop_reinit() { let log_file = format!("{}/ldk_node.log", config.node_config.clone().storage_dir_path); assert!(std::path::Path::new(&log_file).exists()); - node.stop().unwrap(); - assert_eq!(node.stop(), Err(NodeError::NotRunning)); + node.stop().await.unwrap(); + assert_eq!(node.stop().await, Err(NodeError::NotRunning)); - node.start().unwrap(); - assert_eq!(node.start(), Err(NodeError::AlreadyRunning)); + node.start().await.unwrap(); + assert_eq!(node.start().await, Err(NodeError::AlreadyRunning)); - node.stop().unwrap(); - assert_eq!(node.stop(), Err(NodeError::NotRunning)); + node.stop().await.unwrap(); + assert_eq!(node.stop().await, Err(NodeError::NotRunning)); drop(node); setup_builder!(builder, config.node_config); builder.set_chain_source_esplora(esplora_url.clone(), Some(sync_config)); let reinitialized_node = - builder.build_with_store(config.node_entropy.into(), test_sync_store).unwrap(); - reinitialized_node.start().unwrap(); + builder.build_with_store(config.node_entropy.into(), test_sync_store).await.unwrap(); + reinitialized_node.start().await.unwrap(); assert_eq!(reinitialized_node.node_id(), expected_node_id); assert_eq!( @@ -376,17 +381,17 @@ async fn start_stop_reinit() { expected_amount.to_sat() ); - reinitialized_node.stop().unwrap(); + reinitialized_node.stop().await.unwrap(); } #[tokio::test(flavor = "multi_thread", worker_threads = 1)] async fn onchain_send_receive() { let (bitcoind, electrsd) = setup_bitcoind_and_electrsd(); let chain_source = random_chain_source(&bitcoind, &electrsd); - let (node_a, node_b) = setup_two_nodes(&chain_source, false, true, false); + let (node_a, node_b) = setup_two_nodes(&chain_source, false, true, false).await; - let addr_a = node_a.onchain_payment().new_address().unwrap(); - let addr_b = node_b.onchain_payment().new_address().unwrap(); + let addr_a = node_a.onchain_payment().new_address().await.unwrap(); + let addr_b = node_b.onchain_payment().new_address().await.unwrap(); // This is a Bitcoin Testnet address. Sending funds to this address from the Regtest network will fail let static_address = "tb1q0d40e5rta4fty63z64gztf8c3v20cvet6v2jdh"; let unchecked_address = Address::::from_str(static_address).unwrap(); @@ -453,22 +458,22 @@ async fn onchain_send_receive() { assert_eq!( Err(NodeError::InsufficientFunds), - node_a.onchain_payment().send_to_address(&addr_b, expected_node_a_balance + 1, None) + node_a.onchain_payment().send_to_address(&addr_b, expected_node_a_balance + 1, None).await ); assert_eq!( Err(NodeError::InvalidAddress), - node_a.onchain_payment().send_to_address(&addr_c, expected_node_a_balance + 1, None) + node_a.onchain_payment().send_to_address(&addr_c, expected_node_a_balance + 1, None).await ); assert_eq!( Err(NodeError::InvalidAddress), - node_a.onchain_payment().send_all_to_address(&addr_c, true, None) + node_a.onchain_payment().send_all_to_address(&addr_c, true, None).await ); let amount_to_send_sats = 54321; let txid = - node_b.onchain_payment().send_to_address(&addr_a, amount_to_send_sats, None).unwrap(); + node_b.onchain_payment().send_to_address(&addr_a, amount_to_send_sats, None).await.unwrap(); wait_for_tx(&electrsd.client, txid).await; node_a.sync_wallets().await.unwrap(); node_b.sync_wallets().await.unwrap(); @@ -532,8 +537,8 @@ async fn onchain_send_receive() { _ => panic!("Unexpected payment kind"), } - let addr_b = node_b.onchain_payment().new_address().unwrap(); - let txid = node_a.onchain_payment().send_all_to_address(&addr_b, true, None).unwrap(); + let addr_b = node_b.onchain_payment().new_address().await.unwrap(); + let txid = node_a.onchain_payment().send_all_to_address(&addr_b, true, None).await.unwrap(); generate_blocks_and_wait(&bitcoind.client, &electrsd.client, 6).await; wait_for_tx(&electrsd.client, txid).await; @@ -555,8 +560,8 @@ async fn onchain_send_receive() { node_b.list_payments_with_filter(|p| matches!(p.kind, PaymentKind::Onchain { .. })).await; assert_eq!(node_b_payments.len(), 4); - let addr_b = node_b.onchain_payment().new_address().unwrap(); - let txid = node_a.onchain_payment().send_all_to_address(&addr_b, false, None).unwrap(); + let addr_b = node_b.onchain_payment().new_address().await.unwrap(); + let txid = node_a.onchain_payment().send_all_to_address(&addr_b, false, None).await.unwrap(); generate_blocks_and_wait(&bitcoind.client, &electrsd.client, 6).await; wait_for_tx(&electrsd.client, txid).await; @@ -584,11 +589,11 @@ async fn onchain_send_receive() { async fn onchain_send_all_retains_reserve() { let (bitcoind, electrsd) = setup_bitcoind_and_electrsd(); let chain_source = random_chain_source(&bitcoind, &electrsd); - let (node_a, node_b) = setup_two_nodes(&chain_source, false, true, false); + let (node_a, node_b) = setup_two_nodes(&chain_source, false, true, false).await; // Setup nodes - let addr_a = node_a.onchain_payment().new_address().unwrap(); - let addr_b = node_b.onchain_payment().new_address().unwrap(); + let addr_a = node_a.onchain_payment().new_address().await.unwrap(); + let addr_b = node_b.onchain_payment().new_address().await.unwrap(); let premine_amount_sat = 1_000_000; let reserve_amount_sat = 25_000; @@ -607,7 +612,7 @@ async fn onchain_send_all_retains_reserve() { assert_eq!(node_b.list_balances().spendable_onchain_balance_sats, premine_amount_sat); // Send all over, with 0 reserve as we don't have any channels open. - let txid = node_a.onchain_payment().send_all_to_address(&addr_b, true, None).unwrap(); + let txid = node_a.onchain_payment().send_all_to_address(&addr_b, true, None).await.unwrap(); wait_for_tx(&electrsd.client, txid).await; generate_blocks_and_wait(&bitcoind.client, &electrsd.client, 6).await; @@ -648,7 +653,7 @@ async fn onchain_send_all_retains_reserve() { .contains(&node_b.list_balances().spendable_onchain_balance_sats)); // Send all over again, this time ensuring the reserve is accounted for - let txid = node_b.onchain_payment().send_all_to_address(&addr_a, true, None).unwrap(); + let txid = node_b.onchain_payment().send_all_to_address(&addr_a, true, None).await.unwrap(); wait_for_tx(&electrsd.client, txid).await; generate_blocks_and_wait(&bitcoind.client, &electrsd.client, 6).await; @@ -672,11 +677,11 @@ async fn onchain_wallet_recovery() { let original_config = random_config(true); let original_node_entropy = original_config.node_entropy; - let original_node = setup_node(&chain_source, original_config); + let original_node = setup_node(&chain_source, original_config).await; let premine_amount_sat = 100_000; - let addr_1 = original_node.onchain_payment().new_address().unwrap(); + let addr_1 = original_node.onchain_payment().new_address().await.unwrap(); premine_and_distribute_funds( &bitcoind.client, @@ -688,7 +693,7 @@ async fn onchain_wallet_recovery() { original_node.sync_wallets().await.unwrap(); assert_eq!(original_node.list_balances().spendable_onchain_balance_sats, premine_amount_sat); - let addr_2 = original_node.onchain_payment().new_address().unwrap(); + let addr_2 = original_node.onchain_payment().new_address().await.unwrap(); let txid = bitcoind .client @@ -707,14 +712,14 @@ async fn onchain_wallet_recovery() { premine_amount_sat * 2 ); - original_node.stop().unwrap(); + original_node.stop().await.unwrap(); drop(original_node); // Now we start from scratch, only the seed remains the same. let mut recovered_config = random_config(true); recovered_config.node_entropy = original_node_entropy; recovered_config.recovery_mode = true; - let recovered_node = setup_node(&chain_source, recovered_config); + let recovered_node = setup_node(&chain_source, recovered_config).await; recovered_node.sync_wallets().await.unwrap(); assert_eq!( @@ -723,10 +728,10 @@ async fn onchain_wallet_recovery() { ); // Check we sync even when skipping some addresses. - let _addr_3 = recovered_node.onchain_payment().new_address().unwrap(); - let _addr_4 = recovered_node.onchain_payment().new_address().unwrap(); - let _addr_5 = recovered_node.onchain_payment().new_address().unwrap(); - let addr_6 = recovered_node.onchain_payment().new_address().unwrap(); + let _addr_3 = recovered_node.onchain_payment().new_address().await.unwrap(); + let _addr_4 = recovered_node.onchain_payment().new_address().await.unwrap(); + let _addr_5 = recovered_node.onchain_payment().new_address().await.unwrap(); + let addr_6 = recovered_node.onchain_payment().new_address().await.unwrap(); let txid = bitcoind .client @@ -768,7 +773,7 @@ async fn run_rbf_test(is_insert_block: bool) { macro_rules! config_node { ($chain_source:expr, $anchor_channels:expr) => {{ let config_a = random_config($anchor_channels); - let node = setup_node(&$chain_source, config_a); + let node = setup_node(&$chain_source, config_a).await; node }}; } @@ -783,8 +788,10 @@ async fn run_rbf_test(is_insert_block: bool) { premine_blocks(bitcoind, electrs).await; // Helpers declaration before starting the test - let all_addrs = - nodes.iter().map(|node| node.onchain_payment().new_address().unwrap()).collect::>(); + let mut all_addrs = Vec::with_capacity(nodes.len()); + for node in &nodes { + all_addrs.push(node.onchain_payment().new_address().await.unwrap()); + } let amount_sat = 2_100_000; let mut txid; macro_rules! distribute_funds_all_nodes { @@ -871,10 +878,10 @@ async fn run_rbf_test(is_insert_block: bool) { // Check if it is possible to send all funds from the node let mut txids = Vec::new(); let addr = bitcoind.new_address().unwrap(); - nodes.iter().for_each(|node| { - let txid = node.onchain_payment().send_all_to_address(&addr, true, None).unwrap(); + for node in &nodes { + let txid = node.onchain_payment().send_all_to_address(&addr, true, None).await.unwrap(); txids.push(txid); - }); + } for txid in txids { wait_for_tx(electrs, txid).await; } @@ -887,7 +894,7 @@ async fn sign_verify_msg() { let (bitcoind, electrsd) = setup_bitcoind_and_electrsd(); let config = random_config(true); let chain_source = random_chain_source(&bitcoind, &electrsd); - let node = setup_node(&chain_source, config); + let node = setup_node(&chain_source, config).await; // Tests arbitrary message signing and later verification let msg = "OK computer".as_bytes(); @@ -900,7 +907,7 @@ async fn sign_verify_msg() { async fn connection_multi_listen() { let (bitcoind, electrsd) = setup_bitcoind_and_electrsd(); let chain_source = random_chain_source(&bitcoind, &electrsd); - let (node_a, node_b) = setup_two_nodes(&chain_source, false, false, false); + let (node_a, node_b) = setup_two_nodes(&chain_source, false, false, false).await; let node_id_b = node_b.node_id(); @@ -920,7 +927,7 @@ async fn connection_restart_behavior() { async fn do_connection_restart_behavior(persist: bool) { let (bitcoind, electrsd) = setup_bitcoind_and_electrsd(); let chain_source = random_chain_source(&bitcoind, &electrsd); - let (node_a, node_b) = setup_two_nodes(&chain_source, false, false, false); + let (node_a, node_b) = setup_two_nodes(&chain_source, false, false, false).await; let node_id_a = node_a.node_id(); let node_id_b = node_b.node_id(); @@ -939,10 +946,10 @@ async fn do_connection_restart_behavior(persist: bool) { assert!(peer_details_a.is_connected); // Restart nodes. - node_a.stop().unwrap(); - node_b.stop().unwrap(); - node_b.start().unwrap(); - node_a.start().unwrap(); + node_a.stop().await.unwrap(); + node_b.stop().await.unwrap(); + node_b.start().await.unwrap(); + node_a.start().await.unwrap(); // Sleep a bit to allow for the reconnect to happen. tokio::time::sleep(std::time::Duration::from_secs(5)).await; @@ -967,7 +974,7 @@ async fn do_connection_restart_behavior(persist: bool) { async fn concurrent_connections_succeed() { let (bitcoind, electrsd) = setup_bitcoind_and_electrsd(); let chain_source = random_chain_source(&bitcoind, &electrsd); - let (node_a, node_b) = setup_two_nodes(&chain_source, false, true, false); + let (node_a, node_b) = setup_two_nodes(&chain_source, false, true, false).await; let node_a = Arc::new(node_a); let node_b = Arc::new(node_b); @@ -977,19 +984,16 @@ async fn concurrent_connections_succeed() { let mut handles = Vec::new(); for _ in 0..10 { - let thread_node = Arc::clone(&node_a); - let thread_addr = node_addr_b.clone(); - let handle = std::thread::spawn(move || { - tokio::runtime::Runtime::new() - .unwrap() - .block_on(async move { thread_node.connect(node_id_b, thread_addr, false).await }) - .unwrap(); + let task_node = Arc::clone(&node_a); + let task_addr = node_addr_b.clone(); + let handle = tokio::spawn(async move { + task_node.connect(node_id_b, task_addr, false).await.unwrap(); }); handles.push(handle); } for h in handles { - h.join().unwrap(); + h.await.unwrap(); } } @@ -998,10 +1002,10 @@ async fn splice_channel() { let (bitcoind, electrsd) = setup_bitcoind_and_electrsd(); let chain_source = random_chain_source(&bitcoind, &electrsd); - let (node_a, node_b) = setup_two_nodes(&chain_source, false, true, false); + let (node_a, node_b) = setup_two_nodes(&chain_source, false, true, false).await; - let address_a = node_a.onchain_payment().new_address().unwrap(); - let address_b = node_b.onchain_payment().new_address().unwrap(); + let address_a = node_a.onchain_payment().new_address().await.unwrap(); + let address_b = node_b.onchain_payment().new_address().await.unwrap(); let premine_amount_sat = 5_000_000; premine_and_distribute_funds( &bitcoind.client, @@ -1043,7 +1047,7 @@ async fn splice_channel() { assert_eq!(node_b.list_balances().total_lightning_balance_sats, 0); // Test that splicing and payments fail when there are insufficient funds - let address = node_b.onchain_payment().new_address().unwrap(); + let address = node_b.onchain_payment().new_address().await.unwrap(); let amount_msat = 400_000_000; assert_eq!( @@ -1117,7 +1121,7 @@ async fn splice_channel() { ); // Splice-out funds for Node A from the payment sent by Node B - let address = node_a.onchain_payment().new_address().unwrap(); + let address = node_a.onchain_payment().new_address().await.unwrap(); node_a.splice_out(&user_channel_id_a, node_b.node_id(), &address, amount_msat / 1000).unwrap(); let txo = expect_splice_negotiated_event!(node_a, node_b.node_id()); @@ -1152,9 +1156,9 @@ async fn splice_channel() { async fn simple_bolt12_send_receive() { let (bitcoind, electrsd) = setup_bitcoind_and_electrsd(); let chain_source = random_chain_source(&bitcoind, &electrsd); - let (node_a, node_b) = setup_two_nodes(&chain_source, false, true, false); + let (node_a, node_b) = setup_two_nodes(&chain_source, false, true, false).await; - let address_a = node_a.onchain_payment().new_address().unwrap(); + let address_a = node_a.onchain_payment().new_address().await.unwrap(); let premine_amount_sat = 5_000_000; premine_and_distribute_funds( &bitcoind.client, @@ -1203,7 +1207,7 @@ async fn simple_bolt12_send_receive() { bolt12_invoice.is_some(), "bolt12_invoice should be present for BOLT12 payments" ); - node_a.event_handled().unwrap(); + node_a.event_handled().await.unwrap(); }, ref e => panic!("{} got unexpected event!: {:?}", "node_a", e), } @@ -1415,32 +1419,32 @@ async fn async_payment() { config_sender.log_writer = TestLogWriter::Custom(Arc::new(MultiNodeLogger::new("sender ".to_string()))); config_sender.async_payments_role = Some(AsyncPaymentsRole::Client); - let node_sender = setup_node(&chain_source, config_sender); + let node_sender = setup_node(&chain_source, config_sender).await; let mut config_sender_lsp = random_config(true); config_sender_lsp.log_writer = TestLogWriter::Custom(Arc::new(MultiNodeLogger::new("sender_lsp ".to_string()))); config_sender_lsp.async_payments_role = Some(AsyncPaymentsRole::Server); - let node_sender_lsp = setup_node(&chain_source, config_sender_lsp); + let node_sender_lsp = setup_node(&chain_source, config_sender_lsp).await; let mut config_receiver_lsp = random_config(true); config_receiver_lsp.log_writer = TestLogWriter::Custom(Arc::new(MultiNodeLogger::new("receiver_lsp".to_string()))); config_receiver_lsp.async_payments_role = Some(AsyncPaymentsRole::Server); - let node_receiver_lsp = setup_node(&chain_source, config_receiver_lsp); + let node_receiver_lsp = setup_node(&chain_source, config_receiver_lsp).await; let mut config_receiver = random_config(true); config_receiver.node_config.listening_addresses = None; config_receiver.node_config.node_alias = None; config_receiver.log_writer = TestLogWriter::Custom(Arc::new(MultiNodeLogger::new("receiver ".to_string()))); - let node_receiver = setup_node(&chain_source, config_receiver); + let node_receiver = setup_node(&chain_source, config_receiver).await; - let address_sender = node_sender.onchain_payment().new_address().unwrap(); - let address_sender_lsp = node_sender_lsp.onchain_payment().new_address().unwrap(); - let address_receiver_lsp = node_receiver_lsp.onchain_payment().new_address().unwrap(); - let address_receiver = node_receiver.onchain_payment().new_address().unwrap(); + let address_sender = node_sender.onchain_payment().new_address().await.unwrap(); + let address_sender_lsp = node_sender_lsp.onchain_payment().new_address().await.unwrap(); + let address_receiver_lsp = node_receiver_lsp.onchain_payment().new_address().await.unwrap(); + let address_receiver = node_receiver.onchain_payment().new_address().await.unwrap(); let premine_amount_sat = 4_000_000; premine_and_distribute_funds( &bitcoind.client, @@ -1523,7 +1527,7 @@ async fn async_payment() { tokio::time::sleep(std::time::Duration::from_millis(100)).await; }; - node_receiver.stop().unwrap(); + node_receiver.stop().await.unwrap(); let payment_id = node_sender .bolt12_payment() @@ -1534,7 +1538,7 @@ async fn async_payment() { // Sleep to allow the payment reach a state where the htlc is held and waiting for the receiver to come online. tokio::time::sleep(std::time::Duration::from_millis(3000)).await; - node_receiver.start().unwrap(); + node_receiver.start().await.unwrap(); expect_payment_successful_event!(node_sender, Some(payment_id), None); } @@ -1568,10 +1572,10 @@ async fn test_node_announcement_propagation() { config_b.node_config.listening_addresses = Some(node_b_listening_addresses.clone()); config_b.node_config.announcement_addresses = None; - let node_a = setup_node(&chain_source, config_a); - let node_b = setup_node(&chain_source, config_b); + let node_a = setup_node(&chain_source, config_a).await; + let node_b = setup_node(&chain_source, config_b).await; - let address_a = node_a.onchain_payment().new_address().unwrap(); + let address_a = node_a.onchain_payment().new_address().await.unwrap(); let premine_amount_sat = 5_000_000; premine_and_distribute_funds( &bitcoind.client, @@ -1636,9 +1640,9 @@ async fn generate_bip21_uri() { let (bitcoind, electrsd) = setup_bitcoind_and_electrsd(); let chain_source = random_chain_source(&bitcoind, &electrsd); - let (node_a, node_b) = setup_two_nodes(&chain_source, false, true, false); + let (node_a, node_b) = setup_two_nodes(&chain_source, false, true, false).await; - let address_a = node_a.onchain_payment().new_address().unwrap(); + let address_a = node_a.onchain_payment().new_address().await.unwrap(); let premined_sats = 5_000_000; let expected_amount_sats = 100_000; @@ -1693,9 +1697,9 @@ async fn unified_send_receive_bip21_uri() { let (bitcoind, electrsd) = setup_bitcoind_and_electrsd(); let chain_source = random_chain_source(&bitcoind, &electrsd); - let (node_a, node_b) = setup_two_nodes(&chain_source, false, true, false); + let (node_a, node_b) = setup_two_nodes(&chain_source, false, true, false).await; - let address_a = node_a.onchain_payment().new_address().unwrap(); + let address_a = node_a.onchain_payment().new_address().await.unwrap(); let premined_sats = 5_000_000; premine_and_distribute_funds( @@ -1835,8 +1839,8 @@ async fn do_lsps2_client_service_integration(client_trusts_lsp: bool) { setup_builder!(service_builder, service_config.node_config); service_builder.set_chain_source_esplora(esplora_url.clone(), Some(sync_config)); service_builder.set_liquidity_provider_lsps2(lsps2_service_config); - let service_node = service_builder.build(service_config.node_entropy.into()).unwrap(); - service_node.start().unwrap(); + let service_node = service_builder.build(service_config.node_entropy.into()).await.unwrap(); + service_node.start().await.unwrap(); let service_node_id = service_node.node_id(); let service_addr = service_node.listening_addresses().unwrap().first().unwrap().clone(); @@ -1845,18 +1849,18 @@ async fn do_lsps2_client_service_integration(client_trusts_lsp: bool) { setup_builder!(client_builder, client_config.node_config); client_builder.set_chain_source_esplora(esplora_url.clone(), Some(sync_config)); client_builder.set_liquidity_source_lsps2(service_node_id, service_addr, None); - let client_node = client_builder.build(client_config.node_entropy.into()).unwrap(); - client_node.start().unwrap(); + let client_node = client_builder.build(client_config.node_entropy.into()).await.unwrap(); + client_node.start().await.unwrap(); let payer_config = random_config(true); setup_builder!(payer_builder, payer_config.node_config); payer_builder.set_chain_source_esplora(esplora_url.clone(), Some(sync_config)); - let payer_node = payer_builder.build(payer_config.node_entropy.into()).unwrap(); - payer_node.start().unwrap(); + let payer_node = payer_builder.build(payer_config.node_entropy.into()).await.unwrap(); + payer_node.start().await.unwrap(); - let service_addr = service_node.onchain_payment().new_address().unwrap(); - let client_addr = client_node.onchain_payment().new_address().unwrap(); - let payer_addr = payer_node.onchain_payment().new_address().unwrap(); + let service_addr = service_node.onchain_payment().new_address().await.unwrap(); + let client_addr = client_node.onchain_payment().new_address().await.unwrap(); + let payer_addr = payer_node.onchain_payment().new_address().await.unwrap(); let premine_amount_sat = 10_000_000; @@ -2047,7 +2051,7 @@ async fn facade_logging() { config.log_writer = TestLogWriter::LogFacade; println!("== Facade logging starts =="); - let _node = setup_node(&chain_source, config); + let _node = setup_node(&chain_source, config).await; assert!(!logger.retrieve_logs().is_empty()); for (_, entry) in logger.retrieve_logs().iter().enumerate() { @@ -2059,9 +2063,9 @@ async fn facade_logging() { async fn spontaneous_send_with_custom_preimage() { let (bitcoind, electrsd) = setup_bitcoind_and_electrsd(); let chain_source = random_chain_source(&bitcoind, &electrsd); - let (node_a, node_b) = setup_two_nodes(&chain_source, false, true, false); + let (node_a, node_b) = setup_two_nodes(&chain_source, false, true, false).await; - let address_a = node_a.onchain_payment().new_address().unwrap(); + let address_a = node_a.onchain_payment().new_address().await.unwrap(); let premine_sat = 1_000_000; premine_and_distribute_funds( &bitcoind.client, @@ -2130,8 +2134,8 @@ async fn drop_in_async_context() { let (bitcoind, electrsd) = setup_bitcoind_and_electrsd(); let chain_source = random_chain_source(&bitcoind, &electrsd); let config = random_config(true); - let node = setup_node(&chain_source, config); - node.stop().unwrap(); + let node = setup_node(&chain_source, config).await; + node.stop().await.unwrap(); } #[tokio::test(flavor = "multi_thread", worker_threads = 1)] @@ -2164,8 +2168,8 @@ async fn lsps2_client_trusts_lsp() { setup_builder!(service_builder, service_config.node_config); service_builder.set_chain_source_esplora(esplora_url.clone(), Some(sync_config)); service_builder.set_liquidity_provider_lsps2(lsps2_service_config); - let service_node = service_builder.build(service_config.node_entropy.into()).unwrap(); - service_node.start().unwrap(); + let service_node = service_builder.build(service_config.node_entropy.into()).await.unwrap(); + service_node.start().await.unwrap(); let service_node_id = service_node.node_id(); let service_addr = service_node.listening_addresses().unwrap().first().unwrap().clone(); @@ -2173,19 +2177,19 @@ async fn lsps2_client_trusts_lsp() { setup_builder!(client_builder, client_config.node_config); client_builder.set_chain_source_esplora(esplora_url.clone(), Some(sync_config)); client_builder.set_liquidity_source_lsps2(service_node_id, service_addr.clone(), None); - let client_node = client_builder.build(client_config.node_entropy.into()).unwrap(); - client_node.start().unwrap(); + let client_node = client_builder.build(client_config.node_entropy.into()).await.unwrap(); + client_node.start().await.unwrap(); let client_node_id = client_node.node_id(); let payer_config = random_config(true); setup_builder!(payer_builder, payer_config.node_config); payer_builder.set_chain_source_esplora(esplora_url.clone(), Some(sync_config)); - let payer_node = payer_builder.build(payer_config.node_entropy.into()).unwrap(); - payer_node.start().unwrap(); + let payer_node = payer_builder.build(payer_config.node_entropy.into()).await.unwrap(); + payer_node.start().await.unwrap(); - let service_addr_onchain = service_node.onchain_payment().new_address().unwrap(); - let client_addr_onchain = client_node.onchain_payment().new_address().unwrap(); - let payer_addr_onchain = payer_node.onchain_payment().new_address().unwrap(); + let service_addr_onchain = service_node.onchain_payment().new_address().await.unwrap(); + let client_addr_onchain = client_node.onchain_payment().new_address().await.unwrap(); + let payer_addr_onchain = payer_node.onchain_payment().new_address().await.unwrap(); let premine_amount_sat = 10_000_000; @@ -2341,8 +2345,8 @@ async fn lsps2_lsp_trusts_client_but_client_does_not_claim() { setup_builder!(service_builder, service_config.node_config); service_builder.set_chain_source_esplora(esplora_url.clone(), Some(sync_config)); service_builder.set_liquidity_provider_lsps2(lsps2_service_config); - let service_node = service_builder.build(service_config.node_entropy.into()).unwrap(); - service_node.start().unwrap(); + let service_node = service_builder.build(service_config.node_entropy.into()).await.unwrap(); + service_node.start().await.unwrap(); let service_node_id = service_node.node_id(); let service_addr = service_node.listening_addresses().unwrap().first().unwrap().clone(); @@ -2351,20 +2355,20 @@ async fn lsps2_lsp_trusts_client_but_client_does_not_claim() { setup_builder!(client_builder, client_config.node_config); client_builder.set_chain_source_esplora(esplora_url.clone(), Some(sync_config)); client_builder.set_liquidity_source_lsps2(service_node_id, service_addr.clone(), None); - let client_node = client_builder.build(client_config.node_entropy.into()).unwrap(); - client_node.start().unwrap(); + let client_node = client_builder.build(client_config.node_entropy.into()).await.unwrap(); + client_node.start().await.unwrap(); let client_node_id = client_node.node_id(); let payer_config = random_config(true); setup_builder!(payer_builder, payer_config.node_config); payer_builder.set_chain_source_esplora(esplora_url.clone(), Some(sync_config)); - let payer_node = payer_builder.build(payer_config.node_entropy.into()).unwrap(); - payer_node.start().unwrap(); + let payer_node = payer_builder.build(payer_config.node_entropy.into()).await.unwrap(); + payer_node.start().await.unwrap(); - let service_addr_onchain = service_node.onchain_payment().new_address().unwrap(); - let client_addr_onchain = client_node.onchain_payment().new_address().unwrap(); - let payer_addr_onchain = payer_node.onchain_payment().new_address().unwrap(); + let service_addr_onchain = service_node.onchain_payment().new_address().await.unwrap(); + let client_addr_onchain = client_node.onchain_payment().new_address().await.unwrap(); + let payer_addr_onchain = payer_node.onchain_payment().new_address().await.unwrap(); let premine_amount_sat = 10_000_000; @@ -2455,14 +2459,14 @@ async fn payment_persistence_after_restart() { let payment_amount_msat = 1_000_000; // 1000 sats per payment { - let node_a = setup_node(&chain_source, config_a.clone()); + let node_a = setup_node(&chain_source, config_a.clone()).await; println!("\n== Node B =="); let config_b = random_config(true); - let node_b = setup_node(&chain_source, config_b); + let node_b = setup_node(&chain_source, config_b).await; - let addr_a = node_a.onchain_payment().new_address().unwrap(); - let addr_b = node_b.onchain_payment().new_address().unwrap(); + let addr_a = node_a.onchain_payment().new_address().await.unwrap(); + let addr_b = node_b.onchain_payment().new_address().await.unwrap(); // Premine sufficient funds for a large channel and many payments let premine_amount_sat = 10_000_000; @@ -2522,13 +2526,13 @@ async fn payment_persistence_after_restart() { // Shut down both nodes println!("\nShutting down nodes..."); - node_a.stop().unwrap(); - node_b.stop().unwrap(); + node_a.stop().await.unwrap(); + node_b.stop().await.unwrap(); } // Restart node_a with the same config println!("\nRestarting node A..."); - let restarted_node_a = setup_node(&chain_source, config_a); + let restarted_node_a = setup_node(&chain_source, config_a).await; // Assert all 200 payments are still in the store let outbound_payments_after = restarted_node_a @@ -2562,7 +2566,7 @@ async fn payment_persistence_after_restart() { outbound_payments_after.len() ); - restarted_node_a.stop().unwrap(); + restarted_node_a.stop().await.unwrap(); } enum OldLdkVersion { @@ -2675,9 +2679,9 @@ async fn do_persistence_backwards_compatibility(version: OldLdkVersion) { let node_entropy = NodeEntropy::from_seed_bytes(seed_bytes.to_vec()).unwrap(); #[cfg(not(feature = "uniffi"))] let node_entropy = NodeEntropy::from_seed_bytes(seed_bytes); - let node_new = builder_new.build(node_entropy.into()).unwrap(); + let node_new = builder_new.build(node_entropy.into()).await.unwrap(); - node_new.start().unwrap(); + node_new.start().await.unwrap(); node_new.sync_wallets().await.unwrap(); let new_balance = node_new.list_balances().spendable_onchain_balance_sats; @@ -2686,7 +2690,7 @@ async fn do_persistence_backwards_compatibility(version: OldLdkVersion) { assert_eq!(old_node_id, new_node_id); assert_eq!(old_balance, new_balance); - node_new.stop().unwrap(); + node_new.stop().await.unwrap(); } #[tokio::test(flavor = "multi_thread", worker_threads = 1)] @@ -2724,9 +2728,9 @@ async fn fs_store_persistence_backwards_compatibility() { let node_entropy = NodeEntropy::from_seed_bytes(seed_bytes.to_vec()).unwrap(); #[cfg(not(feature = "uniffi"))] let node_entropy = NodeEntropy::from_seed_bytes(seed_bytes); - let node_new = builder_new.build_with_fs_store(node_entropy.into()).unwrap(); + let node_new = builder_new.build_with_fs_store(node_entropy.into()).await.unwrap(); - node_new.start().unwrap(); + node_new.start().await.unwrap(); node_new.sync_wallets().await.unwrap(); let new_balance = node_new.list_balances().spendable_onchain_balance_sats; @@ -2735,18 +2739,18 @@ async fn fs_store_persistence_backwards_compatibility() { assert_eq!(old_node_id, new_node_id); assert_eq!(old_balance, new_balance); - node_new.stop().unwrap(); + node_new.stop().await.unwrap(); } #[tokio::test(flavor = "multi_thread", worker_threads = 1)] async fn onchain_fee_bump_rbf() { let (bitcoind, electrsd) = setup_bitcoind_and_electrsd(); let chain_source = random_chain_source(&bitcoind, &electrsd); - let (node_a, node_b) = setup_two_nodes(&chain_source, false, true, false); + let (node_a, node_b) = setup_two_nodes(&chain_source, false, true, false).await; // Fund both nodes - let addr_a = node_a.onchain_payment().new_address().unwrap(); - let addr_b = node_b.onchain_payment().new_address().unwrap(); + let addr_a = node_a.onchain_payment().new_address().await.unwrap(); + let addr_b = node_b.onchain_payment().new_address().await.unwrap(); let premine_amount_sat = 500_000; premine_and_distribute_funds( @@ -2763,7 +2767,7 @@ async fn onchain_fee_bump_rbf() { // Send a transaction from node_b to node_a that we'll later bump let amount_to_send_sats = 100_000; let txid = - node_b.onchain_payment().send_to_address(&addr_a, amount_to_send_sats, None).unwrap(); + node_b.onchain_payment().send_to_address(&addr_a, amount_to_send_sats, None).await.unwrap(); wait_for_tx(&electrsd.client, txid).await; // Give the chain source time to index the unconfirmed transaction before syncing. // Without this, Esplora may not yet have the tx, causing sync to miss it and @@ -2886,10 +2890,10 @@ async fn onchain_fee_bump_rbf() { async fn open_channel_with_all_with_anchors() { let (bitcoind, electrsd) = setup_bitcoind_and_electrsd(); let chain_source = random_chain_source(&bitcoind, &electrsd); - let (node_a, node_b) = setup_two_nodes(&chain_source, false, true, false); + let (node_a, node_b) = setup_two_nodes(&chain_source, false, true, false).await; - let addr_a = node_a.onchain_payment().new_address().unwrap(); - let addr_b = node_b.onchain_payment().new_address().unwrap(); + let addr_a = node_a.onchain_payment().new_address().await.unwrap(); + let addr_b = node_b.onchain_payment().new_address().await.unwrap(); let premine_amount_sat = 1_000_000; @@ -2931,18 +2935,18 @@ async fn open_channel_with_all_with_anchors() { assert_eq!(channel.counterparty_node_id, node_b.node_id()); assert_eq!(channel.funding_txo.unwrap(), funding_txo); - node_a.stop().unwrap(); - node_b.stop().unwrap(); + node_a.stop().await.unwrap(); + node_b.stop().await.unwrap(); } #[tokio::test(flavor = "multi_thread", worker_threads = 1)] async fn open_channel_with_all_without_anchors() { let (bitcoind, electrsd) = setup_bitcoind_and_electrsd(); let chain_source = random_chain_source(&bitcoind, &electrsd); - let (node_a, node_b) = setup_two_nodes(&chain_source, false, false, false); + let (node_a, node_b) = setup_two_nodes(&chain_source, false, false, false).await; - let addr_a = node_a.onchain_payment().new_address().unwrap(); - let addr_b = node_b.onchain_payment().new_address().unwrap(); + let addr_a = node_a.onchain_payment().new_address().await.unwrap(); + let addr_b = node_b.onchain_payment().new_address().await.unwrap(); let premine_amount_sat = 1_000_000; @@ -2982,18 +2986,18 @@ async fn open_channel_with_all_without_anchors() { assert_eq!(channel.counterparty_node_id, node_b.node_id()); assert_eq!(channel.funding_txo.unwrap(), funding_txo); - node_a.stop().unwrap(); - node_b.stop().unwrap(); + node_a.stop().await.unwrap(); + node_b.stop().await.unwrap(); } #[tokio::test(flavor = "multi_thread", worker_threads = 1)] async fn splice_in_with_all_balance() { let (bitcoind, electrsd) = setup_bitcoind_and_electrsd(); let chain_source = random_chain_source(&bitcoind, &electrsd); - let (node_a, node_b) = setup_two_nodes(&chain_source, false, true, false); + let (node_a, node_b) = setup_two_nodes(&chain_source, false, true, false).await; - let addr_a = node_a.onchain_payment().new_address().unwrap(); - let addr_b = node_b.onchain_payment().new_address().unwrap(); + let addr_a = node_a.onchain_payment().new_address().await.unwrap(); + let addr_b = node_b.onchain_payment().new_address().await.unwrap(); let premine_amount_sat = 5_000_000; let channel_amount_sat = 1_000_000; @@ -3060,6 +3064,6 @@ async fn splice_in_with_all_balance() { "Remaining balance {remaining_balance} should be close to the anchor reserve {anchor_reserve_sat}" ); - node_a.stop().unwrap(); - node_b.stop().unwrap(); + node_a.stop().await.unwrap(); + node_b.stop().await.unwrap(); } diff --git a/tests/integration_tests_vss_no_auth.rs b/tests/integration_tests_vss_no_auth.rs index f1bd3a24d2..eeea450e7f 100644 --- a/tests/integration_tests_vss_no_auth.rs +++ b/tests/integration_tests_vss_no_auth.rs @@ -60,10 +60,12 @@ async fn vss_v0_schema_backwards_compatibility() { let node_id = node_old.node_id(); // Workaround necessary as v0.6.2's VSS runtime wasn't dropsafe in a tokio context. - tokio::task::block_in_place(move || { + tokio::task::spawn_blocking(move || { node_old.stop().unwrap(); drop(node_old); - }); + }) + .await + .unwrap(); (balance, node_id) }; @@ -81,9 +83,10 @@ async fn vss_v0_schema_backwards_compatibility() { store_id, HashMap::new(), ) + .await .unwrap(); - node_new.start().unwrap(); + node_new.start().await.unwrap(); node_new.sync_wallets().await.unwrap(); let new_balance = node_new.list_balances().spendable_onchain_balance_sats; @@ -92,5 +95,5 @@ async fn vss_v0_schema_backwards_compatibility() { assert_eq!(old_node_id, new_node_id); assert_eq!(old_balance, new_balance); - node_new.stop().unwrap(); + node_new.stop().await.unwrap(); } diff --git a/tests/reorg_test.rs b/tests/reorg_test.rs index e177808a88..d405fb2641 100644 --- a/tests/reorg_test.rs +++ b/tests/reorg_test.rs @@ -17,11 +17,12 @@ proptest! { #![proptest_config(proptest::test_runner::Config::with_cases(5))] #[test] fn reorg_test(reorg_depth in 1..=6usize, force_close in prop::bool::ANY) { - let rt = tokio::runtime::Builder::new_multi_thread() - .enable_all() - .build() - .unwrap(); - rt.block_on(async { + let rt = tokio::runtime::Builder::new_multi_thread() + .enable_all() + .build() + .unwrap(); + let (test_done, test_result) = std::sync::mpsc::channel(); + rt.spawn(async move { let (bitcoind, electrsd) = setup_bitcoind_and_electrsd(); let chain_source_a = random_chain_source(&bitcoind, &electrsd); @@ -31,7 +32,7 @@ proptest! { macro_rules! config_node { ($chain_source: expr, $anchor_channels: expr) => {{ let config_a = random_config($anchor_channels); - let node = setup_node(&$chain_source, config_a); + let node = setup_node(&$chain_source, config_a).await; node }}; } @@ -50,10 +51,12 @@ proptest! { }}; } - let amount_sat = 2_100_000; - let addr_nodes = - nodes.iter().map(|node| node.onchain_payment().new_address().unwrap()).collect::>(); - premine_and_distribute_funds(bitcoind, electrs, addr_nodes, Amount::from_sat(amount_sat)).await; + let amount_sat = 2_100_000; + let mut addr_nodes = Vec::with_capacity(nodes.len()); + for node in &nodes { + addr_nodes.push(node.onchain_payment().new_address().await.unwrap()); + } + premine_and_distribute_funds(bitcoind, electrs, addr_nodes, Amount::from_sat(amount_sat)).await; macro_rules! sync_wallets { () => { @@ -88,7 +91,7 @@ proptest! { for _ in 0..$expected { match $node.next_event_async().await { Event::ChannelReady { user_channel_id, counterparty_node_id, .. } => { - $node.event_handled().unwrap(); + $node.event_handled().await.unwrap(); user_channels.insert(counterparty_node_id, user_channel_id); }, other => panic!("Unexpected event: {:?}", other), @@ -146,11 +149,12 @@ proptest! { if force_close { for node in &nodes { - node.sync_wallets().await.unwrap(); - // If there is no more balance, there is nothing to process here. - if node.list_balances().lightning_balances.len() < 1 { - return; - } + node.sync_wallets().await.unwrap(); + // If there is no more balance, there is nothing to process here. + if node.list_balances().lightning_balances.len() < 1 { + test_done.send(()).unwrap(); + return; + } match node.list_balances().lightning_balances[0] { LightningBalance::ClaimableAwaitingConfirmations { confirmation_height, @@ -199,6 +203,8 @@ proptest! { assert_eq!(node.next_event(), None); }); - }) + test_done.send(()).unwrap(); + }); + test_result.recv().unwrap(); } } From f58384d9009ba6425ec402272c5e2e1a1b10c9c1 Mon Sep 17 00:00:00 2001 From: Elias Rohrer Date: Mon, 8 Jun 2026 17:46:13 +0200 Subject: [PATCH 5/5] Document runtime assumptions Describe the Tokio runtime expectations for LDK Node callers so async API users know when runtime handles are reused and why runtime worker threads must remain available for node progress. Co-Authored-By: HAL 9000 --- src/lib.rs | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/src/lib.rs b/src/lib.rs index 65bf822b87..ebb102cb5d 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -16,6 +16,16 @@ //! Lightning node with an integrated on-chain wallet. While minimalism is at its core, LDK Node //! aims to be sufficiently modular and configurable to be useful for a variety of use cases. //! +//! ## Runtime Assumptions +//! +//! LDK Node's Rust API is async and expects its futures to be polled by a Tokio runtime. When a +//! node is built from within an existing Tokio context, LDK Node reuses that runtime handle for +//! internally spawned work; otherwise it creates an owned multi-thread Tokio runtime for its +//! background tasks. Callers should keep the runtime threads available to drive network, timer, and +//! persistence futures while node operations are in progress. Long synchronous work should run +//! outside those worker threads, or on a dedicated blocking pool, so it does not prevent LDK Node's +//! I/O and storage tasks from making progress. +//! //! ## Getting Started //! //! The primary abstraction of the library is the [`Node`], which can be retrieved by setting up