From d6482c873ebaf9a6bc1312304758a168de136f2e Mon Sep 17 00:00:00 2001 From: Elias Rohrer Date: Tue, 27 Jan 2026 12:34:53 +0100 Subject: [PATCH 1/6] Refactor `BroadcasterInterface` to include `BroadcastType` Add a `BroadcastType` enum to provide context about the type of transaction being broadcast. This information can be useful for logging, filtering, or prioritization purposes. The `BroadcastType` variants are: - `Funding`: A funding transaction establishing a new channel - `CooperativeClose`: A cooperative close transaction - `UnilateralClose`: A force-close transaction - `AnchorBump`: An anchor transaction for CPFP fee-bumping - `Claim`: A transaction claiming outputs from commitment tx - `Sweep`: A transaction sweeping spendable outputs to wallet Co-Authored-By: HAL 9000 Signed-off-by: Elias Rohrer --- fuzz/src/chanmon_consistency.rs | 8 +++--- fuzz/src/full_stack.rs | 8 +++--- lightning-liquidity/src/lsps2/service.rs | 4 +-- lightning/src/chain/chaininterface.rs | 27 +++++++++++++++++++- lightning/src/chain/onchaintx.rs | 12 +++++---- lightning/src/events/bump_transaction/mod.rs | 11 +++++--- lightning/src/ln/channelmanager.rs | 21 ++++++++++----- lightning/src/util/sweep.rs | 6 +++-- lightning/src/util/test_utils.rs | 14 +++++----- 9 files changed, 78 insertions(+), 33 deletions(-) diff --git a/fuzz/src/chanmon_consistency.rs b/fuzz/src/chanmon_consistency.rs index f87af5c6ff5..6e4b70e4cd8 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::{ + BroadcastType, 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, BroadcastType)]) { + 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..3f51010d28a 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::{ + BroadcastType, 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, BroadcastType)]) { + let owned_txs: Vec = txs.iter().map(|(tx, _)| (*tx).clone()).collect(); self.txn_broadcasted.lock().unwrap().extend(owned_txs); } } diff --git a/lightning-liquidity/src/lsps2/service.rs b/lightning-liquidity/src/lsps2/service.rs index 1b5bf964996..6a00a2220fa 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::{BroadcastType, BroadcasterInterface}; use lightning::events::HTLCHandlingFailureType; use lightning::ln::channelmanager::{AChannelManager, FailureCode, InterceptId}; use lightning::ln::msgs::{ErrorAction, LightningError}; @@ -2039,7 +2039,7 @@ where } if let Some(funding_tx) = jit_channel.get_funding_tx() { - self.tx_broadcaster.broadcast_transactions(&[funding_tx]); + self.tx_broadcaster.broadcast_transactions(&[(funding_tx, BroadcastType::Funding)]); } } } diff --git a/lightning/src/chain/chaininterface.rs b/lightning/src/chain/chaininterface.rs index 117e9b3af05..85895b99531 100644 --- a/lightning/src/chain/chaininterface.rs +++ b/lightning/src/chain/chaininterface.rs @@ -19,6 +19,28 @@ 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, Copy, Debug, Hash, PartialEq, Eq)] +pub enum BroadcastType { + /// A funding transaction establishing a new channel. + Funding, + /// A cooperative close transaction mutually agreed upon by both parties. + CooperativeClose, + /// A commitment transaction being broadcast to force-close the channel. + Commitment, + /// An anchor transaction used for CPFP fee-bumping a commitment transaction. + Anchor, + /// A transaction claiming outputs from a commitment transaction (HTLC claims, penalty/justice). + Claim, + /// An HTLC resolution transaction (HTLC-timeout or HTLC-success) for anchor channels. + HtlcResolution, + /// A transaction sweeping spendable outputs to the user's wallet. + Sweep, +} + // 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 +67,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 [`BroadcastType`] indicating the class of transaction + /// being broadcast, which may be useful for logging, filtering, or prioritization. + fn broadcast_transactions(&self, txs: &[(&Transaction, BroadcastType)]); } /// An enum that represents the priority at which we want a transaction to confirm used for feerate diff --git a/lightning/src/chain/onchaintx.rs b/lightning/src/chain/onchaintx.rs index fb65aa0f157..c7d085c99ab 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::{ + BroadcastType, BroadcasterInterface, FeeEstimator, LowerBoundedFeeEstimator, +}; use crate::chain::channelmonitor::ANTI_REORG_DELAY; use crate::chain::package::{PackageSolvingData, PackageTemplate}; use crate::chain::transaction::MaybeSignedTransaction; @@ -516,7 +518,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, BroadcastType::Claim)]); } else { log_info!(logger, "Waiting for signature of unsigned onchain transaction {}", tx.0.compute_txid()); } @@ -863,7 +865,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, BroadcastType::Claim)]); } else { log_info!(logger, "Waiting for signature of unsigned onchain transaction {}", tx.0.compute_txid()); } @@ -1084,7 +1086,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, BroadcastType::Claim)]); } else { log_info!(logger, "Waiting for signature of RBF-bumped unsigned onchain transaction {}", bump_tx.0.compute_txid()); @@ -1187,7 +1189,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, BroadcastType::Claim)]); } else { log_info!(logger, "Waiting for signature of unsigned onchain transaction {}", bump_tx.0.compute_txid()); } diff --git a/lightning/src/events/bump_transaction/mod.rs b/lightning/src/events/bump_transaction/mod.rs index e141d9b8abc..3918774e962 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, BroadcastType, BroadcasterInterface, }; use crate::chain::ClaimId; use crate::io_extras::sink; @@ -788,7 +788,7 @@ 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, BroadcastType::Commitment)]); return Ok(()); } @@ -955,7 +955,10 @@ where anchor_txid, commitment_tx.compute_txid() ); - self.broadcaster.broadcast_transactions(&[&commitment_tx, &anchor_tx]); + self.broadcaster.broadcast_transactions(&[ + (&commitment_tx, BroadcastType::Commitment), + (&anchor_tx, BroadcastType::Anchor), + ]); return Ok(()); } } @@ -1188,7 +1191,7 @@ where } log_info!(self.logger, "Broadcasting {}", log_tx!(htlc_tx)); - self.broadcaster.broadcast_transactions(&[&htlc_tx]); + self.broadcaster.broadcast_transactions(&[(&htlc_tx, BroadcastType::HtlcResolution)]); } Ok(()) diff --git a/lightning/src/ln/channelmanager.rs b/lightning/src/ln/channelmanager.rs index 10c77505408..e59e84c4c29 100644 --- a/lightning/src/ln/channelmanager.rs +++ b/lightning/src/ln/channelmanager.rs @@ -39,7 +39,7 @@ use crate::blinded_path::payment::{AsyncBolt12OfferContext, Bolt12OfferContext, use crate::blinded_path::NodeIdLookUp; use crate::chain; use crate::chain::chaininterface::{ - BroadcasterInterface, ConfirmationTarget, FeeEstimator, LowerBoundedFeeEstimator, + BroadcastType, BroadcasterInterface, ConfirmationTarget, FeeEstimator, LowerBoundedFeeEstimator, }; use crate::chain::channelmonitor::{ Balance, ChannelMonitor, ChannelMonitorUpdate, ChannelMonitorUpdateStep, MonitorEvent, @@ -6548,7 +6548,7 @@ where "Broadcasting signed interactive funding transaction {}", funding_tx.compute_txid() ); - self.tx_broadcaster.broadcast_transactions(&[funding_tx]); + self.tx_broadcaster.broadcast_transactions(&[(funding_tx, BroadcastType::Funding)]); { let mut pending_events = self.pending_events.lock().unwrap(); emit_channel_pending_event!(pending_events, channel); @@ -9481,7 +9481,7 @@ 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, BroadcastType::Funding)]); } } } @@ -10147,7 +10147,7 @@ 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, BroadcastType::Funding)]); } } @@ -11608,7 +11608,8 @@ 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, BroadcastType::CooperativeClose)]); let _ = self.handle_error(err, *counterparty_node_id); } Ok(()) @@ -12935,7 +12936,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, + BroadcastType::CooperativeClose, + )]); } } else { // We don't know how to handle a channel_ready or signed_closing_tx for a @@ -13062,7 +13066,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, + BroadcastType::CooperativeClose, + )]); false } else { true diff --git a/lightning/src/util/sweep.rs b/lightning/src/util/sweep.rs index bf048efdae1..6475b805a5b 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::{ + BroadcastType, BroadcasterInterface, ConfirmationTarget, FeeEstimator, +}; use crate::chain::channelmonitor::{ANTI_REORG_DELAY, ARCHIVAL_DELAY_BLOCKS}; use crate::chain::{self, BestBlock, Confirm, Filter, Listen, WatchedOutput}; use crate::io; @@ -582,7 +584,7 @@ where // 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]); + self.broadcaster.broadcast_transactions(&[(&spending_tx, BroadcastType::Sweep)]); } Ok(()) diff --git a/lightning/src/util/test_utils.rs b/lightning/src/util/test_utils.rs index 34f5d5fe36e..16c5e53a3dd 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::{BroadcastType, ConfirmationTarget}; 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, BroadcastType)]) { // 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); } } From 851f00db4140a8decc6c7bcc987f7171c710be79 Mon Sep 17 00:00:00 2001 From: Elias Rohrer Date: Tue, 27 Jan 2026 13:06:51 +0100 Subject: [PATCH 2/6] Refactor `BroadcastType` with channel context We add the `ChannelId` as context to the just-added `BroadcastType` enum. Co-Authored-By: HAL 9000 Signed-off-by: Elias Rohrer --- lightning-liquidity/src/lsps2/service.rs | 15 +-- lightning/src/chain/chaininterface.rs | 42 +++++-- lightning/src/chain/channelmonitor.rs | 5 +- lightning/src/chain/onchaintx.rs | 41 +++++-- lightning/src/events/bump_transaction/mod.rs | 26 ++-- lightning/src/ln/channelmanager.rs | 28 +++-- lightning/src/util/sweep.rs | 121 ++++++++++--------- 7 files changed, 182 insertions(+), 96 deletions(-) diff --git a/lightning-liquidity/src/lsps2/service.rs b/lightning-liquidity/src/lsps2/service.rs index 6a00a2220fa..97f37c80aaf 100644 --- a/lightning-liquidity/src/lsps2/service.rs +++ b/lightning-liquidity/src/lsps2/service.rs @@ -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, BroadcastType::Funding)]); + if let Some(funding_tx) = jit_channel.get_funding_tx() { + self.tx_broadcaster.broadcast_transactions(&[( + funding_tx, + BroadcastType::Funding { channel_ids: vec![ch_id] }, + )]); + } } } } diff --git a/lightning/src/chain/chaininterface.rs b/lightning/src/chain/chaininterface.rs index 85895b99531..5f57258fc9e 100644 --- a/lightning/src/chain/chaininterface.rs +++ b/lightning/src/chain/chaininterface.rs @@ -15,6 +15,7 @@ use core::{cmp, ops::Deref}; +use crate::ln::types::ChannelId; use crate::prelude::*; use bitcoin::transaction::Transaction; @@ -23,22 +24,47 @@ use bitcoin::transaction::Transaction; /// /// 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, Copy, Debug, Hash, PartialEq, Eq)] +#[derive(Clone, Debug, Hash, PartialEq, Eq)] pub enum BroadcastType { /// A funding transaction establishing a new channel. - Funding, + Funding { + /// The IDs of the channels being funded. + /// + /// A single funding transaction may establish multiple channels when using batch funding. + channel_ids: Vec, + }, /// A cooperative close transaction mutually agreed upon by both parties. - CooperativeClose, + CooperativeClose { + /// The ID of the channel being closed. + channel_id: ChannelId, + }, /// A commitment transaction being broadcast to force-close the channel. - Commitment, + Commitment { + /// The ID of the channel being force-closed. + channel_id: ChannelId, + }, /// An anchor transaction used for CPFP fee-bumping a commitment transaction. - Anchor, + Anchor { + /// The ID of the channel whose commitment transaction is being fee-bumped. + channel_id: ChannelId, + }, /// A transaction claiming outputs from a commitment transaction (HTLC claims, penalty/justice). - Claim, + Claim { + /// The ID of the channel from which outputs are being claimed. + channel_id: ChannelId, + }, /// An HTLC resolution transaction (HTLC-timeout or HTLC-success) for anchor channels. - HtlcResolution, + HtlcResolution { + /// The ID of the channel whose HTLCs are being resolved. + channel_id: ChannelId, + }, /// A transaction sweeping spendable outputs to the user's wallet. - Sweep, + 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. diff --git a/lightning/src/chain/channelmonitor.rs b/lightning/src/chain/channelmonitor.rs index 515a3dc5f1d..5f1106417fc 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 diff --git a/lightning/src/chain/onchaintx.rs b/lightning/src/chain/onchaintx.rs index c7d085c99ab..f3494d6db55 100644 --- a/lightning/src/chain/onchaintx.rs +++ b/lightning/src/chain/onchaintx.rs @@ -35,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::{ @@ -223,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. @@ -285,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 && @@ -345,7 +348,9 @@ impl OnchainTxHandler { entry.write(writer)?; } - write_tlv_fields!(writer, {}); + write_tlv_fields!(writer, { + (1, self.channel_id, required), + }); Ok(()) } } @@ -369,7 +374,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,12 +426,24 @@ impl<'a, 'b, ES: EntropySource, SP: SignerProvider> ReadableArgs<(&'a ES, &'b SP } } - read_tlv_fields!(reader, {}); + let mut channel_id = None; + read_tlv_fields!(reader, { + (1, channel_id, option), + }); + + // For backwards compatibility with monitors serialized before channel_id was added, + // we derive a channel_id from the funding outpoint if not present. + let channel_id = channel_id.or_else(|| { + channel_parameters.funding_outpoint + .map(|op| ChannelId::v1_from_funding_outpoint(op)) + }) // funding_outpoint must be known during intialization. + .ok_or(DecodeError::InvalidValue)?; 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, @@ -446,11 +463,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, @@ -518,7 +537,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, BroadcastType::Claim)]); + broadcaster.broadcast_transactions(&[(&tx.0, BroadcastType::Claim { channel_id: self.channel_id })]); } else { log_info!(logger, "Waiting for signature of unsigned onchain transaction {}", tx.0.compute_txid()); } @@ -865,7 +884,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, BroadcastType::Claim)]); + broadcaster.broadcast_transactions(&[(&tx.0, BroadcastType::Claim { channel_id: self.channel_id })]); } else { log_info!(logger, "Waiting for signature of unsigned onchain transaction {}", tx.0.compute_txid()); } @@ -1086,7 +1105,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, BroadcastType::Claim)]); + broadcaster.broadcast_transactions(&[(&bump_tx.0, BroadcastType::Claim { channel_id: self.channel_id })]); } else { log_info!(logger, "Waiting for signature of RBF-bumped unsigned onchain transaction {}", bump_tx.0.compute_txid()); @@ -1189,7 +1208,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, BroadcastType::Claim)]); + broadcaster.broadcast_transactions(&[(&bump_tx.0, BroadcastType::Claim { channel_id: self.channel_id })]); } else { log_info!(logger, "Waiting for signature of unsigned onchain transaction {}", bump_tx.0.compute_txid()); } @@ -1283,6 +1302,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}; @@ -1367,6 +1387,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 3918774e962..5ec34b3efb1 100644 --- a/lightning/src/events/bump_transaction/mod.rs +++ b/lightning/src/events/bump_transaction/mod.rs @@ -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, BroadcastType::Commitment)]); + self.broadcaster.broadcast_transactions(&[( + &commitment_tx, + BroadcastType::Commitment { channel_id }, + )]); return Ok(()); } @@ -956,8 +959,8 @@ where commitment_tx.compute_txid() ); self.broadcaster.broadcast_transactions(&[ - (&commitment_tx, BroadcastType::Commitment), - (&anchor_tx, BroadcastType::Anchor), + (&commitment_tx, BroadcastType::Commitment { channel_id }), + (&anchor_tx, BroadcastType::Anchor { channel_id }), ]); return Ok(()); } @@ -966,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] @@ -1191,7 +1194,10 @@ where } log_info!(self.logger, "Broadcasting {}", log_tx!(htlc_tx)); - self.broadcaster.broadcast_transactions(&[(&htlc_tx, BroadcastType::HtlcResolution)]); + self.broadcaster.broadcast_transactions(&[( + &htlc_tx, + BroadcastType::HtlcResolution { channel_id }, + )]); } Ok(()) @@ -1201,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, @@ -1215,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, @@ -1231,6 +1239,7 @@ where }); }, BumpTransactionEvent::HTLCResolution { + channel_id, claim_id, target_feerate_sat_per_1000_weight, htlc_descriptors, @@ -1244,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 e59e84c4c29..00b24c0a0e2 100644 --- a/lightning/src/ln/channelmanager.rs +++ b/lightning/src/ln/channelmanager.rs @@ -6548,7 +6548,10 @@ where "Broadcasting signed interactive funding transaction {}", funding_tx.compute_txid() ); - self.tx_broadcaster.broadcast_transactions(&[(funding_tx, BroadcastType::Funding)]); + self.tx_broadcaster.broadcast_transactions(&[( + funding_tx, + BroadcastType::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 +9466,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 +9477,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 +9486,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, BroadcastType::Funding)]); + self.tx_broadcaster.broadcast_transactions(&[( + &tx, + BroadcastType::Funding { channel_ids: batch_channel_ids }, + )]); } } } @@ -10147,7 +10155,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, BroadcastType::Funding)]); + self.tx_broadcaster.broadcast_transactions(&[( + &tx, + BroadcastType::Funding { channel_ids: vec![channel.context.channel_id()] }, + )]); } } @@ -11608,8 +11619,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, BroadcastType::CooperativeClose)]); + self.tx_broadcaster.broadcast_transactions(&[( + &broadcast_tx, + BroadcastType::CooperativeClose { channel_id: msg.channel_id }, + )]); let _ = self.handle_error(err, *counterparty_node_id); } Ok(()) @@ -12938,7 +12951,7 @@ This indicates a bug inside LDK. Please report this error at https://github.com/ log_info!(logger, "Broadcasting closing tx {}", log_tx!(broadcast_tx)); self.tx_broadcaster.broadcast_transactions(&[( &broadcast_tx, - BroadcastType::CooperativeClose, + BroadcastType::CooperativeClose { channel_id }, )]); } } else { @@ -13057,6 +13070,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, @@ -13068,7 +13082,7 @@ This indicates a bug inside LDK. Please report this error at https://github.com/ log_info!(logger, "Broadcasting {}", log_tx!(tx)); self.tx_broadcaster.broadcast_transactions(&[( &tx, - BroadcastType::CooperativeClose, + BroadcastType::CooperativeClose { channel_id }, )]); false } else { diff --git a/lightning/src/util/sweep.rs b/lightning/src/util/sweep.rs index 6475b805a5b..397111526c3 100644 --- a/lightning/src/util/sweep.rs +++ b/lightning/src/util/sweep.rs @@ -525,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, BroadcastType::Sweep)]); + if let Some((spending_tx, channel_ids)) = spending_tx_and_chan_id { + self.broadcaster + .broadcast_transactions(&[(&spending_tx, BroadcastType::Sweep { channel_ids })]); } Ok(()) From 6ba7461aefe658a0cff998bce2f23c67eea68b75 Mon Sep 17 00:00:00 2001 From: Elias Rohrer Date: Tue, 27 Jan 2026 13:06:51 +0100 Subject: [PATCH 3/6] Refactor `TransactionType` with channel context We add the `ChannelId` as context to the just-added `TransactionType` enum. Co-Authored-By: HAL 9000 Signed-off-by: Elias Rohrer --- fuzz/src/chanmon_consistency.rs | 4 ++-- fuzz/src/full_stack.rs | 4 ++-- lightning-liquidity/.cargo/config.toml | 2 ++ lightning-liquidity/src/lsps2/service.rs | 4 ++-- lightning/src/chain/chaininterface.rs | 6 +++--- lightning/src/chain/onchaintx.rs | 10 +++++----- lightning/src/events/bump_transaction/mod.rs | 10 +++++----- lightning/src/ln/channelmanager.rs | 15 ++++++++------- lightning/src/util/sweep.rs | 4 ++-- lightning/src/util/test_utils.rs | 4 ++-- 10 files changed, 33 insertions(+), 30 deletions(-) create mode 100644 lightning-liquidity/.cargo/config.toml diff --git a/fuzz/src/chanmon_consistency.rs b/fuzz/src/chanmon_consistency.rs index 6e4b70e4cd8..f363da534ac 100644 --- a/fuzz/src/chanmon_consistency.rs +++ b/fuzz/src/chanmon_consistency.rs @@ -37,7 +37,7 @@ use lightning::blinded_path::message::{BlindedMessagePath, MessageContext, Messa use lightning::blinded_path::payment::{BlindedPaymentPath, ReceiveTlvs}; use lightning::chain; use lightning::chain::chaininterface::{ - BroadcastType, BroadcasterInterface, ConfirmationTarget, FeeEstimator, + TransactionType, BroadcasterInterface, ConfirmationTarget, FeeEstimator, }; use lightning::chain::channelmonitor::{ChannelMonitor, MonitorEvent}; use lightning::chain::transaction::OutPoint; @@ -161,7 +161,7 @@ pub struct TestBroadcaster { txn_broadcasted: RefCell>, } impl BroadcasterInterface for TestBroadcaster { - fn broadcast_transactions(&self, txs: &[(&Transaction, BroadcastType)]) { + 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 3f51010d28a..e3bcb179fcf 100644 --- a/fuzz/src/full_stack.rs +++ b/fuzz/src/full_stack.rs @@ -34,7 +34,7 @@ use lightning::blinded_path::message::{BlindedMessagePath, MessageContext, Messa use lightning::blinded_path::payment::{BlindedPaymentPath, ReceiveTlvs}; use lightning::chain; use lightning::chain::chaininterface::{ - BroadcastType, BroadcasterInterface, ConfirmationTarget, FeeEstimator, + TransactionType, BroadcasterInterface, ConfirmationTarget, FeeEstimator, }; use lightning::chain::chainmonitor; use lightning::chain::transaction::OutPoint; @@ -186,7 +186,7 @@ struct TestBroadcaster { txn_broadcasted: Mutex>, } impl BroadcasterInterface for TestBroadcaster { - fn broadcast_transactions(&self, txs: &[(&Transaction, BroadcastType)]) { + 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 97f37c80aaf..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::{BroadcastType, BroadcasterInterface}; +use lightning::chain::chaininterface::{BroadcasterInterface, TransactionType}; use lightning::events::HTLCHandlingFailureType; use lightning::ln::channelmanager::{AChannelManager, FailureCode, InterceptId}; use lightning::ln::msgs::{ErrorAction, LightningError}; @@ -2038,7 +2038,7 @@ where if let Some(funding_tx) = jit_channel.get_funding_tx() { self.tx_broadcaster.broadcast_transactions(&[( funding_tx, - BroadcastType::Funding { channel_ids: vec![ch_id] }, + TransactionType::Funding { channel_ids: vec![ch_id] }, )]); } } diff --git a/lightning/src/chain/chaininterface.rs b/lightning/src/chain/chaininterface.rs index 5f57258fc9e..55dd1418cde 100644 --- a/lightning/src/chain/chaininterface.rs +++ b/lightning/src/chain/chaininterface.rs @@ -25,7 +25,7 @@ use bitcoin::transaction::Transaction; /// 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 BroadcastType { +pub enum TransactionType { /// A funding transaction establishing a new channel. Funding { /// The IDs of the channels being funded. @@ -94,9 +94,9 @@ pub trait BroadcasterInterface { /// Bitcoin transaction packages are defined in BIP 331 and here: /// /// - /// Each transaction is paired with a [`BroadcastType`] indicating the class of 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, BroadcastType)]); + 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/onchaintx.rs b/lightning/src/chain/onchaintx.rs index f3494d6db55..1d4c8084825 100644 --- a/lightning/src/chain/onchaintx.rs +++ b/lightning/src/chain/onchaintx.rs @@ -24,7 +24,7 @@ use bitcoin::transaction::Transaction; use crate::chain::chaininterface::ConfirmationTarget; use crate::chain::chaininterface::{ - BroadcastType, BroadcasterInterface, FeeEstimator, LowerBoundedFeeEstimator, + BroadcasterInterface, FeeEstimator, LowerBoundedFeeEstimator, TransactionType, }; use crate::chain::channelmonitor::ANTI_REORG_DELAY; use crate::chain::package::{PackageSolvingData, PackageTemplate}; @@ -537,7 +537,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, BroadcastType::Claim { channel_id: self.channel_id })]); + 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()); } @@ -884,7 +884,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, BroadcastType::Claim { channel_id: self.channel_id })]); + 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()); } @@ -1105,7 +1105,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, BroadcastType::Claim { channel_id: self.channel_id })]); + 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()); @@ -1208,7 +1208,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, BroadcastType::Claim { channel_id: self.channel_id })]); + 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()); } diff --git a/lightning/src/events/bump_transaction/mod.rs b/lightning/src/events/bump_transaction/mod.rs index 5ec34b3efb1..dbc07cc703a 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, BroadcastType, BroadcasterInterface, + compute_feerate_sat_per_1000_weight, fee_for_weight, BroadcasterInterface, TransactionType, }; use crate::chain::ClaimId; use crate::io_extras::sink; @@ -790,7 +790,7 @@ where package_target_feerate_sat_per_1000_weight); self.broadcaster.broadcast_transactions(&[( &commitment_tx, - BroadcastType::Commitment { channel_id }, + TransactionType::Commitment { channel_id }, )]); return Ok(()); } @@ -959,8 +959,8 @@ where commitment_tx.compute_txid() ); self.broadcaster.broadcast_transactions(&[ - (&commitment_tx, BroadcastType::Commitment { channel_id }), - (&anchor_tx, BroadcastType::Anchor { channel_id }), + (&commitment_tx, TransactionType::Commitment { channel_id }), + (&anchor_tx, TransactionType::Anchor { channel_id }), ]); return Ok(()); } @@ -1196,7 +1196,7 @@ where log_info!(self.logger, "Broadcasting {}", log_tx!(htlc_tx)); self.broadcaster.broadcast_transactions(&[( &htlc_tx, - BroadcastType::HtlcResolution { channel_id }, + TransactionType::HtlcResolution { channel_id }, )]); } diff --git a/lightning/src/ln/channelmanager.rs b/lightning/src/ln/channelmanager.rs index 00b24c0a0e2..ea6f71e0d7a 100644 --- a/lightning/src/ln/channelmanager.rs +++ b/lightning/src/ln/channelmanager.rs @@ -39,7 +39,8 @@ use crate::blinded_path::payment::{AsyncBolt12OfferContext, Bolt12OfferContext, use crate::blinded_path::NodeIdLookUp; use crate::chain; use crate::chain::chaininterface::{ - BroadcastType, BroadcasterInterface, ConfirmationTarget, FeeEstimator, LowerBoundedFeeEstimator, + BroadcasterInterface, ConfirmationTarget, FeeEstimator, LowerBoundedFeeEstimator, + TransactionType, }; use crate::chain::channelmonitor::{ Balance, ChannelMonitor, ChannelMonitorUpdate, ChannelMonitorUpdateStep, MonitorEvent, @@ -6550,7 +6551,7 @@ where ); self.tx_broadcaster.broadcast_transactions(&[( funding_tx, - BroadcastType::Funding { channel_ids: vec![channel.context().channel_id()] }, + TransactionType::Funding { channel_ids: vec![channel.context().channel_id()] }, )]); { let mut pending_events = self.pending_events.lock().unwrap(); @@ -9488,7 +9489,7 @@ This indicates a bug inside LDK. Please report this error at https://github.com/ log_info!(self.logger, "Broadcasting batch funding tx {}", tx.compute_txid()); self.tx_broadcaster.broadcast_transactions(&[( &tx, - BroadcastType::Funding { channel_ids: batch_channel_ids }, + TransactionType::Funding { channel_ids: batch_channel_ids }, )]); } } @@ -10157,7 +10158,7 @@ This indicates a bug inside LDK. Please report this error at https://github.com/ log_info!(logger, "Broadcasting funding transaction with txid {}", tx.compute_txid()); self.tx_broadcaster.broadcast_transactions(&[( &tx, - BroadcastType::Funding { channel_ids: vec![channel.context.channel_id()] }, + TransactionType::Funding { channel_ids: vec![channel.context.channel_id()] }, )]); } } @@ -11621,7 +11622,7 @@ This indicates a bug inside LDK. Please report this error at https://github.com/ log_info!(logger, "Broadcasting {}", log_tx!(broadcast_tx)); self.tx_broadcaster.broadcast_transactions(&[( &broadcast_tx, - BroadcastType::CooperativeClose { channel_id: msg.channel_id }, + TransactionType::CooperativeClose { channel_id: msg.channel_id }, )]); let _ = self.handle_error(err, *counterparty_node_id); } @@ -12951,7 +12952,7 @@ This indicates a bug inside LDK. Please report this error at https://github.com/ log_info!(logger, "Broadcasting closing tx {}", log_tx!(broadcast_tx)); self.tx_broadcaster.broadcast_transactions(&[( &broadcast_tx, - BroadcastType::CooperativeClose { channel_id }, + TransactionType::CooperativeClose { channel_id }, )]); } } else { @@ -13082,7 +13083,7 @@ This indicates a bug inside LDK. Please report this error at https://github.com/ log_info!(logger, "Broadcasting {}", log_tx!(tx)); self.tx_broadcaster.broadcast_transactions(&[( &tx, - BroadcastType::CooperativeClose { channel_id }, + TransactionType::CooperativeClose { channel_id }, )]); false } else { diff --git a/lightning/src/util/sweep.rs b/lightning/src/util/sweep.rs index 397111526c3..167ef84a28f 100644 --- a/lightning/src/util/sweep.rs +++ b/lightning/src/util/sweep.rs @@ -9,7 +9,7 @@ //! sweeping them. use crate::chain::chaininterface::{ - BroadcastType, BroadcasterInterface, ConfirmationTarget, FeeEstimator, + BroadcasterInterface, ConfirmationTarget, FeeEstimator, TransactionType, }; use crate::chain::channelmonitor::{ANTI_REORG_DELAY, ARCHIVAL_DELAY_BLOCKS}; use crate::chain::{self, BestBlock, Confirm, Filter, Listen, WatchedOutput}; @@ -597,7 +597,7 @@ where // Persistence completely successfully. If we have a spending transaction, we broadcast it. if let Some((spending_tx, channel_ids)) = spending_tx_and_chan_id { self.broadcaster - .broadcast_transactions(&[(&spending_tx, BroadcastType::Sweep { channel_ids })]); + .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 16c5e53a3dd..f5c73ca4ca3 100644 --- a/lightning/src/util/test_utils.rs +++ b/lightning/src/util/test_utils.rs @@ -14,7 +14,7 @@ use crate::chain; use crate::chain::chaininterface; #[cfg(any(test, feature = "_externalize_tests"))] use crate::chain::chaininterface::FEERATE_FLOOR_SATS_PER_KW; -use crate::chain::chaininterface::{BroadcastType, ConfirmationTarget}; +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, BroadcastType)]) { + 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 // . From 05b61e07b88fd3b9a8992e0584d6852dad18bab7 Mon Sep 17 00:00:00 2001 From: Elias Rohrer Date: Tue, 27 Jan 2026 14:53:01 +0100 Subject: [PATCH 4/6] f Improve docs, merge some variants --- lightning/src/chain/chaininterface.rs | 22 +++++++++----------- lightning/src/events/bump_transaction/mod.rs | 8 +++---- 2 files changed, 14 insertions(+), 16 deletions(-) diff --git a/lightning/src/chain/chaininterface.rs b/lightning/src/chain/chaininterface.rs index 55dd1418cde..48a0303f970 100644 --- a/lightning/src/chain/chaininterface.rs +++ b/lightning/src/chain/chaininterface.rs @@ -33,19 +33,19 @@ pub enum TransactionType { /// A single funding transaction may establish multiple channels when using batch funding. channel_ids: Vec, }, - /// A cooperative close transaction mutually agreed upon by both parties. + /// A transaction cooperatively closing a channel. CooperativeClose { /// The ID of the channel being closed. channel_id: ChannelId, }, - /// A commitment transaction being broadcast to force-close the channel. - Commitment { + /// A transaction being broadcast to force-close the channel. + UnilateralClose { /// The ID of the channel being force-closed. channel_id: ChannelId, }, - /// An anchor transaction used for CPFP fee-bumping a commitment transaction. - Anchor { - /// The ID of the channel whose commitment transaction is being fee-bumped. + /// 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). @@ -53,12 +53,10 @@ pub enum TransactionType { /// The ID of the channel from which outputs are being claimed. channel_id: ChannelId, }, - /// An HTLC resolution transaction (HTLC-timeout or HTLC-success) for anchor channels. - HtlcResolution { - /// The ID of the channel whose HTLCs are being resolved. - channel_id: ChannelId, - }, - /// A transaction sweeping spendable outputs to the user's wallet. + /// 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. /// diff --git a/lightning/src/events/bump_transaction/mod.rs b/lightning/src/events/bump_transaction/mod.rs index dbc07cc703a..97cca12ccdc 100644 --- a/lightning/src/events/bump_transaction/mod.rs +++ b/lightning/src/events/bump_transaction/mod.rs @@ -790,7 +790,7 @@ where package_target_feerate_sat_per_1000_weight); self.broadcaster.broadcast_transactions(&[( &commitment_tx, - TransactionType::Commitment { channel_id }, + TransactionType::UnilateralClose { channel_id }, )]); return Ok(()); } @@ -959,8 +959,8 @@ where commitment_tx.compute_txid() ); self.broadcaster.broadcast_transactions(&[ - (&commitment_tx, TransactionType::Commitment { channel_id }), - (&anchor_tx, TransactionType::Anchor { channel_id }), + (&commitment_tx, TransactionType::UnilateralClose { channel_id }), + (&anchor_tx, TransactionType::AnchorBump { channel_id }), ]); return Ok(()); } @@ -1196,7 +1196,7 @@ where log_info!(self.logger, "Broadcasting {}", log_tx!(htlc_tx)); self.broadcaster.broadcast_transactions(&[( &htlc_tx, - TransactionType::HtlcResolution { channel_id }, + TransactionType::UnilateralClose { channel_id }, )]); } From 754eb5fef3f9d829e4e00bb97e2c43bf8f16a275 Mon Sep 17 00:00:00 2001 From: Elias Rohrer Date: Wed, 28 Jan 2026 10:34:20 +0100 Subject: [PATCH 5/6] f Set channel_id after the fact --- lightning/src/chain/channelmonitor.rs | 3 ++- lightning/src/chain/onchaintx.rs | 29 ++++++++++++++------------- 2 files changed, 17 insertions(+), 15 deletions(-) diff --git a/lightning/src/chain/channelmonitor.rs b/lightning/src/chain/channelmonitor.rs index 5f1106417fc..f5966b48e0e 100644 --- a/lightning/src/chain/channelmonitor.rs +++ b/lightning/src/chain/channelmonitor.rs @@ -6618,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) )?; @@ -6714,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/onchaintx.rs b/lightning/src/chain/onchaintx.rs index 1d4c8084825..349d8979054 100644 --- a/lightning/src/chain/onchaintx.rs +++ b/lightning/src/chain/onchaintx.rs @@ -348,11 +348,17 @@ impl OnchainTxHandler { entry.write(writer)?; } - write_tlv_fields!(writer, { - (1, self.channel_id, required), - }); + 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])> @@ -426,18 +432,13 @@ impl<'a, 'b, ES: EntropySource, SP: SignerProvider> ReadableArgs<(&'a ES, &'b SP } } - let mut channel_id = None; - read_tlv_fields!(reader, { - (1, channel_id, option), - }); + read_tlv_fields!(reader, {}); - // For backwards compatibility with monitors serialized before channel_id was added, - // we derive a channel_id from the funding outpoint if not present. - let channel_id = channel_id.or_else(|| { - channel_parameters.funding_outpoint - .map(|op| ChannelId::v1_from_funding_outpoint(op)) - }) // funding_outpoint must be known during intialization. - .ok_or(DecodeError::InvalidValue)?; + // `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()); From 5912fd424c1ab2e3968710804ce7654f539c08ee Mon Sep 17 00:00:00 2001 From: Elias Rohrer Date: Tue, 27 Jan 2026 14:03:53 +0100 Subject: [PATCH 6/6] Drop unused imports --- lightning/src/chain/mod.rs | 1 - lightning/src/util/sweep.rs | 2 +- 2 files changed, 1 insertion(+), 2 deletions(-) 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/util/sweep.rs b/lightning/src/util/sweep.rs index 167ef84a28f..db06340e297 100644 --- a/lightning/src/util/sweep.rs +++ b/lightning/src/util/sweep.rs @@ -28,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;