Skip to content

perf(debug-trace-server): build the witness execution form once per block - #185

Merged
flyq merged 18 commits into
mainfrom
liquan/perf/share-light-witness-executor
Oct 21, 2026
Merged

flyq merged 18 commits into
mainfrom
liquan/perf/share-light-witness-executor

Conversation

@flyq

@flyq flyq commented Aug 11, 2026

Copy link
Copy Markdown
Contributor

Summary

Every trace request deep-cloned the block's LightWitness out of the shared Arc<BlockData> (data.witness.clone() — the multi-hundred-KB-to-MB kvs/levels maps) and then rebuilt the LightWitnessExecutor direct-lookup table from scratch, two O(witness) passes paid on every debug_traceTransaction (transaction responses are never response-cached) and on every block-level response-cache miss or bypass (JS, mux, struct-logger-with-flags). Surfaced by the review pass on #184 as the largest remaining hot-path allocation; pre-existing, so fixed here separately.

Change

BlockData.witness now stores the LightWitnessExecutor — witness plus lookup table — built once where the BlockData is assembled (DB load, remote fetch, test fixtures) and shared by every request served from that entry, including repeated traces of a hot block. The trace entry points (trace_block, trace_transaction, parity_trace_block, parity_trace_transaction) and TracingEnv borrow it (&LightWitnessExecutor); no per-request clone or table rebuild remains. LightWitnessExecutor gains light_witness() (for extract_code_hashes and witness-size accounting) and lookup_table_memory_bytes(), which the block-data cache weigher now charges alongside the witness payload so the cache budget reflects the stored table.

Testing

cargo test --workspace: 374 passed / 0 failed; clippy / fmt / cargo-sort clean.

Notes

Based on #184 (liquan/chore/upgrade-reth-2.3) — merge after it lands.

flyq added 14 commits August 10, 2026 15:31
reth v2.3.0's V2 sparse tries merge extension nodes into their child branches
and silently drop extensions whose child branch is absent from the witness — a
legal minimal exclusion proof that the frozen MegaETH witness format produces,
and whose split-on-insert never needs the child. Vendors the proven v1.6.0
serial sparse trie into stateless-core, reveals the storage trie directly
(dropping the synthesized account-leaf wrapper reveal_witness required), and
adds a regression test for the extension-split shape.
…update docs

- Drop the returnValue 0x-strip shim: alloy-rpc-types-trace 2.x serializes the
  prefix on both mega-reth and this server, so emitting the frame as-is is what
  now matches.
- rust-version 1.94 (mega-evm and the optimism monorepo crates require it).
- Document the vendored withdrawal sparse trie and erc7562Tracer handling.
The reth section now holds only paradigmxyz/reth tag v2.3.0 crates;
alloy-op-evm, alloy-op-hardforks, and reth-optimism-chainspec move next to
the op-alloy crates they share a source identity (and pin comment) with.
Replaces the vendored reth-1.6 serial sparse trie (~1.9k lines) with a
~250-line linearization of the witnessed storage trie into
alloy_trie::HashBuilder — the primitive reth's own eth_getProof generator is
built on. Surviving and updated leaves stream as add_leaf (leaf paths are the
hashed slots and never change); unwitnessed subtrees stream as add_branch
(position and hash invariant under edits elsewhere); every structural change
— extension splits, branch collapses, new branches — falls out of the stream
shape inside the builder, so the module carries no trie mutation code at all.
A pre-pass with no updates must reproduce storage_root exactly, self-checking
the walk on every witness before its post-state root is trusted. Updates the
witness cannot prove (descent into an unwitnessed subtree, a collapse
adopting an unwitnessed node) fail closed, matching reth-1.6 behavior.
Drops the reth-trie-common, smallvec, and auto_impl dependencies; adds tests
for collapse merging, unwitnessed-survivor collapse, unwitnessed-region
descent, and diverging-removal no-ops.
- withdrawals: skip the post-pass when a block carries no withdrawal updates;
  factor the branch-child partition into child_regions/ChildRegion shared by
  emission and survival walks; drop the dead empty-region arm; slice-pattern
  the sole-survivor check; add_insert_leaves helper for the four insert-only
  loops; flatten withdrawals/ back to withdrawals.rs; dedupe test fixtures
  (TrieNode::rlp, SLOT_B, ext_with_absent_branch).
- debug-trace-server: reject the unsupported erc7562Tracer at the
  RequestShape::classify gate (-32602 before any block data is fetched), the
  executor arms staying as unreachable backstops; drop per-tx EvmState clones
  the revm-40 API made avoidable (block/tx prestate, mux); take tracing
  inspectors by mem::replace instead of cloning their recorded trace arenas.
- executor: project slot_num from header.slot_number like every other
  BlockEnv field instead of pinning 0.
Beyond the textual Cargo.toml conflict (alloy family stays 2.0.5; #172's new
alloy-rpc-client added at 2.0.5):
- workspace reqwest 0.12 -> 0.13: #172 hands our tuned client to alloy's
  transport, which rides reqwest 0.13 in the 2.x era; stateless-r2 keeps its
  own 0.12 pin (API-coupled to mega-reth, no type crossing).
- #169's builtin_tracer_kind gains the alloy-2.x Erc7562Tracer arm in its
  not-routed-here group.
- mux rejection wording: since alloy 2.x an unknown tracer key in a mux
  config parses as a JS tracer name and is rejected by MuxInspector
  construction rather than serde; the error context stays 'invalid muxTracer
  tracerConfig' so #169's defense-in-depth test keeps its contract.
