Skip to content

engine: Restrict no-reorg to the prefix of known finalized#786

Merged
fjl merged 1 commit into
ethereum:mainfrom
mkalinin:reorg-not-finalized
Apr 23, 2026
Merged

engine: Restrict no-reorg to the prefix of known finalized#786
fjl merged 1 commit into
ethereum:mainfrom
mkalinin:reorg-not-finalized

Conversation

@mkalinin

Copy link
Copy Markdown
Contributor

This PR is an alternative to #770 and introduces the following changes:

  • Restricts a no-reorg optimization to an ancestor of the latest finalized block known to the EL client
  • Introduces a -38006: Too deep reorg error to handle the case when EL client cannot do a reorg due to the depth being beyond client's reorg capability. This depth is supposed to be implementation specific

For detail see discussion in #770 thread.

@fjl fjl merged commit 4db2ff9 into ethereum:main Apr 23, 2026
5 checks passed
@potuz

potuz commented Apr 24, 2026

Copy link
Copy Markdown
Contributor

Just a comment to leave somewhere, but I think these types of reorgs going back on the canonical chain can only happen up to the "Justified" hash instead of the finalized one, eg. most validators are malicious and start voting on top of the justified root but empty.

jihoonsong added a commit to jihoonsong/execution-apis that referenced this pull request Apr 24, 2026
daniellehrner added a commit to daniellehrner/besu that referenced this pull request Apr 28, 2026
Create a new abstract engine_fcu class for it to not affect previous hardforks including Osaka which currently runs on mainnet

Signed-off-by: daniellehrner <daniel.lehrner@consensys.net>
daniellehrner added a commit to besu-eth/besu that referenced this pull request Apr 28, 2026
Implement changes from ethereum/execution-apis#786 :

Create a new abstract engine_fcu class for it to not affect previous hardforks including Osaka which currently runs on mainnet

Signed-off-by: daniellehrner <daniel.lehrner@consensys.net>
Sahil-4555 pushed a commit to Sahil-4555/erigon that referenced this pull request Apr 30, 2026
… hash (erigontech#20825)

closes erigontech#20888

implements ethereum/execution-apis#786

which is needed for glamsterdam-devnet-0

---------

Co-authored-by: Alex Sharov <AskAlexSharov@gmail.com>
edg-l pushed a commit to lambdaclass/ethrex that referenced this pull request Apr 30, 2026
Implements ethereum/execution-apis#786 (engine:
Restrict no-reorg to the prefix of known finalized) on top of bal-devnet-4
for the glamsterdam-devnet-0 testbed, as requested by EthPandaOps.

Spec changes implemented:

1. The no-reorg skip optimization in engine_forkchoiceUpdated is now
   restricted to the case where the head references a VALID canonical
   ancestor of the latest known finalized block. Previously ethrex skipped
   for any canonical ancestor below the current head; that is no longer
   permitted by the spec for unfinalized ancestors, which must trigger a
   reorg back.

2. A new -38006 "Too deep reorg" engine error is introduced. It is
   returned when an FCU would replace more canonical blocks than the
   client's implementation-specific limit. Mapped through a new
   InvalidForkChoice::TooDeepReorg variant and a new RpcErr::TooDeepReorg
   variant carrying JSON-RPC error code -38006.

Implementation decisions not specified by the spec:

- REORG_DEPTH_LIMIT = 32. The spec leaves this implementation-specific.
  32 was chosen because it matches one Ethereum finalization epoch and
  aligns with the value nethermind picked for the closely related
  execution-apis PR 770 in their engine-api-glamsterdam branch.
- Reorg depth is computed as (latest_block_number - canonical_link_height),
  where canonical_link_height is the head itself when the head is canonical
  (canonical-truncate FCU) and the height of head's deepest canonical
  ancestor otherwise (sidechain reorg). This correctly counts the number
  of canonical blocks that would be replaced.
- The skip optimization fires for head.number <= stored_finalized rather
  than strict <. Strictly the spec says "ancestor", but treating the
  finalized block itself as a valid skip target is a no-op in practice.
