Skip to content

rpc/jsonrpc: source executionWitness codes from pre-state reads#21518

Merged
AskAlexSharov merged 1 commit into
mainfrom
awskii/witness-codes-prestate
May 30, 2026
Merged

rpc/jsonrpc: source executionWitness codes from pre-state reads#21518
AskAlexSharov merged 1 commit into
mainfrom
awskii/witness-codes-prestate

Conversation

@awskii

@awskii awskii commented May 30, 2026

Copy link
Copy Markdown
Member

Problem

debug_executionWitness's codes field is built from RecordingState.GetAccessedCode() — every bytecode reached via GetCode/GetCodeSize during execution, including contracts created in-block. That matches Geth's witness.AddCode semantics, but EEST fixtures (and the canonical stateless-witness format) are filled by Reth's Canonical mode, which only emits bytecodes loaded from pre-state by codehash and excludes in-block-created code. A stateless verifier re-derives created code by replaying the block, so emitting it is redundant over-inclusion.

This is the dominant remaining source of codes-witness divergence: a per-block +1 (sometimes +2) over-inclusion.

Fix

Source the witness codes from GetPreStateCode() (inner-reader / pre-block reads only) instead of GetAccessedCode(). One-line change plus a corrected doc comment.

The stateless verifier (witnessStateless.ReadAccountCode) already resolves in-block-created code from codeUpdates (written during re-execution) before consulting the witness codeMap, so the verify path does not regress.

Evidence (EEST zkevm@v0.4.0 corpus, ~2,881 fixtures)

Metric before (main codes path) after
Pass rate 94.6% 98.5%
codes witness mismatches 924 11
stateless-exec failures 0 0
state-root mismatches 0 0

A static sweep over all 924 pre-fix codes mismatches found that 100% (925/925) of the over-included bytecodes were absent from pre-state — every one was created in-block (278 survive into post-state, e.g. cross-tx selfdestruct-to-self and in-block EIP-7702 delegation markers; the rest transient). Zero regressions vs the prior best; 111 newly-passing fixtures.

The residual 11 codes mismatches are EEST eip8025_optional_proofs/witness_validation_codes/* negative-validation fixtures (deliberately truncated/padded witnesses that test a verifier's rejection path), plus one same-codehash edge case (witness_codes_create_same_hash_then_read).

Notes

  • This supersedes the narrower EIP-6780 transient-code filter approach (it covered only the deleted ∧ ∉ pre-state subset; the pre-state rule covers all in-block-created code).
  • RecordingState.GetAccessedCode() / OnCodeAccess are now unused by the witness builder but remain wired into the IBS code-read path; removing that machinery is deferred to keep this diff surgical.

Refs #21307.

The witness `codes` field was built from RecordingState.GetAccessedCode()
— every bytecode reached via GetCode/GetCodeSize during execution,
including contracts created in-block. EEST fixtures are filled by Reth's
Canonical witness mode, which only emits bytecodes loaded from pre-state
by codehash and excludes in-block-created code (a stateless verifier
re-derives that code by replaying the block).

Sourcing from GetPreStateCode() (inner-reader / pre-block reads only)
removes the over-inclusion. The stateless verifier already resolves
created code via codeUpdates before consulting the witness codeMap, so
nothing in the verify path regresses.

Measured against the EEST zkevm@v0.4.0 corpus: codes-witness mismatches
drop from 924 to 11, pass rate 94.6% -> 98.5%, zero stateless-execution
or state-root regressions. The residual 11 are EEST negative-validation
fixtures (deliberately truncated/padded witnesses) plus one same-codehash
edge case.

Refs #21307.
@awskii awskii requested review from lupin012 and yperbasis as code owners May 30, 2026 02:37
@AskAlexSharov AskAlexSharov added this pull request to the merge queue May 30, 2026
Merged via the queue into main with commit a6b3b96 May 30, 2026
90 checks passed
@AskAlexSharov AskAlexSharov deleted the awskii/witness-codes-prestate branch May 30, 2026 04:54
pull Bot pushed a commit to Dustin4444/erigon that referenced this pull request May 31, 2026
…tness headers (erigontech#21529)

## Problem

`debug_executionWitness`'s `headers` field carried only the **parent
header** plus the blocks **individually** reached via the BLOCKHASH
opcode (a sparse set). A stateless verifier validates each ancestor
header by chaining `parentHash` links back from the trusted parent, so
it needs a **contiguous** chain from the parent down to the oldest
accessed ancestor — the intermediate headers are load-bearing even when
no opcode read them directly.

## Fix

`collectAccessedHeaders` now fills the contiguous range
`[oldest-accessed .. parent]` instead of emitting a sparse `{parent} ∪
accessed` set. When no BLOCKHASH gap exists (the common case), the
output is unchanged — only blocks that reach deeper ancestors are
affected.

## Evidence (EEST `zkevm@v0.4.0` corpus)

Fixes **6 of 7** `eip8025_optional_proofs/witness_headers/*` fixtures.
Confirmed expected = contiguous `[1..parent]`:

| fixture | witness block | parent | expected headers | before | after |
|---|---|---|---|---|---|
| blockhash_at_offset | 11 | 10 | 1–10 (10) | 2 | ✅ |
| blockhash_boundary | 257 | 256 | 1–256 (256) | — | ✅ |
| multiple_blockhash_max_wins | 9 | 8 | 1–8 | 2 | ✅ |
| max_wins_across_multiple_transactions | 6 | 5 | 1–5 | 2 | ✅ |
| blockhash_in_reverted_tx / _inner_call | — | — | contiguous | — | ✅ |

The 7th, `witness_headers_extra_unused_older_ancestor`, is a
**verifier-tolerance** test — its description is *"A contiguous extra
older ancestor should still validate."* It ships a reference witness
padded with one ancestor older than any BLOCKHASH access; a minimal
correct producer cannot match it by strict equality. Out of scope for a
producer-comparison suite (same category as the `witness_validation_*`
negative tests).

No regressions: the change is a no-op for blocks without a BLOCKHASH
ancestor gap. `TestExecutionWitness` passes; `make lint` clean.

Refs erigontech#21307. Independent of erigontech#21518 (witness codes from pre-state).

Co-authored-by: lupin012 <58134934+lupin012@users.noreply.github.com>
pull Bot pushed a commit to Dustin4444/erigon that referenced this pull request May 31, 2026
erigontech#21539)

A pre-state contract whose code hash was already produced by an in-block
CREATE is redundant in `codes` — the verifier replays the CREATE and has
the bytecode.

Track hashes written via `UpdateAccountCode`; `ReadAccountCode` skips a
pre-state read whose hash was already created. Writes flush at tx
finalization, so a read *before* the create is still kept.

Corpus (zkevm@v0.4.0): fixes `create_same_hash_then_read`, keeps
`keeps_prestate_code_read_even_if_later_created_with_same_hash`. codes
mm 11→10, no regressions.

Builds on erigontech#21518. Refs erigontech#20534.
yperbasis pushed a commit to Sahil-4555/erigon that referenced this pull request Jun 1, 2026
…1531)

Order `codes` and `state` lex-ascending by content (was: codes by
codehash, state by trie-traversal). Confirmed against zkevm@v0.4.0
fixtures.

`state` is sorted after `verifyWitnessStateless` — its `RLPDecode`
treats node 0 as the trie root, so the witness stays root-first through
verification.

No pass-rate change (multiset compare); serialization alignment only.

Builds on erigontech#21518. Refs erigontech#20534 (causes 1 & 3).
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.

debug_executionWitness should be 100% correct and work fine for Zilkworm

2 participants