fix: PoS dev mode — enable Fusaka (Fulu CL + Osaka EL) block production#21646
Conversation
b6a17b3 to
56143a5
Compare
Three issues prevented `--chain dev` from producing blocks past genesis:
1. Beacon API routes not registered: dev mode set
BeaconAPIRouter.Active=true but never enabled the endpoint groups
(Beacon, Node, Validator, Config, Events). The dev validator polls
/eth/v1/beacon/genesis and /eth/v1/validator/duties/* which returned
404, deadlocking block production.
2. Empty RequestsHash mismatch: the block builder used common.Hash{}
(zero) when no EIP-7685 requests exist, but the CL computes
SHA256("") = e3b0c442... over an empty list. Fix: use the existing
empty.RequestsHash constant.
3. Prague/Osaka not activated + system contracts missing: the CL ran at
Fulu (epoch 0) but the EL had PragueTime=nil and OsakaTime=nil.
Enabling Prague requires the EIP-7002/7251/2935 system contracts in
the dev genesis alloc (copied from Hoodi testnet).
Dev mode now runs the full Fusaka fork: Fulu on CL, Osaka on EL.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
56143a5 to
abb171d
Compare
| cfg.Genesis.Config.ShanghaiTime = &zero | ||
| cfg.Genesis.Config.CancunTime = &zero | ||
| cfg.Genesis.Config.PragueTime = nil // Prague may need more config; leave disabled | ||
| cfg.Genesis.Config.PragueTime = &zero |
There was a problem hiding this comment.
might be nice to extract all of this into a SetDevnetEthConfig function or something along those lines since this switch's devnet case has grown quite a lot
There was a problem hiding this comment.
Pull request overview
This PR fixes PoS --chain dev block production by aligning Erigon’s dev-mode EL/CL configuration with Fusaka-era requirements, so the embedded dev validator can progress past genesis and produced blocks are accepted by the CL.
Changes:
- Fixes Prague
requestsHashcomputation for empty EIP-7685 request sets by using the canonical empty hash. - Enables Prague + Osaka at genesis in dev mode and injects required Prague system-contract predeploys into the dev genesis alloc.
- Enables Beacon API endpoint groups in dev mode so the embedded dev validator can successfully poll required routes (e.g.,
/eth/v1/beacon/genesis).
Reviewed changes
Copilot reviewed 2 out of 2 changed files in this pull request and generated 2 comments.
| File | Description |
|---|---|
| execution/builder/exec.go | Uses the canonical empty requests hash for Prague blocks to match CL expectations. |
| cmd/utils/flags.go | Enables Prague/Osaka + Beacon API endpoint groups in dev mode; adds Prague system-contract predeploys to dev genesis alloc. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| zero := uint64(0) | ||
| cfg.Genesis.Config.ShanghaiTime = &zero | ||
| cfg.Genesis.Config.CancunTime = &zero | ||
| cfg.Genesis.Config.PragueTime = nil // Prague may need more config; leave disabled | ||
| cfg.Genesis.Config.PragueTime = &zero | ||
| cfg.Genesis.Config.OsakaTime = &zero |
| cfg.Genesis.Alloc[common.HexToAddress("0x0000BBdDc7CE488642fb579F8B00f3a590007251")] = types.GenesisAccount{ | ||
| Balance: new(big.Int), | ||
| Nonce: 1, | ||
| Code: common.FromHex("0x3373fffffffffffffffffffffffffffffffffffffffe1460d35760115f54807fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff1461019a57600182026001905f5b5f82111560685781019083028483029004916001019190604d565b9093900492505050366060146088573661019a573461019a575f5260205ff35b341061019a57600154600101600155600354806004026004013381556001015f358155600101602035815560010160403590553360601b5f5260605f60143760745fa0600101600355005b6003546002548082038060021160e7575060025b5f5b8181146101295782810160040260040181607402815460601b815260140181600101548152602001816002015481526020019060030154905260010160e9565b910180921461013b5790600255610146565b90505f6002555f6003555b5f54807fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff141561017357505f5b6001546001828201116101885750505f61018e565b01600190035b5f555f6001556074025ff35b5f5ffd"), | ||
| Storage: map[common.Hash]common.Hash{common.Hash{}: common.HexToHash("0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff")}, | ||
| } |
yperbasis
left a comment
There was a problem hiding this comment.
Major
-
Dev genesis predeploy set is incomplete. The PR adds the predeploys whose absence hard-fails finalization (7002/7251/2935) but omits the two whose absence fails silently:
- Deposit contract (EIP-6110):
DepositContractstays0x0, somerge.Finalize→misc.ParseDepositLogslogs a WARN on every block. Cosmetic, but noisy. - EIP-4788 beacon roots: not predeployed, so with Cancun at genesis the beacon-roots syscall hits an empty account and silently no-ops — EIP-4788 reads return zero.
Both live in
allocs/hoodi.json. Adding the 4788 predeploy + the deposit contract (and settingconfig.DepositContract) makes the dev genesis a faithful Cancun+Prague genesis and resolves both Copilot comments at once. - Deposit contract (EIP-6110):
-
Genesis/alloc content is in the wrong layer. The system-contract hex is hardcoded inline in the
SetEthConfigswitch (CLI flag → config translation), duplicating bytecode that already lives inallocs/hoodi.json. Better to push it intoDeveloperGenesisBlock()/allocs/dev.json, or extract aSetDevnetEthConfighelper (as @taratorio noted) — keepsflags.gofocused and avoids a silent-staleness copy.
Minor
-
Root cause of the
requestsHashbug is duplicated consensus logic.execution/builder/exec.goreimplements the requests-hash computation thatmerge.goFinalizealready does correctly (outRequests.Hash()). The one-line fix is right, but the duplication is the underlying smell and can drift again on the next EIP-7685 change — worth a tracking issue to converge the two paths. -
No regression test. Per the repo's TDD guidance, the consensus-relevant
exec.gofix warrants a focused test (theexecmoduletester/engineapitesterharnesses already exist) asserting the builder emitsempty.RequestsHashfor an empty-request Prague block — or a note in the PR on why TDD was skipped.
…hainspec Addresses review feedback on #21646: - Add the EIP-6110 deposit contract (and set DepositContract in the chain config) plus the EIP-4788 beacon-roots predeploy to the dev genesis, alongside the EIP-2935/7002/7251 contracts — all copied verbatim from allocs/hoodi.json. Fixes the ParseDepositLogs WARN on every block and the silently no-op 4788 syscall. - Move all dev genesis content (fork times, TTD, deposit contract, system-contract allocs) into DeveloperGenesisBlock()/allocs/dev.json, and extract the remaining Caplin/beacon-genesis setup from the SetEthConfig switch into setDevnetEthConfig. - Delete the builder's post-AssembleBlock RequestsHash override: it wrote to a discarded header copy. The final block is rebuilt from current.Header in finishBlock, which merge.FinalizeAndAssemble already stamps with outRequests.Hash() (empty.RequestsHash for empty sets). Verified dead by running the new regression test against the pre-fix zero-hash code (passes) and against zero-hash corruption injected into the live merge.go path (fails with 'invalid block hash'). - Add TestEngineApiBuiltBlockEmptyRequestsHash pinning the requests hash of an empty-request Prague block end to end.
Dev mode now self-enables the Beacon API endpoint groups the embedded dev validator needs.
…erigontech#21723) ## Problem `kurtosis / assertoor_regular_serial_test` failed on erigontech#21646 ([job link](https://github.com/erigontech/erigon/actions/runs/27271494252/job/80543276225)): the runner lost Docker Hub connectivity for the entire job (even the Docker login step timed out against `registry-1.docker.io`), and `kurtosis engine start` exhausted all 3 retries failing to pull `badouralix/curl-jq:latest` — the logs-aggregator healthcheck container. A sibling job on a different runner passed at the same time, so this was per-runner registry egress, i.e. exactly the class of flake the cached-image setup exists to absorb. ## Root cause The `docker-cl-*` cache covers the CL images plus kurtosis engine/core/expander/vector/fluent-bit, but `kurtosis engine start` launches **three more** helper containers, none of them cached: - `badouralix/curl-jq:latest` — logs-aggregator healthcheck (`logs_aggregator_functions/shared_helpers.go` in kurtosis-tech/kurtosis) - `traefik:2.10.6` — reverse proxy (`reverse_proxy_functions/implementations/traefik/consts.go`) - `alpine:3.17` — volume-init helper (`engine_functions`/`logs_collector_functions`) A passing run's log confirms these are the only three images pulled at engine start (every cached image shows no pull line), so the engine-start step — whose stated purpose is to keep registry blips out of the test step — still had a hard Docker Hub dependency. ## Fix Add the three images to the existing pull → `docker save` → cache → `docker load` pipeline and cache keys in both `test-kurtosis-assertoor.yml` and `test-kurtosis-gloas.yml`, following the established vector/fluent-bit pattern. Kurtosis uses image download mode "missing", so pre-loaded images are used without contacting the registry (verified in the passing run: vector/fluent-bit are started without pull lines). After this, `kurtosis engine start` is fully cache-served. ## Rollout - The cache key changes, so this PR's own kurtosis runs cold-pull once and exercise the new pull/save path. - On merge, the push touching these files triggers `cache-warming-kurtosis-cl-images.yml` and `cache-warming-kurtosis-gloas-images.yml` (paths filters) which re-warm the main-scope cache under the new key. No manual action needed. ## Residual exposure (out of scope) Enclave-time images with intentionally mutable tags (`ethpandaops/ethereum-genesis-generator`, `rpc-snooper:latest`, `spamoor:master`, suite-specific CL devnet tags, …) are still pulled from Docker Hub during `kurtosis run`. Pinning/caching those would change test semantics (they deliberately track moving tags), so they stay as-is. ## Validation - `actionlint`: no new findings (the two pre-existing SC2086 infos in the apt-get line are unchanged) - YAML parse clean; resolved cache key ≈ 209 chars (limit 512) - `make lint`: 0 issues
Summary
--chain devcouldn't produce blocks past genesis. Two bugs, plus a builder cleanup found while addressing review:Beacon API routes not registered — dev mode set
BeaconAPIRouter.Active=truebut never enabled endpoint groups (Beacon,Node,Validator,Config,Events). The embedded dev validator polls/eth/v1/beacon/genesiswhich returned 404, deadlocking block production.CL/EL fork mismatch: Prague/Osaka not activated, system contracts missing — the CL ran Fulu (epoch 0) but the EL had
PragueTime=nilandOsakaTime=nil. Pre-Prague EL headers carry norequestsHash, while the CL reconstructs the header withSHA256("")=e3b0c442…over the (empty) execution-requests list, so every produced block was rejected with "mismatching hash" (cl/cltypes/eth1_block.go). Activating Prague in turn requires the system-contract predeploys in the dev genesis alloc.Dead
requestsHashoverride in the builder removed —execution/builder/exec.gore-derivedheader.RequestsHashafterAssembleBlock, but on a discarded header copy: the final block is rebuilt fromcurrent.HeaderinfinishBlock, whichmerge.FinalizeAndAssemblealready stamps withoutRequests.Hash()(empty.RequestsHashfor empty request sets). An earlier revision of this PR changed the dead write fromcommon.Hash{}toempty.RequestsHash; it is now deleted outright, leavingFinalizeAndAssembleas the single source of truth. Verified dead by running the new regression test against the old zero-hash code (passes) and against zero-hash corruption injected into the livemerge.gopath (fails with "invalid block hash").The dev genesis is now a faithful Cancun+Prague genesis and lives in the genesis layer:
allocs/dev.json(copied verbatim fromallocs/hoodi.json): EIP-6110 deposit contract (withDepositContractset in the chain config — fixes theParseDepositLogsWARN on every block), EIP-4788 beacon roots (otherwise a silent no-op with Cancun at genesis), EIP-2935 history storage, EIP-7002 withdrawal requests, EIP-7251 consolidation requests.SetEthConfigintoDeveloperGenesisBlock(); the remaining Caplin/beacon-genesis setup is extracted intosetDevnetEthConfig, leaving theSetEthConfigswitch case at one line.TestEngineApiBuiltBlockEmptyRequestsHashpins the requests hash of an empty-request Prague block end to end (FCU → getPayload → newPayload → canonical header via RPC).Dev mode now runs full Fusaka: Fulu on CL, Osaka on EL.
Note: the dev genesis hash changes (new predeploys in the alloc) — wipe existing
--chain devdatadirs.Test plan
erigon --chain dev --dev.slot-time 2produces Fulu/Osaka blocks at 2s intervalsParseDepositLogsWARNs; EIP-4788 ring buffer populated (eth_getStorageAtof slottimestamp % 8191returns the block timestamp); blockrequestsHashise3b0c442…TestEngineApiBuiltBlockEmptyRequestsHashfails on injected live-path corruption, passes otherwise