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