From 4074f745bd42ea7f407c6e6976753ede916c54e4 Mon Sep 17 00:00:00 2001 From: Nikita Mescheryakov Date: Tue, 23 Sep 2025 23:00:16 +0500 Subject: [PATCH] internal/ethapi: fix merge transition in eth_simulate (#32616) This PR fixes the fork detection of `eth_simulateV1`, particularly for networks that are post-merge since genesis, like Hoodi. --- consensus/beacon/consensus.go | 14 ++------------ internal/ethapi/simulate.go | 14 +++++++++++--- params/config.go | 10 ++++++++++ 3 files changed, 23 insertions(+), 15 deletions(-) diff --git a/consensus/beacon/consensus.go b/consensus/beacon/consensus.go index 196cbc857c..84926c3d0b 100644 --- a/consensus/beacon/consensus.go +++ b/consensus/beacon/consensus.go @@ -71,16 +71,6 @@ func New(ethone consensus.Engine) *Beacon { return &Beacon{ethone: ethone} } -// isPostMerge reports whether the given block number is assumed to be post-merge. -// Here we check the MergeNetsplitBlock to allow configuring networks with a PoW or -// PoA chain for unit testing purposes. -func isPostMerge(config *params.ChainConfig, blockNum uint64, timestamp uint64) bool { - mergedAtGenesis := config.TerminalTotalDifficulty != nil && config.TerminalTotalDifficulty.Sign() == 0 - return mergedAtGenesis || - config.MergeNetsplitBlock != nil && blockNum >= config.MergeNetsplitBlock.Uint64() || - config.ShanghaiTime != nil && timestamp >= *config.ShanghaiTime -} - // Author implements consensus.Engine, returning the verified author of the block. func (beacon *Beacon) Author(header *types.Header) (common.Address, error) { if !beacon.IsPoSHeader(header) { @@ -328,7 +318,7 @@ func (beacon *Beacon) verifyHeaders(chain consensus.ChainHeaderReader, headers [ // Prepare implements consensus.Engine, initializing the difficulty field of a // header to conform to the beacon protocol. The changes are done inline. func (beacon *Beacon) Prepare(chain consensus.ChainHeaderReader, header *types.Header) error { - if !isPostMerge(chain.Config(), header.Number.Uint64(), header.Time) { + if !chain.Config().IsPostMerge(header.Number.Uint64(), header.Time) { return beacon.ethone.Prepare(chain, header) } header.Difficulty = beaconDifficulty @@ -442,7 +432,7 @@ func (beacon *Beacon) SealHash(header *types.Header) common.Hash { // the difficulty that a new block should have when created at time // given the parent block's time and difficulty. func (beacon *Beacon) CalcDifficulty(chain consensus.ChainHeaderReader, time uint64, parent *types.Header) *big.Int { - if !isPostMerge(chain.Config(), parent.Number.Uint64()+1, time) { + if !chain.Config().IsPostMerge(parent.Number.Uint64()+1, time) { return beacon.ethone.CalcDifficulty(chain, time, parent) } return beaconDifficulty diff --git a/internal/ethapi/simulate.go b/internal/ethapi/simulate.go index e26e5bd0e9..75b5c5ffa8 100644 --- a/internal/ethapi/simulate.go +++ b/internal/ethapi/simulate.go @@ -474,22 +474,30 @@ func (sim *simulator) makeHeaders(blocks []simBlock) ([]*types.Header, error) { overrides := block.BlockOverrides var withdrawalsHash *common.Hash - if sim.chainConfig.IsShanghai(overrides.Number.ToInt(), (uint64)(*overrides.Time)) { + number := overrides.Number.ToInt() + timestamp := (uint64)(*overrides.Time) + if sim.chainConfig.IsShanghai(number, timestamp) { withdrawalsHash = &types.EmptyWithdrawalsHash } var parentBeaconRoot *common.Hash - if sim.chainConfig.IsCancun(overrides.Number.ToInt(), (uint64)(*overrides.Time)) { + if sim.chainConfig.IsCancun(number, timestamp) { parentBeaconRoot = &common.Hash{} if overrides.BeaconRoot != nil { parentBeaconRoot = overrides.BeaconRoot } } + // Set difficulty to zero if the given block is post-merge. Without this, all post-merge hardforks would remain inactive. + // For example, calling eth_simulateV1(..., blockParameter: 0x0) on hoodi network will cause all blocks to have a difficulty of 1 and be treated as pre-merge. + difficulty := header.Difficulty + if sim.chainConfig.IsPostMerge(number.Uint64(), timestamp) { + difficulty = big.NewInt(0) + } header = overrides.MakeHeader(&types.Header{ UncleHash: types.EmptyUncleHash, ReceiptHash: types.EmptyReceiptsHash, TxHash: types.EmptyTxsHash, Coinbase: header.Coinbase, - Difficulty: header.Difficulty, + Difficulty: difficulty, GasLimit: header.GasLimit, WithdrawalsHash: withdrawalsHash, ParentBeaconRoot: parentBeaconRoot, diff --git a/params/config.go b/params/config.go index cf94fb6758..3788325d5c 100644 --- a/params/config.go +++ b/params/config.go @@ -678,6 +678,16 @@ func (c *ChainConfig) IsTerminalPoWBlock(parentTotalDiff *big.Int, totalDiff *bi return parentTotalDiff.Cmp(c.TerminalTotalDifficulty) < 0 && totalDiff.Cmp(c.TerminalTotalDifficulty) >= 0 } +// IsPostMerge reports whether the given block number is assumed to be post-merge. +// Here we check the MergeNetsplitBlock to allow configuring networks with a PoW or +// PoA chain for unit testing purposes. +func (c *ChainConfig) IsPostMerge(blockNum uint64, timestamp uint64) bool { + mergedAtGenesis := c.TerminalTotalDifficulty != nil && c.TerminalTotalDifficulty.Sign() == 0 + return mergedAtGenesis || + c.MergeNetsplitBlock != nil && blockNum >= c.MergeNetsplitBlock.Uint64() || + c.ShanghaiTime != nil && timestamp >= *c.ShanghaiTime +} + // IsShanghai returns whether time is either equal to the Shanghai fork time or greater. func (c *ChainConfig) IsShanghai(num *big.Int, time uint64) bool { return c.IsLondon(num) && isTimestampForked(c.ShanghaiTime, time)