diff --git a/lightning/src/chain/channelmonitor.rs b/lightning/src/chain/channelmonitor.rs index d46cfaa636c..2d0420c48c8 100644 --- a/lightning/src/chain/channelmonitor.rs +++ b/lightning/src/chain/channelmonitor.rs @@ -3906,6 +3906,8 @@ impl ChannelMonitorImpl { } fn promote_funding(&mut self, new_funding_txid: Txid) -> Result<(), ()> { + let prev_funding_txid = self.funding.funding_txid(); + let new_funding = self .pending_funding .iter_mut() @@ -3921,9 +3923,20 @@ impl ChannelMonitorImpl { self.funding.prev_holder_commitment_tx.clone(), ); + let no_further_updates_allowed = self.no_further_updates_allowed(); + // The swap above places the previous `FundingScope` into `pending_funding`. for funding in self.pending_funding.drain(..) { - self.outputs_to_watch.remove(&funding.funding_txid()); + let funding_txid = funding.funding_txid(); + self.outputs_to_watch.remove(&funding_txid); + if no_further_updates_allowed && funding_txid != prev_funding_txid { + self.pending_events.push(Event::DiscardFunding { + channel_id: self.channel_id, + funding_info: crate::events::FundingInfo::OutPoint { + outpoint: funding.funding_outpoint(), + }, + }); + } } if let Some((alternative_funding_txid, _)) = self.alternative_funding_confirmed.take() { // In exceedingly rare cases, it's possible there was a reorg that caused a potential funding to diff --git a/lightning/src/events/mod.rs b/lightning/src/events/mod.rs index 30c928297a8..d36143dce9f 100644 --- a/lightning/src/events/mod.rs +++ b/lightning/src/events/mod.rs @@ -1502,6 +1502,10 @@ pub enum Event { /// Used to indicate to the user that they can abandon the funding transaction and recycle the /// inputs for another purpose. /// + /// When splicing, users can expect to receive an event for each negotiated splice transaction + /// that did not become locked. The negotiated splice transaction that became locked can be + /// obtained via [`Event::ChannelReady::funding_txo`]. + /// /// This event is not guaranteed to be generated for channels that are closed due to a restart. /// /// # Failure Behavior and Persistence diff --git a/lightning/src/ln/channel.rs b/lightning/src/ln/channel.rs index e637f44d3cf..462c86eab36 100644 --- a/lightning/src/ln/channel.rs +++ b/lightning/src/ln/channel.rs @@ -39,6 +39,8 @@ use crate::chain::transaction::{OutPoint, TransactionData}; use crate::chain::BestBlock; use crate::events::bump_transaction::BASE_INPUT_WEIGHT; use crate::events::ClosureReason; +#[cfg(splicing)] +use crate::events::FundingInfo; use crate::ln::chan_utils; #[cfg(splicing)] use crate::ln::chan_utils::FUNDING_TRANSACTION_WITNESS_WEIGHT; @@ -6086,16 +6088,35 @@ where #[cfg(splicing)] macro_rules! promote_splice_funding { - ($self: expr, $funding: expr) => { + ($self: expr, $funding: expr) => {{ + let prev_funding_txid = $self.funding.get_funding_txid(); if let Some(scid) = $self.funding.short_channel_id { $self.context.historical_scids.push(scid); } core::mem::swap(&mut $self.funding, $funding); $self.interactive_tx_signing_session = None; $self.pending_splice = None; - $self.pending_funding.clear(); $self.context.announcement_sigs_state = AnnouncementSigsState::NotSent; - }; + + // The swap above places the previous `FundingScope` into `pending_funding`. + let discarded_funding = $self + .pending_funding + .drain(..) + .filter(|funding| funding.get_funding_txid() != prev_funding_txid) + .map(|mut funding| { + funding + .funding_transaction + .take() + .map(|tx| FundingInfo::Tx { transaction: tx }) + .unwrap_or_else(|| FundingInfo::OutPoint { + outpoint: funding + .get_funding_txo() + .expect("Negotiated splices must have a known funding outpoint"), + }) + }) + .collect::>(); + discarded_funding + }}; } #[cfg(any(test, fuzzing))] @@ -6179,6 +6200,7 @@ pub struct SpliceFundingPromotion { pub funding_txo: OutPoint, pub monitor_update: Option, pub announcement_sigs: Option, + pub discarded_funding: Vec, } impl FundedChannel @@ -8634,23 +8656,25 @@ where log_trace!(logger, "Regenerating latest commitment update in channel {} with{} {} update_adds, {} update_fulfills, {} update_fails, and {} update_fail_malformeds", &self.context.channel_id(), if update_fee.is_some() { " update_fee," } else { "" }, update_add_htlcs.len(), update_fulfill_htlcs.len(), update_fail_htlcs.len(), update_fail_malformed_htlcs.len()); - let commitment_signed = - if let Ok(update) = self.send_commitment_no_state_update(logger) { - if self.context.signer_pending_commitment_update { - log_trace!( - logger, - "Commitment update generated: clearing signer_pending_commitment_update" - ); - self.context.signer_pending_commitment_update = false; - } - update - } else { - if !self.context.signer_pending_commitment_update { - log_trace!(logger, "Commitment update awaiting signer: setting signer_pending_commitment_update"); - self.context.signer_pending_commitment_update = true; - } - return Err(()); - }; + let commitment_signed = if let Ok(update) = self.send_commitment_no_state_update(logger) { + if self.context.signer_pending_commitment_update { + log_trace!( + logger, + "Commitment update generated: clearing signer_pending_commitment_update" + ); + self.context.signer_pending_commitment_update = false; + } + update + } else { + if !self.context.signer_pending_commitment_update { + log_trace!( + logger, + "Commitment update awaiting signer: setting signer_pending_commitment_update" + ); + self.context.signer_pending_commitment_update = true; + } + return Err(()); + }; Ok(msgs::CommitmentUpdate { update_add_htlcs, update_fulfill_htlcs, @@ -9954,7 +9978,7 @@ where &self.context.channel_id, ); - { + let discarded_funding = { // Scope `funding` since it is swapped within `promote_splice_funding` and we don't want // to unintentionally use it. let funding = self @@ -9962,8 +9986,8 @@ where .iter_mut() .find(|funding| funding.get_funding_txid() == Some(splice_txid)) .unwrap(); - promote_splice_funding!(self, funding); - } + promote_splice_funding!(self, funding) + }; let funding_txo = self .funding @@ -9984,7 +10008,12 @@ where let announcement_sigs = self.get_announcement_sigs(node_signer, chain_hash, user_config, block_height, logger); - Some(SpliceFundingPromotion { funding_txo, monitor_update, announcement_sigs }) + Some(SpliceFundingPromotion { + funding_txo, + monitor_update, + announcement_sigs, + discarded_funding, + }) } /// When a transaction is confirmed, we check whether it is or spends the funding transaction @@ -10066,16 +10095,17 @@ where &self.context.channel_id, ); - let (funding_txo, monitor_update, announcement_sigs) = + let (funding_txo, monitor_update, announcement_sigs, discarded_funding) = self.maybe_promote_splice_funding( node_signer, chain_hash, user_config, height, logger, ).map(|splice_promotion| ( Some(splice_promotion.funding_txo), splice_promotion.monitor_update, splice_promotion.announcement_sigs, - )).unwrap_or((None, None, None)); + splice_promotion.discarded_funding, + )).unwrap_or((None, None, None, Vec::new())); - return Ok((Some(FundingConfirmedMessage::Splice(splice_locked, funding_txo, monitor_update)), announcement_sigs)); + return Ok((Some(FundingConfirmedMessage::Splice(splice_locked, funding_txo, monitor_update, discarded_funding)), announcement_sigs)); } } } @@ -10227,7 +10257,7 @@ where log_info!(logger, "Sending a splice_locked to our peer for channel {}", &self.context.channel_id); debug_assert!(chain_node_signer.is_some()); - let (funding_txo, monitor_update, announcement_sigs) = chain_node_signer + let (funding_txo, monitor_update, announcement_sigs, discarded_funding) = chain_node_signer .and_then(|(chain_hash, node_signer, user_config)| { // We can only promote on blocks connected, which is when we expect // `chain_node_signer` to be `Some`. @@ -10237,10 +10267,11 @@ where Some(splice_promotion.funding_txo), splice_promotion.monitor_update, splice_promotion.announcement_sigs, + splice_promotion.discarded_funding, )) - .unwrap_or((None, None, None)); + .unwrap_or((None, None, None, Vec::new())); - return Ok((Some(FundingConfirmedMessage::Splice(splice_locked, funding_txo, monitor_update)), timed_out_htlcs, announcement_sigs)); + return Ok((Some(FundingConfirmedMessage::Splice(splice_locked, funding_txo, monitor_update, discarded_funding)), timed_out_htlcs, announcement_sigs)); } } diff --git a/lightning/src/ln/channelmanager.rs b/lightning/src/ln/channelmanager.rs index f42ea461891..b35a0824084 100644 --- a/lightning/src/ln/channelmanager.rs +++ b/lightning/src/ln/channelmanager.rs @@ -11230,19 +11230,30 @@ This indicates a bug inside LDK. Please report this error at https://github.com/ insert_short_channel_id!(short_to_chan_info, chan); } - let mut pending_events = self.pending_events.lock().unwrap(); - pending_events.push_back(( - events::Event::ChannelReady { - channel_id: chan.context.channel_id(), - user_channel_id: chan.context.get_user_id(), - counterparty_node_id: chan.context.get_counterparty_node_id(), - funding_txo: Some( - splice_promotion.funding_txo.into_bitcoin_outpoint(), - ), - channel_type: chan.funding.get_channel_type().clone(), - }, - None, - )); + { + let mut pending_events = self.pending_events.lock().unwrap(); + pending_events.push_back(( + events::Event::ChannelReady { + channel_id: chan.context.channel_id(), + user_channel_id: chan.context.get_user_id(), + counterparty_node_id: chan.context.get_counterparty_node_id(), + funding_txo: Some( + splice_promotion.funding_txo.into_bitcoin_outpoint(), + ), + channel_type: chan.funding.get_channel_type().clone(), + }, + None, + )); + splice_promotion.discarded_funding.into_iter().for_each( + |funding_info| { + let event = Event::DiscardFunding { + channel_id: chan.context.channel_id(), + funding_info, + }; + pending_events.push_back((event, None)); + }, + ); + } if let Some(announcement_sigs) = splice_promotion.announcement_sigs { log_trace!( @@ -13409,7 +13420,7 @@ where pub(super) enum FundingConfirmedMessage { Establishment(msgs::ChannelReady), #[cfg(splicing)] - Splice(msgs::SpliceLocked, Option, Option), + Splice(msgs::SpliceLocked, Option, Option, Vec), } impl< @@ -13485,7 +13496,7 @@ where } }, #[cfg(splicing)] - Some(FundingConfirmedMessage::Splice(splice_locked, funding_txo, monitor_update_opt)) => { + Some(FundingConfirmedMessage::Splice(splice_locked, funding_txo, monitor_update_opt, discarded_funding)) => { let counterparty_node_id = funded_channel.context.get_counterparty_node_id(); let channel_id = funded_channel.context.channel_id(); @@ -13515,6 +13526,13 @@ where funding_txo: Some(funding_txo.into_bitcoin_outpoint()), channel_type: funded_channel.funding.get_channel_type().clone(), }, None)); + discarded_funding.into_iter().for_each(|funding_info| { + let event = Event::DiscardFunding { + channel_id: funded_channel.context.channel_id(), + funding_info, + }; + pending_events.push_back((event, None)); + }); } pending_msg_events.push(MessageSendEvent::SendSpliceLocked {