feat(l1): bump zkEVM fixtures to v0.3.3 and tighten witness validation#6498
feat(l1): bump zkEVM fixtures to v0.3.3 and tighten witness validation#6498
Conversation
The v0.3.3 release adds tests for invalid/unusual execution witnesses that exercised existing gaps in ethrex's stateless validation. Fix them: - get_account_code / get_code_metadata now error on missing bytecode instead of silently falling back to empty code. A missing code hash means the witness is incomplete, so the guest program must not conclude anything about the state. - RpcExecutionWitness::into_execution_witness tolerates extra unused state entries that don't decode as trie nodes (matches other clients and passes validation_state_extra_unused_trie_node). - GuestProgramState::from_witness rejects non-contiguous or reordered block_headers_bytes; headers must form a contiguous ascending chain. - execute_blocks now propagates errors from get_first_invalid_block_hash instead of swallowing NoncontiguousBlockHeaders. Test runner: run_stateless_from_fixture reads the fixture's statelessOutputBytes valid flag and expects execution to fail when the fixture marks the block invalid (valid=0x00).
The regular Blockchain EF tests already exercise these tests as part of make test (which runs test-stateless), but a dedicated job makes failures visible and self-contained. Mirrors the matrix-less setup of the existing blockchain EF tests step.
🤖 Kimi Code ReviewThe PR introduces important security hardening for witness validation and fixes error-handling bugs. Overall quality is high with good documentation. Critical Security Improvements
The change from permissive to strict code lookup prevents a critical witness withholding attack. Previously, missing bytecode silently defaulted to empty, allowing a malicious prover to omit contract code and alter execution results. // Before (insecure):
None => {
println!("Missing bytecode... Defaulting to empty code.");
Ok(Code::default()) // Silent failure!
}
// After (secure):
.ok_or_else(|| {
GuestProgramStateError::Custom(format!(
"missing bytecode for hash {} in execution witness", ...
))
})Same applies to Bug Fix: Error Propagation
Previously, database errors from Err(e) => Err(ExecutionError::GuestProgramState(e)), // Was: Ok(())Witness Structure Validation
The contiguous header check prevents manipulation where reordered or gapped ancestor headers could trick the stateless client into accepting an invalid chain. Using Tolerance for Extra Witness Data
Switching from
Test Infrastructure
The negative test handling is well-structured. The Minor Observations
The upgrade to zkevm fixtures v0.3.3 ( Automated review by Kimi (Moonshot AI) · kimi-k2.5 · custom prompt |
Lines of code reportTotal lines added: Detailed view |
🤖 Codex Code Review
Aside from those points, the other behavior changes look directionally correct: surfacing Automated review by OpenAI Codex · gpt-5.4 · custom prompt |
🤖 Claude Code ReviewHere is my review: Review: feat(l1): bump zkEVM fixtures to v0.3.3 and tighten witness validationOverviewThis PR bumps the zkEVM fixture suite from v0.3.0 to v0.3.3, fixes 16 newly-exposed stateless validation bugs, and brings the test count to 93/93. The fixes span four distinct areas: trie node tolerance, block header ordering enforcement, missing-bytecode error propagation, and the test runner's invalid-fixture handling. Correctness
The change from a hard error on undecoded entries to a silent skip is well-reasoned. Node lookups happen by
The early ordering check is correct and important. One minor concern: if window[1].number != window[0].number + 1 {
if window[1].number.checked_sub(1) != Some(window[0].number) {Note also that a redundant contiguity check already exists at
This is a meaningful correctness improvement. The previous silent fallback to empty code was actively dangerous: a malformed witness missing bytecode for a contract would produce incorrect execution results that could still pass state-root validation (or worse, pass prover verification). The removal of the The two functions now produce identical error messages. Consider using a shared helper or at minimum consistent formatting, to avoid them drifting apart.
This was a real bug. Test Runner
The byte-offset parsing is correct. The comment on line 624 is helpful. A few observations:
#[test]
fn test_expected_stateless_valid() {
// 32 zero bytes (payload root) + 0x00 (invalid)
let invalid = format!("0x{}{}", "00".repeat(32), "00");
assert!(!expected_stateless_valid(Some(&invalid)));
// 32 zero bytes + 0x01 (valid)
let valid = format!("0x{}{}", "00".repeat(32), "01");
assert!(expected_stateless_valid(Some(&valid)));
assert!(expected_stateless_valid(None));
}
The two-stage handling (conversion failure vs. execution failure, each conditioned on Minor / Style
SecurityThe changes are net positive from a security standpoint:
SummaryThe core logic changes are correct and fix genuine validation gaps. The main actionable items are:
Everything else is clean. Good work fixing the silent fallback — that was the highest-risk item. Automated review by Claude (Anthropic) · sonnet · custom prompt |
Greptile SummaryThis PR bumps the zkEVM fixture suite from v0.3.0 to v0.3.3, then fixes the 16 stateless-validation gaps the new Confidence Score: 5/5Safe to merge — all changes correctly harden witness validation with no P0/P1 issues found. All four changes are logically sound and well-motivated by the new fixture test failures they fix. The error-propagation fix in execute_blocks closes a real silent-failure path. The contiguity check, missing-bytecode errors, and extra-node tolerance all match the expected semantics. No security regressions or data integrity issues identified. No files require special attention.
|
| Filename | Overview |
|---|---|
| crates/common/types/block_execution_witness.rs | Four targeted hardening changes: tolerate undecodable extra trie nodes (filter_map instead of early-return), validate header contiguity before BTreeMap insertion, and convert silent empty-code fallbacks in get_account_code/get_code_metadata into proper errors. All changes are logically sound. |
| crates/guest-program/src/common/execution.rs | Fixes error swallowing: the previous if let Ok(Some(...)) pattern silently ignored Err returns from get_first_invalid_block_hash (e.g. NoncontiguousBlockHeaders). The match now properly propagates those errors. |
| tooling/ef_tests/blockchain/test_runner.rs | Adds expected_stateless_valid() to parse the validity flag from statelessOutputBytes, and updates run_stateless_from_fixture to correctly handle witness-conversion failures and execution failures for intentionally-invalid fixtures. |
| tooling/ef_tests/blockchain/.fixtures_url_zkevm | Single-line bump from zkevm@v0.3.0 to zkevm@v0.3.3. |
Flowchart
%%{init: {'theme': 'neutral'}}%%
flowchart TD
A[run_stateless_from_fixture] --> B{block has\nexecutionWitness?}
B -- No --> SKIP1[skip]
B -- Yes --> C[parse statelessOutputBytes\nexpected_stateless_valid flag]
C --> D[RpcExecutionWitness::into_execution_witness]
D -- Error --> E{expect_stateless_valid?}
E -- false --> SKIP2[continue — invalid witness\nis expected failure]
E -- true --> FAIL1[return Err: conversion failed]
D -- Ok --> F[from_witness: validate\nheader contiguity]
F -- NoncontiguousBlockHeaders --> G[propagate via\nexecute_blocks match arm]
F -- Ok --> H[get_account_code /\nget_code_metadata]
H -- missing hash --> ERR[return Err: missing bytecode\nin execution witness]
H -- Ok --> I[execute_blocks]
I --> J{execute_result?}
J -- Ok --> K{expect_stateless_valid?}
K -- true --> SUCCESS[test passes]
K -- false --> FAIL2[return Err: expected failure\nbut succeeded]
J -- Err --> L{expect_stateless_valid?}
L -- false --> SUCCESS2[test passes — expected failure]
L -- true --> FAIL3[return Err: stateless\nexecution failed]
Reviews (1): Last reviewed commit: "Add a dedicated CI job for the stateless..." | Re-trigger Greptile
There was a problem hiding this comment.
Pull request overview
Updates the zkevm stateless fixture set and tightens stateless witness validation so ethrex correctly rejects intentionally-invalid witnesses introduced in the newer fixtures.
Changes:
- Bump zkevm fixtures download to
v0.3.3. - Make stateless fixture runner interpret
statelessOutputBytesvalidity flag and invert expectations for invalid-witness cases. - Tighten witness validation: propagate block-header validation errors, reject non-contiguous/reordered headers, error on missing bytecode, and tolerate non-decodable extra unused trie-node entries during witness conversion.
Reviewed changes
Copilot reviewed 5 out of 5 changed files in this pull request and generated no comments.
| File | Description |
|---|---|
| tooling/ef_tests/blockchain/.fixtures_url_zkevm | Updates zkevm fixtures URL to v0.3.3. |
| tooling/ef_tests/blockchain/test_runner.rs | Reads fixture validity flag and expects failure for invalid-witness fixtures; handles witness-conversion failures accordingly. |
| crates/guest-program/src/common/execution.rs | Propagates get_first_invalid_block_hash errors instead of swallowing them. |
| crates/common/types/block_execution_witness.rs | Loosens decoding of extra unused trie entries, enforces contiguous ascending header chain, and errors on missing bytecode/code-metadata in the witness. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
Moving the "headers must be a contiguous ascending chain" invariant into GuestProgramState::from_witness was too broad: ethrex-generated witnesses used by the regular stateless EF tests may list ancestor headers in other orders, and the strict check rejected them. Instead, apply the check only inside RpcExecutionWitness::into_execution_witness, which is the ingress point for fixture-provided witnesses (and the one actually exercised by the v0.3.3 validation_headers_non_contiguous_chain test). Revert the error-propagation change in execute_blocks for the same reason. Also rename the new CI job to "Test - Stateless zkEVM" to match the existing "Test - <runner>" naming convention, and fix cargo fmt.
Summary
.fixtures_url_zkevmfromzkevm@v0.3.0tozkevm@v0.3.3. v0.3.3 introduces newwitness_validation_*tests that exercise invalid execution witnesses (missing code, reordered headers, extra unused trie nodes, etc.).get_account_code/get_code_metadatanow error on missing bytecode instead of silently falling back to empty code. A missing code hash means the witness is invalid; the guest program must not conclude anything about the state.RpcExecutionWitness::into_execution_witnesstolerates extra unused state entries that don't decode as trie nodes (matches other clients, passesvalidation_state_extra_unused_trie_node).GuestProgramState::from_witnessrejects non-contiguous or reorderedblock_headers_bytes. Headers must form a contiguous ascending chain.execute_blocksnow propagates errors fromget_first_invalid_block_hashinstead of swallowingNoncontiguousBlockHeaders.run_stateless_from_fixturereads the fixture'sstatelessOutputBytesvalid flag (byte 32) and expects execution to fail when the fixture marks the block invalid (valid=0x00).Final: 93/93 zkevm tests pass under
--features stateless.Test plan
cd tooling/ef_tests/blockchain && make test-stateless-zkevmpassesmake test) is no worse than main