Skip to content
4 changes: 4 additions & 0 deletions src/Nethermind/Nethermind.JsonRpc/ErrorType.cs
Original file line number Diff line number Diff line change
Expand Up @@ -87,5 +87,9 @@ public static class ErrorCodes
/// </summary>
public const int UnknownBlockError = -39001;

/// <summary>
/// Unsupported fork error
/// </summary>
public const int UnsupportedFork = -38005;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -84,9 +84,15 @@ private ExecutionPayload CreateParentBlockRequestOnHead(IBlockTree blockTree)
};
}

private static ExecutionPayload CreateBlockRequest(ExecutionPayload parent, Address miner, IList<Withdrawal>? withdrawals = null, ulong? dataGasUsed = null, ulong? excessDataGas = null, Transaction[]? transactions = null)
private static ExecutionPayload CreateBlockRequest(ExecutionPayload parent, Address miner, IList<Withdrawal>? withdrawals = null, Transaction[]? transactions = null)
Comment thread
flcl42 marked this conversation as resolved.
=> CreateBlockRequestInternal<ExecutionPayload>(parent, miner, withdrawals, transactions: transactions);

private static ExecutionPayloadV3 CreateBlockRequestV3(ExecutionPayload parent, Address miner, IList<Withdrawal>? withdrawals = null, ulong? dataGasUsed = null, ulong? excessDataGas = null, Transaction[]? transactions = null)
=> CreateBlockRequestInternal<ExecutionPayloadV3>(parent, miner, withdrawals, dataGasUsed, excessDataGas, transactions: transactions);

private static T CreateBlockRequestInternal<T>(ExecutionPayload parent, Address miner, IList<Withdrawal>? 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,
Expand All @@ -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;
}

Comment thread
flcl42 marked this conversation as resolved.
blockRequest.SetTransactions(transactions ?? Array.Empty<Transaction>());
TryCalculateHash(blockRequest, out Keccak? hash);
blockRequest.BlockHash = hash;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
153 changes: 132 additions & 21 deletions src/Nethermind/Nethermind.Merge.Plugin.Test/EngineModuleTests.V3.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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<Withdrawal>());

ResultWrapper<PayloadStatusV1> 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<Withdrawal>());

ResultWrapper<PayloadStatusV1> 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<Withdrawal>());

ResultWrapper<PayloadStatusV1> 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<GetPayloadV3Result?> 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()
Comment thread
flcl42 marked this conversation as resolved.
{
(IEngineRpcModule rpcModule, string payloadId) = await BuildAndGetPayloadV3Result(Cancun.Instance);
ResultWrapper<GetPayloadV2Result?> getPayloadResult =
await rpcModule.engine_getPayloadV2(Bytes.FromHexString(payloadId));
Assert.That(getPayloadResult.ErrorCode,
Is.EqualTo(ErrorCodes.UnsupportedFork));
}

[Test]
Expand All @@ -57,29 +111,92 @@ 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));
}

[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<byte[]>());

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<Withdrawal>());
JsonRpcConfig jsonRpcConfig = new() { EnabledModules = new[] { "Engine" } };
RpcModuleProvider moduleProvider = new(new FileSystem(), jsonRpcConfig, LimboLogs.Instance);
moduleProvider.Register(new SingletonModulePool<IEngineRpcModule>(new SingletonFactory<IEngineRpcModule>(rpcModule), true));

ExecutionPayloadV3 executionPayload = CreateBlockRequestV3(
CreateParentBlockRequestOnHead(chain.BlockTree), TestItem.AddressD, withdrawals: Array.Empty<Withdrawal>(), 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<byte[]>());

{
JObject executionPayloadAsJObject = serializer.Deserialize<JObject>(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<JObject>(serializer.Serialize(new ExecutionPayload()))
.Properties().Select(prop => prop.Name).ToArray();

ResultWrapper<PayloadStatusV1> errorCode = (await rpcModule.engine_newPayloadV3(executionPayload, null!));
foreach (string prop in props)
{
JObject executionPayloadAsJObject = serializer.Deserialize<JObject>(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<JObject>(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));
}
Comment thread
flcl42 marked this conversation as resolved.
}

private const string FurtherValidationStatus = "FurtherValidation";
Expand Down Expand Up @@ -162,8 +279,8 @@ public async Task<string> 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<Withdrawal>(), transactions: transactions);
ExecutionPayloadV3 executionPayload = CreateBlockRequestV3(
CreateParentBlockRequestOnHead(blockchain.BlockTree), TestItem.AddressD, withdrawals: Array.Empty<Withdrawal>(), 0, 0, transactions: transactions);
ResultWrapper<PayloadStatusV1> result = await engineRpcModule.engine_newPayloadV3(executionPayload, blobVersionedHashes);

return result.Data.Status;
Expand Down Expand Up @@ -225,7 +342,7 @@ public static IEnumerable<TestCaseData> BlobVersionedHashesDoNotMatchTestSource

private async Task<ExecutionPayload> SendNewBlockV3(IEngineRpcModule rpc, MergeTestBlockchain chain, IList<Withdrawal>? withdrawals)
{
ExecutionPayload executionPayload = CreateBlockRequest(
ExecutionPayloadV3 executionPayload = CreateBlockRequestV3(
CreateParentBlockRequestOnHead(chain.BlockTree), TestItem.AddressD, withdrawals, 0, 0);
ResultWrapper<PayloadStatusV1> executePayloadResult = await rpc.engine_newPayloadV3(executionPayload, Array.Empty<byte[]>());

Expand Down Expand Up @@ -268,10 +385,4 @@ private async Task<ExecutionPayload> 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);
}
}
Loading