diff --git a/lightning/src/events/bump_transaction.rs b/lightning/src/events/bump_transaction/mod.rs similarity index 87% rename from lightning/src/events/bump_transaction.rs rename to lightning/src/events/bump_transaction/mod.rs index 6ecd6075712..c1a4384a28d 100644 --- a/lightning/src/events/bump_transaction.rs +++ b/lightning/src/events/bump_transaction/mod.rs @@ -11,6 +11,8 @@ //! //! [`Event`]: crate::events::Event +pub mod sync; + use alloc::collections::BTreeMap; use core::ops::Deref; @@ -30,6 +32,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; @@ -319,6 +322,8 @@ pub struct CoinSelection { /// sign for them. The coin selection method aims to mimic Bitcoin Core's `fundrawtransaction` RPC, /// which most wallets should be able to satisfy. Otherwise, consider implementing [`WalletSource`], /// which can provide a default implementation of this trait when used with [`Wallet`]. +/// +/// For a synchronous version of this trait, see [`sync::CoinSelectionSourceSync`]. pub trait CoinSelectionSource { /// Performs coin selection of a set of UTXOs, with at least 1 confirmation each, that are /// available to spend. Implementations are free to pick their coin selection algorithm of @@ -346,42 +351,46 @@ 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`]. +/// +/// For a synchronous version of this trait, see [`sync::WalletSourceSync`]. pub trait WalletSource { /// Returns all UTXOs, with at least 1 confirmation each, that are available to spend. - fn list_confirmed_utxos(&self) -> Result, ()>; + 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(&self) -> Result; + fn get_change_script<'a>(&'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(&self, psbt: Psbt) -> Result; + fn sign_psbt<'a>(&'a self, psbt: Psbt) -> AsyncResult<'a, Transaction>; } /// 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 +/// +/// For a synchronous version of this wrapper, see [`sync::WalletSync`]. +pub struct Wallet where - W::Target: WalletSource, - L::Target: Logger, + W::Target: WalletSource + MaybeSend, + L::Target: Logger + MaybeSend, { source: W, logger: L, @@ -391,10 +400,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 +419,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 +512,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 }) } } @@ -554,6 +580,8 @@ where /// [`CoinSelectionSource`] to fee bump transactions via Child-Pays-For-Parent (CPFP) or /// Replace-By-Fee (RBF). /// +/// For a synchronous version of this handler, see [`sync::BumpTransactionEventHandlerSync`]. +/// /// [`Event::BumpTransaction`]: crate::events::Event::BumpTransaction pub struct BumpTransactionEventHandler where @@ -625,7 +653,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 +680,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 +754,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 +801,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 +842,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 +894,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 +933,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 +949,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 +978,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 ); - } + }); }, } } @@ -963,6 +1001,9 @@ where mod tests { use super::*; + use crate::events::bump_transaction::sync::{ + BumpTransactionEventHandlerSync, CoinSelectionSourceSync, + }; use crate::io::Cursor; use crate::ln::chan_utils::ChannelTransactionParameters; use crate::sign::KeysManager; @@ -978,7 +1019,7 @@ mod tests { // (commitment + anchor value, commitment + input weight, target feerate, result) expected_selects: Mutex>, } - impl CoinSelectionSource for TestCoinSelectionSource { + impl CoinSelectionSourceSync for TestCoinSelectionSource { fn select_confirmed_utxos( &self, _claim_id: ClaimId, must_spend: Vec, _must_pay_to: &[TxOut], target_feerate_sat_per_1000_weight: u32, @@ -1063,7 +1104,7 @@ mod tests { }; let signer = KeysManager::new(&[42; 32], 42, 42); let logger = TestLogger::new(); - let handler = BumpTransactionEventHandler::new(&broadcaster, &source, &signer, &logger); + let handler = BumpTransactionEventHandlerSync::new(&broadcaster, &source, &signer, &logger); let mut transaction_parameters = ChannelTransactionParameters::test_dummy(42_000_000); transaction_parameters.channel_type_features = diff --git a/lightning/src/events/bump_transaction/sync.rs b/lightning/src/events/bump_transaction/sync.rs new file mode 100644 index 00000000000..1df2cb5fde5 --- /dev/null +++ b/lightning/src/events/bump_transaction/sync.rs @@ -0,0 +1,209 @@ +// This file is Copyright its original authors, visible in version control +// history. +// +// This file is licensed under the Apache License, Version 2.0 or the MIT license +// , at your option. +// You may not use this file except in accordance with one or both of these +// licenses. + +//! This module provides synchronous wrappers around [`BumpTransactionEventHandler`] and related types. + +use core::future::Future; +use core::ops::Deref; +use core::task; + +use crate::chain::chaininterface::BroadcasterInterface; +use crate::chain::ClaimId; +use crate::prelude::*; +use crate::sign::SignerProvider; +use crate::sync::Arc; +use crate::util::async_poll::{dummy_waker, AsyncResult, MaybeSend, MaybeSync}; +use crate::util::logger::Logger; + +use bitcoin::{Psbt, ScriptBuf, Transaction, TxOut}; + +use super::BumpTransactionEvent; +use super::{ + BumpTransactionEventHandler, CoinSelection, CoinSelectionSource, Input, Utxo, Wallet, + WalletSource, +}; + +/// A synchronous version of the [`WalletSource`] trait. +pub trait WalletSourceSync { + /// A synchronous version of [`WalletSource::list_confirmed_utxos`]. + fn list_confirmed_utxos(&self) -> Result, ()>; + /// A synchronous version of [`WalletSource::get_change_script`]. + fn get_change_script(&self) -> Result; + /// A Synchronous version of [`WalletSource::sign_psbt`]. + fn sign_psbt(&self, psbt: Psbt) -> Result; +} + +pub(crate) struct WalletSourceSyncWrapper(T) +where + T::Target: WalletSourceSync; + +impl WalletSource for WalletSourceSyncWrapper +where + T::Target: WalletSourceSync, +{ + fn list_confirmed_utxos<'a>(&'a self) -> AsyncResult<'a, Vec> { + let utxos = self.0.list_confirmed_utxos(); + Box::pin(async move { utxos }) + } + + fn get_change_script<'a>(&'a self) -> AsyncResult<'a, ScriptBuf> { + let script = self.0.get_change_script(); + Box::pin(async move { script }) + } + + fn sign_psbt<'a>(&'a self, psbt: Psbt) -> AsyncResult<'a, Transaction> { + let signed_psbt = self.0.sign_psbt(psbt); + Box::pin(async move { signed_psbt }) + } +} + +/// A synchronous wrapper around [`Wallet`] to be used in contexts where async is not available. +pub struct WalletSync +where + W::Target: WalletSourceSync + MaybeSend, + L::Target: Logger + MaybeSend, +{ + wallet: Wallet>, L>, +} + +impl WalletSync +where + W::Target: WalletSourceSync + MaybeSend, + L::Target: Logger + MaybeSend, +{ + /// Constructs a new [`WalletSync`] instance. + pub fn new(source: W, logger: L) -> Self { + Self { wallet: Wallet::new(Arc::new(WalletSourceSyncWrapper(source)), logger) } + } +} + +impl CoinSelectionSourceSync + for WalletSync +where + W::Target: WalletSourceSync + MaybeSend + MaybeSync, + L::Target: Logger + MaybeSend + MaybeSync, +{ + fn select_confirmed_utxos( + &self, claim_id: ClaimId, must_spend: Vec, must_pay_to: &[TxOut], + target_feerate_sat_per_1000_weight: u32, + ) -> Result { + let mut fut = self.wallet.select_confirmed_utxos( + claim_id, + must_spend, + must_pay_to, + target_feerate_sat_per_1000_weight, + ); + let mut waker = dummy_waker(); + let mut ctx = task::Context::from_waker(&mut waker); + match fut.as_mut().poll(&mut ctx) { + task::Poll::Ready(result) => result, + task::Poll::Pending => { + unreachable!( + "Wallet::select_confirmed_utxos should not be pending in a sync context" + ); + }, + } + } + + fn sign_psbt(&self, psbt: Psbt) -> Result { + let mut fut = self.wallet.sign_psbt(psbt); + let mut waker = dummy_waker(); + let mut ctx = task::Context::from_waker(&mut waker); + match fut.as_mut().poll(&mut ctx) { + task::Poll::Ready(result) => result, + task::Poll::Pending => { + unreachable!("Wallet::sign_psbt should not be pending in a sync context"); + }, + } + } +} + +/// A synchronous version of the [`CoinSelectionSource`] trait. +pub trait CoinSelectionSourceSync { + /// A synchronous version of [`CoinSelectionSource::select_confirmed_utxos`]. + fn select_confirmed_utxos( + &self, claim_id: ClaimId, must_spend: Vec, must_pay_to: &[TxOut], + target_feerate_sat_per_1000_weight: u32, + ) -> Result; + + /// A synchronous version of [`CoinSelectionSource::sign_psbt`]. + fn sign_psbt(&self, psbt: Psbt) -> Result; +} + +struct CoinSelectionSourceSyncWrapper(T) +where + T::Target: CoinSelectionSourceSync; + +impl CoinSelectionSource for CoinSelectionSourceSyncWrapper +where + T::Target: CoinSelectionSourceSync, +{ + 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, + ) -> AsyncResult<'a, CoinSelection> { + let coins = self.0.select_confirmed_utxos( + claim_id, + must_spend, + must_pay_to, + target_feerate_sat_per_1000_weight, + ); + Box::pin(async move { coins }) + } + + fn sign_psbt<'a>(&'a self, psbt: Psbt) -> AsyncResult<'a, Transaction> { + let psbt = self.0.sign_psbt(psbt); + Box::pin(async move { psbt }) + } +} + +/// A synchronous wrapper around [`BumpTransactionEventHandler`] to be used in contexts where async is not available. +pub struct BumpTransactionEventHandlerSync +where + B::Target: BroadcasterInterface, + C::Target: CoinSelectionSourceSync, + SP::Target: SignerProvider, + L::Target: Logger, +{ + bump_transaction_event_handler: + Arc>, SP, L>>, +} + +impl BumpTransactionEventHandlerSync +where + B::Target: BroadcasterInterface, + C::Target: CoinSelectionSourceSync, + SP::Target: SignerProvider, + L::Target: Logger, +{ + /// Constructs a new instance of [`BumpTransactionEventHandlerSync`]. + pub fn new(broadcaster: B, utxo_source: C, signer_provider: SP, logger: L) -> Self { + let bump_transaction_event_handler = Arc::new(BumpTransactionEventHandler::new( + broadcaster, + Arc::new(CoinSelectionSourceSyncWrapper(utxo_source)), + signer_provider, + logger, + )); + Self { bump_transaction_event_handler } + } + + /// Handles all variants of [`BumpTransactionEvent`]. + pub fn handle_event(&self, event: &BumpTransactionEvent) { + let mut fut = Box::pin(self.bump_transaction_event_handler.handle_event(event)); + let mut waker = dummy_waker(); + let mut ctx = task::Context::from_waker(&mut waker); + match fut.as_mut().poll(&mut ctx) { + task::Poll::Ready(result) => result, + task::Poll::Pending => { + // In a sync context, we can't wait for the future to complete. + unreachable!("BumpTransactionEventHandlerSync::handle_event should not be pending in a sync context"); + }, + } + } +} diff --git a/lightning/src/ln/async_signer_tests.rs b/lightning/src/ln/async_signer_tests.rs index 96ec664da94..3616030be7a 100644 --- a/lightning/src/ln/async_signer_tests.rs +++ b/lightning/src/ln/async_signer_tests.rs @@ -20,7 +20,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::sync::WalletSourceSync; use crate::events::{ClosureReason, Event}; use crate::ln::chan_utils::ClosingTransaction; use crate::ln::channel::DISCONNECT_PEER_AWAITING_RESPONSE_TICKS; diff --git a/lightning/src/ln/functional_test_utils.rs b/lightning/src/ln/functional_test_utils.rs index 3ff2a045d28..ebed3af4880 100644 --- a/lightning/src/ln/functional_test_utils.rs +++ b/lightning/src/ln/functional_test_utils.rs @@ -16,7 +16,8 @@ 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}; +use crate::events::bump_transaction::sync::{BumpTransactionEventHandlerSync, WalletSourceSync, WalletSync}; use crate::ln::types::ChannelId; use crate::types::features::ChannelTypeFeatures; use crate::types::payment::{PaymentPreimage, PaymentHash, PaymentSecret}; @@ -472,9 +473,9 @@ pub struct Node<'chan_man, 'node_cfg: 'chan_man, 'chan_mon_cfg: 'node_cfg> { pub connect_style: Rc>, pub override_init_features: Rc>>, pub wallet_source: Arc, - pub bump_tx_handler: BumpTransactionEventHandler< + pub bump_tx_handler: BumpTransactionEventHandlerSync< &'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, >, @@ -3424,6 +3425,7 @@ pub fn create_network<'a, 'b: 'a, 'c: 'b>(node_count: usize, cfgs: &'b Vec(node_count: usize, cfgs: &'b Vec 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 00b85cc1ef8..855fa51b20b 100644 --- a/lightning/src/util/test_utils.rs +++ b/lightning/src/util/test_utils.rs @@ -21,7 +21,8 @@ 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::sync::WalletSourceSync; +use crate::events::bump_transaction::Utxo; #[cfg(any(test, feature = "_externalize_tests"))] use crate::ln::chan_utils::CommitmentTransaction; use crate::ln::channel_state::ChannelDetails; @@ -85,7 +86,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; @@ -1874,36 +1874,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 { @@ -1913,7 +1913,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)