Conversation
Pin MaximumUncleCount=0 for post-merge releases so the consensus rule is enforced at the fork gate (BlockValidator.ValidateUncles) rather than only through MergeHeaderValidator's UnclesHash==empty check, which depends on PoSSwitcher resolving IsPostMerge correctly. - Paris.Apply now sets spec.MaximumUncleCount=0; all post-Paris NamedReleaseSpec forks inherit 0 (Shanghai through Amsterdam). - ChainSpecBasedSpecProvider gates MaximumUncleCount via a new IsPostMergeRelease helper covering TTD=0 (PoS-from-genesis), TerminalPoWBlockNumber, and Shanghai-or-later by timestamp (the signal that lets TTD-driven chains like mainnet register post-merge at spec-construction time). - BlockchainTestBase drops the IsPostMerge=(Difficulty==0) shortcut so CI exercises the real PoSSwitcher path and matches hive's production semantics; PoSSwitcher.GetBlockConsensusInfo already has the same difficulty==0 fallback when TotalDifficulty is null. Verified: UncleFromSideChain_Cancun/Prague still reject via the spec gate; Ethereum.Blockchain.Block.Test (1146), Legacy (2539), and Nethermind.Specs.Test (208) all pass.
|
Claude finished @benaadams's task in 4m 54s —— View job PR Review
SummaryThis PR is a well-targeted bug fix that enforces the EIP-3675 uncle ban at the spec level, reducing reliance on Finding: Medium —
|
There was a problem hiding this comment.
Pull request overview
Updates release specs to enforce EIP-3675’s “no uncles post-merge” rule via MaximumUncleCount = 0, aiming to make uncle rejection occur in BlockValidator.ValidateUncles (spec-gated) rather than relying primarily on merge-header checks and IsPostMerge resolution.
Changes:
- Set
Paris.Applyto pinMaximumUncleCount = 0so post-Paris forks inherit the uncle ban. - Gate
ReleaseSpec.MaximumUncleCountinChainSpecBasedSpecProvidervia a newIsPostMergeRelease(...)helper. - Adjust blockchain tests to stop manually setting
Header.IsPostMergeand instead exercise the real PoS switcher path.
Reviewed changes
Copilot reviewed 3 out of 3 changed files in this pull request and generated 2 comments.
| File | Description |
|---|---|
| src/Nethermind/Nethermind.Specs/Forks/15_Paris.cs | Sets Paris fork to enforce MaximumUncleCount=0 (uncle ban). |
| src/Nethermind/Nethermind.Specs/ChainSpecStyle/ChainSpecBasedSpecProvider.cs | Adds post-merge detection to choose 0 vs 2 uncles in constructed ReleaseSpecs. |
| src/Nethermind/Ethereum.Test.Base/BlockchainTestBase.cs | Removes test-only IsPostMerge shortcut to align with production pipeline behavior. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
- BlockchainTestBase: the comment explains why the shortcut is absent (preserving the CI/hive divergence detection that motivates this path) rather than naming what else sets the flag. - ChainSpecBasedSpecProvider: IsPostMergeRelease now explicitly acknowledges the mainnet Paris-Shanghai window where the static gate cannot fire (TTD-driven merge with no TerminalPoWBlockNumber pin) and names MergeHeaderValidator / PoSSwitcher as the runtime safety net.
MainnetSpecProvider maps the terminal PoW block (ParisBlockNumber = 15_537_393) to Paris.Instance via an off-by-one in its block-range resolution (line 53: `BlockNumber < ParisBlockNumber` - so 15537393 falls through to Paris). Gating the uncle ban on Paris would spec-reject a consensus-valid pre-merge block that contained uncles. Move the MaximumUncleCount=0 assignment to Shanghai.Apply instead. Shanghai is the first NamedReleaseSpec boundary that is unambiguously post-merge on every chain, so inheritance cascades through Cancun, Prague, Osaka, BPO* and Amsterdam without disturbing the terminal PoW block. Paris-era post-merge blocks remain protected by MergeHeaderValidator (UnclesHash == empty via PoSSwitcher), unchanged from prior behaviour. The hive UncleFromSideChain_Cancun/Prague failures are still caught since Cancun and Prague inherit from Shanghai. Fixing MainnetSpecProvider's Paris off-by-one is out of scope.
|
@claude re-review |
|
Claude finished @benaadams's task in 5m 15s —— View job PR Re-Review
SummaryThe PR has been significantly revised since the first review. The Copilot concern about the terminal PoW block was valid and has been correctly addressed: the uncle ban was moved from Resolved since last review✅ Previous Medium — ChainSpecBasedSpecProvider vs MainnetSpecProvider inconsistency Resolved. Previously, ✅ Copilot concern 1 — Paris uncle ban would reject a valid PoW block Resolved correctly. ✅ Previous Low — Comment style in BlockchainTestBase.cs Addressed. The comment now explains the constraint: "Setting IsPostMerge here would bypass PoSSwitcher and hide divergences between CI and hive's production pipeline." Remaining findingsLow — AuRa
|
- IsPostMergeRelease: correct the TerminalPoWBlockNumber wording - the branch only fires when the chainspec has post-merge block-number transitions that push releaseStartBlock past the terminal block; TerminalPoWBlockNumber itself creates no spec boundary (AddTransitions explicitly excludes it). - Paris.Apply: drop the "off-by-one" framing - the `< ParisBlockNumber` boundary is intentional, making Paris a mixed-era spec covering both the terminal PoW block and the post-merge window up to Shanghai. That mixed scope is exactly why the uncle ban cannot live here.
|
@claude re-review |
|
Claude finished @benaadams's task in 1m 29s —— View job PR Re-Review
SummaryThe latest commit ( Resolved since last review✅ Previous Low — "off-by-one" terminology in Fixed. The comment no longer uses "off-by-one". It now correctly explains that ✅ Previous Low — Fixed. The comment (lines 346–353) now explicitly states: "TerminalPoWBlockNumber fires only when releaseStartBlock advances past it, which requires the chainspec to carry post-merge block-number transitions (TerminalPoWBlockNumber is excluded from AddTransitions above, so it creates no spec boundary of its own). Paris-era blocks on TTD-driven chains with no such later transitions are not detected here — runtime MergeHeaderValidator (UnclesHash == empty via PoSSwitcher) is the safety net." This is accurate and complete. ✅ Previous Low — Already resolved in prior iteration. Comment correctly explains the constraint: "Setting IsPostMerge here would bypass PoSSwitcher and hide divergences between CI and hive's production pipeline." Remaining findingsLow — AuRa
|
Matches the existing codebase pattern (HeaderValidator.cs:328 uses `_specProvider.TerminalTotalDifficulty?.IsZero == true`) and reads as "is this value zero" rather than constructing a zero to compare against. Same semantics for the nullable type.
* Enforce EIP-3675 uncle ban at the spec level Pin MaximumUncleCount=0 for post-merge releases so the consensus rule is enforced at the fork gate (BlockValidator.ValidateUncles) rather than only through MergeHeaderValidator's UnclesHash==empty check, which depends on PoSSwitcher resolving IsPostMerge correctly. - Paris.Apply now sets spec.MaximumUncleCount=0; all post-Paris NamedReleaseSpec forks inherit 0 (Shanghai through Amsterdam). - ChainSpecBasedSpecProvider gates MaximumUncleCount via a new IsPostMergeRelease helper covering TTD=0 (PoS-from-genesis), TerminalPoWBlockNumber, and Shanghai-or-later by timestamp (the signal that lets TTD-driven chains like mainnet register post-merge at spec-construction time). - BlockchainTestBase drops the IsPostMerge=(Difficulty==0) shortcut so CI exercises the real PoSSwitcher path and matches hive's production semantics; PoSSwitcher.GetBlockConsensusInfo already has the same difficulty==0 fallback when TotalDifficulty is null. Verified: UncleFromSideChain_Cancun/Prague still reject via the spec gate; Ethereum.Blockchain.Block.Test (1146), Legacy (2539), and Nethermind.Specs.Test (208) all pass. * Reframe uncle-ban comments: why, not what - BlockchainTestBase: the comment explains why the shortcut is absent (preserving the CI/hive divergence detection that motivates this path) rather than naming what else sets the flag. - ChainSpecBasedSpecProvider: IsPostMergeRelease now explicitly acknowledges the mainnet Paris-Shanghai window where the static gate cannot fire (TTD-driven merge with no TerminalPoWBlockNumber pin) and names MergeHeaderValidator / PoSSwitcher as the runtime safety net. * Pin MaximumUncleCount=0 on Shanghai, not Paris MainnetSpecProvider maps the terminal PoW block (ParisBlockNumber = 15_537_393) to Paris.Instance via an off-by-one in its block-range resolution (line 53: `BlockNumber < ParisBlockNumber` - so 15537393 falls through to Paris). Gating the uncle ban on Paris would spec-reject a consensus-valid pre-merge block that contained uncles. Move the MaximumUncleCount=0 assignment to Shanghai.Apply instead. Shanghai is the first NamedReleaseSpec boundary that is unambiguously post-merge on every chain, so inheritance cascades through Cancun, Prague, Osaka, BPO* and Amsterdam without disturbing the terminal PoW block. Paris-era post-merge blocks remain protected by MergeHeaderValidator (UnclesHash == empty via PoSSwitcher), unchanged from prior behaviour. The hive UncleFromSideChain_Cancun/Prague failures are still caught since Cancun and Prague inherit from Shanghai. Fixing MainnetSpecProvider's Paris off-by-one is out of scope. * Tighten uncle-ban comments - IsPostMergeRelease: correct the TerminalPoWBlockNumber wording - the branch only fires when the chainspec has post-merge block-number transitions that push releaseStartBlock past the terminal block; TerminalPoWBlockNumber itself creates no spec boundary (AddTransitions explicitly excludes it). - Paris.Apply: drop the "off-by-one" framing - the `< ParisBlockNumber` boundary is intentional, making Paris a mixed-era spec covering both the terminal PoW block and the post-merge window up to Shanghai. That mixed scope is exactly why the uncle ban cannot live here. * Use TerminalTotalDifficulty?.IsZero idiom Matches the existing codebase pattern (HeaderValidator.cs:328 uses `_specProvider.TerminalTotalDifficulty?.IsZero == true`) and reads as "is this value zero" rather than constructing a zero to compare against. Same semantics for the nullable type.
Changes
Types of changes
What types of changes does your code introduce?
Testing
Requires testing
If yes, did you write tests?