Skip to content

fix: resolve in-memory parent header for multi-block eth_simulateV1#365

Open
TheFrozenFire wants to merge 1 commit into
bnb-chain:mainfrom
TheFrozenFire:fix/eth-simulatev1-multiblock-parent-header
Open

fix: resolve in-memory parent header for multi-block eth_simulateV1#365
TheFrozenFire wants to merge 1 commit into
bnb-chain:mainfrom
TheFrozenFire:fix/eth-simulatev1-multiblock-parent-header

Conversation

@TheFrozenFire

Copy link
Copy Markdown

Description

eth_simulateV1 returned -32603 "Failed to get parent header from global header reader" whenever a request contained more than one blockStateCalls entry (one block works, two or more fail). This makes each block produced during a multi-block simulation visible to the global header reader by hash, so the next block's parlia pre-execution can resolve its parent. Fixes bnb-chain/reth#194.

Rationale

The simulate loop chains blocks: block N+1's parent is the in-memory block N produced one iteration earlier. BSC pre-execution (check_new_block) and the parlia snapshot rebuild resolve the parent by hash via HEADER_CACHE_READER, which only contains real canonical headers. Block 1's parent is the canonical chain tip and resolves; block 2's parent is the synthetic block 1, which is never persisted, so the lookup returns None and execution aborts. geth serves multi-block simulate fine, so this is a reth-bsc-specific gap.

Example

Reproduced against a BSC archive node. (The reported node version 1.10.2-dev is the bnb-chain/reth baseline — reth-node-core 1.10.2 with the -dev suffix for an untagged build — not a distinct reth-bsc release.)

# 1 block — succeeds
cast rpc eth_simulateV1 --raw '[{"blockStateCalls":[{"calls":[]}]},"latest"]' --rpc-url $URL

# 2 blocks — fails before this change
cast rpc eth_simulateV1 --raw '[{"blockStateCalls":[{"calls":[]},{"calls":[]}]},"latest"]' --rpc-url $URL
# => error -32603 "Failed to get parent header from global header reader"

With this change the two-block request is expected to return two simulated blocks instead of erroring.

Note: the failing case above is reproduced; the success case has not yet been validated on a node built from this branch (the publicly reachable endpoint runs the pre-fix binary). On-node verification of the two-block call — including a simulated block landing on an epoch boundary — is still pending.

Changes

Notable changes:

  • BscBlockBuilder::apply_pre_execution_changes seeds its parent header into HEADER_CACHE_READER by hash before pre-execution runs.
  • Add HeaderCacheReader::insert_header_hash_only / insert_header_to_cache_hash_only — a hash-keyed insert that deliberately leaves the canonical block-number index untouched.
  • Add regression test test_simulated_block_parent_resolvable_by_hash_only.

Potential Impacts

  • Real block building/import: the parent is already canonical, so the seed is a no-op re-insert (same header, same hash) — no behavior change.
  • The snapshot lookup (snapshot_by_hash) is fixed implicitly: the provider falls back to get_header_by_hash + try_rebuild, which now walks the seeded header back to the tip's snapshot.
  • Hash-only seeding intentionally does not write the block-number index, so a speculative simulated block can never shadow the real chain in by-number lookups.
  • Memory: each simulated block adds one entry to the 100k-capacity LRU hash map — negligible.

eth_simulateV1 aborted with "Failed to get parent header from global
header reader" whenever a request contained more than one blockStateCalls
entry. The simulate loop chains blocks, so block N+1's parent is the
in-memory block N produced one iteration earlier. BSC pre-execution
(check_new_block) and the parlia snapshot rebuild resolve the parent by
hash through the global header reader, which only holds real canonical
headers, so the synthetic parent was unresolvable.

BscBlockBuilder now seeds its parent header into the reader by hash only,
leaving the canonical block-number index untouched, before pre-execution
runs. For real block building the parent is already canonical, so this is
a no-op re-seed; for simulation it makes each produced block resolvable as
the next block's parent. The snapshot lookup is fixed implicitly via the
provider's get_header_by_hash + try_rebuild fallback.

Fixes bnb-chain/reth#194
@hashdit-bot

hashdit-bot Bot commented Jun 9, 2026

Copy link
Copy Markdown

Pull Request Review

This PR fixes a BSC/reth eth_simulateV1 multi-block simulation bug by making each in-memory simulated parent header discoverable via the global header cache using its hash before pre-execution. It introduces a new hash-only cache insertion path (insert_header_hash_only / insert_header_to_cache_hash_only) so simulated blocks are available for parent-by-hash resolution without polluting canonical number-based lookups. A regression test was added to verify that hash-based lookup succeeds for seeded simulated headers while by-number lookup remains untouched.

Sensitive Content

No sensitive content detected.

Security Issues

No serious security issues detected.


Generated by Hashdit Bot. This tool can absolutely NOT replace manual audits.

@TheFrozenFire
TheFrozenFire marked this pull request as ready for review June 10, 2026 15:15
@TheFrozenFire
TheFrozenFire requested a review from joey0612 as a code owner June 10, 2026 15:15
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

eth_simulateV1 not working with >1 blockStateCalls

1 participant