diff --git a/lightning-background-processor/src/lib.rs b/lightning-background-processor/src/lib.rs index bb99d65e6b5..dce803e6dea 100644 --- a/lightning-background-processor/src/lib.rs +++ b/lightning-background-processor/src/lib.rs @@ -3110,10 +3110,16 @@ mod tests { let event = receiver.recv_timeout(EVENT_DEADLINE).expect("Events not handled within deadline"); match event { - Event::SpendableOutputs { outputs, channel_id } => { + Event::SpendableOutputs { outputs, channel_id, counterparty_node_id } => { nodes[0] .sweeper - .track_spendable_outputs(outputs, channel_id, false, Some(153)) + .track_spendable_outputs( + outputs, + channel_id, + counterparty_node_id, + false, + Some(153), + ) .unwrap(); }, _ => panic!("Unexpected event: {:?}", event), diff --git a/lightning-liquidity/src/lsps2/service.rs b/lightning-liquidity/src/lsps2/service.rs index 4c688d39eef..1909e871596 100644 --- a/lightning-liquidity/src/lsps2/service.rs +++ b/lightning-liquidity/src/lsps2/service.rs @@ -2020,21 +2020,22 @@ where // 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. if let Some(ch_id) = jit_channel.get_channel_id() { - let is_channel_ready = self + let channel_details = self .channel_manager .get_cm() .list_channels() .into_iter() - .any(|cd| cd.channel_id == ch_id && cd.is_channel_ready); + .find(|cd| cd.channel_id == ch_id && cd.is_channel_ready); - if !is_channel_ready { - return; - } + let counterparty_node_id = match channel_details { + Some(cd) => cd.counterparty.node_id, + None => return, + }; if let Some(funding_tx) = jit_channel.get_funding_tx() { self.tx_broadcaster.broadcast_transactions(&[( funding_tx, - TransactionType::Funding { channel_ids: vec![ch_id] }, + TransactionType::Funding { channels: vec![(counterparty_node_id, ch_id)] }, )]); } } diff --git a/lightning-tests/src/upgrade_downgrade_tests.rs b/lightning-tests/src/upgrade_downgrade_tests.rs index 8df670321be..14b0a5c5822 100644 --- a/lightning-tests/src/upgrade_downgrade_tests.rs +++ b/lightning-tests/src/upgrade_downgrade_tests.rs @@ -308,7 +308,9 @@ fn test_0_1_legacy_remote_key_derivation() { connect_blocks(&nodes[0], ANTI_REORG_DELAY - 1); let mut spendable_event = nodes[0].chain_monitor.chain_monitor.get_and_clear_pending_events(); assert_eq!(spendable_event.len(), 1); - if let Event::SpendableOutputs { outputs, channel_id: ev_id } = spendable_event.pop().unwrap() { + if let Event::SpendableOutputs { outputs, channel_id: ev_id, counterparty_node_id: _ } = + spendable_event.pop().unwrap() + { assert_eq!(ev_id.unwrap().0, channel_id); assert_eq!(outputs.len(), 1); let spk = Builder::new().push_opcode(opcodes::all::OP_RETURN).into_script(); diff --git a/lightning/src/chain/chaininterface.rs b/lightning/src/chain/chaininterface.rs index 758fd1a74e2..806e947c153 100644 --- a/lightning/src/chain/chaininterface.rs +++ b/lightning/src/chain/chaininterface.rs @@ -18,6 +18,7 @@ use core::{cmp, ops::Deref}; use crate::ln::types::ChannelId; use crate::prelude::*; +use bitcoin::secp256k1::PublicKey; use bitcoin::transaction::Transaction; /// Represents the class of transaction being broadcast. @@ -33,10 +34,10 @@ pub enum TransactionType { /// /// [`ChannelManager::funding_transaction_generated`]: crate::ln::channelmanager::ChannelManager::funding_transaction_generated Funding { - /// The IDs of the channels being funded. + /// The counterparty node IDs and channel IDs of the channels being funded. /// /// A single funding transaction may establish multiple channels when using batch funding. - channel_ids: Vec, + channels: Vec<(PublicKey, ChannelId)>, }, /// A transaction cooperatively closing a channel. /// @@ -45,6 +46,8 @@ pub enum TransactionType { /// /// [`ChannelManager::close_channel`]: crate::ln::channelmanager::ChannelManager::close_channel CooperativeClose { + /// The `node_id` of the channel counterparty. + counterparty_node_id: PublicKey, /// The ID of the channel being closed. channel_id: ChannelId, }, @@ -56,6 +59,8 @@ pub enum TransactionType { /// /// [`ChannelManager::force_close_broadcasting_latest_txn`]: crate::ln::channelmanager::ChannelManager::force_close_broadcasting_latest_txn UnilateralClose { + /// The `node_id` of the channel counterparty. + counterparty_node_id: PublicKey, /// The ID of the channel being force-closed. channel_id: ChannelId, }, @@ -66,6 +71,8 @@ pub enum TransactionType { /// /// [`BumpTransactionEvent`]: crate::events::bump_transaction::BumpTransactionEvent AnchorBump { + /// The `node_id` of the channel counterparty. + counterparty_node_id: PublicKey, /// The ID of the channel whose closing transaction is being fee-bumped. channel_id: ChannelId, }, @@ -81,6 +88,8 @@ pub enum TransactionType { /// [`ChannelMonitor`]: crate::chain::ChannelMonitor /// [`Event::SpendableOutputs`]: crate::events::Event::SpendableOutputs Claim { + /// The `node_id` of the channel counterparty. + counterparty_node_id: PublicKey, /// The ID of the channel from which outputs are being claimed. channel_id: ChannelId, }, @@ -90,10 +99,10 @@ pub enum TransactionType { /// [`OutputSweeper`]: crate::util::sweep::OutputSweeper /// [`SpendableOutputDescriptor`]: crate::sign::SpendableOutputDescriptor Sweep { - /// The IDs of the channels from which outputs are being swept, if known. + /// The counterparty node IDs and channel IDs from which outputs are being swept, if known. /// /// A single sweep transaction may aggregate outputs from multiple channels. - channel_ids: Vec, + channels: Vec<(PublicKey, ChannelId)>, }, /// A splice transaction modifying an existing channel's funding. /// @@ -101,6 +110,8 @@ pub enum TransactionType { /// /// [`ChannelManager::splice_channel`]: crate::ln::channelmanager::ChannelManager::splice_channel Splice { + /// The `node_id` of the channel counterparty. + counterparty_node_id: PublicKey, /// The ID of the channel being spliced. channel_id: ChannelId, }, diff --git a/lightning/src/chain/channelmonitor.rs b/lightning/src/chain/channelmonitor.rs index a537ff55874..6205fa895a7 100644 --- a/lightning/src/chain/channelmonitor.rs +++ b/lightning/src/chain/channelmonitor.rs @@ -1879,8 +1879,8 @@ impl ChannelMonitor { initial_holder_commitment_tx.trust().commitment_number(); let onchain_tx_handler = OnchainTxHandler::new( - channel_id, channel_parameters.channel_value_satoshis, channel_keys_id, - destination_script.into(), keys, channel_parameters.clone(), + channel_id, counterparty_node_id, channel_parameters.channel_value_satoshis, + channel_keys_id, destination_script.into(), keys, channel_parameters.clone(), initial_holder_commitment_tx.clone(), secp_ctx, ); @@ -5619,6 +5619,7 @@ impl ChannelMonitorImpl { self.pending_events.push(Event::SpendableOutputs { outputs: vec![descriptor], channel_id: Some(self.channel_id()), + counterparty_node_id: Some(self.counterparty_node_id), }); self.spendable_txids_confirmed.push(entry.txid); }, @@ -6643,6 +6644,8 @@ impl<'a, 'b, ES: EntropySource, SP: SignerProvider> ReadableArgs<(&'a ES, &'b SP }; let dummy_node_id = PublicKey::from_slice(&[2; 33]).unwrap(); + onchain_tx_handler + .set_counterparty_node_id(counterparty_node_id.unwrap_or(dummy_node_id)); let monitor = ChannelMonitor::from_impl(ChannelMonitorImpl { funding: FundingScope { channel_parameters, diff --git a/lightning/src/chain/onchaintx.rs b/lightning/src/chain/onchaintx.rs index 8de99eb8601..3eb6d64f3a2 100644 --- a/lightning/src/chain/onchaintx.rs +++ b/lightning/src/chain/onchaintx.rs @@ -18,7 +18,7 @@ use bitcoin::hashes::Hash; use bitcoin::locktime::absolute::LockTime; use bitcoin::script::{Script, ScriptBuf}; use bitcoin::secp256k1; -use bitcoin::secp256k1::{ecdsa::Signature, Secp256k1}; +use bitcoin::secp256k1::{ecdsa::Signature, PublicKey, Secp256k1}; use bitcoin::transaction::OutPoint as BitcoinOutPoint; use bitcoin::transaction::Transaction; @@ -224,6 +224,7 @@ pub(crate) enum FeerateStrategy { #[derive(Clone)] pub struct OnchainTxHandler { channel_id: ChannelId, + counterparty_node_id: PublicKey, 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. @@ -287,6 +288,7 @@ impl PartialEq for OnchainTxHandler bool { // `signer`, `secp_ctx`, and `pending_claim_events` are excluded on purpose. self.channel_id == other.channel_id && + self.counterparty_node_id == other.counterparty_node_id && self.channel_value_satoshis == other.channel_value_satoshis && self.channel_keys_id == other.channel_keys_id && self.destination_script == other.destination_script && @@ -358,6 +360,14 @@ impl OnchainTxHandler { pub(crate) fn set_channel_id(&mut self, channel_id: ChannelId) { self.channel_id = channel_id; } + + // `ChannelMonitor`s already track the `counterparty_node_id`, however, due to the + // deserialization 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 a + // dummy key and override it after reading the respective field via this method. + pub(crate) fn set_counterparty_node_id(&mut self, counterparty_node_id: PublicKey) { + self.counterparty_node_id = counterparty_node_id; + } } impl<'a, 'b, ES: EntropySource, SP: SignerProvider> ReadableArgs<(&'a ES, &'b SP, u64, [u8; 32])> @@ -433,17 +443,20 @@ 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`. + // `ChannelMonitor`s already track the `channel_id` and `counterparty_node_id`, however, due + // to the deserialization order there we can't make use of `ReadableArgs` to hand them in + // directly. Instead we opt to initialize them with dummy values and override them after + // reading the respective fields via `OnchainTxHandler::set_channel_id` and + // `OnchainTxHandler::set_counterparty_node_id`. let channel_id = ChannelId([0u8; 32]); + let counterparty_node_id = PublicKey::from_slice(&[2; 33]).unwrap(); let mut secp_ctx = Secp256k1::new(); secp_ctx.seeded_randomize(&entropy_source.get_secure_random_bytes()); Ok(OnchainTxHandler { channel_id, + counterparty_node_id, channel_value_satoshis, channel_keys_id, destination_script, @@ -463,13 +476,14 @@ impl<'a, 'b, ES: EntropySource, SP: SignerProvider> ReadableArgs<(&'a ES, &'b SP impl OnchainTxHandler { pub(crate) fn new( - channel_id: ChannelId, channel_value_satoshis: u64, channel_keys_id: [u8; 32], - destination_script: ScriptBuf, signer: ChannelSigner, + channel_id: ChannelId, counterparty_node_id: PublicKey, 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, + counterparty_node_id, channel_value_satoshis, channel_keys_id, destination_script, @@ -533,7 +547,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, TransactionType::Claim { channel_id: self.channel_id })]); + broadcaster.broadcast_transactions(&[(&tx.0, TransactionType::Claim { counterparty_node_id: self.counterparty_node_id, channel_id: self.channel_id })]); } else { log_info!(logger, "Waiting for signature of unsigned onchain transaction {}", tx.0.compute_txid()); } @@ -875,7 +889,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, TransactionType::Claim { channel_id: self.channel_id })]); + broadcaster.broadcast_transactions(&[(&tx.0, TransactionType::Claim { counterparty_node_id: self.counterparty_node_id, channel_id: self.channel_id })]); } else { log_info!(logger, "Waiting for signature of unsigned onchain transaction {}", tx.0.compute_txid()); } @@ -1093,7 +1107,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, TransactionType::Claim { channel_id: self.channel_id })]); + broadcaster.broadcast_transactions(&[(&bump_tx.0, TransactionType::Claim { counterparty_node_id: self.counterparty_node_id, channel_id: self.channel_id })]); } else { log_info!(logger, "Waiting for signature of RBF-bumped unsigned onchain transaction {}", bump_tx.0.compute_txid()); @@ -1190,7 +1204,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, TransactionType::Claim { channel_id: self.channel_id })]); + broadcaster.broadcast_transactions(&[(&bump_tx.0, TransactionType::Claim { counterparty_node_id: self.counterparty_node_id, channel_id: self.channel_id })]); } else { log_info!(logger, "Waiting for signature of unsigned onchain transaction {}", bump_tx.0.compute_txid()); } @@ -1368,8 +1382,10 @@ mod tests { } let holder_commit = HolderCommitmentTransaction::dummy(1000000, funding_outpoint, nondust_htlcs); let destination_script = ScriptBuf::new(); + let counterparty_node_id = PublicKey::from_slice(&[2; 33]).unwrap(); let mut tx_handler = OnchainTxHandler::new( ChannelId::from_bytes([0; 32]), + counterparty_node_id, 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 15ea2775a71..ff034176385 100644 --- a/lightning/src/events/bump_transaction/mod.rs +++ b/lightning/src/events/bump_transaction/mod.rs @@ -774,7 +774,7 @@ 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, channel_id: ChannelId, claim_id: ClaimId, + &self, channel_id: ChannelId, counterparty_node_id: PublicKey, claim_id: ClaimId, package_target_feerate_sat_per_1000_weight: u32, commitment_tx: &Transaction, commitment_tx_fee_sat: u64, anchor_descriptor: &AnchorDescriptor, ) -> Result<(), ()> { @@ -799,7 +799,7 @@ where package_target_feerate_sat_per_1000_weight); self.broadcaster.broadcast_transactions(&[( &commitment_tx, - TransactionType::UnilateralClose { channel_id }, + TransactionType::UnilateralClose { counterparty_node_id, channel_id }, )]); return Ok(()); } @@ -968,8 +968,11 @@ where commitment_tx.compute_txid() ); self.broadcaster.broadcast_transactions(&[ - (&commitment_tx, TransactionType::UnilateralClose { channel_id }), - (&anchor_tx, TransactionType::AnchorBump { channel_id }), + ( + &commitment_tx, + TransactionType::UnilateralClose { counterparty_node_id, channel_id }, + ), + (&anchor_tx, TransactionType::AnchorBump { counterparty_node_id, channel_id }), ]); return Ok(()); } @@ -978,8 +981,9 @@ 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, channel_id: ChannelId, claim_id: ClaimId, target_feerate_sat_per_1000_weight: u32, - htlc_descriptors: &[HTLCDescriptor], tx_lock_time: LockTime, + &self, channel_id: ChannelId, counterparty_node_id: PublicKey, claim_id: ClaimId, + target_feerate_sat_per_1000_weight: u32, htlc_descriptors: &[HTLCDescriptor], + tx_lock_time: LockTime, ) -> Result<(), ()> { let channel_type = &htlc_descriptors[0] .channel_derivation_parameters @@ -1205,7 +1209,7 @@ where log_info!(self.logger, "Broadcasting {}", log_tx!(htlc_tx)); self.broadcaster.broadcast_transactions(&[( &htlc_tx, - TransactionType::UnilateralClose { channel_id }, + TransactionType::UnilateralClose { counterparty_node_id, channel_id }, )]); } @@ -1217,6 +1221,7 @@ where match event { BumpTransactionEvent::ChannelClose { channel_id, + counterparty_node_id, claim_id, package_target_feerate_sat_per_1000_weight, commitment_tx, @@ -1232,6 +1237,7 @@ where ); self.handle_channel_close( *channel_id, + *counterparty_node_id, *claim_id, *package_target_feerate_sat_per_1000_weight, commitment_tx, @@ -1249,6 +1255,7 @@ where }, BumpTransactionEvent::HTLCResolution { channel_id, + counterparty_node_id, claim_id, target_feerate_sat_per_1000_weight, htlc_descriptors, @@ -1263,6 +1270,7 @@ where ); self.handle_htlc_resolution( *channel_id, + *counterparty_node_id, *claim_id, *target_feerate_sat_per_1000_weight, htlc_descriptors, diff --git a/lightning/src/events/mod.rs b/lightning/src/events/mod.rs index 3d860e9f363..a5ac08be23b 100644 --- a/lightning/src/events/mod.rs +++ b/lightning/src/events/mod.rs @@ -1318,6 +1318,10 @@ pub enum Event { /// /// This will always be `Some` for events generated by LDK versions 0.0.117 and above. channel_id: Option, + /// The `node_id` of the channel counterparty. + /// + /// This will always be `Some` for events generated by LDK versions 0.3 and above. + counterparty_node_id: Option, }, /// This event is generated when a payment has been successfully forwarded through us and a /// forwarding fee earned. @@ -2012,11 +2016,12 @@ impl Writeable for Event { }); }, // 4u8 used to be `PendingHTLCsForwardable` - &Event::SpendableOutputs { ref outputs, channel_id } => { + &Event::SpendableOutputs { ref outputs, channel_id, counterparty_node_id } => { 5u8.write(writer)?; write_tlv_fields!(writer, { (0, WithoutLength(outputs), required), (1, channel_id, option), + (3, counterparty_node_id, option), }); }, &Event::HTLCIntercepted { @@ -2521,11 +2526,17 @@ impl MaybeReadable for Event { let mut f = || { let mut outputs = WithoutLength(Vec::new()); let mut channel_id: Option = None; + let mut counterparty_node_id: Option = None; read_tlv_fields!(reader, { (0, outputs, required), (1, channel_id, option), + (3, counterparty_node_id, option), }); - Ok(Some(Event::SpendableOutputs { outputs: outputs.0, channel_id })) + Ok(Some(Event::SpendableOutputs { + outputs: outputs.0, + channel_id, + counterparty_node_id, + })) }; f() }, diff --git a/lightning/src/ln/channel.rs b/lightning/src/ln/channel.rs index 2762ab63fc1..eb227a50855 100644 --- a/lightning/src/ln/channel.rs +++ b/lightning/src/ln/channel.rs @@ -2110,6 +2110,7 @@ where }; let channel_id = context.channel_id; + let counterparty_node_id = context.counterparty_node_id; let signing_session = if let Some(signing_session) = context.interactive_tx_signing_session.as_mut() @@ -2223,9 +2224,9 @@ where let funding_tx = funding_tx.map(|tx| { let tx_type = if splice_negotiated.is_some() { - TransactionType::Splice { channel_id } + TransactionType::Splice { counterparty_node_id, channel_id } } else { - TransactionType::Funding { channel_ids: vec![channel_id] } + TransactionType::Funding { channels: vec![(counterparty_node_id, channel_id)] } }; (tx, tx_type) }); @@ -9168,9 +9169,14 @@ where let funding_tx = funding_tx.map(|tx| { let tx_type = if splice_negotiated.is_some() { - TransactionType::Splice { channel_id: self.context.channel_id } + TransactionType::Splice { + counterparty_node_id: self.context.counterparty_node_id, + channel_id: self.context.channel_id, + } } else { - TransactionType::Funding { channel_ids: vec![self.context.channel_id] } + TransactionType::Funding { + channels: vec![(self.context.counterparty_node_id, self.context.channel_id)], + } }; (tx, tx_type) }); diff --git a/lightning/src/ln/channelmanager.rs b/lightning/src/ln/channelmanager.rs index 665a79a9610..8b4a25c2c04 100644 --- a/lightning/src/ln/channelmanager.rs +++ b/lightning/src/ln/channelmanager.rs @@ -6572,7 +6572,10 @@ impl< funding_tx.compute_txid() ); let tx_type = transaction_type.unwrap_or_else(|| TransactionType::Funding { - channel_ids: vec![channel.context().channel_id()], + channels: vec![( + channel.context().get_counterparty_node_id(), + channel.context().channel_id(), + )], }); self.tx_broadcaster.broadcast_transactions(&[(funding_tx, tx_type)]); { @@ -9583,7 +9586,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(); + let mut batch_channels = 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(); @@ -9594,7 +9597,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); + batch_channels.push((counterparty_node_id, channel_id)); let mut pending_events = self.pending_events.lock().unwrap(); emit_channel_pending_event!(pending_events, funded_chan); @@ -9605,7 +9608,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, - TransactionType::Funding { channel_ids: batch_channel_ids }, + TransactionType::Funding { channels: batch_channels }, )]); } } @@ -10273,7 +10276,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, - TransactionType::Funding { channel_ids: vec![channel.context.channel_id()] }, + TransactionType::Funding { channels: vec![(counterparty_node_id, channel.context.channel_id())] }, )]); } } @@ -11715,7 +11718,10 @@ 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, - TransactionType::CooperativeClose { channel_id: msg.channel_id }, + TransactionType::CooperativeClose { + counterparty_node_id: *counterparty_node_id, + channel_id: msg.channel_id, + }, )]); let _ = self.handle_error(err, *counterparty_node_id); } @@ -12956,7 +12962,10 @@ 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, - TransactionType::CooperativeClose { channel_id }, + TransactionType::CooperativeClose { + counterparty_node_id: node_id, + channel_id, + }, )]); } } else { @@ -13087,7 +13096,10 @@ 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, - TransactionType::CooperativeClose { channel_id }, + TransactionType::CooperativeClose { + counterparty_node_id: *cp_id, + channel_id, + }, )]); false } else { diff --git a/lightning/src/ln/functional_tests.rs b/lightning/src/ln/functional_tests.rs index 8e854b31150..a7a062add11 100644 --- a/lightning/src/ln/functional_tests.rs +++ b/lightning/src/ln/functional_tests.rs @@ -3430,7 +3430,7 @@ macro_rules! check_spendable_outputs { let secp_ctx = Secp256k1::new(); for event in events.drain(..) { match event { - Event::SpendableOutputs { mut outputs, channel_id: _ } => { + Event::SpendableOutputs { mut outputs, channel_id: _, counterparty_node_id: _ } => { for outp in outputs.drain(..) { let script = Builder::new().push_opcode(opcodes::all::OP_RETURN).into_script(); diff --git a/lightning/src/ln/monitor_tests.rs b/lightning/src/ln/monitor_tests.rs index 097266cf83f..aebd83ae75d 100644 --- a/lightning/src/ln/monitor_tests.rs +++ b/lightning/src/ln/monitor_tests.rs @@ -2209,7 +2209,7 @@ fn do_test_revoked_counterparty_aggregated_claims(keyed_anchors: bool, p2a_ancho let spendable_output_events = nodes[1].chain_monitor.chain_monitor.get_and_clear_pending_events(); assert_eq!(spendable_output_events.len(), 2); for event in spendable_output_events { - if let Event::SpendableOutputs { outputs, channel_id: _ } = event { + if let Event::SpendableOutputs { outputs, channel_id: _, counterparty_node_id: _ } = event { assert_eq!(outputs.len(), 1); let spend_tx = nodes[1].keys_manager.backing.spend_spendable_outputs( &[&outputs[0]], Vec::new(), ScriptBuf::new_op_return(&[]), 253, None, &Secp256k1::new(), @@ -2992,7 +2992,7 @@ fn do_test_anchors_aggregated_revoked_htlc_tx(p2a_anchor: bool) { // - 1 static to_remote output. assert_eq!(spendable_output_events.len(), 4); for event in spendable_output_events { - if let Event::SpendableOutputs { outputs, channel_id } = event { + if let Event::SpendableOutputs { outputs, channel_id, counterparty_node_id: _ } = event { assert_eq!(outputs.len(), 1); assert!([chan_b.2, chan_a.2].contains(&channel_id.unwrap())); let spend_tx = nodes[0].keys_manager.backing.spend_spendable_outputs( diff --git a/lightning/src/ln/reorg_tests.rs b/lightning/src/ln/reorg_tests.rs index b56caf96008..25ad031bb35 100644 --- a/lightning/src/ln/reorg_tests.rs +++ b/lightning/src/ln/reorg_tests.rs @@ -622,7 +622,7 @@ fn do_test_to_remote_after_local_detection(style: ConnectStyle) { let mut node_a_spendable = nodes[0].chain_monitor.chain_monitor.get_and_clear_pending_events(); assert_eq!(node_a_spendable.len(), 1); - if let Event::SpendableOutputs { outputs, channel_id } = node_a_spendable.pop().unwrap() { + if let Event::SpendableOutputs { outputs, channel_id, counterparty_node_id: _ } = node_a_spendable.pop().unwrap() { assert_eq!(outputs.len(), 1); assert_eq!(channel_id, Some(chan_id)); let spend_tx = nodes[0].keys_manager.backing.spend_spendable_outputs(&[&outputs[0]], Vec::new(), @@ -643,7 +643,7 @@ fn do_test_to_remote_after_local_detection(style: ConnectStyle) { let mut node_b_spendable = nodes[1].chain_monitor.chain_monitor.get_and_clear_pending_events(); assert_eq!(node_b_spendable.len(), 1); - if let Event::SpendableOutputs { outputs, channel_id } = node_b_spendable.pop().unwrap() { + if let Event::SpendableOutputs { outputs, channel_id, counterparty_node_id: _ } = node_b_spendable.pop().unwrap() { assert_eq!(outputs.len(), 1); assert_eq!(channel_id, Some(chan_id)); let spend_tx = nodes[1].keys_manager.backing.spend_spendable_outputs(&[&outputs[0]], Vec::new(), diff --git a/lightning/src/ln/splicing_tests.rs b/lightning/src/ln/splicing_tests.rs index 9c7c8b55eac..1ad7fa193bb 100644 --- a/lightning/src/ln/splicing_tests.rs +++ b/lightning/src/ln/splicing_tests.rs @@ -334,14 +334,22 @@ pub fn sign_interactive_funding_tx<'a, 'b, 'c, 'd>( let tx = { let mut initiator_txn = initiator.tx_broadcaster.txn_broadcast_with_types(); assert_eq!(initiator_txn.len(), 1); - let acceptor_txn = acceptor.tx_broadcaster.txn_broadcast_with_types(); - assert_eq!(initiator_txn, acceptor_txn); - let (tx, tx_type) = initiator_txn.remove(0); - // Verify transaction type is Splice + let mut acceptor_txn = acceptor.tx_broadcaster.txn_broadcast_with_types(); + assert_eq!(acceptor_txn.len(), 1); + // Compare transactions only (not types, as counterparty_node_id differs per perspective) + assert_eq!(initiator_txn[0].0, acceptor_txn[0].0); + let (tx, initiator_tx_type) = initiator_txn.remove(0); + let (_, acceptor_tx_type) = acceptor_txn.remove(0); + // Verify transaction types are Splice for both nodes assert!( - matches!(tx_type, TransactionType::Splice { .. }), + matches!(initiator_tx_type, TransactionType::Splice { .. }), "Expected TransactionType::Splice, got {:?}", - tx_type + initiator_tx_type + ); + assert!( + matches!(acceptor_tx_type, TransactionType::Splice { .. }), + "Expected TransactionType::Splice, got {:?}", + acceptor_tx_type ); tx }; diff --git a/lightning/src/util/sweep.rs b/lightning/src/util/sweep.rs index 6815b395d91..b70eb274085 100644 --- a/lightning/src/util/sweep.rs +++ b/lightning/src/util/sweep.rs @@ -32,7 +32,7 @@ use crate::{log_debug, log_error}; use bitcoin::block::Header; use bitcoin::locktime::absolute::LockTime; -use bitcoin::secp256k1::Secp256k1; +use bitcoin::secp256k1::{PublicKey, Secp256k1}; use bitcoin::{BlockHash, ScriptBuf, Transaction, Txid}; use core::future::Future; @@ -55,6 +55,13 @@ pub struct TrackedSpendableOutput { /// /// Will be `None` if no `channel_id` was given to [`OutputSweeper::track_spendable_outputs`] pub channel_id: Option, + /// The `node_id` of the channel counterparty. + /// + /// Will be `None` if no `counterparty_node_id` was given to + /// [`OutputSweeper::track_spendable_outputs`]. + /// + /// This will be `None` for outputs tracked with LDK 0.2 and prior. + pub counterparty_node_id: Option, /// The current status of the output spend. pub status: OutputSpendStatus, } @@ -93,6 +100,7 @@ impl TrackedSpendableOutput { impl_writeable_tlv_based!(TrackedSpendableOutput, { (0, descriptor, required), (2, channel_id, option), + (3, counterparty_node_id, option), (4, status, required), }); @@ -413,7 +421,8 @@ where /// [`Event::SpendableOutputs`]: crate::events::Event::SpendableOutputs pub async fn track_spendable_outputs( &self, output_descriptors: Vec, channel_id: Option, - exclude_static_outputs: bool, delay_until_height: Option, + counterparty_node_id: Option, exclude_static_outputs: bool, + delay_until_height: Option, ) -> Result<(), ()> { let mut relevant_descriptors = output_descriptors .into_iter() @@ -432,6 +441,7 @@ where let output_info = TrackedSpendableOutput { descriptor, channel_id, + counterparty_node_id, status: OutputSpendStatus::PendingInitialBroadcast { delayed_until_height: delay_until_height, }, @@ -529,7 +539,7 @@ where // Sweep the outputs. let spending_tx_and_chan_id = self .update_state( - |sweeper_state| -> Result<(Option<(Transaction, Vec)>, bool), ()> { + |sweeper_state| -> Result<(Option<(Transaction, Vec<(PublicKey, ChannelId)>)>, bool), ()> { let cur_height = sweeper_state.best_block.height; let cur_hash = sweeper_state.best_block.block_hash; @@ -571,16 +581,20 @@ where .outputs .iter_mut() .filter(|o| filter_fn(&**o, cur_height)); - let mut channel_ids = Vec::new(); + let mut channels = 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); + if let (Some(counterparty_node_id), Some(channel_id)) = + (output_info.counterparty_node_id, output_info.channel_id) + { + if !channels.iter().any(|(cp, ch)| { + *cp == counterparty_node_id && *ch == channel_id + }) { + channels.push((counterparty_node_id, channel_id)); } } @@ -588,7 +602,7 @@ where sweeper_state.dirty = true; } - Ok((Some((spending_tx, channel_ids)), false)) + Ok((Some((spending_tx, channels)), false)) } else { Ok((None, false)) } @@ -597,9 +611,9 @@ where .await?; // Persistence completely successfully. If we have a spending transaction, we broadcast it. - if let Some((spending_tx, channel_ids)) = spending_tx_and_chan_id { + if let Some((spending_tx, channels)) = spending_tx_and_chan_id { self.broadcaster - .broadcast_transactions(&[(&spending_tx, TransactionType::Sweep { channel_ids })]); + .broadcast_transactions(&[(&spending_tx, TransactionType::Sweep { channels })]); } Ok(()) @@ -1010,11 +1024,13 @@ where /// [`Event::SpendableOutputs`]: crate::events::Event::SpendableOutputs pub fn track_spendable_outputs( &self, output_descriptors: Vec, channel_id: Option, - exclude_static_outputs: bool, delay_until_height: Option, + counterparty_node_id: Option, exclude_static_outputs: bool, + delay_until_height: Option, ) -> Result<(), ()> { let mut fut = pin!(self.sweeper.track_spendable_outputs( output_descriptors, channel_id, + counterparty_node_id, exclude_static_outputs, delay_until_height, ));