Skip to content

fuzz: add force-close support to chanmon_consistency#4381

Draft
joostjager wants to merge 34 commits into
lightningdevkit:mainfrom
joostjager:fuzz-force-close
Draft

fuzz: add force-close support to chanmon_consistency#4381
joostjager wants to merge 34 commits into
lightningdevkit:mainfrom
joostjager:fuzz-force-close

Conversation

@joostjager

@joostjager joostjager commented Feb 4, 2026

Copy link
Copy Markdown
Contributor

Add force-close coverage to the chanmon_consistency fuzzer. Previously, the fuzzer only exercised cooperative channel flows. This PR enables the fuzzer to force-close channels and verify that on-chain resolution, HTLC timeouts, and payment preimage propagation all work correctly under channel monitor consistency
constraints.

Based on #4583, #4660

@ldk-reviews-bot

ldk-reviews-bot commented Feb 4, 2026

Copy link
Copy Markdown

👋 I see @wpaulino was un-assigned.
If you'd like another reviewer assignment, please click here.

@codecov

codecov Bot commented Feb 10, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 86.68%. Comparing base (1743b99) to head (9a3dc7c).

Additional details and impacted files
@@            Coverage Diff             @@
##             main    #4381      +/-   ##
==========================================
+ Coverage   86.58%   86.68%   +0.10%     
==========================================
  Files         159      159              
  Lines      110498   110827     +329     
  Branches   110498   110827     +329     
==========================================
+ Hits        95678    96075     +397     
+ Misses      12281    12221      -60     
+ Partials     2539     2531       -8     
Flag Coverage Δ
fuzzing-fake-hashes 6.60% <ø> (-0.02%) ⬇️
fuzzing-real-hashes 28.99% <ø> (+5.84%) ⬆️
tests 86.26% <ø> (+0.06%) ⬆️

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.

Comment thread fuzz/src/chanmon_consistency.rs Outdated
},
events::Event::SplicePending { .. } => {},
events::Event::SpliceFailed { .. } => {},
events::Event::ChannelClosed { .. } => {},

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.

We should probably open a new channel to replace the force closed one?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

My plan is to start with closing only, but indeed, re-opening is interesting too perhapas.

Comment thread fuzz/src/chanmon_consistency.rs Outdated
@TheBlueMatt

Copy link
Copy Markdown
Collaborator

Needs rebase. Is this stalled waiting on fixes that were discovered by the fuzzer?

@joostjager

Copy link
Copy Markdown
Contributor Author

I was working on my local branch only for a while. Just pushed what I have. But indeed, the mixed mode failure is also showing up in different ways with fc fuzzing.

@joostjager joostjager force-pushed the fuzz-force-close branch 2 times, most recently from a1b1367 to ba6cbfa Compare April 16, 2026 13:33
@joostjager joostjager force-pushed the fuzz-force-close branch 2 times, most recently from f3cbd42 to 88ee2b7 Compare April 21, 2026 08:23
@joostjager

Copy link
Copy Markdown
Contributor Author

Rebased onto #4571, because compile times became unworkable with the many macros.

@joostjager joostjager force-pushed the fuzz-force-close branch 6 times, most recently from 5e7af7c to 2667e5e Compare April 29, 2026 10:09
wpaulino and others added 29 commits June 19, 2026 11:06
There's no need to inform users of negotiated splices when they're not
contributing as it just produces noise. Once they do start contributing,
they cannot stop, so we always emit the event going forward. Note that
we still emit `Event::ChannelReady` with the new locked funding outpoint
for each locked splice, so users can still learn that a splice occurred
that way.
Previously, this could result in an acceptor not receiving a
`Event::SpliceNegotiationFailed` for a splice in which they reused the
same contribution (except for the feerate change). Our API should
guarantee that users should always see `SpliceNegotiated` and
`SpliceNegotiationFailed` events for splices that they contribute to.
While a contribution may be valid at the time the splice is requested,
quiescence still needs to happen, which can affect the balances of the
channel as it fully settles all pending state. After doing so, it's
possible that the contribution is no longer valid. Since quiescence
itself doesn't have a terminal message, we see a `WarnAndDisconnect`
event happen.
We keep some `WarnAndDisconnect` cases as mandated by the spec, but
otherwise prefer sending `tx_abort` to terminate quiescence and avoid
reconnection loops.
Send `tx_abort` to terminate quiescence and avoid reconnection loops.
This mirrors what we do for counterparty `splice_init` messages, making
sure we don't accept RBFs once a channel has requested shutdown.
Assert that splice_locked and ChannelReady only reference txids
that are already confirmed in the harness chain model.

Thread ChainState through message processing so those checks can run
at delivery time.
After tx_abort, assert no active splice negotiation remains.
The check uses ChannelDetails splice state so the fuzzer can catch
stale active state without relying on delayed application events.
It still rejects empty SpliceDetails while allowing queued
contributions from a newer splice attempt.
Have ChannelMonitor hand singular ClaimRequests to OnchainTxHandler.

