Skip to content

Add tests for skipping reorg only if ancestor of finalised block#11439

Merged
Marchhill merged 54 commits into
masterfrom
feature/reorg-before-finalized
May 21, 2026
Merged

Add tests for skipping reorg only if ancestor of finalised block#11439
Marchhill merged 54 commits into
masterfrom
feature/reorg-before-finalized

Conversation

@Marchhill

@Marchhill Marchhill commented Apr 30, 2026

Copy link
Copy Markdown
Contributor

Implements ethereum/execution-apis#786

Changes

  • Only skip reorg if new head is ancestor of last finalized block

Types of changes

What types of changes does your code introduce?

  • Bugfix (a non-breaking change that fixes an issue)
  • New feature (a non-breaking change that adds functionality)
  • Breaking change (a change that causes existing functionality not to work as expected)
  • Optimization
  • Refactoring
  • Documentation update
  • Build-related changes
  • Other: test

Testing

Requires testing

  • Yes
  • No

If yes, did you write tests?

  • Yes
  • No

Documentation

Requires documentation update

  • Yes
  • No

Requires explanation in Release Notes

  • Yes
  • No

svlachakis and others added 30 commits April 9, 2026 17:53
# Conflicts:
#	src/Nethermind/Nethermind.Merge.Plugin/BlockTreeExtensions.cs
#	src/Nethermind/Nethermind.Merge.Plugin/Handlers/ForkchoiceUpdatedHandler.cs
#	src/Nethermind/Nethermind.Taiko/Rpc/TaikoForkchoiceUpdatedHandler.cs
@Marchhill Marchhill changed the title Feature/reorg before finalized Skip reorg only if ancestor of finalised block Apr 30, 2026
@claude

claude Bot commented Apr 30, 2026

Copy link
Copy Markdown
Contributor

Claude finished @Marchhill's task in 3m 58s —— View job


Re-reviewing with the current branch state (commits 4b01e2c add tests and 344026e refactor tests applied) and incorporating the author's responses.

  • Set up todo list
  • Read coding rules
  • Read diff and changed files
  • Assess prior findings vs. author's responses
  • Post final review summary

Re-review: Skip reorg only if ancestor of finalised block

Prior HIGH: Tests deleted, not replaced — RESOLVED

The new commits add two parameterised test methods:

  • forkchoiceUpdatedV1_processed_skips_reorg_only_when_head_is_ancestor_of_finalized (offset = -1 / 0 / +1)
  • forkchoiceUpdatedV1_unprocessed_skips_reorg_only_when_head_is_ancestor_of_finalized (offset = -1 / 0 / +1)

Together they cover both code paths (processed and unprocessed blocks), and the three offsets pin the < boundary precisely: only offset < 0 takes the skip path. This is better coverage than the removed single test. The remaining deleted tests (forkchoiceUpdated_rejects_repeated_hash_when_head_on_sibling_branch, forkchoiceUpdated_safe_block_that_is_real_ancestor_of_new_head_is_accepted, etc.) are replaced by the new consistency/ordering tests also added in these commits.

Prior MEDIUM: blocks is not null guard removed — Acknowledged by author

Author: "we didn't have this before and we don't need it." The early-return at line 242 runs after EnsureNewHead (which is read-only — it calls FindBlock/FindParent but does not commit state). The spec's "MAY skip" language covers the entire FCU response including finalization marker updates, so skipping _manualBlockFinalizationManager.MarkFinalized and _blockTree.ForkChoiceUpdated is spec-compliant. Accepted.

Prior MEDIUM: <=< boundary and DB lookup — Acknowledged by author

Author: "The < is explicit in the method name and tests. We don't care about this zero finalized hash edge case." The method name IsOnMainChainBehindFinalized unambiguously signals "strictly behind," and the offset = 0 test case confirms the exact-finalized-number block is NOT skipped. Accepted. The wasted FindHeader(Keccak.Zero) call when no finality is established is a micro-cost, not a correctness issue.

Prior MEDIUM: Spec comments and -38006 TooDeepReorg removed — Acknowledged by author

Author: "comment not needed was too verbose, too deep error outside of PR scope." The -38006 code is absent from the codebase outside this PR's scope. Accepted.


Remaining findings

Low — No in-code spec reference on IsOnMainChainBehindFinalized

The coding-style rule states: "Non-obvious consensus rules or algorithms must reference the EIP number or Yellow Paper section." The virtual IsOnMainChainBehindFinalized implements a specific point from execution-apis/pull/786. The PR description links to the spec, but the overridable method has no in-code reference. Since Taiko overrides it (and its override correctly carries a rationale comment), the base class should have at least a brief comment. Non-blocking.

// https://github.com/ethereum/execution-apis/pull/786 — EL MAY skip FCU
// if headBlockHash is a valid ancestor of the latest known finalized block.
protected virtual bool IsOnMainChainBehindFinalized(...)

Verdict

