Skip to content
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
66 changes: 54 additions & 12 deletions lightning/src/ln/channelmanager.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3372,8 +3372,12 @@ macro_rules! process_events_body {

// TODO: This behavior should be documented. It's unintuitive that we query
// ChannelMonitors when clearing other events.
if $self.process_pending_monitor_events() {
result = NotifyOption::DoPersist;
match $self.process_pending_monitor_events() {
NotifyOption::DoPersist => result = NotifyOption::DoPersist,
NotifyOption::SkipPersistHandleEvents
if result == NotifyOption::SkipPersistNoEvents =>
result = NotifyOption::SkipPersistHandleEvents,
_ => {},
}
}

Expand Down Expand Up @@ -10263,6 +10267,8 @@ This indicates a bug inside LDK. Please report this error at https://github.com/
pending_msg_events: &mut Vec<MessageSendEvent>, is_connected: bool,
chan: &mut FundedChannel<SP>,
) -> PostMonitorUpdateChanResume {
debug_assert!(self.total_consistency_lock.try_write().is_err());

let chan_id = chan.context.channel_id();
let outbound_alias = chan.context.outbound_scid_alias();
let counterparty_node_id = chan.context.get_counterparty_node_id();
Expand All @@ -10280,6 +10286,9 @@ This indicates a bug inside LDK. Please report this error at https://github.com/

if chan.blocked_monitor_updates_pending() != 0 {
log_debug!(logger, "Channel has blocked monitor updates, completing update actions but leaving channel blocked");
if !update_actions.is_empty() {
self.needs_persist_flag.store(true, Ordering::Release);
}
PostMonitorUpdateChanResume::Blocked { update_actions }
} else {
log_debug!(logger, "Channel is open and awaiting update, resuming it");
Expand Down Expand Up @@ -10311,6 +10320,11 @@ This indicates a bug inside LDK. Please report this error at https://github.com/
None
};

// Checked before handle_channel_resumption moves these fields.
let has_state_changes = updates.funding_broadcastable.is_some()
|| updates.channel_ready.is_some()
|| updates.announcement_sigs.is_some();

let (htlc_forwards, decode_update_add_htlcs) = self.handle_channel_resumption(
pending_msg_events,
chan,
Expand All @@ -10333,6 +10347,20 @@ This indicates a bug inside LDK. Please report this error at https://github.com/
let unbroadcasted_batch_funding_txid =
chan.context.unbroadcasted_batch_funding_txid(&chan.funding);

// Queuing outbound messages (commitment_update, raa) alone does
// not require ChannelManager persistence.
let needs_persist = has_state_changes
|| !updates.finalized_claimed_htlcs.is_empty()
|| !updates.failed_htlcs.is_empty()
|| !update_actions.is_empty()
|| unbroadcasted_batch_funding_txid.is_some()
|| !htlc_forwards.is_empty()
|| decode_update_add_htlcs.is_some();

if needs_persist {
self.needs_persist_flag.store(true, Ordering::Release);
}
Comment on lines 10347 to +10362
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The needs_persist check omits committed_outbound_htlc_sources (pruning optimization, replayable) and tx_signatures (resendable message). This is likely intentional, but consider adding a comment explaining their exclusion so future maintainers of MonitorRestoreUpdates know the invariant.

More importantly: handle_channel_resumption (called above) invokes emit_channel_pending_event! and emit_initial_channel_ready_event!, which can push events to self.pending_events and mutate channel flags — neither of which is captured in this needs_persist check. This appears safe today because:

  • emit_channel_pending_event! requires is_funding_broadcastable() && !channel_pending_event_emitted — in practice this aligns with funding_broadcastable.is_some()
  • emit_initial_channel_ready_event! requires is_usable() && !initial_channel_ready_event_emitted — in practice this aligns with channel_ready.is_some()

Both conditions imply has_state_changes = true. However, this coupling is implicit. If those conditions ever diverge, events could land in pending_events without triggering persistence. In process_events_body the !pending_events.is_empty() check provides a safety net, but in get_and_clear_pending_msg_events there is no such fallback.

Consider adding a debug_assert or explicit comment documenting this invariant.


PostMonitorUpdateChanResume::Unblocked {
channel_id: chan_id,
counterparty_node_id,
Expand Down Expand Up @@ -10620,7 +10648,11 @@ This indicates a bug inside LDK. Please report this error at https://github.com/
let per_peer_state = self.per_peer_state.read().unwrap();
let mut peer_state_lock;
let peer_state_mutex_opt = per_peer_state.get(counterparty_node_id);
if peer_state_mutex_opt.is_none() { return }
if peer_state_mutex_opt.is_none() {
// Peer is gone; conservatively request persistence.
self.needs_persist_flag.store(true, Ordering::Release);
return;
}
peer_state_lock = peer_state_mutex_opt.unwrap().lock().unwrap();
let peer_state = &mut *peer_state_lock;

Expand Down Expand Up @@ -10681,6 +10713,9 @@ This indicates a bug inside LDK. Please report this error at https://github.com/
} else {
let update_actions = peer_state.monitor_update_blocked_actions
.remove(channel_id).unwrap_or(Vec::new());
if !update_actions.is_empty() {
self.needs_persist_flag.store(true, Ordering::Release);
}
log_trace!(logger, "Channel is closed, applying {} post-update actions", update_actions.len());
mem::drop(peer_state_lock);
mem::drop(per_peer_state);
Expand Down Expand Up @@ -13013,19 +13048,24 @@ This indicates a bug inside LDK. Please report this error at https://github.com/
Ok(())
}

/// Process pending events from the [`chain::Watch`], returning whether any events were processed.
fn process_pending_monitor_events(&self) -> bool {
/// Process pending events from the [`chain::Watch`], returning the appropriate
/// [`NotifyOption`] for persistence and event handling.
fn process_pending_monitor_events(&self) -> NotifyOption {
debug_assert!(self.total_consistency_lock.try_write().is_err()); // Caller holds read lock

let mut failed_channels: Vec<(Result<Infallible, _>, _)> = Vec::new();
let mut pending_monitor_events = self.chain_monitor.release_pending_monitor_events();
let has_pending_monitor_events = !pending_monitor_events.is_empty();
if pending_monitor_events.is_empty() {
return NotifyOption::SkipPersistNoEvents;
}
let mut needs_persist = false;
for (funding_outpoint, channel_id, mut monitor_events, counterparty_node_id) in
pending_monitor_events.drain(..)
{
for monitor_event in monitor_events.drain(..) {
match monitor_event {
MonitorEvent::HTLCEvent(htlc_update) => {
needs_persist = true;
let logger = WithContext::from(
&self.logger,
Some(counterparty_node_id),
Expand Down Expand Up @@ -13078,6 +13118,7 @@ This indicates a bug inside LDK. Please report this error at https://github.com/
},
MonitorEvent::HolderForceClosed(_)
| MonitorEvent::HolderForceClosedWithInfo { .. } => {
needs_persist = true;
let per_peer_state = self.per_peer_state.read().unwrap();
if let Some(peer_state_mutex) = per_peer_state.get(&counterparty_node_id) {
let mut peer_state_lock = peer_state_mutex.lock().unwrap();
Expand Down Expand Up @@ -13110,6 +13151,7 @@ This indicates a bug inside LDK. Please report this error at https://github.com/
}
},
MonitorEvent::CommitmentTxConfirmed(_) => {
needs_persist = true;
let per_peer_state = self.per_peer_state.read().unwrap();
if let Some(peer_state_mutex) = per_peer_state.get(&counterparty_node_id) {
let mut peer_state_lock = peer_state_mutex.lock().unwrap();
Expand Down Expand Up @@ -13145,7 +13187,11 @@ This indicates a bug inside LDK. Please report this error at https://github.com/
let _ = self.handle_error(err, counterparty_node_id);
}

has_pending_monitor_events
if needs_persist {
NotifyOption::DoPersist
} else {
NotifyOption::SkipPersistHandleEvents
Comment on lines 13187 to +13193
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Minor: the function now uses two distinct mechanisms for signaling persistence need:

  1. Return value (NotifyOption::DoPersist): for HTLCEvent, HolderForceClosed*, CommitmentTxConfirmed
  2. Direct flag (needs_persist_flag.store): for state changes discovered inside channel_monitor_updatedtry_resume_channel_post_monitor_update

The SkipPersistHandleEvents return here relies on the caller calling event_persist_notifier.notify() so the background processor wakes up and sees the directly-set flag. This works correctly today but is subtle — consider adding a comment at the return site explaining that persistence may have already been requested via needs_persist_flag and that the caller must still call notify() for SkipPersistHandleEvents.

}
}

fn handle_holding_cell_free_result(&self, result: FreeHoldingCellsResult) {
Expand Down Expand Up @@ -15171,8 +15217,6 @@ impl<
fn get_and_clear_pending_msg_events(&self) -> Vec<MessageSendEvent> {
let events = RefCell::new(Vec::new());
PersistenceNotifierGuard::optionally_notify(self, || {
let mut result = NotifyOption::SkipPersistNoEvents;

// This method is quite performance-sensitive. Not only is it called very often, but it
// *is* the critical path between generating a message for a peer and giving it to the
// `PeerManager` to send. Thus, we should avoid adding any more logic here than we
Expand All @@ -15181,9 +15225,7 @@ impl<

// TODO: This behavior should be documented. It's unintuitive that we query
// ChannelMonitors when clearing other events.
if self.process_pending_monitor_events() {
result = NotifyOption::DoPersist;
}
let mut result = self.process_pending_monitor_events();

if self.maybe_generate_initial_closing_signed() {
result = NotifyOption::DoPersist;
Expand Down
Loading