- The change is unconditional and not gated on a fork timestamp. Spec PR
  786 edits paris.md, framing the change as a clarification of merge-era
  semantics rather than a new fork rule, so it applies on all forks.
- L2 callers of apply_fork_choice (l2_connection, block_producer,
  block_fetcher) are intentionally untouched. Their head=safe=finalized
  pattern always leaves stored_finalized >= head.number, so the new skip
  rule never fires there, and reorg depth is at most 1.

Tests:

- The existing test_new_head_with_canonical_ancestor_should_skip smoke
  test asserted the old behavior. Renamed to
  new_head_ancestor_of_finalized_should_skip and rewritten to set up an
  ancestor-of-finalized scenario, which is now the only case that
  triggers the skip.
- All 5 fork-choice smoke tests in test/tests/blockchain pass.
- EF blockchain (LEVM + stateless) and state suites pass on the parent
  branch (verification of the bal-devnet-4 baseline).
- Hive engine simulator verification is the next step and should be run
  before connecting to the devnet.

References:
- execution-apis PR 786 (merged): restricts no-reorg, adds -38006
- execution-apis PR 770 (closed/superseded): the alternative reorg-depth
  approach nethermind ships; informs the depth-limit constant choice.
daniellehrner added a commit to besu-eth/besu that referenced this pull request Apr 30, 2026
Implement changes from ethereum/execution-apis#786 :

Create a new abstract engine_fcu class for it to not affect previous hardforks including Osaka which currently runs on mainnet

Signed-off-by: daniellehrner <daniel.lehrner@consensys.net>
edg-l pushed a commit to lambdaclass/ethrex that referenced this pull request May 6, 2026
Implements ethereum/execution-apis#786 (engine:
Restrict no-reorg to the prefix of known finalized) on top of bal-devnet-4
for the glamsterdam-devnet-0 testbed, as requested by EthPandaOps.

Spec changes implemented:

1. The no-reorg skip optimization in engine_forkchoiceUpdated is now
   restricted to the case where the head references a VALID canonical
   ancestor of the latest known finalized block. Previously ethrex skipped
   for any canonical ancestor below the current head; that is no longer
   permitted by the spec for unfinalized ancestors, which must trigger a
   reorg back.

2. A new -38006 "Too deep reorg" engine error is introduced. It is
   returned when an FCU would replace more canonical blocks than the
   client's implementation-specific limit. Mapped through a new
   InvalidForkChoice::TooDeepReorg variant and a new RpcErr::TooDeepReorg
   variant carrying JSON-RPC error code -38006.

Implementation decisions not specified by the spec:

- REORG_DEPTH_LIMIT = 32. The spec leaves this implementation-specific.
  32 was chosen because it matches one Ethereum finalization epoch and
  aligns with the value nethermind picked for the closely related
  execution-apis PR 770 in their engine-api-glamsterdam branch.
- Reorg depth is computed as (latest_block_number - canonical_link_height),
  where canonical_link_height is the head itself when the head is canonical
  (canonical-truncate FCU) and the height of head's deepest canonical
  ancestor otherwise (sidechain reorg). This correctly counts the number
  of canonical blocks that would be replaced.
- The skip optimization fires for head.number <= stored_finalized rather
  than strict <. Strictly the spec says "ancestor", but treating the
  finalized block itself as a valid skip target is a no-op in practice.
- The change is unconditional and not gated on a fork timestamp. Spec PR
  786 edits paris.md, framing the change as a clarification of merge-era
  semantics rather than a new fork rule, so it applies on all forks.
- L2 callers of apply_fork_choice (l2_connection, block_producer,
  block_fetcher) are intentionally untouched. Their head=safe=finalized
  pattern always leaves stored_finalized >= head.number, so the new skip
  rule never fires there, and reorg depth is at most 1.

Tests:

