Skip to content

Fork override trace block#11493

Open
DarkLord017 wants to merge 33 commits into
masterfrom
fork-override-trace-block
Open

Fork override trace block#11493
DarkLord017 wants to merge 33 commits into
masterfrom
fork-override-trace-block

Conversation

@DarkLord017
Copy link
Copy Markdown
Contributor

@DarkLord017 DarkLord017 commented May 5, 2026

Fixes Closes Resolves #9056

Changes

  • Added new feature for trace_block to do spec(fork) overrides
  • TraceRpcModule.trace_block resolves the fork name to a spec, adjusts BaseFeePerGas/ExcessBlobGas in the block header via AdjustHeaderForSpec, then re-executes the block under that spec.
  • MainnetSpecProvider + ChainSpecBasedSpecProvider both implement IForkAwareSpecProvider so mainnet and all chain-spec networks expose their named forks.
  • Tests added for the RPC fork override behavior in TraceRpcModuleTests.cs and for IForkAwareSpecProvider in ChainSpecBasedSpecProviderTests.cs.

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: Description

Testing

Requires testing

  • Yes
  • No

If yes, did you write tests?

  • Yes
  • No

Notes on testing

Documentation

Requires documentation update

  • Yes
  • No

Requires explanation in Release Notes

  • Yes
  • No

@claude
Copy link
Copy Markdown
Contributor

claude Bot commented May 5, 2026

Claude finished @DarkLord017's task in 4m 25s —— View job


PR Review: Fork override trace block

  • Gather context and read changed files
  • Read relevant coding rules
  • Analyze changes for correctness, security, performance
  • Post inline review comments
  • Summarize findings

Summary

The PR adds a fork parameter to trace_block that lets callers override the active EVM spec (e.g., {"forkName":"Berlin"}) without altering any chain state. The plumbing is clean: a new OverridableSpecProvider wraps the real spec provider inside the existing OverridableEnv scope, is registered as ISpecProvider in the child DI lifetime, and is reset when the scope disposes. The ModExp gas tests give good confidence that the override actually reaches precompile selection. Overall the approach is solid.


Findings

# Severity File Summary
1 Medium TraceRpcModule.cs:284 null ForkName from JSON throws ArgumentNullException instead of clean RPC error
2 Medium MainnetSpecProvider.cs BPO1–BPO5 forks missing from _forks map, unavailable as fork overrides
3 Low ForkActivationParameter.cs No XML docs; mutable class where record fits better
4 Low OverridableSpecProvider.cs SetOverride/ResetOverride exposed as public; should be internal
5 Low MainnetSpecProvider.cs:127 AvailableForks iterates in non-deterministic hash order; error messages will be jumbled
6 Design IForkAwareSpecProvider.cs Only MainnetSpecProvider implements it; Sepolia/Gnosis/AuRa nodes silently fail
7 Nit OverridableEnvFactory.cs:44 Leading Reset() in BuildAndOverride before SetOverride needs a brief comment for future readers

