Skip to content

fuzz: add force-close support to chanmon_consistency#4381

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

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

Conversation

@joostjager
Copy link
Copy Markdown
Contributor

@joostjager joostjager commented Feb 4, 2026

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 #4465 and #4583

@ldk-reviews-bot
Copy link
Copy Markdown

ldk-reviews-bot commented Feb 4, 2026

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

@codecov
Copy link
Copy Markdown

codecov Bot commented Feb 10, 2026

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 86.49%. Comparing base (b8118e3) to head (5b5597f).

Additional details and impacted files
@@            Coverage Diff             @@
##             main    #4381      +/-   ##
==========================================
+ Coverage   86.40%   86.49%   +0.08%     
==========================================
  Files         158      158              
  Lines      109293   109625     +332     
  Branches   109293   109625     +332     
==========================================
+ Hits        94439    94819     +380     
+ Misses      12309    12256      -53     
- Partials     2545     2550       +5     
Flag Coverage Δ
fuzzing-fake-hashes 5.07% <ø> (-0.01%) ⬇️
fuzzing-real-hashes 29.01% <ø> (+6.23%) ⬆️
tests 86.16% <ø> (+0.02%) ⬆️

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?

Comment thread fuzz/src/chanmon_consistency.rs Outdated

// Only check for no broadcasts if no force-closes happened.
if !fc_ab && !fc_bc {
assert!(broadcast.txn_broadcasted.borrow().is_empty());
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

I have some changes that will be going up soon that rework this, you may want to wait until then. Each node will have its own broadcaster, and there's also a concept of a "chain" now so we can mine transactions.

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.

Changes were very useful! The per-node broadcasters (broadcast_a/broadcast_b/broadcast_c) are used to selectively drain and confirm each node's force-close commitment txs, and the ChainState abstraction is used to confirm broadcast transactions and advance block height past HTLC timelocks during settlement.

@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
joostjager added 29 commits May 15, 2026 13:33
Treat HTLC-forward processing and monitor completion as real progress
in the chanmon harness.

This keeps the settle loop running after passes that only unblock
follow-up work instead of stopping before the next event or message
batch.
Build the replacement persister with the configured monitor update
status during reload.

This keeps non-deferred restart behavior aligned with the active
persistence-style matrix.
Track deferred monitor writes in the harness and checkpoint the
ChannelManager state before flushing them to the persister.

This extends setup, reload, and settle paths to model deferred
ChainMonitor persistence ordering.
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.
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.
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.
Collect local notes and fuzz inputs used while investigating the
force-close chanmon_consistency work.

These files are marked dropme so they can aid review and reduction
without becoming part of the final patch set.
Allow fuzz builds to use cheaper ECDSA signatures and skip assertions
that rely on production signature sizes.

The fuzz signer intentionally trades realistic DER lengths for speed, so
weight lower-bound checks must not assume low-R signatures there.
Seed each harness wallet with confirmed coinbase outputs before building
channels. The modeled chain now includes the wallet funding, so later
chain checks can require transactions to spend existing outputs.
Track confirmed UTXOs in the harness chain state and require
transactions to spend outputs that exist and remain unspent.

Also reject absolute-height timelocks before they mature, while
preserving the obscured commitment number encoding used by commitment
transactions.
Notify channel monitors about confirmed transactions and best-block
updates while harness nodes catch up to the modeled chain.

When advancing across empty blocks, jump directly to the next block that
contains transactions while still reporting the intervening best block.
Teach the harness to deliver additional control and announcement
messages emitted during reconnects and timer-driven state changes.

This keeps delayed message handling from panicking on valid events that
can be produced by the channel manager.
Stop treating every channel close or broadcast transaction as an
immediate invariant failure.

Later commits add explicit force-close coverage, so the baseline harness
must allow channels and broadcaster queues to reflect closure progress.
Return the transactions confirmed from the pending pool and apply their
effects to the harness wallets.

This keeps wallet UTXO state aligned with the fake chain when splice or
other pending transactions are mined by fuzz input.
Map the primary signer-unblock opcodes so nodes 0, 1, and 2 each get
distinct controls for counterparty commitment signing, per-commitment
points, and commitment secret release.

Make node B's primary signer-unblock controls retry all pending
channels once an operation is available, while keeping the older
channel-specific release-secret controls for now.

This avoids duplicated node coverage and makes the byte controls easier
to reason about in reduced test cases.
Make event processing robust to splice, close, spendable-output, and
bump-transaction events that can arise during on-chain cleanup.

Splice pending handling now finds the matching broadcast transaction by
txid instead of assuming queue order.
Add cleanup helpers and fuzz opcodes for monitor bump events,
broadcast confirmation, careful chain advancement, and node resyncs.

The all-events loop now advances messages, node events, monitor events,
pending transactions, and broadcasts until the harness quiesces.
Before final assertions, catch raw monitors up to node height and drive
timer ticks plus block advancement until pending work clears.

The final liveness probe now uses each channel's advertised sendable
range instead of a fixed amount that may be outside its limits.
Track payment hashes, paths, claims, sender outcomes, and closed
channels so settle-all can distinguish unresolved work from valid
force-close outcomes.

Keep payments pending until the sender observes PaymentSent or
PaymentFailed, including abandoned sends with committed HTLCs still in
flight. When reload selects an older raw monitor, catch it up to the
harness node height immediately so it observes historical funding
spends before later sync starts from the manager height.

This lets the harness accept sender failure for claimed dust paths
while still asserting that observable payment lifecycles complete.
Add explicit force-close fuzz actions for the A-B and B-C channels.

Enable holder commitment and holder HTLC signing together so on-chain
cleanup retries do not split the paired monitor-side signer operations.
The all-node holder-signing byte remains as a compatibility alias for
existing fuzz inputs.

The harness records dust HTLC paths before closing so later payment
resolution checks can account for claims blocked by dust outputs.
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.

4 participants