Fix race condition in async UtxoFuture resolution - #4348

Merged
TheBlueMatt merged 2 commits into
lightningdevkit:mainfrom
tnull:2026-01-fix-gossip-verification
Jan 29, 2026
Merged

Fix race condition in async UtxoFuture resolution #4348
TheBlueMatt merged 2 commits into
lightningdevkit:mainfrom
tnull:2026-01-fix-gossip-verification

Conversation

@tnull

@tnulltnull commented Jan 26, 2026

Copy link
Copy Markdown
Contributor

Fixes#4346.

Previously, we refactored the GossipVerifier to not require holding a
circular reference. As part of this, we moved to a model where the
UtxoFutures are now polled by the background processor which checks
for completion through get_and_clear_pending_msg_events.

However, as part of this refactor we introduced race-condition: as we
only held Weak references in PendingChecksContext and the
UtxoFuture was directly dropped by the GossipVerifier after calling
resolve, the actual data was dropped with the future and gone when the
background processor attempted to retrieve and apply it via
check_resolved_futures.

Here, we fix this issue by simply holding on to the stateArcs in a
separate pending_statesVec that is only pruned in
check_resolved_futures, ensuring any completed results are collected
first.

@ldk-reviews-bot

ldk-reviews-bot commented Jan 26, 2026

Copy link
Copy Markdown

👋 Thanks for assigning @TheBlueMatt 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.

@tnull
tnull marked this pull request as draft January 26, 2026 16:16
@tnull
tnullforce-pushed the 2026-01-fix-gossip-verification branch 2 times, most recently from 7c871f2 to 5c757b3CompareJanuary 26, 2026 16:18
Comment threadlightning/src/routing/utxo.rs Outdated
Comment threadlightning/src/routing/utxo.rs Outdated
Comment threadlightning/src/routing/utxo.rs Outdated
Comment threadlightning/src/routing/utxo.rs Outdated
Comment threadlightning/src/routing/utxo.rs Outdated
Comment threadlightning/src/routing/utxo.rs Outdated
Comment threadlightning/src/routing/utxo.rs Outdated
Comment threadlightning/src/routing/utxo.rs Outdated
@tnulltnull self-assigned this Jan 28, 2026
@tnulltnull moved this to Goal: Merge in Weekly GoalsJan 28, 2026
@tnull
tnullforce-pushed the 2026-01-fix-gossip-verification branch from 5c757b3 to e3bf6a5CompareJanuary 28, 2026 13:53
@tnull
tnull marked this pull request as ready for review January 28, 2026 13:54
@tnull
tnullforce-pushed the 2026-01-fix-gossip-verification branch 2 times, most recently from f672e27 to 7c08572CompareJanuary 28, 2026 13:57

@tnulltnull left a comment

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

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

Now switched to a much less invasive approach where we simply keep track of the pending future states Arcs in a separate Vec until the next call of check_resolved_futures, which will collect completed states and prune the data structures.

@tnulltnull added this to the 0.3 milestone Jan 28, 2026
@tnull

Copy link
Copy Markdown
ContributorAuthor

Whoops, seems there are still some more tests to fix, drafting again 🤭

@tnull
tnull marked this pull request as draft January 28, 2026 14:16
@tnull
tnullforce-pushed the 2026-01-fix-gossip-verification branch 2 times, most recently from 585fd56 to 0da4883CompareJanuary 28, 2026 14:27
@tnull

Copy link
Copy Markdown
ContributorAuthor

Whoops, seems there are still some more tests to fix, drafting again 🤭

... and they're passing again.

@tnull
tnull marked this pull request as ready for review January 28, 2026 14:28
@codecov

codecovBot commented Jan 28, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 91.66667% with 25 lines in your changes missing coverage. Please review.
✅ Project coverage is 86.10%. Comparing base (9e91b2e) to head (114f6b5).
⚠️ Report is 13 commits behind head on main.

Files with missing linesPatch %Lines
lightning/src/routing/utxo.rs91.66%23 Missing and 2 partials ⚠️
Additional details and impacted files
@@ Coverage Diff @@## main #4348 +/- ##
==========================================
+ Coverage 86.08% 86.10% +0.01% 
==========================================
Files 156 156 Lines 102428 102610 +182 Branches 102428 102610 +182 ==========================================
+ Hits 88179 88354 +175 - Misses 11754 11762 +8 + Partials 2495 2494 -1 
FlagCoverage Δ
tests86.10% <91.66%> (+0.01%)⬆️

Flags with carried forward coverage won't be shown. Click here to find out more.

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

