Skip to content

Ensure ChannelMonitorUpdates are ordered with full monitor writes #3196

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Jul 23, 2024
Merged
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
22 changes: 19 additions & 3 deletions lightning/src/chain/chainmonitor.rs
Original file line number Diff line number Diff line change
Expand Up @@ -167,10 +167,17 @@ struct MonitorHolder<ChannelSigner: EcdsaChannelSigner> {
monitor: ChannelMonitor<ChannelSigner>,
/// The full set of pending monitor updates for this Channel.
///
/// Note that this lock must be held during updates to prevent a race where we call
/// update_persisted_channel, the user returns a
/// Note that this lock must be held from [`ChannelMonitor::update_monitor`] through to
/// [`Persist::update_persisted_channel`] to prevent a race where we call
/// [`Persist::update_persisted_channel`], the user returns a
/// [`ChannelMonitorUpdateStatus::InProgress`], and then calls channel_monitor_updated
Copy link
Contributor

Choose a reason for hiding this comment

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

nit: Since you're already here, mind ticking/linkng channel_monitor_updated / Vec as well?

/// immediately, racing our insertion of the pending update into the contained Vec.
///
/// This also avoids a race where we update a [`ChannelMonitor`], then while connecting a block
/// persist a full [`ChannelMonitor`] prior to persisting the [`ChannelMonitorUpdate`]. This
/// could cause users to have a full [`ChannelMonitor`] on disk as well as a
/// [`ChannelMonitorUpdate`] which was already applied. While this isn't an issue for the
/// LDK-provided update-based [`Persist`], its somewhat surprising for users so we avoid it.
Copy link
Contributor

Choose a reason for hiding this comment

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

Suggested change
/// LDK-provided update-based [`Persist`], its somewhat surprising for users so we avoid it.
/// LDK-provided update-based [`Persist`], it is somewhat surprising for users so we avoid it.

pending_monitor_updates: Mutex<Vec<u64>>,
}

Expand Down Expand Up @@ -322,6 +329,11 @@ where C::Target: chain::Filter,
let has_pending_claims = monitor_state.monitor.has_pending_claims();
if has_pending_claims || get_partition_key(funding_outpoint) % partition_factor == 0 {
log_trace!(logger, "Syncing Channel Monitor for channel {}", log_funding_info!(monitor));
// Even though we don't track monitor updates from chain-sync as pending, we still want
// updates per-channel to be well-ordered so that users don't see a
// `ChannelMonitorUpdate` after a channel persist for a channel with the same
// `latest_update_id`.
let _pending_monitor_updates = monitor_state.pending_monitor_updates.lock().unwrap();
match self.persister.update_persisted_channel(*funding_outpoint, None, monitor) {
ChannelMonitorUpdateStatus::Completed =>
log_trace!(logger, "Finished syncing Channel Monitor for channel {} for block-data",
Expand Down Expand Up @@ -796,10 +808,14 @@ where C::Target: chain::Filter,
let monitor = &monitor_state.monitor;
let logger = WithChannelMonitor::from(&self.logger, &monitor, None);
log_trace!(logger, "Updating ChannelMonitor to id {} for channel {}", update.update_id, log_funding_info!(monitor));

// We hold a `pending_monitor_updates` lock through `update_monitor` to ensure we
// have well-ordered updates from the users' point of view. See the
// `pending_monitor_updates` docs for more.
let mut pending_monitor_updates = monitor_state.pending_monitor_updates.lock().unwrap();
let update_res = monitor.update_monitor(update, &self.broadcaster, &self.fee_estimator, &self.logger);

let update_id = update.update_id;
let mut pending_monitor_updates = monitor_state.pending_monitor_updates.lock().unwrap();
let persist_res = if update_res.is_err() {
// Even if updating the monitor returns an error, the monitor's state will
// still be changed. Therefore, we should persist the updated monitor despite the error.
Expand Down
Loading