Skip to content

chore: upgrade to reth v2.3.0 / revm 40 / alloy 2.0.5 (mega-evm revm-40 branch) - #184

Open
flyq wants to merge 14 commits into
mainfrom
liquan/chore/upgrade-reth-2.3
Open

chore: upgrade to reth v2.3.0 / revm 40 / alloy 2.0.5 (mega-evm revm-40 branch)#184
flyq wants to merge 14 commits into
mainfrom
liquan/chore/upgrade-reth-2.3

Conversation

@flyq

@flyq flyq commented Aug 10, 2026

Copy link
Copy Markdown
Member

Summary

Switches the workspace from the reth v1.6.0 era to the reth v2.3.0 stack, following mega-evm's revm-40 upgrade (megaeth-labs/mega-evm#365): reth git tags v1.6.0 → v2.3.0, revm 27.1.0 → 40.0.3, revm-inspectors 0.27.3 → 0.40.1, alloy 1.0.23 → 2.0.5, alloy-evm 0.15.0 → 0.36.0, alloy-hardforks 0.2.7 → 0.4.7, alloy-primitives 1.3.0 → 1.6.0, alloy-trie 0.9.0 → 0.9.4, op-alloy 0.18.12 → 2.0.0, reqwest 0.12 → 0.13 across the workspace including stateless-r2's explicit pin (its API exposes &reqwest::Client and must match mega-reth, whose upstream/reth workspace is on 0.13), leaving a single reqwest in the lockfile, and mega-evm tag v1.7.0 → branch cz/chore/upgrade-revm-40 (locked at 8d1fc07). salt stays at v1.0.5. MSRV moves to 1.94.

OP crates: one source identity

crates.io has no op-revm / alloy-op-evm release built against revm 40, so mega-evm pins the whole OP family to the Optimism monorepo at rev f67d87cd; this workspace must share that exact source or OpTxEnvelope / receipt / hardfork types split into incompatible copies. alloy-op-evm, alloy-op-hardforks, op-alloy-consensus/network/rpc-types, and reth-optimism-chainspec (which moved out of paradigmxyz/reth into the monorepo) are all pinned there, grouped under # op in the manifests; the monorepo's own reth deps use tag = "v2.3.0", the same source as our direct reth pins, so the graph unifies with a single copy of every crate (verified in Cargo.lock).

Withdrawal MPT verification reimplemented on alloy-trie

reth v2.3.0's sparse tries moved to a V2 node model that merges extension nodes into their child branches. An extension whose child branch is absent from the witness — a legal minimal exclusion proof the frozen MegaETH witness generator emits, whose split-on-insert never reads the child — cannot be represented: every reveal path silently drops it and the insert then fails on a blind node (caught by the synthetic fixture at block 360; both v2.3.0 trie impls also panic on standalone extensions, and reth's own flat-witness entry point shares the gap with no in-tree users). Upstream sidesteps this by making its internal proof producers over-retain extension children (AddedRemovedKeys), a contract our frozen witness format — and the immutable historical witnesses in R2 — can never satisfy.

MptWitness::verify (crates/stateless-core/src/withdrawals.rs) now linearizes the witnessed storage trie into alloy_trie::HashBuilder — the same primitive reth's eth_getProof generator is built on — as a sorted stream: add_leaf for surviving/updated leaves (leaf paths are the hashed slots and never change) and add_branch for unwitnessed subtrees (position and hash invariant under edits elsewhere). All structural changes — extension splits, branch collapses, new branches — fall out of the stream shape inside the builder, so the module contains no trie mutation code; this also returns to the storage-trie-only design of the original implementation (#16), dropping the synthesized account-leaf wrapper #119 introduced. A pre-pass with no updates must reproduce storage_root bit-for-bit before the post-pass output is trusted (skipped entirely when a block carries no withdrawal updates); updates the witness cannot prove — descent into an unwitnessed subtree, a collapse adopting an unwitnessed node — fail closed, matching the previous sparse-trie behavior. The reth-trie-common / reth-trie-sparse dependencies are gone. New tests cover the extension-split regression, collapse merging, unwitnessed-survivor and unwitnessed-descent fail-closed paths, and diverging-removal no-ops.

Trace server: output drift and request gating

Struct-logger traces drift in serialization only (execution is byte-identical per mega-evm's replay gate): logs gain "refund": 0, memory words and returnValue gain 0x prefixes — post-upgrade mega-reth serializes the prefix too, so the old strip-shim is removed. Gas values are unchanged (tx_gas_used() equals the old gas_used() with EIP-8037 pinned off). The new alloy builtin erc7562Tracer is not implemented: RequestShape classifies it as Unsupported and classify_and_gate rejects it with -32602 before any block data is fetched, the executor arms remaining as unreachable backstops. Per-tx EvmState clones the revm-40 API made avoidable are dropped, and tracing inspectors are taken by mem::replace instead of cloning their recorded trace arenas. Since alloy 2.x, an unknown tracer name inside a mux config parses as a JS-tracer key and is rejected at MuxInspector construction rather than by serde; the error stays request-attributable with the same tracerConfig wording (#169's defense-in-depth test keeps its contract).

