Skip to content

Fix spurious debug_assert in UTXO gossip dedup check#4466

Merged
TheBlueMatt merged 1 commit intolightningdevkit:mainfrom
tnull:2026-03-fix-utxo-debug-assert-race
Mar 6, 2026
Merged

Fix spurious debug_assert in UTXO gossip dedup check#4466
TheBlueMatt merged 1 commit intolightningdevkit:mainfrom
tnull:2026-03-fix-utxo-debug-assert-race

Conversation

@tnull
Copy link
Contributor

@tnull tnull commented Mar 6, 2026

Fixes #4439.

check_replace_previous_entry hit debug_assert!(false) when channel_announce was None on a still-live UtxoMessages. The comment claimed this was unreachable because channel_announce is set under the same lock as the channel map entry. However, there is a legitimate race:

  1. A channel announcement arrives, an async UTXO lookup starts, and pending_channels[scid] is set with a Weak to the UtxoMessages.
  2. The lookup resolves. resolve_single_future takes both channel_announce and complete via .take(), but the Arc<Mutex<UtxoMessages>> is still alive on the stack of check_resolved_futures.
  3. A duplicate announcement for the same SCID arrives during this window. check_replace_previous_entry upgrades the Weak, finds channel_announce is None, and hits the assert.

Replace the unconditional debug_assert!(false) with a targeted check that complete has also been taken (confirming the future resolved), which would catch a genuinely unexpected state where channel_announce is None but complete is still pending.

Co-Authored-By: HAL 9000

`check_replace_previous_entry` hit `debug_assert!(false)` when
`channel_announce` was `None` on a still-live `UtxoMessages`. The
comment claimed this was unreachable because `channel_announce` is set
under the same lock as the channel map entry. However, there is a
legitimate race:

1. A channel announcement arrives, an async UTXO lookup starts, and
   `pending_channels[scid]` is set with a `Weak` to the
   `UtxoMessages`.
2. The lookup resolves. `resolve_single_future` takes both
   `channel_announce` and `complete` via `.take()`, but the
   `Arc<Mutex<UtxoMessages>>` is still alive on the stack of
   `check_resolved_futures`.
3. A duplicate announcement for the same SCID arrives during this
   window. `check_replace_previous_entry` upgrades the `Weak`,
   finds `channel_announce` is `None`, and hits the assert.

Replace the unconditional `debug_assert!(false)` with a targeted check
that `complete` has also been taken (confirming the future resolved),
which would catch a genuinely unexpected state where
`channel_announce` is `None` but `complete` is still pending.

Co-Authored-By: HAL 9000
@ldk-reviews-bot
Copy link

ldk-reviews-bot commented Mar 6, 2026

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

Copy link
Collaborator

@TheBlueMatt TheBlueMatt left a comment

Choose a reason for hiding this comment

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

Thanks. CI unrelated:

Well, this is embarrassing.
cargo-semver-checks had a problem and crashed. To help us diagnose the problem you can send us a crash report.

@ldk-reviews-bot
Copy link

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

@TheBlueMatt TheBlueMatt merged commit 36d04c3 into lightningdevkit:main Mar 6, 2026
16 of 20 checks passed
Sign up for free to 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.

The debug_assert in check_replace_previous_entry is actually reachable

3 participants