Skip to content

fix: add Rex6 activation to mainnet genesis - #190

Merged
flyq merged 5 commits into
mainfrom
liquan/fix/mainnet-genesis-rex6
Aug 27, 2026
Merged

fix: add Rex6 activation to mainnet genesis#190
flyq merged 5 commits into
mainfrom
liquan/fix/mainnet-genesis-rex6

Conversation

@flyq

@flyq flyq commented Aug 25, 2026

Copy link
Copy Markdown
Member

Summary

Rex6 activated on mainnet at 1787626800 (2026-08-25 03:00 UTC) and replaced the SequencerRegistry contract at 0x6342000000000000000000000000000000000006. 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 noopTracer fails on post-activation blocks:

-32000  Trace execution failed: Block replay failed during transaction execution:
        failed to apply blockhash contract call: SequencerRegistry at
        0x6342000000000000000000000000000000000006 has unexpected code hash
        0xabd7e8f1... (expected 0x63cd411a...);
        refusing to overwrite stateful contract storage without migration

noopTracer does 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_genesis gates the Rex6 registry config on rex6Time (crates/stateless-core/src/chain_spec.rs:142). With the field absent it builds sequencer_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-evm is pinned at v1.7.0 throughout and already supports Rex6; the gap is purely genesis data. On-chain confirmation: eth_getCode at that address returns 3874 bytes at block 24,372,730 and 5365 bytes at 24,850,266, matching the observed failure boundary.

Fix

"rex6Time": 1787626800,
"rex6MinRotationDelay": 21600

Both are required together — parse_required_from rejects a rex6Time without its rex6MinRotationDelay. The values match the production node's genesis; with this commit the two config sections 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_hardfork now walks the canonical mega_mainnet_hardforks() ladder against ChainSpec::from_genesis of the real test_data/mainnet/genesis.json 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 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:

  • Before: every tracer variant fails on post-activation blocks; noopTracer passes at every depth.
  • After: all six RPC methods and every tracer variant (callTracer / flatCallTracer / 4byteTracer / prestateTracer / default struct logger / noopTracer), plus trace_block / trace_transaction, plus deep historical blocks served from R2 (h2 negotiated, witness_r2 source) — 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:

  • debug-trace-server re-reads --genesis-file on every start — replace the file and restart.
  • stateless-validator persists genesis in the DB's GENESIS_CONFIG table (bin/stateless-validator/src/app.rs:41-58) and reads the stored copy when --genesis-file is omitted. Fixing an existing deployment means starting once with --genesis-file so store_genesis overwrites 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_from reads env for every #[clap(env = ...)] field, so a concurrent sibling test's with_env_var can leak into this parse without the lock.

🤖 Generated with Claude Code

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>
@mega-maxwell

mega-maxwell Bot commented Aug 25, 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 1f1ad959..67c8226f · updated 2026-08-27T02:33:33+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.

flyq and others added 3 commits August 25, 2026 21:29
`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>
@vincent-k2026

Copy link
Copy Markdown
Collaborator

Values check out — verified independently, not from the PR body:

  • rex6Time = 1787626800 matches mega-evm docs/spec/upgrades/overview.md (Mainnet, Aug 25 2026 03:00 UTC).
  • rex6MinRotationDelay = 21600 matches mega-kona crates/protocol/registry/etc/configs.json, whose mainnet entry is field-for-field identical to this genesis across the whole rex1–rex5 ladder plus rex5InitialSequencer / rex5InitialAdmin.

Should fix before merge — nothing here fails on the parent commit

cargo test --workspace being green proves nothing about this fix. The largest fixture in test_data/mainnet/blocks is block 15,412,411; the Rex6 boundary you documented is 24,372,730 → 24,850,266. validate_block_mainnet_fixtures (crates/stateless-core/src/executor.rs:699) passes with or without the two fields.

Two options, I'd take the second:

  1. Add one post-Rex6 paired fixture (block + witness) — it rides the existing loop and fails on the parent commit.
  2. Add a ladder-completeness assertion: walk mega_mainnet_hardforks().forks_iter() and assert none resolves to ForkCondition::Never under ChainSpec::from_genesis(mainnet genesis), with an explicit "not yet scheduled" allowlist. That also trips automatically when Rex7 lands and genesis is forgotten again — which is the failure mode actually being fixed here.

Non-blocking

  • Existing validator deployments aren't fixed by this PR, since genesis lives in GENESIS_CONFIG. A startup reconciliation — warn (or refuse) when the stored genesis config differs from the one shipped with the binary — beats relying on operators remembering to start once with --genesis-file. Follow-up.
  • The env_lock commit is unrelated to Rex6 and isn't mentioned in the body. The change itself is correct (try_parse_from reads env for every #[clap(env = ...)] field, so a sibling's with_env_var lands in this parse) — just call it out or split it.
  • No label. This is a production-outage fix; bug fits.

…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>
@flyq flyq added the bug Something isn't working label Aug 27, 2026
@flyq
flyq merged commit 11adc11 into main Aug 27, 2026
24 of 25 checks passed
@flyq
flyq deleted the liquan/fix/mainnet-genesis-rex6 branch August 27, 2026 09:35
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

bug Something isn't working

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants