How we know it works
Verification
Every change goes through three independent gates: a source build, a sample regression run, and an end-to-end zkVM proof. This page explains what each one tests and where to read the results.
Three gates, three failure modes
| Gate | What it catches | Where to read it |
|---|---|---|
Source build (build-riscv64.yml) |
Renamed musl/runtime symbols, unresolved wraps, broken module compilation, missing toolchain. | Actions tab |
| Sample regression | Behavioural drift in the runtime shims — a soft-float helper that suddenly gets reached, an allocator that starts handing out misaligned pointers. | zk-testing dashboard |
| End-to-end proof | Real workload regressions: a state-transition function that produces wrong output, a proof that takes 10× longer to generate, a binary that no longer loads in the prover. | zk-testing dashboard project 1 |
The README badges at the top of the repository surface the latest status of each gate.
Gate 1 — build-riscv64.yml
The GitHub Actions workflow does the same thing build.sh all riscv64
does locally:
- run: ./build.sh modules riscv64
- run: ./build.sh bflat riscv64
- run: ./build.sh layouts riscv64
It runs in the project’s
Dockerfile.build
image, which contains the full RISC-V64 cross-toolchain, the .NET SDK,
and the Python dependencies for patch_elf.py (LIEF and pyelftools).
Each module compiles with its own command line:
module.c→riscv64-linux-gnu-gcc -march=rv64imadmodule.S→riscv64-linux-gnu-as --march=rv64ima --mabi=lp64module.cpp→riscv64-linux-gnu-g++ -march=rv64imad
Failure modes this gate catches:
- A wrap target that no longer exists in upstream musl (the linker produces an undefined-symbol error during the layouts build).
- A module that fails to compile because a header changed (immediate compiler error).
- A python dependency drift in
patch_elf.py(caught when the layouts pipeline tries to invoke the postprocessor). - A change to
BuildCommand.csthat breaks the cross-architecture path for x86-hosted builds (the layouts target builds both Linux- and Windows-hosted variants).
Gate 2 — Sample regression
Every directory under samples/
is built with --libc zisk_sim and run under qemu-riscv64. Output is
checked against a known-good baseline.
Gate 3 — End-to-end zkVM proofs
The Nethermind
StatelessExecutor — a
real Ethereum state-transition function written in real production C# —
is built with --libc zisk and proven inside Zisk on every commit.
Results are pushed to the
zk-testing dashboard.
The dashboard surfaces three things:
- Proof success
- A green badge on the README means the latest commit produced a valid proof. A red badge means the binary either failed to load, crashed during execution, or produced output that didn't match the canonical reference.
- Proof timing
- How long Zisk takes to prove the workload. A sudden regression usually means a runtime shim started doing more work — for example, the bump allocator hitting a different code path that triggers many more allocations.
- Binary size
- The size of the postprocessed ELF. Tracks the cost of the workload in prover steps and is a leading indicator for the previous metric.
Manual smoke tests
Before any release, the maintainers run a short manual checklist that covers cases CI can’t easily exercise:
- Build a fresh layout from a clean checkout in the Docker image.
- Build
bflat-libziskosand link it into a sample with--extlib. - Smoke-test the resulting binary under both
zisk_sim(QEMU) and the Zisk prover. - Walk a
--print-fn-boundariesreport by eye looking for unexpected function shapes (anything where EH and objdump disagree by more than a single instruction is investigated). - Verify the symbol-size HTML chart (
--symchart) for the Nethermind state-transition build, watching for sudden jumps in any single module’s contribution.
Reading the regression dashboard
The badge at the top of the README links to the project page on
zk-testing.nethermind.dev. The page shows:
- Per-commit proof status (success / failure / not yet attempted).
- A trendline of proof generation time over the last N commits.
- The diff between the current commit’s output and the reference.
- Build artifacts: the
.elf, the.symchart.html, and the.print-fn-boundaries.txtfor inspection.
If a commit regresses any gate, the bisect is usually a one-step operation against the diff in the dashboard. The combination of a loud, deterministic failure mode (gate 2) and a precise quantitative signal (gate 3) means even subtle behavioural drifts have a place to surface.
When in doubt
Run the simulator path locally:
$ bflat build samples/HelloWorld/hello.cs --os linux --libc zisk_sim -x
$ qemu-riscv64 ./hello
Hello world!
If --libc zisk_sim works and the equivalent --libc zisk build
crashes inside Zisk, the difference is almost always in the
postprocessor — --print-fn-boundaries is the next step.