Read ChannelManager even if we have no-peer post-update actions - #3790

Merged
TheBlueMatt merged 1 commit into
lightningdevkit:mainfrom
TheBlueMatt:2025-05-0.1-upgrade-fail
May 23, 2025
Merged

Read ChannelManager even if we have no-peer post-update actions#3790
TheBlueMatt merged 1 commit into
lightningdevkit:mainfrom
TheBlueMatt:2025-05-0.1-upgrade-fail

Conversation

@TheBlueMatt

Copy link
Copy Markdown
Collaborator

In 93b4479 we fixed an issue which could cause a ChannelMonitorUpdate to get marked as blocked on itself, leading to an eventual force-closure.

One potential side-effect of that issue, however, is that any further ChannelMonitorUpdates to the same channel while it is blocked will not have any post-update actions processed (as there is a pending blocked ChannelMonitorUpdate sitting in the channel).

This can leave a dangling MonitorUpdateCompletionAction sitting around even after the channel is closed.

In 0.1, because ChannelMonitorUpdates to closed channels were finally fully tracked, we started enforcing that any post-update completion action we had on startup corresponded to a peer entry, while at the same time no longer creating peer entries just because we had serialized one in the data we were loading (only creating them if we had channel(s) or a ChannelMonitor).

This can cause some ChannelManager to no longer deserialize on 0.1 as we might have a left-over dangling
MonitorUpdateCompletionAction and will no longer always have a peer entry just because of it.

Here we fix this issue by specifically checking for dangling MonitorUpdateCompletionAction::PaymentClaim entries and dropping them if there is no corresponding channel or peer state entry. We only check for PaymentClaimed actions rather than allowing for any dangling actions as 93b4479 was only triggerable with MPP claims, so dangling
MonitorUpdateCompletionActions for forwarded payments should be exceedingly rare.

This also adds an upgrade test to test a slightly convoluted version of this scenario.

@ldk-reviews-bot

ldk-reviews-bot commented May 22, 2025

Copy link
Copy Markdown

I've assigned @joostjager 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.

Comment threadlightning/src/ln/channelmanager.rs Outdated
Comment threadlightning/src/ln/channelmanager.rs Outdated
let logger =
WithContext::from(&args.logger, Some(node_id), None, None);
log_error!(logger, "Got blocked actions {:?} without a per-peer-state for {}", actions, node_id);
return Err(DecodeError::InvalidValue);

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.

This is all new code that adds a new error condition.

Here we fix this issue by specifically checking for dangling
MonitorUpdateCompletionAction::PaymentClaim entries and dropping
them if there is no corresponding channel or peer state entry.

How are they dropped now, compared to how it happens in main?

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.

On main in the no-peer-state case we always return an error, now we sometimes do not and ignore the completion actions. For the have-peer-state case, we used to store the completion actions even if we have no channel or monitor the completion actions are based on, but now we do not.

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 see

@ldk-reviews-bot

Copy link
Copy Markdown

👋 The first review has been submitted!

Do you think this PR is ready for a second reviewer? If so, click here to assign a second reviewer.

