Skip to content

refactor: single home for block-execution env assembly, fixing the fallback branch's BlockLimits drift - #171

Merged
flyq merged 10 commits into
mainfrom
liquan/refactor/block-execution-env
Aug 11, 2026
Merged

refactor: single home for block-execution env assembly, fixing the fallback branch's BlockLimits drift#171
flyq merged 10 commits into
mainfrom
liquan/refactor/block-execution-env

Conversation

@flyq

@flyq flyq commented Aug 1, 2026

Copy link
Copy Markdown
Member

Summary

Extracts create_block_execution_env in stateless-core::executor — the (chain_spec, header, ext_env) → (EvmEnv, MegaBlockExecutorFactory, MegaBlockExecutionCtx) assembly incl. the hardfork → BlockLimits mapping — and has both replay_block and the trace server's TracingEnv::new consume it. This deletes the trace server's hand-copied assembly (tracing_executor.rs) and with it the one line where the copies had drifted.

Root cause (history)

  • BlockLimits::no_limits().with_block_gas_limit(header.gas_limit) entered core's replay fallback in chore: Upgrade salt, mega-evm version & update test_data #53 (2025-12-13), when the mega-evm upgrade replaced BlockLimits::from_evm_env(&evm_env) with the hardfork-aware mapping — core's replay has enforced the block's own gas ceiling in every era.
  • The debug-trace-server was born seven weeks later in feat: add standalone debug-trace-server for debug_*/trace_* RPC methods #69 (2026-02-03) and copied the mapping line-for-line — except its fallback is a bare BlockLimits::no_limits(), from its very first commit; no commit since ever touched it (git log -S'with_block_gas_limit' on the file: zero hits) and feat: add standalone debug-trace-server for debug_*/trace_* RPC methods #69's description never mentions limits.
  • The omission looks accidental rather than deliberate: a deliberate "tracing should be unlimited" policy would loosen the hardfork branch too, but that branch kept full hardfork limits — and the adjacent comment even says "match validator behavior".

Behavior

On MegaETH mainnet the fallback branch is unreachable (MiniRex activates at genesis, so hardfork() is always Some), and for canonical blocks the two fallbacks execute identically anyway (gas_used ≤ gas_limit by consensus). The only observable change: on a chain with no hardfork schedule, tracing a block that exceeds its own declared gas limit now fails the same way validation does, instead of silently tracing past the ceiling.

Testing

Two new regression tests pin the mapping through the shared helper: the no-hardfork fallback must equal no_limits().with_block_gas_limit(header.gas_limit) exactly, and the active-hardfork path must equal BlockLimits::from_hardfork_and_block_gas_limit(..) (BlockLimits derives PartialEq, so drift in any limit field fails the test). Full workspace suite 330 passed / 0 failed; fmt / clippy (0 warnings) / cargo sort / no-std check all clean.

Notes

Future mega-evm upgrades (new hardfork variants, new BlockLimits fields, ctx changes) now get wired once instead of mirrored by hand across the two binaries — this was the churn point flagged in the upgrade checklist.

🤖 Generated with Claude Code

Reachability (review follow-up)

The divergence lives only on the "no MegaETH hardfork active" fallback branch. Mainnet and testnet genesis both schedule hardforks, so chain_spec.hardfork(ts) is Some from genesis onward and the two paths already agreed in production. This PR is therefore preventive consolidation, plus fixing the fallback branch's divergence — the fallback is reachable only for a genesis with no MegaETH forks scheduled (bare devnets, test harnesses), which is also why the merge carries no production urgency.

flyq and others added 2 commits August 1, 2026 15:52
… binaries

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…imits drift

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@mega-maxwell

mega-maxwell Bot commented Aug 1, 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 d536e8a9 · updated 2026-08-04T14:48:25+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 1, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 92.2%. Comparing base (11078d7) to head (d536e8a).

☔ 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 and others added 3 commits August 2, 2026 16:43
/simplify pass over the PR:
- create_block_execution_env returns a named BlockExecutionEnv<ENV> instead
  of an anonymous 3-tuple: the assembled shape (factory generics included)
  is now spelled once in core, and TracingEnv collapses its three mirrored
  fields into one, letting the trace server drop its mega_evm and
  OpAlloyReceiptBuilder imports — closing the residual drift channel the
  single-home consolidation left open
- module doc's Key Functions list now advertises create_block_execution_env
  as the shared entry point (create_evm_env demoted to its sub-step)
- test helper uses LightWitness::default() instead of a field-by-field
  struct literal

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…method

The macro existed to bind an executor whose concrete type the bin could
not reasonably name — spelling it there would re-import the mega-evm
type vocabulary. With BlockExecutionEnv now the named single home in
core, that home can also own the executor prologue:
start_executor_with_inspector creates the executor and applies the
pre-execution changes, returning the concrete EnvExecutor type (spelled
once, next to the env that produces it). The ten dispatch sites become
plain method calls with inferred types, and the macro is gone;
executor.evm field accesses keep working on the concrete type.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…nction

The replay loop needs only trait-level access to the executor, so a
generic fn with the same HRTB shape core's execute_transactions already
uses (for<'t> &'t Recovered<OpTxEnvelope>: ExecutableTx<E>) expresses it
without naming any concrete executor type. The bin gains a direct
op-alloy-consensus dependency for the envelope type in the bound.

The two remaining macros stay by necessity: log_at! (tracing levels must
be compile-time constants; the runtime-level dispatch IS the macro) and
the validator's metric! (const &'static str concatenation requires
concat!).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

@vincent-k2026 vincent-k2026 left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

