Skip to content

fuzz: add fuzzing that tests pruning - #404

Merged
apoelstra merged 2 commits into
BlockstreamResearch:masterfrom
stringhandler:st-fuzz-prune-generated
Nov 6, 2026
Merged

apoelstra merged 2 commits into
BlockstreamResearch:masterfrom
stringhandler:st-fuzz-prune-generated

Conversation

@stringhandler

Copy link
Copy Markdown
Contributor

In #403 we added a fix for pruning. This PR adds code to fuzz pruning of programs, which didn't exist before. It is LLM generated. It is also built on top of #403, so it needs that PR to be merged first.

Results

Measured on 20,000 generated programs, from the inputs that pseudorandom_inputs gives (the CI test uses the first 1,000):

Programs that pass every check 99.97% (the other 0.03% give PruningFailed)
Program size median 200 nodes, 90th percentile 523
Pruning hides at least one branch 95% (4.4 branches on average)
A case node keeps both branches 43%
Pruning shrinks the witness data 88%
A hidden branch shares a node with kept code 91%
Contains a disconnect node 89% (of the CI test's 1,000)

On the commit before the #391 fix, 64 of the CI test's 1,000 programs fail, the first at input 1. libFuzzer, with the -len_control=0 -max_len=4096 that fuzz.sh now passes, finds the bug from an empty corpus in 7, 12 and 32 seconds (3 runs).

Coverage of src/node/prune.rs, over the CI test's inputs and a 2,712-input libFuzzer corpus: 97.6% of lines and all 23 functions. The uncovered lines are error paths: programs that fail to run, and the rejections in the check after pruning.

Not covered: the generator never produces separate nodes with one IHR but different types inside them (0 of 20,000), so only unit tests cover merging such nodes before pruning.

@stringhandler
stringhandler marked this pull request as ready for review November 6, 2026 10:29
@stringhandler
stringhandler force-pushed the st-fuzz-prune-generated branch from efd860d to 0b5656c Compare November 6, 2026 10:30
@apoelstra

Copy link
Copy Markdown
Collaborator

In 229cc0c:

Your commit message has a stray comment :)

@apoelstra

Copy link
Copy Markdown
Collaborator

I can't tell whether the CI failure is related. I kicked the Windows job. It had a link error related to being unable to find rust_fuzzer_test_input

liblibfuzzer_sys-97574484c5670dec.rlib(libfuzzer_sys-97574484c5670dec.libfuzzer_sys.9bf7ff58a9991154-cgu.0.rcgu.o) : error LNK2019: unresolved external symbol rust_fuzzer_test_input referenced in function _RNCNvCsdodvM0aJ1Ye_13libfuzzer_sys15test_input_wrap0B3_
177
          D:\a\rust-simplicity\rust-simplicity\target\debug\deps\simplicity_fuzz-6ba0a1b08117996e.exe

The existing generator picks a combinator and then type checks it, so
few of its programs are well-typed, and those are tiny. Add a generator
which picks a type first, and then only combinators which can have that
type, so every program is well-typed. It never generates fail,
assertions or jets which can fail, so every program also runs. It
reuses nodes, so that pruning sees nodes shared between hidden and kept
code, runs some case nodes on two inputs, so that they take both
branches, and generates disconnect nodes. It uses Elements jets, because
libsimplicity decodes Elements jet codes. It does not generate separate
nodes with one IHR but different types inside them, which only unit
tests cover.

Add check_prune, which prunes a generated program and checks that:

* the pruned program keeps the CMR and runs,
* it decodes to the same IHR and AMR and encodes back to the same bytes,
* libsimplicity accepts it and computes the same CMR, AMR and IHR,
* libsimplicity rejects the unpruned program as anti-DOS exactly when
  pruning hid a branch.

Programs which pruning fails on with ExecutionError::PruningFailed are
counted rather than checked, and the tests require them to be rare.
Programs over a cost cap are skipped: their cost comes from running the
same nodes many times, which takes time without exercising pruning.

The prune_generated target calls check_prune, with long inputs from the
start, as only those give large programs. A test of the fuzz library
calls it on 1000 inputs, in a new job of the fuzz workflow.

Also build ElementsEnv::dummy with the test-utils feature, so that the
fuzz crate can run Elements jets.

Assisted-by: Claude
@stringhandler
stringhandler force-pushed the st-fuzz-prune-generated branch from 0b5656c to 698a2b0 Compare November 6, 2026 14:50
@stringhandler

Copy link
Copy Markdown
Contributor Author

fixed both

@apoelstra apoelstra 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.

ACK 698a2b0; successfully ran local tests

@apoelstra
apoelstra merged commit 7fb2601 into BlockstreamResearch:master Nov 6, 2026
28 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.

2 participants