fix: add Rex6 activation to mainnet genesis - #190
Conversation
Rex6 activated on mainnet at 1787626800 (2026-08-25 03:00 UTC), replacing
the SequencerRegistry contract at 0x6342000000000000000000000000000000000006.
Our mainnet genesis never carried the fork, so `ChainSpec::from_genesis`
built `sequencer_registry_rex6_config: None` (chain_spec.rs:142-157), the
executor kept applying the Rex5 expectations, and every block past the
activation failed the code-hash guard:
failed to apply blockhash contract call: SequencerRegistry at
0x6342...0006 has unexpected code hash 0xabd7e8f1... (expected
0x63cd411a...); refusing to overwrite stateful contract storage
without migration
Both fields are required together: `parse_required_from` rejects a
`rex6Time` without its `rex6MinRotationDelay`. The values match what the
production rpc-node carries -- with this commit the two `config` sections
are field-for-field identical.
Verified on a trace server built at 7f1d870: before the fix every tracer
shape failed on post-activation blocks; after it, all six RPC methods and
every tracer variant (callTracer / flatCallTracer / 4byteTracer /
prestateTracer / default struct logger / noopTracer) plus deep historical
blocks served from R2 all pass.
Note for anyone triaging a similar report: `noopTracer` does not go
through that replay path and keeps succeeding throughout, so a smoke test
built on it alone shows green while every real tracer is broken.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude review status
🛠️ Review did not finish Attempted 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. |
`try_parse_from` calls `std::env::var` for every `#[clap(env = ...)]` field, so this test touches the environment and must hold `env_lock` like every other test in the binary -- the requirement its own helper module states: "Every env-touching test in a given test binary must hold env_lock for the duration of its env access." Without it, `witness_endpoint_accepts_multiple_forms` can set `STATELESS_VALIDATOR_WITNESS_ENDPOINT` (via `with_env_var`, correctly holding the lock) while this test parses, so the parse picks the sibling's value up and the `is_empty()` assertion fails. That is the benign symptom and it just failed CI on this branch; the reason the lock is a soundness rule rather than a tidiness one is the other manifestation -- Rust 2024's `set_var` precondition is process-wide "no other thread touches the environment", and a concurrent `getenv` can read a reallocated `environ`. An audit of every `#[test]` calling `try_parse_from` across the workspace found this to be the only omission. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
`try_parse_from` calls `std::env::var` for every `#[clap(env = ...)]` field, so this test touches the environment and must hold `env_lock` like every other test in the binary -- the requirement its own helper module states: "Every env-touching test in a given test binary must hold env_lock for the duration of its env access." Without it, `witness_endpoint_accepts_multiple_forms` can set `STATELESS_VALIDATOR_WITNESS_ENDPOINT` (via `with_env_var`, correctly holding the lock) while this test parses, so the parse picks the sibling's value up and the `is_empty()` assertion fails. That is the benign symptom and it just failed CI on this branch; the reason the lock is a soundness rule rather than a tidiness one is the other manifestation -- Rust 2024's `set_var` precondition is process-wide "no other thread touches the environment", and a concurrent `getenv` can read a reallocated `environ`. An audit of every `#[test]` calling `try_parse_from` across the workspace found this to be the only omission. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…megaeth-labs/stateless-validator into liquan/fix/mainnet-genesis-rex6
|
Values check out — verified independently, not from the PR body:
Should fix before merge — nothing here fails on the parent commit
Two options, I'd take the second:
Non-blocking
|
…fork The chain-spec suite runs entirely on synthetic genesis JSON, so the shipped test_data/mainnet/genesis.json had no test that could notice a missing fork - the exact shape of the Rex6 production failure this branch fixes, and nothing on the branch failed on the parent commit. The new test walks the canonical mega_mainnet_hardforks() ladder against ChainSpec::from_genesis(mainnet genesis) and demands a Timestamp activation for every fork outside an explicit not-yet-scheduled allowlist (empty today); an allowlisted fork that becomes scheduled fails too, so the list cannot rot. On the parent genesis it fails naming Rex6; when a future fork lands in the ladder without its genesis field, it fails at test time instead of at the activation boundary. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Summary
Rex6 activated on mainnet at
1787626800(2026-08-25 03:00 UTC) and replaced theSequencerRegistrycontract at0x6342000000000000000000000000000000000006. Our mainnet genesis never carried the fork, so every block past the activation fails to replay. This adds the two fields the fork needs.Symptom
Every tracer shape except
noopTracerfails on post-activation blocks:noopTracerdoes not go through that replay path and keeps succeeding, so a smoke test built on it alone reads green while every real tracer is broken — worth knowing when triaging a similar report.Root cause
ChainSpec::from_genesisgates the Rex6 registry config onrex6Time(crates/stateless-core/src/chain_spec.rs:142). With the field absent it buildssequencer_registry_rex6_config: None, the executor keeps applying the Rex5 expectations, and the code-hash guard refuses the now-Rex6 contract.Not a code regression —
mega-evmis pinned atv1.7.0throughout and already supports Rex6; the gap is purely genesis data. On-chain confirmation:eth_getCodeat that address returns 3874 bytes at block 24,372,730 and 5365 bytes at 24,850,266, matching the observed failure boundary.Fix
Both are required together —
parse_required_fromrejects arex6Timewithout itsrex6MinRotationDelay. The values match the production node's genesis; with this commit the twoconfigsections are field-for-field identical.Regression test
Nothing on this branch failed on the parent commit (review finding): the chain-spec suite runs entirely on synthetic genesis JSON, the largest paired execution fixture is block 15,412,411 — a year before the activation — so the shipped genesis file itself had no discriminating coverage.
mainnet_genesis_schedules_every_canonical_hardforknow walks the canonicalmega_mainnet_hardforks()ladder againstChainSpec::from_genesisof the realtest_data/mainnet/genesis.jsonand demands aTimestampactivation for every fork outside an explicit not-yet-scheduled allowlist (empty today; an allowlisted fork that becomes scheduled fails too, so the list cannot rot into shadowing the check). On the parent commit's genesis it fails naming Rex6, and it trips automatically when a future fork lands in the ladder without its genesis field — the failure mode actually being fixed here.Testing
Trace server built at
7f1d870, deployed on a test box against a local upgraded rpc-node:noopTracerpasses at every depth.callTracer/flatCallTracer/4byteTracer/prestateTracer/ default struct logger /noopTracer), plustrace_block/trace_transaction, plus deep historical blocks served from R2 (h2 negotiated,witness_r2source) — all pass.cargo test --workspace: 475 passed, 0 failed (1 new). The new test was verified to discriminate: against the parent commit's genesis it fails naming Rex6; against this branch it passes.Notes
Deployment paths differ between the two binaries:
--genesis-fileon every start — replace the file and restart.GENESIS_CONFIGtable (bin/stateless-validator/src/app.rs:41-58) and reads the stored copy when--genesis-fileis omitted. Fixing an existing deployment means starting once with--genesis-filesostore_genesisoverwrites it.The branch also carries
test: hold the env lock in witness_endpoint_is_optional_at_parse_time— unrelated to Rex6, riding along:try_parse_fromreads env for every#[clap(env = ...)]field, so a concurrent sibling test'swith_env_varcan leak into this parse without the lock.🤖 Generated with Claude Code