diff --git a/src/Nethermind/Nethermind.Consensus/EngineApiVersions.cs b/src/Nethermind/Nethermind.Consensus/EngineApiVersions.cs index d3c7cde9be7d..9d8285ccdec2 100644 --- a/src/Nethermind/Nethermind.Consensus/EngineApiVersions.cs +++ b/src/Nethermind/Nethermind.Consensus/EngineApiVersions.cs @@ -3,12 +3,41 @@ namespace Nethermind.Consensus; +/// +/// 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 { - 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 version (e.g. Cancun/Prague/Osaka all use V3). + 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 + } + + /// engine_newPayload method versions. + public static class NewPayload + { + 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 2d9a72fb65c1..9e80c7316e38 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; } @@ -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}"); @@ -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) @@ -127,34 +127,48 @@ protected virtual int WritePayloadIdMembers(BlockHeader parentHeader, Span return position; } + /// + /// 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 payloadVersion) => + (fcuVersion, payloadVersion) switch + { + // Exception: FCUv2 also accepts Paris (V1) attributes for backward compatibility. + (EngineApiVersions.Fcu.V2, PayloadAttributesVersions.V1) => true, + _ => fcuVersion == payloadVersion + }; + + /// + /// Validates that the payload attributes version is consistent with the FCU version and the fork indicated by the timestamp. + /// + /// + /// — 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) { - // version calculated from parameters should match api version - if (actualVersion != apiVersion) + // This FCU version doesn't support this fork at all (e.g. V3 attrs sent to FCUv2). + if (!IsSupportedFcuForkCombination(fcuVersion, actualVersion)) { - // 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"; - return actualVersion <= EngineApiVersions.Paris ? PayloadAttributesValidationResult.InvalidParams : PayloadAttributesValidationResult.InvalidPayloadAttributes; + error = $"{methodName}{fcuVersion} expected"; + return PayloadAttributesValidationResult.InvalidPayloadAttributes; } - // timestamp should correspond to proper api version - if (timestampVersion != apiVersion) + // 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 timestampVersion <= EngineApiVersions.Paris ? PayloadAttributesValidationResult.InvalidParams : PayloadAttributesValidationResult.UnsupportedFork; + // FCU also doesn't support this fork → UnsupportedFork (post-Paris only) + return fcuVersion != timestampVersion && timestampVersion >= PayloadAttributesVersions.V2 + ? PayloadAttributesValidationResult.UnsupportedFork + : PayloadAttributesValidationResult.InvalidPayloadAttributes; } error = null; @@ -163,44 +177,71 @@ private static PayloadAttributesValidationResult ValidateVersion( public virtual PayloadAttributesValidationResult Validate( ISpecProvider specProvider, - int apiVersion, - [NotNullWhen(false)] out string? error) => - ValidateVersion( - apiVersion: apiVersion, - actualVersion: this.GetVersion(), - timestampVersion: specProvider.GetSpec(ForkActivation.TimestampOnly(Timestamp)) - .ExpectedPayloadAttributesVersion(), + int fcuVersion, + [NotNullWhen(false)] out string? error) + { + int actualVersion = this.GetVersion(); + PayloadAttributesValidationResult result = ValidateVersion( + fcuVersion, + 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, InvalidParams, InvalidPayloadAttributes, UnsupportedFork }; +public enum PayloadAttributesValidationResult : byte { Success, InvalidPayloadAttributes, UnsupportedFork }; 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 } => 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.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.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"); } } 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); } 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/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 => 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