Skip to content

Commit bbb5ed5

Browse files
authored
Merge pull request #4353 from tnull/2026-01-broadcast-type-refactor
Refactor `BroadcasterInterface` to include `TransactionType`
2 parents 86f914c + 0715f4a commit bbb5ed5

File tree

19 files changed

+357
-134
lines changed

19 files changed

+357
-134
lines changed

fuzz/src/chanmon_consistency.rs

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,9 @@ use bitcoin::WPubkeyHash;
3636
use lightning::blinded_path::message::{BlindedMessagePath, MessageContext, MessageForwardNode};
3737
use lightning::blinded_path::payment::{BlindedPaymentPath, ReceiveTlvs};
3838
use lightning::chain;
39-
use lightning::chain::chaininterface::{BroadcasterInterface, ConfirmationTarget, FeeEstimator};
39+
use lightning::chain::chaininterface::{
40+
TransactionType, BroadcasterInterface, ConfirmationTarget, FeeEstimator,
41+
};
4042
use lightning::chain::channelmonitor::{ChannelMonitor, MonitorEvent};
4143
use lightning::chain::transaction::OutPoint;
4244
use lightning::chain::{
@@ -159,8 +161,8 @@ pub struct TestBroadcaster {
159161
txn_broadcasted: RefCell<Vec<Transaction>>,
160162
}
161163
impl BroadcasterInterface for TestBroadcaster {
162-
fn broadcast_transactions(&self, txs: &[&Transaction]) {
163-
for tx in txs {
164+
fn broadcast_transactions(&self, txs: &[(&Transaction, TransactionType)]) {
165+
for (tx, _broadcast_type) in txs {
164166
self.txn_broadcasted.borrow_mut().push((*tx).clone());
165167
}
166168
}

fuzz/src/full_stack.rs

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,9 @@ use lightning::ln::funding::{FundingTxInput, SpliceContribution};
3535
use lightning::blinded_path::message::{BlindedMessagePath, MessageContext, MessageForwardNode};
3636
use lightning::blinded_path::payment::{BlindedPaymentPath, ReceiveTlvs};
3737
use lightning::chain;
38-
use lightning::chain::chaininterface::{BroadcasterInterface, ConfirmationTarget, FeeEstimator};
38+
use lightning::chain::chaininterface::{
39+
TransactionType, BroadcasterInterface, ConfirmationTarget, FeeEstimator,
40+
};
3941
use lightning::chain::chainmonitor;
4042
use lightning::chain::transaction::OutPoint;
4143
use lightning::chain::{BestBlock, ChannelMonitorUpdateStatus, Confirm, Listen};
@@ -188,8 +190,8 @@ struct TestBroadcaster {
188190
txn_broadcasted: Mutex<Vec<Transaction>>,
189191
}
190192
impl BroadcasterInterface for TestBroadcaster {
191-
fn broadcast_transactions(&self, txs: &[&Transaction]) {
192-
let owned_txs: Vec<Transaction> = txs.iter().map(|tx| (*tx).clone()).collect();
193+
fn broadcast_transactions(&self, txs: &[(&Transaction, TransactionType)]) {
194+
let owned_txs: Vec<Transaction> = txs.iter().map(|(tx, _)| (*tx).clone()).collect();
193195
self.txn_broadcasted.lock().unwrap().extend(owned_txs);
194196
}
195197
}

lightning-liquidity/src/lsps2/service.rs

Lines changed: 9 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,7 @@ use crate::prelude::{new_hash_map, HashMap};
4040
use crate::sync::{Arc, Mutex, MutexGuard, RwLock};
4141
use crate::utils::async_poll::dummy_waker;
4242

43-
use lightning::chain::chaininterface::BroadcasterInterface;
43+
use lightning::chain::chaininterface::{BroadcasterInterface, TransactionType};
4444
use lightning::events::HTLCHandlingFailureType;
4545
use lightning::ln::channelmanager::{AChannelManager, FailureCode, InterceptId};
4646
use lightning::ln::msgs::{ErrorAction, LightningError};
@@ -2019,23 +2019,24 @@ where
20192019
// (for example when a forwarded HTLC nears expiry). Broadcasting funding after a
20202020
// close could then confirm the commitment and trigger unintended on‑chain handling.
20212021
// To avoid this, we check ChannelManager’s view (`is_channel_ready`) before broadcasting.
2022-
let channel_id_opt = jit_channel.get_channel_id();
2023-
if let Some(ch_id) = channel_id_opt {
2022+
if let Some(ch_id) = jit_channel.get_channel_id() {
20242023
let is_channel_ready = self
20252024
.channel_manager
20262025
.get_cm()
20272026
.list_channels()
20282027
.into_iter()
20292028
.any(|cd| cd.channel_id == ch_id && cd.is_channel_ready);
2029+
20302030
if !is_channel_ready {
20312031
return;
20322032
}
2033-
} else {
2034-
return;
2035-
}
20362033

2037-
if let Some(funding_tx) = jit_channel.get_funding_tx() {
2038-
self.tx_broadcaster.broadcast_transactions(&[funding_tx]);
2034+
if let Some(funding_tx) = jit_channel.get_funding_tx() {
2035+
self.tx_broadcaster.broadcast_transactions(&[(
2036+
funding_tx,
2037+
TransactionType::Funding { channel_ids: vec![ch_id] },
2038+
)]);
2039+
}
20392040
}
20402041
}
20412042
}

lightning/src/chain/chaininterface.rs

Lines changed: 92 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,10 +15,97 @@
1515
1616
use core::{cmp, ops::Deref};
1717

18+
use crate::ln::types::ChannelId;
1819
use crate::prelude::*;
1920

2021
use bitcoin::transaction::Transaction;
2122

23+
/// Represents the class of transaction being broadcast.
24+
///
25+
/// This is used to provide context about the type of transaction being broadcast, which may be
26+
/// useful for logging, filtering, or prioritization purposes.
27+
#[derive(Clone, Debug, Hash, PartialEq, Eq)]
28+
pub enum TransactionType {
29+
/// A funding transaction establishing a new channel.
30+
///
31+
/// If we initiated the channel the transaction given to
32+
/// [`ChannelManager::funding_transaction_generated`] will be broadcast with this type.
33+
///
34+
/// [`ChannelManager::funding_transaction_generated`]: crate::ln::channelmanager::ChannelManager::funding_transaction_generated
35+
Funding {
36+
/// The IDs of the channels being funded.
37+
///
38+
/// A single funding transaction may establish multiple channels when using batch funding.
39+
channel_ids: Vec<ChannelId>,
40+
},
41+
/// A transaction cooperatively closing a channel.
42+
///
43+
/// A transaction of this type will be broadcast when cooperatively closing a channel via
44+
/// [`ChannelManager::close_channel`] or if the counterparty closes the channel.
45+
///
46+
/// [`ChannelManager::close_channel`]: crate::ln::channelmanager::ChannelManager::close_channel
47+
CooperativeClose {
48+
/// The ID of the channel being closed.
49+
channel_id: ChannelId,
50+
},
51+
/// A transaction being broadcast to force-close the channel.
52+
///
53+
/// A transaction of this type will be broadcast when unilaterally closing a channel via
54+
/// [`ChannelManager::force_close_broadcasting_latest_txn`] or if the counterparty force-closes
55+
/// the channel.
56+
///
57+
/// [`ChannelManager::force_close_broadcasting_latest_txn`]: crate::ln::channelmanager::ChannelManager::force_close_broadcasting_latest_txn
58+
UnilateralClose {
59+
/// The ID of the channel being force-closed.
60+
channel_id: ChannelId,
61+
},
62+
/// An anchor bumping transaction used for CPFP fee-bumping a closing transaction.
63+
///
64+
/// This will be broadcast after an anchor channel has been closed. See
65+
/// [`BumpTransactionEvent`] for more information.
66+
///
67+
/// [`BumpTransactionEvent`]: crate::events::bump_transaction::BumpTransactionEvent
68+
AnchorBump {
69+
/// The ID of the channel whose closing transaction is being fee-bumped.
70+
channel_id: ChannelId,
71+
},
72+
/// A transaction which is resolving an output spendable by both us and our counterparty.
73+
///
74+
/// When a channel closes via the unilateral close path, there may be transaction outputs which
75+
/// are spendable by either our counterparty or us and represent some lightning state. In order
76+
/// to resolve that state, the [`ChannelMonitor`] will spend any such outputs, ensuring funds
77+
/// are only available to us prior to generating an [`Event::SpendableOutputs`]. This
78+
/// transaction is one such transaction - resolving in-flight HTLCs or punishing our
79+
/// counterparty if they broadcasted an outdated state.
80+
///
81+
/// [`ChannelMonitor`]: crate::chain::ChannelMonitor
82+
/// [`Event::SpendableOutputs`]: crate::events::Event::SpendableOutputs
83+
Claim {
84+
/// The ID of the channel from which outputs are being claimed.
85+
channel_id: ChannelId,
86+
},
87+
/// A transaction generated by the [`OutputSweeper`], sweeping [`SpendableOutputDescriptor`]s
88+
/// to the user's wallet.
89+
///
90+
/// [`OutputSweeper`]: crate::util::sweep::OutputSweeper
91+
/// [`SpendableOutputDescriptor`]: crate::sign::SpendableOutputDescriptor
92+
Sweep {
93+
/// The IDs of the channels from which outputs are being swept, if known.
94+
///
95+
/// A single sweep transaction may aggregate outputs from multiple channels.
96+
channel_ids: Vec<ChannelId>,
97+
},
98+
/// A splice transaction modifying an existing channel's funding.
99+
///
100+
/// A transaction of this type will be broadcast as a result of a [`ChannelManager::splice_channel`] operation.
101+
///
102+
/// [`ChannelManager::splice_channel`]: crate::ln::channelmanager::ChannelManager::splice_channel
103+
Splice {
104+
/// The ID of the channel being spliced.
105+
channel_id: ChannelId,
106+
},
107+
}
108+
22109
// TODO: Define typed abstraction over feerates to handle their conversions.
23110
pub(crate) fn compute_feerate_sat_per_1000_weight(fee_sat: u64, weight: u64) -> u32 {
24111
(fee_sat * 1000 / weight).try_into().unwrap_or(u32::max_value())
@@ -45,11 +132,14 @@ pub trait BroadcasterInterface {
45132
///
46133
/// Bitcoin transaction packages are defined in BIP 331 and here:
47134
/// <https://github.com/bitcoin/bitcoin/blob/master/doc/policy/packages.md>
48-
fn broadcast_transactions(&self, txs: &[&Transaction]);
135+
///
136+
/// Each transaction is paired with a [`TransactionType`] indicating the class of transaction
137+
/// being broadcast, which may be useful for logging, filtering, or prioritization.
138+
fn broadcast_transactions(&self, txs: &[(&Transaction, TransactionType)]);
49139
}
50140

51141
impl<T: BroadcasterInterface + ?Sized, B: Deref<Target = T>> BroadcasterInterface for B {
52-
fn broadcast_transactions(&self, txs: &[&Transaction]) {
142+
fn broadcast_transactions(&self, txs: &[(&Transaction, TransactionType)]) {
53143
self.deref().broadcast_transactions(txs)
54144
}
55145
}

lightning/src/chain/channelmonitor.rs

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1879,8 +1879,9 @@ impl<Signer: EcdsaChannelSigner> ChannelMonitor<Signer> {
18791879
initial_holder_commitment_tx.trust().commitment_number();
18801880

18811881
let onchain_tx_handler = OnchainTxHandler::new(
1882-
channel_parameters.channel_value_satoshis, channel_keys_id, destination_script.into(),
1883-
keys, channel_parameters.clone(), initial_holder_commitment_tx.clone(), secp_ctx
1882+
channel_id, channel_parameters.channel_value_satoshis, channel_keys_id,
1883+
destination_script.into(), keys, channel_parameters.clone(),
1884+
initial_holder_commitment_tx.clone(), secp_ctx,
18841885
);
18851886

18861887
let funding_outpoint = channel_parameters.funding_outpoint
@@ -6491,7 +6492,7 @@ impl<'a, 'b, ES: EntropySource, SP: SignerProvider> ReadableArgs<(&'a ES, &'b SP
64916492
return Err(DecodeError::InvalidValue);
64926493
}
64936494
}
6494-
let onchain_tx_handler: OnchainTxHandler<SP::EcdsaSigner> = ReadableArgs::read(
6495+
let mut onchain_tx_handler: OnchainTxHandler<SP::EcdsaSigner> = ReadableArgs::read(
64956496
reader, (entropy_source, signer_provider, channel_value_satoshis, channel_keys_id)
64966497
)?;
64976498

@@ -6587,6 +6588,7 @@ impl<'a, 'b, ES: EntropySource, SP: SignerProvider> ReadableArgs<(&'a ES, &'b SP
65876588
}
65886589

65896590
let channel_id = channel_id.unwrap_or(ChannelId::v1_from_funding_outpoint(outpoint));
6591+
onchain_tx_handler.set_channel_id(channel_id);
65906592

65916593
let (current_holder_commitment_tx, current_holder_htlc_data) = {
65926594
let holder_commitment_tx = onchain_tx_handler.current_holder_commitment_tx();

lightning/src/chain/mod.rs

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,6 @@ use bitcoin::secp256k1::PublicKey;
2020

2121
use crate::chain::channelmonitor::{ChannelMonitor, ChannelMonitorUpdate, MonitorEvent};
2222
use crate::chain::transaction::{OutPoint, TransactionData};
23-
use crate::impl_writeable_tlv_based;
2423
use crate::ln::types::ChannelId;
2524
use crate::sign::ecdsa::EcdsaChannelSigner;
2625
use crate::sign::HTLCDescriptor;

lightning/src/chain/onchaintx.rs

Lines changed: 33 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,9 @@ use bitcoin::transaction::OutPoint as BitcoinOutPoint;
2323
use bitcoin::transaction::Transaction;
2424

2525
use crate::chain::chaininterface::ConfirmationTarget;
26-
use crate::chain::chaininterface::{BroadcasterInterface, FeeEstimator, LowerBoundedFeeEstimator};
26+
use crate::chain::chaininterface::{
27+
BroadcasterInterface, FeeEstimator, LowerBoundedFeeEstimator, TransactionType,
28+
};
2729
use crate::chain::channelmonitor::ANTI_REORG_DELAY;
2830
use crate::chain::package::{PackageSolvingData, PackageTemplate};
2931
use crate::chain::transaction::MaybeSignedTransaction;
@@ -33,6 +35,7 @@ use crate::ln::chan_utils::{
3335
HTLCOutputInCommitment, HolderCommitmentTransaction,
3436
};
3537
use crate::ln::msgs::DecodeError;
38+
use crate::ln::types::ChannelId;
3639
use crate::sign::{ecdsa::EcdsaChannelSigner, EntropySource, HTLCDescriptor, SignerProvider};
3740
use crate::util::logger::Logger;
3841
use crate::util::ser::{
@@ -220,6 +223,7 @@ pub(crate) enum FeerateStrategy {
220223
/// do RBF bumping if possible.
221224
#[derive(Clone)]
222225
pub struct OnchainTxHandler<ChannelSigner: EcdsaChannelSigner> {
226+
channel_id: ChannelId,
223227
channel_value_satoshis: u64, // Deprecated as of 0.2.
224228
channel_keys_id: [u8; 32], // Deprecated as of 0.2.
225229
destination_script: ScriptBuf, // Deprecated as of 0.2.
@@ -282,7 +286,8 @@ impl<ChannelSigner: EcdsaChannelSigner> PartialEq for OnchainTxHandler<ChannelSi
282286
#[rustfmt::skip]
283287
fn eq(&self, other: &Self) -> bool {
284288
// `signer`, `secp_ctx`, and `pending_claim_events` are excluded on purpose.
285-
self.channel_value_satoshis == other.channel_value_satoshis &&
289+
self.channel_id == other.channel_id &&
290+
self.channel_value_satoshis == other.channel_value_satoshis &&
286291
self.channel_keys_id == other.channel_keys_id &&
287292
self.destination_script == other.destination_script &&
288293
self.holder_commitment == other.holder_commitment &&
@@ -345,6 +350,14 @@ impl<ChannelSigner: EcdsaChannelSigner> OnchainTxHandler<ChannelSigner> {
345350
write_tlv_fields!(writer, {});
346351
Ok(())
347352
}
353+
354+
// `ChannelMonitor`s already track the `channel_id`, however, due to the derserialization order
355+
// there we can't make use of `ReadableArgs` to hand it into `OnchainTxHandler`'s
356+
// deserialization logic directly. Instead we opt to initialize it with 0s and override it
357+
// after reading the respective field via this method.
358+
pub(crate) fn set_channel_id(&mut self, channel_id: ChannelId) {
359+
self.channel_id = channel_id;
360+
}
348361
}
349362

350363
impl<'a, 'b, ES: EntropySource, SP: SignerProvider> ReadableArgs<(&'a ES, &'b SP, u64, [u8; 32])>
@@ -366,7 +379,7 @@ impl<'a, 'b, ES: EntropySource, SP: SignerProvider> ReadableArgs<(&'a ES, &'b SP
366379
let prev_holder_commitment = Readable::read(reader)?;
367380
let _prev_holder_htlc_sigs: Option<Vec<Option<(usize, Signature)>>> = Readable::read(reader)?;
368381

369-
let channel_parameters = ReadableArgs::<Option<u64>>::read(reader, Some(channel_value_satoshis))?;
382+
let channel_parameters: ChannelTransactionParameters = ReadableArgs::<Option<u64>>::read(reader, Some(channel_value_satoshis))?;
370383

371384
// Read the serialized signer bytes, but don't deserialize them, as we'll obtain our signer
372385
// by re-deriving the private key material.
@@ -420,10 +433,17 @@ impl<'a, 'b, ES: EntropySource, SP: SignerProvider> ReadableArgs<(&'a ES, &'b SP
420433

421434
read_tlv_fields!(reader, {});
422435

436+
// `ChannelMonitor`s already track the `channel_id`, however, due to the derserialization
437+
// order there we can't make use of `ReadableArgs` to hand it in directly. Instead we opt
438+
// to initialize it with 0s and override it after reading the respective field via
439+
// `OnchainTxHandler::set_channel_id`.
440+
let channel_id = ChannelId([0u8; 32]);
441+
423442
let mut secp_ctx = Secp256k1::new();
424443
secp_ctx.seeded_randomize(&entropy_source.get_secure_random_bytes());
425444

426445
Ok(OnchainTxHandler {
446+
channel_id,
427447
channel_value_satoshis,
428448
channel_keys_id,
429449
destination_script,
@@ -443,11 +463,13 @@ impl<'a, 'b, ES: EntropySource, SP: SignerProvider> ReadableArgs<(&'a ES, &'b SP
443463

444464
impl<ChannelSigner: EcdsaChannelSigner> OnchainTxHandler<ChannelSigner> {
445465
pub(crate) fn new(
446-
channel_value_satoshis: u64, channel_keys_id: [u8; 32], destination_script: ScriptBuf,
447-
signer: ChannelSigner, channel_parameters: ChannelTransactionParameters,
466+
channel_id: ChannelId, channel_value_satoshis: u64, channel_keys_id: [u8; 32],
467+
destination_script: ScriptBuf, signer: ChannelSigner,
468+
channel_parameters: ChannelTransactionParameters,
448469
holder_commitment: HolderCommitmentTransaction, secp_ctx: Secp256k1<secp256k1::All>,
449470
) -> Self {
450471
OnchainTxHandler {
472+
channel_id,
451473
channel_value_satoshis,
452474
channel_keys_id,
453475
destination_script,
@@ -511,7 +533,7 @@ impl<ChannelSigner: EcdsaChannelSigner> OnchainTxHandler<ChannelSigner> {
511533
if tx.is_fully_signed() {
512534
let log_start = if feerate_was_bumped { "Broadcasting RBF-bumped" } else { "Rebroadcasting" };
513535
log_info!(logger, "{} onchain {}", log_start, log_tx!(tx.0));
514-
broadcaster.broadcast_transactions(&[&tx.0]);
536+
broadcaster.broadcast_transactions(&[(&tx.0, TransactionType::Claim { channel_id: self.channel_id })]);
515537
} else {
516538
log_info!(logger, "Waiting for signature of unsigned onchain transaction {}", tx.0.compute_txid());
517539
}
@@ -853,7 +875,7 @@ impl<ChannelSigner: EcdsaChannelSigner> OnchainTxHandler<ChannelSigner> {
853875
OnchainClaim::Tx(tx) => {
854876
if tx.is_fully_signed() {
855877
log_info!(logger, "Broadcasting onchain {}", log_tx!(tx.0));
856-
broadcaster.broadcast_transactions(&[&tx.0]);
878+
broadcaster.broadcast_transactions(&[(&tx.0, TransactionType::Claim { channel_id: self.channel_id })]);
857879
} else {
858880
log_info!(logger, "Waiting for signature of unsigned onchain transaction {}", tx.0.compute_txid());
859881
}
@@ -1071,7 +1093,7 @@ impl<ChannelSigner: EcdsaChannelSigner> OnchainTxHandler<ChannelSigner> {
10711093
OnchainClaim::Tx(bump_tx) => {
10721094
if bump_tx.is_fully_signed() {
10731095
log_info!(logger, "Broadcasting RBF-bumped onchain {}", log_tx!(bump_tx.0));
1074-
broadcaster.broadcast_transactions(&[&bump_tx.0]);
1096+
broadcaster.broadcast_transactions(&[(&bump_tx.0, TransactionType::Claim { channel_id: self.channel_id })]);
10751097
} else {
10761098
log_info!(logger, "Waiting for signature of RBF-bumped unsigned onchain transaction {}",
10771099
bump_tx.0.compute_txid());
@@ -1168,7 +1190,7 @@ impl<ChannelSigner: EcdsaChannelSigner> OnchainTxHandler<ChannelSigner> {
11681190
OnchainClaim::Tx(bump_tx) => {
11691191
if bump_tx.is_fully_signed() {
11701192
log_info!(logger, "Broadcasting onchain {}", log_tx!(bump_tx.0));
1171-
broadcaster.broadcast_transactions(&[&bump_tx.0]);
1193+
broadcaster.broadcast_transactions(&[(&bump_tx.0, TransactionType::Claim { channel_id: self.channel_id })]);
11721194
} else {
11731195
log_info!(logger, "Waiting for signature of unsigned onchain transaction {}", bump_tx.0.compute_txid());
11741196
}
@@ -1262,6 +1284,7 @@ mod tests {
12621284
};
12631285
use crate::ln::channel_keys::{DelayedPaymentBasepoint, HtlcBasepoint, RevocationBasepoint};
12641286
use crate::ln::functional_test_utils::create_dummy_block;
1287+
use crate::ln::types::ChannelId;
12651288
use crate::sign::{ChannelDerivationParameters, ChannelSigner, HTLCDescriptor, InMemorySigner};
12661289
use crate::types::payment::{PaymentHash, PaymentPreimage};
12671290
use crate::util::test_utils::{TestBroadcaster, TestFeeEstimator, TestLogger};
@@ -1346,6 +1369,7 @@ mod tests {
13461369
let holder_commit = HolderCommitmentTransaction::dummy(1000000, funding_outpoint, nondust_htlcs);
13471370
let destination_script = ScriptBuf::new();
13481371
let mut tx_handler = OnchainTxHandler::new(
1372+
ChannelId::from_bytes([0; 32]),
13491373
1000000,
13501374
[0; 32],
13511375
destination_script.clone(),

0 commit comments

Comments
 (0)