refactor: introduce EDR-owned protocol hardfork types - #1601
Conversation
|
There was a problem hiding this comment.
Pull request overview
This PR introduces EDR-owned “protocol-level” hardfork enums for Ethereum L1 and OP, separating protocol upgrade sequencing/parameters from the EVM behavior-class identifiers consumed by revm (e.g., EvmSpecId, op_revm::OpSpecId). It then propagates that distinction through chain specs, provider validation, RPC conversion, receipts, and OP chain config generation.
Changes:
- Add protocol-level hardfork abstractions (
ProtocolHardfork,ProtocolParams) and extendHardforkChainSpecto include both protocol-level and EVM-level hardfork types. - Introduce owned hardfork enums (
edr_chain_l1::L1Hardfork,edr_op::OpHardfork) and update call sites to use protocol-level hardforks while converting to EVM spec IDs where required. - Update OP chain config generator + generated OP chain config modules to use
OpHardforkinstead ofop_revm::OpSpecId.
Reviewed changes
Copilot reviewed 52 out of 94 changed files in this pull request and generated 1 comment.
Show a summary per file
| File | Description |
|---|---|
| crates/tool/op_chain_config_generator/src/main.rs | Switch generator logic to use edr_op::OpHardfork for activations/base-fee params. |
| crates/tool/op_chain_config_generator/Cargo.toml | Replace op-revm dependency with edr_op. |
| crates/test/block_replay/src/lib.rs | Move hardfork-dependent logic to protocol hardfork helpers (to_evm_spec_id, miner_reward). |
| crates/edr_solidity_tests/tests/it/repros.rs | Update test runner hardfork type alias usage (EvmHardfork). |
| crates/edr_solidity_tests/tests/it/helpers.rs | Update hardfork generic usage; allow impl Into<HardforkT> in constructor. |
| crates/edr_provider/tests/integration/estimate_gas.rs | Adjust test config hardfork type to edr_chain_l1::Hardfork. |
| crates/edr_provider/tests/integration/eip7825.rs | Adjust test config hardfork type to edr_chain_l1::Hardfork. |
| crates/edr_provider/src/test_utils.rs | Update header override hardfork type to edr_chain_l1::Hardfork. |
| crates/edr_provider/src/requests/validation.rs | Accept protocol hardforks and convert to EvmSpecId when validating EVM-specific rules. |
| crates/edr_provider/src/requests/resolve.rs | Pass protocol hardfork through to call validation. |
| crates/edr_provider/src/requests/eth/transactions.rs | Pass protocol hardfork through to initcode-size validation. |
| crates/edr_provider/src/requests/eth/gas.rs | Convert protocol hardfork to EvmSpecId for tx validation. |
| crates/edr_provider/src/requests/eth/call.rs | Convert protocol hardfork to EvmSpecId for tx validation. |
| crates/edr_provider/src/debug_trace.rs | Convert protocol hardfork to EvmSpecId for tracing checks. |
| crates/edr_provider/src/data.rs | Replace Into<EvmSpecId> comparisons with to_evm_spec_id(); use protocol miner_reward(). |
| crates/edr_op/src/test_utils.rs | Use OpHardfork in header override helpers. |
| crates/edr_op/src/spec.rs | Introduce protocol/EVM hardfork split for OP chain spec; retype CfgEnv for EVM execution. |
| crates/edr_op/src/rpc/transaction.rs | Convert protocol hardfork to EvmSpecId for RPC formatting. |
| crates/edr_op/src/receipt/block.rs | Use new L1BlockReceipt::new and convert protocol hardfork to EvmSpecId. |
| crates/edr_op/src/lib.rs | Re-export OpHardfork, define EvmHardfork = op_revm::OpSpecId, and alias Hardfork = OpHardfork. |
| crates/edr_op/src/hardfork/op.rs | Update base fee params to be keyed by OpHardfork. |
| crates/edr_op/src/hardfork/base.rs | Update base fee params to be keyed by OpHardfork. |
| crates/edr_op/src/hardfork.rs | Add owned OpHardfork enum + conversions + protocol params + parity tests. |
| crates/edr_op/src/block/builder.rs | Convert protocol hardfork into op_revm::OpSpecId for L1BlockInfo::try_fetch. |
| crates/edr_op/src/hardfork/generated/zora.rs | Generated OP chain config now uses OpHardfork. |
| crates/edr_op/src/hardfork/generated/xterio_eth.rs | Generated OP chain config now uses OpHardfork. |
| crates/edr_op/src/hardfork/generated/worldchain.rs | Generated OP chain config now uses OpHardfork. |
| crates/edr_op/src/hardfork/generated/unichain.rs | Generated OP chain config now uses OpHardfork. |
| crates/edr_op/src/hardfork/generated/tbn.rs | Generated OP chain config now uses OpHardfork. |
| crates/edr_op/src/hardfork/generated/swell.rs | Generated OP chain config now uses OpHardfork. |
| crates/edr_op/src/hardfork/generated/swan.rs | Generated OP chain config now uses OpHardfork. |
| crates/edr_op/src/hardfork/generated/sseed.rs | Generated OP chain config now uses OpHardfork. |
| crates/edr_op/src/hardfork/generated/soneium.rs | Generated OP chain config now uses OpHardfork. |
| crates/edr_op/src/hardfork/generated/soneium_minato.rs | Generated OP chain config now uses OpHardfork. |
| crates/edr_op/src/hardfork/generated/silent_data_mainnet.rs | Generated OP chain config now uses OpHardfork. |
| crates/edr_op/src/hardfork/generated/shape.rs | Generated OP chain config now uses OpHardfork. |
| crates/edr_op/src/hardfork/generated/settlus_sepolia.rs | Generated OP chain config now uses OpHardfork. |
| crates/edr_op/src/hardfork/generated/settlus_mainnet.rs | Generated OP chain config now uses OpHardfork. |
| crates/edr_op/src/hardfork/generated/redstone.rs | Generated OP chain config now uses OpHardfork. |
| crates/edr_op/src/hardfork/generated/radius_testnet.rs | Generated OP chain config now uses OpHardfork. |
| crates/edr_op/src/hardfork/generated/race.rs | Generated OP chain config now uses OpHardfork. |
| crates/edr_op/src/hardfork/generated/polynomial.rs | Generated OP chain config now uses OpHardfork. |
| crates/edr_op/src/hardfork/generated/pivotal.rs | Generated OP chain config now uses OpHardfork. |
| crates/edr_op/src/hardfork/generated/ozean.rs | Generated OP chain config now uses OpHardfork. |
| crates/edr_op/src/hardfork/generated/orderly.rs | Generated OP chain config now uses OpHardfork. |
| crates/edr_op/src/hardfork/generated/op.rs | Generated OP chain config now uses OpHardfork. |
| crates/edr_op/src/hardfork/generated/mode.rs | Generated OP chain config now uses OpHardfork. |
| crates/edr_op/src/hardfork/generated/mint.rs | Generated OP chain config now uses OpHardfork. |
| crates/edr_op/src/hardfork/generated/metal.rs | Generated OP chain config now uses OpHardfork. |
| crates/edr_op/src/hardfork/generated/lyra.rs | Generated OP chain config now uses OpHardfork. |
| crates/edr_op/src/hardfork/generated/lisk.rs | Generated OP chain config now uses OpHardfork. |
| crates/edr_op/src/hardfork/generated/ink.rs | Generated OP chain config now uses OpHardfork. |
| crates/edr_op/src/hardfork/generated/hashkeychain.rs | Generated OP chain config now uses OpHardfork. |
| crates/edr_op/src/hardfork/generated/funki.rs | Generated OP chain config now uses OpHardfork. |
| crates/edr_op/src/hardfork/generated/fraxtal.rs | Generated OP chain config now uses OpHardfork. |
| crates/edr_op/src/hardfork/generated/ethernity.rs | Generated OP chain config now uses OpHardfork. |
| crates/edr_op/src/hardfork/generated/cyber.rs | Generated OP chain config now uses OpHardfork. |
| crates/edr_op/src/hardfork/generated/creator_chain_testnet.rs | Generated OP chain config now uses OpHardfork. |
| crates/edr_op/src/hardfork/generated/celo.rs | Generated OP chain config now uses OpHardfork. |
| crates/edr_op/src/hardfork/generated/camp.rs | Generated OP chain config now uses OpHardfork. |
| crates/edr_op/src/hardfork/generated/boba.rs | Generated OP chain config now uses OpHardfork. |
| crates/edr_op/src/hardfork/generated/bob.rs | Generated OP chain config now uses OpHardfork. |
| crates/edr_op/src/hardfork/generated/base.rs | Generated OP chain config now uses OpHardfork. |
| crates/edr_op/src/hardfork/generated/automata.rs | Generated OP chain config now uses OpHardfork. |
| crates/edr_op/src/hardfork/generated/arena_z.rs | Generated OP chain config now uses OpHardfork. |
| crates/edr_napi/src/solidity_tests/op.rs | Adjust runner type params to use EVM-level hardfork and new config conversion helper. |
| crates/edr_napi/src/solidity_tests/l1.rs | Adjust runner type params to use EVM-level hardfork and new config conversion helper. |
| crates/edr_napi_core/src/solidity/config.rs | Introduce chain-spec-driven conversion (try_into_runner_config) parsing protocol hardfork then mapping to EVM hardfork. |
| crates/edr_napi_core/src/provider/config.rs | Require ProtocolHardfork for provider configs; keep gas-cap derivation via Into<EvmSpecId>. |
| crates/edr_generic/src/spec.rs | Re-type block-env hardfork handling; retype CfgEnv for EVM execution via to_evm_cfg_env. |
| crates/edr_generic/src/rpc/transaction.rs | Convert protocol hardfork to EVM spec where required for RPC shaping. |
| crates/edr_generic/src/rpc/receipt.rs | Use protocol hardfork type directly in comparisons via Self::Hardfork. |
| crates/edr_generic/src/receipt.rs | Compare against protocol hardfork (edr_chain_l1::Hardfork) instead of EvmSpecId. |
| crates/edr_eth/src/block/reward.rs | Remove standalone miner_reward(EvmSpecId) helper (migrated to protocol params). |
| crates/edr_eth/src/block.rs | Stop re-exporting removed miner_reward. |
| crates/edr_chain_spec/src/lib.rs | Extend HardforkChainSpec; add ProtocolParams + ProtocolHardfork. |
| crates/edr_chain_l1/src/spec.rs | Re-type CfgEnv for EVM execution; convert protocol hardfork to EVM spec for precompile set selection. |
| crates/edr_chain_l1/src/rpc/transaction.rs | Convert protocol hardfork to EvmSpecId for RPC formatting. |
| crates/edr_chain_l1/src/rpc/receipt.rs | Use Self::Hardfork in comparisons. |
| crates/edr_chain_l1/src/receipt/builder.rs | Compare against protocol hardfork constants. |
| crates/edr_chain_l1/src/receipt.rs | Add L1BlockReceipt::new helper; construct receipts using hardfork.to_evm_spec_id(). |
| crates/edr_chain_l1/src/lib.rs | Export new hardfork module; define EvmHardfork/Hardfork aliases. |
| crates/edr_chain_l1/src/hardfork.rs | New EDR-owned Ethereum L1 hardfork enum + conversions + protocol params + parity tests. |
| crates/edr_chain_l1/src/chains.rs | Re-export L1 hardfork name constants from owned type. |
| crates/edr_chain_l1/src/block.rs | Use protocol hardfork -> EVM spec conversions where needed (blob params, comparisons). |
| crates/chain/spec/provider/src/lib.rs | Relax Hardfork bound in ProviderChainSpec. |
| crates/chain/spec/evm/src/lib.rs | Make EVM context use EvmHardfork; add to_evm_cfg_env conversion helper. |
| crates/blockchain/local/src/lib.rs | Switch generic hardfork bounds to ProtocolHardfork; remove unnecessary cloning in some paths. |
| crates/blockchain/fork/src/lib.rs | Switch generic hardfork bounds to ProtocolHardfork. |
| crates/block/storage/src/reservable.rs | Switch generic hardfork bounds to ProtocolHardfork; remove hardfork clones. |
| crates/block/local/src/lib.rs | Switch generic hardfork bounds to ProtocolHardfork; use to_evm_spec_id() for merge checks. |
| crates/block/header/src/lib.rs | Switch hardfork bounds to ProtocolHardfork; use to_evm_spec_id() internally. |
| crates/block/header/src/difficulty.rs | Replace local bomb_delay(EvmSpecId) with protocol hardfork’s bomb_delay(). |
| Cargo.lock | Update lockfile for dependency changes (add edr_op, remove op-revm from generator). |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| /// String identifier for the latest hardfork | ||
| pub const LATEST: &str = "Latest"; | ||
| } | ||
|
|
||
| impl FromStr for L1Hardfork { |
There was a problem hiding this comment.
This is intentional: the name module is a verbatim copy of revm-primitives, and revm's own SpecId::from_str also rejects Latest. We mirror both so hardfork strings parse identically before and after this refactor (there's a test pinning the rejection). The const has no consumers, so if it reads as misleading we can drop it in a follow-up
Codecov Report❌ Patch coverage is Additional details and impacted files@@ Coverage Diff @@
## main #1601 +/- ##
==========================================
+ Coverage 79.90% 79.97% +0.07%
==========================================
Files 452 453 +1
Lines 78968 79293 +325
Branches 78968 79293 +325
==========================================
+ Hits 63099 63417 +318
- Misses 13688 13695 +7
Partials 2181 2181 ☔ View full report in Codecov by Harness. 🚀 New features to boost your workflow:
|
040d4a2 to
29eca92
Compare
…ponding associated type names Two sites got narrower bounds: chain/spec/evm/src/lib.rs:27 — ContextForChainSpec only needs the EVM-level type, so it now projects through EvmHardforkChainSpec. edr_napi_core/src/solidity/config.rs:224-225 — the combined bound decomposed into EvmHardforkChainSpec<EvmHardfork: HardforkTr> + ProtocolHardforkChainSpec<ProtocolHardfork: FromStr<Err = UnknownHardfork>>.
…spec `PartialHeader::new` required `HardforkT: ProtocolHardfork` solely to reach `ProtocolParams::bomb_delay` for the Ethash fallback, which pushed L1 proof-of-work policy onto every chain: `OpHardfork::bomb_delay` was an `unreachable!()` and `OpChainSpec::MIN_ETHASH_DIFFICULTY` a meaningless 0. Chains now supply the computation as `ProviderChainSpec::default_block_difficulty`, carried down to the header layer as a `fn` pointer on `BlockConfig`. A pointer rather than a closure parameter because the reservation path resolves lazily behind `RwLock`s, arbitrarily far from `reserve_blocks`, with only the cloned `BlockConfig` crossing that boundary - and both it and `Reservation` derive `Clone`/`Debug`. No trait bound had to be widened to make this reachable. The Ethash formula moves to `edr_chain_l1::difficulty`, its only consumer. Its `assert!(spec_id >= BYZANTIUM)` and the `unreachable!()` in `bomb_delay` are both replaced by `PreMergeL1Hardfork`, whose `TryFrom<L1Hardfork>` failure *is* the post-merge branch, leaving `bomb_delay` total over an exhaustive match. `MIN_ETHASH_DIFFICULTY` and `BlockConfig::min_ethash_difficulty` are gone, and `ProtocolParams` keeps only `miner_reward`. `PartialHeader::new` and the storage/blockchain layers that forward to it now require just `Copy + Into<EvmSpecId> + PartialOrd`.
`BlockBuilder::finalize_block` took a `rewards` vector that every caller built identically, out of values the builder already owned: the amount from the blockchain's hardfork, and the recipient read straight off `block_builder.header().beneficiary`. The amount was threaded down from `edr_provider` as a `u128` so the builder could be told a number it could compute itself. `EthBlockBuilder` used to be both the shared Ethereum block-building machinery and L1's `BlockBuilder` implementation, while OP wrapped it. That dual role is what made the reward awkward to place, since the reward is L1-only but the engine is instantiated with `OpHardfork` too. It now loses its `BlockBuilder` impl and keeps only the engine role: the reward loop becomes `apply_rewards`, and `finalize` is reward-free. A new `L1BlockBuilder` wraps it and implements `BlockBuilder`, pinned to L1's hardfork type so it can pay the reward; `OpBlockBuilder` simply doesn't. Behavior is unchanged for OP, whose reward was already zero and skipped. With the reward L1-local, `ProtocolParams` has no members left. Deleting it reduces `ProtocolHardfork` to `Copy + Into<EvmSpecId> + PartialOrd`, which is purely derivable, and removes the `miner_reward` stub OP only carried to answer `None`.
Wodann
left a comment
There was a problem hiding this comment.
I left comments on the remaining open discussions. I think there are two more kinks to agree on.
|
@Wodann I left the umbrella |
Wodann
left a comment
There was a problem hiding this comment.
Thanks for addressing all of the feedback. LGTM!
Three conflicts, all from `main`'s rename of the hardfork associated types (#1601) landing on lines this branch had also changed. - `crates/edr_provider/src/provider.rs`: `main` only renamed `HardforkChainSpec` to `ProtocolHardforkChainSpec` and `Hardfork` to `ProtocolHardfork`. This branch rewrote the file around the event loop, so the conflict was positional. Kept this branch's file and reapplied the two renames. - `crates/edr_generic/tests/integration/helpers.rs` and `issues/issue_947.rs`: both sides edited the same bound list. `main` renamed the associated type; this branch added the bounds that `Provider::new` needs now that it spawns the event loop. Kept both.
This refactor introduces EDR-owned hardfork types
L1HardforkandOpHardfork, so that EDR's business logic is ruled by its own vocabulary, converted to revm's types only at the EVM boundary. Beyond decoupling EDR from revm'sSpecIdchanges, owning the types gives EDR a home for protocol semantics of its own.Motivation
EDR must be protocol compliant: it cares about all hardforks, including ones with no EVM-semantics changes. Starting with revm
39.0.0(releasev108, the "SpecId cleanup" in revm's migration guide),SpecIdmodels EVM behavior classes only: the release removed the variants for Frontier Thawing, DAO Fork, Constantinople, Muir Glacier, Arrow Glacier, and Gray Glacier as "EVM-equivalent", and shifted the discriminants of the remaining ones. EDR currently pins revm38.0.0, the last version that still models every hardfork; upgrading past it while EDR's protocol logic is keyed onSpecIdwould lose information EDR relies on, because EVM-equivalent is not header-equivalent:This PR therefore lands before the pending revm upgrade (#1590, on hold): against the current pin the conversions introduced here are lossless 1:1, so the refactor is behavior-preserving by construction, and the upgrade's
SpecIdchanges will then only touch theFromconversion impls.Changes
Owned enums
One enum per chain type, listing every protocol upgrade in activation order. Left SCREAMING variant names keep the existing use sites textually identical.
Trait layer(
edr_chain_spec)Chain-generic code needs to name both vocabularies:
HardforkChainSpecnow carriesEvmHardfork(what revm consumes:SpecIdfor L1/generic,OpSpecIdfor OP) alongside the protocol-levelHardfork.ProtocolHardforkbundles the bounds generic code needs and providesto_evm_spec_id()for gates that are genuinely about EVM behavior.Protocol parameters re-homed
The difficulty bomb delay and block reward are protocol-level parameters, not EVM semantics. Muir Glacier (EIP-2384), Arrow Glacier (EIP-4345), and Gray Glacier (EIP-5133) consisted of nothing but a bomb-delay change, precisely why
SpecIdcollapsed them. EDR must replay protocol history, so these parameters belong on its own hardfork vocabulary.bomb_delayandminer_rewardwere free functions keyed on revm'sSpecId, exactly the coupling that breaks when revm collapses protocol-only forks. They now live on the owned enums asProtocolParamsimpls (tables moved verbatim).Where the design is deliberately not independent yet
Some hardfork gates in chain-generic code still ask revm's EVM classes (
hardfork.to_evm_spec_id() >= EvmSpecId::X):ProtocolParamsdoesn't answer these gate questions yet, andL1Hardfork/OpHardforkare deliberately not comparable to each other, so generic code has nothing chain-agnostic to gate on. The intended end state extends thebomb_delay/miner_rewardpattern into a semantic API — instead ofhardfork >= LONDON, ask something likehardfork.supports_eip1559_gas_fee(), but that was consciously left out to scope this PR to the critical changes (see follow-ups). The remaining gates are safe: they are genuinely EVM behavior-class questions, which surviveSpecIdcleanups.Solidity test runner.
To keep the vocabulary consistent across all of EDR's interfaces, the runner's config ingress also parses the owned types (
TestRunnerConfig::try_into_runner_config::<ChainSpecT>()), converting to the revm type immediately after. The runner itself stays in revm vocabulary: it has only a few hardfork gates, all tied to EVM-semantics forks, and refactoring the vendored foundry port would diverge the backport further without being a blocker for this change.One vocabulary per layer
Each layer speaks its own hardfork vocabulary and converts it into the types of the layer below when crossing the seam: the TS/JS surface speaks JS enums and strings, the Rust protocol internals speak the owned types, and only EVM execution speaks revm's behavior classes.