Mergeable. The only remaining blocking issue (test coverage) has been fully resolved with well-structured parameterised tests. All three medium findings have been explicitly acknowledged by the author with defensible rationale. The single remaining issue (missing in-code spec reference) is Low and does not block merge.

@github-actions github-actions Bot added test and removed taiko related to the taiko alethia rollup labels May 20, 2026
@Marchhill

Copy link
Copy Markdown
Contributor Author

@claude review

@claude

claude Bot commented May 20, 2026

Copy link
Copy Markdown
Contributor

Claude finished @Marchhill's task in 4m 29s —— View job


PR Review: Skip reorg only if ancestor of finalised block

  • Set up todo list
  • Read coding rules
  • Read diff and changed files
  • Analyze correctness, edge cases, regressions
  • Post inline comments for issues
  • Post final review summary

Summary

This PR, in its current state against master, contributes 81 lines of tests to EngineModuleTests.V1.cs. The corresponding implementation (IsOnMainChainBehindFinalized in BlockTreeExtensions.cs and ForkchoiceUpdatedHandler.cs) is already reflected in the branch and its diff against master is zero — those changes landed via earlier merged commits.

This is a re-review. A previous round found a High blocker (missing tests), which the author resolved with two parameterised test methods. A subsequent re-review declared it Mergeable. This review audits the current tip (b5613f8 fix test) and confirms there are no new issues.


Findings

No new Critical / High / Medium findings

All previously-raised blockers are resolved. Below are the observations from examining the current code:

Tests — boundary coverage is correct

forkchoiceUpdatedV1_processed_skips_reorg_only_when_head_is_ancestor_of_finalized and forkchoiceUpdatedV1_unprocessed_skips_reorg_only_when_head_is_ancestor_of_finalized both use offset ∈ {-1, 0, +1} against a chain where FinalizedHash = blocks[2]. The three offsets map exactly to:

offset head number relative to finalized expected path
-1 strictly below (<) skip → Valid
0 equal to finalized no skip
+1 above finalized no skip

This pins the < boundary introduced in IsOnMainChainBehindFinalized precisely. The processed and unprocessed branches in the handler are exercised separately.

BuildChainWithLoweredFinalized helper — correct setup

The helper builds 5 blocks (indices 0-4), advances head to blocks[4], then calls an FCU to set FinalizedHash = blocks[2]. It does not reset the head. Subsequent FCU calls in the tests pass Keccak.Zero for finalizedBlockHash (processed test) to bypass the RejectIfInconsistent guard that would reject a request where finalized.Number > head.Number — this is necessary and correct since the skip check consults blockTree.FinalizedHash (the stored value) rather than the request-level hash. The comment in the test explains this clearly.

FlipCanonicalMarkerTo usage in the unprocessed test — correct

Calling UpdateMainChain(new[] { targetBlock }, wereProcessed: false) correctly puts the block into the !blockInfo.WasProcessed branch without removing it from the main chain, so IsMainChain still returns true when the skip condition evaluates blockTree.IsMainChain(header). This is the right way to force the unprocessed code path for a block that is genuinely on the canonical chain.

Unprocessed test for offset = -1 uses non-zero finalizedBlockHash

The FCU is new(head: blocks[1], finalized: blocks[2], safe: blocks[2]) — i.e. finalized.Number > head.Number. In the unprocessed path, IsOnMainChainBehindFinalized fires at line 163 before RejectIfInconsistent (line 240), so the early Valid return is reached without ever hitting the consistency check. This is spec-compliant and intentional.

Low — spec reference still absent on IsOnMainChainBehindFinalized (unchanged from prior review)

Neither BlockTreeExtensions.IsOnMainChainBehindFinalized nor the virtual method override in ForkchoiceUpdatedHandler carries a link to execution-apis#786. Per repo coding style, non-obvious consensus rules should reference their spec. The Taiko override has a rationale comment; the base class method does not. Non-blocking, but worth a one-line addition.

// https://github.com/ethereum/execution-apis/pull/786 — EL MAY skip FCU
// if headBlockHash is a valid ancestor of the latest known finalized block.
protected virtual bool IsOnMainChainBehindFinalized(...)

Verdict

Mergeable. No Critical, High, or Medium findings. The test coverage is solid, the boundary semantics are correctly pinned, and both handler branches are exercised. The single remaining finding is Low and non-blocking.

@Marchhill Marchhill requested a review from LukaszRozmej May 20, 2026 13:14
@github-actions github-actions Bot removed the test label May 20, 2026
@Marchhill Marchhill changed the title Skip reorg only if ancestor of finalised block Add tests for skipping reorg only if ancestor of finalised block May 20, 2026
@github-actions github-actions Bot added test and removed new feature labels May 20, 2026
@Marchhill Marchhill merged commit 1b311e7 into master May 21, 2026
545 checks passed
@Marchhill Marchhill deleted the feature/reorg-before-finalized branch May 21, 2026 11:11
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

6 participants