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)