- The existing test_new_head_with_canonical_ancestor_should_skip smoke
  test asserted the old behavior. Renamed to
  new_head_ancestor_of_finalized_should_skip and rewritten to set up an
  ancestor-of-finalized scenario, which is now the only case that
  triggers the skip.
- All 5 fork-choice smoke tests in test/tests/blockchain pass.
- EF blockchain (LEVM + stateless) and state suites pass on the parent
  branch (verification of the bal-devnet-4 baseline).
- Hive engine simulator verification is the next step and should be run
  before connecting to the devnet.

References:
- execution-apis PR 786 (merged): restricts no-reorg, adds -38006
- execution-apis PR 770 (closed/superseded): the alternative reorg-depth
  approach nethermind ships; informs the depth-limit constant choice.
edg-l pushed a commit to lambdaclass/ethrex that referenced this pull request May 6, 2026
Implements ethereum/execution-apis#786 (engine:
Restrict no-reorg to the prefix of known finalized) on top of bal-devnet-4
for the glamsterdam-devnet-0 testbed, as requested by EthPandaOps.

Spec changes implemented:

1. The no-reorg skip optimization in engine_forkchoiceUpdated is now
   restricted to the case where the head references a VALID canonical
   ancestor of the latest known finalized block. Previously ethrex skipped
   for any canonical ancestor below the current head; that is no longer
   permitted by the spec for unfinalized ancestors, which must trigger a
   reorg back.

2. A new -38006 "Too deep reorg" engine error is introduced. It is
   returned when an FCU would replace more canonical blocks than the
   client's implementation-specific limit. Mapped through a new
   InvalidForkChoice::TooDeepReorg variant and a new RpcErr::TooDeepReorg
   variant carrying JSON-RPC error code -38006.

Implementation decisions not specified by the spec:

- REORG_DEPTH_LIMIT = 32. The spec leaves this implementation-specific.
  32 was chosen because it matches one Ethereum finalization epoch and
  aligns with the value nethermind picked for the closely related
  execution-apis PR 770 in their engine-api-glamsterdam branch.
- Reorg depth is computed as (latest_block_number - canonical_link_height),
  where canonical_link_height is the head itself when the head is canonical
  (canonical-truncate FCU) and the height of head's deepest canonical
  ancestor otherwise (sidechain reorg). This correctly counts the number
  of canonical blocks that would be replaced.
- The skip optimization fires for head.number <= stored_finalized rather
  than strict <. Strictly the spec says "ancestor", but treating the
  finalized block itself as a valid skip target is a no-op in practice.
- The change is unconditional and not gated on a fork timestamp. Spec PR
  786 edits paris.md, framing the change as a clarification of merge-era
  semantics rather than a new fork rule, so it applies on all forks.
- L2 callers of apply_fork_choice (l2_connection, block_producer,
  block_fetcher) are intentionally untouched. Their head=safe=finalized
  pattern always leaves stored_finalized >= head.number, so the new skip
  rule never fires there, and reorg depth is at most 1.

Tests:

- The existing test_new_head_with_canonical_ancestor_should_skip smoke
  test asserted the old behavior. Renamed to
  new_head_ancestor_of_finalized_should_skip and rewritten to set up an
  ancestor-of-finalized scenario, which is now the only case that
  triggers the skip.
- All 5 fork-choice smoke tests in test/tests/blockchain pass.
- EF blockchain (LEVM + stateless) and state suites pass on the parent
  branch (verification of the bal-devnet-4 baseline).
- Hive engine simulator verification is the next step and should be run
  before connecting to the devnet.

References:
- execution-apis PR 786 (merged): restricts no-reorg, adds -38006
- execution-apis PR 770 (closed/superseded): the alternative reorg-depth
  approach nethermind ships; informs the depth-limit constant choice.
edg-l pushed a commit to lambdaclass/ethrex that referenced this pull request May 6, 2026
Implements ethereum/execution-apis#786 (engine:
Restrict no-reorg to the prefix of known finalized) on top of bal-devnet-4
for the glamsterdam-devnet-0 testbed, as requested by EthPandaOps.

Spec changes implemented:

