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)