Convert them to PackageTemplates only after duplicate filtering.

This makes the single-outpoint invariant explicit at that boundary.
Clarify ChannelMonitor comments around on-chain event thresholds.
Some events only wait for anti-reorg finality, while CSV-delayed
outputs wait until spendable through the same threshold queue.
Move repeated OnchainTxHandler setup into shared test helpers so the
claim-replay coverage can focus on the behavior under test.
Add a monitor test for an inbound HTLC claimed by preimage from a
holder commitment. Confirm that the claimable balance remains unchanged
after the HTLC-success spend reaches anti-reorg finality but before the
CSV-delayed output is spendable.
Treat HTLCSpendConfirmation entries as irrevocably resolved once
the commitment HTLC output spend reaches anti-reorg finality. Do
not wait for CSV maturity of any delayed output created by that
spend.

Delayed outputs remain tracked separately as MaturingOutput entries,
keeping claimable balances alive until they are CSV-mature and can be
surfaced as SpendableOutputs.
Check that any HTLCSpendConfirmation carrying a local-output CSV
has a matching delayed MaturingOutput. Scan spendable outputs before
recording HTLC spend confirmations so the invariant is present when
the assertion runs.
Aggregated HTLC spends create one delayed output per HTLC input, all
sharing the same transaction and CSV delay, so txid and CSV alone do
not identify the output. Match on the output index paired with the
spending input as well.

AI tools were used in preparing this commit.
A replayed holder HTLC claim may arrive as a single-outpoint
request after earlier requests were merged into a delayed package.
Check whether an existing delayed package already covers the new
request instead of requiring exact outpoint-set equality.

Add focused OnchainTxHandler coverage and a ChannelMonitor regression
through claim_funds for both current anchor variants.
When a transaction spends one outpoint from a delayed package, the
split outpoint is tracked as a ContentiousOutpoint until the spend
reaches anti-reorg finality. Reject replayed claim requests for those
pending-spent outpoints so they are not added back before the spend
reaches anti-reorg finality or reorgs out.

Add an OnchainTxHandler regression that replays a holder claim during
that pending-spent window and verifies reorg resurrection still works.
Classify duplicate outpoint state in one helper.

Preserve existing filter ordering and timelock logging.
Replay the original claim requests once the contentious spend reorgs
out and verify the resurrected delayed package is not duplicated.

AI tools were used in preparing this commit.
Filter regenerated HTLC claim requests once ChannelMonitor has persisted
anti-reorg finality for the commitment HTLC output spend.

This keeps replayed preimage updates from recreating claims after
OnchainTxHandler has cleaned up its active retry state, relying on the
monitor's persisted HTLC resolution state.
Log when a replayed preimage claim is skipped because the
HTLC output reached anti-reorg finality without that preimage.
Hash HTLC claim outpoints in canonical order so the same logical HTLC
set produces the same ClaimId regardless of descriptor order.

Add a unit test covering reversed descriptor order.
Allow chanmon consistency fuzz inputs to block holder-side signer
operations and later retry monitor-driven claim signing. This gives
force-close sequences a way to cover local on-chain claim
construction while reusing the harness' existing signer-op blocking
machinery.
Replace repeated per-node setup and event-processing calls with loops.

Keep the existing assertions and early-continue behavior intact.
Extract chain connection and finish-time relay helper boundaries.
Also isolate splice quiescence warning detection without changing
behavior.
Fold the mempool follow-up into the force-close fuzzing layer so
this branch has one commit for settlement coverage.

Keep relay and mining opcodes from the mempool model while adding
explicit and timeout-driven close tracking, holder signer unblocks,
and cleanup that drives on-chain claims. Skip stale PaymentClaimable
events once their claim_deadline has passed so the harness does not
call claim_funds outside the LDK API contract.

This leaves PaymentClaimBuffer as an accounting failure after a live
claim attempt instead of clearing the pending settlement obligation.
Treat stale splice commands as no-ops when their channel is gone.
Fuzz input can target a channel after it has already closed.

Relax claimed MPP failure accounting so sender-side failure can be
accepted when any path was blocked. One blocked MPP part can fail
the whole payment while remaining parts fail back normally.

Also include rustfmt output required by the commit hook for
possiblyrandom.
Keep force-close cleanup focused on payment work that can still make
progress. Claimed payments may miss receiver PaymentClaimed once every
path is terminal, and sends that never left the holding cell no longer
keep settlement pending.

Also tolerate stale splice funding-signing events when a channel closes
before the application handles the queued event.

Verified with cargo fmt --all, fuzz-crate cargo check, the targeted
non-splice crash subset, and the full chanmon corpus. Only the known
splice signatures remain.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

Status: No status

Development

Successfully merging this pull request may close these issues.

5 participants