log_error!(WithContext::from(&args.logger, Some(node_id), None, None), "Got blocked actions without a per-peer-state for {}", node_id);
return Err(DecodeError::InvalidValue);
for actions in monitor_update_blocked_actions.values() {
for action in actions.iter() {

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.

Repeated code block

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 wasn't quite sure it was worth DRYing it up any given its so short (and the comments are slightly different)

@TheBlueMatt
TheBlueMattforce-pushed the 2025-05-0.1-upgrade-fail branch from 7105605 to bb2e29eCompareMay 22, 2025 14:35
@TheBlueMatt

Copy link
Copy Markdown
CollaboratorAuthor

Oops, the first set of checks were too eager, luckily test_reload_mon_update_completion_actions caught it -

$ git diff-tree -U1 710560554 bb2e29e97
diff --git a/lightning/src/ln/channelmanager.rs b/lightning/src/ln/channelmanager.rs
index 84251ca33b..54e453d56b 100644
--- a/lightning/src/ln/channelmanager.rs+++ b/lightning/src/ln/channelmanager.rs@@ -14179,6 +14179,14 @@ where
let mut max_in_flight_update_id = 0;
+ let starting_len = $chan_in_flight_upds.len();
$chan_in_flight_upds.retain(|upd| upd.update_id > $monitor.get_latest_update_id());
+ if $chan_in_flight_upds.len() < starting_len {+ log_debug!(+ $logger,+ "{} ChannelMonitorUpdates completed after ChannelManager was last serialized",+ starting_len - $chan_in_flight_upds.len()+ );+ }
let funding_txo = $monitor.get_funding_txo();
for update in $chan_in_flight_upds.iter() {
- log_trace!($logger, "Replaying ChannelMonitorUpdate {} for {}channel {}",+ log_debug!($logger, "Replaying ChannelMonitorUpdate {} for {}channel {}",
update.update_id, $channel_info_log, &$monitor.channel_id());
@@ -14746,24 +14754,7 @@ where
}
- let peer_state = peer_state.lock().unwrap();- let channel_id_matches = |updates: &(_, Vec<ChannelMonitorUpdate>)| {- updates.1.iter().any(|update| update.channel_id == Some(*channel_id))- };- if !peer_state.in_flight_monitor_updates.values().any(channel_id_matches) {- // If there are no pending `ChannelMonitorUpdate`s for this channel but we- // have pending post-update actions, its possible that one was left over- // from pre-0.1 payment claims where MPP claims led to a channel blocked on- // itself and later `ChannelMonitorUpdate`s didn't get their post-update- // actions run.- // This should only have happened for `PaymentClaimed` post-update actions,- // which we check here.- for action in actions.iter() {- if let MonitorUpdateCompletionAction::PaymentClaimed { .. } = action {- } else {- let logger =- WithContext::from(&args.logger, Some(node_id), None, None);- log_error!(logger, "Got blocked actions {:?} without a per-peer-state for {}", actions, node_id);- return Err(DecodeError::InvalidValue);- }- }- }+ // Note that we may have a post-update action for a channel that has no pending+ // `ChannelMonitorUpdate`s, but unlike the no-peer-state case, it may simply be+ // because we had a `ChannelMonitorUpdate` complete after the last time this+ // `ChannelManager` was serialized. In that case, we'll run the post-update+ // actions as soon as we get going.
}

@codecov

codecovBot commented May 22, 2025

Copy link
Copy Markdown

Codecov Report

Attention: Patch coverage is 0% with 14 lines in your changes missing coverage. Please review.

Project coverage is 89.78%. Comparing base (02b5564) to head (bb2e29e).

Files with missing linesPatch %Lines
lightning/src/ln/channelmanager.rs0.00%14 Missing ⚠️
Additional details and impacted files
@@ Coverage Diff @@## main #3790 +/- ##
==========================================
- Coverage 89.80% 89.78% -0.03% 
==========================================
Files 159 159 Lines 128691 128703 +12 Branches 128691 128703 +12 ==========================================
- Hits 115573 115552 -21 - Misses 10446 10472 +26 - Partials 2672 2679 +7 

☔ 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.

Comment threadlightning/src/ln/channelmanager.rs Outdated
@ldk-reviews-bot

Copy link
Copy Markdown

✅ Added second reviewer: @valentinewallace

@TheBlueMatt
TheBlueMattforce-pushed the 2025-05-0.1-upgrade-fail branch from bb2e29e to 5ca1019CompareMay 22, 2025 16:04
@TheBlueMatt

TheBlueMatt commented May 22, 2025

Copy link
Copy Markdown
CollaboratorAuthor

rustfmt'd (and used match instead of if let)

In 93b4479 we fixed an issue which
could cause a `ChannelMonitorUpdate` to get marked as blocked on
itself, leading to an eventual force-closure.
One potential side-effect of that issue, however, is that any
further `ChannelMonitorUpdate`s to the same channel while it is
blocked will not have any post-update actions processed (as there
is a pending blocked `ChannelMonitorUpdate` sitting in the
channel).
This can leave a dangling `MonitorUpdateCompletionAction` sitting
around even after the channel is closed.
In 0.1, because `ChannelMonitorUpdate`s to closed channels were
finally fully tracked, we started enforcing that any post-update
completion action we had on startup corresponded to a peer entry,
while at the same time no longer creating peer entries just because
we had serialized one in the data we were loading (only creating
them if we had channel(s) or a `ChannelMonitor`).
This can cause some `ChannelManager` to no longer deserialize on
0.1 as we might have a left-over dangling
`MonitorUpdateCompletionAction` and will no longer always have a
peer entry just because of it.
Here we fix this issue by specifically checking for dangling
`MonitorUpdateCompletionAction::PaymentClaim` entries and dropping
them if there is no corresponding channel or peer state entry. We
only check for `PaymentClaimed` actions rather than allowing for
any dangling actions as 93b4479
was only triggerable with MPP claims, so dangling
`MonitorUpdateCompletionAction`s for forwarded payments should be
exceedingly rare.
This also adds an upgrade test to test a slightly convoluted
version of this scenario.
@TheBlueMatt
TheBlueMattforce-pushed the 2025-05-0.1-upgrade-fail branch from 5ca1019 to 70b5552CompareMay 22, 2025 16:05
return Err(DecodeError::InvalidValue);
for actions in monitor_update_blocked_actions.values() {
for action in actions.iter() {
if matches!(action, MonitorUpdateCompletionAction::PaymentClaimed { .. }) {

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.

Hm I meant !matches! to avoid the else.

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 liked the comment in the block that's ignoring the PaymentClaimeds. I mean I can move it outside if you prefer but I often like comments in empty if blocks 🤷‍♂️

@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.

Code LGTM


// Finally, reload the node in the latest LDK. This previously failed.
let config = test_default_channel_config();
reload_node!(nodes[3], config, &node_d_ser, &[&mon_ser], persister, chain_mon, node);

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.

Test LGTM, just took a slightly closer look. It also fails as expected on main.

@TheBlueMatt
TheBlueMatt merged commit 63a5e03 into lightningdevkit:mainMay 23, 2025
@TheBlueMattTheBlueMatt mentioned this pull request May 23, 2025
@TheBlueMatt

Copy link
Copy Markdown
CollaboratorAuthor

Backported in #3794

Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants

@TheBlueMatt@ldk-reviews-bot@joostjager@wpaulino@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

Read ChannelManager even if we have no-peer post-update actions - #3790

Merged
TheBlueMatt merged 1 commit into
lightningdevkit:mainfrom
TheBlueMatt:2025-05-0.1-upgrade-fail
May 23, 2025
Merged

Read ChannelManager even if we have no-peer post-update actions#3790
TheBlueMatt merged 1 commit into
lightningdevkit:mainfrom
TheBlueMatt:2025-05-0.1-upgrade-fail

Conversation

@TheBlueMatt

Copy link
Copy Markdown
Collaborator

In 93b4479 we fixed an issue which could cause a ChannelMonitorUpdate to get marked as blocked on itself, leading to an eventual force-closure.

One potential side-effect of that issue, however, is that any further ChannelMonitorUpdates to the same channel while it is blocked will not have any post-update actions processed (as there is a pending blocked ChannelMonitorUpdate sitting in the channel).

This can leave a dangling MonitorUpdateCompletionAction sitting around even after the channel is closed.

In 0.1, because ChannelMonitorUpdates to closed channels were finally fully tracked, we started enforcing that any post-update completion action we had on startup corresponded to a peer entry, while at the same time no longer creating peer entries just because we had serialized one in the data we were loading (only creating them if we had channel(s) or a ChannelMonitor).

This can cause some ChannelManager to no longer deserialize on 0.1 as we might have a left-over dangling
MonitorUpdateCompletionAction and will no longer always have a peer entry just because of it.

Here we fix this issue by specifically checking for dangling MonitorUpdateCompletionAction::PaymentClaim entries and dropping them if there is no corresponding channel or peer state entry. We only check for PaymentClaimed actions rather than allowing for any dangling actions as 93b4479 was only triggerable with MPP claims, so dangling
MonitorUpdateCompletionActions for forwarded payments should be exceedingly rare.

This also adds an upgrade test to test a slightly convoluted version of this scenario.

@ldk-reviews-bot

ldk-reviews-bot commented May 22, 2025

Copy link
Copy Markdown

I've assigned @joostjager 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.

Comment threadlightning/src/ln/channelmanager.rs Outdated
Comment threadlightning/src/ln/channelmanager.rs Outdated
let logger =
WithContext::from(&args.logger, Some(node_id), None, None);
log_error!(logger, "Got blocked actions {:?} without a per-peer-state for {}", actions, node_id);
return Err(DecodeError::InvalidValue);

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.

This is all new code that adds a new error condition.

Here we fix this issue by specifically checking for dangling
MonitorUpdateCompletionAction::PaymentClaim entries and dropping
them if there is no corresponding channel or peer state entry.

How are they dropped now, compared to how it happens in main?

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.

On main in the no-peer-state case we always return an error, now we sometimes do not and ignore the completion actions. For the have-peer-state case, we used to store the completion actions even if we have no channel or monitor the completion actions are based on, but now we do not.

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 see

@ldk-reviews-bot

Copy link
Copy Markdown

👋 The first review has been submitted!

Do you think this PR is ready for a second reviewer? If so, click here to assign a second reviewer.

log_error!(WithContext::from(&args.logger, Some(node_id), None, None), "Got blocked actions without a per-peer-state for {}", node_id);
return Err(DecodeError::InvalidValue);
for actions in monitor_update_blocked_actions.values() {
for action in actions.iter() {

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.

Repeated code block

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 wasn't quite sure it was worth DRYing it up any given its so short (and the comments are slightly different)

@TheBlueMatt
TheBlueMattforce-pushed the 2025-05-0.1-upgrade-fail branch from 7105605 to bb2e29eCompareMay 22, 2025 14:35
@TheBlueMatt

Copy link
Copy Markdown
CollaboratorAuthor

Oops, the first set of checks were too eager, luckily test_reload_mon_update_completion_actions caught it -

$ git diff-tree -U1 710560554 bb2e29e97
diff --git a/lightning/src/ln/channelmanager.rs b/lightning/src/ln/channelmanager.rs
index 84251ca33b..54e453d56b 100644
--- a/lightning/src/ln/channelmanager.rs+++ b/lightning/src/ln/channelmanager.rs@@ -14179,6 +14179,14 @@ where
let mut max_in_flight_update_id = 0;
+ let starting_len = $chan_in_flight_upds.len();
$chan_in_flight_upds.retain(|upd| upd.update_id > $monitor.get_latest_update_id());
+ if $chan_in_flight_upds.len() < starting_len {+ log_debug!(+ $logger,+ "{} ChannelMonitorUpdates completed after ChannelManager was last serialized",+ starting_len - $chan_in_flight_upds.len()+ );+ }
let funding_txo = $monitor.get_funding_txo();
for update in $chan_in_flight_upds.iter() {
- log_trace!($logger, "Replaying ChannelMonitorUpdate {} for {}channel {}",+ log_debug!($logger, "Replaying ChannelMonitorUpdate {} for {}channel {}",
update.update_id, $channel_info_log, &$monitor.channel_id());
@@ -14746,24 +14754,7 @@ where
}
- let peer_state = peer_state.lock().unwrap();- let channel_id_matches = |updates: &(_, Vec<ChannelMonitorUpdate>)| {- updates.1.iter().any(|update| update.channel_id == Some(*channel_id))- };- if !peer_state.in_flight_monitor_updates.values().any(channel_id_matches) {- // If there are no pending `ChannelMonitorUpdate`s for this channel but we- // have pending post-update actions, its possible that one was left over- // from pre-0.1 payment claims where MPP claims led to a channel blocked on- // itself and later `ChannelMonitorUpdate`s didn't get their post-update- // actions run.- // This should only have happened for `PaymentClaimed` post-update actions,- // which we check here.- for action in actions.iter() {- if let MonitorUpdateCompletionAction::PaymentClaimed { .. } = action {- } else {- let logger =- WithContext::from(&args.logger, Some(node_id), None, None);- log_error!(logger, "Got blocked actions {:?} without a per-peer-state for {}", actions, node_id);- return Err(DecodeError::InvalidValue);- }- }- }+ // Note that we may have a post-update action for a channel that has no pending+ // `ChannelMonitorUpdate`s, but unlike the no-peer-state case, it may simply be+ // because we had a `ChannelMonitorUpdate` complete after the last time this+ // `ChannelManager` was serialized. In that case, we'll run the post-update+ // actions as soon as we get going.
}

@codecov

codecovBot commented May 22, 2025

Copy link
Copy Markdown

Codecov Report

Attention: Patch coverage is 0% with 14 lines in your changes missing coverage. Please review.

Project coverage is 89.78%. Comparing base (02b5564) to head (bb2e29e).

Files with missing linesPatch %Lines
lightning/src/ln/channelmanager.rs0.00%14 Missing ⚠️
Additional details and impacted files
@@ Coverage Diff @@## main #3790 +/- ##
==========================================
- Coverage 89.80% 89.78% -0.03% 
==========================================
Files 159 159 Lines 128691 128703 +12 Branches 128691 128703 +12 ==========================================
- Hits 115573 115552 -21 - Misses 10446 10472 +26 - Partials 2672 2679 +7 

☔ 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.

Comment threadlightning/src/ln/channelmanager.rs Outdated
@ldk-reviews-bot

Copy link
Copy Markdown

✅ Added second reviewer: @valentinewallace

@TheBlueMatt
TheBlueMattforce-pushed the 2025-05-0.1-upgrade-fail branch from bb2e29e to 5ca1019CompareMay 22, 2025 16:04
@TheBlueMatt

TheBlueMatt commented May 22, 2025

Copy link
Copy Markdown
CollaboratorAuthor

rustfmt'd (and used match instead of if let)

In 93b4479 we fixed an issue which
could cause a `ChannelMonitorUpdate` to get marked as blocked on
itself, leading to an eventual force-closure.
One potential side-effect of that issue, however, is that any
further `ChannelMonitorUpdate`s to the same channel while it is
blocked will not have any post-update actions processed (as there
is a pending blocked `ChannelMonitorUpdate` sitting in the
channel).
This can leave a dangling `MonitorUpdateCompletionAction` sitting
around even after the channel is closed.
In 0.1, because `ChannelMonitorUpdate`s to closed channels were
finally fully tracked, we started enforcing that any post-update
completion action we had on startup corresponded to a peer entry,
while at the same time no longer creating peer entries just because
we had serialized one in the data we were loading (only creating
them if we had channel(s) or a `ChannelMonitor`).
This can cause some `ChannelManager` to no longer deserialize on
0.1 as we might have a left-over dangling
`MonitorUpdateCompletionAction` and will no longer always have a
peer entry just because of it.
Here we fix this issue by specifically checking for dangling
`MonitorUpdateCompletionAction::PaymentClaim` entries and dropping
them if there is no corresponding channel or peer state entry. We
only check for `PaymentClaimed` actions rather than allowing for
any dangling actions as 93b4479
was only triggerable with MPP claims, so dangling
`MonitorUpdateCompletionAction`s for forwarded payments should be
exceedingly rare.
This also adds an upgrade test to test a slightly convoluted
version of this scenario.
@TheBlueMatt
TheBlueMattforce-pushed the 2025-05-0.1-upgrade-fail branch from 5ca1019 to 70b5552CompareMay 22, 2025 16:05
return Err(DecodeError::InvalidValue);
for actions in monitor_update_blocked_actions.values() {
for action in actions.iter() {
if matches!(action, MonitorUpdateCompletionAction::PaymentClaimed { .. }) {

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.

Hm I meant !matches! to avoid the else.

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 liked the comment in the block that's ignoring the PaymentClaimeds. I mean I can move it outside if you prefer but I often like comments in empty if blocks 🤷‍♂️

@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.

Code LGTM


// Finally, reload the node in the latest LDK. This previously failed.
let config = test_default_channel_config();
reload_node!(nodes[3], config, &node_d_ser, &[&mon_ser], persister, chain_mon, node);

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.

Test LGTM, just took a slightly closer look. It also fails as expected on main.

@TheBlueMatt
TheBlueMatt merged commit 63a5e03 into lightningdevkit:mainMay 23, 2025
@TheBlueMattTheBlueMatt mentioned this pull request May 23, 2025
@TheBlueMatt

Copy link
Copy Markdown
CollaboratorAuthor

Backported in #3794

Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants

@TheBlueMatt@ldk-reviews-bot@joostjager@wpaulino@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

Read ChannelManager even if we have no-peer post-update actions - #3790

Merged
TheBlueMatt merged 1 commit into
lightningdevkit:mainfrom
TheBlueMatt:2025-05-0.1-upgrade-fail
May 23, 2025
Merged

Read ChannelManager even if we have no-peer post-update actions#3790
TheBlueMatt merged 1 commit into
lightningdevkit:mainfrom
TheBlueMatt:2025-05-0.1-upgrade-fail

Conversation

@TheBlueMatt

Copy link
Copy Markdown
Collaborator

In 93b4479 we fixed an issue which could cause a ChannelMonitorUpdate to get marked as blocked on itself, leading to an eventual force-closure.

One potential side-effect of that issue, however, is that any further ChannelMonitorUpdates to the same channel while it is blocked will not have any post-update actions processed (as there is a pending blocked ChannelMonitorUpdate sitting in the channel).

This can leave a dangling MonitorUpdateCompletionAction sitting around even after the channel is closed.

In 0.1, because ChannelMonitorUpdates to closed channels were finally fully tracked, we started enforcing that any post-update completion action we had on startup corresponded to a peer entry, while at the same time no longer creating peer entries just because we had serialized one in the data we were loading (only creating them if we had channel(s) or a ChannelMonitor).

This can cause some ChannelManager to no longer deserialize on 0.1 as we might have a left-over dangling
MonitorUpdateCompletionAction and will no longer always have a peer entry just because of it.

Here we fix this issue by specifically checking for dangling MonitorUpdateCompletionAction::PaymentClaim entries and dropping them if there is no corresponding channel or peer state entry. We only check for PaymentClaimed actions rather than allowing for any dangling actions as 93b4479 was only triggerable with MPP claims, so dangling
MonitorUpdateCompletionActions for forwarded payments should be exceedingly rare.

This also adds an upgrade test to test a slightly convoluted version of this scenario.

@ldk-reviews-bot

ldk-reviews-bot commented May 22, 2025

Copy link
Copy Markdown

I've assigned @joostjager 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.

Comment threadlightning/src/ln/channelmanager.rs Outdated
Comment threadlightning/src/ln/channelmanager.rs Outdated
let logger =
WithContext::from(&args.logger, Some(node_id), None, None);
log_error!(logger, "Got blocked actions {:?} without a per-peer-state for {}", actions, node_id);
return Err(DecodeError::InvalidValue);

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.

This is all new code that adds a new error condition.

Here we fix this issue by specifically checking for dangling
MonitorUpdateCompletionAction::PaymentClaim entries and dropping
them if there is no corresponding channel or peer state entry.

How are they dropped now, compared to how it happens in main?

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.

On main in the no-peer-state case we always return an error, now we sometimes do not and ignore the completion actions. For the have-peer-state case, we used to store the completion actions even if we have no channel or monitor the completion actions are based on, but now we do not.

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 see

@ldk-reviews-bot

Copy link
Copy Markdown

👋 The first review has been submitted!

Do you think this PR is ready for a second reviewer? If so, click here to assign a second reviewer.

log_error!(WithContext::from(&args.logger, Some(node_id), None, None), "Got blocked actions without a per-peer-state for {}", node_id);
return Err(DecodeError::InvalidValue);
for actions in monitor_update_blocked_actions.values() {
for action in actions.iter() {

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.

Repeated code block

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 wasn't quite sure it was worth DRYing it up any given its so short (and the comments are slightly different)

@TheBlueMatt
TheBlueMattforce-pushed the 2025-05-0.1-upgrade-fail branch from 7105605 to bb2e29eCompareMay 22, 2025 14:35
@TheBlueMatt

Copy link
Copy Markdown
CollaboratorAuthor

Oops, the first set of checks were too eager, luckily test_reload_mon_update_completion_actions caught it -

$ git diff-tree -U1 710560554 bb2e29e97
diff --git a/lightning/src/ln/channelmanager.rs b/lightning/src/ln/channelmanager.rs
index 84251ca33b..54e453d56b 100644
--- a/lightning/src/ln/channelmanager.rs+++ b/lightning/src/ln/channelmanager.rs@@ -14179,6 +14179,14 @@ where
let mut max_in_flight_update_id = 0;
+ let starting_len = $chan_in_flight_upds.len();
$chan_in_flight_upds.retain(|upd| upd.update_id > $monitor.get_latest_update_id());
+ if $chan_in_flight_upds.len() < starting_len {+ log_debug!(+ $logger,+ "{} ChannelMonitorUpdates completed after ChannelManager was last serialized",+ starting_len - $chan_in_flight_upds.len()+ );+ }
let funding_txo = $monitor.get_funding_txo();
for update in $chan_in_flight_upds.iter() {
- log_trace!($logger, "Replaying ChannelMonitorUpdate {} for {}channel {}",+ log_debug!($logger, "Replaying ChannelMonitorUpdate {} for {}channel {}",
update.update_id, $channel_info_log, &$monitor.channel_id());
@@ -14746,24 +14754,7 @@ where
}
- let peer_state = peer_state.lock().unwrap();- let channel_id_matches = |updates: &(_, Vec<ChannelMonitorUpdate>)| {- updates.1.iter().any(|update| update.channel_id == Some(*channel_id))- };- if !peer_state.in_flight_monitor_updates.values().any(channel_id_matches) {- // If there are no pending `ChannelMonitorUpdate`s for this channel but we- // have pending post-update actions, its possible that one was left over- // from pre-0.1 payment claims where MPP claims led to a channel blocked on- // itself and later `ChannelMonitorUpdate`s didn't get their post-update- // actions run.- // This should only have happened for `PaymentClaimed` post-update actions,- // which we check here.- for action in actions.iter() {- if let MonitorUpdateCompletionAction::PaymentClaimed { .. } = action {- } else {- let logger =- WithContext::from(&args.logger, Some(node_id), None, None);- log_error!(logger, "Got blocked actions {:?} without a per-peer-state for {}", actions, node_id);- return Err(DecodeError::InvalidValue);- }- }- }+ // Note that we may have a post-update action for a channel that has no pending+ // `ChannelMonitorUpdate`s, but unlike the no-peer-state case, it may simply be+ // because we had a `ChannelMonitorUpdate` complete after the last time this+ // `ChannelManager` was serialized. In that case, we'll run the post-update+ // actions as soon as we get going.
}

@codecov

codecovBot commented May 22, 2025

Copy link
Copy Markdown

Codecov Report

Attention: Patch coverage is 0% with 14 lines in your changes missing coverage. Please review.

Project coverage is 89.78%. Comparing base (02b5564) to head (bb2e29e).

Files with missing linesPatch %Lines
lightning/src/ln/channelmanager.rs0.00%14 Missing ⚠️
Additional details and impacted files
@@ Coverage Diff @@## main #3790 +/- ##
==========================================
- Coverage 89.80% 89.78% -0.03% 
==========================================
Files 159 159 Lines 128691 128703 +12 Branches 128691 128703 +12 ==========================================
- Hits 115573 115552 -21 - Misses 10446 10472 +26 - Partials 2672 2679 +7 

☔ 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.

Comment threadlightning/src/ln/channelmanager.rs Outdated
@ldk-reviews-bot

Copy link
Copy Markdown

✅ Added second reviewer: @valentinewallace

@TheBlueMatt
TheBlueMattforce-pushed the 2025-05-0.1-upgrade-fail branch from bb2e29e to 5ca1019CompareMay 22, 2025 16:04
@TheBlueMatt

TheBlueMatt commented May 22, 2025

Copy link
Copy Markdown
CollaboratorAuthor

rustfmt'd (and used match instead of if let)

In 93b4479 we fixed an issue which
could cause a `ChannelMonitorUpdate` to get marked as blocked on
itself, leading to an eventual force-closure.
One potential side-effect of that issue, however, is that any
further `ChannelMonitorUpdate`s to the same channel while it is
blocked will not have any post-update actions processed (as there
is a pending blocked `ChannelMonitorUpdate` sitting in the
channel).
This can leave a dangling `MonitorUpdateCompletionAction` sitting
around even after the channel is closed.
In 0.1, because `ChannelMonitorUpdate`s to closed channels were
finally fully tracked, we started enforcing that any post-update
completion action we had on startup corresponded to a peer entry,
while at the same time no longer creating peer entries just because
we had serialized one in the data we were loading (only creating
them if we had channel(s) or a `ChannelMonitor`).
This can cause some `ChannelManager` to no longer deserialize on
0.1 as we might have a left-over dangling
`MonitorUpdateCompletionAction` and will no longer always have a
peer entry just because of it.
Here we fix this issue by specifically checking for dangling
`MonitorUpdateCompletionAction::PaymentClaim` entries and dropping
them if there is no corresponding channel or peer state entry. We
only check for `PaymentClaimed` actions rather than allowing for
any dangling actions as 93b4479
was only triggerable with MPP claims, so dangling
`MonitorUpdateCompletionAction`s for forwarded payments should be
exceedingly rare.
This also adds an upgrade test to test a slightly convoluted
version of this scenario.
@TheBlueMatt
TheBlueMattforce-pushed the 2025-05-0.1-upgrade-fail branch from 5ca1019 to 70b5552CompareMay 22, 2025 16:05
return Err(DecodeError::InvalidValue);
for actions in monitor_update_blocked_actions.values() {
for action in actions.iter() {
if matches!(action, MonitorUpdateCompletionAction::PaymentClaimed { .. }) {

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.

Hm I meant !matches! to avoid the else.

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 liked the comment in the block that's ignoring the PaymentClaimeds. I mean I can move it outside if you prefer but I often like comments in empty if blocks 🤷‍♂️

@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.

Code LGTM


// Finally, reload the node in the latest LDK. This previously failed.
let config = test_default_channel_config();
reload_node!(nodes[3], config, &node_d_ser, &[&mon_ser], persister, chain_mon, node);

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.

Test LGTM, just took a slightly closer look. It also fails as expected on main.

@TheBlueMatt
TheBlueMatt merged commit 63a5e03 into lightningdevkit:mainMay 23, 2025
@TheBlueMattTheBlueMatt mentioned this pull request May 23, 2025
@TheBlueMatt

Copy link
Copy Markdown
CollaboratorAuthor

Backported in #3794

Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants

@TheBlueMatt@ldk-reviews-bot@joostjager@wpaulino@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

Read ChannelManager even if we have no-peer post-update actions - #3790

Merged
TheBlueMatt merged 1 commit into
lightningdevkit:mainfrom
TheBlueMatt:2025-05-0.1-upgrade-fail
May 23, 2025
Merged

Read ChannelManager even if we have no-peer post-update actions#3790
TheBlueMatt merged 1 commit into
lightningdevkit:mainfrom
TheBlueMatt:2025-05-0.1-upgrade-fail

Conversation

@TheBlueMatt

Copy link
Copy Markdown
Collaborator

In 93b4479 we fixed an issue which could cause a ChannelMonitorUpdate to get marked as blocked on itself, leading to an eventual force-closure.

One potential side-effect of that issue, however, is that any further ChannelMonitorUpdates to the same channel while it is blocked will not have any post-update actions processed (as there is a pending blocked ChannelMonitorUpdate sitting in the channel).

This can leave a dangling MonitorUpdateCompletionAction sitting around even after the channel is closed.

In 0.1, because ChannelMonitorUpdates to closed channels were finally fully tracked, we started enforcing that any post-update completion action we had on startup corresponded to a peer entry, while at the same time no longer creating peer entries just because we had serialized one in the data we were loading (only creating them if we had channel(s) or a ChannelMonitor).

This can cause some ChannelManager to no longer deserialize on 0.1 as we might have a left-over dangling
MonitorUpdateCompletionAction and will no longer always have a peer entry just because of it.

Here we fix this issue by specifically checking for dangling MonitorUpdateCompletionAction::PaymentClaim entries and dropping them if there is no corresponding channel or peer state entry. We only check for PaymentClaimed actions rather than allowing for any dangling actions as 93b4479 was only triggerable with MPP claims, so dangling
MonitorUpdateCompletionActions for forwarded payments should be exceedingly rare.

This also adds an upgrade test to test a slightly convoluted version of this scenario.

@ldk-reviews-bot

ldk-reviews-bot commented May 22, 2025

Copy link
Copy Markdown

I've assigned @joostjager 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.

Comment threadlightning/src/ln/channelmanager.rs Outdated
Comment threadlightning/src/ln/channelmanager.rs Outdated
let logger =
WithContext::from(&args.logger, Some(node_id), None, None);
log_error!(logger, "Got blocked actions {:?} without a per-peer-state for {}", actions, node_id);
return Err(DecodeError::InvalidValue);

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.

This is all new code that adds a new error condition.

Here we fix this issue by specifically checking for dangling
MonitorUpdateCompletionAction::PaymentClaim entries and dropping
them if there is no corresponding channel or peer state entry.

How are they dropped now, compared to how it happens in main?

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.

On main in the no-peer-state case we always return an error, now we sometimes do not and ignore the completion actions. For the have-peer-state case, we used to store the completion actions even if we have no channel or monitor the completion actions are based on, but now we do not.

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 see

@ldk-reviews-bot

Copy link
Copy Markdown

👋 The first review has been submitted!

Do you think this PR is ready for a second reviewer? If so, click here to assign a second reviewer.

log_error!(WithContext::from(&args.logger, Some(node_id), None, None), "Got blocked actions without a per-peer-state for {}", node_id);
return Err(DecodeError::InvalidValue);
for actions in monitor_update_blocked_actions.values() {
for action in actions.iter() {

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.

Repeated code block

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 wasn't quite sure it was worth DRYing it up any given its so short (and the comments are slightly different)

@TheBlueMatt
TheBlueMattforce-pushed the 2025-05-0.1-upgrade-fail branch from 7105605 to bb2e29eCompareMay 22, 2025 14:35
@TheBlueMatt

Copy link
Copy Markdown
CollaboratorAuthor

Oops, the first set of checks were too eager, luckily test_reload_mon_update_completion_actions caught it -

$ git diff-tree -U1 710560554 bb2e29e97
diff --git a/lightning/src/ln/channelmanager.rs b/lightning/src/ln/channelmanager.rs
index 84251ca33b..54e453d56b 100644
--- a/lightning/src/ln/channelmanager.rs+++ b/lightning/src/ln/channelmanager.rs@@ -14179,6 +14179,14 @@ where
let mut max_in_flight_update_id = 0;
+ let starting_len = $chan_in_flight_upds.len();
$chan_in_flight_upds.retain(|upd| upd.update_id > $monitor.get_latest_update_id());
+ if $chan_in_flight_upds.len() < starting_len {+ log_debug!(+ $logger,+ "{} ChannelMonitorUpdates completed after ChannelManager was last serialized",+ starting_len - $chan_in_flight_upds.len()+ );+ }
let funding_txo = $monitor.get_funding_txo();
for update in $chan_in_flight_upds.iter() {
- log_trace!($logger, "Replaying ChannelMonitorUpdate {} for {}channel {}",+ log_debug!($logger, "Replaying ChannelMonitorUpdate {} for {}channel {}",
update.update_id, $channel_info_log, &$monitor.channel_id());
@@ -14746,24 +14754,7 @@ where
}
- let peer_state = peer_state.lock().unwrap();- let channel_id_matches = |updates: &(_, Vec<ChannelMonitorUpdate>)| {- updates.1.iter().any(|update| update.channel_id == Some(*channel_id))- };- if !peer_state.in_flight_monitor_updates.values().any(channel_id_matches) {- // If there are no pending `ChannelMonitorUpdate`s for this channel but we- // have pending post-update actions, its possible that one was left over- // from pre-0.1 payment claims where MPP claims led to a channel blocked on- // itself and later `ChannelMonitorUpdate`s didn't get their post-update- // actions run.- // This should only have happened for `PaymentClaimed` post-update actions,- // which we check here.- for action in actions.iter() {- if let MonitorUpdateCompletionAction::PaymentClaimed { .. } = action {- } else {- let logger =- WithContext::from(&args.logger, Some(node_id), None, None);- log_error!(logger, "Got blocked actions {:?} without a per-peer-state for {}", actions, node_id);- return Err(DecodeError::InvalidValue);- }- }- }+ // Note that we may have a post-update action for a channel that has no pending+ // `ChannelMonitorUpdate`s, but unlike the no-peer-state case, it may simply be+ // because we had a `ChannelMonitorUpdate` complete after the last time this+ // `ChannelManager` was serialized. In that case, we'll run the post-update+ // actions as soon as we get going.
}

@codecov

codecovBot commented May 22, 2025

Copy link
Copy Markdown

Codecov Report

Attention: Patch coverage is 0% with 14 lines in your changes missing coverage. Please review.

Project coverage is 89.78%. Comparing base (02b5564) to head (bb2e29e).

Files with missing linesPatch %Lines
lightning/src/ln/channelmanager.rs0.00%14 Missing ⚠️
Additional details and impacted files
@@ Coverage Diff @@## main #3790 +/- ##
==========================================
- Coverage 89.80% 89.78% -0.03% 
==========================================
Files 159 159 Lines 128691 128703 +12 Branches 128691 128703 +12 ==========================================
- Hits 115573 115552 -21 - Misses 10446 10472 +26 - Partials 2672 2679 +7 

☔ 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.

Comment threadlightning/src/ln/channelmanager.rs Outdated
@ldk-reviews-bot

Copy link
Copy Markdown

✅ Added second reviewer: @valentinewallace

@TheBlueMatt
TheBlueMattforce-pushed the 2025-05-0.1-upgrade-fail branch from bb2e29e to 5ca1019CompareMay 22, 2025 16:04
@TheBlueMatt

TheBlueMatt commented May 22, 2025

Copy link
Copy Markdown
CollaboratorAuthor

rustfmt'd (and used match instead of if let)

In 93b4479 we fixed an issue which
could cause a `ChannelMonitorUpdate` to get marked as blocked on
itself, leading to an eventual force-closure.
One potential side-effect of that issue, however, is that any
further `ChannelMonitorUpdate`s to the same channel while it is
blocked will not have any post-update actions processed (as there
is a pending blocked `ChannelMonitorUpdate` sitting in the
channel).
This can leave a dangling `MonitorUpdateCompletionAction` sitting
around even after the channel is closed.
In 0.1, because `ChannelMonitorUpdate`s to closed channels were
finally fully tracked, we started enforcing that any post-update
completion action we had on startup corresponded to a peer entry,
while at the same time no longer creating peer entries just because
we had serialized one in the data we were loading (only creating
them if we had channel(s) or a `ChannelMonitor`).
This can cause some `ChannelManager` to no longer deserialize on
0.1 as we might have a left-over dangling
`MonitorUpdateCompletionAction` and will no longer always have a
peer entry just because of it.
Here we fix this issue by specifically checking for dangling
`MonitorUpdateCompletionAction::PaymentClaim` entries and dropping
them if there is no corresponding channel or peer state entry. We
only check for `PaymentClaimed` actions rather than allowing for
any dangling actions as 93b4479
was only triggerable with MPP claims, so dangling
`MonitorUpdateCompletionAction`s for forwarded payments should be
exceedingly rare.
This also adds an upgrade test to test a slightly convoluted
version of this scenario.
@TheBlueMatt
TheBlueMattforce-pushed the 2025-05-0.1-upgrade-fail branch from 5ca1019 to 70b5552CompareMay 22, 2025 16:05
return Err(DecodeError::InvalidValue);
for actions in monitor_update_blocked_actions.values() {
for action in actions.iter() {
if matches!(action, MonitorUpdateCompletionAction::PaymentClaimed { .. }) {

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.

Hm I meant !matches! to avoid the else.

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 liked the comment in the block that's ignoring the PaymentClaimeds. I mean I can move it outside if you prefer but I often like comments in empty if blocks 🤷‍♂️

@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.

Code LGTM


// Finally, reload the node in the latest LDK. This previously failed.
let config = test_default_channel_config();
reload_node!(nodes[3], config, &node_d_ser, &[&mon_ser], persister, chain_mon, node);

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.

Test LGTM, just took a slightly closer look. It also fails as expected on main.

@TheBlueMatt
TheBlueMatt merged commit 63a5e03 into lightningdevkit:mainMay 23, 2025
@TheBlueMattTheBlueMatt mentioned this pull request May 23, 2025
@TheBlueMatt

Copy link
Copy Markdown
CollaboratorAuthor

Backported in #3794

Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants

@TheBlueMatt@ldk-reviews-bot@joostjager@wpaulino@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

Read ChannelManager even if we have no-peer post-update actions - #3790

Merged
TheBlueMatt merged 1 commit into
lightningdevkit:mainfrom
TheBlueMatt:2025-05-0.1-upgrade-fail
May 23, 2025
Merged

Read ChannelManager even if we have no-peer post-update actions#3790
TheBlueMatt merged 1 commit into
lightningdevkit:mainfrom
TheBlueMatt:2025-05-0.1-upgrade-fail

Conversation

@TheBlueMatt

Copy link
Copy Markdown
Collaborator

In 93b4479 we fixed an issue which could cause a ChannelMonitorUpdate to get marked as blocked on itself, leading to an eventual force-closure.

One potential side-effect of that issue, however, is that any further ChannelMonitorUpdates to the same channel while it is blocked will not have any post-update actions processed (as there is a pending blocked ChannelMonitorUpdate sitting in the channel).

This can leave a dangling MonitorUpdateCompletionAction sitting around even after the channel is closed.

In 0.1, because ChannelMonitorUpdates to closed channels were finally fully tracked, we started enforcing that any post-update completion action we had on startup corresponded to a peer entry, while at the same time no longer creating peer entries just because we had serialized one in the data we were loading (only creating them if we had channel(s) or a ChannelMonitor).

This can cause some ChannelManager to no longer deserialize on 0.1 as we might have a left-over dangling
MonitorUpdateCompletionAction and will no longer always have a peer entry just because of it.

Here we fix this issue by specifically checking for dangling MonitorUpdateCompletionAction::PaymentClaim entries and dropping them if there is no corresponding channel or peer state entry. We only check for PaymentClaimed actions rather than allowing for any dangling actions as 93b4479 was only triggerable with MPP claims, so dangling
MonitorUpdateCompletionActions for forwarded payments should be exceedingly rare.

This also adds an upgrade test to test a slightly convoluted version of this scenario.

@ldk-reviews-bot

ldk-reviews-bot commented May 22, 2025

Copy link
Copy Markdown

I've assigned @joostjager 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.

Comment threadlightning/src/ln/channelmanager.rs Outdated
Comment threadlightning/src/ln/channelmanager.rs Outdated
let logger =
WithContext::from(&args.logger, Some(node_id), None, None);
log_error!(logger, "Got blocked actions {:?} without a per-peer-state for {}", actions, node_id);
return Err(DecodeError::InvalidValue);

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.

This is all new code that adds a new error condition.

Here we fix this issue by specifically checking for dangling
MonitorUpdateCompletionAction::PaymentClaim entries and dropping
them if there is no corresponding channel or peer state entry.

How are they dropped now, compared to how it happens in main?

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.

On main in the no-peer-state case we always return an error, now we sometimes do not and ignore the completion actions. For the have-peer-state case, we used to store the completion actions even if we have no channel or monitor the completion actions are based on, but now we do not.

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 see

@ldk-reviews-bot

Copy link
Copy Markdown

👋 The first review has been submitted!

Do you think this PR is ready for a second reviewer? If so, click here to assign a second reviewer.

log_error!(WithContext::from(&args.logger, Some(node_id), None, None), "Got blocked actions without a per-peer-state for {}", node_id);
return Err(DecodeError::InvalidValue);
for actions in monitor_update_blocked_actions.values() {
for action in actions.iter() {

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.

Repeated code block

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 wasn't quite sure it was worth DRYing it up any given its so short (and the comments are slightly different)

@TheBlueMatt
TheBlueMattforce-pushed the 2025-05-0.1-upgrade-fail branch from 7105605 to bb2e29eCompareMay 22, 2025 14:35
@TheBlueMatt

Copy link
Copy Markdown
CollaboratorAuthor

Oops, the first set of checks were too eager, luckily test_reload_mon_update_completion_actions caught it -

$ git diff-tree -U1 710560554 bb2e29e97
diff --git a/lightning/src/ln/channelmanager.rs b/lightning/src/ln/channelmanager.rs
index 84251ca33b..54e453d56b 100644
--- a/lightning/src/ln/channelmanager.rs+++ b/lightning/src/ln/channelmanager.rs@@ -14179,6 +14179,14 @@ where
let mut max_in_flight_update_id = 0;
+ let starting_len = $chan_in_flight_upds.len();
$chan_in_flight_upds.retain(|upd| upd.update_id > $monitor.get_latest_update_id());
+ if $chan_in_flight_upds.len() < starting_len {+ log_debug!(+ $logger,+ "{} ChannelMonitorUpdates completed after ChannelManager was last serialized",+ starting_len - $chan_in_flight_upds.len()+ );+ }
let funding_txo = $monitor.get_funding_txo();
for update in $chan_in_flight_upds.iter() {
- log_trace!($logger, "Replaying ChannelMonitorUpdate {} for {}channel {}",+ log_debug!($logger, "Replaying ChannelMonitorUpdate {} for {}channel {}",
update.update_id, $channel_info_log, &$monitor.channel_id());
@@ -14746,24 +14754,7 @@ where
}
- let peer_state = peer_state.lock().unwrap();- let channel_id_matches = |updates: &(_, Vec<ChannelMonitorUpdate>)| {- updates.1.iter().any(|update| update.channel_id == Some(*channel_id))- };- if !peer_state.in_flight_monitor_updates.values().any(channel_id_matches) {- // If there are no pending `ChannelMonitorUpdate`s for this channel but we- // have pending post-update actions, its possible that one was left over- // from pre-0.1 payment claims where MPP claims led to a channel blocked on- // itself and later `ChannelMonitorUpdate`s didn't get their post-update- // actions run.- // This should only have happened for `PaymentClaimed` post-update actions,- // which we check here.- for action in actions.iter() {- if let MonitorUpdateCompletionAction::PaymentClaimed { .. } = action {- } else {- let logger =- WithContext::from(&args.logger, Some(node_id), None, None);- log_error!(logger, "Got blocked actions {:?} without a per-peer-state for {}", actions, node_id);- return Err(DecodeError::InvalidValue);- }- }- }+ // Note that we may have a post-update action for a channel that has no pending+ // `ChannelMonitorUpdate`s, but unlike the no-peer-state case, it may simply be+ // because we had a `ChannelMonitorUpdate` complete after the last time this+ // `ChannelManager` was serialized. In that case, we'll run the post-update+ // actions as soon as we get going.
}

@codecov

codecovBot commented May 22, 2025

Copy link
Copy Markdown

Codecov Report

Attention: Patch coverage is 0% with 14 lines in your changes missing coverage. Please review.

Project coverage is 89.78%. Comparing base (02b5564) to head (bb2e29e).

Files with missing linesPatch %Lines
lightning/src/ln/channelmanager.rs0.00%14 Missing ⚠️
Additional details and impacted files
@@ Coverage Diff @@## main #3790 +/- ##
==========================================
- Coverage 89.80% 89.78% -0.03% 
==========================================
Files 159 159 Lines 128691 128703 +12 Branches 128691 128703 +12 ==========================================
- Hits 115573 115552 -21 - Misses 10446 10472 +26 - Partials 2672 2679 +7 

☔ 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.

Comment threadlightning/src/ln/channelmanager.rs Outdated
@ldk-reviews-bot

Copy link
Copy Markdown

✅ Added second reviewer: @valentinewallace

@TheBlueMatt
TheBlueMattforce-pushed the 2025-05-0.1-upgrade-fail branch from bb2e29e to 5ca1019CompareMay 22, 2025 16:04
@TheBlueMatt

TheBlueMatt commented May 22, 2025

Copy link
Copy Markdown
CollaboratorAuthor

rustfmt'd (and used match instead of if let)

In 93b4479 we fixed an issue which
could cause a `ChannelMonitorUpdate` to get marked as blocked on
itself, leading to an eventual force-closure.
One potential side-effect of that issue, however, is that any
further `ChannelMonitorUpdate`s to the same channel while it is
blocked will not have any post-update actions processed (as there
is a pending blocked `ChannelMonitorUpdate` sitting in the
channel).
This can leave a dangling `MonitorUpdateCompletionAction` sitting
around even after the channel is closed.
In 0.1, because `ChannelMonitorUpdate`s to closed channels were
finally fully tracked, we started enforcing that any post-update
completion action we had on startup corresponded to a peer entry,
while at the same time no longer creating peer entries just because
we had serialized one in the data we were loading (only creating
them if we had channel(s) or a `ChannelMonitor`).
This can cause some `ChannelManager` to no longer deserialize on
0.1 as we might have a left-over dangling
`MonitorUpdateCompletionAction` and will no longer always have a
peer entry just because of it.
Here we fix this issue by specifically checking for dangling
`MonitorUpdateCompletionAction::PaymentClaim` entries and dropping
them if there is no corresponding channel or peer state entry. We
only check for `PaymentClaimed` actions rather than allowing for
any dangling actions as 93b4479
was only triggerable with MPP claims, so dangling
`MonitorUpdateCompletionAction`s for forwarded payments should be
exceedingly rare.
This also adds an upgrade test to test a slightly convoluted
version of this scenario.
@TheBlueMatt
TheBlueMattforce-pushed the 2025-05-0.1-upgrade-fail branch from 5ca1019 to 70b5552CompareMay 22, 2025 16:05
return Err(DecodeError::InvalidValue);
for actions in monitor_update_blocked_actions.values() {
for action in actions.iter() {
if matches!(action, MonitorUpdateCompletionAction::PaymentClaimed { .. }) {

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.

Hm I meant !matches! to avoid the else.

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 liked the comment in the block that's ignoring the PaymentClaimeds. I mean I can move it outside if you prefer but I often like comments in empty if blocks 🤷‍♂️

@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.

Code LGTM


// Finally, reload the node in the latest LDK. This previously failed.
let config = test_default_channel_config();
reload_node!(nodes[3], config, &node_d_ser, &[&mon_ser], persister, chain_mon, node);

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.

Test LGTM, just took a slightly closer look. It also fails as expected on main.

@TheBlueMatt
TheBlueMatt merged commit 63a5e03 into lightningdevkit:mainMay 23, 2025
@TheBlueMattTheBlueMatt mentioned this pull request May 23, 2025
@TheBlueMatt

Copy link
Copy Markdown
CollaboratorAuthor

Backported in #3794

Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants

@TheBlueMatt@ldk-reviews-bot@joostjager@wpaulino@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

Read ChannelManager even if we have no-peer post-update actions - #3790

Merged
TheBlueMatt merged 1 commit into
lightningdevkit:mainfrom
TheBlueMatt:2025-05-0.1-upgrade-fail
May 23, 2025
Merged

Read ChannelManager even if we have no-peer post-update actions#3790
TheBlueMatt merged 1 commit into
lightningdevkit:mainfrom
TheBlueMatt:2025-05-0.1-upgrade-fail

Conversation

@TheBlueMatt

Copy link
Copy Markdown
Collaborator

In 93b4479 we fixed an issue which could cause a ChannelMonitorUpdate to get marked as blocked on itself, leading to an eventual force-closure.

One potential side-effect of that issue, however, is that any further ChannelMonitorUpdates to the same channel while it is blocked will not have any post-update actions processed (as there is a pending blocked ChannelMonitorUpdate sitting in the channel).

This can leave a dangling MonitorUpdateCompletionAction sitting around even after the channel is closed.

In 0.1, because ChannelMonitorUpdates to closed channels were finally fully tracked, we started enforcing that any post-update completion action we had on startup corresponded to a peer entry, while at the same time no longer creating peer entries just because we had serialized one in the data we were loading (only creating them if we had channel(s) or a ChannelMonitor).

This can cause some ChannelManager to no longer deserialize on 0.1 as we might have a left-over dangling
MonitorUpdateCompletionAction and will no longer always have a peer entry just because of it.

Here we fix this issue by specifically checking for dangling MonitorUpdateCompletionAction::PaymentClaim entries and dropping them if there is no corresponding channel or peer state entry. We only check for PaymentClaimed actions rather than allowing for any dangling actions as 93b4479 was only triggerable with MPP claims, so dangling
MonitorUpdateCompletionActions for forwarded payments should be exceedingly rare.

This also adds an upgrade test to test a slightly convoluted version of this scenario.

@ldk-reviews-bot

ldk-reviews-bot commented May 22, 2025

Copy link
Copy Markdown

I've assigned @joostjager 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.

Comment threadlightning/src/ln/channelmanager.rs Outdated
Comment threadlightning/src/ln/channelmanager.rs Outdated
let logger =
WithContext::from(&args.logger, Some(node_id), None, None);
log_error!(logger, "Got blocked actions {:?} without a per-peer-state for {}", actions, node_id);
return Err(DecodeError::InvalidValue);

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.

This is all new code that adds a new error condition.

Here we fix this issue by specifically checking for dangling
MonitorUpdateCompletionAction::PaymentClaim entries and dropping
them if there is no corresponding channel or peer state entry.

How are they dropped now, compared to how it happens in main?

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.

On main in the no-peer-state case we always return an error, now we sometimes do not and ignore the completion actions. For the have-peer-state case, we used to store the completion actions even if we have no channel or monitor the completion actions are based on, but now we do not.

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 see

@ldk-reviews-bot

Copy link
Copy Markdown

👋 The first review has been submitted!

Do you think this PR is ready for a second reviewer? If so, click here to assign a second reviewer.

log_error!(WithContext::from(&args.logger, Some(node_id), None, None), "Got blocked actions without a per-peer-state for {}", node_id);
return Err(DecodeError::InvalidValue);
for actions in monitor_update_blocked_actions.values() {
for action in actions.iter() {

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.

Repeated code block

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 wasn't quite sure it was worth DRYing it up any given its so short (and the comments are slightly different)

@TheBlueMatt
TheBlueMattforce-pushed the 2025-05-0.1-upgrade-fail branch from 7105605 to bb2e29eCompareMay 22, 2025 14:35
@TheBlueMatt

Copy link
Copy Markdown
CollaboratorAuthor

Oops, the first set of checks were too eager, luckily test_reload_mon_update_completion_actions caught it -

$ git diff-tree -U1 710560554 bb2e29e97
diff --git a/lightning/src/ln/channelmanager.rs b/lightning/src/ln/channelmanager.rs
index 84251ca33b..54e453d56b 100644
--- a/lightning/src/ln/channelmanager.rs+++ b/lightning/src/ln/channelmanager.rs@@ -14179,6 +14179,14 @@ where
let mut max_in_flight_update_id = 0;
+ let starting_len = $chan_in_flight_upds.len();
$chan_in_flight_upds.retain(|upd| upd.update_id > $monitor.get_latest_update_id());
+ if $chan_in_flight_upds.len() < starting_len {+ log_debug!(+ $logger,+ "{} ChannelMonitorUpdates completed after ChannelManager was last serialized",+ starting_len - $chan_in_flight_upds.len()+ );+ }
let funding_txo = $monitor.get_funding_txo();
for update in $chan_in_flight_upds.iter() {
- log_trace!($logger, "Replaying ChannelMonitorUpdate {} for {}channel {}",+ log_debug!($logger, "Replaying ChannelMonitorUpdate {} for {}channel {}",
update.update_id, $channel_info_log, &$monitor.channel_id());
@@ -14746,24 +14754,7 @@ where
}
- let peer_state = peer_state.lock().unwrap();- let channel_id_matches = |updates: &(_, Vec<ChannelMonitorUpdate>)| {- updates.1.iter().any(|update| update.channel_id == Some(*channel_id))- };- if !peer_state.in_flight_monitor_updates.values().any(channel_id_matches) {- // If there are no pending `ChannelMonitorUpdate`s for this channel but we- // have pending post-update actions, its possible that one was left over- // from pre-0.1 payment claims where MPP claims led to a channel blocked on- // itself and later `ChannelMonitorUpdate`s didn't get their post-update- // actions run.- // This should only have happened for `PaymentClaimed` post-update actions,- // which we check here.- for action in actions.iter() {- if let MonitorUpdateCompletionAction::PaymentClaimed { .. } = action {- } else {- let logger =- WithContext::from(&args.logger, Some(node_id), None, None);- log_error!(logger, "Got blocked actions {:?} without a per-peer-state for {}", actions, node_id);- return Err(DecodeError::InvalidValue);- }- }- }+ // Note that we may have a post-update action for a channel that has no pending+ // `ChannelMonitorUpdate`s, but unlike the no-peer-state case, it may simply be+ // because we had a `ChannelMonitorUpdate` complete after the last time this+ // `ChannelManager` was serialized. In that case, we'll run the post-update+ // actions as soon as we get going.
}

@codecov

codecovBot commented May 22, 2025

Copy link
Copy Markdown

Codecov Report

Attention: Patch coverage is 0% with 14 lines in your changes missing coverage. Please review.

Project coverage is 89.78%. Comparing base (02b5564) to head (bb2e29e).

Files with missing linesPatch %Lines
lightning/src/ln/channelmanager.rs0.00%14 Missing ⚠️
Additional details and impacted files
@@ Coverage Diff @@## main #3790 +/- ##
==========================================
- Coverage 89.80% 89.78% -0.03% 
==========================================
Files 159 159 Lines 128691 128703 +12 Branches 128691 128703 +12 ==========================================
- Hits 115573 115552 -21 - Misses 10446 10472 +26 - Partials 2672 2679 +7 

☔ 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.

Comment threadlightning/src/ln/channelmanager.rs Outdated
@ldk-reviews-bot

Copy link
Copy Markdown

✅ Added second reviewer: @valentinewallace

@TheBlueMatt
TheBlueMattforce-pushed the 2025-05-0.1-upgrade-fail branch from bb2e29e to 5ca1019CompareMay 22, 2025 16:04
@TheBlueMatt

TheBlueMatt commented May 22, 2025

Copy link
Copy Markdown
CollaboratorAuthor

rustfmt'd (and used match instead of if let)

In 93b4479 we fixed an issue which
could cause a `ChannelMonitorUpdate` to get marked as blocked on
itself, leading to an eventual force-closure.
One potential side-effect of that issue, however, is that any
further `ChannelMonitorUpdate`s to the same channel while it is
blocked will not have any post-update actions processed (as there
is a pending blocked `ChannelMonitorUpdate` sitting in the
channel).
This can leave a dangling `MonitorUpdateCompletionAction` sitting
around even after the channel is closed.
In 0.1, because `ChannelMonitorUpdate`s to closed channels were
finally fully tracked, we started enforcing that any post-update
completion action we had on startup corresponded to a peer entry,
while at the same time no longer creating peer entries just because
we had serialized one in the data we were loading (only creating
them if we had channel(s) or a `ChannelMonitor`).
This can cause some `ChannelManager` to no longer deserialize on
0.1 as we might have a left-over dangling
`MonitorUpdateCompletionAction` and will no longer always have a
peer entry just because of it.
Here we fix this issue by specifically checking for dangling
`MonitorUpdateCompletionAction::PaymentClaim` entries and dropping
them if there is no corresponding channel or peer state entry. We
only check for `PaymentClaimed` actions rather than allowing for
any dangling actions as 93b4479
was only triggerable with MPP claims, so dangling
`MonitorUpdateCompletionAction`s for forwarded payments should be
exceedingly rare.
This also adds an upgrade test to test a slightly convoluted
version of this scenario.
@TheBlueMatt
TheBlueMattforce-pushed the 2025-05-0.1-upgrade-fail branch from 5ca1019 to 70b5552CompareMay 22, 2025 16:05
return Err(DecodeError::InvalidValue);
for actions in monitor_update_blocked_actions.values() {
for action in actions.iter() {
if matches!(action, MonitorUpdateCompletionAction::PaymentClaimed { .. }) {

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.

Hm I meant !matches! to avoid the else.

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 liked the comment in the block that's ignoring the PaymentClaimeds. I mean I can move it outside if you prefer but I often like comments in empty if blocks 🤷‍♂️

@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.

Code LGTM


// Finally, reload the node in the latest LDK. This previously failed.
let config = test_default_channel_config();
reload_node!(nodes[3], config, &node_d_ser, &[&mon_ser], persister, chain_mon, node);

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.

Test LGTM, just took a slightly closer look. It also fails as expected on main.

@TheBlueMatt
TheBlueMatt merged commit 63a5e03 into lightningdevkit:mainMay 23, 2025
@TheBlueMattTheBlueMatt mentioned this pull request May 23, 2025
@TheBlueMatt

Copy link
Copy Markdown
CollaboratorAuthor

Backported in #3794

Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants

@TheBlueMatt@ldk-reviews-bot@joostjager@wpaulino@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

Read ChannelManager even if we have no-peer post-update actions - #3790

Merged
TheBlueMatt merged 1 commit into
lightningdevkit:mainfrom
TheBlueMatt:2025-05-0.1-upgrade-fail
May 23, 2025
Merged

Read ChannelManager even if we have no-peer post-update actions#3790
TheBlueMatt merged 1 commit into
lightningdevkit:mainfrom
TheBlueMatt:2025-05-0.1-upgrade-fail

Conversation

@TheBlueMatt

Copy link
Copy Markdown
Collaborator

In 93b4479 we fixed an issue which could cause a ChannelMonitorUpdate to get marked as blocked on itself, leading to an eventual force-closure.

One potential side-effect of that issue, however, is that any further ChannelMonitorUpdates to the same channel while it is blocked will not have any post-update actions processed (as there is a pending blocked ChannelMonitorUpdate sitting in the channel).

This can leave a dangling MonitorUpdateCompletionAction sitting around even after the channel is closed.

In 0.1, because ChannelMonitorUpdates to closed channels were finally fully tracked, we started enforcing that any post-update completion action we had on startup corresponded to a peer entry, while at the same time no longer creating peer entries just because we had serialized one in the data we were loading (only creating them if we had channel(s) or a ChannelMonitor).

This can cause some ChannelManager to no longer deserialize on 0.1 as we might have a left-over dangling
MonitorUpdateCompletionAction and will no longer always have a peer entry just because of it.

Here we fix this issue by specifically checking for dangling MonitorUpdateCompletionAction::PaymentClaim entries and dropping them if there is no corresponding channel or peer state entry. We only check for PaymentClaimed actions rather than allowing for any dangling actions as 93b4479 was only triggerable with MPP claims, so dangling
MonitorUpdateCompletionActions for forwarded payments should be exceedingly rare.

This also adds an upgrade test to test a slightly convoluted version of this scenario.

@ldk-reviews-bot

ldk-reviews-bot commented May 22, 2025

Copy link
Copy Markdown

I've assigned @joostjager 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.

Comment threadlightning/src/ln/channelmanager.rs Outdated
Comment threadlightning/src/ln/channelmanager.rs Outdated
let logger =
WithContext::from(&args.logger, Some(node_id), None, None);
log_error!(logger, "Got blocked actions {:?} without a per-peer-state for {}", actions, node_id);
return Err(DecodeError::InvalidValue);

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.

This is all new code that adds a new error condition.

Here we fix this issue by specifically checking for dangling
MonitorUpdateCompletionAction::PaymentClaim entries and dropping
them if there is no corresponding channel or peer state entry.

How are they dropped now, compared to how it happens in main?

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.

On main in the no-peer-state case we always return an error, now we sometimes do not and ignore the completion actions. For the have-peer-state case, we used to store the completion actions even if we have no channel or monitor the completion actions are based on, but now we do not.

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 see

@ldk-reviews-bot

Copy link
Copy Markdown

👋 The first review has been submitted!

Do you think this PR is ready for a second reviewer? If so, click here to assign a second reviewer.

log_error!(WithContext::from(&args.logger, Some(node_id), None, None), "Got blocked actions without a per-peer-state for {}", node_id);
return Err(DecodeError::InvalidValue);
for actions in monitor_update_blocked_actions.values() {
for action in actions.iter() {

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.

Repeated code block

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 wasn't quite sure it was worth DRYing it up any given its so short (and the comments are slightly different)

@TheBlueMatt
TheBlueMattforce-pushed the 2025-05-0.1-upgrade-fail branch from 7105605 to bb2e29eCompareMay 22, 2025 14:35
@TheBlueMatt

Copy link
Copy Markdown
CollaboratorAuthor

Oops, the first set of checks were too eager, luckily test_reload_mon_update_completion_actions caught it -

$ git diff-tree -U1 710560554 bb2e29e97
diff --git a/lightning/src/ln/channelmanager.rs b/lightning/src/ln/channelmanager.rs
index 84251ca33b..54e453d56b 100644
--- a/lightning/src/ln/channelmanager.rs+++ b/lightning/src/ln/channelmanager.rs@@ -14179,6 +14179,14 @@ where
let mut max_in_flight_update_id = 0;
+ let starting_len = $chan_in_flight_upds.len();
$chan_in_flight_upds.retain(|upd| upd.update_id > $monitor.get_latest_update_id());
+ if $chan_in_flight_upds.len() < starting_len {+ log_debug!(+ $logger,+ "{} ChannelMonitorUpdates completed after ChannelManager was last serialized",+ starting_len - $chan_in_flight_upds.len()+ );+ }
let funding_txo = $monitor.get_funding_txo();
for update in $chan_in_flight_upds.iter() {
- log_trace!($logger, "Replaying ChannelMonitorUpdate {} for {}channel {}",+ log_debug!($logger, "Replaying ChannelMonitorUpdate {} for {}channel {}",
update.update_id, $channel_info_log, &$monitor.channel_id());
@@ -14746,24 +14754,7 @@ where
}
- let peer_state = peer_state.lock().unwrap();- let channel_id_matches = |updates: &(_, Vec<ChannelMonitorUpdate>)| {- updates.1.iter().any(|update| update.channel_id == Some(*channel_id))- };- if !peer_state.in_flight_monitor_updates.values().any(channel_id_matches) {- // If there are no pending `ChannelMonitorUpdate`s for this channel but we- // have pending post-update actions, its possible that one was left over- // from pre-0.1 payment claims where MPP claims led to a channel blocked on- // itself and later `ChannelMonitorUpdate`s didn't get their post-update- // actions run.- // This should only have happened for `PaymentClaimed` post-update actions,- // which we check here.- for action in actions.iter() {- if let MonitorUpdateCompletionAction::PaymentClaimed { .. } = action {- } else {- let logger =- WithContext::from(&args.logger, Some(node_id), None, None);- log_error!(logger, "Got blocked actions {:?} without a per-peer-state for {}", actions, node_id);- return Err(DecodeError::InvalidValue);- }- }- }+ // Note that we may have a post-update action for a channel that has no pending+ // `ChannelMonitorUpdate`s, but unlike the no-peer-state case, it may simply be+ // because we had a `ChannelMonitorUpdate` complete after the last time this+ // `ChannelManager` was serialized. In that case, we'll run the post-update+ // actions as soon as we get going.
}

@codecov

codecovBot commented May 22, 2025

Copy link
Copy Markdown

Codecov Report

Attention: Patch coverage is 0% with 14 lines in your changes missing coverage. Please review.

Project coverage is 89.78%. Comparing base (02b5564) to head (bb2e29e).

Files with missing linesPatch %Lines
lightning/src/ln/channelmanager.rs0.00%14 Missing ⚠️
Additional details and impacted files
@@ Coverage Diff @@## main #3790 +/- ##
==========================================
- Coverage 89.80% 89.78% -0.03% 
==========================================
Files 159 159 Lines 128691 128703 +12 Branches 128691 128703 +12 ==========================================
- Hits 115573 115552 -21 - Misses 10446 10472 +26 - Partials 2672 2679 +7 

☔ 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.

Comment threadlightning/src/ln/channelmanager.rs Outdated
@ldk-reviews-bot

Copy link
Copy Markdown

✅ Added second reviewer: @valentinewallace

@TheBlueMatt
TheBlueMattforce-pushed the 2025-05-0.1-upgrade-fail branch from bb2e29e to 5ca1019CompareMay 22, 2025 16:04
@TheBlueMatt

TheBlueMatt commented May 22, 2025

Copy link
Copy Markdown
CollaboratorAuthor

rustfmt'd (and used match instead of if let)

In 93b4479 we fixed an issue which
could cause a `ChannelMonitorUpdate` to get marked as blocked on
itself, leading to an eventual force-closure.
One potential side-effect of that issue, however, is that any
further `ChannelMonitorUpdate`s to the same channel while it is
blocked will not have any post-update actions processed (as there
is a pending blocked `ChannelMonitorUpdate` sitting in the
channel).
This can leave a dangling `MonitorUpdateCompletionAction` sitting
around even after the channel is closed.
In 0.1, because `ChannelMonitorUpdate`s to closed channels were
finally fully tracked, we started enforcing that any post-update
completion action we had on startup corresponded to a peer entry,
while at the same time no longer creating peer entries just because
we had serialized one in the data we were loading (only creating
them if we had channel(s) or a `ChannelMonitor`).
This can cause some `ChannelManager` to no longer deserialize on
0.1 as we might have a left-over dangling
`MonitorUpdateCompletionAction` and will no longer always have a
peer entry just because of it.
Here we fix this issue by specifically checking for dangling
`MonitorUpdateCompletionAction::PaymentClaim` entries and dropping
them if there is no corresponding channel or peer state entry. We
only check for `PaymentClaimed` actions rather than allowing for
any dangling actions as 93b4479
was only triggerable with MPP claims, so dangling
`MonitorUpdateCompletionAction`s for forwarded payments should be
exceedingly rare.
This also adds an upgrade test to test a slightly convoluted
version of this scenario.
@TheBlueMatt
TheBlueMattforce-pushed the 2025-05-0.1-upgrade-fail branch from 5ca1019 to 70b5552CompareMay 22, 2025 16:05
return Err(DecodeError::InvalidValue);
for actions in monitor_update_blocked_actions.values() {
for action in actions.iter() {
if matches!(action, MonitorUpdateCompletionAction::PaymentClaimed { .. }) {

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.

Hm I meant !matches! to avoid the else.

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 liked the comment in the block that's ignoring the PaymentClaimeds. I mean I can move it outside if you prefer but I often like comments in empty if blocks 🤷‍♂️

@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.

Code LGTM


// Finally, reload the node in the latest LDK. This previously failed.
let config = test_default_channel_config();
reload_node!(nodes[3], config, &node_d_ser, &[&mon_ser], persister, chain_mon, node);

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.

Test LGTM, just took a slightly closer look. It also fails as expected on main.

@TheBlueMatt
TheBlueMatt merged commit 63a5e03 into lightningdevkit:mainMay 23, 2025
@TheBlueMattTheBlueMatt mentioned this pull request May 23, 2025
@TheBlueMatt

Copy link
Copy Markdown
CollaboratorAuthor

Backported in #3794

Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants

@TheBlueMatt@ldk-reviews-bot@joostjager@wpaulino@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

Read ChannelManager even if we have no-peer post-update actions - #3790

Merged
TheBlueMatt merged 1 commit into
lightningdevkit:mainfrom
TheBlueMatt:2025-05-0.1-upgrade-fail
May 23, 2025
Merged

Read ChannelManager even if we have no-peer post-update actions#3790
TheBlueMatt merged 1 commit into
lightningdevkit:mainfrom
TheBlueMatt:2025-05-0.1-upgrade-fail

Conversation

@TheBlueMatt

Copy link
Copy Markdown
Collaborator

In 93b4479 we fixed an issue which could cause a ChannelMonitorUpdate to get marked as blocked on itself, leading to an eventual force-closure.

One potential side-effect of that issue, however, is that any further ChannelMonitorUpdates to the same channel while it is blocked will not have any post-update actions processed (as there is a pending blocked ChannelMonitorUpdate sitting in the channel).

This can leave a dangling MonitorUpdateCompletionAction sitting around even after the channel is closed.

In 0.1, because ChannelMonitorUpdates to closed channels were finally fully tracked, we started enforcing that any post-update completion action we had on startup corresponded to a peer entry, while at the same time no longer creating peer entries just because we had serialized one in the data we were loading (only creating them if we had channel(s) or a ChannelMonitor).

This can cause some ChannelManager to no longer deserialize on 0.1 as we might have a left-over dangling
MonitorUpdateCompletionAction and will no longer always have a peer entry just because of it.

Here we fix this issue by specifically checking for dangling MonitorUpdateCompletionAction::PaymentClaim entries and dropping them if there is no corresponding channel or peer state entry. We only check for PaymentClaimed actions rather than allowing for any dangling actions as 93b4479 was only triggerable with MPP claims, so dangling
MonitorUpdateCompletionActions for forwarded payments should be exceedingly rare.

This also adds an upgrade test to test a slightly convoluted version of this scenario.

@ldk-reviews-bot

ldk-reviews-bot commented May 22, 2025

Copy link
Copy Markdown

I've assigned @joostjager 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.

Comment threadlightning/src/ln/channelmanager.rs Outdated
Comment threadlightning/src/ln/channelmanager.rs Outdated
let logger =
WithContext::from(&args.logger, Some(node_id), None, None);
log_error!(logger, "Got blocked actions {:?} without a per-peer-state for {}", actions, node_id);
return Err(DecodeError::InvalidValue);

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.

This is all new code that adds a new error condition.

Here we fix this issue by specifically checking for dangling
MonitorUpdateCompletionAction::PaymentClaim entries and dropping
them if there is no corresponding channel or peer state entry.

How are they dropped now, compared to how it happens in main?

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.

On main in the no-peer-state case we always return an error, now we sometimes do not and ignore the completion actions. For the have-peer-state case, we used to store the completion actions even if we have no channel or monitor the completion actions are based on, but now we do not.

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 see

@ldk-reviews-bot

Copy link
Copy Markdown

👋 The first review has been submitted!

Do you think this PR is ready for a second reviewer? If so, click here to assign a second reviewer.

log_error!(WithContext::from(&args.logger, Some(node_id), None, None), "Got blocked actions without a per-peer-state for {}", node_id);
return Err(DecodeError::InvalidValue);
for actions in monitor_update_blocked_actions.values() {
for action in actions.iter() {

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.

Repeated code block

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 wasn't quite sure it was worth DRYing it up any given its so short (and the comments are slightly different)

@TheBlueMatt
TheBlueMattforce-pushed the 2025-05-0.1-upgrade-fail branch from 7105605 to bb2e29eCompareMay 22, 2025 14:35
@TheBlueMatt

Copy link
Copy Markdown
CollaboratorAuthor

Oops, the first set of checks were too eager, luckily test_reload_mon_update_completion_actions caught it -

$ git diff-tree -U1 710560554 bb2e29e97
diff --git a/lightning/src/ln/channelmanager.rs b/lightning/src/ln/channelmanager.rs
index 84251ca33b..54e453d56b 100644
--- a/lightning/src/ln/channelmanager.rs+++ b/lightning/src/ln/channelmanager.rs@@ -14179,6 +14179,14 @@ where
let mut max_in_flight_update_id = 0;
+ let starting_len = $chan_in_flight_upds.len();
$chan_in_flight_upds.retain(|upd| upd.update_id > $monitor.get_latest_update_id());
+ if $chan_in_flight_upds.len() < starting_len {+ log_debug!(+ $logger,+ "{} ChannelMonitorUpdates completed after ChannelManager was last serialized",+ starting_len - $chan_in_flight_upds.len()+ );+ }
let funding_txo = $monitor.get_funding_txo();
for update in $chan_in_flight_upds.iter() {
- log_trace!($logger, "Replaying ChannelMonitorUpdate {} for {}channel {}",+ log_debug!($logger, "Replaying ChannelMonitorUpdate {} for {}channel {}",
update.update_id, $channel_info_log, &$monitor.channel_id());
@@ -14746,24 +14754,7 @@ where
}
- let peer_state = peer_state.lock().unwrap();- let channel_id_matches = |updates: &(_, Vec<ChannelMonitorUpdate>)| {- updates.1.iter().any(|update| update.channel_id == Some(*channel_id))- };- if !peer_state.in_flight_monitor_updates.values().any(channel_id_matches) {- // If there are no pending `ChannelMonitorUpdate`s for this channel but we- // have pending post-update actions, its possible that one was left over- // from pre-0.1 payment claims where MPP claims led to a channel blocked on- // itself and later `ChannelMonitorUpdate`s didn't get their post-update- // actions run.- // This should only have happened for `PaymentClaimed` post-update actions,- // which we check here.- for action in actions.iter() {- if let MonitorUpdateCompletionAction::PaymentClaimed { .. } = action {- } else {- let logger =- WithContext::from(&args.logger, Some(node_id), None, None);- log_error!(logger, "Got blocked actions {:?} without a per-peer-state for {}", actions, node_id);- return Err(DecodeError::InvalidValue);- }- }- }+ // Note that we may have a post-update action for a channel that has no pending+ // `ChannelMonitorUpdate`s, but unlike the no-peer-state case, it may simply be+ // because we had a `ChannelMonitorUpdate` complete after the last time this+ // `ChannelManager` was serialized. In that case, we'll run the post-update+ // actions as soon as we get going.
}

@codecov

codecovBot commented May 22, 2025

Copy link
Copy Markdown

Codecov Report

Attention: Patch coverage is 0% with 14 lines in your changes missing coverage. Please review.

Project coverage is 89.78%. Comparing base (02b5564) to head (bb2e29e).

Files with missing linesPatch %Lines
lightning/src/ln/channelmanager.rs0.00%14 Missing ⚠️
Additional details and impacted files
@@ Coverage Diff @@## main #3790 +/- ##
==========================================
- Coverage 89.80% 89.78% -0.03% 
==========================================
Files 159 159 Lines 128691 128703 +12 Branches 128691 128703 +12 ==========================================
- Hits 115573 115552 -21 - Misses 10446 10472 +26 - Partials 2672 2679 +7 

☔ 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.

Comment threadlightning/src/ln/channelmanager.rs Outdated
@ldk-reviews-bot

Copy link
Copy Markdown

✅ Added second reviewer: @valentinewallace

@TheBlueMatt
TheBlueMattforce-pushed the 2025-05-0.1-upgrade-fail branch from bb2e29e to 5ca1019CompareMay 22, 2025 16:04
@TheBlueMatt

TheBlueMatt commented May 22, 2025

Copy link
Copy Markdown
CollaboratorAuthor

rustfmt'd (and used match instead of if let)

In 93b4479 we fixed an issue which
could cause a `ChannelMonitorUpdate` to get marked as blocked on
itself, leading to an eventual force-closure.
One potential side-effect of that issue, however, is that any
further `ChannelMonitorUpdate`s to the same channel while it is
blocked will not have any post-update actions processed (as there
is a pending blocked `ChannelMonitorUpdate` sitting in the
channel).
This can leave a dangling `MonitorUpdateCompletionAction` sitting
around even after the channel is closed.
In 0.1, because `ChannelMonitorUpdate`s to closed channels were
finally fully tracked, we started enforcing that any post-update
completion action we had on startup corresponded to a peer entry,
while at the same time no longer creating peer entries just because
we had serialized one in the data we were loading (only creating
them if we had channel(s) or a `ChannelMonitor`).
This can cause some `ChannelManager` to no longer deserialize on
0.1 as we might have a left-over dangling
`MonitorUpdateCompletionAction` and will no longer always have a
peer entry just because of it.
Here we fix this issue by specifically checking for dangling
`MonitorUpdateCompletionAction::PaymentClaim` entries and dropping
them if there is no corresponding channel or peer state entry. We
only check for `PaymentClaimed` actions rather than allowing for
any dangling actions as 93b4479
was only triggerable with MPP claims, so dangling
`MonitorUpdateCompletionAction`s for forwarded payments should be
exceedingly rare.
This also adds an upgrade test to test a slightly convoluted
version of this scenario.
@TheBlueMatt
TheBlueMattforce-pushed the 2025-05-0.1-upgrade-fail branch from 5ca1019 to 70b5552CompareMay 22, 2025 16:05
return Err(DecodeError::InvalidValue);
for actions in monitor_update_blocked_actions.values() {
for action in actions.iter() {
if matches!(action, MonitorUpdateCompletionAction::PaymentClaimed { .. }) {

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.

Hm I meant !matches! to avoid the else.

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 liked the comment in the block that's ignoring the PaymentClaimeds. I mean I can move it outside if you prefer but I often like comments in empty if blocks 🤷‍♂️

@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.

Code LGTM


// Finally, reload the node in the latest LDK. This previously failed.
let config = test_default_channel_config();
reload_node!(nodes[3], config, &node_d_ser, &[&mon_ser], persister, chain_mon, node);

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.

Test LGTM, just took a slightly closer look. It also fails as expected on main.

@TheBlueMatt
TheBlueMatt merged commit 63a5e03 into lightningdevkit:mainMay 23, 2025
@TheBlueMattTheBlueMatt mentioned this pull request May 23, 2025
@TheBlueMatt

Copy link
Copy Markdown
CollaboratorAuthor

Backported in #3794

Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants

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