Only mark all mon updates complete if there are no blocked updates - #3907

Merged
TheBlueMatt merged 1 commit into
lightningdevkit:mainfrom
TheBlueMatt:2025-06-no-mon-compl-with-blocked-updates
Jul 11, 2025
Merged

Only mark all mon updates complete if there are no blocked updates#3907
TheBlueMatt merged 1 commit into
lightningdevkit:mainfrom
TheBlueMatt:2025-06-no-mon-compl-with-blocked-updates

Conversation

@TheBlueMatt

Copy link
Copy Markdown
Collaborator

In handle_new_monitor_update!, we correctly check that the channel doesn't have any blocked monitor updates pending before calling handle_monitor_update_completion! (which calls Channel::monitor_updating_restored, which in turn assumes that all generated ChannelMonitorUpdates, including blocked ones, have completed).

We, however, did not do the same check at several other places where we called handle_monitor_update_completion!. Specifically, after a monitor update completes during reload (processed via a BackgroundEvent or when monitor update completes async, we didn't check if there were any blocked monitor updates before completing).

Here we add the missing check, as well as an assertion in Channel::monitor_updating_restored.

@ldk-reviews-bot

ldk-reviews-bot commented Jul 3, 2025

Copy link
Copy Markdown

I've assigned @wpaulino as a reviewer!
I'll wait for their review and will help manage the review process.
Once they submit their review, I'll check if a second reviewer would be helpful.

@TheBlueMatt
TheBlueMattforce-pushed the 2025-06-no-mon-compl-with-blocked-updates branch 2 times, most recently from 6bfe0bd to 49ad15cCompareJuly 3, 2025 01:33
Comment threadlightning/src/ln/quiescence_tests.rs Outdated
@TheBlueMatt
TheBlueMattforce-pushed the 2025-06-no-mon-compl-with-blocked-updates branch 2 times, most recently from 7759b4d to 95c0696CompareJuly 3, 2025 01:36
Comment threadlightning/src/ln/quiescence_tests.rs
@TheBlueMatt
TheBlueMattforce-pushed the 2025-06-no-mon-compl-with-blocked-updates branch from 95c0696 to ab0dda7CompareJuly 3, 2025 01:36
@codecov

codecovBot commented Jul 3, 2025

Copy link
Copy Markdown

Codecov Report

Attention: Patch coverage is 97.05882% with 2 lines in your changes missing coverage. Please review.

Project coverage is 88.80%. Comparing base (257ebad) to head (ab0dda7).

Files with missing linesPatch %Lines
lightning/src/ln/chanmon_update_fail_tests.rs97.95%0 Missing and 1 partial ⚠️
lightning/src/ln/quiescence_tests.rs91.66%1 Missing ⚠️
Additional details and impacted files
@@ Coverage Diff @@## main #3907 +/- ##
==========================================
- Coverage 88.82% 88.80% -0.02% 
==========================================
Files 165 165 Lines 119075 119123 +48 Branches 119075 119123 +48 ==========================================
+ Hits 105769 105790 +21 - Misses 10986 11003 +17 - Partials 2320 2330 +10 

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

@ldk-reviews-bot

Copy link
Copy Markdown

🔔 1st Reminder

Hey @wpaulino! This PR has been waiting for your review.
Please take a look when you have a chance. If you're unable to review, please let us know so we can find another reviewer.

@ldk-reviews-bot

Copy link
Copy Markdown

🔔 2nd Reminder

Hey @wpaulino! This PR has been waiting for your review.
Please take a look when you have a chance. If you're unable to review, please let us know so we can find another reviewer.

log_trace!(logger, "Channel is open and awaiting update, resuming it");
handle_monitor_update_completion!(self, peer_state_lock, peer_state, per_peer_state, chan);
if chan.blocked_monitor_updates_pending() == 0 {
handle_monitor_update_completion!(self, peer_state_lock, peer_state, per_peer_state, chan);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Wouldn't we still want to call self.handle_monitor_update_completion_actions so that we can actually unblock any monitor updates that are pending blocked?

Copy link
Copy Markdown
CollaboratorAuthor

Choose a reason for hiding this comment

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

Hmm, we don't do so in handle_new_monitor_update's base case, which I was trying to match everywhere. A few tests fail if we change it there (with almost-harmless message ordering changes, so nothing major, but we'd have to fix that), but I'm not convinced its a bug - a channel should never block itself, and the unblocking of the next monitor update in a channel has to come in from a different channel or from an event, not from the channel itself. So that other channel, once it makes progress, should always unblock us.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Yeah that's what I figured, was just being overly-cautious in case we ever introduce such a monitor update that blocks the same channel.

Copy link
Copy Markdown
CollaboratorAuthor

Choose a reason for hiding this comment

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

Yea, I think you're right that that change makes sense, but I don't think it makes sense as a backport and we should do it everywhere, not just in the new places, in a separate PR.

Comment threadlightning/src/ln/channelmanager.rs Outdated
@wpaulino

Copy link
Copy Markdown
Contributor

LGTM after squash

@TheBlueMatt
TheBlueMattforce-pushed the 2025-06-no-mon-compl-with-blocked-updates branch from ac695db to b47d7dbCompareJuly 8, 2025 22:43
@TheBlueMatt

Copy link
Copy Markdown
CollaboratorAuthor

Squashed without further changes.

wpaulino
wpaulino previously approved these changes Jul 8, 2025
@TheBlueMattTheBlueMatt added the weekly goal Someone wants to land this this week label Jul 9, 2025
@ldk-reviews-bot

Copy link
Copy Markdown

✅ Added second reviewer: @joostjager

@joostjagerjoostjager left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

I'd ask someone with more background on the channel state machine as a 2nd reviewer.

Comment threadlightning/src/ln/chanmon_update_fail_tests.rs Outdated
Comment threadlightning/src/ln/channelmanager.rs Outdated
@TheBlueMattTheBlueMatt self-assigned this Jul 10, 2025

@valentinewallacevalentinewallace left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

LGTM logic-wise, nice catch. Had to re-read some code to refresh on the paths but it makes sense.

Comment threadlightning/src/ln/chanmon_update_fail_tests.rs Outdated
Comment threadlightning/src/ln/quiescence_tests.rs
In `handle_new_monitor_update!`, we correctly check that the
channel doesn't have any blocked monitor updates pending before
calling `handle_monitor_update_completion!` (which calls
`Channel::monitor_updating_restored`, which in turn assumes that
all generated `ChannelMonitorUpdate`s, including blocked ones, have
completed).
We, however, did not do the same check at several other places
where we called `handle_monitor_update_completion!`. Specifically,
after a monitor update completes during reload (processed via a
`BackgroundEvent` or when monitor update completes async, we didn't
check if there were any blocked monitor updates before completing).
Here we add the missing check, as well as an assertion in
`Channel::monitor_updating_restored`.
@TheBlueMatt
TheBlueMattforce-pushed the 2025-06-no-mon-compl-with-blocked-updates branch from b47d7db to 418d59dCompareJuly 10, 2025 23:05
@TheBlueMatt

Copy link
Copy Markdown
CollaboratorAuthor

Pushed some tirivial cleanups:

$ git diff-tree -U2 b47d7db4f1 418d59dff3
diff --git a/lightning/src/ln/chanmon_update_fail_tests.rs b/lightning/src/ln/chanmon_update_fail_tests.rs
index 2a3f3b52ba..c8d408425f 100644
--- a/lightning/src/ln/chanmon_update_fail_tests.rs+++ b/lightning/src/ln/chanmon_update_fail_tests.rs@@ -3411,5 +3411,12 @@ fn test_inbound_reload_without_init_mon() {
}
-fn do_test_blocked_chan_preimage_release(completion_mode: u8) {+#[derive(PartialEq, Eq)]+enum BlockedUpdateComplMode {+	Async,+	AtReload,+	Sync,+}++fn do_test_blocked_chan_preimage_release(completion_mode: BlockedUpdateComplMode) {
// Test that even if a channel's `ChannelMonitorUpdate` flow is blocked waiting on an event to
// be handled HTLC preimage `ChannelMonitorUpdate`s will still go out.
@@ -3459,5 +3466,5 @@ fn do_test_blocked_chan_preimage_release(completion_mode: u8) {
let as_htlc_fulfill_updates = get_htlc_update_msgs!(nodes[0], node_b_id);
-	if completion_mode != 0 {+	if completion_mode != BlockedUpdateComplMode::Sync {
// We use to incorrectly handle monitor update completion in cases where we completed a
// monitor update async or after reload. We test both based on the `completion_mode`.
@@ -3470,5 +3477,5 @@ fn do_test_blocked_chan_preimage_release(completion_mode: u8) {
assert!(get_monitor!(nodes[1], chan_id_2).get_stored_preimages().contains_key(&payment_hash_2));
assert!(nodes[1].node.get_and_clear_pending_msg_events().is_empty());
-	if completion_mode == 1 {+	if completion_mode == BlockedUpdateComplMode::AtReload {
let node_ser = nodes[1].node.encode();
let chan_mon_0 = get_monitor!(nodes[1], chan_id_1).encode();
@@ -3487,5 +3494,5 @@ fn do_test_blocked_chan_preimage_release(completion_mode: u8) {
reconnect_nodes(a_b_reconnect);
reconnect_nodes(ReconnectArgs::new(&nodes[2], &nodes[1]));
-	} else if completion_mode == 2 {+	} else if completion_mode == BlockedUpdateComplMode::Async {
let (latest_update, _) = get_latest_mon_update_id(&nodes[1], chan_id_2);
nodes[1]
@@ -3499,6 +3506,7 @@ fn do_test_blocked_chan_preimage_release(completion_mode: u8) {
// update_fulfill_htlc + CS is held, even though the preimage is already on disk for the
// channel.
-	// Note that in completion_mode 1 we completed the CS dance in `reconnect_nodes` above.-	if completion_mode != 1 {+	// Note that when completing as a side effect of a reload we completed the CS dance in+	// `reconnect_nodes` above.+	if completion_mode != BlockedUpdateComplMode::AtReload {
nodes[1].node.handle_commitment_signed_batch_test(
node_a_id,
@@ -3547,7 +3555,7 @@ fn do_test_blocked_chan_preimage_release(completion_mode: u8) {
#[test]
fn test_blocked_chan_preimage_release() {
-	do_test_blocked_chan_preimage_release(0);-	do_test_blocked_chan_preimage_release(1);-	do_test_blocked_chan_preimage_release(2);+	do_test_blocked_chan_preimage_release(BlockedUpdateComplMode::AtReload);+	do_test_blocked_chan_preimage_release(BlockedUpdateComplMode::Sync);+	do_test_blocked_chan_preimage_release(BlockedUpdateComplMode::Async);
}
diff --git a/lightning/src/ln/channelmanager.rs b/lightning/src/ln/channelmanager.rs
index b9a0c01c6a..195fbc286e 100644
--- a/lightning/src/ln/channelmanager.rs+++ b/lightning/src/ln/channelmanager.rs@@ -8229,13 +8229,9 @@ This indicates a bug inside LDK. Please report this error at https://github.com/
{
if chan.is_awaiting_monitor_update() {
- let should_resume = chan.blocked_monitor_updates_pending() == 0;- let action_msg = if should_resume {- "resuming it"- } else {- "leaving it blocked due to a blocked monitor update"- };- log_trace!(logger, "Channel is open and awaiting update, {action_msg}");- if should_resume {+ if chan.blocked_monitor_updates_pending() == 0 {+ log_trace!(logger, "Channel is open and awaiting update, resuming it");
handle_monitor_update_completion!(self, peer_state_lock, peer_state, per_peer_state, chan);
+ } else {+ log_trace!(logger, "Channel is open and awaiting update, leaving it blocked due to a blocked monitor update");
}
} else {
diff --git a/lightning/src/ln/quiescence_tests.rs b/lightning/src/ln/quiescence_tests.rs
index e6274f75f0..c2ab17e726 100644
--- a/lightning/src/ln/quiescence_tests.rs+++ b/lightning/src/ln/quiescence_tests.rs@@ -255,7 +255,7 @@ fn test_quiescence_waits_for_async_signer_and_monitor_update() {
// We have two updates pending:
{
- let chain_monitor = &nodes[0].chain_monitor;+ let test_chain_mon = &nodes[0].chain_monitor;
let (_, latest_update) =
- chain_monitor.latest_monitor_update_id.lock().unwrap().get(&chan_id).unwrap().clone();+ test_chain_mon.latest_monitor_update_id.lock().unwrap().get(&chan_id).unwrap().clone();
let chain_monitor = &nodes[0].chain_monitor.chain_monitor;
// One for the latest commitment transaction update from the last `revoke_and_ack`
@@ -263,9 +263,7 @@ fn test_quiescence_waits_for_async_signer_and_monitor_update() {
expect_payment_sent(&nodes[0], preimage, None, false, true);
- let chain_monitor = &nodes[0].chain_monitor;
let (_, new_latest_update) =
- chain_monitor.latest_monitor_update_id.lock().unwrap().get(&chan_id).unwrap().clone();+ test_chain_mon.latest_monitor_update_id.lock().unwrap().get(&chan_id).unwrap().clone();
assert_eq!(new_latest_update, latest_update + 1);
- let chain_monitor = &nodes[0].chain_monitor.chain_monitor;
// One for the commitment secret update from the last `revoke_and_ack`
chain_monitor.channel_monitor_updated(chan_id, new_latest_update).unwrap();

@TheBlueMatt

Copy link
Copy Markdown
CollaboratorAuthor

Only diff since @wpaulino's ACK are the trivial changes above, so landing.

@TheBlueMatt
TheBlueMatt merged commit f5dd77c into lightningdevkit:mainJul 11, 2025
@TheBlueMattTheBlueMatt mentioned this pull request Jul 15, 2025
@TheBlueMatt

Copy link
Copy Markdown
CollaboratorAuthor

Backported in #3932

TheBlueMatt added a commit to TheBlueMatt/rust-lightning that referenced this pull request Jul 24, 2025
v0.1.5 - Jul 16, 2025 - "Async Path Reduction"
Performance Improvements
========================
* `NetworkGraph`'s expensive internal consistency checks have now been
disabled in debug builds in addition to release builds (lightningdevkit#3687).
Bug Fixes
=========
* Pathfinding which results in a multi-path payment is now substantially
smarter, using fewer paths and better optimizing fees and successes (lightningdevkit#3890).
* A counterparty delaying claiming multiple HTLCs with different expiries can
no longer cause our `ChannelMonitor` to continuously rebroadcast invalid
transactions or RBF bump attempts (lightningdevkit#3923).
* Reorgs can no longer cause us to fail to claim HTLCs after a counterparty
delayed claiming multiple HTLCs with different expiries (lightningdevkit#3923).
* Force-closing a channel while it is blocked on another channel's async
`ChannelMonitorUpdate` can no longer lead to a panic (lightningdevkit#3858).
* `ChannelMonitorUpdate`s can no longer be released to storage too early when
doing async updates or on restart. This only impacts async
`ChannelMonitorUpdate` persistence and can lead to loss of funds only in rare
cases with `ChannelMonitorUpdate` persistence order inversions (lightningdevkit#3907).
Security
========
0.1.5 fixes a vulnerability which could allow a peer to overdraw their reserve
value, potentially cutting into commitment transaction fees on channels with a
low reserve.
* Due to a bug in checking whether an HTLC is dust during acceptance, near-dust
HTLCs were not counted towards the commitment transaction fee, but did
eventually contribute to it when we built a commitment transaction. This can
be used by a counterparty to overdraw their reserve value, or, for channels
with a low reserve value, cut into the commitment transaction fee (lightningdevkit#3933).
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

weekly goalSomeone wants to land this this week

Projects

Status: Done

Development

Successfully merging this pull request may close these issues.

5 participants

@TheBlueMatt@ldk-reviews-bot@wpaulino@joostjager@valentinewallace
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Add copy buttons to all
 blocks\n(function() {\n function addCopyButtons() {\n document.querySelectorAll('pre code').forEach(function(codeBlock) {\n if (codeBlock.parentElement.hasAttribute('data-copy-added')) return;\n codeBlock.parentElement.setAttribute('data-copy-added', 'true');\n \n var btn = document.createElement('button');\n btn.textContent = 'Copy';\n btn.style.cssText = 'position:absolute;top:4px;right:4px;padding:2px 8px;font-size:11px;background:#4ecdc4;border:none;border-radius:4px;color:#1a1a2e;cursor:pointer;opacity:0.7;transition:opacity 0.2s;';\n btn.onmouseover = function() { this.style.opacity = '1'; };\n btn.onmouseout = function() { this.style.opacity = '0.7'; };\n btn.onclick = function() {\n navigator.clipboard.writeText(codeBlock.textContent).then(function() {\n btn.textContent = 'Copied!';\n setTimeout(function() { btn.textContent = 'Copy'; }, 1500);\n });\n };\n codeBlock.parentElement.style.position = 'relative';\n codeBlock.parentElement.appendChild(btn);\n });\n }\n \n addCopyButtons();\n \n // Re-run on dynamic content\n var observer = new MutationObserver(addCopyButtons);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Add Copy Buttons to Code Blocks");
}
} catch(__e) { console.warn('[Userscript:Add Copy Buttons to Code Blocks]', __e); }
})();
(function(){
try {
var __m = "github.com";
var __re = new RegExp('^' + "github\\.com" + '
Skip to content

Only mark all mon updates complete if there are no blocked updates - #3907

Merged
TheBlueMatt merged 1 commit into
lightningdevkit:mainfrom
TheBlueMatt:2025-06-no-mon-compl-with-blocked-updates
Jul 11, 2025
Merged

Only mark all mon updates complete if there are no blocked updates#3907
TheBlueMatt merged 1 commit into
lightningdevkit:mainfrom
TheBlueMatt:2025-06-no-mon-compl-with-blocked-updates

Conversation

@TheBlueMatt

Copy link
Copy Markdown
Collaborator

In handle_new_monitor_update!, we correctly check that the channel doesn't have any blocked monitor updates pending before calling handle_monitor_update_completion! (which calls Channel::monitor_updating_restored, which in turn assumes that all generated ChannelMonitorUpdates, including blocked ones, have completed).

We, however, did not do the same check at several other places where we called handle_monitor_update_completion!. Specifically, after a monitor update completes during reload (processed via a BackgroundEvent or when monitor update completes async, we didn't check if there were any blocked monitor updates before completing).

Here we add the missing check, as well as an assertion in Channel::monitor_updating_restored.

@ldk-reviews-bot

ldk-reviews-bot commented Jul 3, 2025

Copy link
Copy Markdown

I've assigned @wpaulino as a reviewer!
I'll wait for their review and will help manage the review process.
Once they submit their review, I'll check if a second reviewer would be helpful.

@TheBlueMatt
TheBlueMattforce-pushed the 2025-06-no-mon-compl-with-blocked-updates branch 2 times, most recently from 6bfe0bd to 49ad15cCompareJuly 3, 2025 01:33
Comment threadlightning/src/ln/quiescence_tests.rs Outdated
@TheBlueMatt
TheBlueMattforce-pushed the 2025-06-no-mon-compl-with-blocked-updates branch 2 times, most recently from 7759b4d to 95c0696CompareJuly 3, 2025 01:36
Comment threadlightning/src/ln/quiescence_tests.rs
@TheBlueMatt
TheBlueMattforce-pushed the 2025-06-no-mon-compl-with-blocked-updates branch from 95c0696 to ab0dda7CompareJuly 3, 2025 01:36
@codecov

codecovBot commented Jul 3, 2025

Copy link
Copy Markdown

Codecov Report

Attention: Patch coverage is 97.05882% with 2 lines in your changes missing coverage. Please review.

Project coverage is 88.80%. Comparing base (257ebad) to head (ab0dda7).

Files with missing linesPatch %Lines
lightning/src/ln/chanmon_update_fail_tests.rs97.95%0 Missing and 1 partial ⚠️
lightning/src/ln/quiescence_tests.rs91.66%1 Missing ⚠️
Additional details and impacted files
@@ Coverage Diff @@## main #3907 +/- ##
==========================================
- Coverage 88.82% 88.80% -0.02% 
==========================================
Files 165 165 Lines 119075 119123 +48 Branches 119075 119123 +48 ==========================================
+ Hits 105769 105790 +21 - Misses 10986 11003 +17 - Partials 2320 2330 +10 

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

@ldk-reviews-bot

Copy link
Copy Markdown

🔔 1st Reminder

Hey @wpaulino! This PR has been waiting for your review.
Please take a look when you have a chance. If you're unable to review, please let us know so we can find another reviewer.

@ldk-reviews-bot

Copy link
Copy Markdown

🔔 2nd Reminder

Hey @wpaulino! This PR has been waiting for your review.
Please take a look when you have a chance. If you're unable to review, please let us know so we can find another reviewer.

log_trace!(logger, "Channel is open and awaiting update, resuming it");
handle_monitor_update_completion!(self, peer_state_lock, peer_state, per_peer_state, chan);
if chan.blocked_monitor_updates_pending() == 0 {
handle_monitor_update_completion!(self, peer_state_lock, peer_state, per_peer_state, chan);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Wouldn't we still want to call self.handle_monitor_update_completion_actions so that we can actually unblock any monitor updates that are pending blocked?

Copy link
Copy Markdown
CollaboratorAuthor

Choose a reason for hiding this comment

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

Hmm, we don't do so in handle_new_monitor_update's base case, which I was trying to match everywhere. A few tests fail if we change it there (with almost-harmless message ordering changes, so nothing major, but we'd have to fix that), but I'm not convinced its a bug - a channel should never block itself, and the unblocking of the next monitor update in a channel has to come in from a different channel or from an event, not from the channel itself. So that other channel, once it makes progress, should always unblock us.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Yeah that's what I figured, was just being overly-cautious in case we ever introduce such a monitor update that blocks the same channel.

Copy link
Copy Markdown
CollaboratorAuthor

Choose a reason for hiding this comment

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

Yea, I think you're right that that change makes sense, but I don't think it makes sense as a backport and we should do it everywhere, not just in the new places, in a separate PR.

Comment threadlightning/src/ln/channelmanager.rs Outdated
@wpaulino

Copy link
Copy Markdown
Contributor

LGTM after squash

@TheBlueMatt
TheBlueMattforce-pushed the 2025-06-no-mon-compl-with-blocked-updates branch from ac695db to b47d7dbCompareJuly 8, 2025 22:43
@TheBlueMatt

Copy link
Copy Markdown
CollaboratorAuthor

Squashed without further changes.

wpaulino
wpaulino previously approved these changes Jul 8, 2025
@TheBlueMattTheBlueMatt added the weekly goal Someone wants to land this this week label Jul 9, 2025
@ldk-reviews-bot

Copy link
Copy Markdown

✅ Added second reviewer: @joostjager

@joostjagerjoostjager left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

I'd ask someone with more background on the channel state machine as a 2nd reviewer.

Comment threadlightning/src/ln/chanmon_update_fail_tests.rs Outdated
Comment threadlightning/src/ln/channelmanager.rs Outdated
@TheBlueMattTheBlueMatt self-assigned this Jul 10, 2025

@valentinewallacevalentinewallace left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

LGTM logic-wise, nice catch. Had to re-read some code to refresh on the paths but it makes sense.

Comment threadlightning/src/ln/chanmon_update_fail_tests.rs Outdated
Comment threadlightning/src/ln/quiescence_tests.rs
In `handle_new_monitor_update!`, we correctly check that the
channel doesn't have any blocked monitor updates pending before
calling `handle_monitor_update_completion!` (which calls
`Channel::monitor_updating_restored`, which in turn assumes that
all generated `ChannelMonitorUpdate`s, including blocked ones, have
completed).
We, however, did not do the same check at several other places
where we called `handle_monitor_update_completion!`. Specifically,
after a monitor update completes during reload (processed via a
`BackgroundEvent` or when monitor update completes async, we didn't
check if there were any blocked monitor updates before completing).
Here we add the missing check, as well as an assertion in
`Channel::monitor_updating_restored`.
@TheBlueMatt
TheBlueMattforce-pushed the 2025-06-no-mon-compl-with-blocked-updates branch from b47d7db to 418d59dCompareJuly 10, 2025 23:05
@TheBlueMatt

Copy link
Copy Markdown
CollaboratorAuthor

Pushed some tirivial cleanups:

$ git diff-tree -U2 b47d7db4f1 418d59dff3
diff --git a/lightning/src/ln/chanmon_update_fail_tests.rs b/lightning/src/ln/chanmon_update_fail_tests.rs
index 2a3f3b52ba..c8d408425f 100644
--- a/lightning/src/ln/chanmon_update_fail_tests.rs+++ b/lightning/src/ln/chanmon_update_fail_tests.rs@@ -3411,5 +3411,12 @@ fn test_inbound_reload_without_init_mon() {
}
-fn do_test_blocked_chan_preimage_release(completion_mode: u8) {+#[derive(PartialEq, Eq)]+enum BlockedUpdateComplMode {+	Async,+	AtReload,+	Sync,+}++fn do_test_blocked_chan_preimage_release(completion_mode: BlockedUpdateComplMode) {
// Test that even if a channel's `ChannelMonitorUpdate` flow is blocked waiting on an event to
// be handled HTLC preimage `ChannelMonitorUpdate`s will still go out.
@@ -3459,5 +3466,5 @@ fn do_test_blocked_chan_preimage_release(completion_mode: u8) {
let as_htlc_fulfill_updates = get_htlc_update_msgs!(nodes[0], node_b_id);
-	if completion_mode != 0 {+	if completion_mode != BlockedUpdateComplMode::Sync {
// We use to incorrectly handle monitor update completion in cases where we completed a
// monitor update async or after reload. We test both based on the `completion_mode`.
@@ -3470,5 +3477,5 @@ fn do_test_blocked_chan_preimage_release(completion_mode: u8) {
assert!(get_monitor!(nodes[1], chan_id_2).get_stored_preimages().contains_key(&payment_hash_2));
assert!(nodes[1].node.get_and_clear_pending_msg_events().is_empty());
-	if completion_mode == 1 {+	if completion_mode == BlockedUpdateComplMode::AtReload {
let node_ser = nodes[1].node.encode();
let chan_mon_0 = get_monitor!(nodes[1], chan_id_1).encode();
@@ -3487,5 +3494,5 @@ fn do_test_blocked_chan_preimage_release(completion_mode: u8) {
reconnect_nodes(a_b_reconnect);
reconnect_nodes(ReconnectArgs::new(&nodes[2], &nodes[1]));
-	} else if completion_mode == 2 {+	} else if completion_mode == BlockedUpdateComplMode::Async {
let (latest_update, _) = get_latest_mon_update_id(&nodes[1], chan_id_2);
nodes[1]
@@ -3499,6 +3506,7 @@ fn do_test_blocked_chan_preimage_release(completion_mode: u8) {
// update_fulfill_htlc + CS is held, even though the preimage is already on disk for the
// channel.
-	// Note that in completion_mode 1 we completed the CS dance in `reconnect_nodes` above.-	if completion_mode != 1 {+	// Note that when completing as a side effect of a reload we completed the CS dance in+	// `reconnect_nodes` above.+	if completion_mode != BlockedUpdateComplMode::AtReload {
nodes[1].node.handle_commitment_signed_batch_test(
node_a_id,
@@ -3547,7 +3555,7 @@ fn do_test_blocked_chan_preimage_release(completion_mode: u8) {
#[test]
fn test_blocked_chan_preimage_release() {
-	do_test_blocked_chan_preimage_release(0);-	do_test_blocked_chan_preimage_release(1);-	do_test_blocked_chan_preimage_release(2);+	do_test_blocked_chan_preimage_release(BlockedUpdateComplMode::AtReload);+	do_test_blocked_chan_preimage_release(BlockedUpdateComplMode::Sync);+	do_test_blocked_chan_preimage_release(BlockedUpdateComplMode::Async);
}
diff --git a/lightning/src/ln/channelmanager.rs b/lightning/src/ln/channelmanager.rs
index b9a0c01c6a..195fbc286e 100644
--- a/lightning/src/ln/channelmanager.rs+++ b/lightning/src/ln/channelmanager.rs@@ -8229,13 +8229,9 @@ This indicates a bug inside LDK. Please report this error at https://github.com/
{
if chan.is_awaiting_monitor_update() {
- let should_resume = chan.blocked_monitor_updates_pending() == 0;- let action_msg = if should_resume {- "resuming it"- } else {- "leaving it blocked due to a blocked monitor update"- };- log_trace!(logger, "Channel is open and awaiting update, {action_msg}");- if should_resume {+ if chan.blocked_monitor_updates_pending() == 0 {+ log_trace!(logger, "Channel is open and awaiting update, resuming it");
handle_monitor_update_completion!(self, peer_state_lock, peer_state, per_peer_state, chan);
+ } else {+ log_trace!(logger, "Channel is open and awaiting update, leaving it blocked due to a blocked monitor update");
}
} else {
diff --git a/lightning/src/ln/quiescence_tests.rs b/lightning/src/ln/quiescence_tests.rs
index e6274f75f0..c2ab17e726 100644
--- a/lightning/src/ln/quiescence_tests.rs+++ b/lightning/src/ln/quiescence_tests.rs@@ -255,7 +255,7 @@ fn test_quiescence_waits_for_async_signer_and_monitor_update() {
// We have two updates pending:
{
- let chain_monitor = &nodes[0].chain_monitor;+ let test_chain_mon = &nodes[0].chain_monitor;
let (_, latest_update) =
- chain_monitor.latest_monitor_update_id.lock().unwrap().get(&chan_id).unwrap().clone();+ test_chain_mon.latest_monitor_update_id.lock().unwrap().get(&chan_id).unwrap().clone();
let chain_monitor = &nodes[0].chain_monitor.chain_monitor;
// One for the latest commitment transaction update from the last `revoke_and_ack`
@@ -263,9 +263,7 @@ fn test_quiescence_waits_for_async_signer_and_monitor_update() {
expect_payment_sent(&nodes[0], preimage, None, false, true);
- let chain_monitor = &nodes[0].chain_monitor;
let (_, new_latest_update) =
- chain_monitor.latest_monitor_update_id.lock().unwrap().get(&chan_id).unwrap().clone();+ test_chain_mon.latest_monitor_update_id.lock().unwrap().get(&chan_id).unwrap().clone();
assert_eq!(new_latest_update, latest_update + 1);
- let chain_monitor = &nodes[0].chain_monitor.chain_monitor;
// One for the commitment secret update from the last `revoke_and_ack`
chain_monitor.channel_monitor_updated(chan_id, new_latest_update).unwrap();

@TheBlueMatt

Copy link
Copy Markdown
CollaboratorAuthor

Only diff since @wpaulino's ACK are the trivial changes above, so landing.

@TheBlueMatt
TheBlueMatt merged commit f5dd77c into lightningdevkit:mainJul 11, 2025
@TheBlueMattTheBlueMatt mentioned this pull request Jul 15, 2025
@TheBlueMatt

Copy link
Copy Markdown
CollaboratorAuthor

Backported in #3932

TheBlueMatt added a commit to TheBlueMatt/rust-lightning that referenced this pull request Jul 24, 2025
v0.1.5 - Jul 16, 2025 - "Async Path Reduction"
Performance Improvements
========================
* `NetworkGraph`'s expensive internal consistency checks have now been
disabled in debug builds in addition to release builds (lightningdevkit#3687).
Bug Fixes
=========
* Pathfinding which results in a multi-path payment is now substantially
smarter, using fewer paths and better optimizing fees and successes (lightningdevkit#3890).
* A counterparty delaying claiming multiple HTLCs with different expiries can
no longer cause our `ChannelMonitor` to continuously rebroadcast invalid
transactions or RBF bump attempts (lightningdevkit#3923).
* Reorgs can no longer cause us to fail to claim HTLCs after a counterparty
delayed claiming multiple HTLCs with different expiries (lightningdevkit#3923).
* Force-closing a channel while it is blocked on another channel's async
`ChannelMonitorUpdate` can no longer lead to a panic (lightningdevkit#3858).
* `ChannelMonitorUpdate`s can no longer be released to storage too early when
doing async updates or on restart. This only impacts async
`ChannelMonitorUpdate` persistence and can lead to loss of funds only in rare
cases with `ChannelMonitorUpdate` persistence order inversions (lightningdevkit#3907).
Security
========
0.1.5 fixes a vulnerability which could allow a peer to overdraw their reserve
value, potentially cutting into commitment transaction fees on channels with a
low reserve.
* Due to a bug in checking whether an HTLC is dust during acceptance, near-dust
HTLCs were not counted towards the commitment transaction fee, but did
eventually contribute to it when we built a commitment transaction. This can
be used by a counterparty to overdraw their reserve value, or, for channels
with a low reserve value, cut into the commitment transaction fee (lightningdevkit#3933).
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

weekly goalSomeone wants to land this this week

Projects

Status: Done

Development

Successfully merging this pull request may close these issues.

5 participants

@TheBlueMatt@ldk-reviews-bot@wpaulino@joostjager@valentinewallace
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Force GitHub README to respect dark mode\n(function() {\n var style = document.createElement('style');\n style.textContent = '\n .markdown-body {\n color-scheme: dark light;\n }\n .markdown-body pre { background: #161b22 !important; }\n .markdown-body code { background: rgba(110, 118, 129, 0.4) !important; }\n .markdown-body table th, .markdown-body table td { border-color: #30363d !important; }\n .markdown-body img { background: #0d1117; }\n .markdown-body blockquote { border-left-color: #8b949e; }\n .markdown-body hr { border-color: #30363d; }\n ';\n document.head.appendChild(style);\n})();", "GitHub Dark Mode README Fix"); } } catch(__e) { console.warn('[Userscript:GitHub Dark Mode README Fix]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

Only mark all mon updates complete if there are no blocked updates - #3907

Merged
TheBlueMatt merged 1 commit into
lightningdevkit:mainfrom
TheBlueMatt:2025-06-no-mon-compl-with-blocked-updates
Jul 11, 2025
Merged

Only mark all mon updates complete if there are no blocked updates#3907
TheBlueMatt merged 1 commit into
lightningdevkit:mainfrom
TheBlueMatt:2025-06-no-mon-compl-with-blocked-updates

Conversation

@TheBlueMatt

Copy link
Copy Markdown
Collaborator

In handle_new_monitor_update!, we correctly check that the channel doesn't have any blocked monitor updates pending before calling handle_monitor_update_completion! (which calls Channel::monitor_updating_restored, which in turn assumes that all generated ChannelMonitorUpdates, including blocked ones, have completed).

We, however, did not do the same check at several other places where we called handle_monitor_update_completion!. Specifically, after a monitor update completes during reload (processed via a BackgroundEvent or when monitor update completes async, we didn't check if there were any blocked monitor updates before completing).

Here we add the missing check, as well as an assertion in Channel::monitor_updating_restored.

@ldk-reviews-bot

ldk-reviews-bot commented Jul 3, 2025

Copy link
Copy Markdown

I've assigned @wpaulino as a reviewer!
I'll wait for their review and will help manage the review process.
Once they submit their review, I'll check if a second reviewer would be helpful.

@TheBlueMatt
TheBlueMattforce-pushed the 2025-06-no-mon-compl-with-blocked-updates branch 2 times, most recently from 6bfe0bd to 49ad15cCompareJuly 3, 2025 01:33
Comment threadlightning/src/ln/quiescence_tests.rs Outdated
@TheBlueMatt
TheBlueMattforce-pushed the 2025-06-no-mon-compl-with-blocked-updates branch 2 times, most recently from 7759b4d to 95c0696CompareJuly 3, 2025 01:36
Comment threadlightning/src/ln/quiescence_tests.rs
@TheBlueMatt
TheBlueMattforce-pushed the 2025-06-no-mon-compl-with-blocked-updates branch from 95c0696 to ab0dda7CompareJuly 3, 2025 01:36
@codecov

codecovBot commented Jul 3, 2025

Copy link
Copy Markdown

Codecov Report

Attention: Patch coverage is 97.05882% with 2 lines in your changes missing coverage. Please review.

Project coverage is 88.80%. Comparing base (257ebad) to head (ab0dda7).

Files with missing linesPatch %Lines
lightning/src/ln/chanmon_update_fail_tests.rs97.95%0 Missing and 1 partial ⚠️
lightning/src/ln/quiescence_tests.rs91.66%1 Missing ⚠️
Additional details and impacted files
@@ Coverage Diff @@## main #3907 +/- ##
==========================================
- Coverage 88.82% 88.80% -0.02% 
==========================================
Files 165 165 Lines 119075 119123 +48 Branches 119075 119123 +48 ==========================================
+ Hits 105769 105790 +21 - Misses 10986 11003 +17 - Partials 2320 2330 +10 

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

@ldk-reviews-bot

Copy link
Copy Markdown

🔔 1st Reminder

Hey @wpaulino! This PR has been waiting for your review.
Please take a look when you have a chance. If you're unable to review, please let us know so we can find another reviewer.

@ldk-reviews-bot

Copy link
Copy Markdown

🔔 2nd Reminder

Hey @wpaulino! This PR has been waiting for your review.
Please take a look when you have a chance. If you're unable to review, please let us know so we can find another reviewer.

log_trace!(logger, "Channel is open and awaiting update, resuming it");
handle_monitor_update_completion!(self, peer_state_lock, peer_state, per_peer_state, chan);
if chan.blocked_monitor_updates_pending() == 0 {
handle_monitor_update_completion!(self, peer_state_lock, peer_state, per_peer_state, chan);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Wouldn't we still want to call self.handle_monitor_update_completion_actions so that we can actually unblock any monitor updates that are pending blocked?

Copy link
Copy Markdown
CollaboratorAuthor

Choose a reason for hiding this comment

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

Hmm, we don't do so in handle_new_monitor_update's base case, which I was trying to match everywhere. A few tests fail if we change it there (with almost-harmless message ordering changes, so nothing major, but we'd have to fix that), but I'm not convinced its a bug - a channel should never block itself, and the unblocking of the next monitor update in a channel has to come in from a different channel or from an event, not from the channel itself. So that other channel, once it makes progress, should always unblock us.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Yeah that's what I figured, was just being overly-cautious in case we ever introduce such a monitor update that blocks the same channel.

Copy link
Copy Markdown
CollaboratorAuthor

Choose a reason for hiding this comment

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

Yea, I think you're right that that change makes sense, but I don't think it makes sense as a backport and we should do it everywhere, not just in the new places, in a separate PR.

Comment threadlightning/src/ln/channelmanager.rs Outdated
@wpaulino

Copy link
Copy Markdown
Contributor

LGTM after squash

@TheBlueMatt
TheBlueMattforce-pushed the 2025-06-no-mon-compl-with-blocked-updates branch from ac695db to b47d7dbCompareJuly 8, 2025 22:43
@TheBlueMatt

Copy link
Copy Markdown
CollaboratorAuthor

Squashed without further changes.

wpaulino
wpaulino previously approved these changes Jul 8, 2025
@TheBlueMattTheBlueMatt added the weekly goal Someone wants to land this this week label Jul 9, 2025
@ldk-reviews-bot

Copy link
Copy Markdown

✅ Added second reviewer: @joostjager

@joostjagerjoostjager left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

I'd ask someone with more background on the channel state machine as a 2nd reviewer.

Comment threadlightning/src/ln/chanmon_update_fail_tests.rs Outdated
Comment threadlightning/src/ln/channelmanager.rs Outdated
@TheBlueMattTheBlueMatt self-assigned this Jul 10, 2025

@valentinewallacevalentinewallace left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

LGTM logic-wise, nice catch. Had to re-read some code to refresh on the paths but it makes sense.

Comment threadlightning/src/ln/chanmon_update_fail_tests.rs Outdated
Comment threadlightning/src/ln/quiescence_tests.rs
In `handle_new_monitor_update!`, we correctly check that the
channel doesn't have any blocked monitor updates pending before
calling `handle_monitor_update_completion!` (which calls
`Channel::monitor_updating_restored`, which in turn assumes that
all generated `ChannelMonitorUpdate`s, including blocked ones, have
completed).
We, however, did not do the same check at several other places
where we called `handle_monitor_update_completion!`. Specifically,
after a monitor update completes during reload (processed via a
`BackgroundEvent` or when monitor update completes async, we didn't
check if there were any blocked monitor updates before completing).
Here we add the missing check, as well as an assertion in
`Channel::monitor_updating_restored`.
@TheBlueMatt
TheBlueMattforce-pushed the 2025-06-no-mon-compl-with-blocked-updates branch from b47d7db to 418d59dCompareJuly 10, 2025 23:05
@TheBlueMatt

Copy link
Copy Markdown
CollaboratorAuthor

Pushed some tirivial cleanups:

$ git diff-tree -U2 b47d7db4f1 418d59dff3
diff --git a/lightning/src/ln/chanmon_update_fail_tests.rs b/lightning/src/ln/chanmon_update_fail_tests.rs
index 2a3f3b52ba..c8d408425f 100644
--- a/lightning/src/ln/chanmon_update_fail_tests.rs+++ b/lightning/src/ln/chanmon_update_fail_tests.rs@@ -3411,5 +3411,12 @@ fn test_inbound_reload_without_init_mon() {
}
-fn do_test_blocked_chan_preimage_release(completion_mode: u8) {+#[derive(PartialEq, Eq)]+enum BlockedUpdateComplMode {+	Async,+	AtReload,+	Sync,+}++fn do_test_blocked_chan_preimage_release(completion_mode: BlockedUpdateComplMode) {
// Test that even if a channel's `ChannelMonitorUpdate` flow is blocked waiting on an event to
// be handled HTLC preimage `ChannelMonitorUpdate`s will still go out.
@@ -3459,5 +3466,5 @@ fn do_test_blocked_chan_preimage_release(completion_mode: u8) {
let as_htlc_fulfill_updates = get_htlc_update_msgs!(nodes[0], node_b_id);
-	if completion_mode != 0 {+	if completion_mode != BlockedUpdateComplMode::Sync {
// We use to incorrectly handle monitor update completion in cases where we completed a
// monitor update async or after reload. We test both based on the `completion_mode`.
@@ -3470,5 +3477,5 @@ fn do_test_blocked_chan_preimage_release(completion_mode: u8) {
assert!(get_monitor!(nodes[1], chan_id_2).get_stored_preimages().contains_key(&payment_hash_2));
assert!(nodes[1].node.get_and_clear_pending_msg_events().is_empty());
-	if completion_mode == 1 {+	if completion_mode == BlockedUpdateComplMode::AtReload {
let node_ser = nodes[1].node.encode();
let chan_mon_0 = get_monitor!(nodes[1], chan_id_1).encode();
@@ -3487,5 +3494,5 @@ fn do_test_blocked_chan_preimage_release(completion_mode: u8) {
reconnect_nodes(a_b_reconnect);
reconnect_nodes(ReconnectArgs::new(&nodes[2], &nodes[1]));
-	} else if completion_mode == 2 {+	} else if completion_mode == BlockedUpdateComplMode::Async {
let (latest_update, _) = get_latest_mon_update_id(&nodes[1], chan_id_2);
nodes[1]
@@ -3499,6 +3506,7 @@ fn do_test_blocked_chan_preimage_release(completion_mode: u8) {
// update_fulfill_htlc + CS is held, even though the preimage is already on disk for the
// channel.
-	// Note that in completion_mode 1 we completed the CS dance in `reconnect_nodes` above.-	if completion_mode != 1 {+	// Note that when completing as a side effect of a reload we completed the CS dance in+	// `reconnect_nodes` above.+	if completion_mode != BlockedUpdateComplMode::AtReload {
nodes[1].node.handle_commitment_signed_batch_test(
node_a_id,
@@ -3547,7 +3555,7 @@ fn do_test_blocked_chan_preimage_release(completion_mode: u8) {
#[test]
fn test_blocked_chan_preimage_release() {
-	do_test_blocked_chan_preimage_release(0);-	do_test_blocked_chan_preimage_release(1);-	do_test_blocked_chan_preimage_release(2);+	do_test_blocked_chan_preimage_release(BlockedUpdateComplMode::AtReload);+	do_test_blocked_chan_preimage_release(BlockedUpdateComplMode::Sync);+	do_test_blocked_chan_preimage_release(BlockedUpdateComplMode::Async);
}
diff --git a/lightning/src/ln/channelmanager.rs b/lightning/src/ln/channelmanager.rs
index b9a0c01c6a..195fbc286e 100644
--- a/lightning/src/ln/channelmanager.rs+++ b/lightning/src/ln/channelmanager.rs@@ -8229,13 +8229,9 @@ This indicates a bug inside LDK. Please report this error at https://github.com/
{
if chan.is_awaiting_monitor_update() {
- let should_resume = chan.blocked_monitor_updates_pending() == 0;- let action_msg = if should_resume {- "resuming it"- } else {- "leaving it blocked due to a blocked monitor update"- };- log_trace!(logger, "Channel is open and awaiting update, {action_msg}");- if should_resume {+ if chan.blocked_monitor_updates_pending() == 0 {+ log_trace!(logger, "Channel is open and awaiting update, resuming it");
handle_monitor_update_completion!(self, peer_state_lock, peer_state, per_peer_state, chan);
+ } else {+ log_trace!(logger, "Channel is open and awaiting update, leaving it blocked due to a blocked monitor update");
}
} else {
diff --git a/lightning/src/ln/quiescence_tests.rs b/lightning/src/ln/quiescence_tests.rs
index e6274f75f0..c2ab17e726 100644
--- a/lightning/src/ln/quiescence_tests.rs+++ b/lightning/src/ln/quiescence_tests.rs@@ -255,7 +255,7 @@ fn test_quiescence_waits_for_async_signer_and_monitor_update() {
// We have two updates pending:
{
- let chain_monitor = &nodes[0].chain_monitor;+ let test_chain_mon = &nodes[0].chain_monitor;
let (_, latest_update) =
- chain_monitor.latest_monitor_update_id.lock().unwrap().get(&chan_id).unwrap().clone();+ test_chain_mon.latest_monitor_update_id.lock().unwrap().get(&chan_id).unwrap().clone();
let chain_monitor = &nodes[0].chain_monitor.chain_monitor;
// One for the latest commitment transaction update from the last `revoke_and_ack`
@@ -263,9 +263,7 @@ fn test_quiescence_waits_for_async_signer_and_monitor_update() {
expect_payment_sent(&nodes[0], preimage, None, false, true);
- let chain_monitor = &nodes[0].chain_monitor;
let (_, new_latest_update) =
- chain_monitor.latest_monitor_update_id.lock().unwrap().get(&chan_id).unwrap().clone();+ test_chain_mon.latest_monitor_update_id.lock().unwrap().get(&chan_id).unwrap().clone();
assert_eq!(new_latest_update, latest_update + 1);
- let chain_monitor = &nodes[0].chain_monitor.chain_monitor;
// One for the commitment secret update from the last `revoke_and_ack`
chain_monitor.channel_monitor_updated(chan_id, new_latest_update).unwrap();

@TheBlueMatt

Copy link
Copy Markdown
CollaboratorAuthor

Only diff since @wpaulino's ACK are the trivial changes above, so landing.

@TheBlueMatt
TheBlueMatt merged commit f5dd77c into lightningdevkit:mainJul 11, 2025
@TheBlueMattTheBlueMatt mentioned this pull request Jul 15, 2025
@TheBlueMatt

Copy link
Copy Markdown
CollaboratorAuthor

Backported in #3932

TheBlueMatt added a commit to TheBlueMatt/rust-lightning that referenced this pull request Jul 24, 2025
v0.1.5 - Jul 16, 2025 - "Async Path Reduction"
Performance Improvements
========================
* `NetworkGraph`'s expensive internal consistency checks have now been
disabled in debug builds in addition to release builds (lightningdevkit#3687).
Bug Fixes
=========
* Pathfinding which results in a multi-path payment is now substantially
smarter, using fewer paths and better optimizing fees and successes (lightningdevkit#3890).
* A counterparty delaying claiming multiple HTLCs with different expiries can
no longer cause our `ChannelMonitor` to continuously rebroadcast invalid
transactions or RBF bump attempts (lightningdevkit#3923).
* Reorgs can no longer cause us to fail to claim HTLCs after a counterparty
delayed claiming multiple HTLCs with different expiries (lightningdevkit#3923).
* Force-closing a channel while it is blocked on another channel's async
`ChannelMonitorUpdate` can no longer lead to a panic (lightningdevkit#3858).
* `ChannelMonitorUpdate`s can no longer be released to storage too early when
doing async updates or on restart. This only impacts async
`ChannelMonitorUpdate` persistence and can lead to loss of funds only in rare
cases with `ChannelMonitorUpdate` persistence order inversions (lightningdevkit#3907).
Security
========
0.1.5 fixes a vulnerability which could allow a peer to overdraw their reserve
value, potentially cutting into commitment transaction fees on channels with a
low reserve.
* Due to a bug in checking whether an HTLC is dust during acceptance, near-dust
HTLCs were not counted towards the commitment transaction fee, but did
eventually contribute to it when we built a commitment transaction. This can
be used by a counterparty to overdraw their reserve value, or, for channels
with a low reserve value, cut into the commitment transaction fee (lightningdevkit#3933).
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

weekly goalSomeone wants to land this this week

Projects

Status: Done

Development

Successfully merging this pull request may close these issues.

5 participants

@TheBlueMatt@ldk-reviews-bot@wpaulino@joostjager@valentinewallace
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Highlight search terms from Google/DuckDuckGo/Bing referrer\n(function() {\n var ref = document.referrer;\n var terms = [];\n \n if (ref.includes('google.com') || ref.includes('duckduckgo.com') || ref.includes('bing.com')) {\n var url = new URL(ref);\n var q = url.searchParams.get('q') || url.searchParams.get('p');\n if (q) {\n terms = q.split(/\\s+/).filter(function(t) { return t.length > 2; });\n }\n }\n \n if (terms.length === 0) return;\n \n var style = document.createElement('style');\n style.textContent = '.userscript-highlight { background: #fbbf24; color: #1a1a2e; padding: 1px 3px; border-radius: 2px; }';\n document.head.appendChild(style);\n \n function highlight(node) {\n if (node.nodeType === 3) { // text node\n var text = node.textContent;\n var found = false;\n terms.forEach(function(term) {\n var regex = new RegExp('(' + term.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\') + ')', 'gi');\n if (regex.test(text)) {\n found = true;\n var frag = document.createDocumentFragment();\n var parts = text.split(regex);\n parts.forEach(function(part, i) {\n if (i % 2 === 0) {\n frag.appendChild(document.createTextNode(part));\n } else {\n var span = document.createElement('span');\n span.className = 'userscript-highlight';\n span.textContent = part;\n frag.appendChild(span);\n }\n });\n node.parentNode.replaceChild(frag, node);\n }\n });\n } else if (node.nodeType === 1 && node.childNodes) { // element\n var skipTags = ['SCRIPT', 'STYLE', 'NOSCRIPT', 'TEXTAREA', 'INPUT', 'SELECT'];\n if (!skipTags.includes(node.tagName)) {\n Array.from(node.childNodes).forEach(highlight);\n }\n }\n }\n \n highlight(document.body);\n \n // Re-highlight on dynamic content\n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1 || node.nodeType === 3) highlight(node);\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Highlight Search Terms"); } } catch(__e) { console.warn('[Userscript:Highlight Search Terms]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

Only mark all mon updates complete if there are no blocked updates - #3907

Merged
TheBlueMatt merged 1 commit into
lightningdevkit:mainfrom
TheBlueMatt:2025-06-no-mon-compl-with-blocked-updates
Jul 11, 2025
Merged

Only mark all mon updates complete if there are no blocked updates#3907
TheBlueMatt merged 1 commit into
lightningdevkit:mainfrom
TheBlueMatt:2025-06-no-mon-compl-with-blocked-updates

Conversation

@TheBlueMatt

Copy link
Copy Markdown
Collaborator

In handle_new_monitor_update!, we correctly check that the channel doesn't have any blocked monitor updates pending before calling handle_monitor_update_completion! (which calls Channel::monitor_updating_restored, which in turn assumes that all generated ChannelMonitorUpdates, including blocked ones, have completed).

We, however, did not do the same check at several other places where we called handle_monitor_update_completion!. Specifically, after a monitor update completes during reload (processed via a BackgroundEvent or when monitor update completes async, we didn't check if there were any blocked monitor updates before completing).

Here we add the missing check, as well as an assertion in Channel::monitor_updating_restored.

@ldk-reviews-bot

ldk-reviews-bot commented Jul 3, 2025

Copy link
Copy Markdown

I've assigned @wpaulino as a reviewer!
I'll wait for their review and will help manage the review process.
Once they submit their review, I'll check if a second reviewer would be helpful.

@TheBlueMatt
TheBlueMattforce-pushed the 2025-06-no-mon-compl-with-blocked-updates branch 2 times, most recently from 6bfe0bd to 49ad15cCompareJuly 3, 2025 01:33
Comment threadlightning/src/ln/quiescence_tests.rs Outdated
@TheBlueMatt
TheBlueMattforce-pushed the 2025-06-no-mon-compl-with-blocked-updates branch 2 times, most recently from 7759b4d to 95c0696CompareJuly 3, 2025 01:36
Comment threadlightning/src/ln/quiescence_tests.rs
@TheBlueMatt
TheBlueMattforce-pushed the 2025-06-no-mon-compl-with-blocked-updates branch from 95c0696 to ab0dda7CompareJuly 3, 2025 01:36
@codecov

codecovBot commented Jul 3, 2025

Copy link
Copy Markdown

Codecov Report

Attention: Patch coverage is 97.05882% with 2 lines in your changes missing coverage. Please review.

Project coverage is 88.80%. Comparing base (257ebad) to head (ab0dda7).

Files with missing linesPatch %Lines
lightning/src/ln/chanmon_update_fail_tests.rs97.95%0 Missing and 1 partial ⚠️
lightning/src/ln/quiescence_tests.rs91.66%1 Missing ⚠️
Additional details and impacted files
@@ Coverage Diff @@## main #3907 +/- ##
==========================================
- Coverage 88.82% 88.80% -0.02% 
==========================================
Files 165 165 Lines 119075 119123 +48 Branches 119075 119123 +48 ==========================================
+ Hits 105769 105790 +21 - Misses 10986 11003 +17 - Partials 2320 2330 +10 

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

@ldk-reviews-bot

Copy link
Copy Markdown

🔔 1st Reminder

Hey @wpaulino! This PR has been waiting for your review.
Please take a look when you have a chance. If you're unable to review, please let us know so we can find another reviewer.

@ldk-reviews-bot

Copy link
Copy Markdown

🔔 2nd Reminder

Hey @wpaulino! This PR has been waiting for your review.
Please take a look when you have a chance. If you're unable to review, please let us know so we can find another reviewer.

log_trace!(logger, "Channel is open and awaiting update, resuming it");
handle_monitor_update_completion!(self, peer_state_lock, peer_state, per_peer_state, chan);
if chan.blocked_monitor_updates_pending() == 0 {
handle_monitor_update_completion!(self, peer_state_lock, peer_state, per_peer_state, chan);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Wouldn't we still want to call self.handle_monitor_update_completion_actions so that we can actually unblock any monitor updates that are pending blocked?

Copy link
Copy Markdown
CollaboratorAuthor

Choose a reason for hiding this comment

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

Hmm, we don't do so in handle_new_monitor_update's base case, which I was trying to match everywhere. A few tests fail if we change it there (with almost-harmless message ordering changes, so nothing major, but we'd have to fix that), but I'm not convinced its a bug - a channel should never block itself, and the unblocking of the next monitor update in a channel has to come in from a different channel or from an event, not from the channel itself. So that other channel, once it makes progress, should always unblock us.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Yeah that's what I figured, was just being overly-cautious in case we ever introduce such a monitor update that blocks the same channel.

Copy link
Copy Markdown
CollaboratorAuthor

Choose a reason for hiding this comment

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

Yea, I think you're right that that change makes sense, but I don't think it makes sense as a backport and we should do it everywhere, not just in the new places, in a separate PR.

Comment threadlightning/src/ln/channelmanager.rs Outdated
@wpaulino

Copy link
Copy Markdown
Contributor

LGTM after squash

@TheBlueMatt
TheBlueMattforce-pushed the 2025-06-no-mon-compl-with-blocked-updates branch from ac695db to b47d7dbCompareJuly 8, 2025 22:43
@TheBlueMatt

Copy link
Copy Markdown
CollaboratorAuthor

Squashed without further changes.

wpaulino
wpaulino previously approved these changes Jul 8, 2025
@TheBlueMattTheBlueMatt added the weekly goal Someone wants to land this this week label Jul 9, 2025
@ldk-reviews-bot

Copy link
Copy Markdown

✅ Added second reviewer: @joostjager

@joostjagerjoostjager left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

I'd ask someone with more background on the channel state machine as a 2nd reviewer.

Comment threadlightning/src/ln/chanmon_update_fail_tests.rs Outdated
Comment threadlightning/src/ln/channelmanager.rs Outdated
@TheBlueMattTheBlueMatt self-assigned this Jul 10, 2025

@valentinewallacevalentinewallace left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

LGTM logic-wise, nice catch. Had to re-read some code to refresh on the paths but it makes sense.

Comment threadlightning/src/ln/chanmon_update_fail_tests.rs Outdated
Comment threadlightning/src/ln/quiescence_tests.rs
In `handle_new_monitor_update!`, we correctly check that the
channel doesn't have any blocked monitor updates pending before
calling `handle_monitor_update_completion!` (which calls
`Channel::monitor_updating_restored`, which in turn assumes that
all generated `ChannelMonitorUpdate`s, including blocked ones, have
completed).
We, however, did not do the same check at several other places
where we called `handle_monitor_update_completion!`. Specifically,
after a monitor update completes during reload (processed via a
`BackgroundEvent` or when monitor update completes async, we didn't
check if there were any blocked monitor updates before completing).
Here we add the missing check, as well as an assertion in
`Channel::monitor_updating_restored`.
@TheBlueMatt
TheBlueMattforce-pushed the 2025-06-no-mon-compl-with-blocked-updates branch from b47d7db to 418d59dCompareJuly 10, 2025 23:05
@TheBlueMatt

Copy link
Copy Markdown
CollaboratorAuthor

Pushed some tirivial cleanups:

$ git diff-tree -U2 b47d7db4f1 418d59dff3
diff --git a/lightning/src/ln/chanmon_update_fail_tests.rs b/lightning/src/ln/chanmon_update_fail_tests.rs
index 2a3f3b52ba..c8d408425f 100644
--- a/lightning/src/ln/chanmon_update_fail_tests.rs+++ b/lightning/src/ln/chanmon_update_fail_tests.rs@@ -3411,5 +3411,12 @@ fn test_inbound_reload_without_init_mon() {
}
-fn do_test_blocked_chan_preimage_release(completion_mode: u8) {+#[derive(PartialEq, Eq)]+enum BlockedUpdateComplMode {+	Async,+	AtReload,+	Sync,+}++fn do_test_blocked_chan_preimage_release(completion_mode: BlockedUpdateComplMode) {
// Test that even if a channel's `ChannelMonitorUpdate` flow is blocked waiting on an event to
// be handled HTLC preimage `ChannelMonitorUpdate`s will still go out.
@@ -3459,5 +3466,5 @@ fn do_test_blocked_chan_preimage_release(completion_mode: u8) {
let as_htlc_fulfill_updates = get_htlc_update_msgs!(nodes[0], node_b_id);
-	if completion_mode != 0 {+	if completion_mode != BlockedUpdateComplMode::Sync {
// We use to incorrectly handle monitor update completion in cases where we completed a
// monitor update async or after reload. We test both based on the `completion_mode`.
@@ -3470,5 +3477,5 @@ fn do_test_blocked_chan_preimage_release(completion_mode: u8) {
assert!(get_monitor!(nodes[1], chan_id_2).get_stored_preimages().contains_key(&payment_hash_2));
assert!(nodes[1].node.get_and_clear_pending_msg_events().is_empty());
-	if completion_mode == 1 {+	if completion_mode == BlockedUpdateComplMode::AtReload {
let node_ser = nodes[1].node.encode();
let chan_mon_0 = get_monitor!(nodes[1], chan_id_1).encode();
@@ -3487,5 +3494,5 @@ fn do_test_blocked_chan_preimage_release(completion_mode: u8) {
reconnect_nodes(a_b_reconnect);
reconnect_nodes(ReconnectArgs::new(&nodes[2], &nodes[1]));
-	} else if completion_mode == 2 {+	} else if completion_mode == BlockedUpdateComplMode::Async {
let (latest_update, _) = get_latest_mon_update_id(&nodes[1], chan_id_2);
nodes[1]
@@ -3499,6 +3506,7 @@ fn do_test_blocked_chan_preimage_release(completion_mode: u8) {
// update_fulfill_htlc + CS is held, even though the preimage is already on disk for the
// channel.
-	// Note that in completion_mode 1 we completed the CS dance in `reconnect_nodes` above.-	if completion_mode != 1 {+	// Note that when completing as a side effect of a reload we completed the CS dance in+	// `reconnect_nodes` above.+	if completion_mode != BlockedUpdateComplMode::AtReload {
nodes[1].node.handle_commitment_signed_batch_test(
node_a_id,
@@ -3547,7 +3555,7 @@ fn do_test_blocked_chan_preimage_release(completion_mode: u8) {
#[test]
fn test_blocked_chan_preimage_release() {
-	do_test_blocked_chan_preimage_release(0);-	do_test_blocked_chan_preimage_release(1);-	do_test_blocked_chan_preimage_release(2);+	do_test_blocked_chan_preimage_release(BlockedUpdateComplMode::AtReload);+	do_test_blocked_chan_preimage_release(BlockedUpdateComplMode::Sync);+	do_test_blocked_chan_preimage_release(BlockedUpdateComplMode::Async);
}
diff --git a/lightning/src/ln/channelmanager.rs b/lightning/src/ln/channelmanager.rs
index b9a0c01c6a..195fbc286e 100644
--- a/lightning/src/ln/channelmanager.rs+++ b/lightning/src/ln/channelmanager.rs@@ -8229,13 +8229,9 @@ This indicates a bug inside LDK. Please report this error at https://github.com/
{
if chan.is_awaiting_monitor_update() {
- let should_resume = chan.blocked_monitor_updates_pending() == 0;- let action_msg = if should_resume {- "resuming it"- } else {- "leaving it blocked due to a blocked monitor update"- };- log_trace!(logger, "Channel is open and awaiting update, {action_msg}");- if should_resume {+ if chan.blocked_monitor_updates_pending() == 0 {+ log_trace!(logger, "Channel is open and awaiting update, resuming it");
handle_monitor_update_completion!(self, peer_state_lock, peer_state, per_peer_state, chan);
+ } else {+ log_trace!(logger, "Channel is open and awaiting update, leaving it blocked due to a blocked monitor update");
}
} else {
diff --git a/lightning/src/ln/quiescence_tests.rs b/lightning/src/ln/quiescence_tests.rs
index e6274f75f0..c2ab17e726 100644
--- a/lightning/src/ln/quiescence_tests.rs+++ b/lightning/src/ln/quiescence_tests.rs@@ -255,7 +255,7 @@ fn test_quiescence_waits_for_async_signer_and_monitor_update() {
// We have two updates pending:
{
- let chain_monitor = &nodes[0].chain_monitor;+ let test_chain_mon = &nodes[0].chain_monitor;
let (_, latest_update) =
- chain_monitor.latest_monitor_update_id.lock().unwrap().get(&chan_id).unwrap().clone();+ test_chain_mon.latest_monitor_update_id.lock().unwrap().get(&chan_id).unwrap().clone();
let chain_monitor = &nodes[0].chain_monitor.chain_monitor;
// One for the latest commitment transaction update from the last `revoke_and_ack`
@@ -263,9 +263,7 @@ fn test_quiescence_waits_for_async_signer_and_monitor_update() {
expect_payment_sent(&nodes[0], preimage, None, false, true);
- let chain_monitor = &nodes[0].chain_monitor;
let (_, new_latest_update) =
- chain_monitor.latest_monitor_update_id.lock().unwrap().get(&chan_id).unwrap().clone();+ test_chain_mon.latest_monitor_update_id.lock().unwrap().get(&chan_id).unwrap().clone();
assert_eq!(new_latest_update, latest_update + 1);
- let chain_monitor = &nodes[0].chain_monitor.chain_monitor;
// One for the commitment secret update from the last `revoke_and_ack`
chain_monitor.channel_monitor_updated(chan_id, new_latest_update).unwrap();

@TheBlueMatt

Copy link
Copy Markdown
CollaboratorAuthor

Only diff since @wpaulino's ACK are the trivial changes above, so landing.

@TheBlueMatt
TheBlueMatt merged commit f5dd77c into lightningdevkit:mainJul 11, 2025
@TheBlueMattTheBlueMatt mentioned this pull request Jul 15, 2025
@TheBlueMatt

Copy link
Copy Markdown
CollaboratorAuthor

Backported in #3932

TheBlueMatt added a commit to TheBlueMatt/rust-lightning that referenced this pull request Jul 24, 2025
v0.1.5 - Jul 16, 2025 - "Async Path Reduction"
Performance Improvements
========================
* `NetworkGraph`'s expensive internal consistency checks have now been
disabled in debug builds in addition to release builds (lightningdevkit#3687).
Bug Fixes
=========
* Pathfinding which results in a multi-path payment is now substantially
smarter, using fewer paths and better optimizing fees and successes (lightningdevkit#3890).
* A counterparty delaying claiming multiple HTLCs with different expiries can
no longer cause our `ChannelMonitor` to continuously rebroadcast invalid
transactions or RBF bump attempts (lightningdevkit#3923).
* Reorgs can no longer cause us to fail to claim HTLCs after a counterparty
delayed claiming multiple HTLCs with different expiries (lightningdevkit#3923).
* Force-closing a channel while it is blocked on another channel's async
`ChannelMonitorUpdate` can no longer lead to a panic (lightningdevkit#3858).
* `ChannelMonitorUpdate`s can no longer be released to storage too early when
doing async updates or on restart. This only impacts async
`ChannelMonitorUpdate` persistence and can lead to loss of funds only in rare
cases with `ChannelMonitorUpdate` persistence order inversions (lightningdevkit#3907).
Security
========
0.1.5 fixes a vulnerability which could allow a peer to overdraw their reserve
value, potentially cutting into commitment transaction fees on channels with a
low reserve.
* Due to a bug in checking whether an HTLC is dust during acceptance, near-dust
HTLCs were not counted towards the commitment transaction fee, but did
eventually contribute to it when we built a commitment transaction. This can
be used by a counterparty to overdraw their reserve value, or, for channels
with a low reserve value, cut into the commitment transaction fee (lightningdevkit#3933).
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

weekly goalSomeone wants to land this this week

Projects

Status: Done

Development

Successfully merging this pull request may close these issues.

5 participants

@TheBlueMatt@ldk-reviews-bot@wpaulino@joostjager@valentinewallace
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Strip utm_, fbclid, gclid, etc. from all links on page\n(function() {\n var trackingParams = ['utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content',\n 'fbclid', 'gclid', 'dclid', 'msclkid', 'yclid',\n 'ref', 'ref_src', 'source', 'medium', 'campaign'];\n \n function cleanUrl(url) {\n try {\n var u = new URL(url, window.location.origin);\n var changed = false;\n trackingParams.forEach(function(p) {\n if (u.searchParams.has(p)) {\n u.searchParams.delete(p);\n changed = true;\n }\n });\n return changed ? u.toString() : url;\n } catch (e) {\n return url;\n }\n }\n \n function cleanLinks() {\n document.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n \n cleanLinks();\n \n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1) {\n if (node.tagName === 'A') cleanLinks();\n node.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Remove Tracking Parameters from Links"); } } catch(__e) { console.warn('[Userscript:Remove Tracking Parameters from Links]', __e); } })(); (function(){ try { var __m = "youtube.com"; var __re = new RegExp('^' + "youtube\\.com" + '
Skip to content

Only mark all mon updates complete if there are no blocked updates - #3907

Merged
TheBlueMatt merged 1 commit into
lightningdevkit:mainfrom
TheBlueMatt:2025-06-no-mon-compl-with-blocked-updates
Jul 11, 2025
Merged

Only mark all mon updates complete if there are no blocked updates#3907
TheBlueMatt merged 1 commit into
lightningdevkit:mainfrom
TheBlueMatt:2025-06-no-mon-compl-with-blocked-updates

Conversation

@TheBlueMatt

Copy link
Copy Markdown
Collaborator

In handle_new_monitor_update!, we correctly check that the channel doesn't have any blocked monitor updates pending before calling handle_monitor_update_completion! (which calls Channel::monitor_updating_restored, which in turn assumes that all generated ChannelMonitorUpdates, including blocked ones, have completed).

We, however, did not do the same check at several other places where we called handle_monitor_update_completion!. Specifically, after a monitor update completes during reload (processed via a BackgroundEvent or when monitor update completes async, we didn't check if there were any blocked monitor updates before completing).

Here we add the missing check, as well as an assertion in Channel::monitor_updating_restored.

@ldk-reviews-bot

ldk-reviews-bot commented Jul 3, 2025

Copy link
Copy Markdown

I've assigned @wpaulino as a reviewer!
I'll wait for their review and will help manage the review process.
Once they submit their review, I'll check if a second reviewer would be helpful.

@TheBlueMatt
TheBlueMattforce-pushed the 2025-06-no-mon-compl-with-blocked-updates branch 2 times, most recently from 6bfe0bd to 49ad15cCompareJuly 3, 2025 01:33
Comment threadlightning/src/ln/quiescence_tests.rs Outdated
@TheBlueMatt
TheBlueMattforce-pushed the 2025-06-no-mon-compl-with-blocked-updates branch 2 times, most recently from 7759b4d to 95c0696CompareJuly 3, 2025 01:36
Comment threadlightning/src/ln/quiescence_tests.rs
@TheBlueMatt
TheBlueMattforce-pushed the 2025-06-no-mon-compl-with-blocked-updates branch from 95c0696 to ab0dda7CompareJuly 3, 2025 01:36
@codecov

codecovBot commented Jul 3, 2025

Copy link
Copy Markdown

Codecov Report

Attention: Patch coverage is 97.05882% with 2 lines in your changes missing coverage. Please review.

Project coverage is 88.80%. Comparing base (257ebad) to head (ab0dda7).

Files with missing linesPatch %Lines
lightning/src/ln/chanmon_update_fail_tests.rs97.95%0 Missing and 1 partial ⚠️
lightning/src/ln/quiescence_tests.rs91.66%1 Missing ⚠️
Additional details and impacted files
@@ Coverage Diff @@## main #3907 +/- ##
==========================================
- Coverage 88.82% 88.80% -0.02% 
==========================================
Files 165 165 Lines 119075 119123 +48 Branches 119075 119123 +48 ==========================================
+ Hits 105769 105790 +21 - Misses 10986 11003 +17 - Partials 2320 2330 +10 

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

@ldk-reviews-bot

Copy link
Copy Markdown

🔔 1st Reminder

Hey @wpaulino! This PR has been waiting for your review.
Please take a look when you have a chance. If you're unable to review, please let us know so we can find another reviewer.

@ldk-reviews-bot

Copy link
Copy Markdown

🔔 2nd Reminder

Hey @wpaulino! This PR has been waiting for your review.
Please take a look when you have a chance. If you're unable to review, please let us know so we can find another reviewer.

log_trace!(logger, "Channel is open and awaiting update, resuming it");
handle_monitor_update_completion!(self, peer_state_lock, peer_state, per_peer_state, chan);
if chan.blocked_monitor_updates_pending() == 0 {
handle_monitor_update_completion!(self, peer_state_lock, peer_state, per_peer_state, chan);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Wouldn't we still want to call self.handle_monitor_update_completion_actions so that we can actually unblock any monitor updates that are pending blocked?

Copy link
Copy Markdown
CollaboratorAuthor

Choose a reason for hiding this comment

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

Hmm, we don't do so in handle_new_monitor_update's base case, which I was trying to match everywhere. A few tests fail if we change it there (with almost-harmless message ordering changes, so nothing major, but we'd have to fix that), but I'm not convinced its a bug - a channel should never block itself, and the unblocking of the next monitor update in a channel has to come in from a different channel or from an event, not from the channel itself. So that other channel, once it makes progress, should always unblock us.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Yeah that's what I figured, was just being overly-cautious in case we ever introduce such a monitor update that blocks the same channel.

Copy link
Copy Markdown
CollaboratorAuthor

Choose a reason for hiding this comment

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

Yea, I think you're right that that change makes sense, but I don't think it makes sense as a backport and we should do it everywhere, not just in the new places, in a separate PR.

Comment threadlightning/src/ln/channelmanager.rs Outdated
@wpaulino

Copy link
Copy Markdown
Contributor

LGTM after squash

@TheBlueMatt
TheBlueMattforce-pushed the 2025-06-no-mon-compl-with-blocked-updates branch from ac695db to b47d7dbCompareJuly 8, 2025 22:43
@TheBlueMatt

Copy link
Copy Markdown
CollaboratorAuthor

Squashed without further changes.

wpaulino
wpaulino previously approved these changes Jul 8, 2025
@TheBlueMattTheBlueMatt added the weekly goal Someone wants to land this this week label Jul 9, 2025
@ldk-reviews-bot

Copy link
Copy Markdown

✅ Added second reviewer: @joostjager

@joostjagerjoostjager left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

I'd ask someone with more background on the channel state machine as a 2nd reviewer.

Comment threadlightning/src/ln/chanmon_update_fail_tests.rs Outdated
Comment threadlightning/src/ln/channelmanager.rs Outdated
@TheBlueMattTheBlueMatt self-assigned this Jul 10, 2025

@valentinewallacevalentinewallace left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

LGTM logic-wise, nice catch. Had to re-read some code to refresh on the paths but it makes sense.

Comment threadlightning/src/ln/chanmon_update_fail_tests.rs Outdated
Comment threadlightning/src/ln/quiescence_tests.rs
In `handle_new_monitor_update!`, we correctly check that the
channel doesn't have any blocked monitor updates pending before
calling `handle_monitor_update_completion!` (which calls
`Channel::monitor_updating_restored`, which in turn assumes that
all generated `ChannelMonitorUpdate`s, including blocked ones, have
completed).
We, however, did not do the same check at several other places
where we called `handle_monitor_update_completion!`. Specifically,
after a monitor update completes during reload (processed via a
`BackgroundEvent` or when monitor update completes async, we didn't
check if there were any blocked monitor updates before completing).
Here we add the missing check, as well as an assertion in
`Channel::monitor_updating_restored`.
@TheBlueMatt
TheBlueMattforce-pushed the 2025-06-no-mon-compl-with-blocked-updates branch from b47d7db to 418d59dCompareJuly 10, 2025 23:05
@TheBlueMatt

Copy link
Copy Markdown
CollaboratorAuthor

Pushed some tirivial cleanups:

$ git diff-tree -U2 b47d7db4f1 418d59dff3
diff --git a/lightning/src/ln/chanmon_update_fail_tests.rs b/lightning/src/ln/chanmon_update_fail_tests.rs
index 2a3f3b52ba..c8d408425f 100644
--- a/lightning/src/ln/chanmon_update_fail_tests.rs+++ b/lightning/src/ln/chanmon_update_fail_tests.rs@@ -3411,5 +3411,12 @@ fn test_inbound_reload_without_init_mon() {
}
-fn do_test_blocked_chan_preimage_release(completion_mode: u8) {+#[derive(PartialEq, Eq)]+enum BlockedUpdateComplMode {+	Async,+	AtReload,+	Sync,+}++fn do_test_blocked_chan_preimage_release(completion_mode: BlockedUpdateComplMode) {
// Test that even if a channel's `ChannelMonitorUpdate` flow is blocked waiting on an event to
// be handled HTLC preimage `ChannelMonitorUpdate`s will still go out.
@@ -3459,5 +3466,5 @@ fn do_test_blocked_chan_preimage_release(completion_mode: u8) {
let as_htlc_fulfill_updates = get_htlc_update_msgs!(nodes[0], node_b_id);
-	if completion_mode != 0 {+	if completion_mode != BlockedUpdateComplMode::Sync {
// We use to incorrectly handle monitor update completion in cases where we completed a
// monitor update async or after reload. We test both based on the `completion_mode`.
@@ -3470,5 +3477,5 @@ fn do_test_blocked_chan_preimage_release(completion_mode: u8) {
assert!(get_monitor!(nodes[1], chan_id_2).get_stored_preimages().contains_key(&payment_hash_2));
assert!(nodes[1].node.get_and_clear_pending_msg_events().is_empty());
-	if completion_mode == 1 {+	if completion_mode == BlockedUpdateComplMode::AtReload {
let node_ser = nodes[1].node.encode();
let chan_mon_0 = get_monitor!(nodes[1], chan_id_1).encode();
@@ -3487,5 +3494,5 @@ fn do_test_blocked_chan_preimage_release(completion_mode: u8) {
reconnect_nodes(a_b_reconnect);
reconnect_nodes(ReconnectArgs::new(&nodes[2], &nodes[1]));
-	} else if completion_mode == 2 {+	} else if completion_mode == BlockedUpdateComplMode::Async {
let (latest_update, _) = get_latest_mon_update_id(&nodes[1], chan_id_2);
nodes[1]
@@ -3499,6 +3506,7 @@ fn do_test_blocked_chan_preimage_release(completion_mode: u8) {
// update_fulfill_htlc + CS is held, even though the preimage is already on disk for the
// channel.
-	// Note that in completion_mode 1 we completed the CS dance in `reconnect_nodes` above.-	if completion_mode != 1 {+	// Note that when completing as a side effect of a reload we completed the CS dance in+	// `reconnect_nodes` above.+	if completion_mode != BlockedUpdateComplMode::AtReload {
nodes[1].node.handle_commitment_signed_batch_test(
node_a_id,
@@ -3547,7 +3555,7 @@ fn do_test_blocked_chan_preimage_release(completion_mode: u8) {
#[test]
fn test_blocked_chan_preimage_release() {
-	do_test_blocked_chan_preimage_release(0);-	do_test_blocked_chan_preimage_release(1);-	do_test_blocked_chan_preimage_release(2);+	do_test_blocked_chan_preimage_release(BlockedUpdateComplMode::AtReload);+	do_test_blocked_chan_preimage_release(BlockedUpdateComplMode::Sync);+	do_test_blocked_chan_preimage_release(BlockedUpdateComplMode::Async);
}
diff --git a/lightning/src/ln/channelmanager.rs b/lightning/src/ln/channelmanager.rs
index b9a0c01c6a..195fbc286e 100644
--- a/lightning/src/ln/channelmanager.rs+++ b/lightning/src/ln/channelmanager.rs@@ -8229,13 +8229,9 @@ This indicates a bug inside LDK. Please report this error at https://github.com/
{
if chan.is_awaiting_monitor_update() {
- let should_resume = chan.blocked_monitor_updates_pending() == 0;- let action_msg = if should_resume {- "resuming it"- } else {- "leaving it blocked due to a blocked monitor update"- };- log_trace!(logger, "Channel is open and awaiting update, {action_msg}");- if should_resume {+ if chan.blocked_monitor_updates_pending() == 0 {+ log_trace!(logger, "Channel is open and awaiting update, resuming it");
handle_monitor_update_completion!(self, peer_state_lock, peer_state, per_peer_state, chan);
+ } else {+ log_trace!(logger, "Channel is open and awaiting update, leaving it blocked due to a blocked monitor update");
}
} else {
diff --git a/lightning/src/ln/quiescence_tests.rs b/lightning/src/ln/quiescence_tests.rs
index e6274f75f0..c2ab17e726 100644
--- a/lightning/src/ln/quiescence_tests.rs+++ b/lightning/src/ln/quiescence_tests.rs@@ -255,7 +255,7 @@ fn test_quiescence_waits_for_async_signer_and_monitor_update() {
// We have two updates pending:
{
- let chain_monitor = &nodes[0].chain_monitor;+ let test_chain_mon = &nodes[0].chain_monitor;
let (_, latest_update) =
- chain_monitor.latest_monitor_update_id.lock().unwrap().get(&chan_id).unwrap().clone();+ test_chain_mon.latest_monitor_update_id.lock().unwrap().get(&chan_id).unwrap().clone();
let chain_monitor = &nodes[0].chain_monitor.chain_monitor;
// One for the latest commitment transaction update from the last `revoke_and_ack`
@@ -263,9 +263,7 @@ fn test_quiescence_waits_for_async_signer_and_monitor_update() {
expect_payment_sent(&nodes[0], preimage, None, false, true);
- let chain_monitor = &nodes[0].chain_monitor;
let (_, new_latest_update) =
- chain_monitor.latest_monitor_update_id.lock().unwrap().get(&chan_id).unwrap().clone();+ test_chain_mon.latest_monitor_update_id.lock().unwrap().get(&chan_id).unwrap().clone();
assert_eq!(new_latest_update, latest_update + 1);
- let chain_monitor = &nodes[0].chain_monitor.chain_monitor;
// One for the commitment secret update from the last `revoke_and_ack`
chain_monitor.channel_monitor_updated(chan_id, new_latest_update).unwrap();

@TheBlueMatt

Copy link
Copy Markdown
CollaboratorAuthor

Only diff since @wpaulino's ACK are the trivial changes above, so landing.

@TheBlueMatt
TheBlueMatt merged commit f5dd77c into lightningdevkit:mainJul 11, 2025
@TheBlueMattTheBlueMatt mentioned this pull request Jul 15, 2025
@TheBlueMatt

Copy link
Copy Markdown
CollaboratorAuthor

Backported in #3932

TheBlueMatt added a commit to TheBlueMatt/rust-lightning that referenced this pull request Jul 24, 2025
v0.1.5 - Jul 16, 2025 - "Async Path Reduction"
Performance Improvements
========================
* `NetworkGraph`'s expensive internal consistency checks have now been
disabled in debug builds in addition to release builds (lightningdevkit#3687).
Bug Fixes
=========
* Pathfinding which results in a multi-path payment is now substantially
smarter, using fewer paths and better optimizing fees and successes (lightningdevkit#3890).
* A counterparty delaying claiming multiple HTLCs with different expiries can
no longer cause our `ChannelMonitor` to continuously rebroadcast invalid
transactions or RBF bump attempts (lightningdevkit#3923).
* Reorgs can no longer cause us to fail to claim HTLCs after a counterparty
delayed claiming multiple HTLCs with different expiries (lightningdevkit#3923).
* Force-closing a channel while it is blocked on another channel's async
`ChannelMonitorUpdate` can no longer lead to a panic (lightningdevkit#3858).
* `ChannelMonitorUpdate`s can no longer be released to storage too early when
doing async updates or on restart. This only impacts async
`ChannelMonitorUpdate` persistence and can lead to loss of funds only in rare
cases with `ChannelMonitorUpdate` persistence order inversions (lightningdevkit#3907).
Security
========
0.1.5 fixes a vulnerability which could allow a peer to overdraw their reserve
value, potentially cutting into commitment transaction fees on channels with a
low reserve.
* Due to a bug in checking whether an HTLC is dust during acceptance, near-dust
HTLCs were not counted towards the commitment transaction fee, but did
eventually contribute to it when we built a commitment transaction. This can
be used by a counterparty to overdraw their reserve value, or, for channels
with a low reserve value, cut into the commitment transaction fee (lightningdevkit#3933).
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

weekly goalSomeone wants to land this this week

Projects

Status: Done

Development

Successfully merging this pull request may close these issues.

5 participants

@TheBlueMatt@ldk-reviews-bot@wpaulino@joostjager@valentinewallace
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Auto-enable theater mode on YouTube\n(function() {\n function tryTheater() {\n var btn = document.querySelector('button[aria-label=\"Theater mode\"], ytd-player #player button[title=\"Theater mode\"]');\n if (btn && !btn.classList.contains('activated')) {\n btn.click();\n }\n }\n \n // Try immediately\n tryTheater();\n \n // Try after navigation (SPA)\n var lastUrl = location.href;\n setInterval(function() {\n if (location.href !== lastUrl) {\n lastUrl = location.href;\n setTimeout(tryTheater, 500);\n }\n }, 1000);\n \n // Also try on player load\n var observer = new MutationObserver(tryTheater);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "YouTube Theater Mode Default"); } } catch(__e) { console.warn('[Userscript:YouTube Theater Mode Default]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

Only mark all mon updates complete if there are no blocked updates - #3907

Merged
TheBlueMatt merged 1 commit into
lightningdevkit:mainfrom
TheBlueMatt:2025-06-no-mon-compl-with-blocked-updates
Jul 11, 2025
Merged

Only mark all mon updates complete if there are no blocked updates#3907
TheBlueMatt merged 1 commit into
lightningdevkit:mainfrom
TheBlueMatt:2025-06-no-mon-compl-with-blocked-updates

Conversation

@TheBlueMatt

Copy link
Copy Markdown
Collaborator

In handle_new_monitor_update!, we correctly check that the channel doesn't have any blocked monitor updates pending before calling handle_monitor_update_completion! (which calls Channel::monitor_updating_restored, which in turn assumes that all generated ChannelMonitorUpdates, including blocked ones, have completed).

We, however, did not do the same check at several other places where we called handle_monitor_update_completion!. Specifically, after a monitor update completes during reload (processed via a BackgroundEvent or when monitor update completes async, we didn't check if there were any blocked monitor updates before completing).

Here we add the missing check, as well as an assertion in Channel::monitor_updating_restored.

@ldk-reviews-bot

ldk-reviews-bot commented Jul 3, 2025

Copy link
Copy Markdown

I've assigned @wpaulino as a reviewer!
I'll wait for their review and will help manage the review process.
Once they submit their review, I'll check if a second reviewer would be helpful.

@TheBlueMatt
TheBlueMattforce-pushed the 2025-06-no-mon-compl-with-blocked-updates branch 2 times, most recently from 6bfe0bd to 49ad15cCompareJuly 3, 2025 01:33
Comment threadlightning/src/ln/quiescence_tests.rs Outdated
@TheBlueMatt
TheBlueMattforce-pushed the 2025-06-no-mon-compl-with-blocked-updates branch 2 times, most recently from 7759b4d to 95c0696CompareJuly 3, 2025 01:36
Comment threadlightning/src/ln/quiescence_tests.rs
@TheBlueMatt
TheBlueMattforce-pushed the 2025-06-no-mon-compl-with-blocked-updates branch from 95c0696 to ab0dda7CompareJuly 3, 2025 01:36
@codecov

codecovBot commented Jul 3, 2025

Copy link
Copy Markdown

Codecov Report

Attention: Patch coverage is 97.05882% with 2 lines in your changes missing coverage. Please review.

Project coverage is 88.80%. Comparing base (257ebad) to head (ab0dda7).

Files with missing linesPatch %Lines
lightning/src/ln/chanmon_update_fail_tests.rs97.95%0 Missing and 1 partial ⚠️
lightning/src/ln/quiescence_tests.rs91.66%1 Missing ⚠️
Additional details and impacted files
@@ Coverage Diff @@## main #3907 +/- ##
==========================================
- Coverage 88.82% 88.80% -0.02% 
==========================================
Files 165 165 Lines 119075 119123 +48 Branches 119075 119123 +48 ==========================================
+ Hits 105769 105790 +21 - Misses 10986 11003 +17 - Partials 2320 2330 +10 

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

@ldk-reviews-bot

Copy link
Copy Markdown

🔔 1st Reminder

Hey @wpaulino! This PR has been waiting for your review.
Please take a look when you have a chance. If you're unable to review, please let us know so we can find another reviewer.

@ldk-reviews-bot

Copy link
Copy Markdown

🔔 2nd Reminder

Hey @wpaulino! This PR has been waiting for your review.
Please take a look when you have a chance. If you're unable to review, please let us know so we can find another reviewer.

log_trace!(logger, "Channel is open and awaiting update, resuming it");
handle_monitor_update_completion!(self, peer_state_lock, peer_state, per_peer_state, chan);
if chan.blocked_monitor_updates_pending() == 0 {
handle_monitor_update_completion!(self, peer_state_lock, peer_state, per_peer_state, chan);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Wouldn't we still want to call self.handle_monitor_update_completion_actions so that we can actually unblock any monitor updates that are pending blocked?

Copy link
Copy Markdown
CollaboratorAuthor

Choose a reason for hiding this comment

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

Hmm, we don't do so in handle_new_monitor_update's base case, which I was trying to match everywhere. A few tests fail if we change it there (with almost-harmless message ordering changes, so nothing major, but we'd have to fix that), but I'm not convinced its a bug - a channel should never block itself, and the unblocking of the next monitor update in a channel has to come in from a different channel or from an event, not from the channel itself. So that other channel, once it makes progress, should always unblock us.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Yeah that's what I figured, was just being overly-cautious in case we ever introduce such a monitor update that blocks the same channel.

Copy link
Copy Markdown
CollaboratorAuthor

Choose a reason for hiding this comment

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

Yea, I think you're right that that change makes sense, but I don't think it makes sense as a backport and we should do it everywhere, not just in the new places, in a separate PR.

Comment threadlightning/src/ln/channelmanager.rs Outdated
@wpaulino

Copy link
Copy Markdown
Contributor

LGTM after squash

@TheBlueMatt
TheBlueMattforce-pushed the 2025-06-no-mon-compl-with-blocked-updates branch from ac695db to b47d7dbCompareJuly 8, 2025 22:43
@TheBlueMatt

Copy link
Copy Markdown
CollaboratorAuthor

Squashed without further changes.

wpaulino
wpaulino previously approved these changes Jul 8, 2025
@TheBlueMattTheBlueMatt added the weekly goal Someone wants to land this this week label Jul 9, 2025
@ldk-reviews-bot

Copy link
Copy Markdown

✅ Added second reviewer: @joostjager

@joostjagerjoostjager left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

I'd ask someone with more background on the channel state machine as a 2nd reviewer.

Comment threadlightning/src/ln/chanmon_update_fail_tests.rs Outdated
Comment threadlightning/src/ln/channelmanager.rs Outdated
@TheBlueMattTheBlueMatt self-assigned this Jul 10, 2025

@valentinewallacevalentinewallace left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

LGTM logic-wise, nice catch. Had to re-read some code to refresh on the paths but it makes sense.

Comment threadlightning/src/ln/chanmon_update_fail_tests.rs Outdated
Comment threadlightning/src/ln/quiescence_tests.rs
In `handle_new_monitor_update!`, we correctly check that the
channel doesn't have any blocked monitor updates pending before
calling `handle_monitor_update_completion!` (which calls
`Channel::monitor_updating_restored`, which in turn assumes that
all generated `ChannelMonitorUpdate`s, including blocked ones, have
completed).
We, however, did not do the same check at several other places
where we called `handle_monitor_update_completion!`. Specifically,
after a monitor update completes during reload (processed via a
`BackgroundEvent` or when monitor update completes async, we didn't
check if there were any blocked monitor updates before completing).
Here we add the missing check, as well as an assertion in
`Channel::monitor_updating_restored`.
@TheBlueMatt
TheBlueMattforce-pushed the 2025-06-no-mon-compl-with-blocked-updates branch from b47d7db to 418d59dCompareJuly 10, 2025 23:05
@TheBlueMatt

Copy link
Copy Markdown
CollaboratorAuthor

Pushed some tirivial cleanups:

$ git diff-tree -U2 b47d7db4f1 418d59dff3
diff --git a/lightning/src/ln/chanmon_update_fail_tests.rs b/lightning/src/ln/chanmon_update_fail_tests.rs
index 2a3f3b52ba..c8d408425f 100644
--- a/lightning/src/ln/chanmon_update_fail_tests.rs+++ b/lightning/src/ln/chanmon_update_fail_tests.rs@@ -3411,5 +3411,12 @@ fn test_inbound_reload_without_init_mon() {
}
-fn do_test_blocked_chan_preimage_release(completion_mode: u8) {+#[derive(PartialEq, Eq)]+enum BlockedUpdateComplMode {+	Async,+	AtReload,+	Sync,+}++fn do_test_blocked_chan_preimage_release(completion_mode: BlockedUpdateComplMode) {
// Test that even if a channel's `ChannelMonitorUpdate` flow is blocked waiting on an event to
// be handled HTLC preimage `ChannelMonitorUpdate`s will still go out.
@@ -3459,5 +3466,5 @@ fn do_test_blocked_chan_preimage_release(completion_mode: u8) {
let as_htlc_fulfill_updates = get_htlc_update_msgs!(nodes[0], node_b_id);
-	if completion_mode != 0 {+	if completion_mode != BlockedUpdateComplMode::Sync {
// We use to incorrectly handle monitor update completion in cases where we completed a
// monitor update async or after reload. We test both based on the `completion_mode`.
@@ -3470,5 +3477,5 @@ fn do_test_blocked_chan_preimage_release(completion_mode: u8) {
assert!(get_monitor!(nodes[1], chan_id_2).get_stored_preimages().contains_key(&payment_hash_2));
assert!(nodes[1].node.get_and_clear_pending_msg_events().is_empty());
-	if completion_mode == 1 {+	if completion_mode == BlockedUpdateComplMode::AtReload {
let node_ser = nodes[1].node.encode();
let chan_mon_0 = get_monitor!(nodes[1], chan_id_1).encode();
@@ -3487,5 +3494,5 @@ fn do_test_blocked_chan_preimage_release(completion_mode: u8) {
reconnect_nodes(a_b_reconnect);
reconnect_nodes(ReconnectArgs::new(&nodes[2], &nodes[1]));
-	} else if completion_mode == 2 {+	} else if completion_mode == BlockedUpdateComplMode::Async {
let (latest_update, _) = get_latest_mon_update_id(&nodes[1], chan_id_2);
nodes[1]
@@ -3499,6 +3506,7 @@ fn do_test_blocked_chan_preimage_release(completion_mode: u8) {
// update_fulfill_htlc + CS is held, even though the preimage is already on disk for the
// channel.
-	// Note that in completion_mode 1 we completed the CS dance in `reconnect_nodes` above.-	if completion_mode != 1 {+	// Note that when completing as a side effect of a reload we completed the CS dance in+	// `reconnect_nodes` above.+	if completion_mode != BlockedUpdateComplMode::AtReload {
nodes[1].node.handle_commitment_signed_batch_test(
node_a_id,
@@ -3547,7 +3555,7 @@ fn do_test_blocked_chan_preimage_release(completion_mode: u8) {
#[test]
fn test_blocked_chan_preimage_release() {
-	do_test_blocked_chan_preimage_release(0);-	do_test_blocked_chan_preimage_release(1);-	do_test_blocked_chan_preimage_release(2);+	do_test_blocked_chan_preimage_release(BlockedUpdateComplMode::AtReload);+	do_test_blocked_chan_preimage_release(BlockedUpdateComplMode::Sync);+	do_test_blocked_chan_preimage_release(BlockedUpdateComplMode::Async);
}
diff --git a/lightning/src/ln/channelmanager.rs b/lightning/src/ln/channelmanager.rs
index b9a0c01c6a..195fbc286e 100644
--- a/lightning/src/ln/channelmanager.rs+++ b/lightning/src/ln/channelmanager.rs@@ -8229,13 +8229,9 @@ This indicates a bug inside LDK. Please report this error at https://github.com/
{
if chan.is_awaiting_monitor_update() {
- let should_resume = chan.blocked_monitor_updates_pending() == 0;- let action_msg = if should_resume {- "resuming it"- } else {- "leaving it blocked due to a blocked monitor update"- };- log_trace!(logger, "Channel is open and awaiting update, {action_msg}");- if should_resume {+ if chan.blocked_monitor_updates_pending() == 0 {+ log_trace!(logger, "Channel is open and awaiting update, resuming it");
handle_monitor_update_completion!(self, peer_state_lock, peer_state, per_peer_state, chan);
+ } else {+ log_trace!(logger, "Channel is open and awaiting update, leaving it blocked due to a blocked monitor update");
}
} else {
diff --git a/lightning/src/ln/quiescence_tests.rs b/lightning/src/ln/quiescence_tests.rs
index e6274f75f0..c2ab17e726 100644
--- a/lightning/src/ln/quiescence_tests.rs+++ b/lightning/src/ln/quiescence_tests.rs@@ -255,7 +255,7 @@ fn test_quiescence_waits_for_async_signer_and_monitor_update() {
// We have two updates pending:
{
- let chain_monitor = &nodes[0].chain_monitor;+ let test_chain_mon = &nodes[0].chain_monitor;
let (_, latest_update) =
- chain_monitor.latest_monitor_update_id.lock().unwrap().get(&chan_id).unwrap().clone();+ test_chain_mon.latest_monitor_update_id.lock().unwrap().get(&chan_id).unwrap().clone();
let chain_monitor = &nodes[0].chain_monitor.chain_monitor;
// One for the latest commitment transaction update from the last `revoke_and_ack`
@@ -263,9 +263,7 @@ fn test_quiescence_waits_for_async_signer_and_monitor_update() {
expect_payment_sent(&nodes[0], preimage, None, false, true);
- let chain_monitor = &nodes[0].chain_monitor;
let (_, new_latest_update) =
- chain_monitor.latest_monitor_update_id.lock().unwrap().get(&chan_id).unwrap().clone();+ test_chain_mon.latest_monitor_update_id.lock().unwrap().get(&chan_id).unwrap().clone();
assert_eq!(new_latest_update, latest_update + 1);
- let chain_monitor = &nodes[0].chain_monitor.chain_monitor;
// One for the commitment secret update from the last `revoke_and_ack`
chain_monitor.channel_monitor_updated(chan_id, new_latest_update).unwrap();

@TheBlueMatt

Copy link
Copy Markdown
CollaboratorAuthor

Only diff since @wpaulino's ACK are the trivial changes above, so landing.

@TheBlueMatt
TheBlueMatt merged commit f5dd77c into lightningdevkit:mainJul 11, 2025
@TheBlueMattTheBlueMatt mentioned this pull request Jul 15, 2025
@TheBlueMatt

Copy link
Copy Markdown
CollaboratorAuthor

Backported in #3932

TheBlueMatt added a commit to TheBlueMatt/rust-lightning that referenced this pull request Jul 24, 2025
v0.1.5 - Jul 16, 2025 - "Async Path Reduction"
Performance Improvements
========================
* `NetworkGraph`'s expensive internal consistency checks have now been
disabled in debug builds in addition to release builds (lightningdevkit#3687).
Bug Fixes
=========
* Pathfinding which results in a multi-path payment is now substantially
smarter, using fewer paths and better optimizing fees and successes (lightningdevkit#3890).
* A counterparty delaying claiming multiple HTLCs with different expiries can
no longer cause our `ChannelMonitor` to continuously rebroadcast invalid
transactions or RBF bump attempts (lightningdevkit#3923).
* Reorgs can no longer cause us to fail to claim HTLCs after a counterparty
delayed claiming multiple HTLCs with different expiries (lightningdevkit#3923).
* Force-closing a channel while it is blocked on another channel's async
`ChannelMonitorUpdate` can no longer lead to a panic (lightningdevkit#3858).
* `ChannelMonitorUpdate`s can no longer be released to storage too early when
doing async updates or on restart. This only impacts async
`ChannelMonitorUpdate` persistence and can lead to loss of funds only in rare
cases with `ChannelMonitorUpdate` persistence order inversions (lightningdevkit#3907).
Security
========
0.1.5 fixes a vulnerability which could allow a peer to overdraw their reserve
value, potentially cutting into commitment transaction fees on channels with a
low reserve.
* Due to a bug in checking whether an HTLC is dust during acceptance, near-dust
HTLCs were not counted towards the commitment transaction fee, but did
eventually contribute to it when we built a commitment transaction. This can
be used by a counterparty to overdraw their reserve value, or, for channels
with a low reserve value, cut into the commitment transaction fee (lightningdevkit#3933).
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

weekly goalSomeone wants to land this this week

Projects

Status: Done

Development

Successfully merging this pull request may close these issues.

5 participants

@TheBlueMatt@ldk-reviews-bot@wpaulino@joostjager@valentinewallace
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Remove or un-stick sticky/fixed headers that block content\n(function() {\n function unstick() {\n document.querySelectorAll('header, nav, [role=\"banner\"], .header, .navbar, .sticky, .fixed-top, [style*=\"position: fixed\"], [style*=\"position:sticky\"]').forEach(function(el) {\n if (el.style.position === 'fixed' || el.style.position === 'sticky' || \n getComputedStyle(el).position === 'fixed' || getComputedStyle(el).position === 'sticky') {\n el.style.position = 'static';\n el.style.top = 'auto';\n el.style.zIndex = 'auto';\n }\n });\n }\n \n unstick();\n \n var observer = new MutationObserver(unstick);\n observer.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ['style', 'class'] });\n})();", "Kill Sticky Headers"); } } catch(__e) { console.warn('[Userscript:Kill Sticky Headers]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

Only mark all mon updates complete if there are no blocked updates - #3907

Merged
TheBlueMatt merged 1 commit into
lightningdevkit:mainfrom
TheBlueMatt:2025-06-no-mon-compl-with-blocked-updates
Jul 11, 2025
Merged

Only mark all mon updates complete if there are no blocked updates#3907
TheBlueMatt merged 1 commit into
lightningdevkit:mainfrom
TheBlueMatt:2025-06-no-mon-compl-with-blocked-updates

Conversation

@TheBlueMatt

Copy link
Copy Markdown
Collaborator

In handle_new_monitor_update!, we correctly check that the channel doesn't have any blocked monitor updates pending before calling handle_monitor_update_completion! (which calls Channel::monitor_updating_restored, which in turn assumes that all generated ChannelMonitorUpdates, including blocked ones, have completed).

We, however, did not do the same check at several other places where we called handle_monitor_update_completion!. Specifically, after a monitor update completes during reload (processed via a BackgroundEvent or when monitor update completes async, we didn't check if there were any blocked monitor updates before completing).

Here we add the missing check, as well as an assertion in Channel::monitor_updating_restored.

@ldk-reviews-bot

ldk-reviews-bot commented Jul 3, 2025

Copy link
Copy Markdown

I've assigned @wpaulino as a reviewer!
I'll wait for their review and will help manage the review process.
Once they submit their review, I'll check if a second reviewer would be helpful.

@TheBlueMatt
TheBlueMattforce-pushed the 2025-06-no-mon-compl-with-blocked-updates branch 2 times, most recently from 6bfe0bd to 49ad15cCompareJuly 3, 2025 01:33
Comment threadlightning/src/ln/quiescence_tests.rs Outdated
@TheBlueMatt
TheBlueMattforce-pushed the 2025-06-no-mon-compl-with-blocked-updates branch 2 times, most recently from 7759b4d to 95c0696CompareJuly 3, 2025 01:36
Comment threadlightning/src/ln/quiescence_tests.rs
@TheBlueMatt
TheBlueMattforce-pushed the 2025-06-no-mon-compl-with-blocked-updates branch from 95c0696 to ab0dda7CompareJuly 3, 2025 01:36
@codecov

codecovBot commented Jul 3, 2025

Copy link
Copy Markdown

Codecov Report

Attention: Patch coverage is 97.05882% with 2 lines in your changes missing coverage. Please review.

Project coverage is 88.80%. Comparing base (257ebad) to head (ab0dda7).

Files with missing linesPatch %Lines
lightning/src/ln/chanmon_update_fail_tests.rs97.95%0 Missing and 1 partial ⚠️
lightning/src/ln/quiescence_tests.rs91.66%1 Missing ⚠️
Additional details and impacted files
@@ Coverage Diff @@## main #3907 +/- ##
==========================================
- Coverage 88.82% 88.80% -0.02% 
==========================================
Files 165 165 Lines 119075 119123 +48 Branches 119075 119123 +48 ==========================================
+ Hits 105769 105790 +21 - Misses 10986 11003 +17 - Partials 2320 2330 +10 

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

@ldk-reviews-bot

Copy link
Copy Markdown

🔔 1st Reminder

Hey @wpaulino! This PR has been waiting for your review.
Please take a look when you have a chance. If you're unable to review, please let us know so we can find another reviewer.

@ldk-reviews-bot

Copy link
Copy Markdown

🔔 2nd Reminder

Hey @wpaulino! This PR has been waiting for your review.
Please take a look when you have a chance. If you're unable to review, please let us know so we can find another reviewer.

log_trace!(logger, "Channel is open and awaiting update, resuming it");
handle_monitor_update_completion!(self, peer_state_lock, peer_state, per_peer_state, chan);
if chan.blocked_monitor_updates_pending() == 0 {
handle_monitor_update_completion!(self, peer_state_lock, peer_state, per_peer_state, chan);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Wouldn't we still want to call self.handle_monitor_update_completion_actions so that we can actually unblock any monitor updates that are pending blocked?

Copy link
Copy Markdown
CollaboratorAuthor

Choose a reason for hiding this comment

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

Hmm, we don't do so in handle_new_monitor_update's base case, which I was trying to match everywhere. A few tests fail if we change it there (with almost-harmless message ordering changes, so nothing major, but we'd have to fix that), but I'm not convinced its a bug - a channel should never block itself, and the unblocking of the next monitor update in a channel has to come in from a different channel or from an event, not from the channel itself. So that other channel, once it makes progress, should always unblock us.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Yeah that's what I figured, was just being overly-cautious in case we ever introduce such a monitor update that blocks the same channel.

Copy link
Copy Markdown
CollaboratorAuthor

Choose a reason for hiding this comment

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

Yea, I think you're right that that change makes sense, but I don't think it makes sense as a backport and we should do it everywhere, not just in the new places, in a separate PR.

Comment threadlightning/src/ln/channelmanager.rs Outdated
@wpaulino

Copy link
Copy Markdown
Contributor

LGTM after squash

@TheBlueMatt
TheBlueMattforce-pushed the 2025-06-no-mon-compl-with-blocked-updates branch from ac695db to b47d7dbCompareJuly 8, 2025 22:43
@TheBlueMatt

Copy link
Copy Markdown
CollaboratorAuthor

Squashed without further changes.

wpaulino
wpaulino previously approved these changes Jul 8, 2025
@TheBlueMattTheBlueMatt added the weekly goal Someone wants to land this this week label Jul 9, 2025
@ldk-reviews-bot

Copy link
Copy Markdown

✅ Added second reviewer: @joostjager

@joostjagerjoostjager left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

I'd ask someone with more background on the channel state machine as a 2nd reviewer.

Comment threadlightning/src/ln/chanmon_update_fail_tests.rs Outdated
Comment threadlightning/src/ln/channelmanager.rs Outdated
@TheBlueMattTheBlueMatt self-assigned this Jul 10, 2025

@valentinewallacevalentinewallace left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

LGTM logic-wise, nice catch. Had to re-read some code to refresh on the paths but it makes sense.

Comment threadlightning/src/ln/chanmon_update_fail_tests.rs Outdated
Comment threadlightning/src/ln/quiescence_tests.rs
In `handle_new_monitor_update!`, we correctly check that the
channel doesn't have any blocked monitor updates pending before
calling `handle_monitor_update_completion!` (which calls
`Channel::monitor_updating_restored`, which in turn assumes that
all generated `ChannelMonitorUpdate`s, including blocked ones, have
completed).
We, however, did not do the same check at several other places
where we called `handle_monitor_update_completion!`. Specifically,
after a monitor update completes during reload (processed via a
`BackgroundEvent` or when monitor update completes async, we didn't
check if there were any blocked monitor updates before completing).
Here we add the missing check, as well as an assertion in
`Channel::monitor_updating_restored`.
@TheBlueMatt
TheBlueMattforce-pushed the 2025-06-no-mon-compl-with-blocked-updates branch from b47d7db to 418d59dCompareJuly 10, 2025 23:05
@TheBlueMatt

Copy link
Copy Markdown
CollaboratorAuthor

Pushed some tirivial cleanups:

$ git diff-tree -U2 b47d7db4f1 418d59dff3
diff --git a/lightning/src/ln/chanmon_update_fail_tests.rs b/lightning/src/ln/chanmon_update_fail_tests.rs
index 2a3f3b52ba..c8d408425f 100644
--- a/lightning/src/ln/chanmon_update_fail_tests.rs+++ b/lightning/src/ln/chanmon_update_fail_tests.rs@@ -3411,5 +3411,12 @@ fn test_inbound_reload_without_init_mon() {
}
-fn do_test_blocked_chan_preimage_release(completion_mode: u8) {+#[derive(PartialEq, Eq)]+enum BlockedUpdateComplMode {+	Async,+	AtReload,+	Sync,+}++fn do_test_blocked_chan_preimage_release(completion_mode: BlockedUpdateComplMode) {
// Test that even if a channel's `ChannelMonitorUpdate` flow is blocked waiting on an event to
// be handled HTLC preimage `ChannelMonitorUpdate`s will still go out.
@@ -3459,5 +3466,5 @@ fn do_test_blocked_chan_preimage_release(completion_mode: u8) {
let as_htlc_fulfill_updates = get_htlc_update_msgs!(nodes[0], node_b_id);
-	if completion_mode != 0 {+	if completion_mode != BlockedUpdateComplMode::Sync {
// We use to incorrectly handle monitor update completion in cases where we completed a
// monitor update async or after reload. We test both based on the `completion_mode`.
@@ -3470,5 +3477,5 @@ fn do_test_blocked_chan_preimage_release(completion_mode: u8) {
assert!(get_monitor!(nodes[1], chan_id_2).get_stored_preimages().contains_key(&payment_hash_2));
assert!(nodes[1].node.get_and_clear_pending_msg_events().is_empty());
-	if completion_mode == 1 {+	if completion_mode == BlockedUpdateComplMode::AtReload {
let node_ser = nodes[1].node.encode();
let chan_mon_0 = get_monitor!(nodes[1], chan_id_1).encode();
@@ -3487,5 +3494,5 @@ fn do_test_blocked_chan_preimage_release(completion_mode: u8) {
reconnect_nodes(a_b_reconnect);
reconnect_nodes(ReconnectArgs::new(&nodes[2], &nodes[1]));
-	} else if completion_mode == 2 {+	} else if completion_mode == BlockedUpdateComplMode::Async {
let (latest_update, _) = get_latest_mon_update_id(&nodes[1], chan_id_2);
nodes[1]
@@ -3499,6 +3506,7 @@ fn do_test_blocked_chan_preimage_release(completion_mode: u8) {
// update_fulfill_htlc + CS is held, even though the preimage is already on disk for the
// channel.
-	// Note that in completion_mode 1 we completed the CS dance in `reconnect_nodes` above.-	if completion_mode != 1 {+	// Note that when completing as a side effect of a reload we completed the CS dance in+	// `reconnect_nodes` above.+	if completion_mode != BlockedUpdateComplMode::AtReload {
nodes[1].node.handle_commitment_signed_batch_test(
node_a_id,
@@ -3547,7 +3555,7 @@ fn do_test_blocked_chan_preimage_release(completion_mode: u8) {
#[test]
fn test_blocked_chan_preimage_release() {
-	do_test_blocked_chan_preimage_release(0);-	do_test_blocked_chan_preimage_release(1);-	do_test_blocked_chan_preimage_release(2);+	do_test_blocked_chan_preimage_release(BlockedUpdateComplMode::AtReload);+	do_test_blocked_chan_preimage_release(BlockedUpdateComplMode::Sync);+	do_test_blocked_chan_preimage_release(BlockedUpdateComplMode::Async);
}
diff --git a/lightning/src/ln/channelmanager.rs b/lightning/src/ln/channelmanager.rs
index b9a0c01c6a..195fbc286e 100644
--- a/lightning/src/ln/channelmanager.rs+++ b/lightning/src/ln/channelmanager.rs@@ -8229,13 +8229,9 @@ This indicates a bug inside LDK. Please report this error at https://github.com/
{
if chan.is_awaiting_monitor_update() {
- let should_resume = chan.blocked_monitor_updates_pending() == 0;- let action_msg = if should_resume {- "resuming it"- } else {- "leaving it blocked due to a blocked monitor update"- };- log_trace!(logger, "Channel is open and awaiting update, {action_msg}");- if should_resume {+ if chan.blocked_monitor_updates_pending() == 0 {+ log_trace!(logger, "Channel is open and awaiting update, resuming it");
handle_monitor_update_completion!(self, peer_state_lock, peer_state, per_peer_state, chan);
+ } else {+ log_trace!(logger, "Channel is open and awaiting update, leaving it blocked due to a blocked monitor update");
}
} else {
diff --git a/lightning/src/ln/quiescence_tests.rs b/lightning/src/ln/quiescence_tests.rs
index e6274f75f0..c2ab17e726 100644
--- a/lightning/src/ln/quiescence_tests.rs+++ b/lightning/src/ln/quiescence_tests.rs@@ -255,7 +255,7 @@ fn test_quiescence_waits_for_async_signer_and_monitor_update() {
// We have two updates pending:
{
- let chain_monitor = &nodes[0].chain_monitor;+ let test_chain_mon = &nodes[0].chain_monitor;
let (_, latest_update) =
- chain_monitor.latest_monitor_update_id.lock().unwrap().get(&chan_id).unwrap().clone();+ test_chain_mon.latest_monitor_update_id.lock().unwrap().get(&chan_id).unwrap().clone();
let chain_monitor = &nodes[0].chain_monitor.chain_monitor;
// One for the latest commitment transaction update from the last `revoke_and_ack`
@@ -263,9 +263,7 @@ fn test_quiescence_waits_for_async_signer_and_monitor_update() {
expect_payment_sent(&nodes[0], preimage, None, false, true);
- let chain_monitor = &nodes[0].chain_monitor;
let (_, new_latest_update) =
- chain_monitor.latest_monitor_update_id.lock().unwrap().get(&chan_id).unwrap().clone();+ test_chain_mon.latest_monitor_update_id.lock().unwrap().get(&chan_id).unwrap().clone();
assert_eq!(new_latest_update, latest_update + 1);
- let chain_monitor = &nodes[0].chain_monitor.chain_monitor;
// One for the commitment secret update from the last `revoke_and_ack`
chain_monitor.channel_monitor_updated(chan_id, new_latest_update).unwrap();

@TheBlueMatt

Copy link
Copy Markdown
CollaboratorAuthor

Only diff since @wpaulino's ACK are the trivial changes above, so landing.

@TheBlueMatt
TheBlueMatt merged commit f5dd77c into lightningdevkit:mainJul 11, 2025
@TheBlueMattTheBlueMatt mentioned this pull request Jul 15, 2025
@TheBlueMatt

Copy link
Copy Markdown
CollaboratorAuthor

Backported in #3932

TheBlueMatt added a commit to TheBlueMatt/rust-lightning that referenced this pull request Jul 24, 2025
v0.1.5 - Jul 16, 2025 - "Async Path Reduction"
Performance Improvements
========================
* `NetworkGraph`'s expensive internal consistency checks have now been
disabled in debug builds in addition to release builds (lightningdevkit#3687).
Bug Fixes
=========
* Pathfinding which results in a multi-path payment is now substantially
smarter, using fewer paths and better optimizing fees and successes (lightningdevkit#3890).
* A counterparty delaying claiming multiple HTLCs with different expiries can
no longer cause our `ChannelMonitor` to continuously rebroadcast invalid
transactions or RBF bump attempts (lightningdevkit#3923).
* Reorgs can no longer cause us to fail to claim HTLCs after a counterparty
delayed claiming multiple HTLCs with different expiries (lightningdevkit#3923).
* Force-closing a channel while it is blocked on another channel's async
`ChannelMonitorUpdate` can no longer lead to a panic (lightningdevkit#3858).
* `ChannelMonitorUpdate`s can no longer be released to storage too early when
doing async updates or on restart. This only impacts async
`ChannelMonitorUpdate` persistence and can lead to loss of funds only in rare
cases with `ChannelMonitorUpdate` persistence order inversions (lightningdevkit#3907).
Security
========
0.1.5 fixes a vulnerability which could allow a peer to overdraw their reserve
value, potentially cutting into commitment transaction fees on channels with a
low reserve.
* Due to a bug in checking whether an HTLC is dust during acceptance, near-dust
HTLCs were not counted towards the commitment transaction fee, but did
eventually contribute to it when we built a commitment transaction. This can
be used by a counterparty to overdraw their reserve value, or, for channels
with a low reserve value, cut into the commitment transaction fee (lightningdevkit#3933).
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

weekly goalSomeone wants to land this this week

Projects

Status: Done

Development

Successfully merging this pull request may close these issues.

5 participants

@TheBlueMatt@ldk-reviews-bot@wpaulino@joostjager@valentinewallace
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Universal Dark Mode - works on any site\n(function() {\n var enabled = true;\n \n function applyDarkMode() {\n if (!enabled) return;\n \n // Create style element if it doesn't exist\n var style = document.getElementById('universal-dark-mode-style');\n if (!style) {\n style = document.createElement('style');\n style.id = 'universal-dark-mode-style';\n document.head.appendChild(style);\n }\n \n // Dark mode CSS - inverts colors but preserves images/video\n style.textContent = '\n /* Invert everything except media */\n html {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #1a1a2e !important;\n }\n \n /* Restore images, videos, iframes, canvas */\n img, video, iframe, canvas, svg, picture, [style*=\"background-image\"] {\n filter: invert(1) hue-rotate(180deg) !important;\n }\n \n /* Preserve specific elements that should not be inverted */\n .no-dark-mode, .no-dark-mode *,\n [data-theme=\"light\"], [data-theme=\"light\"],\n .ace_editor, .ace_editor *,\n .CodeMirror, .CodeMirror *,\n .monaco-editor, .monaco-editor *,\n .markdown-body pre, .markdown-body pre *,\n .highlight, .highlight *,\n pre code, pre code * {\n filter: none !important;\n }\n \n /* Fix common UI elements */\n .modal, .popup, .dropdown-menu, .tooltip, .popover {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #2d2d44 !important;\n border-color: #444 !important;\n }\n \n /* Scrollbars */\n ::-webkit-scrollbar { background: #1a1a2e !important; }\n ::-webkit-scrollbar-thumb { background: #444 !important; }\n ::-webkit-scrollbar-thumb:hover { background: #555 !important; }\n \n /* Selection */\n ::selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ::-moz-selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ';\n }\n \n function removeDarkMode() {\n var style = document.getElementById('universal-dark-mode-style');\n if (style) style.remove();\n }\n \n // Toggle with Alt+Shift+D\n document.addEventListener('keydown', function(e) {\n if (e.altKey && e.shiftKey && e.key === 'D') {\n e.preventDefault();\n enabled = !enabled;\n if (enabled) {\n applyDarkMode();\n console.log('[Universal Dark Mode] Enabled');\n } else {\n removeDarkMode();\n console.log('[Universal Dark Mode] Disabled');\n }\n }\n });\n \n // Apply on load\n applyDarkMode();\n \n // Re-apply on dynamic content\n var observer = new MutationObserver(function(mutations) {\n if (enabled && !document.getElementById('universal-dark-mode-style')) {\n applyDarkMode();\n }\n });\n observer.observe(document.head, { childList: true });\n \n console.log('[Universal Dark Mode] Loaded - Press Alt+Shift+D to toggle');\n})();", "Universal Dark Mode"); } } catch(__e) { console.warn('[Userscript:Universal Dark Mode]', __e); } })(); })();
Skip to content

Only mark all mon updates complete if there are no blocked updates - #3907

Merged
TheBlueMatt merged 1 commit into
lightningdevkit:mainfrom
TheBlueMatt:2025-06-no-mon-compl-with-blocked-updates
Jul 11, 2025
Merged

Only mark all mon updates complete if there are no blocked updates#3907
TheBlueMatt merged 1 commit into
lightningdevkit:mainfrom
TheBlueMatt:2025-06-no-mon-compl-with-blocked-updates

Conversation

@TheBlueMatt

Copy link
Copy Markdown
Collaborator

In handle_new_monitor_update!, we correctly check that the channel doesn't have any blocked monitor updates pending before calling handle_monitor_update_completion! (which calls Channel::monitor_updating_restored, which in turn assumes that all generated ChannelMonitorUpdates, including blocked ones, have completed).

We, however, did not do the same check at several other places where we called handle_monitor_update_completion!. Specifically, after a monitor update completes during reload (processed via a BackgroundEvent or when monitor update completes async, we didn't check if there were any blocked monitor updates before completing).

Here we add the missing check, as well as an assertion in Channel::monitor_updating_restored.

@ldk-reviews-bot

ldk-reviews-bot commented Jul 3, 2025

Copy link
Copy Markdown

I've assigned @wpaulino as a reviewer!
I'll wait for their review and will help manage the review process.
Once they submit their review, I'll check if a second reviewer would be helpful.

@TheBlueMatt
TheBlueMattforce-pushed the 2025-06-no-mon-compl-with-blocked-updates branch 2 times, most recently from 6bfe0bd to 49ad15cCompareJuly 3, 2025 01:33
Comment threadlightning/src/ln/quiescence_tests.rs Outdated
@TheBlueMatt
TheBlueMattforce-pushed the 2025-06-no-mon-compl-with-blocked-updates branch 2 times, most recently from 7759b4d to 95c0696CompareJuly 3, 2025 01:36
Comment threadlightning/src/ln/quiescence_tests.rs
@TheBlueMatt
TheBlueMattforce-pushed the 2025-06-no-mon-compl-with-blocked-updates branch from 95c0696 to ab0dda7CompareJuly 3, 2025 01:36
@codecov

codecovBot commented Jul 3, 2025

Copy link
Copy Markdown

Codecov Report

Attention: Patch coverage is 97.05882% with 2 lines in your changes missing coverage. Please review.

Project coverage is 88.80%. Comparing base (257ebad) to head (ab0dda7).

Files with missing linesPatch %Lines
lightning/src/ln/chanmon_update_fail_tests.rs97.95%0 Missing and 1 partial ⚠️
lightning/src/ln/quiescence_tests.rs91.66%1 Missing ⚠️
Additional details and impacted files
@@ Coverage Diff @@## main #3907 +/- ##
==========================================
- Coverage 88.82% 88.80% -0.02% 
==========================================
Files 165 165 Lines 119075 119123 +48 Branches 119075 119123 +48 ==========================================
+ Hits 105769 105790 +21 - Misses 10986 11003 +17 - Partials 2320 2330 +10 

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

@ldk-reviews-bot

Copy link
Copy Markdown

🔔 1st Reminder

Hey @wpaulino! This PR has been waiting for your review.
Please take a look when you have a chance. If you're unable to review, please let us know so we can find another reviewer.

@ldk-reviews-bot

Copy link
Copy Markdown

🔔 2nd Reminder

Hey @wpaulino! This PR has been waiting for your review.
Please take a look when you have a chance. If you're unable to review, please let us know so we can find another reviewer.

log_trace!(logger, "Channel is open and awaiting update, resuming it");
handle_monitor_update_completion!(self, peer_state_lock, peer_state, per_peer_state, chan);
if chan.blocked_monitor_updates_pending() == 0 {
handle_monitor_update_completion!(self, peer_state_lock, peer_state, per_peer_state, chan);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Wouldn't we still want to call self.handle_monitor_update_completion_actions so that we can actually unblock any monitor updates that are pending blocked?

Copy link
Copy Markdown
CollaboratorAuthor

Choose a reason for hiding this comment

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

Hmm, we don't do so in handle_new_monitor_update's base case, which I was trying to match everywhere. A few tests fail if we change it there (with almost-harmless message ordering changes, so nothing major, but we'd have to fix that), but I'm not convinced its a bug - a channel should never block itself, and the unblocking of the next monitor update in a channel has to come in from a different channel or from an event, not from the channel itself. So that other channel, once it makes progress, should always unblock us.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Yeah that's what I figured, was just being overly-cautious in case we ever introduce such a monitor update that blocks the same channel.

Copy link
Copy Markdown
CollaboratorAuthor

Choose a reason for hiding this comment

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

Yea, I think you're right that that change makes sense, but I don't think it makes sense as a backport and we should do it everywhere, not just in the new places, in a separate PR.

Comment threadlightning/src/ln/channelmanager.rs Outdated
@wpaulino

Copy link
Copy Markdown
Contributor

LGTM after squash

@TheBlueMatt
TheBlueMattforce-pushed the 2025-06-no-mon-compl-with-blocked-updates branch from ac695db to b47d7dbCompareJuly 8, 2025 22:43
@TheBlueMatt

Copy link
Copy Markdown
CollaboratorAuthor

Squashed without further changes.

wpaulino
wpaulino previously approved these changes Jul 8, 2025
@TheBlueMattTheBlueMatt added the weekly goal Someone wants to land this this week label Jul 9, 2025
@ldk-reviews-bot

Copy link
Copy Markdown

✅ Added second reviewer: @joostjager

@joostjagerjoostjager left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

I'd ask someone with more background on the channel state machine as a 2nd reviewer.

Comment threadlightning/src/ln/chanmon_update_fail_tests.rs Outdated
Comment threadlightning/src/ln/channelmanager.rs Outdated
@TheBlueMattTheBlueMatt self-assigned this Jul 10, 2025

@valentinewallacevalentinewallace left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

LGTM logic-wise, nice catch. Had to re-read some code to refresh on the paths but it makes sense.

Comment threadlightning/src/ln/chanmon_update_fail_tests.rs Outdated
Comment threadlightning/src/ln/quiescence_tests.rs
In `handle_new_monitor_update!`, we correctly check that the
channel doesn't have any blocked monitor updates pending before
calling `handle_monitor_update_completion!` (which calls
`Channel::monitor_updating_restored`, which in turn assumes that
all generated `ChannelMonitorUpdate`s, including blocked ones, have
completed).
We, however, did not do the same check at several other places
where we called `handle_monitor_update_completion!`. Specifically,
after a monitor update completes during reload (processed via a
`BackgroundEvent` or when monitor update completes async, we didn't
check if there were any blocked monitor updates before completing).
Here we add the missing check, as well as an assertion in
`Channel::monitor_updating_restored`.
@TheBlueMatt
TheBlueMattforce-pushed the 2025-06-no-mon-compl-with-blocked-updates branch from b47d7db to 418d59dCompareJuly 10, 2025 23:05
@TheBlueMatt

Copy link
Copy Markdown
CollaboratorAuthor

Pushed some tirivial cleanups:

$ git diff-tree -U2 b47d7db4f1 418d59dff3
diff --git a/lightning/src/ln/chanmon_update_fail_tests.rs b/lightning/src/ln/chanmon_update_fail_tests.rs
index 2a3f3b52ba..c8d408425f 100644
--- a/lightning/src/ln/chanmon_update_fail_tests.rs+++ b/lightning/src/ln/chanmon_update_fail_tests.rs@@ -3411,5 +3411,12 @@ fn test_inbound_reload_without_init_mon() {
}
-fn do_test_blocked_chan_preimage_release(completion_mode: u8) {+#[derive(PartialEq, Eq)]+enum BlockedUpdateComplMode {+	Async,+	AtReload,+	Sync,+}++fn do_test_blocked_chan_preimage_release(completion_mode: BlockedUpdateComplMode) {
// Test that even if a channel's `ChannelMonitorUpdate` flow is blocked waiting on an event to
// be handled HTLC preimage `ChannelMonitorUpdate`s will still go out.
@@ -3459,5 +3466,5 @@ fn do_test_blocked_chan_preimage_release(completion_mode: u8) {
let as_htlc_fulfill_updates = get_htlc_update_msgs!(nodes[0], node_b_id);
-	if completion_mode != 0 {+	if completion_mode != BlockedUpdateComplMode::Sync {
// We use to incorrectly handle monitor update completion in cases where we completed a
// monitor update async or after reload. We test both based on the `completion_mode`.
@@ -3470,5 +3477,5 @@ fn do_test_blocked_chan_preimage_release(completion_mode: u8) {
assert!(get_monitor!(nodes[1], chan_id_2).get_stored_preimages().contains_key(&payment_hash_2));
assert!(nodes[1].node.get_and_clear_pending_msg_events().is_empty());
-	if completion_mode == 1 {+	if completion_mode == BlockedUpdateComplMode::AtReload {
let node_ser = nodes[1].node.encode();
let chan_mon_0 = get_monitor!(nodes[1], chan_id_1).encode();
@@ -3487,5 +3494,5 @@ fn do_test_blocked_chan_preimage_release(completion_mode: u8) {
reconnect_nodes(a_b_reconnect);
reconnect_nodes(ReconnectArgs::new(&nodes[2], &nodes[1]));
-	} else if completion_mode == 2 {+	} else if completion_mode == BlockedUpdateComplMode::Async {
let (latest_update, _) = get_latest_mon_update_id(&nodes[1], chan_id_2);
nodes[1]
@@ -3499,6 +3506,7 @@ fn do_test_blocked_chan_preimage_release(completion_mode: u8) {
// update_fulfill_htlc + CS is held, even though the preimage is already on disk for the
// channel.
-	// Note that in completion_mode 1 we completed the CS dance in `reconnect_nodes` above.-	if completion_mode != 1 {+	// Note that when completing as a side effect of a reload we completed the CS dance in+	// `reconnect_nodes` above.+	if completion_mode != BlockedUpdateComplMode::AtReload {
nodes[1].node.handle_commitment_signed_batch_test(
node_a_id,
@@ -3547,7 +3555,7 @@ fn do_test_blocked_chan_preimage_release(completion_mode: u8) {
#[test]
fn test_blocked_chan_preimage_release() {
-	do_test_blocked_chan_preimage_release(0);-	do_test_blocked_chan_preimage_release(1);-	do_test_blocked_chan_preimage_release(2);+	do_test_blocked_chan_preimage_release(BlockedUpdateComplMode::AtReload);+	do_test_blocked_chan_preimage_release(BlockedUpdateComplMode::Sync);+	do_test_blocked_chan_preimage_release(BlockedUpdateComplMode::Async);
}
diff --git a/lightning/src/ln/channelmanager.rs b/lightning/src/ln/channelmanager.rs
index b9a0c01c6a..195fbc286e 100644
--- a/lightning/src/ln/channelmanager.rs+++ b/lightning/src/ln/channelmanager.rs@@ -8229,13 +8229,9 @@ This indicates a bug inside LDK. Please report this error at https://github.com/
{
if chan.is_awaiting_monitor_update() {
- let should_resume = chan.blocked_monitor_updates_pending() == 0;- let action_msg = if should_resume {- "resuming it"- } else {- "leaving it blocked due to a blocked monitor update"- };- log_trace!(logger, "Channel is open and awaiting update, {action_msg}");- if should_resume {+ if chan.blocked_monitor_updates_pending() == 0 {+ log_trace!(logger, "Channel is open and awaiting update, resuming it");
handle_monitor_update_completion!(self, peer_state_lock, peer_state, per_peer_state, chan);
+ } else {+ log_trace!(logger, "Channel is open and awaiting update, leaving it blocked due to a blocked monitor update");
}
} else {
diff --git a/lightning/src/ln/quiescence_tests.rs b/lightning/src/ln/quiescence_tests.rs
index e6274f75f0..c2ab17e726 100644
--- a/lightning/src/ln/quiescence_tests.rs+++ b/lightning/src/ln/quiescence_tests.rs@@ -255,7 +255,7 @@ fn test_quiescence_waits_for_async_signer_and_monitor_update() {
// We have two updates pending:
{
- let chain_monitor = &nodes[0].chain_monitor;+ let test_chain_mon = &nodes[0].chain_monitor;
let (_, latest_update) =
- chain_monitor.latest_monitor_update_id.lock().unwrap().get(&chan_id).unwrap().clone();+ test_chain_mon.latest_monitor_update_id.lock().unwrap().get(&chan_id).unwrap().clone();
let chain_monitor = &nodes[0].chain_monitor.chain_monitor;
// One for the latest commitment transaction update from the last `revoke_and_ack`
@@ -263,9 +263,7 @@ fn test_quiescence_waits_for_async_signer_and_monitor_update() {
expect_payment_sent(&nodes[0], preimage, None, false, true);
- let chain_monitor = &nodes[0].chain_monitor;
let (_, new_latest_update) =
- chain_monitor.latest_monitor_update_id.lock().unwrap().get(&chan_id).unwrap().clone();+ test_chain_mon.latest_monitor_update_id.lock().unwrap().get(&chan_id).unwrap().clone();
assert_eq!(new_latest_update, latest_update + 1);
- let chain_monitor = &nodes[0].chain_monitor.chain_monitor;
// One for the commitment secret update from the last `revoke_and_ack`
chain_monitor.channel_monitor_updated(chan_id, new_latest_update).unwrap();

@TheBlueMatt

Copy link
Copy Markdown
CollaboratorAuthor

Only diff since @wpaulino's ACK are the trivial changes above, so landing.

@TheBlueMatt
TheBlueMatt merged commit f5dd77c into lightningdevkit:mainJul 11, 2025
@TheBlueMattTheBlueMatt mentioned this pull request Jul 15, 2025
@TheBlueMatt

Copy link
Copy Markdown
CollaboratorAuthor

Backported in #3932

TheBlueMatt added a commit to TheBlueMatt/rust-lightning that referenced this pull request Jul 24, 2025
v0.1.5 - Jul 16, 2025 - "Async Path Reduction"
Performance Improvements
========================
* `NetworkGraph`'s expensive internal consistency checks have now been
disabled in debug builds in addition to release builds (lightningdevkit#3687).
Bug Fixes
=========
* Pathfinding which results in a multi-path payment is now substantially
smarter, using fewer paths and better optimizing fees and successes (lightningdevkit#3890).
* A counterparty delaying claiming multiple HTLCs with different expiries can
no longer cause our `ChannelMonitor` to continuously rebroadcast invalid
transactions or RBF bump attempts (lightningdevkit#3923).
* Reorgs can no longer cause us to fail to claim HTLCs after a counterparty
delayed claiming multiple HTLCs with different expiries (lightningdevkit#3923).
* Force-closing a channel while it is blocked on another channel's async
`ChannelMonitorUpdate` can no longer lead to a panic (lightningdevkit#3858).
* `ChannelMonitorUpdate`s can no longer be released to storage too early when
doing async updates or on restart. This only impacts async
`ChannelMonitorUpdate` persistence and can lead to loss of funds only in rare
cases with `ChannelMonitorUpdate` persistence order inversions (lightningdevkit#3907).
Security
========
0.1.5 fixes a vulnerability which could allow a peer to overdraw their reserve
value, potentially cutting into commitment transaction fees on channels with a
low reserve.
* Due to a bug in checking whether an HTLC is dust during acceptance, near-dust
HTLCs were not counted towards the commitment transaction fee, but did
eventually contribute to it when we built a commitment transaction. This can
be used by a counterparty to overdraw their reserve value, or, for channels
with a low reserve value, cut into the commitment transaction fee (lightningdevkit#3933).
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

weekly goalSomeone wants to land this this week

Projects

Status: Done

Development

Successfully merging this pull request may close these issues.

5 participants

@TheBlueMatt@ldk-reviews-bot@wpaulino@joostjager@valentinewallace