From aa915b6ae74d3b0d5a12b21276385e064845dedf Mon Sep 17 00:00:00 2001 From: Joost Jager Date: Thu, 11 Dec 2025 12:24:13 +0100 Subject: [PATCH 1/6] Store channel-associated events in monitor --- lightning/src/chain/channelmonitor.rs | 17 +++++++++++ lightning/src/ln/channelmanager.rs | 41 +++++++++++++++++++++++++-- 2 files changed, 55 insertions(+), 3 deletions(-) diff --git a/lightning/src/chain/channelmonitor.rs b/lightning/src/chain/channelmonitor.rs index 515a3dc5f1d..0502f4d9829 100644 --- a/lightning/src/chain/channelmonitor.rs +++ b/lightning/src/chain/channelmonitor.rs @@ -635,6 +635,9 @@ impl_writeable_tlv_based_enum_upgradable!(OnchainEvent, #[derive(Clone, Debug, PartialEq, Eq)] pub(crate) enum ChannelMonitorUpdateStep { + EventGenerated { + event: Event, + }, LatestHolderCommitmentTXInfo { commitment_tx: HolderCommitmentTransaction, /// Note that LDK after 0.0.115 supports this only containing dust HTLCs (implying the @@ -724,6 +727,7 @@ impl ChannelMonitorUpdateStep { ChannelMonitorUpdateStep::RenegotiatedFunding { .. } => "RenegotiatedFunding", ChannelMonitorUpdateStep::RenegotiatedFundingLocked { .. } => "RenegotiatedFundingLocked", ChannelMonitorUpdateStep::ReleasePaymentComplete { .. } => "ReleasePaymentComplete", + ChannelMonitorUpdateStep::EventGenerated { .. } => "EventGenerated", } } } @@ -778,6 +782,9 @@ impl_writeable_tlv_based_enum_upgradable!(ChannelMonitorUpdateStep, (12, RenegotiatedFundingLocked) => { (1, funding_txid, required), }, + (13, EventGenerated) => { + (0, event, upgradable_required), + } ); /// Indicates whether the balance is derived from a cooperative close, a force-close @@ -1283,6 +1290,7 @@ pub(crate) struct ChannelMonitorImpl { pending_monitor_events: Vec, pub(super) pending_events: Vec, + pub(super) pending_mgr_events: Vec, pub(super) is_processing_pending_events: bool, // Used to track on-chain events (i.e., transactions part of channels confirmed on chain) on @@ -1961,6 +1969,7 @@ impl ChannelMonitor { payment_preimages: new_hash_map(), pending_monitor_events: Vec::new(), + pending_mgr_events: Vec::new(), pending_events: Vec::new(), is_processing_pending_events: false, @@ -4370,6 +4379,10 @@ impl ChannelMonitorImpl { log_trace!(logger, "HTLC {htlc:?} permanently and fully resolved"); self.htlcs_resolved_to_user.insert(*htlc); }, + ChannelMonitorUpdateStep::EventGenerated { event } => { + log_trace!(logger, "Updating ChannelMonitor with channel manager event: {:?}", event); + self.pending_mgr_events.push(event.clone()); + }, } } @@ -4402,6 +4415,7 @@ impl ChannelMonitorImpl { ChannelMonitorUpdateStep::PaymentPreimage { .. } => {}, ChannelMonitorUpdateStep::ChannelForceClosed { .. } => {}, ChannelMonitorUpdateStep::ReleasePaymentComplete { .. } => {}, + ChannelMonitorUpdateStep::EventGenerated { .. } => {}, } } @@ -6644,6 +6658,7 @@ impl<'a, 'b, ES: EntropySource, SP: SignerProvider> ReadableArgs<(&'a ES, &'b SP let mut alternative_funding_confirmed = None; let mut is_manual_broadcast = RequiredWrapper(None); let mut funding_seen_onchain = RequiredWrapper(None); + let mut pending_mgr_events = Some(Vec::new()); read_tlv_fields!(reader, { (1, funding_spend_confirmed, option), (3, htlcs_resolved_on_chain, optional_vec), @@ -6666,6 +6681,7 @@ impl<'a, 'b, ES: EntropySource, SP: SignerProvider> ReadableArgs<(&'a ES, &'b SP (34, alternative_funding_confirmed, option), (35, is_manual_broadcast, (default_value, false)), (37, funding_seen_onchain, (default_value, true)), + (38, pending_mgr_events, optional_vec), }); // Note that `payment_preimages_with_info` was added (and is always written) in LDK 0.1, so // we can use it to determine if this monitor was last written by LDK 0.1 or later. @@ -6812,6 +6828,7 @@ impl<'a, 'b, ES: EntropySource, SP: SignerProvider> ReadableArgs<(&'a ES, &'b SP payment_preimages, pending_monitor_events: pending_monitor_events.unwrap(), + pending_mgr_events: pending_mgr_events.unwrap(), pending_events, is_processing_pending_events: false, diff --git a/lightning/src/ln/channelmanager.rs b/lightning/src/ln/channelmanager.rs index bfaf1e68d6a..aaf224c9de8 100644 --- a/lightning/src/ln/channelmanager.rs +++ b/lightning/src/ln/channelmanager.rs @@ -4686,8 +4686,7 @@ where } { - let mut pending_events = self.pending_events.lock().unwrap(); - pending_events.push_back(( + let closed_event = ( events::Event::ChannelClosed { channel_id: shutdown_res.channel_id, user_channel_id: shutdown_res.user_channel_id, @@ -4698,7 +4697,43 @@ where last_local_balance_msat: Some(shutdown_res.last_local_balance_msat), }, None, - )); + ); + + let mut per_peer_state = self.per_peer_state.write().unwrap(); + let mut peer_state_lock = per_peer_state + .get_mut(&shutdown_res.counterparty_node_id) + .expect("We must always have a peer entry for a peer with which we have channels") + .lock() + .unwrap(); + let peer_state = &mut *peer_state_lock; + let channel = peer_state + .channel_by_id + .get_mut(&shutdown_res.channel_id) + .expect("We should only be finishing the closure of a channel which existed"); + let funded_chan = channel + .as_funded_mut() + .expect("We should only be finishing the closure of a funded channel here"); + + let update_id = funded_chan.get_latest_unblocked_monitor_update_id(); + let monitor_update_step = + ChannelMonitorUpdateStep::EventGenerated { event: closed_event.0.clone() }; + let monitor_update = ChannelMonitorUpdate { + update_id, + updates: vec![monitor_update_step], + channel_id: Some(shutdown_res.channel_id), + }; + handle_new_monitor_update!( + self, + funded_chan.funding.get_funding_txo().unwrap(), + monitor_update, + peer_state_lock, + peer_state, + per_peer_state, + funded_chan + ); + + let mut pending_events = self.pending_events.lock().unwrap(); + pending_events.push_back(closed_event); if let Some(splice_funding_failed) = shutdown_res.splice_funding_failed.take() { pending_events.push_back(( From 0c1e279e40f11f6eb2b314a209c1d3c125b12621 Mon Sep 17 00:00:00 2001 From: Joost Jager Date: Fri, 12 Dec 2025 15:47:11 +0100 Subject: [PATCH 2/6] fix post close updates --- lightning/src/ln/channelmanager.rs | 67 ++++++++++++++++++++---------- 1 file changed, 44 insertions(+), 23 deletions(-) diff --git a/lightning/src/ln/channelmanager.rs b/lightning/src/ln/channelmanager.rs index aaf224c9de8..c7f55fe396d 100644 --- a/lightning/src/ln/channelmanager.rs +++ b/lightning/src/ln/channelmanager.rs @@ -4706,31 +4706,52 @@ where .lock() .unwrap(); let peer_state = &mut *peer_state_lock; - let channel = peer_state - .channel_by_id - .get_mut(&shutdown_res.channel_id) - .expect("We should only be finishing the closure of a channel which existed"); - let funded_chan = channel - .as_funded_mut() - .expect("We should only be finishing the closure of a funded channel here"); - - let update_id = funded_chan.get_latest_unblocked_monitor_update_id(); + let channel = peer_state.channel_by_id.get_mut(&shutdown_res.channel_id); + let monitor_update_step = ChannelMonitorUpdateStep::EventGenerated { event: closed_event.0.clone() }; - let monitor_update = ChannelMonitorUpdate { - update_id, - updates: vec![monitor_update_step], - channel_id: Some(shutdown_res.channel_id), - }; - handle_new_monitor_update!( - self, - funded_chan.funding.get_funding_txo().unwrap(), - monitor_update, - peer_state_lock, - peer_state, - per_peer_state, - funded_chan - ); + if let Some(channel) = channel { + let funded_chan = channel + .as_funded_mut() + .expect("We should only be finishing the closure of a funded channel here"); + + let update_id = funded_chan.get_latest_unblocked_monitor_update_id(); + let monitor_update = ChannelMonitorUpdate { + update_id, + updates: vec![monitor_update_step], + channel_id: Some(shutdown_res.channel_id), + }; + handle_new_monitor_update!( + self, + funded_chan.funding.get_funding_txo().unwrap(), + monitor_update, + peer_state_lock, + peer_state, + per_peer_state, + funded_chan + ); + } else { + let update_id = *peer_state + .closed_channel_monitor_update_ids + .get(&shutdown_res.channel_id) + .unwrap(); + let monitor_update = ChannelMonitorUpdate { + update_id, + updates: vec![monitor_update_step], + channel_id: Some(shutdown_res.channel_id), + }; + handle_post_close_monitor_update!( + self, + shutdown_res.channel_funding_txo + .expect("We must have had a funding txo if we are applying a post-close monitor update"), + monitor_update, + peer_state_lock, + peer_state, + per_peer_state, + shutdown_res.counterparty_node_id, + shutdown_res.channel_id + ); + } let mut pending_events = self.pending_events.lock().unwrap(); pending_events.push_back(closed_event); From de7133a1e6a23c3086e8afc9da4610c81a65fc55 Mon Sep 17 00:00:00 2001 From: Joost Jager Date: Fri, 12 Dec 2025 16:13:12 +0100 Subject: [PATCH 3/6] increment update id --- lightning/src/chain/channelmonitor.rs | 1 + lightning/src/ln/channelmanager.rs | 12 ++++++++---- 2 files changed, 9 insertions(+), 4 deletions(-) diff --git a/lightning/src/chain/channelmonitor.rs b/lightning/src/chain/channelmonitor.rs index 0502f4d9829..284facfb2c5 100644 --- a/lightning/src/chain/channelmonitor.rs +++ b/lightning/src/chain/channelmonitor.rs @@ -4253,6 +4253,7 @@ impl ChannelMonitorImpl { // provide a preimage at this point. ChannelMonitorUpdateStep::PaymentPreimage { .. } => debug_assert!(self.lockdown_from_offchain), + ChannelMonitorUpdateStep::EventGenerated { .. } => {}, _ => { log_error!(logger, "Attempted to apply post-force-close ChannelMonitorUpdate of type {}", updates.updates[0].variant_name()); panic!("Attempted to apply post-force-close ChannelMonitorUpdate that wasn't providing a payment preimage"); diff --git a/lightning/src/ln/channelmanager.rs b/lightning/src/ln/channelmanager.rs index c7f55fe396d..8a9ddf41a1c 100644 --- a/lightning/src/ln/channelmanager.rs +++ b/lightning/src/ln/channelmanager.rs @@ -4731,10 +4731,14 @@ where funded_chan ); } else { - let update_id = *peer_state - .closed_channel_monitor_update_ids - .get(&shutdown_res.channel_id) - .unwrap(); + let update_id = if let Some(latest_update_id) = + peer_state.closed_channel_monitor_update_ids.get_mut(&shutdown_res.channel_id) + { + *latest_update_id = latest_update_id.saturating_add(1); + *latest_update_id + } else { + panic!("We need the latest ChannelMonitorUpdate ID to build a new update"); + }; let monitor_update = ChannelMonitorUpdate { update_id, updates: vec![monitor_update_step], From d9a579041c64cbc81b1d07de9b68bc2bf8afb86c Mon Sep 17 00:00:00 2001 From: Joost Jager Date: Mon, 15 Dec 2025 11:58:29 +0100 Subject: [PATCH 4/6] fix persistence --- lightning/src/chain/channelmonitor.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/lightning/src/chain/channelmonitor.rs b/lightning/src/chain/channelmonitor.rs index 284facfb2c5..0e0faf12b79 100644 --- a/lightning/src/chain/channelmonitor.rs +++ b/lightning/src/chain/channelmonitor.rs @@ -1763,6 +1763,7 @@ pub(crate) fn write_chanmon_internal( (34, channel_monitor.alternative_funding_confirmed, option), (35, channel_monitor.is_manual_broadcast, required), (37, channel_monitor.funding_seen_onchain, required), + (38, channel_monitor.pending_mgr_events, optional_vec), }); Ok(()) From 276688a7bd111315751fd27b74f6745289a838ad Mon Sep 17 00:00:00 2001 From: Joost Jager Date: Mon, 15 Dec 2025 13:00:47 +0100 Subject: [PATCH 5/6] ignore EventGenerated in test chain mon --- lightning/src/util/test_utils.rs | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/lightning/src/util/test_utils.rs b/lightning/src/util/test_utils.rs index 50514e0a894..45208bc51c0 100644 --- a/lightning/src/util/test_utils.rs +++ b/lightning/src/util/test_utils.rs @@ -663,6 +663,13 @@ impl<'a> chain::Watch for TestChainMonitor<'a> { } else { assert!(new_monitor == *monitor); } + + if update.updates.len() == 1 { + if let ChannelMonitorUpdateStep::EventGenerated { .. } = update.updates[0] { + return update_res; + } + } + self.added_monitors.lock().unwrap().push((channel_id, new_monitor)); update_res } From 56c381baa558d34a475c6d5de91a3f661f88c0d0 Mon Sep 17 00:00:00 2001 From: Joost Jager Date: Mon, 15 Dec 2025 14:24:02 +0100 Subject: [PATCH 6/6] forget event for unfunded chans --- lightning/src/ln/channelmanager.rs | 21 ++++++++++----------- 1 file changed, 10 insertions(+), 11 deletions(-) diff --git a/lightning/src/ln/channelmanager.rs b/lightning/src/ln/channelmanager.rs index 8a9ddf41a1c..6c5bf6bfcbd 100644 --- a/lightning/src/ln/channelmanager.rs +++ b/lightning/src/ln/channelmanager.rs @@ -4731,20 +4731,16 @@ where funded_chan ); } else { - let update_id = if let Some(latest_update_id) = + if let Some(latest_update_id) = peer_state.closed_channel_monitor_update_ids.get_mut(&shutdown_res.channel_id) { *latest_update_id = latest_update_id.saturating_add(1); - *latest_update_id - } else { - panic!("We need the latest ChannelMonitorUpdate ID to build a new update"); - }; - let monitor_update = ChannelMonitorUpdate { - update_id, - updates: vec![monitor_update_step], - channel_id: Some(shutdown_res.channel_id), - }; - handle_post_close_monitor_update!( + let monitor_update = ChannelMonitorUpdate { + update_id: *latest_update_id, + updates: vec![monitor_update_step], + channel_id: Some(shutdown_res.channel_id), + }; + handle_post_close_monitor_update!( self, shutdown_res.channel_funding_txo .expect("We must have had a funding txo if we are applying a post-close monitor update"), @@ -4755,6 +4751,9 @@ where shutdown_res.counterparty_node_id, shutdown_res.channel_id ); + } else { + log_trace!(logger, "No channel or monitor to persist closed channel event"); + }; } let mut pending_events = self.pending_events.lock().unwrap();