The consolidation is right and the test pins it. One question about how the title frames the fix.

What's good

  1. The drift is real and was silent: the trace server used BlockLimits::no_limits() while the validator used no_limits().with_block_gas_limit(header.gas_limit). Same block, and tracing could succeed where validation would hit the gas ceiling — the worst kind of divergence, because tracing is the tool you reach for when validation disagrees with you. With create_block_execution_env as the single home, the two cannot fork again, and execution_env_no_hardfork_fallback_caps_block_gas fails if someone reintroduces the fully-unlimited fallback.
  2. setup_executor! / replay_preceding_txs! become a method and a generic function. The macros existed to dodge spelling the executor type; BlockExecutionEnv::start_executor_with_inspector spells it once, next to the env that produces it, and every call site binds by inference. Nine call sites got simpler.

Non-blocking

Please make the motivating case explicit. The divergence only exists on the "no MegaETH hardfork active" fallback branch. Mainnet and testnet genesis both schedule hardforks, so chain_spec.hardfork(ts) is always Some and the two paths already agree in production. "fixing BlockLimits drift" in the title reads like a live bug.

If it is unreachable in production, saying so — "preventive consolidation, plus fixing the fallback branch's divergence" — is more accurate and costs nothing. If it is reachable (some devnet genesis without hardforks, a test harness), please name the trigger, because that changes the merge urgency.

@flyq flyq changed the title refactor: single home for block-execution env assembly, fixing BlockLimits drift refactor: single home for block-execution env assembly, fixing the fallback branch's BlockLimits drift Aug 4, 2026
@flyq

flyq commented Aug 4, 2026

Copy link
Copy Markdown
Member Author

@vincent-k2026 Agreed — retitled to name the fallback branch, and the description now has a Reachability section stating it explicitly: mainnet and testnet genesis both schedule hardforks, so the diverging fallback is unreachable in production; this is preventive consolidation plus fixing the fallback branch's divergence, with no production merge urgency.

@flyq
flyq changed the base branch from liquan/refactor/simplify-cleanups to main August 4, 2026 14:22
flyq and others added 5 commits August 4, 2026 22:23
…idation

#170 closed (being resplit per review); this commit drops its content from
the branch and re-fits the block-execution-env extraction onto main's
executor shape (writer param, gas_used derivation, and the BLOB constant
stay as on main). Tree verified: core 102 / dts 106 / validator 35+13
tests green, fmt/clippy clean, zero #170 leakage.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…tests

start_executor_with_inspector was only reachable through the trace-server
binary, leaving the shared wiring invisible to core's coverage; construct
it over an empty state and run the pre-execution changes here.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@flyq
flyq requested a review from vincent-k2026 August 4, 2026 14:53

@vincent-k2026 vincent-k2026 left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Re-reviewed at d536e8a. Everything I asked for landed, and the rebase changed this PR's character enough that I re-read the whole diff rather than just my old points — approving.

The rebase is the big one. With #170 closed and its content dropped, the net diff against main is four files (executor.rs +174/-27, tracing_executor.rs +48/-81, plus two dependency lines). That is a single-purpose PR you can hold in your head, which is what the split was for.

Re-read of the current diff:

  • create_block_execution_env is the one home for the EvmEnv + factory + MegaBlockExecutionCtx assembly, and the fallback branch is now no_limits().with_block_gas_limit(header.gas_limit) on both sides. Both branches are pinned by tests, and BlockLimits: PartialEq means drift in any field fails them — not just the gas limit.
  • Replacing setup_executor! / replay_preceding_txs! with start_executor_with_inspector and a generic replay_preceding_txs is a clear improvement over the macros: the executor's concrete type is spelled once next to the env that produces it, and the for<'t> &'t Recovered<OpTxEnvelope>: ExecutableTx<E> bound states what the loop actually needs instead of hiding it behind textual substitution.
  • execution_env_starts_executor_with_inspector (68b4ee3) is a good addition — the prologue was previously only reachable through the trace-server binary, so core's own suite never touched it.

Framing fixed. The retitle names the fallback branch, and the Reachability section states plainly that mainnet/testnet genesis both schedule hardforks so production already agreed — no false urgency. The "Root cause (history)" archaeology (#53 introduced the mapping, #69 copied it seven weeks later minus with_block_gas_limit, git log -S showing nobody touched it since) is more than I asked for and is the right way to argue an omission was accidental rather than policy.

Non-blocking leftovers:

  • Summary still opens with "Stacked on #170 (retarget to main after it merges)". #170 is closed and the base is already main — drop that sentence so the merge commit doesn't reference a PR that never landed.
  • BlockExecutionEnv still has no Debug; lint is green so no repo convention requires it. Purely optional.

lint is green now. pr-review is red on every open PR in the repo (org claude-pr-review action fails with ENOENT ... symlink before reviewing) — infra, not this PR.

@flyq
flyq merged commit cba408b into main Aug 11, 2026
20 of 21 checks passed
@flyq
flyq deleted the liquan/refactor/block-execution-env branch August 11, 2026 08:17
flyq added a commit that referenced this pull request Aug 12, 2026
…sons

Brings in #168/#169/#171/#172. Conflict resolutions combine both sides:
lookup_block_by_number keeps #168's control flow (resolve_block_number
returns (number, tag_hash) and the tag-bound hash skips canonical-hash
resolution) with this branch's data_provider_failure error funnel on all
three fallible steps, and resolve_block_number keeps #168's tuple
signature and unified upstream-tag arm with this branch's typed
UnsupportedBlockTag rejection for `pending` (-32602 via the funnel).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
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