1. The no-reorg skip optimization in engine_forkchoiceUpdated is now
   restricted to the case where the head references a VALID canonical
   ancestor of the latest known finalized block. Previously ethrex skipped
   for any canonical ancestor below the current head; that is no longer
   permitted by the spec for unfinalized ancestors, which must trigger a
   reorg back.

2. A new -38006 "Too deep reorg" engine error is introduced. It is
   returned when an FCU would replace more canonical blocks than the
   client's implementation-specific limit. Mapped through a new
   InvalidForkChoice::TooDeepReorg variant and a new RpcErr::TooDeepReorg
   variant carrying JSON-RPC error code -38006.

Implementation decisions not specified by the spec:

- REORG_DEPTH_LIMIT = 32. The spec leaves this implementation-specific.
  32 was chosen because it matches one Ethereum finalization epoch and
  aligns with the value nethermind picked for the closely related
  execution-apis PR 770 in their engine-api-glamsterdam branch.
- Reorg depth is computed as (latest_block_number - canonical_link_height),
  where canonical_link_height is the head itself when the head is canonical
  (canonical-truncate FCU) and the height of head's deepest canonical
  ancestor otherwise (sidechain reorg). This correctly counts the number
  of canonical blocks that would be replaced.
- The skip optimization fires for head.number <= stored_finalized rather
  than strict <. Strictly the spec says "ancestor", but treating the
  finalized block itself as a valid skip target is a no-op in practice.
- The change is unconditional and not gated on a fork timestamp. Spec PR
  786 edits paris.md, framing the change as a clarification of merge-era
  semantics rather than a new fork rule, so it applies on all forks.
- L2 callers of apply_fork_choice (l2_connection, block_producer,
  block_fetcher) are intentionally untouched. Their head=safe=finalized
  pattern always leaves stored_finalized >= head.number, so the new skip
  rule never fires there, and reorg depth is at most 1.

Tests:

- The existing test_new_head_with_canonical_ancestor_should_skip smoke
  test asserted the old behavior. Renamed to
  new_head_ancestor_of_finalized_should_skip and rewritten to set up an
  ancestor-of-finalized scenario, which is now the only case that
  triggers the skip.
- All 5 fork-choice smoke tests in test/tests/blockchain pass.
- EF blockchain (LEVM + stateless) and state suites pass on the parent
  branch (verification of the bal-devnet-4 baseline).
- Hive engine simulator verification is the next step and should be run
  before connecting to the devnet.

References:
- execution-apis PR 786 (merged): restricts no-reorg, adds -38006
- execution-apis PR 770 (closed/superseded): the alternative reorg-depth
  approach nethermind ships; informs the depth-limit constant choice.
edg-l pushed a commit to lambdaclass/ethrex that referenced this pull request May 7, 2026
Implements ethereum/execution-apis#786 (engine:
Restrict no-reorg to the prefix of known finalized) on top of bal-devnet-4
for the glamsterdam-devnet-0 testbed, as requested by EthPandaOps.

Spec changes implemented:

1. The no-reorg skip optimization in engine_forkchoiceUpdated is now
   restricted to the case where the head references a VALID canonical
   ancestor of the latest known finalized block. Previously ethrex skipped
   for any canonical ancestor below the current head; that is no longer
   permitted by the spec for unfinalized ancestors, which must trigger a
   reorg back.

2. A new -38006 "Too deep reorg" engine error is introduced. It is
   returned when an FCU would replace more canonical blocks than the
   client's implementation-specific limit. Mapped through a new
   InvalidForkChoice::TooDeepReorg variant and a new RpcErr::TooDeepReorg
   variant carrying JSON-RPC error code -38006.

Implementation decisions not specified by the spec:

- REORG_DEPTH_LIMIT = 32. The spec leaves this implementation-specific.
  32 was chosen because it matches one Ethereum finalization epoch and
  aligns with the value nethermind picked for the closely related
  execution-apis PR 770 in their engine-api-glamsterdam branch.