Other API adaptations

AccountInfo gains account_id: None (witness state is address-keyed), BlockEnv.slot_num projects from header.slot_number like every other field (EIP-7843; absent until Amsterdam), BundleState::state uses the AddressMap fixed-bytes hasher, and mega-evm's run_transaction outcome carries result_and_state instead of separately movable fields.

Testing

cargo test --workspace: 373 passed / 0 failed (the ignored suites are the pre-existing env-gated live-server tests), including the mainnet single-block fixtures, the synthetic-chain pipeline test that exposed the trie regression, and the new withdrawal edge-case tests. cargo clippy --workspace --all-targets --all-features, cargo fmt --all --check, and cargo sort --check are clean. Latest main (#169, #172) is merged in.

Notes

mega-evm is pinned to a branch while megaeth-labs/mega-evm#365 is open; re-pin to a tag once it lands. mega-reth's upstream/reth branch pins the Optimism monorepo at op-reth/v2.3.3 (2063657) rather than f67d87cd — if mega-reth ends up embedding stateless-core, the repos must converge on one monorepo rev or the OP types will not unify.

flyq added 4 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.
@mega-maxwell

mega-maxwell Bot commented Aug 10, 2026

Copy link
Copy Markdown

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 did not finish

Attempted head 99485a5d · updated 2026-08-11T01:01:57+00:00

This round did not publish: MODEL_ACTION_FAILED in phase review_retry. Anything listed below is from the last round that did. Re-run the workflow or push a new commit to try again.

@codecov

codecov Bot commented Aug 10, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 93.84615% with 16 lines in your changes missing coverage. Please review.
✅ Project coverage is 91.2%. Comparing base (73a8827) to head (99485a5).

Files with missing lines Patch % Lines
crates/stateless-core/src/withdrawals.rs 93.6% 16 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.

flyq added 5 commits August 10, 2026 16:44
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.
@flyq flyq changed the title chore: upgrade to reth v2.3.0 stack (revm 40, alloy 2.0.5) chore: upgrade to reth v2.3.0 / revm 40 / alloy 2.0.5 (mega-evm revm-40 branch) Aug 10, 2026
flyq added 2 commits August 10, 2026 18:19
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.
@flyq
flyq marked this pull request as ready for review August 10, 2026 10:43
flyq added 2 commits August 10, 2026 19:03
- 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.
Comment thread Cargo.toml
revm-bytecode 11 reordered Bytecode's serde enum, so rows written by
pre-upgrade binaries no longer decode (or, for 19-byte codes, silently
mis-decode into EIP-7702 delegations to garbage addresses). Treat decode
failures and keccak mismatches as cache misses: both binaries re-fetch
misses through the hash-verified RPC tier and overwrite the stale row,
so an in-place upgrade degrades to a one-time re-fetch instead of
halting the node. Regression tests pin real revm-bytecode 6.2.2 row
encodings for both failure shapes.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 2f90a7b3ed

ℹ️ About Codex in GitHub

Codex has been enabled to automatically 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".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

Comment on lines +367 to +371
for (idx, child) in branch.as_ref().children() {
let split = rest.partition_point(|(p, _)| p.get_unchecked(path.len()) <= idx);
let (region, tail) = rest.split_at(split);
rest = tail;
regions.push(ChildRegion { idx, child: child.cloned(), updates: region });

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Emit updates for empty branch slots

When a withdrawal write targets an empty branch child whose nibble is greater than the branch's last populated child, this loop never creates a ChildRegion for it: rest still contains the insert after the loop, and release builds ignore the debug_assert!, so the new leaf is never streamed into HashBuilder. A valid block that adds such a withdrawal slot computes the old withdrawal root and is rejected; the branch walk needs to represent all 16 child slots or otherwise drain the remaining updates.

AGENTS.md reference: AGENTS.md:L132-L132

Useful? React with 👍 / 👎.

Comment on lines +367 to +371
for (idx, child) in branch.as_ref().children() {
let split = rest.partition_point(|(p, _)| p.get_unchecked(path.len()) <= idx);
let (region, tail) = rest.split_at(split);
rest = tail;
regions.push(ChildRegion { idx, child: child.cloned(), updates: region });

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Emit updates for empty branch slots

When a withdrawal write targets an empty branch child whose nibble is greater than the branch's last populated child, this loop never creates a ChildRegion for it: rest still contains the insert after the loop, and release builds ignore the debug_assert!, so the new leaf is never streamed into HashBuilder. A valid block that adds such a withdrawal slot computes the old withdrawal root and is rejected; the branch walk needs to represent all 16 child slots or otherwise drain the remaining updates.

Useful? React with 👍 / 👎.

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