diff --git a/lightning/Cargo.toml b/lightning/Cargo.toml index 94919efe66d..169473b97cd 100644 --- a/lightning/Cargo.toml +++ b/lightning/Cargo.toml @@ -17,7 +17,7 @@ rustdoc-args = ["--cfg", "docsrs"] [features] # Internal test utilities exposed to other repo crates _test_utils = ["regex", "bitcoin/bitcoinconsensus", "lightning-types/_test_utils"] -_externalize_tests = ["inventory", "_test_utils"] +_externalize_tests = ["inventory", "_test_utils", "tokio"] # Allow signing of local transactions that may have been revoked or will be revoked, for functional testing (e.g. justice tx handling). # This is unsafe to use in production because it may result in the counterparty publishing taking our funds. unsafe_revoked_tx_signing = [] @@ -48,12 +48,14 @@ backtrace = { version = "0.3", optional = true } libm = { version = "0.2", default-features = false } inventory = { version = "0.3", optional = true } +tokio = { version = "1.35", features = [ "macros", "rt" ], default-features = false, optional = true } [dev-dependencies] regex = "1.5.6" lightning-types = { version = "0.3.0", path = "../lightning-types", features = ["_test_utils"] } lightning-macros = { path = "../lightning-macros" } parking_lot = { version = "0.12", default-features = false } +tokio = { version = "1.35", features = [ "macros", "rt" ], default-features = false } [dev-dependencies.bitcoin] version = "0.32.2" diff --git a/lightning/src/events/bump_transaction.rs b/lightning/src/events/bump_transaction.rs index 6ecd6075712..d797ee297c3 100644 --- a/lightning/src/events/bump_transaction.rs +++ b/lightning/src/events/bump_transaction.rs @@ -30,6 +30,7 @@ use crate::sign::{ ChannelDerivationParameters, HTLCDescriptor, SignerProvider, P2WPKH_WITNESS_WEIGHT, }; use crate::sync::Mutex; +use crate::util::async_poll::{AsyncResult, MaybeSend, MaybeSync}; use crate::util::logger::Logger; use bitcoin::amount::Amount; @@ -346,21 +347,38 @@ pub trait CoinSelectionSource { /// other claims, implementations must be willing to double spend their UTXOs. The choice of /// which UTXOs to double spend is left to the implementation, but it must strive to keep the /// set of other claims being double spent to a minimum. - fn select_confirmed_utxos( - &self, claim_id: ClaimId, must_spend: Vec, must_pay_to: &[TxOut], + fn select_confirmed_utxos<'a>( + &'a self, claim_id: ClaimId, must_spend: Vec, must_pay_to: &'a [TxOut], target_feerate_sat_per_1000_weight: u32, - ) -> Result; + ) -> AsyncResult<'a, CoinSelection>; /// Signs and provides the full witness for all inputs within the transaction known to the /// trait (i.e., any provided via [`CoinSelectionSource::select_confirmed_utxos`]). /// /// If your wallet does not support signing PSBTs you can call `psbt.extract_tx()` to get the /// unsigned transaction and then sign it with your wallet. - fn sign_psbt(&self, psbt: Psbt) -> Result; + fn sign_psbt<'a>(&'a self, psbt: Psbt) -> AsyncResult<'a, Transaction>; } /// An alternative to [`CoinSelectionSource`] that can be implemented and used along [`Wallet`] to /// provide a default implementation to [`CoinSelectionSource`]. pub trait WalletSource { + /// Returns all UTXOs, with at least 1 confirmation each, that are available to spend. + fn list_confirmed_utxos<'a>(&'a self) -> AsyncResult<'a, Vec>; + /// Returns a script to use for change above dust resulting from a successful coin selection + /// attempt. + fn get_change_script<'a>(&self) -> AsyncResult<'a, ScriptBuf>; + /// Signs and provides the full [`TxIn::script_sig`] and [`TxIn::witness`] for all inputs within + /// the transaction known to the wallet (i.e., any provided via + /// [`WalletSource::list_confirmed_utxos`]). + /// + /// If your wallet does not support signing PSBTs you can call `psbt.extract_tx()` to get the + /// unsigned transaction and then sign it with your wallet. + fn sign_psbt<'a>(&self, psbt: Psbt) -> AsyncResult<'a, Transaction>; +} + +/// A synchronous version of the [`WalletSource`] trait. Implementations of this trait should be wrapped in +/// WalletSourceSyncWrapper for it to be used within rust-lightning. +pub trait WalletSourceSync { /// Returns all UTXOs, with at least 1 confirmation each, that are available to spend. fn list_confirmed_utxos(&self) -> Result, ()>; /// Returns a script to use for change above dust resulting from a successful coin selection @@ -375,13 +393,54 @@ pub trait WalletSource { fn sign_psbt(&self, psbt: Psbt) -> Result; } +/// A wrapper around [`WalletSourceSync`] to allow for async calls. +pub struct WalletSourceSyncWrapper(T) +where + T::Target: WalletSourceSync; + +impl WalletSourceSyncWrapper +where + T::Target: WalletSourceSync, +{ + /// Creates a new [`WalletSourceSyncWrapper`]. + pub fn new(source: T) -> Self { + Self(source) + } +} +impl WalletSource for WalletSourceSyncWrapper +where + T::Target: WalletSourceSync, +{ + /// Returns all UTXOs, with at least 1 confirmation each, that are available to spend. Wraps + /// [`WalletSourceSync::list_confirmed_utxos`]. + fn list_confirmed_utxos<'a>(&'a self) -> AsyncResult<'a, Vec> { + let utxos = self.0.list_confirmed_utxos(); + Box::pin(async move { utxos }) + } + + /// Returns a script to use for change above dust resulting from a successful coin selection attempt. Wraps + /// [`WalletSourceSync::get_change_script`]. + fn get_change_script<'a>(&self) -> AsyncResult<'a, ScriptBuf> { + let script = self.0.get_change_script(); + Box::pin(async move { script }) + } + + /// Signs and provides the full [`TxIn::script_sig`] and [`TxIn::witness`] for all inputs within the transaction + /// known to the wallet (i.e., any provided via [`WalletSource::list_confirmed_utxos`]). Wraps + /// [`WalletSourceSync::sign_psbt`]. + fn sign_psbt<'a>(&self, psbt: Psbt) -> AsyncResult<'a, Transaction> { + let signed_psbt = self.0.sign_psbt(psbt); + Box::pin(async move { signed_psbt }) + } +} + /// A wrapper over [`WalletSource`] that implements [`CoinSelection`] by preferring UTXOs that would /// avoid conflicting double spends. If not enough UTXOs are available to do so, conflicting double /// spends may happen. -pub struct Wallet +pub struct Wallet where - W::Target: WalletSource, - L::Target: Logger, + W::Target: WalletSource + MaybeSend, + L::Target: Logger + MaybeSend, { source: W, logger: L, @@ -391,10 +450,10 @@ where locked_utxos: Mutex>, } -impl Wallet +impl Wallet where - W::Target: WalletSource, - L::Target: Logger, + W::Target: WalletSource + MaybeSend, + L::Target: Logger + MaybeSend, { /// Returns a new instance backed by the given [`WalletSource`] that serves as an implementation /// of [`CoinSelectionSource`]. @@ -410,77 +469,81 @@ where /// `tolerate_high_network_feerates` is set, we'll attempt to spend UTXOs that contribute at /// least 1 satoshi at the current feerate, otherwise, we'll only attempt to spend those which /// contribute at least twice their fee. - fn select_confirmed_utxos_internal( + async fn select_confirmed_utxos_internal( &self, utxos: &[Utxo], claim_id: ClaimId, force_conflicting_utxo_spend: bool, tolerate_high_network_feerates: bool, target_feerate_sat_per_1000_weight: u32, preexisting_tx_weight: u64, input_amount_sat: Amount, target_amount_sat: Amount, ) -> Result { - let mut locked_utxos = self.locked_utxos.lock().unwrap(); - let mut eligible_utxos = utxos - .iter() - .filter_map(|utxo| { - if let Some(utxo_claim_id) = locked_utxos.get(&utxo.outpoint) { - if *utxo_claim_id != claim_id && !force_conflicting_utxo_spend { + let mut selected_amount; + let mut total_fees; + let mut selected_utxos; + { + let mut locked_utxos = self.locked_utxos.lock().unwrap(); + let mut eligible_utxos = utxos + .iter() + .filter_map(|utxo| { + if let Some(utxo_claim_id) = locked_utxos.get(&utxo.outpoint) { + if *utxo_claim_id != claim_id && !force_conflicting_utxo_spend { + log_trace!( + self.logger, + "Skipping UTXO {} to prevent conflicting spend", + utxo.outpoint + ); + return None; + } + } + let fee_to_spend_utxo = Amount::from_sat(fee_for_weight( + target_feerate_sat_per_1000_weight, + BASE_INPUT_WEIGHT + utxo.satisfaction_weight, + )); + let should_spend = if tolerate_high_network_feerates { + utxo.output.value > fee_to_spend_utxo + } else { + utxo.output.value >= fee_to_spend_utxo * 2 + }; + if should_spend { + Some((utxo, fee_to_spend_utxo)) + } else { log_trace!( self.logger, - "Skipping UTXO {} to prevent conflicting spend", + "Skipping UTXO {} due to dust proximity after spend", utxo.outpoint ); - return None; + None } - } - let fee_to_spend_utxo = Amount::from_sat(fee_for_weight( - target_feerate_sat_per_1000_weight, - BASE_INPUT_WEIGHT + utxo.satisfaction_weight, - )); - let should_spend = if tolerate_high_network_feerates { - utxo.output.value > fee_to_spend_utxo - } else { - utxo.output.value >= fee_to_spend_utxo * 2 - }; - if should_spend { - Some((utxo, fee_to_spend_utxo)) - } else { - log_trace!( - self.logger, - "Skipping UTXO {} due to dust proximity after spend", - utxo.outpoint - ); - None - } - }) - .collect::>(); - eligible_utxos.sort_unstable_by_key(|(utxo, _)| utxo.output.value); + }) + .collect::>(); + eligible_utxos.sort_unstable_by_key(|(utxo, _)| utxo.output.value); - let mut selected_amount = input_amount_sat; - let mut total_fees = Amount::from_sat(fee_for_weight( - target_feerate_sat_per_1000_weight, - preexisting_tx_weight, - )); - let mut selected_utxos = Vec::new(); - for (utxo, fee_to_spend_utxo) in eligible_utxos { - if selected_amount >= target_amount_sat + total_fees { - break; + selected_amount = input_amount_sat; + total_fees = Amount::from_sat(fee_for_weight( + target_feerate_sat_per_1000_weight, + preexisting_tx_weight, + )); + selected_utxos = Vec::new(); + for (utxo, fee_to_spend_utxo) in eligible_utxos { + if selected_amount >= target_amount_sat + total_fees { + break; + } + selected_amount += utxo.output.value; + total_fees += fee_to_spend_utxo; + selected_utxos.push(utxo.clone()); + } + if selected_amount < target_amount_sat + total_fees { + log_debug!( + self.logger, + "Insufficient funds to meet target feerate {} sat/kW", + target_feerate_sat_per_1000_weight + ); + return Err(()); + } + for utxo in &selected_utxos { + locked_utxos.insert(utxo.outpoint, claim_id); } - selected_amount += utxo.output.value; - total_fees += fee_to_spend_utxo; - selected_utxos.push(utxo.clone()); - } - if selected_amount < target_amount_sat + total_fees { - log_debug!( - self.logger, - "Insufficient funds to meet target feerate {} sat/kW", - target_feerate_sat_per_1000_weight - ); - return Err(()); - } - for utxo in &selected_utxos { - locked_utxos.insert(utxo.outpoint, claim_id); } - core::mem::drop(locked_utxos); let remaining_amount = selected_amount - target_amount_sat - total_fees; - let change_script = self.source.get_change_script()?; + let change_script = self.source.get_change_script().await?; let change_output_fee = fee_for_weight( target_feerate_sat_per_1000_weight, (8 /* value */ + change_script.consensus_encode(&mut sink()).unwrap() as u64) @@ -499,54 +562,67 @@ where } } -impl CoinSelectionSource for Wallet +impl CoinSelectionSource + for Wallet where - W::Target: WalletSource, - L::Target: Logger, + W::Target: WalletSource + MaybeSend + MaybeSync, + L::Target: Logger + MaybeSend + MaybeSync, { - fn select_confirmed_utxos( - &self, claim_id: ClaimId, must_spend: Vec, must_pay_to: &[TxOut], + fn select_confirmed_utxos<'a>( + &'a self, claim_id: ClaimId, must_spend: Vec, must_pay_to: &'a [TxOut], target_feerate_sat_per_1000_weight: u32, - ) -> Result { - let utxos = self.source.list_confirmed_utxos()?; - // TODO: Use fee estimation utils when we upgrade to bitcoin v0.30.0. - const BASE_TX_SIZE: u64 = 4 /* version */ + 1 /* input count */ + 1 /* output count */ + 4 /* locktime */; - let total_output_size: u64 = must_pay_to - .iter() - .map(|output| 8 /* value */ + 1 /* script len */ + output.script_pubkey.len() as u64) - .sum(); - let total_satisfaction_weight: u64 = - must_spend.iter().map(|input| input.satisfaction_weight).sum(); - let total_input_weight = - (BASE_INPUT_WEIGHT * must_spend.len() as u64) + total_satisfaction_weight; - - let preexisting_tx_weight = 2 /* segwit marker & flag */ + total_input_weight + + ) -> AsyncResult<'a, CoinSelection> { + Box::pin(async move { + let utxos = self.source.list_confirmed_utxos().await?; + // TODO: Use fee estimation utils when we upgrade to bitcoin v0.30.0. + const BASE_TX_SIZE: u64 = 4 /* version */ + 1 /* input count */ + 1 /* output count */ + 4 /* locktime */; + let total_output_size: u64 = must_pay_to + .iter() + .map( + |output| 8 /* value */ + 1 /* script len */ + output.script_pubkey.len() as u64, + ) + .sum(); + let total_satisfaction_weight: u64 = + must_spend.iter().map(|input| input.satisfaction_weight).sum(); + let total_input_weight = + (BASE_INPUT_WEIGHT * must_spend.len() as u64) + total_satisfaction_weight; + + let preexisting_tx_weight = 2 /* segwit marker & flag */ + total_input_weight + ((BASE_TX_SIZE + total_output_size) * WITNESS_SCALE_FACTOR as u64); - let input_amount_sat = must_spend.iter().map(|input| input.previous_utxo.value).sum(); - let target_amount_sat = must_pay_to.iter().map(|output| output.value).sum(); - let do_coin_selection = |force_conflicting_utxo_spend: bool, - tolerate_high_network_feerates: bool| { - log_debug!(self.logger, "Attempting coin selection targeting {} sat/kW (force_conflicting_utxo_spend = {}, tolerate_high_network_feerates = {})", - target_feerate_sat_per_1000_weight, force_conflicting_utxo_spend, tolerate_high_network_feerates); - self.select_confirmed_utxos_internal( - &utxos, - claim_id, - force_conflicting_utxo_spend, - tolerate_high_network_feerates, - target_feerate_sat_per_1000_weight, - preexisting_tx_weight, - input_amount_sat, - target_amount_sat, - ) - }; - do_coin_selection(false, false) - .or_else(|_| do_coin_selection(false, true)) - .or_else(|_| do_coin_selection(true, false)) - .or_else(|_| do_coin_selection(true, true)) + let input_amount_sat = must_spend.iter().map(|input| input.previous_utxo.value).sum(); + let target_amount_sat = must_pay_to.iter().map(|output| output.value).sum(); + + let configs = [(false, false), (false, true), (true, false), (true, true)]; + for (force_conflicting_utxo_spend, tolerate_high_network_feerates) in configs { + log_debug!( + self.logger, + "Attempting coin selection targeting {} sat/kW (force_conflicting_utxo_spend = {}, tolerate_high_network_feerates = {})", + target_feerate_sat_per_1000_weight, + force_conflicting_utxo_spend, + tolerate_high_network_feerates + ); + let attempt = self + .select_confirmed_utxos_internal( + &utxos, + claim_id, + force_conflicting_utxo_spend, + tolerate_high_network_feerates, + target_feerate_sat_per_1000_weight, + preexisting_tx_weight, + input_amount_sat, + target_amount_sat, + ) + .await; + if attempt.is_ok() { + return attempt; + } + } + Err(()) + }) } - fn sign_psbt(&self, psbt: Psbt) -> Result { - self.source.sign_psbt(psbt) + fn sign_psbt<'a>(&'a self, psbt: Psbt) -> AsyncResult<'a, Transaction> { + Box::pin(async move { self.source.sign_psbt(psbt).await }) } } @@ -625,7 +701,7 @@ where /// Handles a [`BumpTransactionEvent::ChannelClose`] event variant by producing a fully-signed /// transaction spending an anchor output of the commitment transaction to bump its fee and /// broadcasts them to the network as a package. - fn handle_channel_close( + async fn handle_channel_close( &self, claim_id: ClaimId, package_target_feerate_sat_per_1000_weight: u32, commitment_tx: &Transaction, commitment_tx_fee_sat: u64, anchor_descriptor: &AnchorDescriptor, @@ -652,12 +728,15 @@ where log_debug!(self.logger, "Performing coin selection for commitment package (commitment and anchor transaction) targeting {} sat/kW", package_target_feerate_sat_per_1000_weight); - let coin_selection: CoinSelection = self.utxo_source.select_confirmed_utxos( - claim_id, - must_spend, - &[], - package_target_feerate_sat_per_1000_weight, - )?; + let coin_selection: CoinSelection = self + .utxo_source + .select_confirmed_utxos( + claim_id, + must_spend, + &[], + package_target_feerate_sat_per_1000_weight, + ) + .await?; let mut anchor_tx = Transaction { version: Version::TWO, @@ -723,7 +802,7 @@ where } log_debug!(self.logger, "Signing anchor transaction {}", anchor_txid); - anchor_tx = self.utxo_source.sign_psbt(anchor_psbt)?; + anchor_tx = self.utxo_source.sign_psbt(anchor_psbt).await?; let signer = self .signer_provider @@ -770,7 +849,7 @@ where /// Handles a [`BumpTransactionEvent::HTLCResolution`] event variant by producing a /// fully-signed, fee-bumped HTLC transaction that is broadcast to the network. - fn handle_htlc_resolution( + async fn handle_htlc_resolution( &self, claim_id: ClaimId, target_feerate_sat_per_1000_weight: u32, htlc_descriptors: &[HTLCDescriptor], tx_lock_time: LockTime, ) -> Result<(), ()> { @@ -811,12 +890,15 @@ where let must_spend_amount = must_spend.iter().map(|input| input.previous_utxo.value.to_sat()).sum::(); - let coin_selection: CoinSelection = self.utxo_source.select_confirmed_utxos( - claim_id, - must_spend, - &htlc_tx.output, - target_feerate_sat_per_1000_weight, - )?; + let coin_selection: CoinSelection = self + .utxo_source + .select_confirmed_utxos( + claim_id, + must_spend, + &htlc_tx.output, + target_feerate_sat_per_1000_weight, + ) + .await?; #[cfg(debug_assertions)] let input_satisfaction_weight: u64 = @@ -860,7 +942,7 @@ where "Signing HTLC transaction {}", htlc_psbt.unsigned_tx.compute_txid() ); - htlc_tx = self.utxo_source.sign_psbt(htlc_psbt)?; + htlc_tx = self.utxo_source.sign_psbt(htlc_psbt).await?; let mut signers = BTreeMap::new(); for (idx, htlc_descriptor) in htlc_descriptors.iter().enumerate() { @@ -899,7 +981,7 @@ where } /// Handles all variants of [`BumpTransactionEvent`]. - pub fn handle_event(&self, event: &BumpTransactionEvent) { + pub async fn handle_event(&self, event: &BumpTransactionEvent) { match event { BumpTransactionEvent::ChannelClose { claim_id, @@ -915,19 +997,21 @@ where log_bytes!(claim_id.0), commitment_tx.compute_txid() ); - if let Err(_) = self.handle_channel_close( + self.handle_channel_close( *claim_id, *package_target_feerate_sat_per_1000_weight, commitment_tx, *commitment_tx_fee_satoshis, anchor_descriptor, - ) { + ) + .await + .unwrap_or_else(|_| { log_error!( self.logger, "Failed bumping commitment transaction fee for {}", commitment_tx.compute_txid() ); - } + }); }, BumpTransactionEvent::HTLCResolution { claim_id, @@ -942,18 +1026,20 @@ where log_bytes!(claim_id.0), log_iter!(htlc_descriptors.iter().map(|d| d.outpoint())) ); - if let Err(_) = self.handle_htlc_resolution( + self.handle_htlc_resolution( *claim_id, *target_feerate_sat_per_1000_weight, htlc_descriptors, *tx_lock_time, - ) { + ) + .await + .unwrap_or_else(|_| { log_error!( self.logger, "Failed bumping HTLC transaction fee for commitment {}", htlc_descriptors[0].commitment_txid ); - } + }); }, } } @@ -979,19 +1065,19 @@ mod tests { expected_selects: Mutex>, } impl CoinSelectionSource for TestCoinSelectionSource { - fn select_confirmed_utxos( - &self, _claim_id: ClaimId, must_spend: Vec, _must_pay_to: &[TxOut], + fn select_confirmed_utxos<'a>( + &'a self, _claim_id: ClaimId, must_spend: Vec, _must_pay_to: &'a [TxOut], target_feerate_sat_per_1000_weight: u32, - ) -> Result { + ) -> AsyncResult<'a, CoinSelection> { let mut expected_selects = self.expected_selects.lock().unwrap(); let (weight, value, feerate, res) = expected_selects.remove(0); assert_eq!(must_spend.len(), 1); assert_eq!(must_spend[0].satisfaction_weight, weight); assert_eq!(must_spend[0].previous_utxo.value.to_sat(), value); assert_eq!(target_feerate_sat_per_1000_weight, feerate); - Ok(res) + Box::pin(async move { Ok(res) }) } - fn sign_psbt(&self, psbt: Psbt) -> Result { + fn sign_psbt<'a>(&'a self, psbt: Psbt) -> AsyncResult<'a, Transaction> { let mut tx = psbt.unsigned_tx; for input in tx.input.iter_mut() { if input.previous_output.txid != Txid::from_byte_array([44; 32]) { @@ -999,7 +1085,7 @@ mod tests { input.witness = Witness::from_slice(&[vec![42; 162]]); } } - Ok(tx) + Box::pin(async move { Ok(tx) }) } } @@ -1009,8 +1095,8 @@ mod tests { } } - #[test] - fn test_op_return_under_funds() { + #[tokio::test] + async fn test_op_return_under_funds() { // Test what happens if we have to select coins but the anchor output value itself suffices // to pay the required fee. // @@ -1069,7 +1155,7 @@ mod tests { transaction_parameters.channel_type_features = ChannelTypeFeatures::anchors_zero_htlc_fee_and_dependencies(); - handler.handle_event(&BumpTransactionEvent::ChannelClose { + let channel_close_event = &BumpTransactionEvent::ChannelClose { channel_id: ChannelId([42; 32]), counterparty_node_id: PublicKey::from_slice(&[2; 33]).unwrap(), claim_id: ClaimId([42; 32]), @@ -1085,6 +1171,7 @@ mod tests { outpoint: OutPoint { txid: Txid::from_byte_array([42; 32]), vout: 0 }, }, pending_htlcs: Vec::new(), - }); + }; + handler.handle_event(channel_close_event).await; } } diff --git a/lightning/src/ln/async_signer_tests.rs b/lightning/src/ln/async_signer_tests.rs index 1f6f508d837..96024dc902d 100644 --- a/lightning/src/ln/async_signer_tests.rs +++ b/lightning/src/ln/async_signer_tests.rs @@ -18,7 +18,7 @@ use bitcoin::transaction::Version; use crate::chain::channelmonitor::LATENCY_GRACE_PERIOD_BLOCKS; use crate::chain::ChannelMonitorUpdateStatus; -use crate::events::bump_transaction::WalletSource; +use crate::events::bump_transaction::WalletSourceSync; use crate::events::{ClosureReason, Event}; use crate::ln::chan_utils::ClosingTransaction; use crate::ln::channel::DISCONNECT_PEER_AWAITING_RESPONSE_TICKS; @@ -805,7 +805,7 @@ fn do_test_async_commitment_signature_ordering(monitor_update_failure: bool) { claim_payment(&nodes[0], &[&nodes[1]], payment_preimage_2); } -fn do_test_async_holder_signatures(anchors: bool, remote_commitment: bool) { +async fn do_test_async_holder_signatures(anchors: bool, remote_commitment: bool) { // Ensures that we can obtain holder signatures for commitment and HTLC transactions // asynchronously by allowing their retrieval to fail and retrying via // `ChannelMonitor::signer_unblocked`. @@ -909,7 +909,7 @@ fn do_test_async_holder_signatures(anchors: bool, remote_commitment: bool) { // No HTLC transaction should be broadcast as the signer is not available yet. if anchors && !remote_commitment { - handle_bump_htlc_event(&nodes[0], 1); + handle_bump_htlc_event(&nodes[0], 1).await; } let txn = nodes[0].tx_broadcaster.txn_broadcast(); assert!(txn.is_empty(), "expected no transaction to be broadcast, got {:?}", txn); @@ -920,7 +920,7 @@ fn do_test_async_holder_signatures(anchors: bool, remote_commitment: bool) { get_monitor!(nodes[0], chan_id).signer_unblocked(nodes[0].tx_broadcaster, nodes[0].fee_estimator, &nodes[0].logger); if anchors && !remote_commitment { - handle_bump_htlc_event(&nodes[0], 1); + handle_bump_htlc_event(&nodes[0], 1).await; } { let txn = nodes[0].tx_broadcaster.txn_broadcast(); @@ -929,24 +929,24 @@ fn do_test_async_holder_signatures(anchors: bool, remote_commitment: bool) { } } -#[test] -fn test_async_holder_signatures_no_anchors() { - do_test_async_holder_signatures(false, false); +#[tokio::test] +async fn test_async_holder_signatures_no_anchors() { + do_test_async_holder_signatures(false, false).await; } -#[test] -fn test_async_holder_signatures_remote_commitment_no_anchors() { - do_test_async_holder_signatures(false, true); +#[tokio::test] +async fn test_async_holder_signatures_remote_commitment_no_anchors() { + do_test_async_holder_signatures(false, true).await; } -#[test] -fn test_async_holder_signatures_anchors() { - do_test_async_holder_signatures(true, false); +#[tokio::test] +async fn test_async_holder_signatures_anchors() { + do_test_async_holder_signatures(true, false).await; } -#[test] -fn test_async_holder_signatures_remote_commitment_anchors() { - do_test_async_holder_signatures(true, true); +#[tokio::test] +async fn test_async_holder_signatures_remote_commitment_anchors() { + do_test_async_holder_signatures(true, true).await; } #[test] diff --git a/lightning/src/ln/functional_test_utils.rs b/lightning/src/ln/functional_test_utils.rs index 8a9a5cb1762..34e6f1b03aa 100644 --- a/lightning/src/ln/functional_test_utils.rs +++ b/lightning/src/ln/functional_test_utils.rs @@ -14,7 +14,7 @@ use crate::chain::{BestBlock, ChannelMonitorUpdateStatus, Confirm, Listen, Watch use crate::chain::channelmonitor::ChannelMonitor; use crate::chain::transaction::OutPoint; use crate::events::{ClaimedHTLC, ClosureReason, Event, HTLCHandlingFailureType, PaidBolt12Invoice, PathFailure, PaymentFailureReason, PaymentPurpose}; -use crate::events::bump_transaction::{BumpTransactionEvent, BumpTransactionEventHandler, Wallet, WalletSource}; +use crate::events::bump_transaction::{BumpTransactionEvent, BumpTransactionEventHandler, Wallet, WalletSourceSync, WalletSourceSyncWrapper}; use crate::ln::types::ChannelId; use crate::types::features::ChannelTypeFeatures; use crate::types::payment::{PaymentPreimage, PaymentHash, PaymentSecret}; @@ -472,7 +472,7 @@ pub struct Node<'chan_man, 'node_cfg: 'chan_man, 'chan_mon_cfg: 'node_cfg> { pub wallet_source: Arc, pub bump_tx_handler: BumpTransactionEventHandler< &'chan_mon_cfg test_utils::TestBroadcaster, - Arc, &'chan_mon_cfg test_utils::TestLogger>>, + Arc>>, &'chan_mon_cfg test_utils::TestLogger>>, &'chan_mon_cfg test_utils::TestKeysInterface, &'chan_mon_cfg test_utils::TestLogger, >, @@ -1846,7 +1846,7 @@ macro_rules! check_closed_event { } } -pub fn handle_bump_htlc_event(node: &Node, count: usize) { +pub async fn handle_bump_htlc_event<'a, 'b, 'c>(node: &Node<'a, 'b, 'c>, count: usize) { let events = node.chain_monitor.chain_monitor.get_and_clear_pending_events(); assert_eq!(events.len(), count); for event in events { @@ -1854,7 +1854,7 @@ pub fn handle_bump_htlc_event(node: &Node, count: usize) { Event::BumpTransaction(bump_event) => { if let BumpTransactionEvent::HTLCResolution { .. } = &bump_event {} else { panic!(); } - node.bump_tx_handler.handle_event(&bump_event); + node.bump_tx_handler.handle_event(&bump_event).await; }, _ => panic!(), } @@ -3422,6 +3422,7 @@ pub fn create_network<'a, 'b: 'a, 'c: 'b>(node_count: usize, cfgs: &'b Vec(node_count: usize, cfgs: &'b Vec> = body; + #[allow(clippy::expect_used, clippy::diverging_sub_expression)] + { + return tokio::runtime::Builder::new_current_thread() + .build() + .expect("Failed building the Runtime") + .block_on(body); + } +} + +async fn test_multiple_package_conflicts_internal() { let chanmon_cfgs = create_chanmon_cfgs(3); let node_cfgs = create_node_cfgs(3, &chanmon_cfgs); let mut user_cfg = test_default_channel_config(); @@ -1626,21 +1645,17 @@ pub fn test_multiple_package_conflicts() { check_closed_broadcast!(nodes[2], true); check_added_monitors(&nodes[2], 1); - let process_bump_event = |node: &Node| { - let events = node.chain_monitor.chain_monitor.get_and_clear_pending_events(); - assert_eq!(events.len(), 1); - let bump_event = match &events[0] { - Event::BumpTransaction(bump_event) => bump_event, - _ => panic!("Unexepected event"), - }; - node.bump_tx_handler.handle_event(bump_event); - - let mut tx = node.tx_broadcaster.txn_broadcast(); - assert_eq!(tx.len(), 1); - tx.pop().unwrap() + let events = nodes[2].chain_monitor.chain_monitor.get_and_clear_pending_events(); + assert_eq!(events.len(), 1); + let bump_event = match &events[0] { + Event::BumpTransaction(bump_event) => bump_event, + _ => panic!("Unexpected event"), }; + nodes[2].bump_tx_handler.handle_event(bump_event).await; - let conflict_tx = process_bump_event(&nodes[2]); + let mut tx = nodes[2].tx_broadcaster.txn_broadcast(); + assert_eq!(tx.len(), 1); + let conflict_tx = tx.pop().unwrap(); assert_eq!(conflict_tx.input.len(), 3); assert_eq!(conflict_tx.input[0].previous_output.txid, node2_commit_tx.compute_txid()); assert_eq!(conflict_tx.input[1].previous_output.txid, node2_commit_tx.compute_txid()); diff --git a/lightning/src/ln/monitor_tests.rs b/lightning/src/ln/monitor_tests.rs index 38e43416866..9df36789378 100644 --- a/lightning/src/ln/monitor_tests.rs +++ b/lightning/src/ln/monitor_tests.rs @@ -13,7 +13,7 @@ use crate::sign::{ecdsa::EcdsaChannelSigner, OutputSpender, SignerProvider, Spen use crate::chain::channelmonitor::{ANTI_REORG_DELAY, ARCHIVAL_DELAY_BLOCKS,LATENCY_GRACE_PERIOD_BLOCKS, COUNTERPARTY_CLAIMABLE_WITHIN_BLOCKS_PINNABLE, Balance, BalanceSource, ChannelMonitorUpdateStep}; use crate::chain::transaction::OutPoint; use crate::chain::chaininterface::{ConfirmationTarget, LowerBoundedFeeEstimator, compute_feerate_sat_per_1000_weight}; -use crate::events::bump_transaction::{BumpTransactionEvent, WalletSource}; +use crate::events::bump_transaction::{BumpTransactionEvent, WalletSourceSync}; use crate::events::{Event, ClosureReason, HTLCHandlingFailureType}; use crate::ln::channel; use crate::ln::types::ChannelId; @@ -441,7 +441,7 @@ fn fuzzy_assert_eq>(a: V, b: V) { assert!(b_u64 >= a_u64 - 5); } -fn do_test_claim_value_force_close(anchors: bool, prev_commitment_tx: bool) { +async fn do_test_claim_value_force_close(anchors: bool, prev_commitment_tx: bool) { // Tests `get_claimable_balances` with an HTLC across a force-close. // We build a channel with an HTLC pending, then force close the channel and check that the // `get_claimable_balances` return value is correct as transactions confirm on-chain. @@ -650,8 +650,8 @@ fn do_test_claim_value_force_close(anchors: bool, prev_commitment_tx: bool) { } else { panic!("Unexpected event"); } - nodes[1].bump_tx_handler.handle_event(&first_htlc_event); - nodes[1].bump_tx_handler.handle_event(&second_htlc_event); + nodes[1].bump_tx_handler.handle_event(&first_htlc_event).await; + nodes[1].bump_tx_handler.handle_event(&second_htlc_event).await; }, _ => panic!("Unexpected event"), } @@ -837,15 +837,15 @@ fn do_test_claim_value_force_close(anchors: bool, prev_commitment_tx: bool) { } } -#[test] -fn test_claim_value_force_close() { - do_test_claim_value_force_close(false, true); - do_test_claim_value_force_close(false, false); - do_test_claim_value_force_close(true, true); - do_test_claim_value_force_close(true, false); +#[tokio::test] +async fn test_claim_value_force_close() { + do_test_claim_value_force_close(false, true).await; + do_test_claim_value_force_close(false, false).await; + do_test_claim_value_force_close(true, true).await; + do_test_claim_value_force_close(true, false).await; } -fn do_test_balances_on_local_commitment_htlcs(anchors: bool) { +async fn do_test_balances_on_local_commitment_htlcs(anchors: bool) { // Previously, when handling the broadcast of a local commitment transactions (with associated // CSV delays prior to spendability), we incorrectly handled the CSV delays on HTLC // transactions. This caused us to miss spendable outputs for HTLCs which were awaiting a CSV @@ -985,7 +985,7 @@ fn do_test_balances_on_local_commitment_htlcs(anchors: bool) { }, htlc_balance_known_preimage.clone(), htlc_balance_unknown_preimage.clone()]), sorted_vec(nodes[0].chain_monitor.chain_monitor.get_monitor(chan_id).unwrap().get_claimable_balances())); if anchors { - handle_bump_htlc_event(&nodes[0], 1); + handle_bump_htlc_event(&nodes[0], 1).await; } let mut timeout_htlc_txn = nodes[0].tx_broadcaster.unique_txn_broadcast(); if anchors { @@ -1018,7 +1018,7 @@ fn do_test_balances_on_local_commitment_htlcs(anchors: bool) { if anchors { // The HTLC timeout claim corresponding to the counterparty preimage claim is removed from the // aggregated package. - handle_bump_htlc_event(&nodes[0], 1); + handle_bump_htlc_event(&nodes[0], 1).await; timeout_htlc_txn = nodes[0].tx_broadcaster.unique_txn_broadcast(); assert_eq!(timeout_htlc_txn.len(), 1); check_spends!(timeout_htlc_txn[0], commitment_tx, coinbase_tx); @@ -1095,10 +1095,10 @@ fn do_test_balances_on_local_commitment_htlcs(anchors: bool) { assert!(nodes[0].chain_monitor.chain_monitor.get_monitor(chan_id).unwrap().get_claimable_balances().is_empty()); } -#[test] -fn test_balances_on_local_commitment_htlcs() { - do_test_balances_on_local_commitment_htlcs(false); - do_test_balances_on_local_commitment_htlcs(true); +#[tokio::test] +async fn test_balances_on_local_commitment_htlcs() { + do_test_balances_on_local_commitment_htlcs(false).await; + do_test_balances_on_local_commitment_htlcs(true).await; } #[test] @@ -1635,7 +1635,7 @@ fn test_revoked_counterparty_commitment_balances() { do_test_revoked_counterparty_commitment_balances(true, false); } -fn do_test_revoked_counterparty_htlc_tx_balances(anchors: bool) { +async fn do_test_revoked_counterparty_htlc_tx_balances(anchors: bool) { // Tests `get_claimable_balances` for revocation spends of HTLC transactions. let mut chanmon_cfgs = create_chanmon_cfgs(2); chanmon_cfgs[1].keys_manager.disable_revocation_policy_check = true; @@ -1700,7 +1700,7 @@ fn do_test_revoked_counterparty_htlc_tx_balances(anchors: bool) { check_added_monitors!(nodes[1], 1); check_closed_event!(nodes[1], 1, ClosureReason::CommitmentTxConfirmed, [nodes[0].node.get_our_node_id()], 1000000); if anchors { - handle_bump_htlc_event(&nodes[1], 1); + handle_bump_htlc_event(&nodes[1], 1).await; } let revoked_htlc_success = { let mut txn = nodes[1].tx_broadcaster.txn_broadcast(); @@ -1716,7 +1716,7 @@ fn do_test_revoked_counterparty_htlc_tx_balances(anchors: bool) { connect_blocks(&nodes[1], TEST_FINAL_CLTV); if anchors { - handle_bump_htlc_event(&nodes[1], 2); + handle_bump_htlc_event(&nodes[1], 2).await; } let revoked_htlc_timeout = { let mut txn = nodes[1].tx_broadcaster.unique_txn_broadcast(); @@ -1925,13 +1925,13 @@ fn do_test_revoked_counterparty_htlc_tx_balances(anchors: bool) { assert!(nodes[0].chain_monitor.chain_monitor.get_monitor(chan_id).unwrap().get_claimable_balances().is_empty()); } -#[test] -fn test_revoked_counterparty_htlc_tx_balances() { - do_test_revoked_counterparty_htlc_tx_balances(false); - do_test_revoked_counterparty_htlc_tx_balances(true); +#[tokio::test] +async fn test_revoked_counterparty_htlc_tx_balances() { + do_test_revoked_counterparty_htlc_tx_balances(false).await; + do_test_revoked_counterparty_htlc_tx_balances(true).await; } -fn do_test_revoked_counterparty_aggregated_claims(anchors: bool) { +async fn do_test_revoked_counterparty_aggregated_claims(anchors: bool) { // Tests `get_claimable_balances` for revoked counterparty commitment transactions when // claiming with an aggregated claim transaction. let mut chanmon_cfgs = create_chanmon_cfgs(2); @@ -2074,7 +2074,7 @@ fn do_test_revoked_counterparty_aggregated_claims(anchors: bool) { check_closed_broadcast(&nodes[0], 1, true); check_added_monitors(&nodes[0], 1); check_closed_event!(&nodes[0], 1, ClosureReason::CommitmentTxConfirmed, false, [nodes[1].node.get_our_node_id()], 1_000_000); - handle_bump_htlc_event(&nodes[0], 1); + handle_bump_htlc_event(&nodes[0], 1).await; } let htlc_success_claim = if anchors { let mut txn = nodes[0].tx_broadcaster.txn_broadcast(); @@ -2211,10 +2211,10 @@ fn do_test_revoked_counterparty_aggregated_claims(anchors: bool) { assert!(nodes[1].chain_monitor.chain_monitor.get_monitor(chan_id).unwrap().get_claimable_balances().is_empty()); } -#[test] -fn test_revoked_counterparty_aggregated_claims() { - do_test_revoked_counterparty_aggregated_claims(false); - do_test_revoked_counterparty_aggregated_claims(true); +#[tokio::test] +async fn test_revoked_counterparty_aggregated_claims() { + do_test_revoked_counterparty_aggregated_claims(false).await; + do_test_revoked_counterparty_aggregated_claims(true).await; } fn do_test_claimable_balance_correct_while_payment_pending(outbound_payment: bool, anchors: bool) { @@ -2391,7 +2391,7 @@ fn test_restored_packages_retry() { do_test_restored_packages_retry(true); } -fn do_test_monitor_rebroadcast_pending_claims(anchors: bool) { +async fn do_test_monitor_rebroadcast_pending_claims(anchors: bool) { // Test that we will retry broadcasting pending claims for a force-closed channel on every // `ChainMonitor::rebroadcast_pending_claims` call. let mut chanmon_cfgs = create_chanmon_cfgs(2); @@ -2437,90 +2437,95 @@ fn do_test_monitor_rebroadcast_pending_claims(anchors: bool) { // bumps if fees have not increased after a block has been connected (assuming the height timer // re-evaluates at every block) or after `ChainMonitor::rebroadcast_pending_claims` is called. let mut prev_htlc_tx_feerate = None; - let mut check_htlc_retry = |should_retry: bool, should_bump: bool| -> Option { - let (htlc_tx, htlc_tx_feerate) = if anchors { - assert!(nodes[0].tx_broadcaster.txn_broadcast().is_empty()); - let events = nodes[0].chain_monitor.chain_monitor.get_and_clear_pending_events(); - assert_eq!(events.len(), if should_retry { 1 } else { 0 }); - if !should_retry { - return None; - } - match &events[0] { - Event::BumpTransaction(event) => { - nodes[0].bump_tx_handler.handle_event(&event); - let mut txn = nodes[0].tx_broadcaster.unique_txn_broadcast(); - assert_eq!(txn.len(), 1); - let htlc_tx = txn.pop().unwrap(); - check_spends!(&htlc_tx, &commitment_txn[0], &coinbase_tx); - let htlc_tx_fee = HTLC_AMT_SAT + coinbase_tx.output[0].value.to_sat() - - htlc_tx.output.iter().map(|output| output.value.to_sat()).sum::(); - let htlc_tx_weight = htlc_tx.weight().to_wu(); - (htlc_tx, compute_feerate_sat_per_1000_weight(htlc_tx_fee, htlc_tx_weight)) - } - _ => panic!("Unexpected event"), - } - } else { - assert!(nodes[0].chain_monitor.chain_monitor.get_and_clear_pending_events().is_empty()); - let mut txn = nodes[0].tx_broadcaster.txn_broadcast(); - assert_eq!(txn.len(), if should_retry { 1 } else { 0 }); - if !should_retry { - return None; - } - let htlc_tx = txn.pop().unwrap(); - check_spends!(htlc_tx, commitment_txn[0]); - let htlc_tx_fee = HTLC_AMT_SAT - htlc_tx.output[0].value.to_sat(); - let htlc_tx_weight = htlc_tx.weight().to_wu(); - (htlc_tx, compute_feerate_sat_per_1000_weight(htlc_tx_fee, htlc_tx_weight)) - }; - if should_bump { - assert!(htlc_tx_feerate > prev_htlc_tx_feerate.take().unwrap()); - } else if let Some(prev_feerate) = prev_htlc_tx_feerate.take() { - assert_eq!(htlc_tx_feerate, prev_feerate); - } - prev_htlc_tx_feerate = Some(htlc_tx_feerate); - Some(htlc_tx) - }; // Connect blocks up to one before the HTLC expires. This should not result in a claim/retry. connect_blocks(&nodes[0], htlc_expiry - nodes[0].best_block_info().1 - 1); - check_htlc_retry(false, false); + check_htlc_retry(&nodes[0], anchors, false, false, &commitment_txn, &coinbase_tx, &mut prev_htlc_tx_feerate, HTLC_AMT_SAT).await; // Connect one more block, producing our first claim. connect_blocks(&nodes[0], 1); - check_htlc_retry(true, false); + check_htlc_retry(&nodes[0], anchors, true, false, &commitment_txn, &coinbase_tx, &mut prev_htlc_tx_feerate, HTLC_AMT_SAT).await; // Connect a few more blocks, expecting a retry with a fee bump. Unfortunately, we cannot bump // HTLC transactions pre-anchors. connect_blocks(&nodes[0], crate::chain::package::LOW_FREQUENCY_BUMP_INTERVAL); - check_htlc_retry(true, anchors); + check_htlc_retry(&nodes[0], anchors, true, anchors, &commitment_txn, &coinbase_tx, &mut prev_htlc_tx_feerate, HTLC_AMT_SAT).await; // Trigger a call and we should have another retry, but without a bump. nodes[0].chain_monitor.chain_monitor.rebroadcast_pending_claims(); - check_htlc_retry(true, false); + check_htlc_retry(&nodes[0], anchors, true, false, &commitment_txn, &coinbase_tx, &mut prev_htlc_tx_feerate, HTLC_AMT_SAT).await; // Double the feerate and trigger a call, expecting a fee-bumped retry. *nodes[0].fee_estimator.sat_per_kw.lock().unwrap() *= 2; nodes[0].chain_monitor.chain_monitor.rebroadcast_pending_claims(); - check_htlc_retry(true, anchors); + check_htlc_retry(&nodes[0], anchors, true, anchors, &commitment_txn, &coinbase_tx, &mut prev_htlc_tx_feerate, HTLC_AMT_SAT).await; // Connect a few more blocks, expecting a retry with a fee bump. Unfortunately, we cannot bump // HTLC transactions pre-anchors. connect_blocks(&nodes[0], crate::chain::package::LOW_FREQUENCY_BUMP_INTERVAL); - let htlc_tx = check_htlc_retry(true, anchors).unwrap(); + let htlc_tx = check_htlc_retry(&nodes[0], anchors, true, anchors, &commitment_txn, &coinbase_tx, &mut prev_htlc_tx_feerate, HTLC_AMT_SAT).await.unwrap(); // Mine the HTLC transaction to ensure we don't retry claims while they're confirmed. mine_transaction(&nodes[0], &htlc_tx); nodes[0].chain_monitor.chain_monitor.rebroadcast_pending_claims(); - check_htlc_retry(false, false); + check_htlc_retry(&nodes[0], anchors, false, false, &commitment_txn, &coinbase_tx, &mut prev_htlc_tx_feerate, HTLC_AMT_SAT).await; } -#[test] -fn test_monitor_timer_based_claim() { - do_test_monitor_rebroadcast_pending_claims(false); - do_test_monitor_rebroadcast_pending_claims(true); +async fn check_htlc_retry( + node: &Node<'_, '_, '_>, anchors: bool, should_retry: bool, should_bump: bool, + commitment_txn: &[Transaction], coinbase_tx: &Transaction, + prev_htlc_tx_feerate: &mut Option, htlc_amt_sat: u64, +) -> Option { + let (htlc_tx, htlc_tx_feerate) = if anchors { + assert!(node.tx_broadcaster.txn_broadcast().is_empty()); + let events = node.chain_monitor.chain_monitor.get_and_clear_pending_events(); + assert_eq!(events.len(), if should_retry { 1 } else { 0 }); + if !should_retry { + return None; + } + match &events[0] { + Event::BumpTransaction(event) => { + node.bump_tx_handler.handle_event(&event).await; + let mut txn = node.tx_broadcaster.unique_txn_broadcast(); + assert_eq!(txn.len(), 1); + let htlc_tx = txn.pop().unwrap(); + check_spends!(&htlc_tx, &commitment_txn[0], &coinbase_tx); + let htlc_tx_fee = htlc_amt_sat + coinbase_tx.output[0].value.to_sat() + - htlc_tx.output.iter().map(|output| output.value.to_sat()).sum::(); + let htlc_tx_weight = htlc_tx.weight().to_wu(); + (htlc_tx, compute_feerate_sat_per_1000_weight(htlc_tx_fee, htlc_tx_weight)) + }, + _ => panic!("Unexpected event"), + } + } else { + assert!(node.chain_monitor.chain_monitor.get_and_clear_pending_events().is_empty()); + let mut txn = node.tx_broadcaster.txn_broadcast(); + assert_eq!(txn.len(), if should_retry { 1 } else { 0 }); + if !should_retry { + return None; + } + let htlc_tx = txn.pop().unwrap(); + check_spends!(htlc_tx, commitment_txn[0]); + let htlc_tx_fee = htlc_amt_sat - htlc_tx.output[0].value.to_sat(); + let htlc_tx_weight = htlc_tx.weight().to_wu(); + (htlc_tx, compute_feerate_sat_per_1000_weight(htlc_tx_fee, htlc_tx_weight)) + }; + if should_bump { + assert!(htlc_tx_feerate > prev_htlc_tx_feerate.take().unwrap()); + } else if let Some(prev_feerate) = prev_htlc_tx_feerate.take() { + assert_eq!(htlc_tx_feerate, prev_feerate); + } + *prev_htlc_tx_feerate = Some(htlc_tx_feerate); + Some(htlc_tx) +} + +#[tokio::test] +async fn test_monitor_timer_based_claim() { + do_test_monitor_rebroadcast_pending_claims(false).await; + do_test_monitor_rebroadcast_pending_claims(true).await; } -fn do_test_yield_anchors_events(have_htlcs: bool) { +async fn do_test_yield_anchors_events(have_htlcs: bool) { // Tests that two parties supporting anchor outputs can open a channel, route payments over // it, and finalize its resolution uncooperatively. Once the HTLCs are locked in, one side will // force close once the HTLCs expire. The force close should stem from an event emitted by LDK, @@ -2621,7 +2626,7 @@ fn do_test_yield_anchors_events(have_htlcs: bool) { }], }; nodes[0].wallet_source.add_utxo(bitcoin::OutPoint { txid: coinbase_tx.compute_txid(), vout: 0 }, coinbase_tx.output[0].value); - nodes[0].bump_tx_handler.handle_event(&event); + nodes[0].bump_tx_handler.handle_event(&event).await; let mut txn = nodes[0].tx_broadcaster.unique_txn_broadcast(); assert_eq!(txn.len(), 2); let anchor_tx = txn.pop().unwrap(); @@ -2679,7 +2684,7 @@ fn do_test_yield_anchors_events(have_htlcs: bool) { for event in holder_events { match event { Event::BumpTransaction(event) => { - nodes[0].bump_tx_handler.handle_event(&event); + nodes[0].bump_tx_handler.handle_event(&event).await; let mut txn = nodes[0].tx_broadcaster.unique_txn_broadcast(); assert_eq!(txn.len(), 1); let htlc_tx = txn.pop().unwrap(); @@ -2708,14 +2713,14 @@ fn do_test_yield_anchors_events(have_htlcs: bool) { } } -#[test] -fn test_yield_anchors_events() { - do_test_yield_anchors_events(true); - do_test_yield_anchors_events(false); +#[tokio::test] +async fn test_yield_anchors_events() { + do_test_yield_anchors_events(true).await; + do_test_yield_anchors_events(false).await; } -#[test] -fn test_anchors_aggregated_revoked_htlc_tx() { +#[tokio::test]// Use an async test framework like `tokio` or `async-std` +async fn test_anchors_aggregated_revoked_htlc_tx() { // Test that `ChannelMonitor`s can properly detect and claim funds from a counterparty claiming // multiple HTLCs from multiple channels in a single transaction via the success path from a // revoked commitment. @@ -2807,7 +2812,7 @@ fn test_anchors_aggregated_revoked_htlc_tx() { }; nodes[1].wallet_source.add_utxo(bitcoin::OutPoint { txid: coinbase_tx.compute_txid(), vout: 0 }, utxo_value); match event { - Event::BumpTransaction(event) => nodes[1].bump_tx_handler.handle_event(&event), + Event::BumpTransaction(event) => nodes[1].bump_tx_handler.handle_event(&event).await, _ => panic!("Unexpected event"), }; let txn = nodes[1].tx_broadcaster.txn_broadcast(); @@ -3110,7 +3115,7 @@ fn test_anchors_monitor_fixes_counterparty_payment_script_on_reload() { } #[cfg(not(ldk_test_vectors))] -fn do_test_monitor_claims_with_random_signatures(anchors: bool, confirm_counterparty_commitment: bool) { +async fn do_test_monitor_claims_with_random_signatures(anchors: bool, confirm_counterparty_commitment: bool) { // Tests that our monitor claims will always use fresh random signatures (ensuring a unique // wtxid) to prevent certain classes of transaction replacement at the bitcoin P2P layer. let chanmon_cfgs = create_chanmon_cfgs(2); @@ -3185,7 +3190,7 @@ fn do_test_monitor_claims_with_random_signatures(anchors: bool, confirm_counterp connect_blocks(&nodes[0], TEST_FINAL_CLTV); } if anchors && !confirm_counterparty_commitment { - handle_bump_htlc_event(&nodes[0], 1); + handle_bump_htlc_event(&nodes[0], 1).await; } let htlc_timeout_tx = { let mut txn = nodes[0].tx_broadcaster.txn_broadcast(); @@ -3198,7 +3203,7 @@ fn do_test_monitor_claims_with_random_signatures(anchors: bool, confirm_counterp // Check we rebroadcast it with a different wtxid. nodes[0].chain_monitor.chain_monitor.rebroadcast_pending_claims(); if anchors && !confirm_counterparty_commitment { - handle_bump_htlc_event(&nodes[0], 1); + handle_bump_htlc_event(&nodes[0], 1).await; } { let mut txn = nodes[0].tx_broadcaster.txn_broadcast(); @@ -3209,12 +3214,12 @@ fn do_test_monitor_claims_with_random_signatures(anchors: bool, confirm_counterp } #[cfg(not(ldk_test_vectors))] -#[test] -fn test_monitor_claims_with_random_signatures() { - do_test_monitor_claims_with_random_signatures(false, false); - do_test_monitor_claims_with_random_signatures(false, true); - do_test_monitor_claims_with_random_signatures(true, false); - do_test_monitor_claims_with_random_signatures(true, true); +#[tokio::test] +async fn test_monitor_claims_with_random_signatures() { + do_test_monitor_claims_with_random_signatures(false, false).await; + do_test_monitor_claims_with_random_signatures(false, true).await; + do_test_monitor_claims_with_random_signatures(true, false).await; + do_test_monitor_claims_with_random_signatures(true, true).await; } #[test] diff --git a/lightning/src/util/async_poll.rs b/lightning/src/util/async_poll.rs index a0034a6caae..93be60adfd0 100644 --- a/lightning/src/util/async_poll.rs +++ b/lightning/src/util/async_poll.rs @@ -97,4 +97,24 @@ pub(crate) fn dummy_waker() -> Waker { } /// A type alias for a future that returns a result of type T. +#[cfg(feature = "std")] pub type AsyncResult<'a, T> = Pin> + 'a + Send>>; +#[cfg(not(feature = "std"))] +pub type AsyncResult<'a, T> = Pin> + 'a>>; + +// Marker trait to optionally implement `Sync` under std. +#[cfg(feature = "std")] +pub use core::marker::Sync as MaybeSync; + +#[cfg(not(feature = "std"))] +pub trait MaybeSync {} +#[cfg(not(feature = "std"))] +impl MaybeSync for T where T: ?Sized {} + +// Marker trait to optionally implement `Send` under std. +#[cfg(feature = "std")] +pub use core::marker::Send as MaybeSend; +#[cfg(not(feature = "std"))] +pub trait MaybeSend {} +#[cfg(not(feature = "std"))] +impl MaybeSend for T where T: ?Sized {} diff --git a/lightning/src/util/test_utils.rs b/lightning/src/util/test_utils.rs index f90bfb97ef7..a668e85f769 100644 --- a/lightning/src/util/test_utils.rs +++ b/lightning/src/util/test_utils.rs @@ -21,7 +21,7 @@ use crate::chain::channelmonitor::{ }; use crate::chain::transaction::OutPoint; use crate::chain::WatchedOutput; -use crate::events::bump_transaction::{Utxo, WalletSource}; +use crate::events::bump_transaction::{Utxo, WalletSourceSync}; #[cfg(any(test, feature = "_externalize_tests"))] use crate::ln::chan_utils::CommitmentTransaction; use crate::ln::channel_state::ChannelDetails; @@ -85,7 +85,6 @@ use crate::io; use crate::prelude::*; use crate::sign::{EntropySource, NodeSigner, RandomBytes, Recipient, SignerProvider}; use crate::sync::{Arc, Mutex}; -use core::cell::RefCell; use core::mem; use core::sync::atomic::{AtomicBool, AtomicUsize, Ordering}; use core::time::Duration; @@ -1871,36 +1870,36 @@ impl Drop for TestScorer { pub struct TestWalletSource { secret_key: SecretKey, - utxos: RefCell>, + utxos: Mutex>, secp: Secp256k1, } impl TestWalletSource { pub fn new(secret_key: SecretKey) -> Self { - Self { secret_key, utxos: RefCell::new(Vec::new()), secp: Secp256k1::new() } + Self { secret_key, utxos: Mutex::new(Vec::new()), secp: Secp256k1::new() } } pub fn add_utxo(&self, outpoint: bitcoin::OutPoint, value: Amount) -> TxOut { let public_key = bitcoin::PublicKey::new(self.secret_key.public_key(&self.secp)); let utxo = Utxo::new_p2pkh(outpoint, value, &public_key.pubkey_hash()); - self.utxos.borrow_mut().push(utxo.clone()); + self.utxos.lock().unwrap().push(utxo.clone()); utxo.output } pub fn add_custom_utxo(&self, utxo: Utxo) -> TxOut { let output = utxo.output.clone(); - self.utxos.borrow_mut().push(utxo); + self.utxos.lock().unwrap().push(utxo); output } pub fn remove_utxo(&self, outpoint: bitcoin::OutPoint) { - self.utxos.borrow_mut().retain(|utxo| utxo.outpoint != outpoint); + self.utxos.lock().unwrap().retain(|utxo| utxo.outpoint != outpoint); } } -impl WalletSource for TestWalletSource { +impl WalletSourceSync for TestWalletSource { fn list_confirmed_utxos(&self) -> Result, ()> { - Ok(self.utxos.borrow().clone()) + Ok(self.utxos.lock().unwrap().clone()) } fn get_change_script(&self) -> Result { @@ -1910,7 +1909,7 @@ impl WalletSource for TestWalletSource { fn sign_psbt(&self, psbt: Psbt) -> Result { let mut tx = psbt.extract_tx_unchecked_fee_rate(); - let utxos = self.utxos.borrow(); + let utxos = self.utxos.lock().unwrap(); for i in 0..tx.input.len() { if let Some(utxo) = utxos.iter().find(|utxo| utxo.outpoint == tx.input[i].previous_output)