funding: add coverage-guided fuzz harness for the channel-opening state machine - #10972
funding: add coverage-guided fuzz harness for the channel-opening state machine#10972MPins wants to merge 2 commits into
Conversation
🟢 PR Severity: LOW
🟢 Low (1 file)
AnalysisThis PR consists solely of a new file, Since this is the only file in the diff and it's a test file, it's also excluded from the file-count/line-count bump calculation (0 non-test files, 0 non-test lines changed), so no severity bump applies. No production code in To override, add a |
a0fcd4f to
3143430
Compare
e094463 to
e68ba03
Compare
Bartok9
left a comment
There was a problem hiding this comment.
Review
Thanks for investing in a real coverage-guided funding-state fuzz harness — this is high-value (channel open is a classic adversarial surface) and matches the spirit of growing fuzz coverage rather than only happy-path unit tests.
Concept ACK on the direction: event enum (funder/fundee, peer adversarial modes, conf/reorg, disconnect/reconnect) is the right shape for a state-machine fuzzer.
Please clarify before a full ACK
- Default CI / developer cost — Is the fuzz target skipped in plain
go test ./funding(build tags, short mode, or fuzz-only entrypoints)? A ~3kLOC harness is worth it if day-to-day tests stay fast; please document the intendedgo test -fuzz=... -fuzztime=invocation in the PR body orfundingpackage comment. - Failure oracle — Beyond panics, which invariants are checked (e.g. consistent chan IDs after confirm, no progress after fatal remote errors, funding tx / short chan id agreement across roles)? A short bullet list would help reviewers trust the harness.
- Entropy — Confirm peer/crypto/timing inputs are derived from fuzz data (no unintended wall-clock flakiness except explicit timeout paths).
- go.mod alignment — On a quick skim the new file imports look sensitive to btcd module paths; CI on this branch is the source of truth — green module build is enough.
Happy to re-review after the CI/skip + oracle notes land. Not requesting changes on concept — just wanting the operational story explicit for maintainers.
|
@MPins, remember to re-request review from reviewers when ready |
e68ba03 to
9e80330
Compare
|
Thanks for the fresh push (@MPins) — saw the two commits land ( Quick re-check against the open questions from the earlier concept-ACK review:
No blocker on concept. Will look again after CI settles and the run-cost note lands (PR body or package comment is enough). |
25f0c9e to
045eb23
Compare
|
Thanks for the additional force-pushes on this tip ( Re-skimmed the current head against the open items from the concept-ACK / last note: Addressed / looking good
Still nice-to-have (non-blocking for concept)
CI: checks on this tip just (re)queued after the latest push — still pending here. Will give a fuller pass once unit/lint are green on No change on concept: still a concept ACK. Happy to move toward a full ACK after green CI + the short run-cost blurb. |
Add FuzzFundingManagerFSM, a native Go fuzz harness that drives the funding manager's channel-opening state machine. A single shared SUT (Alice) is exercised against a per-flow counterparty manager (Bob), with the corpus byte stream decoded into a sequence of events over one or more concurrent funding flows. The harness treats the SUT as the system under test and imposes all adversarial/inconsistent values from the counterparty side, so the SUT validates and rejects them while, as originator, only ever emitting valid values.
createTestFundingManager takes a privKey but hardcoded alicePrivKey for the wallet controller, the signer and the secret key ring, so every manager derived funding and commitment keys from Alice's root key regardless of the identity it presented. chanIDSeed was likewise left as 32 zero bytes, making every manager produce the same pending channel ID sequence. Since lnwallet keys fundingIntents by pending channel ID alone and not by peer, that collides in a manager acting as funder for one channel and fundee for another, compiling the funder's reservation against the fundee's coinless intent. Use privKey for all three components and seed chanIDSeed from it.
045eb23 to
7b75250
Compare
|
Thanks for the further force-push (@MPins) — tip is now Quick delta vs the last note:
Concept ACK unchanged. Happy to look again after green unit/lint + the short run-cost blurb. |
|
@saubyk what is your opinion on the comments above? Personally, I think it's noise. |
Overview
This PR adds
FuzzFundingManagerFSM, a native Go coverage-guided fuzz harness that exercises the funding manager's channel-opening state machine end to end. It decodes the corpus byte stream into a sequence ofevents driving one or more concurrent funding flows against the manager.
Design
*Manager; each flow gets its own counterparty (Bob) manager. The SUT is what we're testing, so all adversarial or inconsistent values are imposed by the counterparty and the SUT must validate/reject them — as originator the SUT only ever emits valid values.channel_readymust be parked, not opened.BOLT-2
channel_typeconformanceOne family of oracles checks what the SUT emits against BOLT 2's
channel_typerequirements, rather than against what the funding manager on the other end happens to accept. The distinction matters here: both ends of the harness are realfunding.Managers, so an lnd-to-lnd flow agrees with itself even where both sides diverge from the spec, and the divergence stays invisible until a peer running the current spec shows up.Since the counterparty is a real manager, it never emits a non-conforming message on its own. The harness therefore rewrites the captured message before delivering it, one corpus-selectable variant per rule:
open_channelwithchannel_typestripped,option_channel_typenegotiatedopen_channelnaming a type neither side advertised the feature foropen_channelnaming a type the SUT's features cannot supportaccept_channelwith the echo strippedaccept_channelechoing a type the SUT never proposedTwo assertions then run on every message the SUT sends: an
open_channelmust carry achannel_type, and anaccept_channelmust echo the received one exactly.Against master this reaches three divergences, all of them fixed by #11064 :
Peer omits
channel_typewhileoption_channel_typeis negotiated. BOLT 2 says the receiver must fail the channel. lnd instead falls back to its default selection and puts that invented type inaccept_channel— which a conforming funder must reject, the echo having to match what it sent.Peer advertises neither bit 44 nor 45 but sends
channel_type, as current BOLT 2 requires of it, the field no longer being feature-gated. lnd accepts the channel and omits the echo entirely.Same peer, lnd funding. lnd's own
open_channelgoes out with nochannel_type.Until #11064 lands these three are logged rather than fatal, so the fuzzer can keep running — it otherwise stops within seconds of starting and reaches nothing else. The must-reject check for case 1 is commented out for the same reason; the case still surfaces, through the echo oracle, as the invented type in
accept_channel. Each spot carries aTODOto restore the hard assertion. The remainingchannel_typeoracles stay armed, master already enforcing the funder-side checks onaccept_channel.