@TheBlueMattTheBlueMatt left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Bleh, don't love the extra vec but you're right that its much simpler and probably worth it. Feel free to squash (maybe with a few more rustfmt'isms fixed) and let's land this.

Comment threadlightning/src/routing/utxo.rs Outdated
Comment threadlightning/src/routing/utxo.rs Outdated
@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.

Signed-off-by: Elias Rohrer <dev@tnull.de>
Previously, we refactored the `GossipVerifier` to not require holding a
circular reference. As part of this, we moved to a model where the
`UtxoFuture`s are now polled by the background processor which checks
for completion through `get_and_clear_pending_msg_events`.
However, as part of this refactor we introduced race-condition: as we
only held `Weak` references in `PendingChecksContext` and the
`UtxoFuture` was directly dropped by the `GossipVerifier` after calling
`resolve`, the actual data was dropped with the future and gone when the
background processor attempted to retrieve and apply it via
`check_resolved_futures`.
Here, we fix this issue by simply holding on to the `state` `Arc`s in a
separate `pending_states` `Vec` that is only pruned in
`check_resolved_futures`, ensuring any completed results are collected
first.
Signed-off-by: Elias Rohrer <dev@tnull.de>
@tnull
tnullforce-pushed the 2026-01-fix-gossip-verification branch from 0da4883 to 114f6b5CompareJanuary 29, 2026 08:11
@tnull

Copy link
Copy Markdown
ContributorAuthor

Bleh, don't love the extra vec but you're right that its much simpler and probably worth it. Feel free to squash (maybe with a few more rustfmt'isms fixed) and let's land this.

Squashed fixup, and included the following changes:

diff --git a/lightning/src/routing/utxo.rs b/lightning/src/routing/utxo.rs
index f3351aa0a..ab653b1ea 100644
--- a/lightning/src/routing/utxo.rs+++ b/lightning/src/routing/utxo.rs@@ -763,22 +763,16 @@ mod tests {
network_graph.read_only().channels().get(&scid).unwrap();
- assert!(network_graph- .read_only()- .nodes()- .get(&node_id_1)- .unwrap()- .announcement_info- .is_none());+ #[rustfmt::skip]+ let is_node_a_announced = network_graph.read_only().nodes().get(&node_id_1).unwrap()+ .announcement_info.is_some();+ assert!(!is_node_a_announced);
network_graph.update_node_from_announcement(&node_a_announce).unwrap();
network_graph.update_node_from_announcement(&node_b_announce).unwrap();
- assert!(network_graph- .read_only()- .nodes()- .get(&node_id_1)- .unwrap()- .announcement_info- .is_some());+ #[rustfmt::skip]+ let is_node_a_announced = network_graph.read_only().nodes().get(&node_id_1).unwrap()+ .announcement_info.is_some();+ assert!(is_node_a_announced);
}
@@ -1023,15 +1017,9 @@ mod tests {
assert!(!notifier_b.notify_pending());
network_graph.pending_checks.check_resolved_futures(&network_graph);
- assert!(!network_graph- .read_only()- .channels()- .get(&scid)- .unwrap()- .announcement_message- .as_ref()- .unwrap()- .contents- .features- .supports_unknown_test_feature());+ #[rustfmt::skip]+ let is_test_feature_set =+ network_graph.read_only().channels().get(&scid).unwrap().announcement_message+ .as_ref().unwrap().contents.features.supports_unknown_test_feature();+ assert!(!is_test_feature_set);
}

@TheBlueMattTheBlueMatt left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

thanks. just gonna land this it ended up being relatively trivial.


assert!(network_graph.read_only().nodes().get(&valid_announcement.contents.node_id_1)
.unwrap().announcement_info.is_none());
#[rustfmt::skip]

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Not really sure that rustfmt::skip is the right fix for these kinds of cases. We can take intermediate variables that clean up rustfmt's crap and improve readability while we're at it.

@TheBlueMatt
TheBlueMatt merged commit a58e7f0 into lightningdevkit:mainJan 29, 2026
20 of 21 checks passed
@github-project-automationgithub-project-automationBot moved this from Goal: Merge to Done in Weekly GoalsJan 29, 2026
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

Status: Done

Development

Successfully merging this pull request may close these issues.

gossip doesnt resolve async

3 participants

@tnull@ldk-reviews-bot@TheBlueMatt
, '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

Fix race condition in async UtxoFuture resolution - #4348

Merged
TheBlueMatt merged 2 commits into
lightningdevkit:mainfrom
tnull:2026-01-fix-gossip-verification
Jan 29, 2026
Merged

Fix race condition in async UtxoFuture resolution #4348
TheBlueMatt merged 2 commits into
lightningdevkit:mainfrom
tnull:2026-01-fix-gossip-verification

Conversation

@tnull

@tnulltnull commented Jan 26, 2026

Copy link
Copy Markdown
Contributor

Fixes#4346.

Previously, we refactored the GossipVerifier to not require holding a
circular reference. As part of this, we moved to a model where the
UtxoFutures are now polled by the background processor which checks
for completion through get_and_clear_pending_msg_events.

However, as part of this refactor we introduced race-condition: as we
only held Weak references in PendingChecksContext and the
UtxoFuture was directly dropped by the GossipVerifier after calling
resolve, the actual data was dropped with the future and gone when the
background processor attempted to retrieve and apply it via
check_resolved_futures.

Here, we fix this issue by simply holding on to the stateArcs in a
separate pending_statesVec that is only pruned in
check_resolved_futures, ensuring any completed results are collected
first.

@ldk-reviews-bot

ldk-reviews-bot commented Jan 26, 2026

Copy link
Copy Markdown

👋 Thanks for assigning @TheBlueMatt 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.

@tnull
tnull marked this pull request as draft January 26, 2026 16:16
@tnull
tnullforce-pushed the 2026-01-fix-gossip-verification branch 2 times, most recently from 7c871f2 to 5c757b3CompareJanuary 26, 2026 16:18
Comment threadlightning/src/routing/utxo.rs Outdated
Comment threadlightning/src/routing/utxo.rs Outdated
Comment threadlightning/src/routing/utxo.rs Outdated
Comment threadlightning/src/routing/utxo.rs Outdated
Comment threadlightning/src/routing/utxo.rs Outdated
Comment threadlightning/src/routing/utxo.rs Outdated
Comment threadlightning/src/routing/utxo.rs Outdated
Comment threadlightning/src/routing/utxo.rs Outdated
@tnulltnull self-assigned this Jan 28, 2026
@tnulltnull moved this to Goal: Merge in Weekly GoalsJan 28, 2026
@tnull
tnullforce-pushed the 2026-01-fix-gossip-verification branch from 5c757b3 to e3bf6a5CompareJanuary 28, 2026 13:53
@tnull
tnull marked this pull request as ready for review January 28, 2026 13:54
@tnull
tnullforce-pushed the 2026-01-fix-gossip-verification branch 2 times, most recently from f672e27 to 7c08572CompareJanuary 28, 2026 13:57

@tnulltnull left a comment

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

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

Now switched to a much less invasive approach where we simply keep track of the pending future states Arcs in a separate Vec until the next call of check_resolved_futures, which will collect completed states and prune the data structures.

@tnulltnull added this to the 0.3 milestone Jan 28, 2026
@tnull

Copy link
Copy Markdown
ContributorAuthor

Whoops, seems there are still some more tests to fix, drafting again 🤭

@tnull
tnull marked this pull request as draft January 28, 2026 14:16
@tnull
tnullforce-pushed the 2026-01-fix-gossip-verification branch 2 times, most recently from 585fd56 to 0da4883CompareJanuary 28, 2026 14:27
@tnull

Copy link
Copy Markdown
ContributorAuthor

Whoops, seems there are still some more tests to fix, drafting again 🤭

... and they're passing again.

@tnull
tnull marked this pull request as ready for review January 28, 2026 14:28
@codecov

codecovBot commented Jan 28, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 91.66667% with 25 lines in your changes missing coverage. Please review.
✅ Project coverage is 86.10%. Comparing base (9e91b2e) to head (114f6b5).
⚠️ Report is 13 commits behind head on main.

Files with missing linesPatch %Lines
lightning/src/routing/utxo.rs91.66%23 Missing and 2 partials ⚠️
Additional details and impacted files
@@ Coverage Diff @@## main #4348 +/- ##
==========================================
+ Coverage 86.08% 86.10% +0.01% 
==========================================
Files 156 156 Lines 102428 102610 +182 Branches 102428 102610 +182 ==========================================
+ Hits 88179 88354 +175 - Misses 11754 11762 +8 + Partials 2495 2494 -1 
FlagCoverage Δ
tests86.10% <91.66%> (+0.01%)⬆️

Flags with carried forward coverage won't be shown. Click here to find out more.

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

@TheBlueMattTheBlueMatt left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Bleh, don't love the extra vec but you're right that its much simpler and probably worth it. Feel free to squash (maybe with a few more rustfmt'isms fixed) and let's land this.

Comment threadlightning/src/routing/utxo.rs Outdated
Comment threadlightning/src/routing/utxo.rs Outdated
@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.

Signed-off-by: Elias Rohrer <dev@tnull.de>
Previously, we refactored the `GossipVerifier` to not require holding a
circular reference. As part of this, we moved to a model where the
`UtxoFuture`s are now polled by the background processor which checks
for completion through `get_and_clear_pending_msg_events`.
However, as part of this refactor we introduced race-condition: as we
only held `Weak` references in `PendingChecksContext` and the
`UtxoFuture` was directly dropped by the `GossipVerifier` after calling
`resolve`, the actual data was dropped with the future and gone when the
background processor attempted to retrieve and apply it via
`check_resolved_futures`.
Here, we fix this issue by simply holding on to the `state` `Arc`s in a
separate `pending_states` `Vec` that is only pruned in
`check_resolved_futures`, ensuring any completed results are collected
first.
Signed-off-by: Elias Rohrer <dev@tnull.de>
@tnull
tnullforce-pushed the 2026-01-fix-gossip-verification branch from 0da4883 to 114f6b5CompareJanuary 29, 2026 08:11
@tnull

Copy link
Copy Markdown
ContributorAuthor

Bleh, don't love the extra vec but you're right that its much simpler and probably worth it. Feel free to squash (maybe with a few more rustfmt'isms fixed) and let's land this.

Squashed fixup, and included the following changes:

diff --git a/lightning/src/routing/utxo.rs b/lightning/src/routing/utxo.rs
index f3351aa0a..ab653b1ea 100644
--- a/lightning/src/routing/utxo.rs+++ b/lightning/src/routing/utxo.rs@@ -763,22 +763,16 @@ mod tests {
network_graph.read_only().channels().get(&scid).unwrap();
- assert!(network_graph- .read_only()- .nodes()- .get(&node_id_1)- .unwrap()- .announcement_info- .is_none());+ #[rustfmt::skip]+ let is_node_a_announced = network_graph.read_only().nodes().get(&node_id_1).unwrap()+ .announcement_info.is_some();+ assert!(!is_node_a_announced);
network_graph.update_node_from_announcement(&node_a_announce).unwrap();
network_graph.update_node_from_announcement(&node_b_announce).unwrap();
- assert!(network_graph- .read_only()- .nodes()- .get(&node_id_1)- .unwrap()- .announcement_info- .is_some());+ #[rustfmt::skip]+ let is_node_a_announced = network_graph.read_only().nodes().get(&node_id_1).unwrap()+ .announcement_info.is_some();+ assert!(is_node_a_announced);
}
@@ -1023,15 +1017,9 @@ mod tests {
assert!(!notifier_b.notify_pending());
network_graph.pending_checks.check_resolved_futures(&network_graph);
- assert!(!network_graph- .read_only()- .channels()- .get(&scid)- .unwrap()- .announcement_message- .as_ref()- .unwrap()- .contents- .features- .supports_unknown_test_feature());+ #[rustfmt::skip]+ let is_test_feature_set =+ network_graph.read_only().channels().get(&scid).unwrap().announcement_message+ .as_ref().unwrap().contents.features.supports_unknown_test_feature();+ assert!(!is_test_feature_set);
}

@TheBlueMattTheBlueMatt left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

thanks. just gonna land this it ended up being relatively trivial.


assert!(network_graph.read_only().nodes().get(&valid_announcement.contents.node_id_1)
.unwrap().announcement_info.is_none());
#[rustfmt::skip]

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Not really sure that rustfmt::skip is the right fix for these kinds of cases. We can take intermediate variables that clean up rustfmt's crap and improve readability while we're at it.

@TheBlueMatt
TheBlueMatt merged commit a58e7f0 into lightningdevkit:mainJan 29, 2026
20 of 21 checks passed
@github-project-automationgithub-project-automationBot moved this from Goal: Merge to Done in Weekly GoalsJan 29, 2026
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

Status: Done

Development

Successfully merging this pull request may close these issues.

gossip doesnt resolve async

3 participants

@tnull@ldk-reviews-bot@TheBlueMatt
, '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

Fix race condition in async UtxoFuture resolution - #4348

Merged
TheBlueMatt merged 2 commits into
lightningdevkit:mainfrom
tnull:2026-01-fix-gossip-verification
Jan 29, 2026
Merged

Fix race condition in async UtxoFuture resolution #4348
TheBlueMatt merged 2 commits into
lightningdevkit:mainfrom
tnull:2026-01-fix-gossip-verification

Conversation

@tnull

@tnulltnull commented Jan 26, 2026

Copy link
Copy Markdown
Contributor

Fixes#4346.

Previously, we refactored the GossipVerifier to not require holding a
circular reference. As part of this, we moved to a model where the
UtxoFutures are now polled by the background processor which checks
for completion through get_and_clear_pending_msg_events.

However, as part of this refactor we introduced race-condition: as we
only held Weak references in PendingChecksContext and the
UtxoFuture was directly dropped by the GossipVerifier after calling
resolve, the actual data was dropped with the future and gone when the
background processor attempted to retrieve and apply it via
check_resolved_futures.

Here, we fix this issue by simply holding on to the stateArcs in a
separate pending_statesVec that is only pruned in
check_resolved_futures, ensuring any completed results are collected
first.

@ldk-reviews-bot

ldk-reviews-bot commented Jan 26, 2026

Copy link
Copy Markdown

👋 Thanks for assigning @TheBlueMatt 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.

@tnull
tnull marked this pull request as draft January 26, 2026 16:16
@tnull
tnullforce-pushed the 2026-01-fix-gossip-verification branch 2 times, most recently from 7c871f2 to 5c757b3CompareJanuary 26, 2026 16:18
Comment threadlightning/src/routing/utxo.rs Outdated
Comment threadlightning/src/routing/utxo.rs Outdated
Comment threadlightning/src/routing/utxo.rs Outdated
Comment threadlightning/src/routing/utxo.rs Outdated
Comment threadlightning/src/routing/utxo.rs Outdated
Comment threadlightning/src/routing/utxo.rs Outdated
Comment threadlightning/src/routing/utxo.rs Outdated
Comment threadlightning/src/routing/utxo.rs Outdated
@tnulltnull self-assigned this Jan 28, 2026
@tnulltnull moved this to Goal: Merge in Weekly GoalsJan 28, 2026
@tnull
tnullforce-pushed the 2026-01-fix-gossip-verification branch from 5c757b3 to e3bf6a5CompareJanuary 28, 2026 13:53
@tnull
tnull marked this pull request as ready for review January 28, 2026 13:54
@tnull
tnullforce-pushed the 2026-01-fix-gossip-verification branch 2 times, most recently from f672e27 to 7c08572CompareJanuary 28, 2026 13:57

@tnulltnull left a comment

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

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

Now switched to a much less invasive approach where we simply keep track of the pending future states Arcs in a separate Vec until the next call of check_resolved_futures, which will collect completed states and prune the data structures.

@tnulltnull added this to the 0.3 milestone Jan 28, 2026
@tnull

Copy link
Copy Markdown
ContributorAuthor

Whoops, seems there are still some more tests to fix, drafting again 🤭

@tnull
tnull marked this pull request as draft January 28, 2026 14:16
@tnull
tnullforce-pushed the 2026-01-fix-gossip-verification branch 2 times, most recently from 585fd56 to 0da4883CompareJanuary 28, 2026 14:27
@tnull

Copy link
Copy Markdown
ContributorAuthor

Whoops, seems there are still some more tests to fix, drafting again 🤭

... and they're passing again.

@tnull
tnull marked this pull request as ready for review January 28, 2026 14:28
@codecov

codecovBot commented Jan 28, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 91.66667% with 25 lines in your changes missing coverage. Please review.
✅ Project coverage is 86.10%. Comparing base (9e91b2e) to head (114f6b5).
⚠️ Report is 13 commits behind head on main.

Files with missing linesPatch %Lines
lightning/src/routing/utxo.rs91.66%23 Missing and 2 partials ⚠️
Additional details and impacted files
@@ Coverage Diff @@## main #4348 +/- ##
==========================================
+ Coverage 86.08% 86.10% +0.01% 
==========================================
Files 156 156 Lines 102428 102610 +182 Branches 102428 102610 +182 ==========================================
+ Hits 88179 88354 +175 - Misses 11754 11762 +8 + Partials 2495 2494 -1 
FlagCoverage Δ
tests86.10% <91.66%> (+0.01%)⬆️

Flags with carried forward coverage won't be shown. Click here to find out more.

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

@TheBlueMattTheBlueMatt left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Bleh, don't love the extra vec but you're right that its much simpler and probably worth it. Feel free to squash (maybe with a few more rustfmt'isms fixed) and let's land this.

Comment threadlightning/src/routing/utxo.rs Outdated
Comment threadlightning/src/routing/utxo.rs Outdated
@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.

Signed-off-by: Elias Rohrer <dev@tnull.de>
Previously, we refactored the `GossipVerifier` to not require holding a
circular reference. As part of this, we moved to a model where the
`UtxoFuture`s are now polled by the background processor which checks
for completion through `get_and_clear_pending_msg_events`.
However, as part of this refactor we introduced race-condition: as we
only held `Weak` references in `PendingChecksContext` and the
`UtxoFuture` was directly dropped by the `GossipVerifier` after calling
`resolve`, the actual data was dropped with the future and gone when the
background processor attempted to retrieve and apply it via
`check_resolved_futures`.
Here, we fix this issue by simply holding on to the `state` `Arc`s in a
separate `pending_states` `Vec` that is only pruned in
`check_resolved_futures`, ensuring any completed results are collected
first.
Signed-off-by: Elias Rohrer <dev@tnull.de>
@tnull
tnullforce-pushed the 2026-01-fix-gossip-verification branch from 0da4883 to 114f6b5CompareJanuary 29, 2026 08:11
@tnull

Copy link
Copy Markdown
ContributorAuthor

Bleh, don't love the extra vec but you're right that its much simpler and probably worth it. Feel free to squash (maybe with a few more rustfmt'isms fixed) and let's land this.

Squashed fixup, and included the following changes:

diff --git a/lightning/src/routing/utxo.rs b/lightning/src/routing/utxo.rs
index f3351aa0a..ab653b1ea 100644
--- a/lightning/src/routing/utxo.rs+++ b/lightning/src/routing/utxo.rs@@ -763,22 +763,16 @@ mod tests {
network_graph.read_only().channels().get(&scid).unwrap();
- assert!(network_graph- .read_only()- .nodes()- .get(&node_id_1)- .unwrap()- .announcement_info- .is_none());+ #[rustfmt::skip]+ let is_node_a_announced = network_graph.read_only().nodes().get(&node_id_1).unwrap()+ .announcement_info.is_some();+ assert!(!is_node_a_announced);
network_graph.update_node_from_announcement(&node_a_announce).unwrap();
network_graph.update_node_from_announcement(&node_b_announce).unwrap();
- assert!(network_graph- .read_only()- .nodes()- .get(&node_id_1)- .unwrap()- .announcement_info- .is_some());+ #[rustfmt::skip]+ let is_node_a_announced = network_graph.read_only().nodes().get(&node_id_1).unwrap()+ .announcement_info.is_some();+ assert!(is_node_a_announced);
}
@@ -1023,15 +1017,9 @@ mod tests {
assert!(!notifier_b.notify_pending());
network_graph.pending_checks.check_resolved_futures(&network_graph);
- assert!(!network_graph- .read_only()- .channels()- .get(&scid)- .unwrap()- .announcement_message- .as_ref()- .unwrap()- .contents- .features- .supports_unknown_test_feature());+ #[rustfmt::skip]+ let is_test_feature_set =+ network_graph.read_only().channels().get(&scid).unwrap().announcement_message+ .as_ref().unwrap().contents.features.supports_unknown_test_feature();+ assert!(!is_test_feature_set);
}

@TheBlueMattTheBlueMatt left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

thanks. just gonna land this it ended up being relatively trivial.


assert!(network_graph.read_only().nodes().get(&valid_announcement.contents.node_id_1)
.unwrap().announcement_info.is_none());
#[rustfmt::skip]

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Not really sure that rustfmt::skip is the right fix for these kinds of cases. We can take intermediate variables that clean up rustfmt's crap and improve readability while we're at it.

@TheBlueMatt
TheBlueMatt merged commit a58e7f0 into lightningdevkit:mainJan 29, 2026
20 of 21 checks passed
@github-project-automationgithub-project-automationBot moved this from Goal: Merge to Done in Weekly GoalsJan 29, 2026
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

Status: Done

Development

Successfully merging this pull request may close these issues.

gossip doesnt resolve async

3 participants

@tnull@ldk-reviews-bot@TheBlueMatt
, '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

Fix race condition in async UtxoFuture resolution - #4348

Merged
TheBlueMatt merged 2 commits into
lightningdevkit:mainfrom
tnull:2026-01-fix-gossip-verification
Jan 29, 2026
Merged

Fix race condition in async UtxoFuture resolution #4348
TheBlueMatt merged 2 commits into
lightningdevkit:mainfrom
tnull:2026-01-fix-gossip-verification

Conversation

@tnull

@tnulltnull commented Jan 26, 2026

Copy link
Copy Markdown
Contributor

Fixes#4346.

Previously, we refactored the GossipVerifier to not require holding a
circular reference. As part of this, we moved to a model where the
UtxoFutures are now polled by the background processor which checks
for completion through get_and_clear_pending_msg_events.

However, as part of this refactor we introduced race-condition: as we
only held Weak references in PendingChecksContext and the
UtxoFuture was directly dropped by the GossipVerifier after calling
resolve, the actual data was dropped with the future and gone when the
background processor attempted to retrieve and apply it via
check_resolved_futures.

Here, we fix this issue by simply holding on to the stateArcs in a
separate pending_statesVec that is only pruned in
check_resolved_futures, ensuring any completed results are collected
first.

@ldk-reviews-bot

ldk-reviews-bot commented Jan 26, 2026

Copy link
Copy Markdown

👋 Thanks for assigning @TheBlueMatt 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.

@tnull
tnull marked this pull request as draft January 26, 2026 16:16
@tnull
tnullforce-pushed the 2026-01-fix-gossip-verification branch 2 times, most recently from 7c871f2 to 5c757b3CompareJanuary 26, 2026 16:18
Comment threadlightning/src/routing/utxo.rs Outdated
Comment threadlightning/src/routing/utxo.rs Outdated
Comment threadlightning/src/routing/utxo.rs Outdated
Comment threadlightning/src/routing/utxo.rs Outdated
Comment threadlightning/src/routing/utxo.rs Outdated
Comment threadlightning/src/routing/utxo.rs Outdated
Comment threadlightning/src/routing/utxo.rs Outdated
Comment threadlightning/src/routing/utxo.rs Outdated
@tnulltnull self-assigned this Jan 28, 2026
@tnulltnull moved this to Goal: Merge in Weekly GoalsJan 28, 2026
@tnull
tnullforce-pushed the 2026-01-fix-gossip-verification branch from 5c757b3 to e3bf6a5CompareJanuary 28, 2026 13:53
@tnull
tnull marked this pull request as ready for review January 28, 2026 13:54
@tnull
tnullforce-pushed the 2026-01-fix-gossip-verification branch 2 times, most recently from f672e27 to 7c08572CompareJanuary 28, 2026 13:57

@tnulltnull left a comment

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

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

Now switched to a much less invasive approach where we simply keep track of the pending future states Arcs in a separate Vec until the next call of check_resolved_futures, which will collect completed states and prune the data structures.

@tnulltnull added this to the 0.3 milestone Jan 28, 2026
@tnull

Copy link
Copy Markdown
ContributorAuthor

Whoops, seems there are still some more tests to fix, drafting again 🤭

@tnull
tnull marked this pull request as draft January 28, 2026 14:16
@tnull
tnullforce-pushed the 2026-01-fix-gossip-verification branch 2 times, most recently from 585fd56 to 0da4883CompareJanuary 28, 2026 14:27
@tnull

Copy link
Copy Markdown
ContributorAuthor

Whoops, seems there are still some more tests to fix, drafting again 🤭

... and they're passing again.

@tnull
tnull marked this pull request as ready for review January 28, 2026 14:28
@codecov

codecovBot commented Jan 28, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 91.66667% with 25 lines in your changes missing coverage. Please review.
✅ Project coverage is 86.10%. Comparing base (9e91b2e) to head (114f6b5).
⚠️ Report is 13 commits behind head on main.

Files with missing linesPatch %Lines
lightning/src/routing/utxo.rs91.66%23 Missing and 2 partials ⚠️
Additional details and impacted files
@@ Coverage Diff @@## main #4348 +/- ##
==========================================
+ Coverage 86.08% 86.10% +0.01% 
==========================================
Files 156 156 Lines 102428 102610 +182 Branches 102428 102610 +182 ==========================================
+ Hits 88179 88354 +175 - Misses 11754 11762 +8 + Partials 2495 2494 -1 
FlagCoverage Δ
tests86.10% <91.66%> (+0.01%)⬆️

Flags with carried forward coverage won't be shown. Click here to find out more.

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

@TheBlueMattTheBlueMatt left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Bleh, don't love the extra vec but you're right that its much simpler and probably worth it. Feel free to squash (maybe with a few more rustfmt'isms fixed) and let's land this.

Comment threadlightning/src/routing/utxo.rs Outdated
Comment threadlightning/src/routing/utxo.rs Outdated
@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.

Signed-off-by: Elias Rohrer <dev@tnull.de>
Previously, we refactored the `GossipVerifier` to not require holding a
circular reference. As part of this, we moved to a model where the
`UtxoFuture`s are now polled by the background processor which checks
for completion through `get_and_clear_pending_msg_events`.
However, as part of this refactor we introduced race-condition: as we
only held `Weak` references in `PendingChecksContext` and the
`UtxoFuture` was directly dropped by the `GossipVerifier` after calling
`resolve`, the actual data was dropped with the future and gone when the
background processor attempted to retrieve and apply it via
`check_resolved_futures`.
Here, we fix this issue by simply holding on to the `state` `Arc`s in a
separate `pending_states` `Vec` that is only pruned in
`check_resolved_futures`, ensuring any completed results are collected
first.
Signed-off-by: Elias Rohrer <dev@tnull.de>
@tnull
tnullforce-pushed the 2026-01-fix-gossip-verification branch from 0da4883 to 114f6b5CompareJanuary 29, 2026 08:11
@tnull

Copy link
Copy Markdown
ContributorAuthor

Bleh, don't love the extra vec but you're right that its much simpler and probably worth it. Feel free to squash (maybe with a few more rustfmt'isms fixed) and let's land this.

Squashed fixup, and included the following changes:

diff --git a/lightning/src/routing/utxo.rs b/lightning/src/routing/utxo.rs
index f3351aa0a..ab653b1ea 100644
--- a/lightning/src/routing/utxo.rs+++ b/lightning/src/routing/utxo.rs@@ -763,22 +763,16 @@ mod tests {
network_graph.read_only().channels().get(&scid).unwrap();
- assert!(network_graph- .read_only()- .nodes()- .get(&node_id_1)- .unwrap()- .announcement_info- .is_none());+ #[rustfmt::skip]+ let is_node_a_announced = network_graph.read_only().nodes().get(&node_id_1).unwrap()+ .announcement_info.is_some();+ assert!(!is_node_a_announced);
network_graph.update_node_from_announcement(&node_a_announce).unwrap();
network_graph.update_node_from_announcement(&node_b_announce).unwrap();
- assert!(network_graph- .read_only()- .nodes()- .get(&node_id_1)- .unwrap()- .announcement_info- .is_some());+ #[rustfmt::skip]+ let is_node_a_announced = network_graph.read_only().nodes().get(&node_id_1).unwrap()+ .announcement_info.is_some();+ assert!(is_node_a_announced);
}
@@ -1023,15 +1017,9 @@ mod tests {
assert!(!notifier_b.notify_pending());
network_graph.pending_checks.check_resolved_futures(&network_graph);
- assert!(!network_graph- .read_only()- .channels()- .get(&scid)- .unwrap()- .announcement_message- .as_ref()- .unwrap()- .contents- .features- .supports_unknown_test_feature());+ #[rustfmt::skip]+ let is_test_feature_set =+ network_graph.read_only().channels().get(&scid).unwrap().announcement_message+ .as_ref().unwrap().contents.features.supports_unknown_test_feature();+ assert!(!is_test_feature_set);
}

@TheBlueMattTheBlueMatt left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

thanks. just gonna land this it ended up being relatively trivial.


assert!(network_graph.read_only().nodes().get(&valid_announcement.contents.node_id_1)
.unwrap().announcement_info.is_none());
#[rustfmt::skip]

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Not really sure that rustfmt::skip is the right fix for these kinds of cases. We can take intermediate variables that clean up rustfmt's crap and improve readability while we're at it.

@TheBlueMatt
TheBlueMatt merged commit a58e7f0 into lightningdevkit:mainJan 29, 2026
20 of 21 checks passed
@github-project-automationgithub-project-automationBot moved this from Goal: Merge to Done in Weekly GoalsJan 29, 2026
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

Status: Done

Development

Successfully merging this pull request may close these issues.

gossip doesnt resolve async

3 participants

@tnull@ldk-reviews-bot@TheBlueMatt
, '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

Fix race condition in async UtxoFuture resolution - #4348

Merged
TheBlueMatt merged 2 commits into
lightningdevkit:mainfrom
tnull:2026-01-fix-gossip-verification
Jan 29, 2026
Merged

Fix race condition in async UtxoFuture resolution #4348
TheBlueMatt merged 2 commits into
lightningdevkit:mainfrom
tnull:2026-01-fix-gossip-verification

Conversation

@tnull

@tnulltnull commented Jan 26, 2026

Copy link
Copy Markdown
Contributor

Fixes#4346.

Previously, we refactored the GossipVerifier to not require holding a
circular reference. As part of this, we moved to a model where the
UtxoFutures are now polled by the background processor which checks
for completion through get_and_clear_pending_msg_events.

However, as part of this refactor we introduced race-condition: as we
only held Weak references in PendingChecksContext and the
UtxoFuture was directly dropped by the GossipVerifier after calling
resolve, the actual data was dropped with the future and gone when the
background processor attempted to retrieve and apply it via
check_resolved_futures.

Here, we fix this issue by simply holding on to the stateArcs in a
separate pending_statesVec that is only pruned in
check_resolved_futures, ensuring any completed results are collected
first.

@ldk-reviews-bot

ldk-reviews-bot commented Jan 26, 2026

Copy link
Copy Markdown

👋 Thanks for assigning @TheBlueMatt 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.

@tnull
tnull marked this pull request as draft January 26, 2026 16:16
@tnull
tnullforce-pushed the 2026-01-fix-gossip-verification branch 2 times, most recently from 7c871f2 to 5c757b3CompareJanuary 26, 2026 16:18
Comment threadlightning/src/routing/utxo.rs Outdated
Comment threadlightning/src/routing/utxo.rs Outdated
Comment threadlightning/src/routing/utxo.rs Outdated
Comment threadlightning/src/routing/utxo.rs Outdated
Comment threadlightning/src/routing/utxo.rs Outdated
Comment threadlightning/src/routing/utxo.rs Outdated
Comment threadlightning/src/routing/utxo.rs Outdated
Comment threadlightning/src/routing/utxo.rs Outdated
@tnulltnull self-assigned this Jan 28, 2026
@tnulltnull moved this to Goal: Merge in Weekly GoalsJan 28, 2026
@tnull
tnullforce-pushed the 2026-01-fix-gossip-verification branch from 5c757b3 to e3bf6a5CompareJanuary 28, 2026 13:53
@tnull
tnull marked this pull request as ready for review January 28, 2026 13:54
@tnull
tnullforce-pushed the 2026-01-fix-gossip-verification branch 2 times, most recently from f672e27 to 7c08572CompareJanuary 28, 2026 13:57

@tnulltnull left a comment

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

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

Now switched to a much less invasive approach where we simply keep track of the pending future states Arcs in a separate Vec until the next call of check_resolved_futures, which will collect completed states and prune the data structures.

@tnulltnull added this to the 0.3 milestone Jan 28, 2026
@tnull

Copy link
Copy Markdown
ContributorAuthor

Whoops, seems there are still some more tests to fix, drafting again 🤭

@tnull
tnull marked this pull request as draft January 28, 2026 14:16
@tnull
tnullforce-pushed the 2026-01-fix-gossip-verification branch 2 times, most recently from 585fd56 to 0da4883CompareJanuary 28, 2026 14:27
@tnull

Copy link
Copy Markdown
ContributorAuthor

Whoops, seems there are still some more tests to fix, drafting again 🤭

... and they're passing again.

@tnull
tnull marked this pull request as ready for review January 28, 2026 14:28
@codecov

codecovBot commented Jan 28, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 91.66667% with 25 lines in your changes missing coverage. Please review.
✅ Project coverage is 86.10%. Comparing base (9e91b2e) to head (114f6b5).
⚠️ Report is 13 commits behind head on main.

Files with missing linesPatch %Lines
lightning/src/routing/utxo.rs91.66%23 Missing and 2 partials ⚠️
Additional details and impacted files
@@ Coverage Diff @@## main #4348 +/- ##
==========================================
+ Coverage 86.08% 86.10% +0.01% 
==========================================
Files 156 156 Lines 102428 102610 +182 Branches 102428 102610 +182 ==========================================
+ Hits 88179 88354 +175 - Misses 11754 11762 +8 + Partials 2495 2494 -1 
FlagCoverage Δ
tests86.10% <91.66%> (+0.01%)⬆️

Flags with carried forward coverage won't be shown. Click here to find out more.

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

@TheBlueMattTheBlueMatt left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Bleh, don't love the extra vec but you're right that its much simpler and probably worth it. Feel free to squash (maybe with a few more rustfmt'isms fixed) and let's land this.

Comment threadlightning/src/routing/utxo.rs Outdated
Comment threadlightning/src/routing/utxo.rs Outdated
@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.

Signed-off-by: Elias Rohrer <dev@tnull.de>
Previously, we refactored the `GossipVerifier` to not require holding a
circular reference. As part of this, we moved to a model where the
`UtxoFuture`s are now polled by the background processor which checks
for completion through `get_and_clear_pending_msg_events`.
However, as part of this refactor we introduced race-condition: as we
only held `Weak` references in `PendingChecksContext` and the
`UtxoFuture` was directly dropped by the `GossipVerifier` after calling
`resolve`, the actual data was dropped with the future and gone when the
background processor attempted to retrieve and apply it via
`check_resolved_futures`.
Here, we fix this issue by simply holding on to the `state` `Arc`s in a
separate `pending_states` `Vec` that is only pruned in
`check_resolved_futures`, ensuring any completed results are collected
first.
Signed-off-by: Elias Rohrer <dev@tnull.de>
@tnull
tnullforce-pushed the 2026-01-fix-gossip-verification branch from 0da4883 to 114f6b5CompareJanuary 29, 2026 08:11
@tnull

Copy link
Copy Markdown
ContributorAuthor

Bleh, don't love the extra vec but you're right that its much simpler and probably worth it. Feel free to squash (maybe with a few more rustfmt'isms fixed) and let's land this.

Squashed fixup, and included the following changes:

diff --git a/lightning/src/routing/utxo.rs b/lightning/src/routing/utxo.rs
index f3351aa0a..ab653b1ea 100644
--- a/lightning/src/routing/utxo.rs+++ b/lightning/src/routing/utxo.rs@@ -763,22 +763,16 @@ mod tests {
network_graph.read_only().channels().get(&scid).unwrap();
- assert!(network_graph- .read_only()- .nodes()- .get(&node_id_1)- .unwrap()- .announcement_info- .is_none());+ #[rustfmt::skip]+ let is_node_a_announced = network_graph.read_only().nodes().get(&node_id_1).unwrap()+ .announcement_info.is_some();+ assert!(!is_node_a_announced);
network_graph.update_node_from_announcement(&node_a_announce).unwrap();
network_graph.update_node_from_announcement(&node_b_announce).unwrap();
- assert!(network_graph- .read_only()- .nodes()- .get(&node_id_1)- .unwrap()- .announcement_info- .is_some());+ #[rustfmt::skip]+ let is_node_a_announced = network_graph.read_only().nodes().get(&node_id_1).unwrap()+ .announcement_info.is_some();+ assert!(is_node_a_announced);
}
@@ -1023,15 +1017,9 @@ mod tests {
assert!(!notifier_b.notify_pending());
network_graph.pending_checks.check_resolved_futures(&network_graph);
- assert!(!network_graph- .read_only()- .channels()- .get(&scid)- .unwrap()- .announcement_message- .as_ref()- .unwrap()- .contents- .features- .supports_unknown_test_feature());+ #[rustfmt::skip]+ let is_test_feature_set =+ network_graph.read_only().channels().get(&scid).unwrap().announcement_message+ .as_ref().unwrap().contents.features.supports_unknown_test_feature();+ assert!(!is_test_feature_set);
}

@TheBlueMattTheBlueMatt left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

thanks. just gonna land this it ended up being relatively trivial.


assert!(network_graph.read_only().nodes().get(&valid_announcement.contents.node_id_1)
.unwrap().announcement_info.is_none());
#[rustfmt::skip]

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Not really sure that rustfmt::skip is the right fix for these kinds of cases. We can take intermediate variables that clean up rustfmt's crap and improve readability while we're at it.

@TheBlueMatt
TheBlueMatt merged commit a58e7f0 into lightningdevkit:mainJan 29, 2026
20 of 21 checks passed
@github-project-automationgithub-project-automationBot moved this from Goal: Merge to Done in Weekly GoalsJan 29, 2026
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

Status: Done

Development

Successfully merging this pull request may close these issues.

gossip doesnt resolve async

3 participants

@tnull@ldk-reviews-bot@TheBlueMatt
, '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

Fix race condition in async UtxoFuture resolution - #4348

Merged
TheBlueMatt merged 2 commits into
lightningdevkit:mainfrom
tnull:2026-01-fix-gossip-verification
Jan 29, 2026
Merged

Fix race condition in async UtxoFuture resolution #4348
TheBlueMatt merged 2 commits into
lightningdevkit:mainfrom
tnull:2026-01-fix-gossip-verification

Conversation

@tnull

@tnulltnull commented Jan 26, 2026

Copy link
Copy Markdown
Contributor

Fixes#4346.

Previously, we refactored the GossipVerifier to not require holding a
circular reference. As part of this, we moved to a model where the
UtxoFutures are now polled by the background processor which checks
for completion through get_and_clear_pending_msg_events.

However, as part of this refactor we introduced race-condition: as we
only held Weak references in PendingChecksContext and the
UtxoFuture was directly dropped by the GossipVerifier after calling
resolve, the actual data was dropped with the future and gone when the
background processor attempted to retrieve and apply it via
check_resolved_futures.

Here, we fix this issue by simply holding on to the stateArcs in a
separate pending_statesVec that is only pruned in
check_resolved_futures, ensuring any completed results are collected
first.

@ldk-reviews-bot

ldk-reviews-bot commented Jan 26, 2026

Copy link
Copy Markdown

👋 Thanks for assigning @TheBlueMatt 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.

@tnull
tnull marked this pull request as draft January 26, 2026 16:16
@tnull
tnullforce-pushed the 2026-01-fix-gossip-verification branch 2 times, most recently from 7c871f2 to 5c757b3CompareJanuary 26, 2026 16:18
Comment threadlightning/src/routing/utxo.rs Outdated
Comment threadlightning/src/routing/utxo.rs Outdated
Comment threadlightning/src/routing/utxo.rs Outdated
Comment threadlightning/src/routing/utxo.rs Outdated
Comment threadlightning/src/routing/utxo.rs Outdated
Comment threadlightning/src/routing/utxo.rs Outdated
Comment threadlightning/src/routing/utxo.rs Outdated
Comment threadlightning/src/routing/utxo.rs Outdated
@tnulltnull self-assigned this Jan 28, 2026
@tnulltnull moved this to Goal: Merge in Weekly GoalsJan 28, 2026
@tnull
tnullforce-pushed the 2026-01-fix-gossip-verification branch from 5c757b3 to e3bf6a5CompareJanuary 28, 2026 13:53
@tnull
tnull marked this pull request as ready for review January 28, 2026 13:54
@tnull
tnullforce-pushed the 2026-01-fix-gossip-verification branch 2 times, most recently from f672e27 to 7c08572CompareJanuary 28, 2026 13:57

@tnulltnull left a comment

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

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

Now switched to a much less invasive approach where we simply keep track of the pending future states Arcs in a separate Vec until the next call of check_resolved_futures, which will collect completed states and prune the data structures.

@tnulltnull added this to the 0.3 milestone Jan 28, 2026
@tnull

Copy link
Copy Markdown
ContributorAuthor

Whoops, seems there are still some more tests to fix, drafting again 🤭

@tnull
tnull marked this pull request as draft January 28, 2026 14:16
@tnull
tnullforce-pushed the 2026-01-fix-gossip-verification branch 2 times, most recently from 585fd56 to 0da4883CompareJanuary 28, 2026 14:27
@tnull

Copy link
Copy Markdown
ContributorAuthor

Whoops, seems there are still some more tests to fix, drafting again 🤭

... and they're passing again.

@tnull
tnull marked this pull request as ready for review January 28, 2026 14:28
@codecov

codecovBot commented Jan 28, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 91.66667% with 25 lines in your changes missing coverage. Please review.
✅ Project coverage is 86.10%. Comparing base (9e91b2e) to head (114f6b5).
⚠️ Report is 13 commits behind head on main.

Files with missing linesPatch %Lines
lightning/src/routing/utxo.rs91.66%23 Missing and 2 partials ⚠️
Additional details and impacted files
@@ Coverage Diff @@## main #4348 +/- ##
==========================================
+ Coverage 86.08% 86.10% +0.01% 
==========================================
Files 156 156 Lines 102428 102610 +182 Branches 102428 102610 +182 ==========================================
+ Hits 88179 88354 +175 - Misses 11754 11762 +8 + Partials 2495 2494 -1 
FlagCoverage Δ
tests86.10% <91.66%> (+0.01%)⬆️

Flags with carried forward coverage won't be shown. Click here to find out more.

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

@TheBlueMattTheBlueMatt left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Bleh, don't love the extra vec but you're right that its much simpler and probably worth it. Feel free to squash (maybe with a few more rustfmt'isms fixed) and let's land this.

Comment threadlightning/src/routing/utxo.rs Outdated
Comment threadlightning/src/routing/utxo.rs Outdated
@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.

Signed-off-by: Elias Rohrer <dev@tnull.de>
Previously, we refactored the `GossipVerifier` to not require holding a
circular reference. As part of this, we moved to a model where the
`UtxoFuture`s are now polled by the background processor which checks
for completion through `get_and_clear_pending_msg_events`.
However, as part of this refactor we introduced race-condition: as we
only held `Weak` references in `PendingChecksContext` and the
`UtxoFuture` was directly dropped by the `GossipVerifier` after calling
`resolve`, the actual data was dropped with the future and gone when the
background processor attempted to retrieve and apply it via
`check_resolved_futures`.
Here, we fix this issue by simply holding on to the `state` `Arc`s in a
separate `pending_states` `Vec` that is only pruned in
`check_resolved_futures`, ensuring any completed results are collected
first.
Signed-off-by: Elias Rohrer <dev@tnull.de>
@tnull
tnullforce-pushed the 2026-01-fix-gossip-verification branch from 0da4883 to 114f6b5CompareJanuary 29, 2026 08:11
@tnull

Copy link
Copy Markdown
ContributorAuthor

Bleh, don't love the extra vec but you're right that its much simpler and probably worth it. Feel free to squash (maybe with a few more rustfmt'isms fixed) and let's land this.

Squashed fixup, and included the following changes:

diff --git a/lightning/src/routing/utxo.rs b/lightning/src/routing/utxo.rs
index f3351aa0a..ab653b1ea 100644
--- a/lightning/src/routing/utxo.rs+++ b/lightning/src/routing/utxo.rs@@ -763,22 +763,16 @@ mod tests {
network_graph.read_only().channels().get(&scid).unwrap();
- assert!(network_graph- .read_only()- .nodes()- .get(&node_id_1)- .unwrap()- .announcement_info- .is_none());+ #[rustfmt::skip]+ let is_node_a_announced = network_graph.read_only().nodes().get(&node_id_1).unwrap()+ .announcement_info.is_some();+ assert!(!is_node_a_announced);
network_graph.update_node_from_announcement(&node_a_announce).unwrap();
network_graph.update_node_from_announcement(&node_b_announce).unwrap();
- assert!(network_graph- .read_only()- .nodes()- .get(&node_id_1)- .unwrap()- .announcement_info- .is_some());+ #[rustfmt::skip]+ let is_node_a_announced = network_graph.read_only().nodes().get(&node_id_1).unwrap()+ .announcement_info.is_some();+ assert!(is_node_a_announced);
}
@@ -1023,15 +1017,9 @@ mod tests {
assert!(!notifier_b.notify_pending());
network_graph.pending_checks.check_resolved_futures(&network_graph);
- assert!(!network_graph- .read_only()- .channels()- .get(&scid)- .unwrap()- .announcement_message- .as_ref()- .unwrap()- .contents- .features- .supports_unknown_test_feature());+ #[rustfmt::skip]+ let is_test_feature_set =+ network_graph.read_only().channels().get(&scid).unwrap().announcement_message+ .as_ref().unwrap().contents.features.supports_unknown_test_feature();+ assert!(!is_test_feature_set);
}

@TheBlueMattTheBlueMatt left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

thanks. just gonna land this it ended up being relatively trivial.


assert!(network_graph.read_only().nodes().get(&valid_announcement.contents.node_id_1)
.unwrap().announcement_info.is_none());
#[rustfmt::skip]

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Not really sure that rustfmt::skip is the right fix for these kinds of cases. We can take intermediate variables that clean up rustfmt's crap and improve readability while we're at it.

@TheBlueMatt
TheBlueMatt merged commit a58e7f0 into lightningdevkit:mainJan 29, 2026
20 of 21 checks passed
@github-project-automationgithub-project-automationBot moved this from Goal: Merge to Done in Weekly GoalsJan 29, 2026
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

Status: Done

Development

Successfully merging this pull request may close these issues.

gossip doesnt resolve async

3 participants

@tnull@ldk-reviews-bot@TheBlueMatt
, '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

Fix race condition in async UtxoFuture resolution - #4348

Merged
TheBlueMatt merged 2 commits into
lightningdevkit:mainfrom
tnull:2026-01-fix-gossip-verification
Jan 29, 2026
Merged

Fix race condition in async UtxoFuture resolution #4348
TheBlueMatt merged 2 commits into
lightningdevkit:mainfrom
tnull:2026-01-fix-gossip-verification

Conversation

@tnull

@tnulltnull commented Jan 26, 2026

Copy link
Copy Markdown
Contributor

Fixes#4346.

Previously, we refactored the GossipVerifier to not require holding a
circular reference. As part of this, we moved to a model where the
UtxoFutures are now polled by the background processor which checks
for completion through get_and_clear_pending_msg_events.

However, as part of this refactor we introduced race-condition: as we
only held Weak references in PendingChecksContext and the
UtxoFuture was directly dropped by the GossipVerifier after calling
resolve, the actual data was dropped with the future and gone when the
background processor attempted to retrieve and apply it via
check_resolved_futures.

Here, we fix this issue by simply holding on to the stateArcs in a
separate pending_statesVec that is only pruned in
check_resolved_futures, ensuring any completed results are collected
first.

@ldk-reviews-bot

ldk-reviews-bot commented Jan 26, 2026

Copy link
Copy Markdown

👋 Thanks for assigning @TheBlueMatt 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.

@tnull
tnull marked this pull request as draft January 26, 2026 16:16
@tnull
tnullforce-pushed the 2026-01-fix-gossip-verification branch 2 times, most recently from 7c871f2 to 5c757b3CompareJanuary 26, 2026 16:18
Comment threadlightning/src/routing/utxo.rs Outdated
Comment threadlightning/src/routing/utxo.rs Outdated
Comment threadlightning/src/routing/utxo.rs Outdated
Comment threadlightning/src/routing/utxo.rs Outdated
Comment threadlightning/src/routing/utxo.rs Outdated
Comment threadlightning/src/routing/utxo.rs Outdated
Comment threadlightning/src/routing/utxo.rs Outdated
Comment threadlightning/src/routing/utxo.rs Outdated
@tnulltnull self-assigned this Jan 28, 2026
@tnulltnull moved this to Goal: Merge in Weekly GoalsJan 28, 2026
@tnull
tnullforce-pushed the 2026-01-fix-gossip-verification branch from 5c757b3 to e3bf6a5CompareJanuary 28, 2026 13:53
@tnull
tnull marked this pull request as ready for review January 28, 2026 13:54
@tnull
tnullforce-pushed the 2026-01-fix-gossip-verification branch 2 times, most recently from f672e27 to 7c08572CompareJanuary 28, 2026 13:57

@tnulltnull left a comment

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

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

Now switched to a much less invasive approach where we simply keep track of the pending future states Arcs in a separate Vec until the next call of check_resolved_futures, which will collect completed states and prune the data structures.

@tnulltnull added this to the 0.3 milestone Jan 28, 2026
@tnull

Copy link
Copy Markdown
ContributorAuthor

Whoops, seems there are still some more tests to fix, drafting again 🤭

@tnull
tnull marked this pull request as draft January 28, 2026 14:16
@tnull
tnullforce-pushed the 2026-01-fix-gossip-verification branch 2 times, most recently from 585fd56 to 0da4883CompareJanuary 28, 2026 14:27
@tnull

Copy link
Copy Markdown
ContributorAuthor

Whoops, seems there are still some more tests to fix, drafting again 🤭

... and they're passing again.

@tnull
tnull marked this pull request as ready for review January 28, 2026 14:28
@codecov

codecovBot commented Jan 28, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 91.66667% with 25 lines in your changes missing coverage. Please review.
✅ Project coverage is 86.10%. Comparing base (9e91b2e) to head (114f6b5).
⚠️ Report is 13 commits behind head on main.

Files with missing linesPatch %Lines
lightning/src/routing/utxo.rs91.66%23 Missing and 2 partials ⚠️
Additional details and impacted files
@@ Coverage Diff @@## main #4348 +/- ##
==========================================
+ Coverage 86.08% 86.10% +0.01% 
==========================================
Files 156 156 Lines 102428 102610 +182 Branches 102428 102610 +182 ==========================================
+ Hits 88179 88354 +175 - Misses 11754 11762 +8 + Partials 2495 2494 -1 
FlagCoverage Δ
tests86.10% <91.66%> (+0.01%)⬆️

Flags with carried forward coverage won't be shown. Click here to find out more.

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

@TheBlueMattTheBlueMatt left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Bleh, don't love the extra vec but you're right that its much simpler and probably worth it. Feel free to squash (maybe with a few more rustfmt'isms fixed) and let's land this.

Comment threadlightning/src/routing/utxo.rs Outdated
Comment threadlightning/src/routing/utxo.rs Outdated
@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.

Signed-off-by: Elias Rohrer <dev@tnull.de>
Previously, we refactored the `GossipVerifier` to not require holding a
circular reference. As part of this, we moved to a model where the
`UtxoFuture`s are now polled by the background processor which checks
for completion through `get_and_clear_pending_msg_events`.
However, as part of this refactor we introduced race-condition: as we
only held `Weak` references in `PendingChecksContext` and the
`UtxoFuture` was directly dropped by the `GossipVerifier` after calling
`resolve`, the actual data was dropped with the future and gone when the
background processor attempted to retrieve and apply it via
`check_resolved_futures`.
Here, we fix this issue by simply holding on to the `state` `Arc`s in a
separate `pending_states` `Vec` that is only pruned in
`check_resolved_futures`, ensuring any completed results are collected
first.
Signed-off-by: Elias Rohrer <dev@tnull.de>
@tnull
tnullforce-pushed the 2026-01-fix-gossip-verification branch from 0da4883 to 114f6b5CompareJanuary 29, 2026 08:11
@tnull

Copy link
Copy Markdown
ContributorAuthor

Bleh, don't love the extra vec but you're right that its much simpler and probably worth it. Feel free to squash (maybe with a few more rustfmt'isms fixed) and let's land this.

Squashed fixup, and included the following changes:

diff --git a/lightning/src/routing/utxo.rs b/lightning/src/routing/utxo.rs
index f3351aa0a..ab653b1ea 100644
--- a/lightning/src/routing/utxo.rs+++ b/lightning/src/routing/utxo.rs@@ -763,22 +763,16 @@ mod tests {
network_graph.read_only().channels().get(&scid).unwrap();
- assert!(network_graph- .read_only()- .nodes()- .get(&node_id_1)- .unwrap()- .announcement_info- .is_none());+ #[rustfmt::skip]+ let is_node_a_announced = network_graph.read_only().nodes().get(&node_id_1).unwrap()+ .announcement_info.is_some();+ assert!(!is_node_a_announced);
network_graph.update_node_from_announcement(&node_a_announce).unwrap();
network_graph.update_node_from_announcement(&node_b_announce).unwrap();
- assert!(network_graph- .read_only()- .nodes()- .get(&node_id_1)- .unwrap()- .announcement_info- .is_some());+ #[rustfmt::skip]+ let is_node_a_announced = network_graph.read_only().nodes().get(&node_id_1).unwrap()+ .announcement_info.is_some();+ assert!(is_node_a_announced);
}
@@ -1023,15 +1017,9 @@ mod tests {
assert!(!notifier_b.notify_pending());
network_graph.pending_checks.check_resolved_futures(&network_graph);
- assert!(!network_graph- .read_only()- .channels()- .get(&scid)- .unwrap()- .announcement_message- .as_ref()- .unwrap()- .contents- .features- .supports_unknown_test_feature());+ #[rustfmt::skip]+ let is_test_feature_set =+ network_graph.read_only().channels().get(&scid).unwrap().announcement_message+ .as_ref().unwrap().contents.features.supports_unknown_test_feature();+ assert!(!is_test_feature_set);
}

@TheBlueMattTheBlueMatt left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

thanks. just gonna land this it ended up being relatively trivial.


assert!(network_graph.read_only().nodes().get(&valid_announcement.contents.node_id_1)
.unwrap().announcement_info.is_none());
#[rustfmt::skip]

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Not really sure that rustfmt::skip is the right fix for these kinds of cases. We can take intermediate variables that clean up rustfmt's crap and improve readability while we're at it.

@TheBlueMatt
TheBlueMatt merged commit a58e7f0 into lightningdevkit:mainJan 29, 2026
20 of 21 checks passed
@github-project-automationgithub-project-automationBot moved this from Goal: Merge to Done in Weekly GoalsJan 29, 2026
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

Status: Done

Development

Successfully merging this pull request may close these issues.

gossip doesnt resolve async

3 participants

@tnull@ldk-reviews-bot@TheBlueMatt
, '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

Fix race condition in async UtxoFuture resolution - #4348

Merged
TheBlueMatt merged 2 commits into
lightningdevkit:mainfrom
tnull:2026-01-fix-gossip-verification
Jan 29, 2026
Merged

Fix race condition in async UtxoFuture resolution #4348
TheBlueMatt merged 2 commits into
lightningdevkit:mainfrom
tnull:2026-01-fix-gossip-verification

Conversation

@tnull

@tnulltnull commented Jan 26, 2026

Copy link
Copy Markdown
Contributor

Fixes#4346.

Previously, we refactored the GossipVerifier to not require holding a
circular reference. As part of this, we moved to a model where the
UtxoFutures are now polled by the background processor which checks
for completion through get_and_clear_pending_msg_events.

However, as part of this refactor we introduced race-condition: as we
only held Weak references in PendingChecksContext and the
UtxoFuture was directly dropped by the GossipVerifier after calling
resolve, the actual data was dropped with the future and gone when the
background processor attempted to retrieve and apply it via
check_resolved_futures.

Here, we fix this issue by simply holding on to the stateArcs in a
separate pending_statesVec that is only pruned in
check_resolved_futures, ensuring any completed results are collected
first.

@ldk-reviews-bot

ldk-reviews-bot commented Jan 26, 2026

Copy link
Copy Markdown

👋 Thanks for assigning @TheBlueMatt 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.

@tnull
tnull marked this pull request as draft January 26, 2026 16:16
@tnull
tnullforce-pushed the 2026-01-fix-gossip-verification branch 2 times, most recently from 7c871f2 to 5c757b3CompareJanuary 26, 2026 16:18
Comment threadlightning/src/routing/utxo.rs Outdated
Comment threadlightning/src/routing/utxo.rs Outdated
Comment threadlightning/src/routing/utxo.rs Outdated
Comment threadlightning/src/routing/utxo.rs Outdated
Comment threadlightning/src/routing/utxo.rs Outdated
Comment threadlightning/src/routing/utxo.rs Outdated
Comment threadlightning/src/routing/utxo.rs Outdated
Comment threadlightning/src/routing/utxo.rs Outdated
@tnulltnull self-assigned this Jan 28, 2026
@tnulltnull moved this to Goal: Merge in Weekly GoalsJan 28, 2026
@tnull
tnullforce-pushed the 2026-01-fix-gossip-verification branch from 5c757b3 to e3bf6a5CompareJanuary 28, 2026 13:53
@tnull
tnull marked this pull request as ready for review January 28, 2026 13:54
@tnull
tnullforce-pushed the 2026-01-fix-gossip-verification branch 2 times, most recently from f672e27 to 7c08572CompareJanuary 28, 2026 13:57

@tnulltnull left a comment

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

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

Now switched to a much less invasive approach where we simply keep track of the pending future states Arcs in a separate Vec until the next call of check_resolved_futures, which will collect completed states and prune the data structures.

@tnulltnull added this to the 0.3 milestone Jan 28, 2026
@tnull

Copy link
Copy Markdown
ContributorAuthor

Whoops, seems there are still some more tests to fix, drafting again 🤭

@tnull
tnull marked this pull request as draft January 28, 2026 14:16
@tnull
tnullforce-pushed the 2026-01-fix-gossip-verification branch 2 times, most recently from 585fd56 to 0da4883CompareJanuary 28, 2026 14:27
@tnull

Copy link
Copy Markdown
ContributorAuthor

Whoops, seems there are still some more tests to fix, drafting again 🤭

... and they're passing again.

@tnull
tnull marked this pull request as ready for review January 28, 2026 14:28
@codecov

codecovBot commented Jan 28, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 91.66667% with 25 lines in your changes missing coverage. Please review.
✅ Project coverage is 86.10%. Comparing base (9e91b2e) to head (114f6b5).
⚠️ Report is 13 commits behind head on main.

Files with missing linesPatch %Lines
lightning/src/routing/utxo.rs91.66%23 Missing and 2 partials ⚠️
Additional details and impacted files
@@ Coverage Diff @@## main #4348 +/- ##
==========================================
+ Coverage 86.08% 86.10% +0.01% 
==========================================
Files 156 156 Lines 102428 102610 +182 Branches 102428 102610 +182 ==========================================
+ Hits 88179 88354 +175 - Misses 11754 11762 +8 + Partials 2495 2494 -1 
FlagCoverage Δ
tests86.10% <91.66%> (+0.01%)⬆️

Flags with carried forward coverage won't be shown. Click here to find out more.

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

@TheBlueMattTheBlueMatt left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Bleh, don't love the extra vec but you're right that its much simpler and probably worth it. Feel free to squash (maybe with a few more rustfmt'isms fixed) and let's land this.

Comment threadlightning/src/routing/utxo.rs Outdated
Comment threadlightning/src/routing/utxo.rs Outdated
@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.

Signed-off-by: Elias Rohrer <dev@tnull.de>
Previously, we refactored the `GossipVerifier` to not require holding a
circular reference. As part of this, we moved to a model where the
`UtxoFuture`s are now polled by the background processor which checks
for completion through `get_and_clear_pending_msg_events`.
However, as part of this refactor we introduced race-condition: as we
only held `Weak` references in `PendingChecksContext` and the
`UtxoFuture` was directly dropped by the `GossipVerifier` after calling
`resolve`, the actual data was dropped with the future and gone when the
background processor attempted to retrieve and apply it via
`check_resolved_futures`.
Here, we fix this issue by simply holding on to the `state` `Arc`s in a
separate `pending_states` `Vec` that is only pruned in
`check_resolved_futures`, ensuring any completed results are collected
first.
Signed-off-by: Elias Rohrer <dev@tnull.de>
@tnull
tnullforce-pushed the 2026-01-fix-gossip-verification branch from 0da4883 to 114f6b5CompareJanuary 29, 2026 08:11
@tnull

Copy link
Copy Markdown
ContributorAuthor

Bleh, don't love the extra vec but you're right that its much simpler and probably worth it. Feel free to squash (maybe with a few more rustfmt'isms fixed) and let's land this.

Squashed fixup, and included the following changes:

diff --git a/lightning/src/routing/utxo.rs b/lightning/src/routing/utxo.rs
index f3351aa0a..ab653b1ea 100644
--- a/lightning/src/routing/utxo.rs+++ b/lightning/src/routing/utxo.rs@@ -763,22 +763,16 @@ mod tests {
network_graph.read_only().channels().get(&scid).unwrap();
- assert!(network_graph- .read_only()- .nodes()- .get(&node_id_1)- .unwrap()- .announcement_info- .is_none());+ #[rustfmt::skip]+ let is_node_a_announced = network_graph.read_only().nodes().get(&node_id_1).unwrap()+ .announcement_info.is_some();+ assert!(!is_node_a_announced);
network_graph.update_node_from_announcement(&node_a_announce).unwrap();
network_graph.update_node_from_announcement(&node_b_announce).unwrap();
- assert!(network_graph- .read_only()- .nodes()- .get(&node_id_1)- .unwrap()- .announcement_info- .is_some());+ #[rustfmt::skip]+ let is_node_a_announced = network_graph.read_only().nodes().get(&node_id_1).unwrap()+ .announcement_info.is_some();+ assert!(is_node_a_announced);
}
@@ -1023,15 +1017,9 @@ mod tests {
assert!(!notifier_b.notify_pending());
network_graph.pending_checks.check_resolved_futures(&network_graph);
- assert!(!network_graph- .read_only()- .channels()- .get(&scid)- .unwrap()- .announcement_message- .as_ref()- .unwrap()- .contents- .features- .supports_unknown_test_feature());+ #[rustfmt::skip]+ let is_test_feature_set =+ network_graph.read_only().channels().get(&scid).unwrap().announcement_message+ .as_ref().unwrap().contents.features.supports_unknown_test_feature();+ assert!(!is_test_feature_set);
}

@TheBlueMattTheBlueMatt left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

thanks. just gonna land this it ended up being relatively trivial.


assert!(network_graph.read_only().nodes().get(&valid_announcement.contents.node_id_1)
.unwrap().announcement_info.is_none());
#[rustfmt::skip]

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Not really sure that rustfmt::skip is the right fix for these kinds of cases. We can take intermediate variables that clean up rustfmt's crap and improve readability while we're at it.

@TheBlueMatt
TheBlueMatt merged commit a58e7f0 into lightningdevkit:mainJan 29, 2026
20 of 21 checks passed
@github-project-automationgithub-project-automationBot moved this from Goal: Merge to Done in Weekly GoalsJan 29, 2026
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

Status: Done

Development

Successfully merging this pull request may close these issues.

gossip doesnt resolve async

3 participants

@tnull@ldk-reviews-bot@TheBlueMatt