- Reorg depth is computed as (latest_block_number - canonical_link_height),
  where canonical_link_height is the head itself when the head is canonical
  (canonical-truncate FCU) and the height of head's deepest canonical
  ancestor otherwise (sidechain reorg). This correctly counts the number
  of canonical blocks that would be replaced.
- The skip optimization fires for head.number <= stored_finalized rather
  than strict <. Strictly the spec says "ancestor", but treating the
  finalized block itself as a valid skip target is a no-op in practice.
- The change is unconditional and not gated on a fork timestamp. Spec PR
  786 edits paris.md, framing the change as a clarification of merge-era
  semantics rather than a new fork rule, so it applies on all forks.
- L2 callers of apply_fork_choice (l2_connection, block_producer,
  block_fetcher) are intentionally untouched. Their head=safe=finalized
  pattern always leaves stored_finalized >= head.number, so the new skip
  rule never fires there, and reorg depth is at most 1.

Tests:

- The existing test_new_head_with_canonical_ancestor_should_skip smoke
  test asserted the old behavior. Renamed to
  new_head_ancestor_of_finalized_should_skip and rewritten to set up an
  ancestor-of-finalized scenario, which is now the only case that
  triggers the skip.
- All 5 fork-choice smoke tests in test/tests/blockchain pass.
- EF blockchain (LEVM + stateless) and state suites pass on the parent
  branch (verification of the bal-devnet-4 baseline).
- Hive engine simulator verification is the next step and should be run
  before connecting to the devnet.

References:
- execution-apis PR 786 (merged): restricts no-reorg, adds -38006
- execution-apis PR 770 (closed/superseded): the alternative reorg-depth
  approach nethermind ships; informs the depth-limit constant choice.
edg-l pushed a commit to lambdaclass/ethrex that referenced this pull request May 7, 2026
Implements ethereum/execution-apis#786 (engine:
Restrict no-reorg to the prefix of known finalized) on top of bal-devnet-4
for the glamsterdam-devnet-0 testbed, as requested by EthPandaOps.

Spec changes implemented:

1. The no-reorg skip optimization in engine_forkchoiceUpdated is now
   restricted to the case where the head references a VALID canonical
   ancestor of the latest known finalized block. Previously ethrex skipped
   for any canonical ancestor below the current head; that is no longer
   permitted by the spec for unfinalized ancestors, which must trigger a
   reorg back.

2. A new -38006 "Too deep reorg" engine error is introduced. It is
   returned when an FCU would replace more canonical blocks than the
   client's implementation-specific limit. Mapped through a new
   InvalidForkChoice::TooDeepReorg variant and a new RpcErr::TooDeepReorg
   variant carrying JSON-RPC error code -38006.

Implementation decisions not specified by the spec:

- REORG_DEPTH_LIMIT = 32. The spec leaves this implementation-specific.
  32 was chosen because it matches one Ethereum finalization epoch and
  aligns with the value nethermind picked for the closely related
  execution-apis PR 770 in their engine-api-glamsterdam branch.
- Reorg depth is computed as (latest_block_number - canonical_link_height),
  where canonical_link_height is the head itself when the head is canonical
  (canonical-truncate FCU) and the height of head's deepest canonical
  ancestor otherwise (sidechain reorg). This correctly counts the number
  of canonical blocks that would be replaced.
- The skip optimization fires for head.number <= stored_finalized rather
  than strict <. Strictly the spec says "ancestor", but treating the
  finalized block itself as a valid skip target is a no-op in practice.
- The change is unconditional and not gated on a fork timestamp. Spec PR
  786 edits paris.md, framing the change as a clarification of merge-era
  semantics rather than a new fork rule, so it applies on all forks.
- L2 callers of apply_fork_choice (l2_connection, block_producer,
  block_fetcher) are intentionally untouched. Their head=safe=finalized
  pattern always leaves stored_finalized >= head.number, so the new skip
  rule never fires there, and reorg depth is at most 1.

Tests:

- The existing test_new_head_with_canonical_ancestor_should_skip smoke
  test asserted the old behavior. Renamed to
  new_head_ancestor_of_finalized_should_skip and rewritten to set up an
  ancestor-of-finalized scenario, which is now the only case that
  triggers the skip.
- All 5 fork-choice smoke tests in test/tests/blockchain pass.
- EF blockchain (LEVM + stateless) and state suites pass on the parent
  branch (verification of the bal-devnet-4 baseline).
- Hive engine simulator verification is the next step and should be run
  before connecting to the devnet.

References:
- execution-apis PR 786 (merged): restricts no-reorg, adds -38006
- execution-apis PR 770 (closed/superseded): the alternative reorg-depth
  approach nethermind ships; informs the depth-limit constant choice.
edg-l pushed a commit to lambdaclass/ethrex that referenced this pull request May 8, 2026
Implements ethereum/execution-apis#786 (engine:
Restrict no-reorg to the prefix of known finalized) on top of bal-devnet-4
for the glamsterdam-devnet-0 testbed, as requested by EthPandaOps.

Spec changes implemented:

1. The no-reorg skip optimization in engine_forkchoiceUpdated is now
   restricted to the case where the head references a VALID canonical
   ancestor of the latest known finalized block. Previously ethrex skipped
   for any canonical ancestor below the current head; that is no longer
   permitted by the spec for unfinalized ancestors, which must trigger a
   reorg back.

2. A new -38006 "Too deep reorg" engine error is introduced. It is
   returned when an FCU would replace more canonical blocks than the
   client's implementation-specific limit. Mapped through a new
   InvalidForkChoice::TooDeepReorg variant and a new RpcErr::TooDeepReorg
   variant carrying JSON-RPC error code -38006.

Implementation decisions not specified by the spec:

- REORG_DEPTH_LIMIT = 32. The spec leaves this implementation-specific.
  32 was chosen because it matches one Ethereum finalization epoch and
  aligns with the value nethermind picked for the closely related
  execution-apis PR 770 in their engine-api-glamsterdam branch.
- Reorg depth is computed as (latest_block_number - canonical_link_height),
  where canonical_link_height is the head itself when the head is canonical
  (canonical-truncate FCU) and the height of head's deepest canonical
  ancestor otherwise (sidechain reorg). This correctly counts the number
  of canonical blocks that would be replaced.
- The skip optimization fires for head.number <= stored_finalized rather
  than strict <. Strictly the spec says "ancestor", but treating the
  finalized block itself as a valid skip target is a no-op in practice.
- The change is unconditional and not gated on a fork timestamp. Spec PR
  786 edits paris.md, framing the change as a clarification of merge-era
  semantics rather than a new fork rule, so it applies on all forks.
- L2 callers of apply_fork_choice (l2_connection, block_producer,
  block_fetcher) are intentionally untouched. Their head=safe=finalized
  pattern always leaves stored_finalized >= head.number, so the new skip
  rule never fires there, and reorg depth is at most 1.

Tests:

- The existing test_new_head_with_canonical_ancestor_should_skip smoke
  test asserted the old behavior. Renamed to
  new_head_ancestor_of_finalized_should_skip and rewritten to set up an
  ancestor-of-finalized scenario, which is now the only case that
  triggers the skip.
- All 5 fork-choice smoke tests in test/tests/blockchain pass.
- EF blockchain (LEVM + stateless) and state suites pass on the parent
  branch (verification of the bal-devnet-4 baseline).
- Hive engine simulator verification is the next step and should be run
  before connecting to the devnet.

References:
- execution-apis PR 786 (merged): restricts no-reorg, adds -38006
- execution-apis PR 770 (closed/superseded): the alternative reorg-depth
  approach nethermind ships; informs the depth-limit constant choice.
ilitteri added a commit to benbencik/ethrex that referenced this pull request May 13, 2026
…daclass#6574)

**Motivation**