mega-reth's upstream/reth workspace — the branch this upgrade wave targets —
is on reqwest 0.13, so the API-coupled pin follows (rustls-tls feature is
named rustls since 0.13; http2 note stays valid, the feature is opt-in now).
The lockfile drops to a single reqwest 0.13.4.
The no_std build (--no-default-features) has no prelude format!; import it
explicitly like evm_database.rs does. Fixes the no-std and coverage CI jobs,
which both compile stateless-core without default features.
- tracing_executor: extract the four TracerKind frame builders into one
  extract_trace_frame! macro shared by the block and tx paths (the twin
  matches this PR had to edit in lockstep six times); the FlatCall take now
  really is the per-tx reset (guarded tail); mux inspectors reset from a
  pristine clone instead of re-cloning + re-parsing the config each tx (also
  removes the only dispatcher unwrap); erc7562 rejection has one home in
  builtin_tracer_kind and the dispatchers route it there; preceding-tx
  replay in the tx-level and parity paths seeds a none-config inspector
  instead of recording every step just to discard it; drop a dead duplicate
  binding.
- rpc_service: classify_and_gate dedups its rejection tail behind an
  exhaustive match (a future RequestShape variant must make an explicit gate
  decision); new test pins the -32602 for unsupported tracers.
- response_cache: the label-conformance test now covers the Unsupported
  variant.
- withdrawals: the leaf arm reuses sortedness instead of collect+sort; the
  split_at_prefix upper bound is a second partition_point.
…lock

Every trace request deep-cloned the block's LightWitness (multi-hundred-KB
kvs/levels maps) out of the shared Arc<BlockData> and then rebuilt the
LightWitnessExecutor direct-lookup table from scratch — paid per
debug_traceTransaction (never response-cached) and per block-level cache
miss/bypass. BlockData now stores the LightWitnessExecutor, built once where
the BlockData is assembled (DB load, remote fetch, fixtures), and the trace
entry points borrow it; the block-data cache weigher charges the lookup
table alongside the witness payload.
@mega-maxwell

mega-maxwell Bot commented Aug 11, 2026 •

Copy link
Copy Markdown
Contributor

Claude review status

Living comment — rewritten in place. The review workflow keeps this single comment up to date instead of posting a new one each round, so it always describes the latest reviewed commit and the earlier text is intentionally gone. No reply is needed here; reply to a finding in its own review thread, and answer an open question in a reply on this PR. The next review round reconciles your answer.

✅ Review clean

Last reviewed: head ed180a36 · updated 2026-10-21T12:19:24+00:00

New this round: 0 finding(s), 0 question(s) · Resolved this round: 0 · Open questions: 0

@codecov

codecov Bot commented Aug 11, 2026 •

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 0% with 6 lines in your changes missing coverage. Please review.
✅ Project coverage is 92.8%. Comparing base (04c1944) to head (ed180a3).
⚠️ Report is 1 commits behind head on main.

Files with missing lines Patch % Lines
crates/stateless-core/src/light_witness.rs 0.0% 6 Missing ⚠️

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

Base automatically changed from liquan/chore/upgrade-reth-2.3 to main October 13, 2026 02:57
flyq and others added 3 commits October 14, 2026 17:52
PR #184 was squash-merged, so this branch (forked from an intermediate
copy of #184) conflicted with main across unrelated files.
Resolve by taking main's tree, which carries the final #184, and
re-applying this PR's change on top of it instead of reconciling the
intermediate #184 history.

- light_witness.rs: main dropped the `impl LightWitnessExecutor` block
  (`kvs()` was unused); re-add it with only `light_witness()` and
  `lookup_table_memory_bytes()`.
- tracing_executor.rs: keep main's `BlockExecutionEnv` plumbing in
  `TracingEnv` and borrow the shared `&LightWitnessExecutor`.
- Adapt tests added on main (single-tx vs block trace parity, DB-tier
  drift) to the executor-typed `BlockData::witness`.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
…tidy witness naming

Simplify pass over the shared-executor change:

- Add `light_witness_executor_memory_bytes` beside `light_witness_memory_bytes`
  so the block-data weigher and its test share one definition of an
  executor's footprint, with a test pinning the lookup-table term.
- Name the witness `witness` across the trace entry points and the
  `TracingEnv` field instead of the stale `light_witness`.
- Split `test_support::fixture_parts` out of `fixture_block_data` so tests
  that need a plain `LightWitness` stop round-tripping through the executor.
- Shorten the `BlockData.witness` doc to what it is.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
…fetch pipeline and cache weight

- do_fetch_block_data: list the execution-form build as the final pipeline step, so a reader
  profiling slow cold fetches sees where the lookup table is built.
- README: say the block-data cache charges a witness as its payload plus the lookup table,
  so an entry weighs more than its witness maps alone.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
@Troublor
Troublor force-pushed the liquan/perf/share-light-witness-executor branch from 00a7b9c to 4f0c128 Compare October 19, 2026 13:13
@chatgpt-codex-connector

chatgpt-codex-connector Bot commented Oct 19, 2026 •

Copy link
Copy Markdown

Codex Review Summary

This comment shows the latest Codex review activity on this pull request.

Review Status Commit Review trigger
📝 Code Review ✅ Completed 2026-10-21T12:15:27.804080Z ed180a3 New commits
ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review" or "@codex security review".

Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings.

…ss footprint math

- Add `BlockData::new`, the one place the witness is wrapped in its execution form.
- Keep the lookup-table footprint in `witness_size` (`LOOKUP_ENTRY_BYTES`); core exposes only `plain_keys()`.
- Let `TracingEnv` own its `WitnessDatabase`, dropping `create_witness_db` and the redundant fields.
- Make the executor-footprint test assert the table is charged instead of restating the formula.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@flyq
flyq merged commit 6a3af3d into main Oct 21, 2026
12 of 13 checks passed
@flyq
flyq deleted the liquan/perf/share-light-witness-executor branch October 21, 2026 12:47
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.

3 participants