diff --git a/fuzz/src/chanmon_consistency.rs b/fuzz/src/chanmon_consistency.rs index f87af5c6ff5..f363da534ac 100644 --- a/fuzz/src/chanmon_consistency.rs +++ b/fuzz/src/chanmon_consistency.rs @@ -36,7 +36,9 @@ use bitcoin::WPubkeyHash; use lightning::blinded_path::message::{BlindedMessagePath, MessageContext, MessageForwardNode}; use lightning::blinded_path::payment::{BlindedPaymentPath, ReceiveTlvs}; use lightning::chain; -use lightning::chain::chaininterface::{BroadcasterInterface, ConfirmationTarget, FeeEstimator}; +use lightning::chain::chaininterface::{ + TransactionType, BroadcasterInterface, ConfirmationTarget, FeeEstimator, +}; use lightning::chain::channelmonitor::{ChannelMonitor, MonitorEvent}; use lightning::chain::transaction::OutPoint; use lightning::chain::{ @@ -159,8 +161,8 @@ pub struct TestBroadcaster { txn_broadcasted: RefCell>, } impl BroadcasterInterface for TestBroadcaster { - fn broadcast_transactions(&self, txs: &[&Transaction]) { - for tx in txs { + fn broadcast_transactions(&self, txs: &[(&Transaction, TransactionType)]) { + for (tx, _broadcast_type) in txs { self.txn_broadcasted.borrow_mut().push((*tx).clone()); } } diff --git a/fuzz/src/full_stack.rs b/fuzz/src/full_stack.rs index 722db37bbd6..e3bcb179fcf 100644 --- a/fuzz/src/full_stack.rs +++ b/fuzz/src/full_stack.rs @@ -33,7 +33,9 @@ use bitcoin::WPubkeyHash; use lightning::blinded_path::message::{BlindedMessagePath, MessageContext, MessageForwardNode}; use lightning::blinded_path::payment::{BlindedPaymentPath, ReceiveTlvs}; use lightning::chain; -use lightning::chain::chaininterface::{BroadcasterInterface, ConfirmationTarget, FeeEstimator}; +use lightning::chain::chaininterface::{ + TransactionType, BroadcasterInterface, ConfirmationTarget, FeeEstimator, +}; use lightning::chain::chainmonitor; use lightning::chain::transaction::OutPoint; use lightning::chain::{BestBlock, ChannelMonitorUpdateStatus, Confirm, Listen}; @@ -184,8 +186,8 @@ struct TestBroadcaster { txn_broadcasted: Mutex>, } impl BroadcasterInterface for TestBroadcaster { - fn broadcast_transactions(&self, txs: &[&Transaction]) { - let owned_txs: Vec = txs.iter().map(|tx| (*tx).clone()).collect(); + fn broadcast_transactions(&self, txs: &[(&Transaction, TransactionType)]) { + let owned_txs: Vec = txs.iter().map(|(tx, _)| (*tx).clone()).collect(); self.txn_broadcasted.lock().unwrap().extend(owned_txs); } } diff --git a/lightning-liquidity/.cargo/config.toml b/lightning-liquidity/.cargo/config.toml new file mode 100644 index 00000000000..e0dbcbb2203 --- /dev/null +++ b/lightning-liquidity/.cargo/config.toml @@ -0,0 +1,2 @@ +[build] +rustflags = ["--cfg=lsps1_service"] diff --git a/lightning-liquidity/src/lsps2/service.rs b/lightning-liquidity/src/lsps2/service.rs index 1b5bf964996..98d6783fe71 100644 --- a/lightning-liquidity/src/lsps2/service.rs +++ b/lightning-liquidity/src/lsps2/service.rs @@ -40,7 +40,7 @@ use crate::prelude::{new_hash_map, HashMap}; use crate::sync::{Arc, Mutex, MutexGuard, RwLock}; use crate::utils::async_poll::dummy_waker; -use lightning::chain::chaininterface::BroadcasterInterface; +use lightning::chain::chaininterface::{BroadcasterInterface, TransactionType}; use lightning::events::HTLCHandlingFailureType; use lightning::ln::channelmanager::{AChannelManager, FailureCode, InterceptId}; use lightning::ln::msgs::{ErrorAction, LightningError}; @@ -2023,23 +2023,24 @@ where // (for example when a forwarded HTLC nears expiry). Broadcasting funding after a // close could then confirm the commitment and trigger unintended on‑chain handling. // To avoid this, we check ChannelManager’s view (`is_channel_ready`) before broadcasting. - let channel_id_opt = jit_channel.get_channel_id(); - if let Some(ch_id) = channel_id_opt { + if let Some(ch_id) = jit_channel.get_channel_id() { let is_channel_ready = self .channel_manager .get_cm() .list_channels() .into_iter() .any(|cd| cd.channel_id == ch_id && cd.is_channel_ready); + if !is_channel_ready { return; } - } else { - return; - } - if let Some(funding_tx) = jit_channel.get_funding_tx() { - self.tx_broadcaster.broadcast_transactions(&[funding_tx]); + if let Some(funding_tx) = jit_channel.get_funding_tx() { + self.tx_broadcaster.broadcast_transactions(&[( + funding_tx, + TransactionType::Funding { channel_ids: vec![ch_id] }, + )]); + } } } } diff --git a/lightning/src/chain/chaininterface.rs b/lightning/src/chain/chaininterface.rs index 117e9b3af05..48a0303f970 100644 --- a/lightning/src/chain/chaininterface.rs +++ b/lightning/src/chain/chaininterface.rs @@ -15,10 +15,56 @@ use core::{cmp, ops::Deref}; +use crate::ln::types::ChannelId; use crate::prelude::*; use bitcoin::transaction::Transaction; +/// Represents the class of transaction being broadcast. +/// +/// This is used to provide context about the type of transaction being broadcast, which may be +/// useful for logging, filtering, or prioritization purposes. +#[derive(Clone, Debug, Hash, PartialEq, Eq)] +pub enum TransactionType { + /// A funding transaction establishing a new channel. + Funding { + /// The IDs of the channels being funded. + /// + /// A single funding transaction may establish multiple channels when using batch funding. + channel_ids: Vec, + }, + /// A transaction cooperatively closing a channel. + CooperativeClose { + /// The ID of the channel being closed. + channel_id: ChannelId, + }, + /// A transaction being broadcast to force-close the channel. + UnilateralClose { + /// The ID of the channel being force-closed. + channel_id: ChannelId, + }, + /// An anchor bumping transaction used for CPFP fee-bumping a closing transaction. + AnchorBump { + /// The ID of the channel whose closing transaction is being fee-bumped. + channel_id: ChannelId, + }, + /// A transaction claiming outputs from a commitment transaction (HTLC claims, penalty/justice). + Claim { + /// The ID of the channel from which outputs are being claimed. + channel_id: ChannelId, + }, + /// A transaction genered by the [`OutputSweeper`], sweeping [`SpendableOutputDescriptor`]s to the user's wallet. + /// + /// [`OutputSweeper`]: crate::util::sweep::OutputSweeper + /// [`SpendableOutputDescriptor`]: crate::sign::SpendableOutputDescriptor + Sweep { + /// The IDs of the channels from which outputs are being swept, if known. + /// + /// A single sweep transaction may aggregate outputs from multiple channels. + channel_ids: Vec, + }, +} + // TODO: Define typed abstraction over feerates to handle their conversions. pub(crate) fn compute_feerate_sat_per_1000_weight(fee_sat: u64, weight: u64) -> u32 { (fee_sat * 1000 / weight).try_into().unwrap_or(u32::max_value()) @@ -45,7 +91,10 @@ pub trait BroadcasterInterface { /// /// Bitcoin transaction packages are defined in BIP 331 and here: /// - fn broadcast_transactions(&self, txs: &[&Transaction]); + /// + /// Each transaction is paired with a [`TransactionType`] indicating the class of transaction + /// being broadcast, which may be useful for logging, filtering, or prioritization. + fn broadcast_transactions(&self, txs: &[(&Transaction, TransactionType)]); } /// An enum that represents the priority at which we want a transaction to confirm used for feerate diff --git a/lightning/src/chain/channelmonitor.rs b/lightning/src/chain/channelmonitor.rs index 515a3dc5f1d..f5966b48e0e 100644 --- a/lightning/src/chain/channelmonitor.rs +++ b/lightning/src/chain/channelmonitor.rs @@ -1904,8 +1904,9 @@ impl ChannelMonitor { initial_holder_commitment_tx.trust().commitment_number(); let onchain_tx_handler = OnchainTxHandler::new( - channel_parameters.channel_value_satoshis, channel_keys_id, destination_script.into(), - keys, channel_parameters.clone(), initial_holder_commitment_tx.clone(), secp_ctx + channel_id, channel_parameters.channel_value_satoshis, channel_keys_id, + destination_script.into(), keys, channel_parameters.clone(), + initial_holder_commitment_tx.clone(), secp_ctx, ); let funding_outpoint = channel_parameters.funding_outpoint @@ -6617,7 +6618,7 @@ impl<'a, 'b, ES: EntropySource, SP: SignerProvider> ReadableArgs<(&'a ES, &'b SP return Err(DecodeError::InvalidValue); } } - let onchain_tx_handler: OnchainTxHandler = ReadableArgs::read( + let mut onchain_tx_handler: OnchainTxHandler = ReadableArgs::read( reader, (entropy_source, signer_provider, channel_value_satoshis, channel_keys_id) )?; @@ -6713,6 +6714,7 @@ impl<'a, 'b, ES: EntropySource, SP: SignerProvider> ReadableArgs<(&'a ES, &'b SP } let channel_id = channel_id.unwrap_or(ChannelId::v1_from_funding_outpoint(outpoint)); + onchain_tx_handler.set_channel_id(channel_id); let (current_holder_commitment_tx, current_holder_htlc_data) = { let holder_commitment_tx = onchain_tx_handler.current_holder_commitment_tx(); diff --git a/lightning/src/chain/mod.rs b/lightning/src/chain/mod.rs index b4cc6a302ae..eddd5ad9dc9 100644 --- a/lightning/src/chain/mod.rs +++ b/lightning/src/chain/mod.rs @@ -20,7 +20,6 @@ use bitcoin::secp256k1::PublicKey; use crate::chain::channelmonitor::{ChannelMonitor, ChannelMonitorUpdate, MonitorEvent}; use crate::chain::transaction::{OutPoint, TransactionData}; -use crate::impl_writeable_tlv_based; use crate::ln::types::ChannelId; use crate::sign::ecdsa::EcdsaChannelSigner; use crate::sign::HTLCDescriptor; diff --git a/lightning/src/chain/onchaintx.rs b/lightning/src/chain/onchaintx.rs index fb65aa0f157..349d8979054 100644 --- a/lightning/src/chain/onchaintx.rs +++ b/lightning/src/chain/onchaintx.rs @@ -23,7 +23,9 @@ use bitcoin::transaction::OutPoint as BitcoinOutPoint; use bitcoin::transaction::Transaction; use crate::chain::chaininterface::ConfirmationTarget; -use crate::chain::chaininterface::{BroadcasterInterface, FeeEstimator, LowerBoundedFeeEstimator}; +use crate::chain::chaininterface::{ + BroadcasterInterface, FeeEstimator, LowerBoundedFeeEstimator, TransactionType, +}; use crate::chain::channelmonitor::ANTI_REORG_DELAY; use crate::chain::package::{PackageSolvingData, PackageTemplate}; use crate::chain::transaction::MaybeSignedTransaction; @@ -33,6 +35,7 @@ use crate::ln::chan_utils::{ HTLCOutputInCommitment, HolderCommitmentTransaction, }; use crate::ln::msgs::DecodeError; +use crate::ln::types::ChannelId; use crate::sign::{ecdsa::EcdsaChannelSigner, EntropySource, HTLCDescriptor, SignerProvider}; use crate::util::logger::Logger; use crate::util::ser::{ @@ -221,6 +224,7 @@ pub(crate) enum FeerateStrategy { /// do RBF bumping if possible. #[derive(Clone)] pub struct OnchainTxHandler { + channel_id: ChannelId, channel_value_satoshis: u64, // Deprecated as of 0.2. channel_keys_id: [u8; 32], // Deprecated as of 0.2. destination_script: ScriptBuf, // Deprecated as of 0.2. @@ -283,7 +287,8 @@ impl PartialEq for OnchainTxHandler bool { // `signer`, `secp_ctx`, and `pending_claim_events` are excluded on purpose. - self.channel_value_satoshis == other.channel_value_satoshis && + self.channel_id == other.channel_id && + self.channel_value_satoshis == other.channel_value_satoshis && self.channel_keys_id == other.channel_keys_id && self.destination_script == other.destination_script && self.holder_commitment == other.holder_commitment && @@ -346,6 +351,14 @@ impl OnchainTxHandler { write_tlv_fields!(writer, {}); Ok(()) } + + // `ChannelMonitor`s already track the `channel_id`, however, due to the derserialization order + // there we can't make use of `ReadableArgs` to hand it into `OnchainTxHandler`'s + // deserialization logic directly. Instead we opt to initialize it with 0s and override it + // after reading the respective field via this method. + pub(crate) fn set_channel_id(&mut self, channel_id: ChannelId) { + self.channel_id = channel_id; + } } impl<'a, 'b, ES: EntropySource, SP: SignerProvider> ReadableArgs<(&'a ES, &'b SP, u64, [u8; 32])> @@ -367,7 +380,7 @@ impl<'a, 'b, ES: EntropySource, SP: SignerProvider> ReadableArgs<(&'a ES, &'b SP let prev_holder_commitment = Readable::read(reader)?; let _prev_holder_htlc_sigs: Option>> = Readable::read(reader)?; - let channel_parameters = ReadableArgs::>::read(reader, Some(channel_value_satoshis))?; + let channel_parameters: ChannelTransactionParameters = ReadableArgs::>::read(reader, Some(channel_value_satoshis))?; // Read the serialized signer bytes, but don't deserialize them, as we'll obtain our signer // by re-deriving the private key material. @@ -421,10 +434,17 @@ impl<'a, 'b, ES: EntropySource, SP: SignerProvider> ReadableArgs<(&'a ES, &'b SP read_tlv_fields!(reader, {}); + // `ChannelMonitor`s already track the `channel_id`, however, due to the derserialization + // order there we can't make use of `ReadableArgs` to hand it in directly. Instead we opt + // to initialize it with 0s and override it after reading the respective field via + // `OnchainTxHandler::set_channel_id`. + let channel_id = ChannelId([0u8; 32]); + let mut secp_ctx = Secp256k1::new(); secp_ctx.seeded_randomize(&entropy_source.get_secure_random_bytes()); Ok(OnchainTxHandler { + channel_id, channel_value_satoshis, channel_keys_id, destination_script, @@ -444,11 +464,13 @@ impl<'a, 'b, ES: EntropySource, SP: SignerProvider> ReadableArgs<(&'a ES, &'b SP impl OnchainTxHandler { pub(crate) fn new( - channel_value_satoshis: u64, channel_keys_id: [u8; 32], destination_script: ScriptBuf, - signer: ChannelSigner, channel_parameters: ChannelTransactionParameters, + channel_id: ChannelId, channel_value_satoshis: u64, channel_keys_id: [u8; 32], + destination_script: ScriptBuf, signer: ChannelSigner, + channel_parameters: ChannelTransactionParameters, holder_commitment: HolderCommitmentTransaction, secp_ctx: Secp256k1, ) -> Self { OnchainTxHandler { + channel_id, channel_value_satoshis, channel_keys_id, destination_script, @@ -516,7 +538,7 @@ impl OnchainTxHandler { if tx.is_fully_signed() { let log_start = if feerate_was_bumped { "Broadcasting RBF-bumped" } else { "Rebroadcasting" }; log_info!(logger, "{} onchain {}", log_start, log_tx!(tx.0)); - broadcaster.broadcast_transactions(&[&tx.0]); + broadcaster.broadcast_transactions(&[(&tx.0, TransactionType::Claim { channel_id: self.channel_id })]); } else { log_info!(logger, "Waiting for signature of unsigned onchain transaction {}", tx.0.compute_txid()); } @@ -863,7 +885,7 @@ impl OnchainTxHandler { OnchainClaim::Tx(tx) => { if tx.is_fully_signed() { log_info!(logger, "Broadcasting onchain {}", log_tx!(tx.0)); - broadcaster.broadcast_transactions(&[&tx.0]); + broadcaster.broadcast_transactions(&[(&tx.0, TransactionType::Claim { channel_id: self.channel_id })]); } else { log_info!(logger, "Waiting for signature of unsigned onchain transaction {}", tx.0.compute_txid()); } @@ -1084,7 +1106,7 @@ impl OnchainTxHandler { OnchainClaim::Tx(bump_tx) => { if bump_tx.is_fully_signed() { log_info!(logger, "Broadcasting RBF-bumped onchain {}", log_tx!(bump_tx.0)); - broadcaster.broadcast_transactions(&[&bump_tx.0]); + broadcaster.broadcast_transactions(&[(&bump_tx.0, TransactionType::Claim { channel_id: self.channel_id })]); } else { log_info!(logger, "Waiting for signature of RBF-bumped unsigned onchain transaction {}", bump_tx.0.compute_txid()); @@ -1187,7 +1209,7 @@ impl OnchainTxHandler { OnchainClaim::Tx(bump_tx) => { if bump_tx.is_fully_signed() { log_info!(logger, "Broadcasting onchain {}", log_tx!(bump_tx.0)); - broadcaster.broadcast_transactions(&[&bump_tx.0]); + broadcaster.broadcast_transactions(&[(&bump_tx.0, TransactionType::Claim { channel_id: self.channel_id })]); } else { log_info!(logger, "Waiting for signature of unsigned onchain transaction {}", bump_tx.0.compute_txid()); } @@ -1281,6 +1303,7 @@ mod tests { }; use crate::ln::channel_keys::{DelayedPaymentBasepoint, HtlcBasepoint, RevocationBasepoint}; use crate::ln::functional_test_utils::create_dummy_block; + use crate::ln::types::ChannelId; use crate::sign::{ChannelDerivationParameters, ChannelSigner, HTLCDescriptor, InMemorySigner}; use crate::types::payment::{PaymentHash, PaymentPreimage}; use crate::util::test_utils::{TestBroadcaster, TestFeeEstimator, TestLogger}; @@ -1365,6 +1388,7 @@ mod tests { let holder_commit = HolderCommitmentTransaction::dummy(1000000, funding_outpoint, nondust_htlcs); let destination_script = ScriptBuf::new(); let mut tx_handler = OnchainTxHandler::new( + ChannelId::from_bytes([0; 32]), 1000000, [0; 32], destination_script.clone(), diff --git a/lightning/src/events/bump_transaction/mod.rs b/lightning/src/events/bump_transaction/mod.rs index e141d9b8abc..97cca12ccdc 100644 --- a/lightning/src/events/bump_transaction/mod.rs +++ b/lightning/src/events/bump_transaction/mod.rs @@ -18,7 +18,7 @@ use core::future::Future; use core::ops::Deref; use crate::chain::chaininterface::{ - compute_feerate_sat_per_1000_weight, fee_for_weight, BroadcasterInterface, + compute_feerate_sat_per_1000_weight, fee_for_weight, BroadcasterInterface, TransactionType, }; use crate::chain::ClaimId; use crate::io_extras::sink; @@ -765,9 +765,9 @@ where /// transaction spending an anchor output of the commitment transaction to bump its fee and /// broadcasts them to the network as a package. 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, + &self, channel_id: ChannelId, claim_id: ClaimId, + package_target_feerate_sat_per_1000_weight: u32, commitment_tx: &Transaction, + commitment_tx_fee_sat: u64, anchor_descriptor: &AnchorDescriptor, ) -> Result<(), ()> { let channel_type = &anchor_descriptor .channel_derivation_parameters @@ -788,7 +788,10 @@ where log_debug!(self.logger, "Pre-signed commitment {} already has feerate {} sat/kW above required {} sat/kW, broadcasting.", commitment_tx.compute_txid(), commitment_tx_feerate_sat_per_1000_weight, package_target_feerate_sat_per_1000_weight); - self.broadcaster.broadcast_transactions(&[&commitment_tx]); + self.broadcaster.broadcast_transactions(&[( + &commitment_tx, + TransactionType::UnilateralClose { channel_id }, + )]); return Ok(()); } @@ -955,7 +958,10 @@ where anchor_txid, commitment_tx.compute_txid() ); - self.broadcaster.broadcast_transactions(&[&commitment_tx, &anchor_tx]); + self.broadcaster.broadcast_transactions(&[ + (&commitment_tx, TransactionType::UnilateralClose { channel_id }), + (&anchor_tx, TransactionType::AnchorBump { channel_id }), + ]); return Ok(()); } } @@ -963,7 +969,7 @@ where /// Handles a [`BumpTransactionEvent::HTLCResolution`] event variant by producing a /// fully-signed, fee-bumped HTLC transaction that is broadcast to the network. async fn handle_htlc_resolution( - &self, claim_id: ClaimId, target_feerate_sat_per_1000_weight: u32, + &self, channel_id: ChannelId, claim_id: ClaimId, target_feerate_sat_per_1000_weight: u32, htlc_descriptors: &[HTLCDescriptor], tx_lock_time: LockTime, ) -> Result<(), ()> { let channel_type = &htlc_descriptors[0] @@ -1188,7 +1194,10 @@ where } log_info!(self.logger, "Broadcasting {}", log_tx!(htlc_tx)); - self.broadcaster.broadcast_transactions(&[&htlc_tx]); + self.broadcaster.broadcast_transactions(&[( + &htlc_tx, + TransactionType::UnilateralClose { channel_id }, + )]); } Ok(()) @@ -1198,6 +1207,7 @@ where pub async fn handle_event(&self, event: &BumpTransactionEvent) { match event { BumpTransactionEvent::ChannelClose { + channel_id, claim_id, package_target_feerate_sat_per_1000_weight, commitment_tx, @@ -1212,6 +1222,7 @@ where commitment_tx.compute_txid() ); self.handle_channel_close( + *channel_id, *claim_id, *package_target_feerate_sat_per_1000_weight, commitment_tx, @@ -1228,6 +1239,7 @@ where }); }, BumpTransactionEvent::HTLCResolution { + channel_id, claim_id, target_feerate_sat_per_1000_weight, htlc_descriptors, @@ -1241,6 +1253,7 @@ where log_iter!(htlc_descriptors.iter().map(|d| d.outpoint())) ); self.handle_htlc_resolution( + *channel_id, *claim_id, *target_feerate_sat_per_1000_weight, htlc_descriptors, diff --git a/lightning/src/ln/channelmanager.rs b/lightning/src/ln/channelmanager.rs index 10c77505408..ea6f71e0d7a 100644 --- a/lightning/src/ln/channelmanager.rs +++ b/lightning/src/ln/channelmanager.rs @@ -40,6 +40,7 @@ use crate::blinded_path::NodeIdLookUp; use crate::chain; use crate::chain::chaininterface::{ BroadcasterInterface, ConfirmationTarget, FeeEstimator, LowerBoundedFeeEstimator, + TransactionType, }; use crate::chain::channelmonitor::{ Balance, ChannelMonitor, ChannelMonitorUpdate, ChannelMonitorUpdateStep, MonitorEvent, @@ -6548,7 +6549,10 @@ where "Broadcasting signed interactive funding transaction {}", funding_tx.compute_txid() ); - self.tx_broadcaster.broadcast_transactions(&[funding_tx]); + self.tx_broadcaster.broadcast_transactions(&[( + funding_tx, + TransactionType::Funding { channel_ids: vec![channel.context().channel_id()] }, + )]); { let mut pending_events = self.pending_events.lock().unwrap(); emit_channel_pending_event!(pending_events, channel); @@ -9463,6 +9467,7 @@ This indicates a bug inside LDK. Please report this error at https://github.com/ let removed_batch_state = funding_batch_states.remove(&txid).into_iter().flatten(); let per_peer_state = self.per_peer_state.read().unwrap(); let mut batch_funding_tx = None; + let mut batch_channel_ids = Vec::new(); for (channel_id, counterparty_node_id, _) in removed_batch_state { if let Some(peer_state_mutex) = per_peer_state.get(&counterparty_node_id) { let mut peer_state = peer_state_mutex.lock().unwrap(); @@ -9473,6 +9478,7 @@ This indicates a bug inside LDK. Please report this error at https://github.com/ funded_chan.context.unbroadcasted_funding(&funded_chan.funding) }); funded_chan.set_batch_ready(); + batch_channel_ids.push(channel_id); let mut pending_events = self.pending_events.lock().unwrap(); emit_channel_pending_event!(pending_events, funded_chan); @@ -9481,7 +9487,10 @@ This indicates a bug inside LDK. Please report this error at https://github.com/ } if let Some(tx) = batch_funding_tx { log_info!(self.logger, "Broadcasting batch funding tx {}", tx.compute_txid()); - self.tx_broadcaster.broadcast_transactions(&[&tx]); + self.tx_broadcaster.broadcast_transactions(&[( + &tx, + TransactionType::Funding { channel_ids: batch_channel_ids }, + )]); } } } @@ -10147,7 +10156,10 @@ This indicates a bug inside LDK. Please report this error at https://github.com/ }; } else { log_info!(logger, "Broadcasting funding transaction with txid {}", tx.compute_txid()); - self.tx_broadcaster.broadcast_transactions(&[&tx]); + self.tx_broadcaster.broadcast_transactions(&[( + &tx, + TransactionType::Funding { channel_ids: vec![channel.context.channel_id()] }, + )]); } } @@ -11608,7 +11620,10 @@ This indicates a bug inside LDK. Please report this error at https://github.com/ mem::drop(per_peer_state); if let Some((broadcast_tx, err)) = tx_err { log_info!(logger, "Broadcasting {}", log_tx!(broadcast_tx)); - self.tx_broadcaster.broadcast_transactions(&[&broadcast_tx]); + self.tx_broadcaster.broadcast_transactions(&[( + &broadcast_tx, + TransactionType::CooperativeClose { channel_id: msg.channel_id }, + )]); let _ = self.handle_error(err, *counterparty_node_id); } Ok(()) @@ -12935,7 +12950,10 @@ This indicates a bug inside LDK. Please report this error at https://github.com/ } if let Some(broadcast_tx) = msgs.signed_closing_tx { log_info!(logger, "Broadcasting closing tx {}", log_tx!(broadcast_tx)); - self.tx_broadcaster.broadcast_transactions(&[&broadcast_tx]); + self.tx_broadcaster.broadcast_transactions(&[( + &broadcast_tx, + TransactionType::CooperativeClose { channel_id }, + )]); } } else { // We don't know how to handle a channel_ready or signed_closing_tx for a @@ -13053,6 +13071,7 @@ This indicates a bug inside LDK. Please report this error at https://github.com/ if let Some((tx, shutdown_res)) = tx_shutdown_result_opt { // We're done with this channel. We got a closing_signed and sent back // a closing_signed with a closing transaction to broadcast. + let channel_id = funded_chan.context.channel_id(); let err = self.locked_handle_funded_coop_close( &mut peer_state.closed_channel_monitor_update_ids, &mut peer_state.in_flight_monitor_updates, @@ -13062,7 +13081,10 @@ This indicates a bug inside LDK. Please report this error at https://github.com/ handle_errors.push((*cp_id, Err(err))); log_info!(logger, "Broadcasting {}", log_tx!(tx)); - self.tx_broadcaster.broadcast_transactions(&[&tx]); + self.tx_broadcaster.broadcast_transactions(&[( + &tx, + TransactionType::CooperativeClose { channel_id }, + )]); false } else { true diff --git a/lightning/src/util/sweep.rs b/lightning/src/util/sweep.rs index bf048efdae1..db06340e297 100644 --- a/lightning/src/util/sweep.rs +++ b/lightning/src/util/sweep.rs @@ -8,7 +8,9 @@ //! [`SpendableOutputDescriptor`]s, i.e., persists them in a given [`KVStoreSync`] and regularly retries //! sweeping them. -use crate::chain::chaininterface::{BroadcasterInterface, ConfirmationTarget, FeeEstimator}; +use crate::chain::chaininterface::{ + BroadcasterInterface, ConfirmationTarget, FeeEstimator, TransactionType, +}; use crate::chain::channelmonitor::{ANTI_REORG_DELAY, ARCHIVAL_DELAY_BLOCKS}; use crate::chain::{self, BestBlock, Confirm, Filter, Listen, WatchedOutput}; use crate::io; @@ -26,7 +28,7 @@ use crate::util::persist::{ OUTPUT_SWEEPER_PERSISTENCE_PRIMARY_NAMESPACE, OUTPUT_SWEEPER_PERSISTENCE_SECONDARY_NAMESPACE, }; use crate::util::ser::{Readable, ReadableArgs, Writeable}; -use crate::{impl_writeable_tlv_based, log_debug, log_error}; +use crate::{log_debug, log_error}; use bitcoin::block::Header; use bitcoin::locktime::absolute::LockTime; @@ -523,66 +525,79 @@ where self.change_destination_source.get_change_destination_script().await?; // Sweep the outputs. - let spending_tx = self - .update_state(|sweeper_state| -> Result<(Option, bool), ()> { - let cur_height = sweeper_state.best_block.height; - let cur_hash = sweeper_state.best_block.block_hash; - - let respend_descriptors_set: HashSet<&SpendableOutputDescriptor> = sweeper_state - .outputs - .iter() - .filter(|o| filter_fn(*o, cur_height)) - .map(|o| &o.descriptor) - .collect(); - - // we first collect into a set to avoid duplicates and to "randomize" the order - // in which outputs are spent. Then we collect into a vec as that is what - // `spend_outputs` requires. - let respend_descriptors: Vec<&SpendableOutputDescriptor> = - respend_descriptors_set.into_iter().collect(); - - // Generate the spending transaction and broadcast it. - if !respend_descriptors.is_empty() { - let spending_tx = self - .spend_outputs( - &sweeper_state, - &respend_descriptors, - change_destination_script, - ) - .map_err(|e| { - log_error!(self.logger, "Error spending outputs: {:?}", e); - })?; - - log_debug!( - self.logger, - "Generating and broadcasting sweeping transaction {}", - spending_tx.compute_txid() - ); - - // As we didn't modify the state so far, the same filter_fn yields the same elements as - // above. - let respend_outputs = - sweeper_state.outputs.iter_mut().filter(|o| filter_fn(&**o, cur_height)); - for output_info in respend_outputs { - if let Some(filter) = self.chain_data_source.as_ref() { - let watched_output = output_info.to_watched_output(cur_hash); - filter.register_output(watched_output); + let spending_tx_and_chan_id = self + .update_state( + |sweeper_state| -> Result<(Option<(Transaction, Vec)>, bool), ()> { + let cur_height = sweeper_state.best_block.height; + let cur_hash = sweeper_state.best_block.block_hash; + + let respend_descriptors_set: HashSet<&SpendableOutputDescriptor> = + sweeper_state + .outputs + .iter() + .filter(|o| filter_fn(*o, cur_height)) + .map(|o| &o.descriptor) + .collect(); + + // we first collect into a set to avoid duplicates and to "randomize" the order + // in which outputs are spent. Then we collect into a vec as that is what + // `spend_outputs` requires. + let respend_descriptors: Vec<&SpendableOutputDescriptor> = + respend_descriptors_set.into_iter().collect(); + + // Generate the spending transaction and broadcast it. + if !respend_descriptors.is_empty() { + let spending_tx = self + .spend_outputs( + &sweeper_state, + &respend_descriptors, + change_destination_script, + ) + .map_err(|e| { + log_error!(self.logger, "Error spending outputs: {:?}", e); + })?; + + log_debug!( + self.logger, + "Generating and broadcasting sweeping transaction {}", + spending_tx.compute_txid() + ); + + // As we didn't modify the state so far, the same filter_fn yields the same elements as + // above. + let respend_outputs = sweeper_state + .outputs + .iter_mut() + .filter(|o| filter_fn(&**o, cur_height)); + let mut channel_ids = Vec::new(); + for output_info in respend_outputs { + if let Some(filter) = self.chain_data_source.as_ref() { + let watched_output = output_info.to_watched_output(cur_hash); + filter.register_output(watched_output); + } + + if let Some(channel_id) = output_info.channel_id { + if !channel_ids.contains(&channel_id) { + channel_ids.push(channel_id); + } + } + + output_info.status.broadcast(cur_hash, cur_height, spending_tx.clone()); + sweeper_state.dirty = true; } - output_info.status.broadcast(cur_hash, cur_height, spending_tx.clone()); - sweeper_state.dirty = true; + Ok((Some((spending_tx, channel_ids)), false)) + } else { + Ok((None, false)) } - - Ok((Some(spending_tx), false)) - } else { - Ok((None, false)) - } - }) + }, + ) .await?; // Persistence completely successfully. If we have a spending transaction, we broadcast it. - if let Some(spending_tx) = spending_tx { - self.broadcaster.broadcast_transactions(&[&spending_tx]); + if let Some((spending_tx, channel_ids)) = spending_tx_and_chan_id { + self.broadcaster + .broadcast_transactions(&[(&spending_tx, TransactionType::Sweep { channel_ids })]); } Ok(()) diff --git a/lightning/src/util/test_utils.rs b/lightning/src/util/test_utils.rs index 34f5d5fe36e..f5c73ca4ca3 100644 --- a/lightning/src/util/test_utils.rs +++ b/lightning/src/util/test_utils.rs @@ -12,9 +12,9 @@ use crate::blinded_path::message::{BlindedMessagePath, MessageForwardNode}; use crate::blinded_path::payment::{BlindedPaymentPath, ReceiveTlvs}; use crate::chain; use crate::chain::chaininterface; -use crate::chain::chaininterface::ConfirmationTarget; #[cfg(any(test, feature = "_externalize_tests"))] use crate::chain::chaininterface::FEERATE_FLOOR_SATS_PER_KW; +use crate::chain::chaininterface::{ConfirmationTarget, TransactionType}; use crate::chain::chainmonitor::{ChainMonitor, Persist}; use crate::chain::channelmonitor::{ ChannelMonitor, ChannelMonitorUpdate, ChannelMonitorUpdateStep, MonitorEvent, @@ -1154,7 +1154,7 @@ impl TestBroadcaster { } impl chaininterface::BroadcasterInterface for TestBroadcaster { - fn broadcast_transactions(&self, txs: &[&Transaction]) { + fn broadcast_transactions(&self, txs: &[(&Transaction, TransactionType)]) { // Assert that any batch of transactions of length greater than 1 is sorted // topologically, and is a `child-with-parents` package as defined in // . @@ -1165,21 +1165,23 @@ impl chaininterface::BroadcasterInterface for TestBroadcaster { // Right now LDK only ever broadcasts packages of length 2. assert!(txs.len() <= 2); if txs.len() == 2 { - let parent_txid = txs[0].compute_txid(); + let parent_txid = txs[0].0.compute_txid(); assert!(txs[1] + .0 .input .iter() .map(|input| input.previous_output.txid) .any(|txid| txid == parent_txid)); - let child_txid = txs[1].compute_txid(); + let child_txid = txs[1].0.compute_txid(); assert!(txs[0] + .0 .input .iter() .map(|input| input.previous_output.txid) .all(|txid| txid != child_txid)); } - for tx in txs { + for (tx, _broadcast_type) in txs { let lock_time = tx.lock_time.to_consensus_u32(); assert!(lock_time < 1_500_000_000); if tx.lock_time.is_block_height() @@ -1195,7 +1197,7 @@ impl chaininterface::BroadcasterInterface for TestBroadcaster { } } } - let owned_txs: Vec = txs.iter().map(|tx| (*tx).clone()).collect(); + let owned_txs: Vec = txs.iter().map(|(tx, _)| (*tx).clone()).collect(); self.txn_broadcasted.lock().unwrap().extend(owned_txs); } }