Bring ethrex up to [bal-devnet-6
spec](https://notes.ethereum.org/@ethpandaops/bal-devnet-6) so we can
participate in the devnet. Rolls up the work that landed across
`bal-devnet-3` → `bal-devnet-4` → `bal-devnet-5` → `bal-devnet-6`. The
original `bal-devnet-6` branch is kept for ongoing devnet work; this
`*-pr` branch is the one intended for merge.

**Description**

- **EIP-7928 (BAL)** — Block Access List support. Fixtures bumped to
snøbal-devnet-6 (`v1.1.0`), `BlockAccessIndex` widened from `u16` to
`u32`, shadow recorder for per-tx access validation in parallel
execution, `SYSTEM_ADDRESS` exclusion.
- **EIP-8037 (2D gas)** — full EELS PR lambdaclass#2689 semantics:
  - `cost_per_state_byte` pinned to 1174 for bal-devnet-4..6.
- Top-level / sub-frame `ExceptionalHalt` reclassifies state gas →
regular dim (both CALL and CREATE return paths).
- REVERT cascade keeps `state_gas_credit_against_drain` elevated so
credit burns propagate.
- CREATE-TX top-halt formula: `gross_spill − credit_against_drain −
already_reclassified` reclassified to regular dim.
- EIP-7702 `set_delegation` refund: subtracts from `state_gas_used` /
`intrinsic_state_gas_charged` (bal-devnet-7-prep accounting,
SELFDESTRUCT-style). See note below.
- `credit_against_drain` capped by `regular_gas_reclassified` so
phantom-drain credits don't cancel real spill.
- `check_2d_gas_allowance` runs per-tx before BAL recording (rejected
txs don't pollute the BAL).
- **EIP-7976 / EIP-7981** — calldata + access-list floor cost
adjustments wired into intrinsic gas + sender refund.
- **Engine / Execution-API** — [execution-apis PR
786](ethereum/execution-apis#786) FCU semantics:
no-reorg restricted to finalized prefix (point 2), `-38006 TooDeepReorg`
returned when the requested reorg depth exceeds ethrex's state-history
retention (point 6).
- **ef-tests** — runner continues processing blocks after
expected-exception (fork-transition tests); 74 bal-devnet-6 Amsterdam
fixtures intentionally skipped (see `docs/known_issues.md`).

**bal-devnet-7 direction (intentional)**

The bal-devnet-6 spec acknowledges as a known-bug that `set_delegation`
for an existing authority should subtract the auth refund from
`tx_state_gas` (mirroring SELFDESTRUCT) but currently does not. Upstream
has a regression test (`9b3961a65
test_snobal_block_gas_used_inflated_by_7702_auth_refund`) that locks in
the un-subtracted behavior for bal-devnet-6 verification. This PR
carries the bal-devnet-7-prep subtraction so we are already aligned with
where EELS is moving; the cost is that 74 snobal-devnet-6 fixtures
expect the old un-subtracted accounting and are skipped via
`tooling/ef_tests/blockchain/tests/all.rs::SKIPPED_BASE` until fixtures
bump to snobal-devnet-7.

**Checklist**

- [ ] Updated `STORE_SCHEMA_VERSION` (crates/storage/lib.rs) if the PR
includes breaking changes to the `Store` requiring a re-sync. *(N/A — no
Store schema changes)*

---------

Co-authored-by: Tomás Arjovsky <tomas.arjovsky@lambdaclass.com>
Co-authored-by: ilitteri <ivanlitteri@hotmail.com>
Co-authored-by: Ivan Litteri <67517699+ilitteri@users.noreply.github.com>
Co-authored-by: MariusVanDerWijden <m.vanderwijden@live.de>
Co-authored-by: Stefan <22667037+qu0b@users.noreply.github.com>
jihoonsong added a commit to jihoonsong/execution-apis that referenced this pull request May 26, 2026
dicethedev pushed a commit to dicethedev/ethrex that referenced this pull request May 28, 2026
…ambdaclass#6676)

## Summary

execution-apis PR
[lambdaclass#786](ethereum/execution-apis#786) revised
paris.md: when `forkchoiceState.headBlockHash` references a VALID
ancestor of the latest known finalized block, the response MUST be
`{payloadStatus: VALID, payloadId: null}` and the client MUST NOT begin
a payload build process.

ethrex already had the correct
`InvalidForkChoice::NewHeadAlreadyCanonical` detection path in
`crates/blockchain/fork_choice.rs` (gate: `head.number <=
stored_finalized && head_is_canonical`; the `<=` matches geth's
`block.NumberU64() <= finalized.Number.Uint64()` in
`eth/catalyst/api.go`, see geth PR
[#34767](ethereum/go-ethereum#34767)).

The bug was in `crates/networking/rpc/engine/fork_choice.rs`: the
`NewHeadAlreadyCanonical` arm returned `Some(head_header)`, which caused
`ForkChoiceUpdatedV3::handle` / `ForkChoiceUpdatedV4::handle` to call
`build_payload` and set a non-null `payloadId` when `payloadAttributes`
was present.

Fix: return `None` for the head header so the V3/V4 dispatch's `if let
(Some(_), Some(_))` guard short-circuits `build_payload`. The `VALID +
latestValidHash = head` response is preserved, matching geth's
`valid(nil)` return.

## Test plan

- `cargo check -p ethrex-rpc` passes
- `cargo clippy -p ethrex-rpc` passes
- `cargo test -p ethrex-rpc` passes
jihoonsong added a commit to ethereum/consensus-specs that referenced this pull request Jun 1, 2026
Currently with proposer boost reorg enabled, proposer of next slot can
suppress fcu call (as laid out by `should_override_forkchoice_update`),
if it thinks there is a chance the current head block can be reorged
out. This is done so because EL could previously skip processing an
`engine_forkchoiceUpdated` call if headBlockHash referenced any ancestor
of the EL's current canonical head.

With ethereum/execution-apis#786, the EL can only skip an fcU if
headBlockHash is an ancestor of the finalized block. Since reorg targets
are always within the unfinalized chain, the EL must now process them.
This makes `should_override_forkchoice_update` unnecessary. We can just
rely on `get_proposer_head` to reorg without doing any prediction before
hand.

This PR removes `should_override_forkchoice_update` itself and anything
references it.

Co-authored-by: Jihoon Song <jihoonsong@users.noreply.github.com>
jihoonsong added a commit to jihoonsong/execution-apis that referenced this pull request Jun 9, 2026
benbencik pushed a commit to benbencik/ethrex that referenced this pull request Jun 13, 2026
…ambdaclass#6676)

## Summary

execution-apis PR
[lambdaclass#786](ethereum/execution-apis#786) revised
paris.md: when `forkchoiceState.headBlockHash` references a VALID
ancestor of the latest known finalized block, the response MUST be
`{payloadStatus: VALID, payloadId: null}` and the client MUST NOT begin
a payload build process.

ethrex already had the correct
`InvalidForkChoice::NewHeadAlreadyCanonical` detection path in
`crates/blockchain/fork_choice.rs` (gate: `head.number <=
stored_finalized && head_is_canonical`; the `<=` matches geth's
`block.NumberU64() <= finalized.Number.Uint64()` in
`eth/catalyst/api.go`, see geth PR
[#34767](ethereum/go-ethereum#34767)).

The bug was in `crates/networking/rpc/engine/fork_choice.rs`: the
`NewHeadAlreadyCanonical` arm returned `Some(head_header)`, which caused
`ForkChoiceUpdatedV3::handle` / `ForkChoiceUpdatedV4::handle` to call
`build_payload` and set a non-null `payloadId` when `payloadAttributes`
was present.

Fix: return `None` for the head header so the V3/V4 dispatch's `if let
(Some(_), Some(_))` guard short-circuits `build_payload`. The `VALID +
latestValidHash = head` response is preserved, matching geth's
`valid(nil)` return.

## Test plan

- `cargo check -p ethrex-rpc` passes
- `cargo clippy -p ethrex-rpc` passes
- `cargo test -p ethrex-rpc` passes
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.

3 participants