ethereum/go-ethereum · error
invalid fork, simulated beacon needs to be started post-merg
Error message
invalid fork, simulated beacon needs to be started post-merge
What it means
payloadVersion maps the chain's latest fork at a given time to an engine-API payload version. The simulated beacon (dev mode) can only produce post-merge payloads (PayloadV2/V3/V4); if the latest active fork is anything pre-Paris (or an unknown fork), the switch falls through and the function panics because no merge payload format exists for that state.
Source
Thrown at eth/catalyst/simulated_beacon.go:114
feeRecipient common.Address
feeRecipientLock sync.Mutex // lock gates concurrent access to the feeRecipient
engineAPI *ConsensusAPI
curForkchoiceState engine.ForkchoiceStateV1
lastBlockTime uint64
}
func payloadVersion(config *params.ChainConfig, time uint64) engine.PayloadVersion {
switch config.LatestFork(time) {
case forks.Amsterdam:
return engine.PayloadV4
case forks.Bogota, forks.BPO5, forks.BPO4, forks.BPO3, forks.BPO2, forks.BPO1, forks.Osaka, forks.Prague, forks.Cancun:
return engine.PayloadV3
case forks.Paris, forks.Shanghai:
return engine.PayloadV2
}
panic("invalid fork, simulated beacon needs to be started post-merge")
}
// NewSimulatedBeacon constructs a new simulated beacon chain.
func NewSimulatedBeacon(period uint64, feeRecipient common.Address, eth *eth.Ethereum) (*SimulatedBeacon, error) {
block := eth.BlockChain().CurrentBlock()
current := engine.ForkchoiceStateV1{
HeadBlockHash: block.Hash(),
SafeBlockHash: block.Hash(),
FinalizedBlockHash: block.Hash(),
}
engineAPI := newConsensusAPIWithoutHeartbeat(eth)
// if genesis block, send forkchoiceUpdated to trigger transition to PoS
if block.Number.Sign() == 0 {
version := payloadVersion(eth.BlockChain().Config(), block.Time)
if _, err := engineAPI.forkchoiceUpdated(context.Background(), current, nil, version, false); err != nil {
return nil, err
}View on GitHub (pinned to 6bb0588ad8)
Solutions
- Use the default dev genesis (geth --dev without a custom genesis file), which is post-merge by construction.
- If a custom genesis is required, set terminalTotalDifficulty to 0 so the chain starts post-merge, and ensure all fork timestamps are at or before the dev chain's start time.
- Verify with 'geth attach' that eth.getBlock('latest').difficulty == 0 before enabling the simulated beacon.
Example fix
// before: datadir/genesis.json
"config": { "chainId": 1337, "homesteadBlock": 0 /* no TTD */ }
// after
"config": { "chainId": 1337, "homesteadBlock": 0, "terminalTotalDifficulty": 0, " parisBlock": 0 } Defensive patterns
Strategy: validation
Validate before calling
// ensure the chain is post-merge before enabling --dev / simulated beacon
head := eth.BlockChain().CurrentBlock()
if head.Difficulty.Cmp(common.Big0) > 0 && cfg.TerminalTotalDifficulty == nil {
return errors.New("simulated beacon requires a post-merge genesis; set terminalTotalDifficulty: 0")
} Try / catch
Not applicable — fix the genesis; recovering would leave dev mode without a payload version.
Prevention
- Prefer stock --dev mode, which ships a valid post-merge dev genesis.
- When customizing a dev genesis, set terminalTotalDifficulty to 0 and align fork timestamps with genesis.
- Smoke-test custom dev geneses with a single --dev.period block before automation.
When it happens
Trigger: Starting geth with --dev (or --dev.period) against a genesis whose merge (Paris/terminal total difficulty) is not active at the current block/time — e.g. TTD or mergeBlock set in the future, or a custom pre-merge genesis — so config.LatestFork(time) returns a pre-Paris fork.
Common situations: Custom dev genesis JSON with pre-merge fork settings or unset merge fields; using a copied mainnet genesis with a future Shanghai timestamp while the simulated beacon starts at genesis time; version drift where a fork enum is not covered by the switch.
Related errors
- nil chainID
- error decoding contract deployer hex %s: %v
- Could not create random uuid: %v
- key generation: could not read from random source:
- key generation: ecdsa.GenerateKey failed:
AI-assisted analysis of ethereum/go-ethereum@6bb0588ad8 (2026-08-15).
Data as JSON: /api/errors/ea232c9d4ea3db2a.
Report an issue: GitHub.