Medium — null ForkName crashes instead of returning RPC error (Issue #1)

JSON clients can send {"forkName": null}. System.Text.Json sets the string property to null despite the = string.Empty default. FrozenDictionary.TryGetValue(null, …) then throws ArgumentNullException (via StringComparer.OrdinalIgnoreCase.GetHashCode(null)) rather than producing a well-formed -32602 InvalidParams error. A one-line guard fixes it:

if (string.IsNullOrWhiteSpace(fork.ForkName))
    return ResultWrapper<>.Fail("Fork name must not be empty", ErrorCodes.InvalidParams);

Medium — BPO1–BPO5 absent from fork map (Issue #2)

MainnetSpecProvider.GetSpec routes to BPO1BPO5 via the timestamp switch, but none appear in _forks. Any call with {"forkName":"BPO2"} returns "Unknown fork". Either add them or document the intentional omission.

Comment thread src/Nethermind/Nethermind.JsonRpc/Modules/Trace/TraceRpcModule.cs Outdated
Comment thread src/Nethermind/Nethermind.Specs/MainnetSpecProvider.cs Outdated
Comment thread src/Nethermind/Nethermind.JsonRpc/Data/ForkActivationParameter.cs Outdated
Comment thread src/Nethermind/Nethermind.Specs/MainnetSpecProvider.cs Outdated
Comment thread src/Nethermind/Nethermind.Core/Specs/IForkAwareSpecProvider.cs
@DarkLord017
Copy link
Copy Markdown
Contributor Author

@claude re-review

@claude
Copy link
Copy Markdown
Contributor

claude Bot commented May 5, 2026

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


PR Re-Review: Fork override trace block

  • Gather context and read changed files
  • Check if previous findings were addressed
  • Analyze changes for correctness, security, performance
  • Post inline review comments
  • Summarize findings

Previous Findings — Status

All six findings from the last review have been addressed:

# Severity Was Now
1 Medium null ForkName throws ArgumentNullException ✅ Fixed — IsNullOrEmpty guard at line 284
2 Medium BPO1–BPO5 missing from _forks ✅ Fixed — Added to MainnetSpecProvider._forks
3 Low ForkActivationParameter was mutable class, no docs ✅ Fixed — record with param doc
4 Low SetOverride/ResetOverride were public ✅ Fixed — Now internal
5 Low AvailableForks iterated in hash order ✅ Fixed — Now sorted via OrderBy
6 Design Only MainnetSpecProvider implemented IForkAwareSpecProvider ✅ Fixed — ChainSpecBasedSpecProvider now also implements it

New Findings

Low — ChainSpecBasedSpecProvider._forks should be FrozenDictionary

_forks is populated once during construction in BuildTransitions() and never mutated. MainnetSpecProvider already uses FrozenDictionary<string, IReleaseSpec> for this, making reads faster and preventing accidental mutation after construction. ChainSpecBasedSpecProvider uses a plain Dictionary<string, IReleaseSpec> which is inconsistent. Consider freezing it after CreateNamedForks returns:

_forks = CreateNamedForks(_chainSpec).ToFrozenDictionary(StringComparer.OrdinalIgnoreCase);

(Also change the field type to FrozenDictionary<string, IReleaseSpec>.)


Overall Assessment

The implementation is solid. The core feature — passing a ForkActivationParameter to trace_block, resolving the spec from IForkAwareSpecProvider, and injecting it via OverridableSpecProvider into the child DI lifetime — is correct and clean. Key quality observations:

  • Correctness: The specOverride flows correctly through OverridableEnvFactory.BuildAndOverrideOverridableSpecProvider.SetOverride, and is reset on Scope.Dispose(). The override does not leak between calls, as confirmed by trace_block_fork_override_does_not_persist_between_calls.
  • State override interaction: State overrides (stateOverride) correctly use the original specProvider.GetSpec(header) while EVM execution uses the overridden spec — intentional and correct.
  • Thread safety: OverridableSpecProvider is intentionally not thread-safe; the class comment correctly documents the single-request-at-a-time constraint.
  • Error handling: The three error paths (unsupported provider, empty/null fork name, unknown fork name) all return clean -32602 InvalidParams errors.
  • Test coverage: The ModExp gas tests (Istanbul vs Berlin) give strong evidence that the override actually reaches precompile selection; the no-leak test verifies scope isolation.

The remaining FrozenDictionary issue is low severity and doesn't block merge.

Comment thread src/Nethermind/Nethermind.Specs/ChainSpecStyle/ChainSpecBasedSpecProvider.cs Outdated
@DarkLord017
Copy link
Copy Markdown
Contributor Author

@claude re-review also why are unnecessary tests failing

@claude
Copy link
Copy Markdown
Contributor

claude Bot commented May 5, 2026

PR Re-Review: Fork override trace block

  • Gather CI status and failing tests
  • Read changed files
  • Check previous findings were addressed
  • Identify CI failure root cause
  • Analyze and investigate test failure
  • Summarize findings

CI failure identified: Nethermind.JsonRpc.Test fails in "checked" and "no-intrinsics" modes with:

eth_simulateV1_sender_is_not_eoa_returns_spec_error_code
Expected: -38024 (SenderIsNotEoa)
But was: 0 (success)

This test passes on master — investigating whether the PR's OverridableSpecProvider registration in the DI scope causes a regression in eth_simulate.

View job run

@DarkLord017
Copy link
Copy Markdown
Contributor Author

@claude re-review

@claude
Copy link
Copy Markdown
Contributor

claude Bot commented May 5, 2026

Claude finished @DarkLord017's task in 10m 15s —— View job


PR Re-Review: Fork override trace block

  • Gather CI status
  • Read changed files
  • Check previous findings were addressed
  • Investigate CI test failures
  • Analyze changes for correctness, security, performance
  • Post inline review comments
  • Summarize findings

Previous Findings — Status

All seven findings from the prior reviews have been addressed:

# Severity Was Now
1 Medium null ForkName throws ArgumentNullException IsNullOrEmpty guard at line 284
2 Medium BPO1–BPO5 missing from _forks ✅ Added to MainnetSpecProvider._forks
3 Low ForkActivationParameter was mutable class, no docs ✅ Positional record with <param> doc
4 Low SetOverride/ResetOverride were public ✅ Now internal
5 Low AvailableForks iterated in hash order OrderBy(static fork => fork) in both providers
6 Design Only MainnetSpecProvider implemented IForkAwareSpecProvider ChainSpecBasedSpecProvider now also implements it
7 Low ChainSpecBasedSpecProvider._forks was plain Dictionary ✅ Changed to FrozenDictionary (line 129)

CI Failure Investigation

Two workflows show conclusion: failure:

  • Nethermind extra test variants (run 25373206654) — all 30 listed jobs pass
  • Nethermind/Ethereum tests (run 25373206643) — all 30 listed jobs pass

Every listed job in both failing runs is an Ethereum Foundation test (Ethereum.Legacy.Blockchain.Test, Ethereum.Blockchain.Pyspec.Test, Ethereum.KeyStore.Test, etc.). No Nethermind unit test jobs appear in these runs. The overall workflow shows "failure" while every individual job shows "success", which strongly suggests a workflow-level infrastructure issue unrelated to this PR's code changes.

The Nethermind tests (Flat DB) run (25373206664) passed, which is where Nethermind.JsonRpc.Test and eth_simulateV1_sender_is_not_eoa_returns_spec_error_code would live. These tests pass.

Root cause of the eth_simulateV1 failure from the prior review: Resolved. The DI wiring is correctly isolated — TraceRpcModule.specProvider is resolved from rpcLifetimeScope which does not include AddModule(env), so trace_simulateV1 / eth_simulateV1 uses the original ISpecProvider, not the OverridableSpecProvider. There is no spec leak between trace_block fork-override calls and eth_simulateV1.


DI Wiring — Verified Correct

The key linkage I traced through TraceModuleFactory.Create():

  • rpcProcessingScope, validationProcessingScope, and tracerLifetimeScope all call .AddModule(env) → triggers OverridableEnv.Load(ContainerBuilder) → registers OverridableSpecProvider as ISpecProvider in those scopes
  • rpcLifetimeScope (where ITraceRpcModule is resolved) does not call .AddModule(env), so TraceRpcModule.specProvider is the original ISpecProvider
  • When ExecuteBlock calls tracerEnv.BuildAndOverride(baseBlock, specOverride: forkSpec), the ITracer in tracerLifetimeScope — which holds a reference to OverridableSpecProvider — sees the overridden spec
  • Reset() is called on Scope.Dispose(), clearing _override before the next call

This is confirmed by the ModExp gas tests passing (override reaches the precompile) and the no-leak test passing (override does not persist between calls).


New Findings

Nit — "Genesis" key always present in ChainSpecBasedSpecProvider (line 143)

CreateNamedForks unconditionally inserts a "Genesis" entry mapped to the block-0 spec (which could be Frontier, Homestead, etc., depending on the network). This key is always present regardless of whether the network uses "Genesis" as a named fork. A user sending {"forkName":"Genesis"} on Gnosis or Sepolia would silently get the block-0 spec of that network — potentially confusing. Consider either adding a clarifying comment or omitting the entry (callers can already get the genesis spec via block number 0).

Overall Assessment

The implementation is correct and well-isolated. The complete data flow — ForkActivationParameterTryGetForkSpecExecuteBlockBuildAndOverrideOverridableSpecProvider.SetOverrideReset on disposal — is sound. Error handling covers all three failure paths (unsupported provider, empty/null fork name, unknown fork name) with clean InvalidParams codes. The TraceStoreRpcModule correctly bypasses the store when a fork override is present.

The two failing CI workflows contain only EF Foundation tests (all individual jobs succeed); the failure is a workflow-level artifact. Nethermind unit tests pass cleanly.


Comment thread src/Nethermind/Nethermind.Specs/ChainSpecStyle/ChainSpecBasedSpecProvider.cs Outdated
@DarkLord017
Copy link
Copy Markdown
Contributor Author

@claude re-review

@claude
Copy link
Copy Markdown
Contributor

claude Bot commented May 5, 2026

Claude finished @DarkLord017's task in 9m 4s —— View job


PR Re-Review: Fork override trace block

  • Gather CI status
  • Read changed files
  • Check previous findings were addressed
  • Analyze changes for correctness, security, performance
  • Post inline review comments
  • Summarize findings

CI Status

Check Result
Build solutions ✅ Success
Code formatting ✅ Success
Code Lint 🔄 In progress
Dependency review ✅ Success
Nethermind tests (Flat DB) 🔄 In progress
Nethermind extra test variants 🔄 In progress

Previous Findings — Status

All seven findings from prior reviews have been addressed:

# Severity Was Now
1 Medium null ForkName throws ArgumentNullException IsNullOrEmpty guard at line 284
2 Medium BPO1–BPO5 missing from _forks ✅ Added to MainnetSpecProvider._forks
3 Low ForkActivationParameter was mutable class, no docs ✅ Positional record with <param> doc
4 Low SetOverride/ResetOverride were public ✅ Now internal
5 Low AvailableForks iterated in hash order OrderBy(static fork => fork) in both providers
6 Design Only MainnetSpecProvider implemented IForkAwareSpecProvider ChainSpecBasedSpecProvider now also implements it
7 Low ChainSpecBasedSpecProvider._forks was plain Dictionary ✅ Changed to FrozenDictionary

New Findings

Low — TestBlockchain.WrapSpecProvider contains unreachable branch

The new if (specProvider is Nethermind.State.OverridableEnv.OverridableSpecProvider envSpecProvider) branch inside WrapSpecProvider can never be true. WrapSpecProvider is invoked as a DI decorator on the parent (root) lifetime scope's ISpecProvider. State.OverridableEnv.OverridableSpecProvider is only registered in the child scopes (rpcProcessingScope, tracerLifetimeScope) inside OverridableEnv.Load. It never appears in the parent scope, so this branch is dead code. It can be safely removed.

Additionally, if the intent was to protect ForkAwareTestSpecProvider from losing its IForkAwareSpecProvider interface after being wrapped by Nethermind.Specs.Test.OverridableSpecProvider, the correct fix would be to set AllowTestChainOverride = false in ForkAwareTestSpecProvider (so the decorator returns the original provider unchanged), rather than the unreachable check.

Fix this →

Nit (from prior review, still present) — "Genesis" key always injected in ChainSpecBasedSpecProvider

CreateNamedForks unconditionally inserts "Genesis" → block-0 spec regardless of network. Callers sending {"forkName":"Genesis"} on Gnosis or Sepolia silently get the block-0 spec of that network. A comment or omission would reduce confusion. This was previously acknowledged as low-severity and doesn't block merge.


Overall Assessment

The implementation is correct and clean. The complete data flow — ForkActivationParameterTryGetForkSpecExecuteBlockBuildAndOverrideOverridableSpecProvider.SetOverrideReset on disposal — is sound. All three error paths (unsupported provider, empty/null fork name, unknown fork name) return clean -32602 InvalidParams codes. TraceStoreRpcModule correctly bypasses the store when a fork override is present. The ModExp gas tests give strong evidence the override actually reaches precompile selection.

The only remaining issue is the dead OverridableSpecProvider guard in WrapSpecProvider. If ForkAwareTestSpecProvider in the tests relies on being the undecorated provider (which requires AllowTestChainOverride = false), that should be set explicitly rather than via an unreachable check.


@DarkLord017
Copy link
Copy Markdown
Contributor Author

@claude re-review

@DarkLord017 DarkLord017 requested a review from LukaszRozmej May 11, 2026 21:14
@claude
Copy link
Copy Markdown
Contributor

claude Bot commented May 11, 2026

Claude finished @DarkLord017's task in 2m 57s —— View job


PR Re-Review: Fork override trace block

  • Gather context
  • Check CI status
  • Read changed files (current state)
  • Check LukaszRozmej's CHANGES_REQUESTED from 2026-05-11 — verify which are addressed
  • Analyze implementation correctness
  • Post findings

CI Status

Check Result
Code formatting ✅ Success
Spell check ✅ Success
Dependency review ✅ Success
Build tools ✅ Success
PR labeler ✅ Success
Build solutions 🔄 In progress
Code Lint 🔄 In progress
Nethermind tests (Flat DB) 🔄 In progress
Nethermind extra test variants 🔄 In progress
Nethermind/Ethereum tests 🔄 In progress
Sync PR Gate (Hoodi) 🔄 In progress

LukaszRozmej's CHANGES_REQUESTED (2026-05-11) — Status

# Comment Status
1 TraceRpcModuleTests.cs — use nameof from Nethermind.Specs.Forks ✅ Done — nameof(Berlin), nameof(Istanbul), nameof(Cancun) used throughout
2 TraceRpcModuleTests.cs — excessive comments Still present — lines 1345–1346 block comment + line 1351 verbose because string (inline suggestion posted)
3 ChainSpecBasedSpecProviderTests.cs — use "London" as input, expect LondonGnosis ✅ Principle addressed — test now uses nameof(Osaka)typeof(OsakaGnosis) for Gnosis (base name → Gnosis variant, same pattern)
4 ChainSpecBasedSpecProvider.cs — use separate classes for Gnosis/Chiado/Sepolia/Hoodi ✅ Done — ForksForChain delegates to GnosisSpecProvider.Forks, ChiadoSpecProvider.Forks, SepoliaSpecProvider.Forks, HoodiSpecProvider.Forks

Implementation Correctness — Confirmed

  • DI flow: OverridableEnvFactory.BuildAndOverrideOverridableSpecProvider.SetOverride → EVM exec → Reset() on Scope.Dispose(). Override does not leak between requests.
  • ForksForChain: correctly maps Gnosis/Chiado/Sepolia/Hoodi chain IDs to their specific FrozenDictionary fork instances (e.g., GnosisSpecProvider.Forks maps "London"LondonGnosis.Instance, "Osaka"OsakaGnosis.Instance).
  • TryResolveForkSpec: handles all four cases cleanly — null (no override), unsupported provider, empty string, unknown fork name.
  • AdjustHeaderForSpec: correctly zeroes BaseFeePerGas for pre-1559 specs; nulls ExcessBlobGas for pre-4844 specs; no redundant parameters.
  • WrapSpecProvider: unwraps OverridableSpecProvider before the AllowTestChainOverride check per LukaszRozmej's suggestion.

Remaining Issue

Low — Excessive comments at lines 1345–1351 (TraceRpcModuleTests.cs)

LukaszRozmej's CHANGES_REQUESTED on this point is still open. The block comment before the trace_block call (lines 1345–1346) and the verbose because string on line 1351 describe what the code does rather than why. The test name trace_block_pre_1559_fork_override_on_london_block_zeroes_base_fee already communicates the intent. A one-line fix removes both (suggestion posted inline).


Overall

One Low item remains from LukaszRozmej's CHANGES_REQUESTED (excessive comments). All previous Critical/High/Medium/Low findings from prior review rounds are resolved. Once the comments are removed, the PR is ready for re-approval.

Comment thread src/Nethermind/Nethermind.Specs/ChainSpecStyle/ChainSpecBasedSpecProvider.cs Outdated
Comment thread src/Nethermind/Nethermind.Specs/GnosisSpecProvider.cs Outdated
Comment thread src/Nethermind/Nethermind.Specs/SepoliaSpecProvider.cs Outdated
@DarkLord017
Copy link
Copy Markdown
Contributor Author

@claude review

@claude
Copy link
Copy Markdown
Contributor

claude Bot commented May 12, 2026

Claude finished @DarkLord017's task in 7m 21s —— View job


PR Review: Fork override trace block

  • Gather context
  • Read relevant rule files
  • Read all changed files (current state)
  • Check LukaszRozmej's latest comments (2026-05-12)
  • Analyze implementation correctness
  • Post findings

LukaszRozmej's 2026-05-12 Comments — Status

# File Comment Status
1 ChainSpecBasedSpecProvider.cs Use ChiadoSpecProvider; add MordenSpecProvider ✅ Both in ForksForChain switch; Taiko/XDC deferred (plugin dependency, no chain IDs exposed here)
2 GnosisSpecProvider.cs "Keep nameof just use different classes?" x.Spec.Name produces canonical names via the Apply chain (OsakaGnosis.Instance.Name == "Osaka")
3 GnosisSpecProvider.cs "Could use OsakaGnosis.Instance.Name — fix Gnosis type" ⚠️ Not implemented; current behavior is correct, but each gnosis fork could explicitly set .Name for self-documentation
4 SepoliaSpecProvider.cs "GetSpec / Forks duplication — extract base class" ⚠️ DarkLord017 replied "okay" but not implemented; same loop is repeated across Sepolia, Hoodi, Chiado, Morden

Findings

# Severity File Summary
1 Low TraceRpcModuleTests.cs:1345–1351 Excessive comments still present — flagged in LukaszRozmej's 2026-05-11 CHANGES_REQUESTED
2 Low SepoliaSpecProvider.cs:45–56 GetSpec loop duplicated across 4 providers; acknowledged by author but not refactored
3 Nit GnosisSpecProvider.cs OsakaGnosis.Instance.Name returns "Osaka" via Apply chain — works, but not explicit; LukaszRozmej noted this could be made self-documenting

Low — Excessive comments (Finding #1)

LukaszRozmej's CHANGES_REQUESTED from 2026-05-11 included "excessive comments." Lines 1345–1346 describe what the code does (already clear from the test name trace_block_pre_1559_fork_override_on_london_block_zeroes_base_fee), and line 1351's because string embeds the internal method name AdjustHeaderForSpec. Inline suggestion posted.

Low — Duplicated GetSpec loop (Finding #2)

SepoliaSpecProvider, HoodiSpecProvider, ChiadoSpecProvider, and MordenSpecProvider each contain the same timestamp-based ForkSchedule iteration loop in GetSpec. LukaszRozmej suggested extracting a shared base class with ForkSchedule as a constructor parameter. DarkLord017 acknowledged ("okay") but hasn't done it. Inline note posted.


Overall Correctness Assessment

The core feature is correct and unchanged from the prior review:

  • DI flow: OverridableEnvFactory.BuildAndOverride(header, specOverride: forkSpec)OverridableSpecProvider.SetOverride → EVM execution → ResetOverride() on Scope.Dispose(). No override leaks between calls.
  • ForksForChain: Correctly routes Gnosis/Chiado/Sepolia/Hoodi/Morden to their respective static FrozenDictionary instances; all other chains fall through to MainnetSpecProvider.Forks.
  • TryResolveForkSpec: Handles null (no override), unsupported provider, empty string, and unknown fork name — all with clean -32602 InvalidParams responses.
  • AdjustHeaderForSpec: Correctly zeroes BaseFeePerGas for pre-EIP-1559 specs; nulls ExcessBlobGas for pre-EIP-4844 specs.
  • GnosisSpecProvider.Forks: Uses x.Spec.Name as key; since OsakaGnosis.Apply calls Osaka.Apply(spec) which sets spec.Name = "Osaka", gnosis forks correctly map to canonical mainnet names — so {"forkName":"Osaka"} on a Gnosis node returns OsakaGnosis.Instance.
  • TraceStoreRpcModule: Correctly bypasses the trace store for fork-override requests.

No Critical, High, or Medium findings. Two Low issues remain; neither blocks merge from a correctness or security standpoint.

View job

Comment thread src/Nethermind/Nethermind.Specs/SepoliaSpecProvider.cs Outdated
@DarkLord017
Copy link
Copy Markdown
Contributor Author

@claude review

@claude
Copy link
Copy Markdown
Contributor

claude Bot commented May 12, 2026

Claude finished @DarkLord017's task in 4m 12s —— View job


PR Review: Fork override trace block

  • Gather context
  • Check CI status
  • Read all changed files (current state)
  • Check open issues from previous reviews
  • Analyze implementation correctness
  • Post findings

CI Status

Check Result
Code formatting ✅ Success
Dependency review ✅ Success
PR labeler ✅ Success
Build solutions 🔄 In progress
Nethermind tests (Flat DB) 🔄 Pending
Nethermind extra test variants 🔄 Pending
Nethermind/Ethereum tests 🔄 Pending
Code Lint 🔄 In progress
Trivy scanner 🔄 In progress

New This Round — ForkScheduleSpecProvider Refactoring

The major new change is the introduction of ForkScheduleSpecProvider (base class) + ForkSpec (value type), plus refactoring SepoliaSpecProvider, HoodiSpecProvider, ChiadoSpecProvider, and MordenSpecProvider to inherit from it. This addresses LukaszRozmej's CHANGES_REQUESTED about reducing duplication. The implementation is clean:

  • ForkSpec: readonly record struct with two constructors for block-based and timestamp-based entries. Well designed.
  • ForkScheduleSpecProvider.GetSpec: correctly handles mixed block/timestamp schedules. Tries timestamp-based entries first (if activation has a timestamp), then falls back to block-based. For real-world block activations this is semantically equivalent to the previous switch-based logic in MainnetSpecProvider.
  • Lazy initialization: _schedule, _forks, _availableForks are all lazy. GnosisSpecProvider correctly uses the Func<ForkSpec[]> constructor to avoid a circular static-init dependency on LondonGnosis.
  • All ISpecProvider members: covered — SealEngine has a default impl in the interface (Ethash); TimestampFork, NetworkId, BeaconChainGenesisTimestamp are abstract; ChainId defaults to NetworkId; all else are implemented.

LukaszRozmej's CHANGES_REQUESTED — Status

# Comment Status
TraceRpcModuleTests.cs — use nameof from Nethermind.Specs.Forks ✅ Done
TraceRpcModuleTests.cs — excessive comments Still present at lines 1345–1351
ChainSpecBasedSpecProviderTests.cs"London" → expect LondonGnosis ✅ Addressed (uses canonical name, expects Gnosis variant)
ChainSpecBasedSpecProvider — use separate classes for each network ✅ Done — ForksForChain delegates to GnosisSpecProvider, ChiadoSpecProvider, SepoliaSpecProvider, HoodiSpecProvider, MordenSpecProvider
SepoliaSpecProvider — extract base class to remove GetSpec duplication ✅ Done — ForkScheduleSpecProvider

Findings

Low — Excessive comments in TraceRpcModuleTests.cs:1345–1351 (LukaszRozmej CHANGES_REQUESTED, still unresolved)

Lines 1345–1346 describe what the code does (already communicated by the test name trace_block_pre_1559_fork_override_on_london_block_zeroes_base_fee). Line 1351's because string embeds the internal method name AdjustHeaderForSpec. Both should be removed:

// Remove lines 1345-1346 (the block comment before the trace_block call)
// Change line 1350-1351 from:
        result.Result.ResultType.Should().Be(ResultType.Success,
            "tracing a London block with a pre-EIP-1559 fork override must succeed (AdjustHeaderForSpec zeroes BaseFeePerGas)");
// To:
        result.Result.ResultType.Should().Be(ResultType.Success);

Nit — ForkScheduleSpecProvider has IForkAwareSpecProvider methods but doesn't declare the interface

ForkScheduleSpecProvider defines AvailableForks, TryGetForkSpec, and Forks — everything IForkAwareSpecProvider requires — but it doesn't declare the interface. MainnetSpecProvider and GnosisSpecProvider have to explicitly add : IForkAwareSpecProvider. In practice, SepoliaSpecProvider, HoodiSpecProvider, ChiadoSpecProvider, and MordenSpecProvider cannot be used directly for fork overrides (the is not IForkAwareSpecProvider guard in TryResolveForkSpec fires). This is acceptable because ChainSpecBasedSpecProvider covers these networks via ForksForChain, but having the base class declare the interface would be cleaner and eliminate the need for the explicit declarations on each subclass.


Implementation Correctness — Confirmed

  • ForkScheduleSpecProvider.GetSpec: Correct for all real-world block activations. The mixed block/timestamp schedule (as in MainnetSpecProvider) is handled correctly: timestamp lookup first (for post-merge blocks), then block number fallback (for pre-merge blocks). The theoretical edge case of a pre-Paris block with a post-Shanghai timestamp never occurs in practice.
  • DI flow: tracerEnv.BuildAndOverride(baseBlock, specOverride: forkSpec)OverridableSpecProvider.SetOverride → EVM execution → ResetOverride() on Scope.Dispose(). No override leaks.
  • TryResolveForkSpec: Handles all four cases cleanly — null, unsupported provider, empty string, unknown fork name.
  • AdjustHeaderForSpec: Correctly zeros BaseFeePerGas for pre-EIP-1559; nulls ExcessBlobGas for pre-EIP-4844. No unused parameters.
  • ChainSpecBasedSpecProvider.ForksForChain: Delegates to the correct network-specific FrozenDictionary — Gnosis/Chiado/Sepolia/Hoodi/Morden are all covered.
  • TraceStoreRpcModule: Correctly bypasses the trace store for fork-override requests.

Overall

One Low issue from LukaszRozmej's CHANGES_REQUESTED remains open (excessive comments at lines 1345–1351 in TraceRpcModuleTests.cs). The ForkScheduleSpecProvider refactoring is well-implemented and correctly addresses the duplication LukaszRozmej flagged. All prior Critical/High/Medium findings are resolved.

@DarkLord017
Copy link
Copy Markdown
Contributor Author

@claude re-review

@claude
Copy link
Copy Markdown
Contributor

claude Bot commented May 12, 2026

Claude finished @DarkLord017's task in 5m 5s —— View job


PR Re-Review: Fork override trace block

  • Gather context and check CI status
  • Read all changed files (current state)
  • Check LukaszRozmej's CHANGES_REQUESTED are addressed
  • Analyze implementation correctness
  • Post findings

CI Status

Check Result
Code formatting ✅ Success
Dependency review ✅ Success
Spell check ✅ Success
PR labeler ✅ Success
Build solutions 🔄 In progress
Code Lint 🔄 In progress
Nethermind tests (Flat DB) 🔄 Queued
Nethermind extra test variants 🔄 In progress
Nethermind/Ethereum tests 🔄 In progress
Sync PR Gate (Hoodi) 🔄 Queued

Previous Findings — Status

# Severity Issue Status
1–9 Med/Low All prior rounds (null guard, BPO forks, record, visibility, ordering, ChainSpec, FrozenDict, BlobGas, unused param) ✅ Resolved
10 Low ForkScheduleSpecProvider GetSpec duplication → extract base class ForkScheduleSpecProvider base class implemented
11 Low Excessive comments lines 1345–1351 (LukaszRozmej CHANGES_REQUESTED) Still present — inline suggestion re-posted

New Finding — Regression

Medium — ChiadoSpecProvider.SealEngine changed to static, silently breaks ISpecProvider

The ForkScheduleSpecProvider refactoring changed ChiadoSpecProvider.SealEngine from an instance property to a static property:

- public string SealEngine => SealEngineType.AuRa;
+ public static string SealEngine => SealEngineType.AuRa;

A static member does not implement the ISpecProvider.SealEngine instance property. ISpecProvider has a default implementation string SealEngine => SealEngineType.Ethash. Any code accessing SealEngine through an ISpecProvider reference on a ChiadoSpecProvider instance will silently get "Ethash" instead of "AuRa". This is a regression — Chiado is an AuRa consensus chain.

GnosisSpecProvider correctly keeps public string SealEngine => SealEngineType.AuRa; (instance). ChiadoSpecProvider must match. Inline suggestion posted.


ForkScheduleSpecProvider Correctness — Verified

The new GetSpec logic is semantically equivalent to the previous per-provider switch implementations:

  • Block-only forks (Mainnet pre-Paris): loop iterates, matches the highest block entry ≤ blockNumber, breaks on first non-match. ✅
  • Post-merge / timestamp forks: after block entries are exhausted, iterates all timestamp entries without breaking; returns the highest matching timestamp spec. ✅
  • Null timestamp (e.g., pre-merge activation): timestamp entries are skipped (forkActivation.Timestamp is ulong ts fails), returns last matched block-based spec. ✅
  • Hoodi all-zero timestamps (ShanghaiTimestamp = CancunTimestamp = 0): three entries with ts=0 all match at timestamp=0, loop returns the last — Cancun. Matches old behavior. ✅
  • FrozenDictionary Forks: built from Spec.Name as key; all Gnosis/Chiado/Sepolia/Hoodi fork specs have distinct names, no duplicate key risk. ✅

LukaszRozmej's CHANGES_REQUESTED (2026-05-11/12) — Status

# Comment Status
Use nameof from Nethermind.Specs.Forks ✅ Done
Excessive comments in test ❌ Lines 1345–1351 still present
"London" input should map to LondonGnosis spec ✅ Addressed
Use separate classes for Gnosis/Chiado/Sepolia/Hoodi ForkScheduleSpecProvider base class
Extract base class to remove GetSpec duplication ✅ Done

Summary

Severity Count Issues
Critical 0
High 0
Medium 1 ChiadoSpecProvider.SealEngine static regression
Low 1 Excessive comments lines 1345–1351 (LukaszRozmej CHANGES_REQUESTED)

Two items must be fixed before merge: the static SealEngine regression on ChiadoSpecProvider and the open CHANGES_REQUESTED on the test comments.

Comment thread src/Nethermind/Nethermind.Specs/ChiadoSpecProvider.cs Outdated
@DarkLord017
Copy link
Copy Markdown
Contributor Author

@claude review

@claude
Copy link
Copy Markdown
Contributor

claude Bot commented May 12, 2026

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


PR Review: Fork override trace block

  • Gather context and check CI status
  • Read all changed files (current state)
  • Check previous findings from last review
  • Analyze implementation correctness
  • Post findings

CI Status

Check Result
Code formatting ✅ Success
Dependency review ✅ Success
Spell check ✅ Success
PR labeler ✅ Success
Build solutions 🔄 In progress
Code Lint 🔄 In progress
Nethermind tests (Flat DB) 🔄 In progress
Nethermind extra test variants 🔄 In progress
Nethermind/Ethereum tests 🔄 Queued
Sync PR Gate (Hoodi) 🔄 In progress
Stateless execution tests 🔄 In progress

Previous Findings — Status

# Severity Issue Status
1–9 Med/Low All prior rounds (null guard, BPO forks, record, visibility, ordering, ChainSpec, FrozenDict, BlobGas, unused param) ✅ Resolved
Medium ChiadoSpecProvider.SealEngine was static, silently returning Ethash via interface Fixed — commit 7480b17 removes static, now correctly public string SealEngine => SealEngineType.AuRa;
Low Excessive comments in TraceRpcModuleTests.cs:1345–1351 (LukaszRozmej CHANGES_REQUESTED) Still present

GetSpec Algorithm Change — Verified Correct

Commit 452c9f9 rewrote ForkScheduleSpecProvider.GetSpec from backward iteration to forward iteration. The new algorithm is semantically equivalent for all real-world cases:

  • All-block schedules (Morden): Forward iteration hits all entries, break fires when block doesn't match — correct.
  • All-timestamp schedules (Sepolia, Hoodi, Chiado): No block entries to break on; all timestamps evaluated in order; returns last matching timestamp spec — correct. For Hoodi's all-zero timestamps (Shanghai=0, Cancun=0), a query with ts=0 correctly returns Cancun.Instance.
  • Mixed schedules (Mainnet, Gnosis): Block forks processed forward, break fires when block doesn't match (which only happens before the block's fork level); timestamp forks after Paris are all reachable for any valid mainnet block at or above Paris height — correct.
  • No timestamp (forkActivation.Timestamp is null): Block forks still processed; timestamp condition forkActivation.Timestamp is ulong ts short-circuits for null — correct.

Remaining Issue

Low — Excessive comments in TraceRpcModuleTests.cs:1345–1351 (LukaszRozmej CHANGES_REQUESTED, still open)

Lines 1345–1346 describe what the code does (already communicated by the test name trace_block_pre_1559_fork_override_on_london_block_zeroes_base_fee). Line 1351 embeds the internal method name AdjustHeaderForSpec in the because string. Both are noise:

// Remove lines 1345-1346, and strip the `because` string from line 1350-1351:
ResultWrapper<IEnumerable<ParityTxTraceFromStore>> result =
    context.TraceRpcModule.trace_block(new BlockParameter(1L), nameof(Berlin));

result.Result.ResultType.Should().Be(ResultType.Success);

Overall Assessment

The implementation is correct and complete. Both recent commits address issues from the last review: the static SealEngine regression on ChiadoSpecProvider is fixed, and the GetSpec forward-iteration algorithm is correct for all real-world fork schedules. The DI flow, override isolation, header adjustment, and error handling are all sound and unchanged.

The one remaining item is the Low-severity excessive comments that LukaszRozmej's CHANGES_REQUESTED has flagged across multiple rounds. Once that's addressed, the PR should be ready for re-approval.


@DarkLord017 DarkLord017 requested a review from LukaszRozmej May 12, 2026 22:18
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Extending trace_ endpoints with fork as a parameter

2 participants