From eec2ef7cd22449b91521fa7222cb9268d988260e Mon Sep 17 00:00:00 2001 From: "muzry.li" Date: Tue, 10 Mar 2026 22:39:12 +0800 Subject: [PATCH 01/13] engine: return -38003 for FCUv2 payloadAttributes mismatch --- .../Producers/PayloadAttributes.cs | 10 ++++++++++ .../EngineModuleTests.V2.cs | 4 ++-- 2 files changed, 12 insertions(+), 2 deletions(-) diff --git a/src/Nethermind/Nethermind.Consensus/Producers/PayloadAttributes.cs b/src/Nethermind/Nethermind.Consensus/Producers/PayloadAttributes.cs index 2d9a72fb65c1..3f2f83d337ad 100644 --- a/src/Nethermind/Nethermind.Consensus/Producers/PayloadAttributes.cs +++ b/src/Nethermind/Nethermind.Consensus/Producers/PayloadAttributes.cs @@ -147,6 +147,11 @@ private static PayloadAttributesValidationResult ValidateVersion( } error = $"{methodName}{apiVersion} expected"; + if (apiVersion == EngineApiVersions.Shanghai) + { + return PayloadAttributesValidationResult.InvalidPayloadAttributes; + } + return actualVersion <= EngineApiVersions.Paris ? PayloadAttributesValidationResult.InvalidParams : PayloadAttributesValidationResult.InvalidPayloadAttributes; } @@ -154,6 +159,11 @@ private static PayloadAttributesValidationResult ValidateVersion( if (timestampVersion != apiVersion) { error = $"{methodName}{timestampVersion} expected"; + if (apiVersion == EngineApiVersions.Shanghai) + { + return PayloadAttributesValidationResult.InvalidPayloadAttributes; + } + return timestampVersion <= EngineApiVersions.Paris ? PayloadAttributesValidationResult.InvalidParams : PayloadAttributesValidationResult.UnsupportedFork; } diff --git a/src/Nethermind/Nethermind.Merge.Plugin.Test/EngineModuleTests.V2.cs b/src/Nethermind/Nethermind.Merge.Plugin.Test/EngineModuleTests.V2.cs index e7901f566b62..cc047415d406 100644 --- a/src/Nethermind/Nethermind.Merge.Plugin.Test/EngineModuleTests.V2.cs +++ b/src/Nethermind/Nethermind.Merge.Plugin.Test/EngineModuleTests.V2.cs @@ -635,13 +635,13 @@ int ErrorCode "{0}V2 expected", null, "0x6817d4b48be0bc14f144cc242cdc47a5ccc40de34b9c3934acad45057369f576", - ErrorCodes.InvalidParams); + MergeErrorCodes.InvalidPayloadAttributes); yield return ( London.Instance, "{0}V1 expected", Array.Empty(), "0xaa4aa15951a28e6adab430a795e36a84649bbafb1257eda23e38b9131cbd3b98", - ErrorCodes.InvalidParams); + MergeErrorCodes.InvalidPayloadAttributes); } [TestCaseSource(nameof(ZeroWithdrawalsTestCases))] From 06fc83ee78b3ef0f304fd67ed088a2a48e3cc065 Mon Sep 17 00:00:00 2001 From: "lukasz.rozmej" Date: Wed, 11 Mar 2026 14:35:05 +0100 Subject: [PATCH 02/13] Fix Payload version validation --- .../Nethermind.Consensus/EngineApiVersions.cs | 24 ++++++ .../Producers/PayloadAttributes.cs | 79 ++++++++++++------- .../EngineModuleTests.V2.cs | 6 +- 3 files changed, 76 insertions(+), 33 deletions(-) diff --git a/src/Nethermind/Nethermind.Consensus/EngineApiVersions.cs b/src/Nethermind/Nethermind.Consensus/EngineApiVersions.cs index d3c7cde9be7d..8d52b58a6731 100644 --- a/src/Nethermind/Nethermind.Consensus/EngineApiVersions.cs +++ b/src/Nethermind/Nethermind.Consensus/EngineApiVersions.cs @@ -11,4 +11,28 @@ public static class EngineApiVersions public const int Prague = 4; public const int Osaka = 5; public const int Amsterdam = 6; + + /// + /// forkchoiceUpdated method versions. + /// + /// Multiple forks may share the same FCU version (e.g. Cancun/Prague/Osaka all use FCUv3). + /// + public static class Fcu + { + public const int V1 = 1; // Paris + public const int V2 = 2; // Shanghai + public const int V3 = 3; // Cancun/Prague/Osaka + public const int V4 = 4; // Amsterdam + } + + /// + /// Maps a fork's engine API version to the forkchoiceUpdated method version it uses. + /// + public static int FcuVersion(int apiVersion) => apiVersion switch + { + >= Amsterdam => Fcu.V4, // Amsterdam + >= Cancun => Fcu.V3, // Cancun/Prague/Osaka + >= Shanghai => Fcu.V2, // Shanghai + _ => Fcu.V1 // Paris + }; } diff --git a/src/Nethermind/Nethermind.Consensus/Producers/PayloadAttributes.cs b/src/Nethermind/Nethermind.Consensus/Producers/PayloadAttributes.cs index 3f2f83d337ad..3021a08f245f 100644 --- a/src/Nethermind/Nethermind.Consensus/Producers/PayloadAttributes.cs +++ b/src/Nethermind/Nethermind.Consensus/Producers/PayloadAttributes.cs @@ -34,7 +34,7 @@ public class PayloadAttributes public string ToString(string indentation) { - var sb = new StringBuilder($"{indentation}{nameof(PayloadAttributes)} {{") + StringBuilder sb = new StringBuilder($"{indentation}{nameof(PayloadAttributes)} {{") .Append($"{nameof(Timestamp)}: {Timestamp}, ") .Append($"{nameof(PrevRandao)}: {PrevRandao}, ") .Append($"{nameof(SuggestedFeeRecipient)}: {SuggestedFeeRecipient}"); @@ -127,6 +127,39 @@ protected virtual int WritePayloadIdMembers(BlockHeader parentHeader, Span return position; } + /// + /// The matrix of valid (FCU version, timestamp fork) combinations. + /// Each entry maps to the PayloadAttributes version the caller must provide. + /// + /// + /// The expected PayloadAttributes version for the combination, + /// or null if this FCU version doesn't support the given fork + /// (e.g. FCUv3 at a Shanghai timestamp, or FCUv2 at a Cancun timestamp). + /// + private static int? ExpectedPayloadVersion(int apiVersion, int timestampVersion) => + (EngineApiVersions.FcuVersion(apiVersion), timestampVersion) switch + { + (EngineApiVersions.Fcu.V1, PayloadAttributesVersions.Paris) => PayloadAttributesVersions.Paris, + (EngineApiVersions.Fcu.V2, PayloadAttributesVersions.Paris or PayloadAttributesVersions.Shanghai) => timestampVersion, + (EngineApiVersions.Fcu.V3, PayloadAttributesVersions.Cancun) => PayloadAttributesVersions.Cancun, + (EngineApiVersions.Fcu.V4, PayloadAttributesVersions.Amsterdam) => PayloadAttributesVersions.Amsterdam, + _ => null + }; + + /// + /// Validates that the payload attributes version is consistent with the engine API version and the fork indicated by the timestamp. + /// + /// The engine API version of the called method (e.g. for FCUv2). + /// The payload attributes version inferred from the fields present in the request (see ). + /// The payload attributes version expected for the fork active at the requested timestamp (see ). + /// Prefix for error messages (e.g. "PayloadAttributesV"). + /// Set to a descriptive message on failure; null on success. + /// + /// when the (FCU version, fork, attributes) combination is valid; + /// when the FCU version doesn't support the timestamp's fork (post-Paris only, since the error code was introduced after Paris); + /// when the attributes structure doesn't match what the fork expects (e.g. missing withdrawals at Shanghai), + /// or when an unsupported Paris-era combination is encountered (falls back from UnsupportedFork since that error code didn't exist at Paris). + /// private static PayloadAttributesValidationResult ValidateVersion( int apiVersion, int actualVersion, @@ -134,37 +167,24 @@ private static PayloadAttributesValidationResult ValidateVersion( string methodName, [NotNullWhen(false)] out string? error) { - // version calculated from parameters should match api version - if (actualVersion != apiVersion) - { - // except of Shanghai api handling Paris fork - if (apiVersion == EngineApiVersions.Shanghai && timestampVersion == PayloadAttributesVersions.Paris || - apiVersion == EngineApiVersions.Amsterdam && timestampVersion == PayloadAttributesVersions.Amsterdam) - { - - error = null; - return PayloadAttributesValidationResult.Success; - } - - error = $"{methodName}{apiVersion} expected"; - if (apiVersion == EngineApiVersions.Shanghai) - { - return PayloadAttributesValidationResult.InvalidPayloadAttributes; - } - - return actualVersion <= EngineApiVersions.Paris ? PayloadAttributesValidationResult.InvalidParams : PayloadAttributesValidationResult.InvalidPayloadAttributes; - } + // Look up the matrix of PayloadVersion that corresponds to the FCU version. + int? expectedPayloadVersion = ExpectedPayloadVersion(apiVersion, timestampVersion); - // timestamp should correspond to proper api version - if (timestampVersion != apiVersion) + // If null — this FCU version doesn't support this fork at all. + if (expectedPayloadVersion is null) { error = $"{methodName}{timestampVersion} expected"; - if (apiVersion == EngineApiVersions.Shanghai) - { - return PayloadAttributesValidationResult.InvalidPayloadAttributes; - } + return timestampVersion >= PayloadAttributesVersions.Shanghai + ? PayloadAttributesValidationResult.UnsupportedFork // error code added in Shanghai + : PayloadAttributesValidationResult.InvalidPayloadAttributes; + } - return timestampVersion <= EngineApiVersions.Paris ? PayloadAttributesValidationResult.InvalidParams : PayloadAttributesValidationResult.UnsupportedFork; + // Compare the expected version to what the caller actually sent. + // If mismatch → InvalidPayloadAttributes (-38003): "wrong attributes structure for this fork (e.g., missing withdrawals at Shanghai, or extra withdrawals before Shanghai) + if (actualVersion != expectedPayloadVersion) + { + error = $"{methodName}{expectedPayloadVersion} expected"; + return PayloadAttributesValidationResult.InvalidPayloadAttributes; } error = null; @@ -178,8 +198,7 @@ public virtual PayloadAttributesValidationResult Validate( ValidateVersion( apiVersion: apiVersion, actualVersion: this.GetVersion(), - timestampVersion: specProvider.GetSpec(ForkActivation.TimestampOnly(Timestamp)) - .ExpectedPayloadAttributesVersion(), + timestampVersion: specProvider.GetSpec(ForkActivation.TimestampOnly(Timestamp)).ExpectedPayloadAttributesVersion(), "PayloadAttributesV", out error); } diff --git a/src/Nethermind/Nethermind.Merge.Plugin.Test/EngineModuleTests.V2.cs b/src/Nethermind/Nethermind.Merge.Plugin.Test/EngineModuleTests.V2.cs index cc047415d406..1fe310087675 100644 --- a/src/Nethermind/Nethermind.Merge.Plugin.Test/EngineModuleTests.V2.cs +++ b/src/Nethermind/Nethermind.Merge.Plugin.Test/EngineModuleTests.V2.cs @@ -236,7 +236,7 @@ int ErrorCode errorResponse.Should().NotBeNull(); errorResponse!.Error.Should().NotBeNull(); - errorResponse!.Error!.Code.Should().Be(input.ErrorCode); + errorResponse!.Error!.Code.Should().Be(MergeErrorCodes.InvalidPayloadAttributes); errorResponse!.Error!.Message.Should().Be(string.Format(input.ErrorMessage, "PayloadAttributes")); } @@ -635,13 +635,13 @@ int ErrorCode "{0}V2 expected", null, "0x6817d4b48be0bc14f144cc242cdc47a5ccc40de34b9c3934acad45057369f576", - MergeErrorCodes.InvalidPayloadAttributes); + ErrorCodes.InvalidParams); yield return ( London.Instance, "{0}V1 expected", Array.Empty(), "0xaa4aa15951a28e6adab430a795e36a84649bbafb1257eda23e38b9131cbd3b98", - MergeErrorCodes.InvalidPayloadAttributes); + ErrorCodes.InvalidParams); } [TestCaseSource(nameof(ZeroWithdrawalsTestCases))] From f16cd4ce3f5cfca80b213755eb7000c280a8cd84 Mon Sep 17 00:00:00 2001 From: "lukasz.rozmej" Date: Wed, 11 Mar 2026 14:58:48 +0100 Subject: [PATCH 03/13] Change EngineApiVersions to versioning per method --- .../Nethermind.Consensus/EngineApiVersions.cs | 47 ++++++----- .../Producers/PayloadAttributes.cs | 83 ++++++++----------- .../EngineRpcModule.Amsterdam.cs | 4 +- .../EngineRpcModule.Cancun.cs | 4 +- .../EngineRpcModule.Paris.cs | 6 +- .../EngineRpcModule.Prague.cs | 2 +- .../EngineRpcModule.Shanghai.cs | 4 +- .../Handlers/GetPayloadV1Handler.cs | 3 +- .../Handlers/GetPayloadV2Handler.cs | 3 +- .../Handlers/GetPayloadV3Handler.cs | 3 +- .../Handlers/GetPayloadV4Handler.cs | 3 +- .../Handlers/GetPayloadV5Handler.cs | 3 +- .../Handlers/GetPayloadV6Handler.cs | 3 +- .../OptimismPayloadAttributesTests.cs | 2 +- .../Rpc/OptimismPayloadAttributes.cs | 4 +- .../TaikoPayloadAttributes.cs | 4 +- 16 files changed, 86 insertions(+), 92 deletions(-) diff --git a/src/Nethermind/Nethermind.Consensus/EngineApiVersions.cs b/src/Nethermind/Nethermind.Consensus/EngineApiVersions.cs index 8d52b58a6731..c15e6737d1ba 100644 --- a/src/Nethermind/Nethermind.Consensus/EngineApiVersions.cs +++ b/src/Nethermind/Nethermind.Consensus/EngineApiVersions.cs @@ -3,20 +3,15 @@ namespace Nethermind.Consensus; +/// +/// Fork ordinals for ordering and range checks. These are NOT method version numbers — +/// use the nested classes (, , ) +/// for actual method versions. +/// public static class EngineApiVersions { - public const int Paris = 1; - public const int Shanghai = 2; - public const int Cancun = 3; - public const int Prague = 4; - public const int Osaka = 5; - public const int Amsterdam = 6; - - /// - /// forkchoiceUpdated method versions. - /// - /// Multiple forks may share the same FCU version (e.g. Cancun/Prague/Osaka all use FCUv3). - /// + /// forkchoiceUpdated method versions. + /// Multiple forks may share the same version (e.g. Cancun/Prague/Osaka all use V3). public static class Fcu { public const int V1 = 1; // Paris @@ -25,14 +20,24 @@ public static class Fcu public const int V4 = 4; // Amsterdam } - /// - /// Maps a fork's engine API version to the forkchoiceUpdated method version it uses. - /// - public static int FcuVersion(int apiVersion) => apiVersion switch + /// engine_newPayload method versions. + public static class NewPayload { - >= Amsterdam => Fcu.V4, // Amsterdam - >= Cancun => Fcu.V3, // Cancun/Prague/Osaka - >= Shanghai => Fcu.V2, // Shanghai - _ => Fcu.V1 // Paris - }; + public const int V1 = 1; // Paris + public const int V2 = 2; // Shanghai + public const int V3 = 3; // Cancun + public const int V4 = 4; // Prague/Osaka + public const int V5 = 5; // Amsterdam + } + + /// engine_getPayload method versions. + public static class GetPayload + { + public const int V1 = 1; // Paris + public const int V2 = 2; // Shanghai + public const int V3 = 3; // Cancun + public const int V4 = 4; // Prague + public const int V5 = 5; // Osaka + public const int V6 = 6; // Amsterdam + } } diff --git a/src/Nethermind/Nethermind.Consensus/Producers/PayloadAttributes.cs b/src/Nethermind/Nethermind.Consensus/Producers/PayloadAttributes.cs index 3021a08f245f..c95a54ccfb4e 100644 --- a/src/Nethermind/Nethermind.Consensus/Producers/PayloadAttributes.cs +++ b/src/Nethermind/Nethermind.Consensus/Producers/PayloadAttributes.cs @@ -128,62 +128,45 @@ protected virtual int WritePayloadIdMembers(BlockHeader parentHeader, Span } /// - /// The matrix of valid (FCU version, timestamp fork) combinations. - /// Each entry maps to the PayloadAttributes version the caller must provide. + /// Whether this FCU version supports the given fork (identified by its payload attributes version). + /// General rule: FCU version must match the payload attributes version. /// - /// - /// The expected PayloadAttributes version for the combination, - /// or null if this FCU version doesn't support the given fork - /// (e.g. FCUv3 at a Shanghai timestamp, or FCUv2 at a Cancun timestamp). - /// - private static int? ExpectedPayloadVersion(int apiVersion, int timestampVersion) => - (EngineApiVersions.FcuVersion(apiVersion), timestampVersion) switch + private static bool IsSupportedFcuForkCombination(int fcuVersion, int timestampVersion) => + (fcuVersion, timestampVersion) switch { - (EngineApiVersions.Fcu.V1, PayloadAttributesVersions.Paris) => PayloadAttributesVersions.Paris, - (EngineApiVersions.Fcu.V2, PayloadAttributesVersions.Paris or PayloadAttributesVersions.Shanghai) => timestampVersion, - (EngineApiVersions.Fcu.V3, PayloadAttributesVersions.Cancun) => PayloadAttributesVersions.Cancun, - (EngineApiVersions.Fcu.V4, PayloadAttributesVersions.Amsterdam) => PayloadAttributesVersions.Amsterdam, - _ => null + // Exception: FCUv2 also accepts Paris (V1) attributes for backward compatibility. + (EngineApiVersions.Fcu.V2, PayloadAttributesVersions.V1) => true, + _ => fcuVersion == timestampVersion }; /// - /// Validates that the payload attributes version is consistent with the engine API version and the fork indicated by the timestamp. + /// Validates that the payload attributes version is consistent with the FCU version and the fork indicated by the timestamp. /// - /// The engine API version of the called method (e.g. for FCUv2). - /// The payload attributes version inferred from the fields present in the request (see ). - /// The payload attributes version expected for the fork active at the requested timestamp (see ). - /// Prefix for error messages (e.g. "PayloadAttributesV"). - /// Set to a descriptive message on failure; null on success. /// - /// when the (FCU version, fork, attributes) combination is valid; - /// when the FCU version doesn't support the timestamp's fork (post-Paris only, since the error code was introduced after Paris); - /// when the attributes structure doesn't match what the fork expects (e.g. missing withdrawals at Shanghai), - /// or when an unsupported Paris-era combination is encountered (falls back from UnsupportedFork since that error code didn't exist at Paris). + /// — FCU version doesn't support this fork (post-Paris only); + /// — attributes structure doesn't match the fork; + /// — valid combination. /// private static PayloadAttributesValidationResult ValidateVersion( - int apiVersion, + int fcuVersion, int actualVersion, int timestampVersion, string methodName, [NotNullWhen(false)] out string? error) { - // Look up the matrix of PayloadVersion that corresponds to the FCU version. - int? expectedPayloadVersion = ExpectedPayloadVersion(apiVersion, timestampVersion); - - // If null — this FCU version doesn't support this fork at all. - if (expectedPayloadVersion is null) + // This FCU version doesn't support this fork at all. + if (!IsSupportedFcuForkCombination(fcuVersion, timestampVersion)) { error = $"{methodName}{timestampVersion} expected"; - return timestampVersion >= PayloadAttributesVersions.Shanghai - ? PayloadAttributesValidationResult.UnsupportedFork // error code added in Shanghai + return timestampVersion >= PayloadAttributesVersions.V2 // error code added in FCUv2 (Shanghai) + ? PayloadAttributesValidationResult.UnsupportedFork : PayloadAttributesValidationResult.InvalidPayloadAttributes; } - // Compare the expected version to what the caller actually sent. - // If mismatch → InvalidPayloadAttributes (-38003): "wrong attributes structure for this fork (e.g., missing withdrawals at Shanghai, or extra withdrawals before Shanghai) - if (actualVersion != expectedPayloadVersion) + // Attributes structure doesn't match what the fork expects. + if (actualVersion != timestampVersion) { - error = $"{methodName}{expectedPayloadVersion} expected"; + error = $"{methodName}{timestampVersion} expected"; return PayloadAttributesValidationResult.InvalidPayloadAttributes; } @@ -193,10 +176,10 @@ private static PayloadAttributesValidationResult ValidateVersion( public virtual PayloadAttributesValidationResult Validate( ISpecProvider specProvider, - int apiVersion, + int fcuVersion, [NotNullWhen(false)] out string? error) => ValidateVersion( - apiVersion: apiVersion, + fcuVersion: fcuVersion, actualVersion: this.GetVersion(), timestampVersion: specProvider.GetSpec(ForkActivation.TimestampOnly(Timestamp)).ExpectedPayloadAttributesVersion(), "PayloadAttributesV", @@ -210,26 +193,26 @@ public static class PayloadAttributesExtensions public static int GetVersion(this PayloadAttributes executionPayload) => executionPayload switch { - { SlotNumber: not null } => PayloadAttributesVersions.Amsterdam, - { ParentBeaconBlockRoot: not null, Withdrawals: not null } => PayloadAttributesVersions.Cancun, - { Withdrawals: not null } => PayloadAttributesVersions.Shanghai, - _ => PayloadAttributesVersions.Paris + { SlotNumber: not null } => PayloadAttributesVersions.V4, + { ParentBeaconBlockRoot: not null, Withdrawals: not null } => PayloadAttributesVersions.V3, + { Withdrawals: not null } => PayloadAttributesVersions.V2, + _ => PayloadAttributesVersions.V1 }; public static int ExpectedPayloadAttributesVersion(this IReleaseSpec spec) => spec switch { - { IsEip7843Enabled: true } => PayloadAttributesVersions.Amsterdam, - { IsEip4844Enabled: true } => PayloadAttributesVersions.Cancun, - { WithdrawalsEnabled: true } => PayloadAttributesVersions.Shanghai, - _ => PayloadAttributesVersions.Paris + { IsEip7843Enabled: true } => PayloadAttributesVersions.V4, + { IsEip4844Enabled: true } => PayloadAttributesVersions.V3, + { WithdrawalsEnabled: true } => PayloadAttributesVersions.V2, + _ => PayloadAttributesVersions.V1 }; } public static class PayloadAttributesVersions { - public const int Paris = 1; - public const int Shanghai = 2; - public const int Cancun = 3; - public const int Amsterdam = 4; + public const int V1 = 1; // Paris + public const int V2 = 2; // Shanghai + public const int V3 = 3; // Cancun/Prague/Osaka + public const int V4 = 4; // Amsterdam } diff --git a/src/Nethermind/Nethermind.Merge.Plugin/EngineRpcModule.Amsterdam.cs b/src/Nethermind/Nethermind.Merge.Plugin/EngineRpcModule.Amsterdam.cs index 596152acb695..c6888d93786e 100644 --- a/src/Nethermind/Nethermind.Merge.Plugin/EngineRpcModule.Amsterdam.cs +++ b/src/Nethermind/Nethermind.Merge.Plugin/EngineRpcModule.Amsterdam.cs @@ -22,10 +22,10 @@ public partial class EngineRpcModule : IEngineRpcModule => _getPayloadHandlerV6.HandleAsync(payloadId); public Task> engine_newPayloadV5(ExecutionPayloadV4 executionPayload, byte[]?[] blobVersionedHashes, Hash256? parentBeaconBlockRoot, byte[][]? executionRequests) - => NewPayload(new ExecutionPayloadParams(executionPayload, blobVersionedHashes, parentBeaconBlockRoot, executionRequests), EngineApiVersions.Amsterdam); + => NewPayload(new ExecutionPayloadParams(executionPayload, blobVersionedHashes, parentBeaconBlockRoot, executionRequests), EngineApiVersions.NewPayload.V5); public Task> engine_forkchoiceUpdatedV4(ForkchoiceStateV1 forkchoiceState, PayloadAttributes? payloadAttributes = null) - => ForkchoiceUpdated(forkchoiceState, payloadAttributes, EngineApiVersions.Amsterdam); + => ForkchoiceUpdated(forkchoiceState, payloadAttributes, EngineApiVersions.Fcu.V4); public Task>> engine_getPayloadBodiesByHashV2(IReadOnlyList blockHashes) => _executionGetPayloadBodiesByHashV2Handler.Handle(blockHashes); diff --git a/src/Nethermind/Nethermind.Merge.Plugin/EngineRpcModule.Cancun.cs b/src/Nethermind/Nethermind.Merge.Plugin/EngineRpcModule.Cancun.cs index b0744d25363d..042ca510fe81 100644 --- a/src/Nethermind/Nethermind.Merge.Plugin/EngineRpcModule.Cancun.cs +++ b/src/Nethermind/Nethermind.Merge.Plugin/EngineRpcModule.Cancun.cs @@ -18,10 +18,10 @@ public partial class EngineRpcModule : IEngineRpcModule private readonly IAsyncHandler> _getBlobsHandler; public Task> engine_forkchoiceUpdatedV3(ForkchoiceStateV1 forkchoiceState, PayloadAttributes? payloadAttributes = null) - => ForkchoiceUpdated(forkchoiceState, payloadAttributes, EngineApiVersions.Cancun); + => ForkchoiceUpdated(forkchoiceState, payloadAttributes, EngineApiVersions.Fcu.V3); public Task> engine_newPayloadV3(ExecutionPayloadV3 executionPayload, byte[]?[] blobVersionedHashes, Hash256? parentBeaconBlockRoot) => - NewPayload(new ExecutionPayloadParams(executionPayload, blobVersionedHashes, parentBeaconBlockRoot), EngineApiVersions.Cancun); + NewPayload(new ExecutionPayloadParams(executionPayload, blobVersionedHashes, parentBeaconBlockRoot), EngineApiVersions.NewPayload.V3); public Task> engine_getPayloadV3(byte[] payloadId) => _getPayloadHandlerV3.HandleAsync(payloadId); diff --git a/src/Nethermind/Nethermind.Merge.Plugin/EngineRpcModule.Paris.cs b/src/Nethermind/Nethermind.Merge.Plugin/EngineRpcModule.Paris.cs index c3bcea2649c5..3d6c1e7789d2 100644 --- a/src/Nethermind/Nethermind.Merge.Plugin/EngineRpcModule.Paris.cs +++ b/src/Nethermind/Nethermind.Merge.Plugin/EngineRpcModule.Paris.cs @@ -33,13 +33,13 @@ public ResultWrapper engine_exchangeTransitionConfigu TransitionConfigurationV1 beaconTransitionConfiguration) => _transitionConfigurationHandler.Handle(beaconTransitionConfiguration); public Task> engine_forkchoiceUpdatedV1(ForkchoiceStateV1 forkchoiceState, PayloadAttributes? payloadAttributes = null) - => ForkchoiceUpdated(forkchoiceState, payloadAttributes, EngineApiVersions.Paris); + => ForkchoiceUpdated(forkchoiceState, payloadAttributes, EngineApiVersions.Fcu.V1); public Task> engine_getPayloadV1(byte[] payloadId) => _getPayloadHandlerV1.HandleAsync(payloadId); public Task> engine_newPayloadV1(ExecutionPayload executionPayload) - => NewPayload(executionPayload, EngineApiVersions.Paris); + => NewPayload(executionPayload, EngineApiVersions.NewPayload.V1); protected async Task> ForkchoiceUpdated(ForkchoiceStateV1 forkchoiceState, PayloadAttributes? payloadAttributes, int version) { @@ -73,7 +73,7 @@ protected async Task> NewPayload(IExecutionPayloa if (!executionPayload.ValidateFork(_specProvider)) { if (_logger.IsWarn) _logger.Warn($"The payload is not supported by the current fork"); - return ResultWrapper.Fail(MergeErrorMessages.UnsupportedFork, version < EngineApiVersions.Shanghai ? ErrorCodes.InvalidParams : MergeErrorCodes.UnsupportedFork); + return ResultWrapper.Fail(MergeErrorMessages.UnsupportedFork, version < EngineApiVersions.NewPayload.V2 ? ErrorCodes.InvalidParams : MergeErrorCodes.UnsupportedFork); } IReleaseSpec releaseSpec = _specProvider.GetSpec(executionPayload.BlockNumber, executionPayload.Timestamp); diff --git a/src/Nethermind/Nethermind.Merge.Plugin/EngineRpcModule.Prague.cs b/src/Nethermind/Nethermind.Merge.Plugin/EngineRpcModule.Prague.cs index c72cccbb3ab1..800abf14ca01 100644 --- a/src/Nethermind/Nethermind.Merge.Plugin/EngineRpcModule.Prague.cs +++ b/src/Nethermind/Nethermind.Merge.Plugin/EngineRpcModule.Prague.cs @@ -19,7 +19,7 @@ public partial class EngineRpcModule : IEngineRpcModule /// EIP-7685. /// public Task> engine_newPayloadV4(ExecutionPayloadV3 executionPayload, byte[]?[] blobVersionedHashes, Hash256? parentBeaconBlockRoot, byte[][]? executionRequests) - => NewPayload(new ExecutionPayloadParams(executionPayload, blobVersionedHashes, parentBeaconBlockRoot, executionRequests), EngineApiVersions.Prague); + => NewPayload(new ExecutionPayloadParams(executionPayload, blobVersionedHashes, parentBeaconBlockRoot, executionRequests), EngineApiVersions.NewPayload.V4); public Task> engine_getPayloadV4(byte[] payloadId) => _getPayloadHandlerV4.HandleAsync(payloadId); diff --git a/src/Nethermind/Nethermind.Merge.Plugin/EngineRpcModule.Shanghai.cs b/src/Nethermind/Nethermind.Merge.Plugin/EngineRpcModule.Shanghai.cs index 0b435dc1172b..2625b2243e9b 100644 --- a/src/Nethermind/Nethermind.Merge.Plugin/EngineRpcModule.Shanghai.cs +++ b/src/Nethermind/Nethermind.Merge.Plugin/EngineRpcModule.Shanghai.cs @@ -19,7 +19,7 @@ public partial class EngineRpcModule : IEngineRpcModule private readonly IAsyncHandler _getPayloadHandlerV2; public Task> engine_forkchoiceUpdatedV2(ForkchoiceStateV1 forkchoiceState, PayloadAttributes? payloadAttributes = null) - => ForkchoiceUpdated(forkchoiceState, payloadAttributes, EngineApiVersions.Shanghai); + => ForkchoiceUpdated(forkchoiceState, payloadAttributes, EngineApiVersions.Fcu.V2); public Task> engine_getPayloadV2(byte[] payloadId) => _getPayloadHandlerV2.HandleAsync(payloadId); @@ -31,5 +31,5 @@ public Task> engine_forkchoiceUpdatedV2 => _executionGetPayloadBodiesByRangeV1Handler.Handle(start, count); public Task> engine_newPayloadV2(ExecutionPayload executionPayload) - => NewPayload(executionPayload, EngineApiVersions.Shanghai); + => NewPayload(executionPayload, EngineApiVersions.NewPayload.V2); } diff --git a/src/Nethermind/Nethermind.Merge.Plugin/Handlers/GetPayloadV1Handler.cs b/src/Nethermind/Nethermind.Merge.Plugin/Handlers/GetPayloadV1Handler.cs index 4c2a3ad5719e..8b169cf72b40 100644 --- a/src/Nethermind/Nethermind.Merge.Plugin/Handlers/GetPayloadV1Handler.cs +++ b/src/Nethermind/Nethermind.Merge.Plugin/Handlers/GetPayloadV1Handler.cs @@ -1,6 +1,7 @@ // SPDX-FileCopyrightText: 2022 Demerzel Solutions Limited // SPDX-License-Identifier: LGPL-3.0-only +using Nethermind.Consensus; using Nethermind.Consensus.Producers; using Nethermind.Core.Specs; using Nethermind.Logging; @@ -25,7 +26,7 @@ namespace Nethermind.Merge.Plugin.Handlers; /// Execution client may stop the building process with the corresponding payload_id value after serving this call. /// public class GetPayloadV1Handler(IPayloadPreparationService payloadPreparationService, ISpecProvider specProvider, ILogManager logManager) - : GetPayloadHandlerBase(1, payloadPreparationService, specProvider, logManager) + : GetPayloadHandlerBase(EngineApiVersions.GetPayload.V1, payloadPreparationService, specProvider, logManager) { protected override ExecutionPayload GetPayloadResultFromBlock(IBlockProductionContext context) => ExecutionPayload.Create(context.CurrentBestBlock!); diff --git a/src/Nethermind/Nethermind.Merge.Plugin/Handlers/GetPayloadV2Handler.cs b/src/Nethermind/Nethermind.Merge.Plugin/Handlers/GetPayloadV2Handler.cs index 49eaad78a0d1..739d39e56ef4 100644 --- a/src/Nethermind/Nethermind.Merge.Plugin/Handlers/GetPayloadV2Handler.cs +++ b/src/Nethermind/Nethermind.Merge.Plugin/Handlers/GetPayloadV2Handler.cs @@ -1,6 +1,7 @@ // SPDX-FileCopyrightText: 2022 Demerzel Solutions Limited // SPDX-License-Identifier: LGPL-3.0-only +using Nethermind.Consensus; using Nethermind.Consensus.Producers; using Nethermind.Core.Specs; using Nethermind.Logging; @@ -17,7 +18,7 @@ public class GetPayloadV2Handler( IPayloadPreparationService payloadPreparationService, ISpecProvider specProvider, ILogManager logManager) - : GetPayloadHandlerBase(2, payloadPreparationService, specProvider, logManager) + : GetPayloadHandlerBase(EngineApiVersions.GetPayload.V2, payloadPreparationService, specProvider, logManager) { protected override GetPayloadV2Result GetPayloadResultFromBlock(IBlockProductionContext context) => new(context.CurrentBestBlock!, context.BlockFees); diff --git a/src/Nethermind/Nethermind.Merge.Plugin/Handlers/GetPayloadV3Handler.cs b/src/Nethermind/Nethermind.Merge.Plugin/Handlers/GetPayloadV3Handler.cs index 8c1eed935237..9146a7dbeec1 100644 --- a/src/Nethermind/Nethermind.Merge.Plugin/Handlers/GetPayloadV3Handler.cs +++ b/src/Nethermind/Nethermind.Merge.Plugin/Handlers/GetPayloadV3Handler.cs @@ -1,6 +1,7 @@ // SPDX-FileCopyrightText: 2025 Demerzel Solutions Limited // SPDX-License-Identifier: LGPL-3.0-only +using Nethermind.Consensus; using Nethermind.Consensus.Producers; using Nethermind.Core.Specs; using Nethermind.Logging; @@ -19,7 +20,7 @@ public class GetPayloadV3Handler( ISpecProvider specProvider, ILogManager logManager, ICensorshipDetector? censorshipDetector = null) - : GetPayloadHandlerBase(3, payloadPreparationService, specProvider, logManager, censorshipDetector) + : GetPayloadHandlerBase(EngineApiVersions.GetPayload.V3, payloadPreparationService, specProvider, logManager, censorshipDetector) { protected override GetPayloadV3Result GetPayloadResultFromBlock(IBlockProductionContext context) { diff --git a/src/Nethermind/Nethermind.Merge.Plugin/Handlers/GetPayloadV4Handler.cs b/src/Nethermind/Nethermind.Merge.Plugin/Handlers/GetPayloadV4Handler.cs index 93bb06d48365..7bc639ab3f1e 100644 --- a/src/Nethermind/Nethermind.Merge.Plugin/Handlers/GetPayloadV4Handler.cs +++ b/src/Nethermind/Nethermind.Merge.Plugin/Handlers/GetPayloadV4Handler.cs @@ -1,6 +1,7 @@ // SPDX-FileCopyrightText: 2025 Demerzel Solutions Limited // SPDX-License-Identifier: LGPL-3.0-only +using Nethermind.Consensus; using Nethermind.Core.Specs; using Nethermind.Logging; using Nethermind.Merge.Plugin.BlockProduction; @@ -19,7 +20,7 @@ public class GetPayloadV4Handler( ISpecProvider specProvider, ILogManager logManager, ICensorshipDetector? censorshipDetector = null) - : GetPayloadHandlerBase(4, payloadPreparationService, specProvider, logManager, censorshipDetector) + : GetPayloadHandlerBase(EngineApiVersions.GetPayload.V4, payloadPreparationService, specProvider, logManager, censorshipDetector) { protected override GetPayloadV4Result GetPayloadResultFromBlock(IBlockProductionContext context) { diff --git a/src/Nethermind/Nethermind.Merge.Plugin/Handlers/GetPayloadV5Handler.cs b/src/Nethermind/Nethermind.Merge.Plugin/Handlers/GetPayloadV5Handler.cs index 656de8ccb6f5..7abb35cce5c6 100644 --- a/src/Nethermind/Nethermind.Merge.Plugin/Handlers/GetPayloadV5Handler.cs +++ b/src/Nethermind/Nethermind.Merge.Plugin/Handlers/GetPayloadV5Handler.cs @@ -1,6 +1,7 @@ // SPDX-FileCopyrightText: 2025 Demerzel Solutions Limited // SPDX-License-Identifier: LGPL-3.0-only +using Nethermind.Consensus; using Nethermind.Core.Specs; using Nethermind.Logging; using Nethermind.Merge.Plugin.BlockProduction; @@ -19,7 +20,7 @@ public class GetPayloadV5Handler( ISpecProvider specProvider, ILogManager logManager, ICensorshipDetector? censorshipDetector = null) - : GetPayloadHandlerBase(5, payloadPreparationService, specProvider, logManager, censorshipDetector) + : GetPayloadHandlerBase(EngineApiVersions.GetPayload.V5, payloadPreparationService, specProvider, logManager, censorshipDetector) { protected override GetPayloadV5Result GetPayloadResultFromBlock(IBlockProductionContext context) { diff --git a/src/Nethermind/Nethermind.Merge.Plugin/Handlers/GetPayloadV6Handler.cs b/src/Nethermind/Nethermind.Merge.Plugin/Handlers/GetPayloadV6Handler.cs index 9c5080d54e42..97d75fe6dbab 100644 --- a/src/Nethermind/Nethermind.Merge.Plugin/Handlers/GetPayloadV6Handler.cs +++ b/src/Nethermind/Nethermind.Merge.Plugin/Handlers/GetPayloadV6Handler.cs @@ -1,6 +1,7 @@ // SPDX-FileCopyrightText: 2026 Demerzel Solutions Limited // SPDX-License-Identifier: LGPL-3.0-only +using Nethermind.Consensus; using Nethermind.Core.Specs; using Nethermind.Logging; using Nethermind.Merge.Plugin.BlockProduction; @@ -19,7 +20,7 @@ public class GetPayloadV6Handler( ISpecProvider specProvider, ILogManager logManager, ICensorshipDetector? censorshipDetector = null) - : GetPayloadHandlerBase(6, payloadPreparationService, specProvider, logManager, censorshipDetector) + : GetPayloadHandlerBase(EngineApiVersions.GetPayload.V6, payloadPreparationService, specProvider, logManager, censorshipDetector) { protected override GetPayloadV6Result GetPayloadResultFromBlock(IBlockProductionContext context) { diff --git a/src/Nethermind/Nethermind.Optimism.Test/OptimismPayloadAttributesTests.cs b/src/Nethermind/Nethermind.Optimism.Test/OptimismPayloadAttributesTests.cs index d977fc69de39..4292e0aaf0c0 100644 --- a/src/Nethermind/Nethermind.Optimism.Test/OptimismPayloadAttributesTests.cs +++ b/src/Nethermind/Nethermind.Optimism.Test/OptimismPayloadAttributesTests.cs @@ -81,7 +81,7 @@ public void Validate_EIP1559Params( ISpecProvider spec = Spec.BuildFor(fork.Timestamp); Assert.That( - payloadAttributes.Validate(spec, EngineApiVersions.Cancun, out var error), + payloadAttributes.Validate(spec, EngineApiVersions.Fcu.V3, out var error), testCase.isValid.On(fork.Timestamp) ? Is.EqualTo(PayloadAttributesValidationResult.Success) : Is.EqualTo(PayloadAttributesValidationResult.InvalidPayloadAttributes), diff --git a/src/Nethermind/Nethermind.Optimism/Rpc/OptimismPayloadAttributes.cs b/src/Nethermind/Nethermind.Optimism/Rpc/OptimismPayloadAttributes.cs index 9d504265c987..bb646350e25e 100644 --- a/src/Nethermind/Nethermind.Optimism/Rpc/OptimismPayloadAttributes.cs +++ b/src/Nethermind/Nethermind.Optimism/Rpc/OptimismPayloadAttributes.cs @@ -105,7 +105,7 @@ protected override int WritePayloadIdMembers(BlockHeader parentHeader, Span Date: Wed, 11 Mar 2026 15:42:39 +0100 Subject: [PATCH 04/13] remove PayloadAttributesValidationResult.InvalidParams --- .../Nethermind.Consensus/Producers/PayloadAttributes.cs | 2 +- .../Handlers/ForkchoiceUpdatedHandler.cs | 2 -- 2 files changed, 1 insertion(+), 3 deletions(-) diff --git a/src/Nethermind/Nethermind.Consensus/Producers/PayloadAttributes.cs b/src/Nethermind/Nethermind.Consensus/Producers/PayloadAttributes.cs index c95a54ccfb4e..22713d527ca3 100644 --- a/src/Nethermind/Nethermind.Consensus/Producers/PayloadAttributes.cs +++ b/src/Nethermind/Nethermind.Consensus/Producers/PayloadAttributes.cs @@ -186,7 +186,7 @@ public virtual PayloadAttributesValidationResult Validate( out error); } -public enum PayloadAttributesValidationResult : byte { Success, InvalidParams, InvalidPayloadAttributes, UnsupportedFork }; +public enum PayloadAttributesValidationResult : byte { Success, InvalidPayloadAttributes, UnsupportedFork }; public static class PayloadAttributesExtensions { diff --git a/src/Nethermind/Nethermind.Merge.Plugin/Handlers/ForkchoiceUpdatedHandler.cs b/src/Nethermind/Nethermind.Merge.Plugin/Handlers/ForkchoiceUpdatedHandler.cs index 1cd7ce507b99..aba6ba96d1f1 100644 --- a/src/Nethermind/Nethermind.Merge.Plugin/Handlers/ForkchoiceUpdatedHandler.cs +++ b/src/Nethermind/Nethermind.Merge.Plugin/Handlers/ForkchoiceUpdatedHandler.cs @@ -338,8 +338,6 @@ private ResultWrapper StartBuildingPayload(Block newH string? error = null; return payloadAttributes?.Validate(_specProvider, version, out error) switch { - PayloadAttributesValidationResult.InvalidParams => - ResultWrapper.Fail(error!, ErrorCodes.InvalidParams), PayloadAttributesValidationResult.InvalidPayloadAttributes => ResultWrapper.Fail(error!, MergeErrorCodes.InvalidPayloadAttributes), PayloadAttributesValidationResult.UnsupportedFork => From 986aa50312cf22d3e858867ccbd92853668b33f4 Mon Sep 17 00:00:00 2001 From: "lukasz.rozmej" Date: Wed, 11 Mar 2026 17:29:25 +0100 Subject: [PATCH 05/13] fix --- .../Producers/PayloadAttributes.cs | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/src/Nethermind/Nethermind.Consensus/Producers/PayloadAttributes.cs b/src/Nethermind/Nethermind.Consensus/Producers/PayloadAttributes.cs index 22713d527ca3..b5fad072e965 100644 --- a/src/Nethermind/Nethermind.Consensus/Producers/PayloadAttributes.cs +++ b/src/Nethermind/Nethermind.Consensus/Producers/PayloadAttributes.cs @@ -154,20 +154,21 @@ private static PayloadAttributesValidationResult ValidateVersion( string methodName, [NotNullWhen(false)] out string? error) { - // This FCU version doesn't support this fork at all. - if (!IsSupportedFcuForkCombination(fcuVersion, timestampVersion)) + // This FCU version doesn't support this fork at all (e.g. V3 attrs sent to FCUv2). + if (!IsSupportedFcuForkCombination(fcuVersion, actualVersion)) { - error = $"{methodName}{timestampVersion} expected"; - return timestampVersion >= PayloadAttributesVersions.V2 // error code added in FCUv2 (Shanghai) - ? PayloadAttributesValidationResult.UnsupportedFork - : PayloadAttributesValidationResult.InvalidPayloadAttributes; + error = $"{methodName}{fcuVersion} expected"; + return PayloadAttributesValidationResult.InvalidPayloadAttributes; } - // Attributes structure doesn't match what the fork expects. + // Attributes structure doesn't match what the fork expects (e.g. V3 attrs sent to when FCUv3 not yet activated in spec). if (actualVersion != timestampVersion) { error = $"{methodName}{timestampVersion} expected"; - return PayloadAttributesValidationResult.InvalidPayloadAttributes; + // FCU also doesn't support this fork → UnsupportedFork (post-Paris only) + return fcuVersion != timestampVersion && timestampVersion >= PayloadAttributesVersions.V2 + ? PayloadAttributesValidationResult.UnsupportedFork + : PayloadAttributesValidationResult.InvalidPayloadAttributes; } error = null; From 6d2f2c6e2c63002cbeaeb60cdae504a820ac1502 Mon Sep 17 00:00:00 2001 From: Lukasz Rozmej Date: Wed, 11 Mar 2026 17:30:35 +0100 Subject: [PATCH 06/13] Update src/Nethermind/Nethermind.Consensus/EngineApiVersions.cs Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- src/Nethermind/Nethermind.Consensus/EngineApiVersions.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/Nethermind/Nethermind.Consensus/EngineApiVersions.cs b/src/Nethermind/Nethermind.Consensus/EngineApiVersions.cs index c15e6737d1ba..9d8285ccdec2 100644 --- a/src/Nethermind/Nethermind.Consensus/EngineApiVersions.cs +++ b/src/Nethermind/Nethermind.Consensus/EngineApiVersions.cs @@ -4,9 +4,9 @@ namespace Nethermind.Consensus; /// -/// Fork ordinals for ordering and range checks. These are NOT method version numbers — -/// use the nested classes (, , ) -/// for actual method versions. +/// Engine API method version constants, grouped by method. +/// Use the nested classes (, , ) +/// to select the appropriate version when calling Execution Engine API methods. /// public static class EngineApiVersions { From 3586eeba2e66ec86d3d51b1c4b8832326b8b7a53 Mon Sep 17 00:00:00 2001 From: Lukasz Rozmej Date: Wed, 11 Mar 2026 17:33:12 +0100 Subject: [PATCH 07/13] Apply suggestion from @Copilot Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- .../Nethermind.Consensus/Producers/PayloadAttributes.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Nethermind/Nethermind.Consensus/Producers/PayloadAttributes.cs b/src/Nethermind/Nethermind.Consensus/Producers/PayloadAttributes.cs index b5fad072e965..07e309512446 100644 --- a/src/Nethermind/Nethermind.Consensus/Producers/PayloadAttributes.cs +++ b/src/Nethermind/Nethermind.Consensus/Producers/PayloadAttributes.cs @@ -195,7 +195,7 @@ public static int GetVersion(this PayloadAttributes executionPayload) => executionPayload switch { { SlotNumber: not null } => PayloadAttributesVersions.V4, - { ParentBeaconBlockRoot: not null, Withdrawals: not null } => PayloadAttributesVersions.V3, + { ParentBeaconBlockRoot: not null } => PayloadAttributesVersions.V3, { Withdrawals: not null } => PayloadAttributesVersions.V2, _ => PayloadAttributesVersions.V1 }; From 6c648b6442414fb428dacad2e7d0d68af25ec914 Mon Sep 17 00:00:00 2001 From: "lukasz.rozmej" Date: Wed, 11 Mar 2026 21:25:04 +0100 Subject: [PATCH 08/13] rename --- .../Nethermind.Consensus/Producers/PayloadAttributes.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/Nethermind/Nethermind.Consensus/Producers/PayloadAttributes.cs b/src/Nethermind/Nethermind.Consensus/Producers/PayloadAttributes.cs index b5fad072e965..82fb07ffdaf4 100644 --- a/src/Nethermind/Nethermind.Consensus/Producers/PayloadAttributes.cs +++ b/src/Nethermind/Nethermind.Consensus/Producers/PayloadAttributes.cs @@ -131,12 +131,12 @@ protected virtual int WritePayloadIdMembers(BlockHeader parentHeader, Span /// Whether this FCU version supports the given fork (identified by its payload attributes version). /// General rule: FCU version must match the payload attributes version. /// - private static bool IsSupportedFcuForkCombination(int fcuVersion, int timestampVersion) => - (fcuVersion, timestampVersion) switch + private static bool IsSupportedFcuForkCombination(int fcuVersion, int payloadVersion) => + (fcuVersion, timestampVersion: payloadVersion) switch { // Exception: FCUv2 also accepts Paris (V1) attributes for backward compatibility. (EngineApiVersions.Fcu.V2, PayloadAttributesVersions.V1) => true, - _ => fcuVersion == timestampVersion + _ => fcuVersion == payloadVersion }; /// From 9031c092ff5030f54e982afe795287db61e4071a Mon Sep 17 00:00:00 2001 From: "lukasz.rozmej" Date: Wed, 11 Mar 2026 23:54:01 +0100 Subject: [PATCH 09/13] proper field validation --- .../Producers/PayloadAttributes.cs | 42 ++++++++++--- .../EngineModuleTests.V1.cs | 62 ++++++++++++++++++- .../Boost/BoostBlockImprovementContext.cs | 5 +- 3 files changed, 99 insertions(+), 10 deletions(-) diff --git a/src/Nethermind/Nethermind.Consensus/Producers/PayloadAttributes.cs b/src/Nethermind/Nethermind.Consensus/Producers/PayloadAttributes.cs index 5eec0fd91b45..0314b35403e1 100644 --- a/src/Nethermind/Nethermind.Consensus/Producers/PayloadAttributes.cs +++ b/src/Nethermind/Nethermind.Consensus/Producers/PayloadAttributes.cs @@ -18,9 +18,9 @@ public class PayloadAttributes { public ulong Timestamp { get; set; } - public Hash256 PrevRandao { get; set; } + public Hash256? PrevRandao { get; set; } - public Address SuggestedFeeRecipient { get; set; } + public Address? SuggestedFeeRecipient { get; set; } public Withdrawal[]? Withdrawals { get; set; } @@ -97,10 +97,10 @@ protected virtual int WritePayloadIdMembers(BlockHeader parentHeader, Span BinaryPrimitives.WriteUInt64BigEndian(inputSpan.Slice(position, sizeof(ulong)), Timestamp); position += sizeof(ulong); - PrevRandao.Bytes.CopyTo(inputSpan.Slice(position, Keccak.Size)); + (PrevRandao ?? Keccak.Zero).Bytes.CopyTo(inputSpan.Slice(position, Keccak.Size)); position += Keccak.Size; - SuggestedFeeRecipient.Bytes.CopyTo(inputSpan.Slice(position, Address.Size)); + (SuggestedFeeRecipient ?? Address.Zero).Bytes.CopyTo(inputSpan.Slice(position, Address.Size)); position += Address.Size; if (Withdrawals is not null) @@ -178,13 +178,41 @@ private static PayloadAttributesValidationResult ValidateVersion( public virtual PayloadAttributesValidationResult Validate( ISpecProvider specProvider, int fcuVersion, - [NotNullWhen(false)] out string? error) => - ValidateVersion( + [NotNullWhen(false)] out string? error) + { + int actualVersion = this.GetVersion(); + PayloadAttributesValidationResult result = ValidateVersion( fcuVersion: fcuVersion, - actualVersion: this.GetVersion(), + actualVersion: actualVersion, timestampVersion: specProvider.GetSpec(ForkActivation.TimestampOnly(Timestamp)).ExpectedPayloadAttributesVersion(), "PayloadAttributesV", out error); + + if (result == PayloadAttributesValidationResult.Success) + { + error = ValidateFields(actualVersion); + result = error is null + ? PayloadAttributesValidationResult.Success + : PayloadAttributesValidationResult.InvalidPayloadAttributes; + } + + return result; + } + + private string? ValidateFields(int actualVersion) + { + if (Timestamp == 0) return $"{nameof(Timestamp)} must be provided"; + if (PrevRandao is null) return $"{nameof(PrevRandao)} must be provided"; + if (SuggestedFeeRecipient is null) return $"{nameof(SuggestedFeeRecipient)} must be provided"; + + return actualVersion switch + { + >= PayloadAttributesVersions.V2 when Withdrawals is null => $"{nameof(Withdrawals)} must be provided", + >= PayloadAttributesVersions.V3 when ParentBeaconBlockRoot is null => $"{nameof(ParentBeaconBlockRoot)} must be provided", + >= PayloadAttributesVersions.V4 when SlotNumber is null => $"{nameof(SlotNumber)} must be provided", + _ => null + }; + } } public enum PayloadAttributesValidationResult : byte { Success, InvalidPayloadAttributes, UnsupportedFork }; diff --git a/src/Nethermind/Nethermind.Merge.Plugin.Test/EngineModuleTests.V1.cs b/src/Nethermind/Nethermind.Merge.Plugin.Test/EngineModuleTests.V1.cs index 82e7a65ccef6..a24b08055eb4 100644 --- a/src/Nethermind/Nethermind.Merge.Plugin.Test/EngineModuleTests.V1.cs +++ b/src/Nethermind/Nethermind.Merge.Plugin.Test/EngineModuleTests.V1.cs @@ -1643,7 +1643,7 @@ private async Task BuildAndGetPayloadResult(MergeTestBlockchai Hash256 parentHead = chain.BlockTree.Head!.ParentHash!; return await BuildAndGetPayloadResult(rpc, chain, startingHead, parentHead, startingHead, - payloadAttributes.Timestamp, payloadAttributes.PrevRandao!, payloadAttributes.SuggestedFeeRecipient); + payloadAttributes.Timestamp, payloadAttributes.PrevRandao!, payloadAttributes.SuggestedFeeRecipient!); } private async Task BuildAndGetPayloadResult(MergeTestBlockchain chain, @@ -1676,4 +1676,64 @@ private void AssertExecutionStatusNotChangedV1(IBlockFinder blockFinder, Hash256 Assert.That(blockFinder.FinalizedHash, Is.Not.EqualTo(finalizedBlockHash)); Assert.That(blockFinder.SafeHash, Is.Not.EqualTo(confirmedBlockHash)); } + + public static IEnumerable ForkchoiceUpdatedFieldValidationTestCases + { + get + { + static PayloadAttributes Attrs( + Withdrawal[]? withdrawals = null, + Hash256? parentBeaconBlockRoot = null, + ulong? slotNumber = null, + Action? mutate = null) + { + PayloadAttributes attrs = new() + { + Timestamp = 1, + PrevRandao = Keccak.Zero, + SuggestedFeeRecipient = Address.Zero, + Withdrawals = withdrawals, + ParentBeaconBlockRoot = parentBeaconBlockRoot, + SlotNumber = slotNumber, + }; + mutate?.Invoke(attrs); + return attrs; + } + + static TestCaseData InvalidFieldCase(IReleaseSpec spec, string method, PayloadAttributes attrs, string testName) => + new(spec, method, attrs) + { + TestName = testName, + ExpectedResult = MergeErrorCodes.InvalidPayloadAttributes, + }; + + yield return InvalidFieldCase(Paris.Instance, nameof(IEngineRpcModule.engine_forkchoiceUpdatedV1), Attrs(mutate: a => a.Timestamp = 0), "FCUv1 Timestamp zero"); + yield return InvalidFieldCase(Paris.Instance, nameof(IEngineRpcModule.engine_forkchoiceUpdatedV1), Attrs(mutate: a => a.PrevRandao = null), "FCUv1 PrevRandao null"); + yield return InvalidFieldCase(Paris.Instance, nameof(IEngineRpcModule.engine_forkchoiceUpdatedV1), Attrs(mutate: a => a.SuggestedFeeRecipient = null), "FCUv1 SuggestedFeeRecipient null"); + + yield return InvalidFieldCase(Cancun.Instance, nameof(IEngineRpcModule.engine_forkchoiceUpdatedV3), Attrs(parentBeaconBlockRoot: Keccak.Zero), "FCUv3 Withdrawals null"); + yield return InvalidFieldCase(Amsterdam.Instance, nameof(IEngineRpcModule.engine_forkchoiceUpdatedV4), Attrs(parentBeaconBlockRoot: Keccak.Zero, slotNumber: 1), "FCUv4 Withdrawals null"); + yield return InvalidFieldCase(Amsterdam.Instance, nameof(IEngineRpcModule.engine_forkchoiceUpdatedV4), Attrs(withdrawals: [], slotNumber: 1), "FCUv4 ParentBeaconBlockRoot null"); + } + } + + [TestCaseSource(nameof(ForkchoiceUpdatedFieldValidationTestCases))] + public async Task ForkchoiceUpdated_should_validate_payload_attributes_fields( + IReleaseSpec releaseSpec, string method, PayloadAttributes payloadAttributes) + { + using MergeTestBlockchain chain = await CreateBlockchain(releaseSpec: releaseSpec); + IEngineRpcModule rpcModule = chain.EngineRpcModule; + ForkchoiceStateV1 fcuState = new(chain.BlockTree.HeadHash, chain.BlockTree.HeadHash, chain.BlockTree.HeadHash); + + // Set a valid timestamp relative to the chain head if test case left it non-zero + if (payloadAttributes.Timestamp != 0) + payloadAttributes.Timestamp = chain.BlockTree.Head!.Timestamp + 1; + + string response = await RpcTest.TestSerializedRequest(rpcModule, method, + chain.JsonSerializer.Serialize(fcuState), + chain.JsonSerializer.Serialize(payloadAttributes)); + JsonRpcErrorResponse errorResponse = chain.JsonSerializer.Deserialize(response); + + return errorResponse.Error?.Code ?? ErrorCodes.None; + } } diff --git a/src/Nethermind/Nethermind.Merge.Plugin/BlockProduction/Boost/BoostBlockImprovementContext.cs b/src/Nethermind/Nethermind.Merge.Plugin/BlockProduction/Boost/BoostBlockImprovementContext.cs index 8b0b0c17a3d4..efda516a9a19 100644 --- a/src/Nethermind/Nethermind.Merge.Plugin/BlockProduction/Boost/BoostBlockImprovementContext.cs +++ b/src/Nethermind/Nethermind.Merge.Plugin/BlockProduction/Boost/BoostBlockImprovementContext.cs @@ -52,14 +52,15 @@ public BoostBlockImprovementContext(Block currentBestBlock, { payloadAttributes = await _boostRelay.GetPayloadAttributes(payloadAttributes, cancellationToken); - _stateReader.TryGetAccount(parentHeader, payloadAttributes.SuggestedFeeRecipient, out AccountStruct account); + Address feeRecipient = payloadAttributes.SuggestedFeeRecipient!; + _stateReader.TryGetAccount(parentHeader, feeRecipient, out AccountStruct account); UInt256 balanceBefore = account.Balance; Block? block = await blockProducer.BuildBlock(parentHeader, _feesTracer, payloadAttributes, IBlockProducer.Flags.None, cancellationToken); if (block is not null) { CurrentBestBlock = block; BlockFees = _feesTracer.Fees; - _stateReader.TryGetAccount(parentHeader, payloadAttributes.SuggestedFeeRecipient, out account); + _stateReader.TryGetAccount(parentHeader, feeRecipient, out account); await _boostRelay.SendPayload(new BoostExecutionPayloadV1 { Block = ExecutionPayload.Create(block), Profit = account.Balance - balanceBefore }, cancellationToken); } From 35626ba29ea121649df0fe454410c7a4838cd084 Mon Sep 17 00:00:00 2001 From: Lukasz Rozmej Date: Thu, 12 Mar 2026 09:24:17 +0100 Subject: [PATCH 10/13] Update src/Nethermind/Nethermind.Merge.Plugin.Test/EngineModuleTests.V2.cs Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- .../Nethermind.Merge.Plugin.Test/EngineModuleTests.V2.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Nethermind/Nethermind.Merge.Plugin.Test/EngineModuleTests.V2.cs b/src/Nethermind/Nethermind.Merge.Plugin.Test/EngineModuleTests.V2.cs index 1fe310087675..e7901f566b62 100644 --- a/src/Nethermind/Nethermind.Merge.Plugin.Test/EngineModuleTests.V2.cs +++ b/src/Nethermind/Nethermind.Merge.Plugin.Test/EngineModuleTests.V2.cs @@ -236,7 +236,7 @@ int ErrorCode errorResponse.Should().NotBeNull(); errorResponse!.Error.Should().NotBeNull(); - errorResponse!.Error!.Code.Should().Be(MergeErrorCodes.InvalidPayloadAttributes); + errorResponse!.Error!.Code.Should().Be(input.ErrorCode); errorResponse!.Error!.Message.Should().Be(string.Format(input.ErrorMessage, "PayloadAttributes")); } From 7fec8e5e4a73f78e45c0a01ab589b50d6d57942d Mon Sep 17 00:00:00 2001 From: Lukasz Rozmej Date: Thu, 12 Mar 2026 09:24:40 +0100 Subject: [PATCH 11/13] Update src/Nethermind/Nethermind.Consensus/Producers/PayloadAttributes.cs Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- .../Nethermind.Consensus/Producers/PayloadAttributes.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Nethermind/Nethermind.Consensus/Producers/PayloadAttributes.cs b/src/Nethermind/Nethermind.Consensus/Producers/PayloadAttributes.cs index 0314b35403e1..c3126e571d7f 100644 --- a/src/Nethermind/Nethermind.Consensus/Producers/PayloadAttributes.cs +++ b/src/Nethermind/Nethermind.Consensus/Producers/PayloadAttributes.cs @@ -132,7 +132,7 @@ protected virtual int WritePayloadIdMembers(BlockHeader parentHeader, Span /// General rule: FCU version must match the payload attributes version. /// private static bool IsSupportedFcuForkCombination(int fcuVersion, int payloadVersion) => - (fcuVersion, timestampVersion: payloadVersion) switch + (fcuVersion, payloadVersion) switch { // Exception: FCUv2 also accepts Paris (V1) attributes for backward compatibility. (EngineApiVersions.Fcu.V2, PayloadAttributesVersions.V1) => true, From 22a93badf085f364f13fd236cb78435fdff9830b Mon Sep 17 00:00:00 2001 From: "lukasz.rozmej" Date: Thu, 12 Mar 2026 10:55:39 +0100 Subject: [PATCH 12/13] fix --- .../EngineModuleTests.V2.cs | 86 ++++++++----------- 1 file changed, 36 insertions(+), 50 deletions(-) diff --git a/src/Nethermind/Nethermind.Merge.Plugin.Test/EngineModuleTests.V2.cs b/src/Nethermind/Nethermind.Merge.Plugin.Test/EngineModuleTests.V2.cs index e7901f566b62..8293f9e6a034 100644 --- a/src/Nethermind/Nethermind.Merge.Plugin.Test/EngineModuleTests.V2.cs +++ b/src/Nethermind/Nethermind.Merge.Plugin.Test/EngineModuleTests.V2.cs @@ -52,10 +52,7 @@ public virtual async Task Should_process_block_as_expected_V2(string latestValid safeBlockHash = startingHead.ToString(), finalizedBlockHash = Keccak.Zero.ToString() }; - Withdrawal[] withdrawals = new[] - { - new Withdrawal { Index = 1, AmountInGwei = 3, Address = TestItem.AddressB, ValidatorIndex = 2 } - }; + Withdrawal[] withdrawals = [new() { Index = 1, AmountInGwei = 3, Address = TestItem.AddressB, ValidatorIndex = 2 }]; var payloadAttrs = new { timestamp = timestamp.ToHexString(true), @@ -63,13 +60,10 @@ public virtual async Task Should_process_block_as_expected_V2(string latestValid suggestedFeeRecipient = feeRecipient.ToString(), withdrawals }; - string?[] @params = new string?[] - { - chain.JsonSerializer.Serialize(fcuState), chain.JsonSerializer.Serialize(payloadAttrs) - }; + string?[] @params = [chain.JsonSerializer.Serialize(fcuState), chain.JsonSerializer.Serialize(payloadAttrs)]; string expectedPayloadId = payloadId; - string response = await RpcTest.TestSerializedRequest(rpc, "engine_forkchoiceUpdatedV2", @params!); + string response = await RpcTest.TestSerializedRequest(rpc, "engine_forkchoiceUpdatedV2", @params); JsonRpcSuccessResponse? successResponse = chain.JsonSerializer.Deserialize(response); successResponse.Should().NotBeNull(); @@ -236,7 +230,7 @@ int ErrorCode errorResponse.Should().NotBeNull(); errorResponse!.Error.Should().NotBeNull(); - errorResponse!.Error!.Code.Should().Be(input.ErrorCode); + errorResponse!.Error!.Code.Should().Be(MergeErrorCodes.InvalidPayloadAttributes); errorResponse!.Error!.Message.Should().Be(string.Format(input.ErrorMessage, "PayloadAttributes")); } @@ -336,10 +330,7 @@ public virtual async Task chain.AddTransactions(txs); ExecutionPayload executionPayload2 = await BuildAndSendNewBlockV2(rpc, chain, true, withdrawals); - Hash256[] blockHashes = new Hash256[] - { - executionPayload1.BlockHash, TestItem.KeccakA, executionPayload2.BlockHash - }; + Hash256[] blockHashes = [executionPayload1.BlockHash, TestItem.KeccakA, executionPayload2.BlockHash]; IEnumerable payloadBodies = rpc.engine_getPayloadBodiesByHashV1(blockHashes).Data; ExecutionPayloadBodyV1Result?[] expected = { @@ -432,8 +423,8 @@ public virtual async Task getPayloadBodiesByRangeV1_should_return_canonical(With IEngineRpcModule rpc = chain.EngineRpcModule; ExecutionPayload executionPayload1 = await SendNewBlockV2(rpc, chain, withdrawals); - await rpc.engine_forkchoiceUpdatedV2(new ForkchoiceStateV1(executionPayload1.BlockHash!, - executionPayload1.BlockHash!, executionPayload1.BlockHash!)); + await rpc.engine_forkchoiceUpdatedV2(new ForkchoiceStateV1(executionPayload1.BlockHash, + executionPayload1.BlockHash!, executionPayload1.BlockHash)); Block head = chain.BlockTree.Head!; @@ -487,9 +478,9 @@ await rpc.engine_forkchoiceUpdatedV2( IEnumerable payloadBodies = rpc.engine_getPayloadBodiesByRangeV1(1, 3).Result.Data; ExecutionPayloadBodyV1Result[] expected = - { + [ new(Array.Empty(), withdrawals), new(Array.Empty(), withdrawals) - }; + ]; payloadBodies.Should().BeEquivalentTo(expected, static o => o.WithStrictOrdering()); } @@ -617,9 +608,9 @@ int ErrorCode JsonRpcErrorResponse? errorResponse = chain.JsonSerializer.Deserialize(response); errorResponse.Should().NotBeNull(); - errorResponse!.Error.Should().NotBeNull(); - errorResponse!.Error!.Code.Should().Be(input.ErrorCode); - errorResponse!.Error!.Message.Should().Be(string.Format(input.ErrorMessage, "ExecutionPayload")); + errorResponse.Error.Should().NotBeNull(); + errorResponse.Error!.Code.Should().Be(input.ErrorCode); + errorResponse.Error!.Message.Should().Be(string.Format(input.ErrorMessage, "ExecutionPayload")); } protected static IEnumerable<( @@ -764,8 +755,8 @@ public void Should_print_payload_attributes_as_expected() [TestCaseSource(nameof(PayloadIdTestCases))] public void Should_compute_payload_id_with_withdrawals((Withdrawal[]? Withdrawals, string PayloadId) input) { - var blockHeader = Build.A.BlockHeader.TestObject; - var payloadAttributes = new PayloadAttributes + BlockHeader blockHeader = Build.A.BlockHeader.TestObject; + PayloadAttributes payloadAttributes = new() { PrevRandao = Keccak.Zero, SuggestedFeeRecipient = Address.Zero, @@ -773,7 +764,7 @@ public void Should_compute_payload_id_with_withdrawals((Withdrawal[]? Withdrawal Withdrawals = input.Withdrawals }; - var payloadId = payloadAttributes.GetPayloadId(blockHeader); + string payloadId = payloadAttributes.GetPayloadId(blockHeader); payloadId.Should().Be(input.PayloadId); } @@ -793,29 +784,24 @@ private static async Task BuildAndGetPayloadResultV2( Withdrawal[][] Withdrawals, // withdrawals per payload (Address, UInt256)[] expectedAccountIncrease)> WithdrawalsTestCases() { - yield return (new[] { Array.Empty() }, Array.Empty<(Address, UInt256)>()); - yield return (new[] { new[] { TestItem.WithdrawalA_1Eth, TestItem.WithdrawalB_2Eth } }, - new[] { (TestItem.AddressA, 1.Ether), (TestItem.AddressB, 2.Ether) }); - yield return (new[] { new[] { TestItem.WithdrawalA_1Eth, TestItem.WithdrawalA_1Eth } }, - new[] { (TestItem.AddressA, 2.Ether), (TestItem.AddressB, 0.Ether) }); - yield return ( - new[] - { - new[] { TestItem.WithdrawalA_1Eth, TestItem.WithdrawalA_1Eth }, new[] { TestItem.WithdrawalA_1Eth } - }, new[] { (TestItem.AddressA, 3.Ether), (TestItem.AddressB, 0.Ether) }); - yield return (new[] - { - new[] { TestItem.WithdrawalA_1Eth, TestItem.WithdrawalA_1Eth }, // 1st payload - new[] { TestItem.WithdrawalA_1Eth }, // 2nd payload + yield return ([Array.Empty()], Array.Empty<(Address, UInt256)>()); + yield return ([[TestItem.WithdrawalA_1Eth, TestItem.WithdrawalB_2Eth]], + [(TestItem.AddressA, 1.Ether), (TestItem.AddressB, 2.Ether)]); + yield return ([[TestItem.WithdrawalA_1Eth, TestItem.WithdrawalA_1Eth]], + [(TestItem.AddressA, 2.Ether), (TestItem.AddressB, 0.Ether)]); + yield return ([[TestItem.WithdrawalA_1Eth, TestItem.WithdrawalA_1Eth], [TestItem.WithdrawalA_1Eth]], + [(TestItem.AddressA, 3.Ether), (TestItem.AddressB, 0.Ether)]); + yield return ([ + [TestItem.WithdrawalA_1Eth, TestItem.WithdrawalA_1Eth], // 1st payload + [TestItem.WithdrawalA_1Eth], // 2nd payload [], // 3rd payload - new[] { TestItem.WithdrawalA_1Eth, TestItem.WithdrawalC_3Eth }, // 4th payload - new[] { TestItem.WithdrawalB_2Eth, TestItem.WithdrawalF_6Eth }, // 5th payload - }, - new[] - { + [TestItem.WithdrawalA_1Eth, TestItem.WithdrawalC_3Eth], // 4th payload + [TestItem.WithdrawalB_2Eth, TestItem.WithdrawalF_6Eth] // 5th payload + ], + [ (TestItem.AddressA, 4.Ether), (TestItem.AddressB, 2.Ether), (TestItem.AddressC, 3.Ether), (TestItem.AddressF, 6.Ether) - }); + ]); } protected static IEnumerable> GetPayloadWithdrawalsTestCases() @@ -842,8 +828,8 @@ private async Task BuildAndGetPayloadResultV2( ? chain.WaitForImprovedBlock() : Task.CompletedTask; - ForkchoiceStateV1 forkchoiceState = new ForkchoiceStateV1(headBlockHash, finalizedBlockHash, safeBlockHash); - PayloadAttributes payloadAttributes = new PayloadAttributes + ForkchoiceStateV1 forkchoiceState = new(headBlockHash, finalizedBlockHash, safeBlockHash); + PayloadAttributes payloadAttributes = new() { Timestamp = timestamp, PrevRandao = random, @@ -911,17 +897,17 @@ bool isValid ExecutionPayloadBodyV1Result result = new ExecutionPayloadBodyV1Result(Array.Empty(), null); yield return ( - new Func(i => null), + _ => null, new ExecutionPayloadBodyV1Result?[] { null, null, null, null, null } ); yield return ( - new Func(i => i.ArgAt(0) % 2 == 0 ? block : null), + i => i.ArgAt(0) % 2 == 0 ? block : null, new[] { null, result, null, result, null } ); yield return ( - new Func(i => block), + _ => block, Enumerable.Repeat(result, 5) ); } @@ -933,6 +919,6 @@ string payloadId { yield return (null, "0xe3b6f7433feedc38"); yield return (Array.Empty(), "0xf74921b673b2e08e"); - yield return (new[] { Build.A.Withdrawal.TestObject }, "0xe0d0b996245ec3a6"); + yield return ([Build.A.Withdrawal.TestObject], "0xe0d0b996245ec3a6"); } } From 2ddb96c18d746e30bb97c5da2515f9f5e840b2d3 Mon Sep 17 00:00:00 2001 From: Lukasz Rozmej Date: Thu, 12 Mar 2026 16:53:00 +0100 Subject: [PATCH 13/13] Apply suggestions from code review Co-authored-by: Marc --- .../Nethermind.Consensus/Producers/PayloadAttributes.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Nethermind/Nethermind.Consensus/Producers/PayloadAttributes.cs b/src/Nethermind/Nethermind.Consensus/Producers/PayloadAttributes.cs index c3126e571d7f..9e80c7316e38 100644 --- a/src/Nethermind/Nethermind.Consensus/Producers/PayloadAttributes.cs +++ b/src/Nethermind/Nethermind.Consensus/Producers/PayloadAttributes.cs @@ -182,8 +182,8 @@ public virtual PayloadAttributesValidationResult Validate( { int actualVersion = this.GetVersion(); PayloadAttributesValidationResult result = ValidateVersion( - fcuVersion: fcuVersion, - actualVersion: actualVersion, + fcuVersion, + actualVersion, timestampVersion: specProvider.GetSpec(ForkActivation.TimestampOnly(Timestamp)).ExpectedPayloadAttributesVersion(), "PayloadAttributesV", out error);