feat(chainspec): hardcode gravity-mainnet hardfork schedule (chainId 127001)#369
feat(chainspec): hardcode gravity-mainnet hardfork schedule (chainId 127001)#369nekomoto911 wants to merge 2 commits into
Conversation
…127001) Make the gravity-reth binary the authoritative source of hardfork activation for gravity mainnet (chainId 127001). On that chain id the two compile-time tables in `gravity::GRAVITY_MAINNET_GATED_ETH_FORKS` and `gravity::GRAVITY_MAINNET_GATED_GRAVITY_FORKS` override whatever the operator's genesis.json supplies; for any other chain id the override is a no-op and genesis-driven behaviour is unchanged. Initial table content: - Prague → Some(1_782_709_200), the timestamp pinned in the deployed mainnet genesis (gravity-mainet-gitops/genesis.json -> config.pragueTime). - Alpha → None — forward-defence so a stray `alphaTime` in mainnet genesis cannot activate Alpha until governance picks a value. Entry semantics: Some(ts) upserts as ForkCondition::Timestamp(ts); None removes the entry from the runtime hardforks vec. Removal is preferred over inserting ForkCondition::Never because Never at the tail of the canonical-ordered vec makes ChainSpec::latest_fork_id() panic (it unwraps hardforks.last() then unwraps an Option<ForkId> that is None for Never). Functionally equivalent: ChainHardforks::fork() returns Never as default for missing entries, and both fork_id and fork_filter already skip Never entries. The override is wired only into the From<Genesis> for ChainSpec funnel inside into_ethereum_chain_spec — the only production path for chain id 127001. ChainSpec::genesis itself is NOT mutated; CL/EL alignment in gravity-sdk's consensus side is out of scope. 19 unit tests cover the Ethereum-side (Prague) override, the Gravity-side (Alpha) override, the helper's Some/None branches on hand-built tables, the EL-local preservation invariant, fork-id / fork-filter stability across genesis variants, the genesis-header end-to-end effect, idempotency, the ChainSpecBuilder bypass tripwire, the latest_fork_id no-panic regression, and a pinned-value tripwire asserting the Prague timestamp matches the deployed mainnet genesis.
…verrides
Make the hardcoded hardfork mechanism extensible to other chain ids the
binary may own in the future. Replace the gravity-mainnet-only flat
tables with chain-id-keyed dispatch maps and rename the entry points
accordingly:
apply_gravity_mainnet_eth_overrides → apply_hardcoded_eth_overrides
apply_gravity_mainnet_gravity_overrides → apply_hardcoded_gravity_overrides
GRAVITY_MAINNET_GATED_ETH_FORKS → HARDCODED_ETH_FORK_OVERRIDES
(now `[(chain_id, &[...])]`)
GRAVITY_MAINNET_GATED_GRAVITY_FORKS → HARDCODED_GRAVITY_FORK_OVERRIDES
(now `[(chain_id, &[...])]`)
A `HardcodedOverrideMap<H>` type alias keeps clippy::type_complexity
happy without sacrificing readability. A new `lookup_overrides` helper
walks the dispatch map; the chain-id check is now external to
`apply_overrides_from_table`, which becomes a pure transform.
Adding another chain id is a single tuple inside each dispatch map —
no changes to the public surface or the spec.rs hook call sites.
Existing tests adjusted for the new signature (`apply_overrides_from_table`
no longer takes chain_id), plus two new tests for `lookup_overrides`
(found-and-not-found branches) and one renamed test for the dispatch-
miss noop on the public entry points.
|
Follow-up (separate PR): unify the CL and EL hardfork schedules under one authority This PR makes the EL hardfork schedule binary-authoritative for mainnet — which fixes the live It's the same class of bug this PR just fixed for the EL, but with a larger blast radius: Suggested direction:
To confirm before merging the layers:
Cheapest to do now, while both |
|
Minor: log a warning when the hardcoded override disagrees with genesis The override is silent when it differs from genesis (e.g. genesis Rationale: the override is a safety net, but a silent one hides the root cause. The motivating case here is a genesis regen that dropped |
|
Superseded by Galxe/gravity-sdk#763, which implements the same hardfork override on the sdk side (Option D-1). #763 unifies EL+CL via the parser-level override and avoids the |
Summary
Make the gravity-reth binary the authoritative source of hardfork activation for gravity mainnet (chainId 127001) by introducing two compile-time tables in
crates/chainspec/src/gravity.rs:GRAVITY_MAINNET_GATED_ETH_FORKS:(EthereumHardfork::Prague, Some(1_782_709_200))— pinned to the value in the deployed mainnet genesis (gravity-mainet-gitopsgenesis.json→config.pragueTime).GRAVITY_MAINNET_GATED_GRAVITY_FORKS:(GravityHardfork::Alpha, None)— forward-defence so a strayalphaTimein mainnet genesis can't activate Alpha until governance picks a value.For any other chain id (testnet 7771625, devnets, anything else), the override is a no-op and current genesis-driven behaviour is unchanged.
Entry semantics
Some(ts)→ upsert the entry asForkCondition::Timestamp(ts); ignore whatever genesis says.None→ remove the matching entry from the runtime hardforks vec; ignore whatever genesis says. Removal is preferred over insertingForkCondition::Neverbecause Never at the tail of the canonical-ordered vec makesChainSpec::latest_fork_id()panic — it unwrapshardforks.last()and then unwrapsOption<ForkId>, which isNoneforNever. Functionally equivalent:ChainHardforks::fork()returnsNeveras the default for missing entries, and bothfork_id/fork_filteralready filterNeverout.Hook location
Two call sites in
crates/chainspec/src/spec.rs::From<Genesis> for ChainSpec:hardforks.append(&mut time_hardforks), before the canonical-ordering pass againstEthereumHardfork::mainnet().extra_fieldsreads (alphaTime/betaBlock/gammaBlock/deltaBlock), beforeChainHardforks::new(gravity_hardforks).This is the only production funnel for chainId 127001. The preserved
ChainSpec.genesisis NOT mutated — the override is EL-local. CL/EL alignment in gravity-sdk's consensus side is out of scope for this PR.Scope notes
into_optimism_chain_spec) is intentionally NOT patched (no-OP-stack policy).Test plan
cargo nextest run -p reth-chainspec→ 69/69 pass (19 new tests ingravity::tests)fork_id/fork_filterbyte-equality across genesis variants at probes[0, 99, 100, table_ts-1, table_ts, table_ts+1, u64::MAX]pragueTime: 0→ override suppressesrequests_hash,genesis_hashmatches the "no pragueTime" variantcs.genesis().config.prague_timeandextra_fields.alphaTimepreservedFrom<Genesis>idempotencyChainSpecBuilder::mainnet()bypass tripwirelatest_fork_id()no-panic regression1_782_709_200(consensus-affecting; requires governance to change)Some/None/ non-mainnet no-op branches on hand-built tables (covers bothEthereumHardforkandGravityHardforkvia the generic helper)cargo +nightly fmt --check -p reth-chainspeccargo +nightly clippy -p reth-chainspec --lib --tests --no-deps -- -D warningsMaintenance
Adding or modifying an entry in either gated table is a mainnet consensus change. The PR doing so must reference the onchain governance / coordination record that authorised the timestamp. Existing entries must never be removed — promoting
NonetoSome(ts)is the only allowed transition.