diff --git a/src/Nethermind/Nethermind.JsonRpc/ErrorType.cs b/src/Nethermind/Nethermind.JsonRpc/ErrorType.cs index 3b4da6c3c8d4..8ae4af13215c 100644 --- a/src/Nethermind/Nethermind.JsonRpc/ErrorType.cs +++ b/src/Nethermind/Nethermind.JsonRpc/ErrorType.cs @@ -87,5 +87,9 @@ public static class ErrorCodes /// public const int UnknownBlockError = -39001; + /// + /// Unsupported fork error + /// + public const int UnsupportedFork = -38005; } } diff --git a/src/Nethermind/Nethermind.Merge.Plugin.Test/EngineModuleTests.HelperFunctions.cs b/src/Nethermind/Nethermind.Merge.Plugin.Test/EngineModuleTests.HelperFunctions.cs index 36459b7444e7..8f48a6e3d708 100644 --- a/src/Nethermind/Nethermind.Merge.Plugin.Test/EngineModuleTests.HelperFunctions.cs +++ b/src/Nethermind/Nethermind.Merge.Plugin.Test/EngineModuleTests.HelperFunctions.cs @@ -84,9 +84,15 @@ private ExecutionPayload CreateParentBlockRequestOnHead(IBlockTree blockTree) }; } - private static ExecutionPayload CreateBlockRequest(ExecutionPayload parent, Address miner, IList? withdrawals = null, ulong? dataGasUsed = null, ulong? excessDataGas = null, Transaction[]? transactions = null) + private static ExecutionPayload CreateBlockRequest(ExecutionPayload parent, Address miner, IList? withdrawals = null, Transaction[]? transactions = null) + => CreateBlockRequestInternal(parent, miner, withdrawals, transactions: transactions); + + private static ExecutionPayloadV3 CreateBlockRequestV3(ExecutionPayload parent, Address miner, IList? withdrawals = null, ulong? dataGasUsed = null, ulong? excessDataGas = null, Transaction[]? transactions = null) + => CreateBlockRequestInternal(parent, miner, withdrawals, dataGasUsed, excessDataGas, transactions: transactions); + + private static T CreateBlockRequestInternal(ExecutionPayload parent, Address miner, IList? withdrawals = null, ulong? dataGasUsed = null, ulong? excessDataGas = null, Transaction[]? transactions = null) where T : ExecutionPayload, new() { - ExecutionPayload blockRequest = new() + T blockRequest = new() { ParentHash = parent.BlockHash, FeeRecipient = miner, @@ -98,10 +104,14 @@ private static ExecutionPayload CreateBlockRequest(ExecutionPayload parent, Addr LogsBloom = Bloom.Empty, Timestamp = parent.Timestamp + 1, Withdrawals = withdrawals, - DataGasUsed = dataGasUsed, - ExcessDataGas = excessDataGas, }; + if (blockRequest is ExecutionPayloadV3 blockRequestV3) + { + blockRequestV3.DataGasUsed = dataGasUsed; + blockRequestV3.ExcessDataGas = excessDataGas; + } + blockRequest.SetTransactions(transactions ?? Array.Empty()); TryCalculateHash(blockRequest, out Keccak? hash); blockRequest.BlockHash = hash; diff --git a/src/Nethermind/Nethermind.Merge.Plugin.Test/EngineModuleTests.Setup.cs b/src/Nethermind/Nethermind.Merge.Plugin.Test/EngineModuleTests.Setup.cs index ba012651ea70..f7dc238563b6 100644 --- a/src/Nethermind/Nethermind.Merge.Plugin.Test/EngineModuleTests.Setup.cs +++ b/src/Nethermind/Nethermind.Merge.Plugin.Test/EngineModuleTests.Setup.cs @@ -80,12 +80,15 @@ private IEngineRpcModule CreateEngineModule(MergeTestBlockchain chain, ISyncConf return new EngineRpcModule( new GetPayloadV1Handler( chain.PayloadPreparationService!, + chain.SpecProvider!, chain.LogManager), new GetPayloadV2Handler( chain.PayloadPreparationService!, + chain.SpecProvider!, chain.LogManager), new GetPayloadV3Handler( chain.PayloadPreparationService!, + chain.SpecProvider!, chain.LogManager), new NewPayloadHandler( chain.BlockValidator, diff --git a/src/Nethermind/Nethermind.Merge.Plugin.Test/EngineModuleTests.V3.cs b/src/Nethermind/Nethermind.Merge.Plugin.Test/EngineModuleTests.V3.cs index 45ac44b43cc4..28e355fa24aa 100644 --- a/src/Nethermind/Nethermind.Merge.Plugin.Test/EngineModuleTests.V3.cs +++ b/src/Nethermind/Nethermind.Merge.Plugin.Test/EngineModuleTests.V3.cs @@ -3,6 +3,8 @@ using System; using System.Collections.Generic; +using System.IO.Abstractions; +using System.Linq; using System.Threading; using System.Threading.Tasks; using FluentAssertions; @@ -15,27 +17,79 @@ using Nethermind.Crypto; using Nethermind.Evm; using Nethermind.JsonRpc; +using Nethermind.JsonRpc.Modules; +using Nethermind.JsonRpc.Test; using Nethermind.Logging; using Nethermind.Merge.Plugin.Data; using Nethermind.Merge.Plugin.GC; using Nethermind.Merge.Plugin.Handlers; +using Nethermind.Serialization.Json; using Nethermind.Specs.Forks; +using Newtonsoft.Json.Linq; using NSubstitute; using NUnit.Framework; -using Transaction = Nethermind.Core.Transaction; namespace Nethermind.Merge.Plugin.Test; public partial class EngineModuleTests { - [TestCaseSource(nameof(ExcessDataGasInGetPayloadV3ForDifferentSpecTestSource))] - public async Task ExccessDataGas_should_present_in_cancun_only((IReleaseSpec Spec, bool IsExcessDataGasSet) input) + [Test] + public async Task NewPayloadV1_should_decline_post_cancun() + { + MergeTestBlockchain chain = await CreateBlockchain(releaseSpec: Cancun.Instance); + IEngineRpcModule rpcModule = CreateEngineModule(chain); + ExecutionPayload executionPayload = CreateBlockRequest( + CreateParentBlockRequestOnHead(chain.BlockTree), TestItem.AddressD, withdrawals: Array.Empty()); + + ResultWrapper errorCode = (await rpcModule.engine_newPayloadV1(executionPayload)); + + Assert.That(errorCode.ErrorCode, Is.EqualTo(ErrorCodes.InvalidParams)); + } + + [Test] + public async Task NewPayloadV2_should_decline_post_cancun() { - (IEngineRpcModule rpcModule, string payloadId) = await BuildAndGetPayloadV3Result(input.Spec); + MergeTestBlockchain chain = await CreateBlockchain(releaseSpec: Cancun.Instance); + IEngineRpcModule rpcModule = CreateEngineModule(chain); + ExecutionPayload executionPayload = CreateBlockRequest( + CreateParentBlockRequestOnHead(chain.BlockTree), TestItem.AddressD, withdrawals: Array.Empty()); + + ResultWrapper errorCode = (await rpcModule.engine_newPayloadV2(executionPayload)); + + Assert.That(errorCode.ErrorCode, Is.EqualTo(ErrorCodes.UnsupportedFork)); + } + + [Test] + public async Task NewPayloadV3_should_decline_pre_cancun_payloads() + { + MergeTestBlockchain chain = await CreateBlockchain(releaseSpec: Shanghai.Instance); + IEngineRpcModule rpcModule = CreateEngineModule(chain); + ExecutionPayloadV3 executionPayload = CreateBlockRequestV3( + CreateParentBlockRequestOnHead(chain.BlockTree), TestItem.AddressD, withdrawals: Array.Empty()); + + ResultWrapper errorCode = (await rpcModule.engine_newPayloadV3(executionPayload, new byte[0][])); + + Assert.That(errorCode.ErrorCode, Is.EqualTo(ErrorCodes.UnsupportedFork)); + } + + [Test] + public async Task GetPayloadV3_should_decline_pre_cancun_payloads() + { + (IEngineRpcModule rpcModule, string payloadId) = await BuildAndGetPayloadV3Result(Shanghai.Instance); ResultWrapper getPayloadResult = await rpcModule.engine_getPayloadV3(Bytes.FromHexString(payloadId)); - Assert.That(getPayloadResult.Data!.ExecutionPayload.ExcessDataGas.HasValue, - Is.EqualTo(input.IsExcessDataGasSet)); + Assert.That(getPayloadResult.ErrorCode, + Is.EqualTo(ErrorCodes.UnsupportedFork)); + } + + [Test] + public async Task GetPayloadV2_should_decline_post_cancun_payloads() + { + (IEngineRpcModule rpcModule, string payloadId) = await BuildAndGetPayloadV3Result(Cancun.Instance); + ResultWrapper getPayloadResult = + await rpcModule.engine_getPayloadV2(Bytes.FromHexString(payloadId)); + Assert.That(getPayloadResult.ErrorCode, + Is.EqualTo(ErrorCodes.UnsupportedFork)); } [Test] @@ -57,13 +111,12 @@ public async Task GetPayloadV3_should_fail_on_unknown_payload() [TestCase(2)] [TestCase(3)] [TestCase(4)] - public async Task PayloadV3_should_return_all_the_blobs(int blobTxCount) + public async Task GetPayloadV3_should_return_all_the_blobs(int blobTxCount) { (IEngineRpcModule rpcModule, string payloadId) = await BuildAndGetPayloadV3Result(Cancun.Instance, blobTxCount); var result = await rpcModule.engine_getPayloadV3(Bytes.FromHexString(payloadId)); BlobsBundleV1 getPayloadResultBlobsBundle = result.Data!.BlobsBundle!; Assert.That(result.Data.ExecutionPayload.DataGasUsed, Is.EqualTo(DataGasCalculator.CalculateDataGas(blobTxCount))); - Assert.That(result.Data.ExecutionPayload.ExcessDataGas, Is.Not.Null); Assert.That(getPayloadResultBlobsBundle.Blobs!.Length, Is.EqualTo(blobTxCount)); Assert.That(getPayloadResultBlobsBundle.Commitments!.Length, Is.EqualTo(blobTxCount)); Assert.That(getPayloadResultBlobsBundle.Proofs!.Length, Is.EqualTo(blobTxCount)); @@ -71,15 +124,79 @@ public async Task PayloadV3_should_return_all_the_blobs(int blobTxCount) [Test] public async Task NewPayloadV3_should_decline_null_blobversionedhashes() + { + + (JsonRpcService jsonRpcService, JsonRpcContext context, EthereumJsonSerializer serializer, ExecutionPayloadV3 executionPayload) + = await PreparePayloadRequestEnv(); + + string executionPayloadString = serializer.Serialize(executionPayload); + string blobsString = serializer.Serialize(Array.Empty()); + + JsonRpcRequest request = RpcTest.GetJsonRequest(nameof(IEngineRpcModule.engine_newPayloadV3), + executionPayloadString, null!); + JsonRpcErrorResponse? response = (await jsonRpcService.SendRequestAsync(request, context)) as JsonRpcErrorResponse; + Assert.That(response?.Error, Is.Not.Null); + Assert.That(response.Error.Code, Is.EqualTo(ErrorCodes.InvalidParams)); + } + + private async Task<(JsonRpcService jsonRpcService, JsonRpcContext context, EthereumJsonSerializer serializer, ExecutionPayloadV3 correctExecutionPayload)> + PreparePayloadRequestEnv() { MergeTestBlockchain chain = await CreateBlockchain(releaseSpec: Cancun.Instance); IEngineRpcModule rpcModule = CreateEngineModule(chain); - ExecutionPayload executionPayload = CreateBlockRequest( - CreateParentBlockRequestOnHead(chain.BlockTree), TestItem.AddressD, withdrawals: Array.Empty()); + JsonRpcConfig jsonRpcConfig = new() { EnabledModules = new[] { "Engine" } }; + RpcModuleProvider moduleProvider = new(new FileSystem(), jsonRpcConfig, LimboLogs.Instance); + moduleProvider.Register(new SingletonModulePool(new SingletonFactory(rpcModule), true)); + + ExecutionPayloadV3 executionPayload = CreateBlockRequestV3( + CreateParentBlockRequestOnHead(chain.BlockTree), TestItem.AddressD, withdrawals: Array.Empty(), dataGasUsed: 0, excessDataGas: 0); + + return (new(moduleProvider, LimboLogs.Instance, jsonRpcConfig), new(RpcEndpoint.Http), new(), executionPayload); + } + + [Test] + public async Task NewPayloadV3_should_decline_empty_fields() + { + (JsonRpcService jsonRpcService, JsonRpcContext context, EthereumJsonSerializer serializer, ExecutionPayloadV3 executionPayload) + = await PreparePayloadRequestEnv(); + + string executionPayloadString = serializer.Serialize(executionPayload); + string blobsString = serializer.Serialize(Array.Empty()); + + { + JObject executionPayloadAsJObject = serializer.Deserialize(executionPayloadString); + JsonRpcRequest request = RpcTest.GetJsonRequest(nameof(IEngineRpcModule.engine_newPayloadV3), + serializer.Serialize(executionPayloadAsJObject), blobsString); + JsonRpcResponse response = await jsonRpcService.SendRequestAsync(request, context); + Assert.That(response is JsonRpcSuccessResponse); + } + + string[] props = serializer.Deserialize(serializer.Serialize(new ExecutionPayload())) + .Properties().Select(prop => prop.Name).ToArray(); - ResultWrapper errorCode = (await rpcModule.engine_newPayloadV3(executionPayload, null!)); + foreach (string prop in props) + { + JObject executionPayloadAsJObject = serializer.Deserialize(executionPayloadString); + executionPayloadAsJObject[prop] = null; + + JsonRpcRequest request = RpcTest.GetJsonRequest(nameof(IEngineRpcModule.engine_newPayloadV3), + serializer.Serialize(executionPayloadAsJObject), blobsString); + JsonRpcErrorResponse? response = (await jsonRpcService.SendRequestAsync(request, context)) as JsonRpcErrorResponse; + Assert.That(response?.Error, Is.Not.Null); + Assert.That(response.Error.Code, Is.EqualTo(ErrorCodes.InvalidParams)); + } - Assert.That(errorCode.Data.Status, Is.EqualTo(PayloadStatus.Invalid)); + foreach (string prop in props) + { + JObject executionPayloadAsJObject = serializer.Deserialize(executionPayloadString); + executionPayloadAsJObject.Remove(prop); + + JsonRpcRequest request = RpcTest.GetJsonRequest(nameof(IEngineRpcModule.engine_newPayloadV3), + serializer.Serialize(executionPayloadAsJObject), blobsString); + JsonRpcErrorResponse? response = (await jsonRpcService.SendRequestAsync(request, context)) as JsonRpcErrorResponse; + Assert.That(response?.Error, Is.Not.Null); + Assert.That(response.Error.Code, Is.EqualTo(ErrorCodes.InvalidParams)); + } } private const string FurtherValidationStatus = "FurtherValidation"; @@ -162,8 +279,8 @@ public async Task NewPayloadV3_should_verify_blob_versioned_hashes_again (MergeTestBlockchain blockchain, IEngineRpcModule engineRpcModule) = await MockRpc(); (byte[][] blobVersionedHashes, Transaction[] transactions) = BuildTransactionsAndBlobVersionedHashesList(hashesFirstBytes, transactionsAndFirstBytesOfTheirHashes, blockchain.SpecProvider.ChainId); - ExecutionPayload executionPayload = CreateBlockRequest( - CreateParentBlockRequestOnHead(blockchain.BlockTree), TestItem.AddressD, withdrawals: Array.Empty(), transactions: transactions); + ExecutionPayloadV3 executionPayload = CreateBlockRequestV3( + CreateParentBlockRequestOnHead(blockchain.BlockTree), TestItem.AddressD, withdrawals: Array.Empty(), 0, 0, transactions: transactions); ResultWrapper result = await engineRpcModule.engine_newPayloadV3(executionPayload, blobVersionedHashes); return result.Data.Status; @@ -225,7 +342,7 @@ public static IEnumerable BlobVersionedHashesDoNotMatchTestSource private async Task SendNewBlockV3(IEngineRpcModule rpc, MergeTestBlockchain chain, IList? withdrawals) { - ExecutionPayload executionPayload = CreateBlockRequest( + ExecutionPayloadV3 executionPayload = CreateBlockRequestV3( CreateParentBlockRequestOnHead(chain.BlockTree), TestItem.AddressD, withdrawals, 0, 0); ResultWrapper executePayloadResult = await rpc.engine_newPayloadV3(executionPayload, Array.Empty()); @@ -268,10 +385,4 @@ private async Task SendNewBlockV3(IEngineRpcModule rpc, MergeT .PayloadId!; return (rpcModule, payloadId); } - - protected static IEnumerable<(IReleaseSpec Spec, bool IsExcessDataGasSet)> ExcessDataGasInGetPayloadV3ForDifferentSpecTestSource() - { - yield return (Shanghai.Instance, false); - yield return (Cancun.Instance, true); - } } diff --git a/src/Nethermind/Nethermind.Merge.Plugin/Data/ExecutionPayload.cs b/src/Nethermind/Nethermind.Merge.Plugin/Data/ExecutionPayload.cs index 2c24119198f7..445b63a1fa74 100644 --- a/src/Nethermind/Nethermind.Merge.Plugin/Data/ExecutionPayload.cs +++ b/src/Nethermind/Nethermind.Merge.Plugin/Data/ExecutionPayload.cs @@ -9,17 +9,16 @@ using Nethermind.Core.Crypto; using Nethermind.Core.Specs; using Nethermind.Int256; -using Nethermind.Serialization.Json; +using Nethermind.Merge.Plugin.Handlers; using Nethermind.Serialization.Rlp; using Nethermind.State.Proofs; -using Newtonsoft.Json; namespace Nethermind.Merge.Plugin.Data; /// /// Represents an object mapping the ExecutionPayload structure of the beacon chain spec. /// -public class ExecutionPayload +public class ExecutionPayload : IForkValidator, IExecutionPayloadParams { public ExecutionPayload() { } // Needed for tests @@ -39,8 +38,6 @@ public ExecutionPayload(Block block) Timestamp = block.Timestamp; BaseFeePerGas = block.BaseFeePerGas; Withdrawals = block.Withdrawals; - DataGasUsed = block.DataGasUsed; - ExcessDataGas = block.ExcessDataGas; SetTransactions(block.Transactions); } @@ -94,20 +91,6 @@ public byte[][] Transactions /// public IEnumerable? Withdrawals { get; set; } - /// - /// Gets or sets as defined in - /// EIP-4844. - /// - [JsonProperty(ItemConverterType = typeof(NullableUInt256Converter), NullValueHandling = NullValueHandling.Ignore)] - public ulong? DataGasUsed { get; set; } - - /// - /// Gets or sets as defined in - /// EIP-4844. - /// - [JsonProperty(ItemConverterType = typeof(NullableUInt256Converter), NullValueHandling = NullValueHandling.Ignore)] - public ulong? ExcessDataGas { get; set; } - /// /// Creates the execution block from payload. @@ -128,9 +111,7 @@ public virtual bool TryGetBlock(out Block? block, UInt256? totalDifficulty = nul BlockNumber, GasLimit, Timestamp, - ExtraData, - DataGasUsed, - ExcessDataGas) + ExtraData) { Hash = BlockHash, ReceiptsRoot = ReceiptsRoot, @@ -159,6 +140,7 @@ public virtual bool TryGetBlock(out Block? block, UInt256? totalDifficulty = nul } } + private Transaction[]? _transactions = null; /// @@ -182,20 +164,20 @@ public void SetTransactions(params Transaction[] transactions) } public override string ToString() => $"{BlockNumber} ({BlockHash.ToShortString()})"; -} -public static class ExecutionPayloadExtensions -{ - public static int GetVersion(this ExecutionPayload executionPayload) => - executionPayload.Withdrawals is null ? 1 : 2; - - public static bool Validate( - this ExecutionPayload executionPayload, - IReleaseSpec spec, - int version, - [NotNullWhen(false)] out string? error) + ExecutionPayload IExecutionPayloadParams.ExecutionPayload => this; + + public virtual ValidationResult ValidateParams(IReleaseSpec spec, int version, out string? error) { - int actualVersion = executionPayload.GetVersion(); + int GetVersion() => Withdrawals is null ? 1 : 2; + + if (spec.IsEip4844Enabled) + { + error = "ExecutionPayloadV3 expected"; + return ValidationResult.Fail; + } + + int actualVersion = GetVersion(); error = actualVersion switch { @@ -204,15 +186,9 @@ public static bool Validate( _ => actualVersion > version ? $"ExecutionPayloadV{version} expected" : null }; - return error is null; + return error is null ? ValidationResult.Success : ValidationResult.Fail; } - public static bool Validate(this ExecutionPayload executionPayload, - ISpecProvider specProvider, - int version, - [NotNullWhen(false)] out string? error) => - executionPayload.Validate( - specProvider.GetSpec(executionPayload.BlockNumber, executionPayload.Timestamp), - version, - out error); + public virtual bool ValidateFork(ISpecProvider specProvider) => + !specProvider.GetSpec(BlockNumber, Timestamp).IsEip4844Enabled; } diff --git a/src/Nethermind/Nethermind.Merge.Plugin/Data/ExecutionPayloadV3.cs b/src/Nethermind/Nethermind.Merge.Plugin/Data/ExecutionPayloadV3.cs new file mode 100644 index 000000000000..94f7170cd02f --- /dev/null +++ b/src/Nethermind/Nethermind.Merge.Plugin/Data/ExecutionPayloadV3.cs @@ -0,0 +1,52 @@ +// SPDX-FileCopyrightText: 2022 Demerzel Solutions Limited +// SPDX-License-Identifier: LGPL-3.0-only + +using System.Diagnostics.CodeAnalysis; +using Nethermind.Core; +using Nethermind.Core.Specs; +using Nethermind.Int256; +using Newtonsoft.Json; + +namespace Nethermind.Merge.Plugin.Data; + +/// +/// Represents an object mapping the ExecutionPayloadV3 structure of the beacon chain spec. +/// +[JsonObject(ItemRequired = Required.Always)] +public class ExecutionPayloadV3 : ExecutionPayload +{ + public ExecutionPayloadV3() { } // Needed for tests + + public ExecutionPayloadV3(Block block) : base(block) + { + DataGasUsed = block.DataGasUsed; + ExcessDataGas = block.ExcessDataGas; + } + + /// + /// Gets or sets as defined in + /// EIP-4844. + /// + public ulong? DataGasUsed { get; set; } + + /// + /// Gets or sets as defined in + /// EIP-4844. + /// + public ulong? ExcessDataGas { get; set; } + + public override bool TryGetBlock(out Block? block, UInt256? totalDifficulty = null) + { + if (!base.TryGetBlock(out block, totalDifficulty)) + { + return false; + } + + block!.Header.DataGasUsed = DataGasUsed; + block.Header.ExcessDataGas = ExcessDataGas; + return true; + } + + public override bool ValidateFork(ISpecProvider specProvider) => + specProvider.GetSpec(BlockNumber, Timestamp).IsEip4844Enabled; +} diff --git a/src/Nethermind/Nethermind.Merge.Plugin/Data/GetPayloadV2Result.cs b/src/Nethermind/Nethermind.Merge.Plugin/Data/GetPayloadV2Result.cs index 1d261ca65d2b..78fffe38a86f 100644 --- a/src/Nethermind/Nethermind.Merge.Plugin/Data/GetPayloadV2Result.cs +++ b/src/Nethermind/Nethermind.Merge.Plugin/Data/GetPayloadV2Result.cs @@ -2,12 +2,13 @@ // SPDX-License-Identifier: LGPL-3.0-only using Nethermind.Core; +using Nethermind.Core.Specs; using Nethermind.Int256; -using Newtonsoft.Json; +using Nethermind.Merge.Plugin.Handlers; namespace Nethermind.Merge.Plugin.Data; -public class GetPayloadV2Result +public class GetPayloadV2Result : IForkValidator { public GetPayloadV2Result(Block block, UInt256 blockFees) { @@ -17,7 +18,9 @@ public GetPayloadV2Result(Block block, UInt256 blockFees) public UInt256 BlockValue { get; } - public ExecutionPayload ExecutionPayload { get; } + public virtual ExecutionPayload ExecutionPayload { get; } + + public bool ValidateFork(ISpecProvider specProvider) => ExecutionPayload.ValidateFork(specProvider); public override string ToString() => $"{{ExecutionPayload: {ExecutionPayload}, Fees: {BlockValue}}}"; } diff --git a/src/Nethermind/Nethermind.Merge.Plugin/Data/GetPayloadV3Result.cs b/src/Nethermind/Nethermind.Merge.Plugin/Data/GetPayloadV3Result.cs index b3730948fad1..c97b48c06f08 100644 --- a/src/Nethermind/Nethermind.Merge.Plugin/Data/GetPayloadV3Result.cs +++ b/src/Nethermind/Nethermind.Merge.Plugin/Data/GetPayloadV3Result.cs @@ -10,11 +10,14 @@ public class GetPayloadV3Result : GetPayloadV2Result { public GetPayloadV3Result(Block block, UInt256 blockFees, BlobsBundleV1 blobsBundle) : base(block, blockFees) { + ExecutionPayload = new(block); BlobsBundle = blobsBundle; } public BlobsBundleV1 BlobsBundle { get; } + public override ExecutionPayloadV3 ExecutionPayload { get; } + public override string ToString() => $"{{ExecutionPayload: {ExecutionPayload}, Fees: {BlockValue}, BlobsBundle blobs count: {BlobsBundle.Blobs.Length}}}"; } diff --git a/src/Nethermind/Nethermind.Merge.Plugin/Data/IExecutionPayloadParams.cs b/src/Nethermind/Nethermind.Merge.Plugin/Data/IExecutionPayloadParams.cs new file mode 100644 index 000000000000..51723767251a --- /dev/null +++ b/src/Nethermind/Nethermind.Merge.Plugin/Data/IExecutionPayloadParams.cs @@ -0,0 +1,49 @@ +// SPDX-FileCopyrightText: 2023 Demerzel Solutions Limited +// SPDX-License-Identifier: LGPL-3.0-only + +using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; +using System.Linq; +using Nethermind.Core.Extensions; +using Nethermind.Core.Specs; + +namespace Nethermind.Merge.Plugin.Data; + +public interface IExecutionPayloadParams +{ + ExecutionPayload ExecutionPayload { get; } + ValidationResult ValidateParams(IReleaseSpec spec, int version, out string? error); +} + +public enum ValidationResult : byte { Success, Fail, Invalid }; + +public class ExecutionPayloadV3Params : IExecutionPayloadParams +{ + private readonly ExecutionPayloadV3 _executionPayload; + private readonly byte[]?[] _blobVersionedHashes; + + public ExecutionPayloadV3Params(ExecutionPayloadV3 executionPayload, byte[]?[] blobVersionedHashes) + { + _executionPayload = executionPayload; + _blobVersionedHashes = blobVersionedHashes; + } + + public ExecutionPayload ExecutionPayload => _executionPayload; + + public ValidationResult ValidateParams(IReleaseSpec spec, int version, out string? error) + { + static IEnumerable FlattenHashesFromTransactions(ExecutionPayloadV3 payload) => + payload.GetTransactions() + .Where(t => t.BlobVersionedHashes is not null) + .SelectMany(t => t.BlobVersionedHashes!); + + if (FlattenHashesFromTransactions(_executionPayload).SequenceEqual(_blobVersionedHashes, Bytes.NullableEqualityComparer)) + { + error = null; + return ValidationResult.Success; + } + + error = "Blob versioned hashes do not match"; + return ValidationResult.Invalid; + } +} diff --git a/src/Nethermind/Nethermind.Merge.Plugin/Data/PayloadStatusV1.cs b/src/Nethermind/Nethermind.Merge.Plugin/Data/PayloadStatusV1.cs index c92f8b9475d2..8a27a65fb105 100644 --- a/src/Nethermind/Nethermind.Merge.Plugin/Data/PayloadStatusV1.cs +++ b/src/Nethermind/Nethermind.Merge.Plugin/Data/PayloadStatusV1.cs @@ -17,10 +17,11 @@ public class PayloadStatusV1 public static readonly PayloadStatusV1 Accepted = new() { Status = PayloadStatus.Accepted }; - public static PayloadStatusV1 Invalid(Keccak? latestValidHash) => new() + public static PayloadStatusV1 Invalid(Keccak? latestValidHash, string? validationError = null) => new() { Status = PayloadStatus.Invalid, - LatestValidHash = latestValidHash + LatestValidHash = latestValidHash, + ValidationError = validationError }; /// diff --git a/src/Nethermind/Nethermind.Merge.Plugin/EngineRpcModule.Cancun.cs b/src/Nethermind/Nethermind.Merge.Plugin/EngineRpcModule.Cancun.cs index 0e99f729adc2..41aa57d7b61b 100644 --- a/src/Nethermind/Nethermind.Merge.Plugin/EngineRpcModule.Cancun.cs +++ b/src/Nethermind/Nethermind.Merge.Plugin/EngineRpcModule.Cancun.cs @@ -1,10 +1,10 @@ // SPDX-FileCopyrightText: 2022 Demerzel Solutions Limited // SPDX-License-Identifier: LGPL-3.0-only +using System; using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; -using Nethermind.Core; using Nethermind.Core.Extensions; using Nethermind.JsonRpc; using Nethermind.Merge.Plugin.Data; @@ -16,32 +16,8 @@ public partial class EngineRpcModule : IEngineRpcModule { private readonly IAsyncHandler _getPayloadHandlerV3; - public Task> engine_newPayloadV3(ExecutionPayload executionPayload, byte[]?[]? blobVersionedHashes = null) => - Validate(executionPayload, blobVersionedHashes) ?? NewPayload(executionPayload, 3); - - private ResultWrapper? Validate(ExecutionPayload executionPayload, byte[]?[]? blobVersionedHashes) - { - ResultWrapper ErrorResult(string error) - { - if (_logger.IsWarn) _logger.Warn(error); - return ResultWrapper.Success( - new PayloadStatusV1 - { - Status = PayloadStatus.Invalid, - LatestValidHash = null, - ValidationError = error - }); - } - - static IEnumerable FlattenHashesFromTransactions(ExecutionPayload payload) => - payload.GetTransactions() - .Where(t => t.BlobVersionedHashes is not null) - .SelectMany(t => t.BlobVersionedHashes!); - - return blobVersionedHashes is null ? ErrorResult("Blob versioned hashes must be set") - : !FlattenHashesFromTransactions(executionPayload).SequenceEqual(blobVersionedHashes, Bytes.NullableEqualityComparer) ? ErrorResult("Blob versioned hashes do not match") - : null; - } + public Task> engine_newPayloadV3(ExecutionPayloadV3 executionPayload, byte[]?[] blobVersionedHashes) => + NewPayload(new ExecutionPayloadV3Params(executionPayload, blobVersionedHashes), 3); public async Task> engine_getPayloadV3(byte[] payloadId) => await _getPayloadHandlerV3.HandleAsync(payloadId); diff --git a/src/Nethermind/Nethermind.Merge.Plugin/EngineRpcModule.Paris.cs b/src/Nethermind/Nethermind.Merge.Plugin/EngineRpcModule.Paris.cs index 3bb0ca79dc62..585af416360c 100644 --- a/src/Nethermind/Nethermind.Merge.Plugin/EngineRpcModule.Paris.cs +++ b/src/Nethermind/Nethermind.Merge.Plugin/EngineRpcModule.Paris.cs @@ -6,6 +6,7 @@ using System.Threading; using System.Threading.Tasks; using Nethermind.Consensus.Producers; +using Nethermind.Core.Specs; using Nethermind.JsonRpc; using Nethermind.Merge.Plugin.Data; using Nethermind.Merge.Plugin.GC; @@ -64,12 +65,24 @@ private async Task> ForkchoiceUpdated(F } } - private async Task> NewPayload(ExecutionPayload executionPayload, int version) + private async Task> NewPayload(IExecutionPayloadParams executionPayloadParams, int version) { - if (!executionPayload.Validate(_specProvider, version, out string? error)) + ExecutionPayload executionPayload = executionPayloadParams.ExecutionPayload; + + if (!executionPayload.ValidateFork(_specProvider)) + { + if (_logger.IsWarn) _logger.Warn($"The payload is not supported by the current fork"); + return ResultWrapper.Fail("unsupported fork", version < 2 ? ErrorCodes.InvalidParams : ErrorCodes.UnsupportedFork); + } + + IReleaseSpec releaseSpec = _specProvider.GetSpec(executionPayload.BlockNumber, executionPayload.Timestamp); + ValidationResult validationResult = executionPayloadParams.ValidateParams(releaseSpec, version, out string? error); + if (validationResult != ValidationResult.Success) { if (_logger.IsWarn) _logger.Warn(error); - return ResultWrapper.Fail(error, ErrorCodes.InvalidParams); + return validationResult == ValidationResult.Fail + ? ResultWrapper.Fail(error!, ErrorCodes.InvalidParams) + : ResultWrapper.Success(PayloadStatusV1.Invalid(null, error)); } if (await _locker.WaitAsync(_timeout)) diff --git a/src/Nethermind/Nethermind.Merge.Plugin/Handlers/GetPayloadHandlerBase.cs b/src/Nethermind/Nethermind.Merge.Plugin/Handlers/GetPayloadHandlerBase.cs index 501a68167074..5e04823bfe6c 100644 --- a/src/Nethermind/Nethermind.Merge.Plugin/Handlers/GetPayloadHandlerBase.cs +++ b/src/Nethermind/Nethermind.Merge.Plugin/Handlers/GetPayloadHandlerBase.cs @@ -4,23 +4,26 @@ using System.Threading.Tasks; using Nethermind.Core; using Nethermind.Core.Extensions; +using Nethermind.Core.Specs; using Nethermind.JsonRpc; using Nethermind.Logging; using Nethermind.Merge.Plugin.BlockProduction; -using Nethermind.Merge.Plugin.Data; namespace Nethermind.Merge.Plugin.Handlers; -public abstract class GetPayloadHandlerBase : IAsyncHandler +public abstract class GetPayloadHandlerBase : IAsyncHandler where TGetPayloadResult : IForkValidator { private readonly int _apiVersion; private readonly IPayloadPreparationService _payloadPreparationService; + private readonly ISpecProvider _specProvider; private readonly ILogger _logger; - protected GetPayloadHandlerBase(int apiVersion, IPayloadPreparationService payloadPreparationService, ILogManager logManager) + protected GetPayloadHandlerBase(int apiVersion, IPayloadPreparationService payloadPreparationService, + ISpecProvider specProvider, ILogManager logManager) { _apiVersion = apiVersion; _payloadPreparationService = payloadPreparationService; + _specProvider = specProvider; _logger = logManager.GetClassLogger(); } @@ -37,11 +40,19 @@ protected GetPayloadHandlerBase(int apiVersion, IPayloadPreparationService paylo return ResultWrapper.Fail("unknown payload", MergeErrorCodes.UnknownPayload); } + TGetPayloadResult getPayloadResult = GetPayloadResultFromBlock(blockContext); + + if (!getPayloadResult.ValidateFork(_specProvider)) + { + if (_logger.IsWarn) _logger.Warn($"The payload is not supported by the current fork"); + return ResultWrapper.Fail("unsupported fork", ErrorCodes.UnsupportedFork); + } + if (_logger.IsInfo) _logger.Info($"GetPayloadV{_apiVersion} result: {block.Header.ToString(BlockHeader.Format.Full)}."); Metrics.GetPayloadRequests++; Metrics.NumberOfTransactionsInGetPayload = block.Transactions.Length; - return ResultWrapper.Success(GetPayloadResultFromBlock(blockContext)); + return ResultWrapper.Success(getPayloadResult); } protected abstract TGetPayloadResult GetPayloadResultFromBlock(IBlockProductionContext blockProductionContext); diff --git a/src/Nethermind/Nethermind.Merge.Plugin/Handlers/GetPayloadV1Handler.cs b/src/Nethermind/Nethermind.Merge.Plugin/Handlers/GetPayloadV1Handler.cs index 295859ae19a0..20a27945f9b3 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.Core.Specs; using Nethermind.Logging; using Nethermind.Merge.Plugin.BlockProduction; using Nethermind.Merge.Plugin.Data; @@ -24,8 +25,8 @@ namespace Nethermind.Merge.Plugin.Handlers; /// public class GetPayloadV1Handler : GetPayloadHandlerBase { - public GetPayloadV1Handler(IPayloadPreparationService payloadPreparationService, ILogManager logManager) : base( - 1, payloadPreparationService, logManager) + public GetPayloadV1Handler(IPayloadPreparationService payloadPreparationService, ISpecProvider specProvider, ILogManager logManager) : base( + 1, payloadPreparationService, specProvider, logManager) { } diff --git a/src/Nethermind/Nethermind.Merge.Plugin/Handlers/GetPayloadV2Handler.cs b/src/Nethermind/Nethermind.Merge.Plugin/Handlers/GetPayloadV2Handler.cs index 2932fe02cc02..1fd7b21355b4 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.Core.Specs; using Nethermind.Logging; using Nethermind.Merge.Plugin.BlockProduction; using Nethermind.Merge.Plugin.Data; @@ -13,8 +14,8 @@ namespace Nethermind.Merge.Plugin.Handlers; /// public class GetPayloadV2Handler : GetPayloadHandlerBase { - public GetPayloadV2Handler(IPayloadPreparationService payloadPreparationService, ILogManager logManager) : base( - 2, payloadPreparationService, logManager) + public GetPayloadV2Handler(IPayloadPreparationService payloadPreparationService, ISpecProvider specProvider, ILogManager logManager) : base( + 2, payloadPreparationService, specProvider, logManager) { } diff --git a/src/Nethermind/Nethermind.Merge.Plugin/Handlers/GetPayloadV3Handler.cs b/src/Nethermind/Nethermind.Merge.Plugin/Handlers/GetPayloadV3Handler.cs index dd1f0ba962c4..9555b51f1a9b 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: 2022 Demerzel Solutions Limited // SPDX-License-Identifier: LGPL-3.0-only +using Nethermind.Core.Specs; using Nethermind.Logging; using Nethermind.Merge.Plugin.BlockProduction; using Nethermind.Merge.Plugin.Data; @@ -13,8 +14,8 @@ namespace Nethermind.Merge.Plugin.Handlers; /// public class GetPayloadV3Handler : GetPayloadHandlerBase { - public GetPayloadV3Handler(IPayloadPreparationService payloadPreparationService, ILogManager logManager) : base( - 3, payloadPreparationService, logManager) + public GetPayloadV3Handler(IPayloadPreparationService payloadPreparationService, ISpecProvider specProvider, ILogManager logManager) : base( + 3, payloadPreparationService, specProvider, logManager) { } diff --git a/src/Nethermind/Nethermind.Merge.Plugin/Handlers/IForkValidator.cs b/src/Nethermind/Nethermind.Merge.Plugin/Handlers/IForkValidator.cs new file mode 100644 index 000000000000..ea0592f9c9ff --- /dev/null +++ b/src/Nethermind/Nethermind.Merge.Plugin/Handlers/IForkValidator.cs @@ -0,0 +1,11 @@ +// SPDX-FileCopyrightText: 2023 Demerzel Solutions Limited +// SPDX-License-Identifier: LGPL-3.0-only + +using Nethermind.Core.Specs; + +namespace Nethermind.Merge.Plugin.Handlers; + +public interface IForkValidator +{ + bool ValidateFork(ISpecProvider specProvider); +} diff --git a/src/Nethermind/Nethermind.Merge.Plugin/IEngineRpcModule.Cancun.cs b/src/Nethermind/Nethermind.Merge.Plugin/IEngineRpcModule.Cancun.cs index 43eb090084a8..f22b0bcb5f7e 100644 --- a/src/Nethermind/Nethermind.Merge.Plugin/IEngineRpcModule.Cancun.cs +++ b/src/Nethermind/Nethermind.Merge.Plugin/IEngineRpcModule.Cancun.cs @@ -14,7 +14,7 @@ public partial interface IEngineRpcModule : IRpcModule Description = "Verifies the payload according to the execution environment rules and returns the verification status and hash of the last valid block.", IsSharable = true, IsImplemented = true)] - Task> engine_newPayloadV3(ExecutionPayload executionPayload, byte[][]? blobVersionedHashes = null); + Task> engine_newPayloadV3(ExecutionPayloadV3 executionPayload, byte[]?[] blobVersionedHashes); [JsonRpcMethod( Description = "Returns the most recent version of an execution payload and fees with respect to the transaction set contained by the mempool.", diff --git a/src/Nethermind/Nethermind.Merge.Plugin/MergePlugin.cs b/src/Nethermind/Nethermind.Merge.Plugin/MergePlugin.cs index 5aa639b66625..ecabc6581d8b 100644 --- a/src/Nethermind/Nethermind.Merge.Plugin/MergePlugin.cs +++ b/src/Nethermind/Nethermind.Merge.Plugin/MergePlugin.cs @@ -307,9 +307,9 @@ public Task InitRpcModules() _api.RpcCapabilitiesProvider = new EngineRpcCapabilitiesProvider(_api.SpecProvider); IEngineRpcModule engineRpcModule = new EngineRpcModule( - new GetPayloadV1Handler(payloadPreparationService, _api.LogManager), - new GetPayloadV2Handler(payloadPreparationService, _api.LogManager), - new GetPayloadV3Handler(payloadPreparationService, _api.LogManager), + new GetPayloadV1Handler(payloadPreparationService, _api.SpecProvider, _api.LogManager), + new GetPayloadV2Handler(payloadPreparationService, _api.SpecProvider, _api.LogManager), + new GetPayloadV3Handler(payloadPreparationService, _api.SpecProvider, _api.LogManager), new NewPayloadHandler( _api.BlockValidator, _api.BlockTree,