From 482366b1b59a79203fc9af4f2126abd13a4b4635 Mon Sep 17 00:00:00 2001 From: Marcin Sobczak Date: Mon, 8 Dec 2025 09:36:13 +0100 Subject: [PATCH 01/14] PoC --- .../Nethermind.JsonRpc/Modules/ModuleType.cs | 11 +- .../ITestingRpcModule.cs | 23 ++++ .../Nethermind.Merge.Plugin/MergePlugin.cs | 3 + .../TestingRpcModule.cs | 100 ++++++++++++++++++ 4 files changed, 132 insertions(+), 5 deletions(-) create mode 100644 src/Nethermind/Nethermind.Merge.Plugin/ITestingRpcModule.cs create mode 100644 src/Nethermind/Nethermind.Merge.Plugin/TestingRpcModule.cs diff --git a/src/Nethermind/Nethermind.JsonRpc/Modules/ModuleType.cs b/src/Nethermind/Nethermind.JsonRpc/Modules/ModuleType.cs index a93b8fdc203..e435a783c5a 100644 --- a/src/Nethermind/Nethermind.JsonRpc/Modules/ModuleType.cs +++ b/src/Nethermind/Nethermind.JsonRpc/Modules/ModuleType.cs @@ -9,27 +9,28 @@ public static class ModuleType { public const string Admin = nameof(Admin); public const string Clique = nameof(Clique); - public const string Engine = nameof(Engine); public const string Db = nameof(Db); public const string Debug = nameof(Debug); + public const string Deposit = nameof(Deposit); + public const string Engine = nameof(Engine); public const string Erc20 = nameof(Erc20); public const string Eth = nameof(Eth); public const string Evm = nameof(Evm); public const string Flashbots = nameof(Flashbots); + public const string Health = nameof(Health); public const string Net = nameof(Net); public const string Nft = nameof(Nft); public const string Parity = nameof(Parity); public const string Personal = nameof(Personal); public const string Proof = nameof(Proof); + public const string Rbuilder = nameof(Rbuilder); + public const string Rpc = nameof(Rpc); public const string Subscribe = nameof(Subscribe); + public const string Testing = nameof(Testing); public const string Trace = nameof(Trace); public const string TxPool = nameof(TxPool); public const string Web3 = nameof(Web3); public const string Vault = nameof(Vault); - public const string Deposit = nameof(Deposit); - public const string Health = nameof(Health); - public const string Rpc = nameof(Rpc); - public const string Rbuilder = nameof(Rbuilder); public static IEnumerable DefaultModules { get; } = new List() { diff --git a/src/Nethermind/Nethermind.Merge.Plugin/ITestingRpcModule.cs b/src/Nethermind/Nethermind.Merge.Plugin/ITestingRpcModule.cs new file mode 100644 index 00000000000..7fc24e8bb50 --- /dev/null +++ b/src/Nethermind/Nethermind.Merge.Plugin/ITestingRpcModule.cs @@ -0,0 +1,23 @@ +// SPDX-FileCopyrightText: 2025 Demerzel Solutions Limited +// SPDX-License-Identifier: LGPL-3.0-only + +using System.Collections.Generic; +using System.Threading.Tasks; +using Nethermind.Consensus.Producers; +using Nethermind.Core.Crypto; +using Nethermind.JsonRpc; +using Nethermind.JsonRpc.Modules; +using Nethermind.Merge.Plugin.Data; + +namespace Nethermind.Merge.Plugin; + +[RpcModule(ModuleType.Testing)] +public interface ITestingRpcModule : IRpcModule +{ + [JsonRpcMethod( + Description = "Building a block from provided transactions, under provided rules.", + IsSharable = true, + IsImplemented = true)] + + public Task> testing_buildBlockV1(Hash256 parentBlockHash, PayloadAttributes payloadAttributes, IEnumerable txRlps, byte[]? extraData); +} diff --git a/src/Nethermind/Nethermind.Merge.Plugin/MergePlugin.cs b/src/Nethermind/Nethermind.Merge.Plugin/MergePlugin.cs index 5a350188359..8e13171b674 100644 --- a/src/Nethermind/Nethermind.Merge.Plugin/MergePlugin.cs +++ b/src/Nethermind/Nethermind.Merge.Plugin/MergePlugin.cs @@ -339,6 +339,9 @@ protected override void Load(ContainerBuilder builder) : NoGCStrategy.Instance, ctx.Resolve()); }) + + // Testing rpc + .RegisterSingletonJsonRpcModule() ; } diff --git a/src/Nethermind/Nethermind.Merge.Plugin/TestingRpcModule.cs b/src/Nethermind/Nethermind.Merge.Plugin/TestingRpcModule.cs new file mode 100644 index 00000000000..ec6fd5cd4cd --- /dev/null +++ b/src/Nethermind/Nethermind.Merge.Plugin/TestingRpcModule.cs @@ -0,0 +1,100 @@ +// SPDX-FileCopyrightText: 2025 Demerzel Solutions Limited +// SPDX-License-Identifier: LGPL-3.0-only + +using System; +using System.Collections.Generic; +using System.Threading.Tasks; +using Nethermind.Blockchain.Find; +using Nethermind.Consensus; +using Nethermind.Consensus.Processing; +using Nethermind.Consensus.Producers; +using Nethermind.Core; +using Nethermind.Core.Crypto; +using Nethermind.Core.Specs; +using Nethermind.Evm.Tracing; +using Nethermind.Int256; +using Nethermind.JsonRpc; +using Nethermind.Logging; +using Nethermind.Merge.Plugin.Data; +using Nethermind.Serialization.Rlp; +using ILogger = Nethermind.Logging.ILogger; + +namespace Nethermind.Merge.Plugin; + +public class TestingRpcModule( + IBlockchainProcessor processor, + IGasLimitCalculator gasLimitCalculator, + IDifficultyCalculator difficultyCalculator, + ISpecProvider specProvider, + IBlockFinder blockFinder, + ILogManager logManager) + : ITestingRpcModule +{ + private readonly ILogger _logger = logManager.GetClassLogger(); + + public Task> testing_buildBlockV1(Hash256 parentBlockHash, PayloadAttributes payloadAttributes, IEnumerable txRlps, byte[]? extraData) + { + Block? parentBlock = blockFinder.FindBlock(parentBlockHash); + + if (parentBlock is not null) + { + BlockHeader header = PrepareBlockHeader(parentBlock.Header, payloadAttributes, extraData); + IEnumerable transactions = GetTransactions(txRlps); + Block block = new BlockToProduce(header, transactions, Array.Empty(), payloadAttributes.Withdrawals); + + FeesTracer feesTracer = new(); + Block? processedBlock = processor.Process(block, ProcessingOptions.ProducingBlock, feesTracer); + + if (processedBlock is not null) + { + GetPayloadV5Result getPayloadV5Result = new(processedBlock, feesTracer.Fees, new BlobsBundleV2(processedBlock), processedBlock.ExecutionRequests!, shouldOverrideBuilder: false); + + if (!getPayloadV5Result.ValidateFork(specProvider)) + { + if (_logger.IsWarn) _logger.Warn($"The payload is not supported by the current fork"); + return ResultWrapper.Fail("unsupported fork", MergeErrorCodes.UnsupportedFork); + } + + if (_logger.IsInfo) _logger.Info($"GetPayloadV5 result: {block.Header.ToString(BlockHeader.Format.Short)}."); + return ResultWrapper.Success(getPayloadV5Result); + } + + return ResultWrapper.Fail("payload processing failed", MergeErrorCodes.UnknownPayload); + } + return ResultWrapper.Fail("unknown parent block", MergeErrorCodes.InvalidPayloadAttributes); + } + + private BlockHeader PrepareBlockHeader(BlockHeader parent, PayloadAttributes payloadAttributes, byte[]? extraData) + { + Address blockAuthor = payloadAttributes.SuggestedFeeRecipient; + BlockHeader header = new( + parent.Hash!, + Keccak.OfAnEmptySequenceRlp, + blockAuthor, + UInt256.Zero, + parent.Number + 1, + payloadAttributes.GetGasLimit() ?? gasLimitCalculator.GetGasLimit(parent), + payloadAttributes.Timestamp, + extraData ?? []) + { + Author = blockAuthor, + MixHash = payloadAttributes.PrevRandao, + ParentBeaconBlockRoot = payloadAttributes.ParentBeaconBlockRoot + }; + + UInt256 difficulty = difficultyCalculator.Calculate(header, parent); + header.Difficulty = difficulty; + header.TotalDifficulty = parent.TotalDifficulty + difficulty; + header.BaseFeePerGas = BaseFeeCalculator.Calculate(parent, specProvider.GetSpec(header)); + + return header; + } + + private IEnumerable GetTransactions(IEnumerable txRlps) + { + foreach (var txRlp in txRlps) + { + yield return TxDecoder.Instance.Decode(new RlpStream(txRlp), RlpBehaviors.SkipTypedWrapping); + } + } +} From 8d7d35e65234c02267892129048ba26eadc7c0d7 Mon Sep 17 00:00:00 2001 From: Marcin Sobczak Date: Mon, 8 Dec 2025 12:47:52 +0100 Subject: [PATCH 02/14] fix encoding --- src/Nethermind/Nethermind.Merge.Plugin/ITestingRpcModule.cs | 2 +- src/Nethermind/Nethermind.Merge.Plugin/TestingRpcModule.cs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Nethermind/Nethermind.Merge.Plugin/ITestingRpcModule.cs b/src/Nethermind/Nethermind.Merge.Plugin/ITestingRpcModule.cs index 7fc24e8bb50..1c2585b4805 100644 --- a/src/Nethermind/Nethermind.Merge.Plugin/ITestingRpcModule.cs +++ b/src/Nethermind/Nethermind.Merge.Plugin/ITestingRpcModule.cs @@ -1,4 +1,4 @@ -// SPDX-FileCopyrightText: 2025 Demerzel Solutions Limited +// SPDX-FileCopyrightText: 2025 Demerzel Solutions Limited // SPDX-License-Identifier: LGPL-3.0-only using System.Collections.Generic; diff --git a/src/Nethermind/Nethermind.Merge.Plugin/TestingRpcModule.cs b/src/Nethermind/Nethermind.Merge.Plugin/TestingRpcModule.cs index ec6fd5cd4cd..1445e516495 100644 --- a/src/Nethermind/Nethermind.Merge.Plugin/TestingRpcModule.cs +++ b/src/Nethermind/Nethermind.Merge.Plugin/TestingRpcModule.cs @@ -1,4 +1,4 @@ -// SPDX-FileCopyrightText: 2025 Demerzel Solutions Limited +// SPDX-FileCopyrightText: 2025 Demerzel Solutions Limited // SPDX-License-Identifier: LGPL-3.0-only using System; From d62c2b7dd1ea55f6b2dc3672facd97bd47f9ddc0 Mon Sep 17 00:00:00 2001 From: Marcin Sobczak Date: Tue, 9 Dec 2025 09:00:24 +0100 Subject: [PATCH 03/14] fix tests? --- src/Nethermind/Nethermind.Merge.Plugin/MergePlugin.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/src/Nethermind/Nethermind.Merge.Plugin/MergePlugin.cs b/src/Nethermind/Nethermind.Merge.Plugin/MergePlugin.cs index 6613e1bc1b8..ffd4fef9141 100644 --- a/src/Nethermind/Nethermind.Merge.Plugin/MergePlugin.cs +++ b/src/Nethermind/Nethermind.Merge.Plugin/MergePlugin.cs @@ -345,6 +345,7 @@ protected override void Load(ContainerBuilder builder) // Testing rpc .RegisterSingletonJsonRpcModule() + .AddSingleton() ; } From 8409f1dc9b7ea691dfc2fd74355cabae78506fee Mon Sep 17 00:00:00 2001 From: Marcin Sobczak Date: Tue, 9 Dec 2025 09:10:20 +0100 Subject: [PATCH 04/14] remove difficulty calculator --- src/Nethermind/Nethermind.Merge.Plugin/TestingRpcModule.cs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/Nethermind/Nethermind.Merge.Plugin/TestingRpcModule.cs b/src/Nethermind/Nethermind.Merge.Plugin/TestingRpcModule.cs index 1445e516495..c82396e4aac 100644 --- a/src/Nethermind/Nethermind.Merge.Plugin/TestingRpcModule.cs +++ b/src/Nethermind/Nethermind.Merge.Plugin/TestingRpcModule.cs @@ -24,7 +24,6 @@ namespace Nethermind.Merge.Plugin; public class TestingRpcModule( IBlockchainProcessor processor, IGasLimitCalculator gasLimitCalculator, - IDifficultyCalculator difficultyCalculator, ISpecProvider specProvider, IBlockFinder blockFinder, ILogManager logManager) @@ -82,7 +81,7 @@ private BlockHeader PrepareBlockHeader(BlockHeader parent, PayloadAttributes pay ParentBeaconBlockRoot = payloadAttributes.ParentBeaconBlockRoot }; - UInt256 difficulty = difficultyCalculator.Calculate(header, parent); + UInt256 difficulty = UInt256.Zero; header.Difficulty = difficulty; header.TotalDifficulty = parent.TotalDifficulty + difficulty; header.BaseFeePerGas = BaseFeeCalculator.Calculate(parent, specProvider.GetSpec(header)); From 3b3fdf292d6c99b328215a910da312f44323724b Mon Sep 17 00:00:00 2001 From: Marcin Sobczak Date: Tue, 9 Dec 2025 10:39:13 +0100 Subject: [PATCH 05/14] fix tests? --- src/Nethermind/Nethermind.Merge.Plugin/MergePlugin.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/Nethermind/Nethermind.Merge.Plugin/MergePlugin.cs b/src/Nethermind/Nethermind.Merge.Plugin/MergePlugin.cs index ffd4fef9141..ee78b24627b 100644 --- a/src/Nethermind/Nethermind.Merge.Plugin/MergePlugin.cs +++ b/src/Nethermind/Nethermind.Merge.Plugin/MergePlugin.cs @@ -346,6 +346,8 @@ protected override void Load(ContainerBuilder builder) // Testing rpc .RegisterSingletonJsonRpcModule() .AddSingleton() + .AddSingleton((ctx) => + new BlockchainProcessor.Options { StoreReceiptsByDefault = false }) ; } From 0f2e9aabf320fd18bb13ea172e55dc75ede7edda Mon Sep 17 00:00:00 2001 From: Marcin Sobczak Date: Thu, 11 Dec 2025 11:45:45 +0100 Subject: [PATCH 06/14] undo test fixes --- src/Nethermind/Nethermind.Merge.Plugin/MergePlugin.cs | 3 --- 1 file changed, 3 deletions(-) diff --git a/src/Nethermind/Nethermind.Merge.Plugin/MergePlugin.cs b/src/Nethermind/Nethermind.Merge.Plugin/MergePlugin.cs index ee78b24627b..6613e1bc1b8 100644 --- a/src/Nethermind/Nethermind.Merge.Plugin/MergePlugin.cs +++ b/src/Nethermind/Nethermind.Merge.Plugin/MergePlugin.cs @@ -345,9 +345,6 @@ protected override void Load(ContainerBuilder builder) // Testing rpc .RegisterSingletonJsonRpcModule() - .AddSingleton() - .AddSingleton((ctx) => - new BlockchainProcessor.Options { StoreReceiptsByDefault = false }) ; } From e1cdef204dff03592a5fbb271484e963325ac192 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kamil=20Chodo=C5=82a?= Date: Fri, 6 Feb 2026 14:23:42 +0100 Subject: [PATCH 07/14] Fix DI --- .../Nethermind.Merge.Plugin.Test/MergePluginTests.cs | 12 ++++++++++++ .../Nethermind.Merge.Plugin/MergePlugin.cs | 3 +++ .../Nethermind.Merge.Plugin/TestingRpcModule.cs | 5 +++-- 3 files changed, 18 insertions(+), 2 deletions(-) diff --git a/src/Nethermind/Nethermind.Merge.Plugin.Test/MergePluginTests.cs b/src/Nethermind/Nethermind.Merge.Plugin.Test/MergePluginTests.cs index c6b82b964fd..331294452a0 100644 --- a/src/Nethermind/Nethermind.Merge.Plugin.Test/MergePluginTests.cs +++ b/src/Nethermind/Nethermind.Merge.Plugin.Test/MergePluginTests.cs @@ -8,6 +8,7 @@ using Nethermind.Api; using Nethermind.Blockchain.Synchronization; using Nethermind.Config; +using Nethermind.Consensus; using Nethermind.Consensus.Clique; using Nethermind.Consensus.Processing; using Nethermind.Core; @@ -117,6 +118,17 @@ public async Task Initializes_correctly() Assert.That(api.BlockProducer, Is.InstanceOf()); } + [Test] + public async Task Init_registers_gas_limit_calculator_for_testing_rpc_module() + { + using IContainer container = BuildContainer(); + INethermindApi api = container.Resolve(); + await _consensusPlugin!.Init(api); + await _plugin.Init(api); + + Assert.DoesNotThrow(() => container.Resolve()); + } + [TestCase(true, true)] [TestCase(false, true)] [TestCase(true, false)] diff --git a/src/Nethermind/Nethermind.Merge.Plugin/MergePlugin.cs b/src/Nethermind/Nethermind.Merge.Plugin/MergePlugin.cs index 2e66e6d99dc..ec8f8f51e03 100644 --- a/src/Nethermind/Nethermind.Merge.Plugin/MergePlugin.cs +++ b/src/Nethermind/Nethermind.Merge.Plugin/MergePlugin.cs @@ -21,6 +21,7 @@ using Nethermind.Core; using Nethermind.Core.Crypto; using Nethermind.Core.Exceptions; +using Nethermind.Core.Specs; using Nethermind.Db; using Nethermind.Facade.Proxy; using Nethermind.HealthChecks; @@ -341,6 +342,8 @@ protected override void Load(ContainerBuilder builder) ctx.Resolve()); }) .AddSingleton() + .AddSingleton((specProvider, blocksConfig) => + new TargetAdjustedGasLimitCalculator(specProvider, blocksConfig)) // Testing rpc .RegisterSingletonJsonRpcModule() diff --git a/src/Nethermind/Nethermind.Merge.Plugin/TestingRpcModule.cs b/src/Nethermind/Nethermind.Merge.Plugin/TestingRpcModule.cs index c82396e4aac..0ac63c820fd 100644 --- a/src/Nethermind/Nethermind.Merge.Plugin/TestingRpcModule.cs +++ b/src/Nethermind/Nethermind.Merge.Plugin/TestingRpcModule.cs @@ -22,7 +22,7 @@ namespace Nethermind.Merge.Plugin; public class TestingRpcModule( - IBlockchainProcessor processor, + IMainProcessingContext mainProcessingContext, IGasLimitCalculator gasLimitCalculator, ISpecProvider specProvider, IBlockFinder blockFinder, @@ -30,6 +30,7 @@ public class TestingRpcModule( : ITestingRpcModule { private readonly ILogger _logger = logManager.GetClassLogger(); + private readonly IBlockchainProcessor _processor = mainProcessingContext.BlockchainProcessor; public Task> testing_buildBlockV1(Hash256 parentBlockHash, PayloadAttributes payloadAttributes, IEnumerable txRlps, byte[]? extraData) { @@ -42,7 +43,7 @@ public class TestingRpcModule( Block block = new BlockToProduce(header, transactions, Array.Empty(), payloadAttributes.Withdrawals); FeesTracer feesTracer = new(); - Block? processedBlock = processor.Process(block, ProcessingOptions.ProducingBlock, feesTracer); + Block? processedBlock = _processor.Process(block, ProcessingOptions.ProducingBlock, feesTracer); if (processedBlock is not null) { From 4a6adefb2133313963aee4c6e2910e8e11f52fbd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kamil=20Chodo=C5=82a?= Date: Fri, 6 Feb 2026 15:04:38 +0100 Subject: [PATCH 08/14] Patch blobExcessGas --- .../MergePluginTests.cs | 87 +++++++++++++++++++ .../TestingRpcModule.cs | 12 ++- 2 files changed, 98 insertions(+), 1 deletion(-) diff --git a/src/Nethermind/Nethermind.Merge.Plugin.Test/MergePluginTests.cs b/src/Nethermind/Nethermind.Merge.Plugin.Test/MergePluginTests.cs index 331294452a0..30189773e77 100644 --- a/src/Nethermind/Nethermind.Merge.Plugin.Test/MergePluginTests.cs +++ b/src/Nethermind/Nethermind.Merge.Plugin.Test/MergePluginTests.cs @@ -2,25 +2,35 @@ // SPDX-License-Identifier: LGPL-3.0-only using System; +using System.Threading; using System.Threading.Tasks; using Autofac; using FluentAssertions; using Nethermind.Api; +using Nethermind.Blockchain.Find; using Nethermind.Blockchain.Synchronization; using Nethermind.Config; using Nethermind.Consensus; using Nethermind.Consensus.Clique; using Nethermind.Consensus.Processing; +using Nethermind.Consensus.Producers; using Nethermind.Core; +using Nethermind.Core.Crypto; using Nethermind.Core.Exceptions; +using Nethermind.Core.Specs; +using Nethermind.Evm; +using Nethermind.Evm.Tracing; using Nethermind.HealthChecks; +using Nethermind.Int256; using Nethermind.JsonRpc; using Nethermind.JsonRpc.Modules; using Nethermind.Logging; using Nethermind.Merge.Plugin.BlockProduction; +using Nethermind.Merge.Plugin.Data; using Nethermind.Runner.Ethereum.Modules; using Nethermind.Runner.Test.Ethereum; using Nethermind.Serialization.Json; +using Nethermind.Specs.Forks; using Nethermind.Specs.ChainSpecStyle; using Nethermind.Specs.Test.ChainSpecStyle; using NUnit.Framework; @@ -129,6 +139,83 @@ public async Task Init_registers_gas_limit_calculator_for_testing_rpc_module() Assert.DoesNotThrow(() => container.Resolve()); } + [Test] + public async Task Testing_buildBlockV1_sets_excess_blob_gas_for_eip4844() + { + Hash256 parentHash = Keccak.Compute("parent"); + BlockHeader parentHeader = new( + Keccak.Compute("grandparent"), + Keccak.OfAnEmptySequenceRlp, + Address.Zero, + UInt256.Zero, + 1, + 30_000_000, + 1, + []) + { + Hash = parentHash, + TotalDifficulty = UInt256.Zero, + BaseFeePerGas = UInt256.One, + GasUsed = 0, + StateRoot = Keccak.EmptyTreeHash, + ReceiptsRoot = Keccak.EmptyTreeHash, + Bloom = Bloom.Empty, + BlobGasUsed = 0, + ExcessBlobGas = 0, + }; + + Block parentBlock = new(parentHeader, Array.Empty(), Array.Empty(), Array.Empty()); + IBlockFinder blockFinder = Substitute.For(); + blockFinder.FindBlock(parentHash).Returns(parentBlock); + + IBlockchainProcessor blockchainProcessor = Substitute.For(); + blockchainProcessor + .Process(Arg.Any(), Arg.Any(), Arg.Any(), Arg.Any()) + .Returns(static callInfo => + { + Block block = callInfo.Arg(); + block.Header.Hash ??= Keccak.Compute("produced"); + block.Header.StateRoot ??= Keccak.EmptyTreeHash; + block.Header.ReceiptsRoot ??= Keccak.EmptyTreeHash; + block.Header.Bloom ??= Bloom.Empty; + block.Header.GasUsed = 0; + block.ExecutionRequests = []; + return block; + }); + + IMainProcessingContext mainProcessingContext = Substitute.For(); + mainProcessingContext.BlockchainProcessor.Returns(blockchainProcessor); + + ISpecProvider specProvider = Substitute.For(); + specProvider.GetSpec(Arg.Any()).Returns(Osaka.Instance); + + IGasLimitCalculator gasLimitCalculator = Substitute.For(); + gasLimitCalculator.GetGasLimit(Arg.Any()).Returns(parentHeader.GasLimit); + + TestingRpcModule module = new( + mainProcessingContext, + gasLimitCalculator, + specProvider, + blockFinder, + LimboLogs.Instance); + + PayloadAttributes payloadAttributes = new() + { + Timestamp = parentHeader.Timestamp + 12, + PrevRandao = Keccak.Compute("randao"), + SuggestedFeeRecipient = Address.Zero, + Withdrawals = [], + ParentBeaconBlockRoot = Keccak.Compute("parentBeaconBlockRoot") + }; + + ResultWrapper result = await module.testing_buildBlockV1(parentHash, payloadAttributes, Array.Empty(), Array.Empty()); + + result.Result.ResultType.Should().Be(ResultType.Success); + result.Data.Should().NotBeNull(); + result.Data!.ExecutionPayload.BlobGasUsed.Should().Be(0); + result.Data!.ExecutionPayload.ExcessBlobGas.Should().Be(BlobGasCalculator.CalculateExcessBlobGas(parentHeader, Osaka.Instance)); + } + [TestCase(true, true)] [TestCase(false, true)] [TestCase(true, false)] diff --git a/src/Nethermind/Nethermind.Merge.Plugin/TestingRpcModule.cs b/src/Nethermind/Nethermind.Merge.Plugin/TestingRpcModule.cs index 0ac63c820fd..7f91fc406db 100644 --- a/src/Nethermind/Nethermind.Merge.Plugin/TestingRpcModule.cs +++ b/src/Nethermind/Nethermind.Merge.Plugin/TestingRpcModule.cs @@ -11,6 +11,7 @@ using Nethermind.Core; using Nethermind.Core.Crypto; using Nethermind.Core.Specs; +using Nethermind.Evm; using Nethermind.Evm.Tracing; using Nethermind.Int256; using Nethermind.JsonRpc; @@ -85,7 +86,16 @@ private BlockHeader PrepareBlockHeader(BlockHeader parent, PayloadAttributes pay UInt256 difficulty = UInt256.Zero; header.Difficulty = difficulty; header.TotalDifficulty = parent.TotalDifficulty + difficulty; - header.BaseFeePerGas = BaseFeeCalculator.Calculate(parent, specProvider.GetSpec(header)); + + header.IsPostMerge = true; + IReleaseSpec spec = specProvider.GetSpec(header); + header.BaseFeePerGas = BaseFeeCalculator.Calculate(parent, spec); + + if (spec.IsEip4844Enabled) + { + header.BlobGasUsed = 0; + header.ExcessBlobGas = BlobGasCalculator.CalculateExcessBlobGas(parent, spec); + } return header; } From cbab85b3c71364f727e358231a835a6d6ab3d201 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kamil=20Chodo=C5=82a?= Date: Fri, 6 Feb 2026 17:27:20 +0100 Subject: [PATCH 09/14] Fixes to withdrawal root --- .../MergePluginTests.cs | 24 ++++++++++++++++--- .../TestingRpcModule.cs | 8 +++++++ 2 files changed, 29 insertions(+), 3 deletions(-) diff --git a/src/Nethermind/Nethermind.Merge.Plugin.Test/MergePluginTests.cs b/src/Nethermind/Nethermind.Merge.Plugin.Test/MergePluginTests.cs index 30189773e77..b39ac8f4f00 100644 --- a/src/Nethermind/Nethermind.Merge.Plugin.Test/MergePluginTests.cs +++ b/src/Nethermind/Nethermind.Merge.Plugin.Test/MergePluginTests.cs @@ -33,6 +33,7 @@ using Nethermind.Specs.Forks; using Nethermind.Specs.ChainSpecStyle; using Nethermind.Specs.Test.ChainSpecStyle; +using Nethermind.State.Proofs; using NUnit.Framework; using NSubstitute; @@ -168,20 +169,27 @@ public async Task Testing_buildBlockV1_sets_excess_blob_gas_for_eip4844() IBlockFinder blockFinder = Substitute.For(); blockFinder.FindBlock(parentHash).Returns(parentBlock); + Hash256? suggestedWithdrawalsRoot = null; IBlockchainProcessor blockchainProcessor = Substitute.For(); blockchainProcessor .Process(Arg.Any(), Arg.Any(), Arg.Any(), Arg.Any()) .Returns(static callInfo => { Block block = callInfo.Arg(); - block.Header.Hash ??= Keccak.Compute("produced"); block.Header.StateRoot ??= Keccak.EmptyTreeHash; block.Header.ReceiptsRoot ??= Keccak.EmptyTreeHash; block.Header.Bloom ??= Bloom.Empty; block.Header.GasUsed = 0; - block.ExecutionRequests = []; + block.Header.Hash ??= Keccak.Compute("produced"); return block; }); + blockchainProcessor + .When(x => x.Process(Arg.Any(), Arg.Any(), Arg.Any(), Arg.Any())) + .Do(callInfo => + { + Block block = callInfo.Arg(); + suggestedWithdrawalsRoot = block.Header.WithdrawalsRoot; + }); IMainProcessingContext mainProcessingContext = Substitute.For(); mainProcessingContext.BlockchainProcessor.Returns(blockchainProcessor); @@ -204,7 +212,16 @@ public async Task Testing_buildBlockV1_sets_excess_blob_gas_for_eip4844() Timestamp = parentHeader.Timestamp + 12, PrevRandao = Keccak.Compute("randao"), SuggestedFeeRecipient = Address.Zero, - Withdrawals = [], + Withdrawals = + [ + new Withdrawal + { + Index = 0, + ValidatorIndex = 0, + Address = Address.Zero, + AmountInGwei = 1 + } + ], ParentBeaconBlockRoot = Keccak.Compute("parentBeaconBlockRoot") }; @@ -214,6 +231,7 @@ public async Task Testing_buildBlockV1_sets_excess_blob_gas_for_eip4844() result.Data.Should().NotBeNull(); result.Data!.ExecutionPayload.BlobGasUsed.Should().Be(0); result.Data!.ExecutionPayload.ExcessBlobGas.Should().Be(BlobGasCalculator.CalculateExcessBlobGas(parentHeader, Osaka.Instance)); + suggestedWithdrawalsRoot.Should().Be(new WithdrawalTrie(payloadAttributes.Withdrawals!).RootHash); } [TestCase(true, true)] diff --git a/src/Nethermind/Nethermind.Merge.Plugin/TestingRpcModule.cs b/src/Nethermind/Nethermind.Merge.Plugin/TestingRpcModule.cs index 7f91fc406db..7d932537a37 100644 --- a/src/Nethermind/Nethermind.Merge.Plugin/TestingRpcModule.cs +++ b/src/Nethermind/Nethermind.Merge.Plugin/TestingRpcModule.cs @@ -18,6 +18,7 @@ using Nethermind.Logging; using Nethermind.Merge.Plugin.Data; using Nethermind.Serialization.Rlp; +using Nethermind.State.Proofs; using ILogger = Nethermind.Logging.ILogger; namespace Nethermind.Merge.Plugin; @@ -97,6 +98,13 @@ private BlockHeader PrepareBlockHeader(BlockHeader parent, PayloadAttributes pay header.ExcessBlobGas = BlobGasCalculator.CalculateExcessBlobGas(parent, spec); } + if (spec.WithdrawalsEnabled) + { + header.WithdrawalsRoot = payloadAttributes.Withdrawals is null || payloadAttributes.Withdrawals.Length == 0 + ? Keccak.EmptyTreeHash + : new WithdrawalTrie(payloadAttributes.Withdrawals).RootHash; + } + return header; } From 4c8955c2c84965de54e6b3bd19b31befc4e6bff6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kamil=20Chodo=C5=82a?= Date: Fri, 6 Feb 2026 17:47:22 +0100 Subject: [PATCH 10/14] materialize txs and set TxRoot --- .../EngineModuleTests.V5.cs | 44 +++++++++++++++++++ .../TestingRpcModule.cs | 6 ++- 2 files changed, 48 insertions(+), 2 deletions(-) diff --git a/src/Nethermind/Nethermind.Merge.Plugin.Test/EngineModuleTests.V5.cs b/src/Nethermind/Nethermind.Merge.Plugin.Test/EngineModuleTests.V5.cs index 825da6cc4e1..bd2634af699 100644 --- a/src/Nethermind/Nethermind.Merge.Plugin.Test/EngineModuleTests.V5.cs +++ b/src/Nethermind/Nethermind.Merge.Plugin.Test/EngineModuleTests.V5.cs @@ -5,14 +5,19 @@ using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; +using Autofac; using CkzgLib; using FluentAssertions; +using Nethermind.Consensus.Producers; using Nethermind.Core; +using Nethermind.Core.Crypto; +using Nethermind.Core.ExecutionRequest; using Nethermind.Core.Extensions; using Nethermind.Core.Test.Builders; using Nethermind.Crypto; using Nethermind.Evm; using Nethermind.JsonRpc; +using Nethermind.Int256; using Nethermind.Merge.Plugin.Data; using Nethermind.Specs.Forks; using Nethermind.TxPool; @@ -37,6 +42,45 @@ public async Task GetPayloadV5_should_return_all_the_blobs([Values(0, 1, 2, 3, 4 Assert.That(IBlobProofsManager.For(ProofVersion.V1).ValidateProofs(wrapper), Is.True); } + [Test] + public async Task Testing_buildBlockV1_empty_block_with_empty_withdrawals_has_valid_hash() + { + MergeTestBlockchain chain = await CreateBlockchain(releaseSpec: Osaka.Instance); + ITestingRpcModule testingRpcModule = chain.Container.Resolve(); + + Block head = chain.BlockTree.Head!; + PayloadAttributes payloadAttributes = new() + { + Timestamp = head.Timestamp + 12, + PrevRandao = TestItem.KeccakA, + SuggestedFeeRecipient = Address.Zero, + Withdrawals = [], + ParentBeaconBlockRoot = TestItem.KeccakB + }; + + ResultWrapper buildResult = await testingRpcModule.testing_buildBlockV1( + head.Hash!, + payloadAttributes, + [], + []); + + buildResult.Result.Should().Be(Result.Success); + buildResult.Data.Should().NotBeNull(); + + ExecutionPayloadV3 executionPayload = buildResult.Data!.ExecutionPayload; + executionPayload.ExecutionRequests = buildResult.Data.ExecutionRequests; + executionPayload.TryGetBlock().Block!.CalculateHash().Should().Be(executionPayload.BlockHash); + + ResultWrapper newPayloadResult = await chain.EngineRpcModule.engine_newPayloadV4( + executionPayload, + [], + payloadAttributes.ParentBeaconBlockRoot, + buildResult.Data.ExecutionRequests); + + newPayloadResult.Result.Should().Be(Result.Success); + newPayloadResult.Data.Status.Should().Be(PayloadStatus.Valid); + } + [Test] public async Task GetBlobsV2_should_throw_if_more_than_128_requested_blobs([Values(128, 129)] int requestSize) { diff --git a/src/Nethermind/Nethermind.Merge.Plugin/TestingRpcModule.cs b/src/Nethermind/Nethermind.Merge.Plugin/TestingRpcModule.cs index 7d932537a37..b6fdb5188ce 100644 --- a/src/Nethermind/Nethermind.Merge.Plugin/TestingRpcModule.cs +++ b/src/Nethermind/Nethermind.Merge.Plugin/TestingRpcModule.cs @@ -3,6 +3,7 @@ using System; using System.Collections.Generic; +using System.Linq; using System.Threading.Tasks; using Nethermind.Blockchain.Find; using Nethermind.Consensus; @@ -41,8 +42,9 @@ public class TestingRpcModule( if (parentBlock is not null) { BlockHeader header = PrepareBlockHeader(parentBlock.Header, payloadAttributes, extraData); - IEnumerable transactions = GetTransactions(txRlps); - Block block = new BlockToProduce(header, transactions, Array.Empty(), payloadAttributes.Withdrawals); + Transaction[] transactions = GetTransactions(txRlps).ToArray(); + header.TxRoot = TxTrie.CalculateRoot(transactions); + Block block = new(header, transactions, Array.Empty(), payloadAttributes.Withdrawals); FeesTracer feesTracer = new(); Block? processedBlock = _processor.Process(block, ProcessingOptions.ProducingBlock, feesTracer); From 279b5fd32d2fdf7c717b51b402bcb9b8ce9bfbdd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kamil=20Chodo=C5=82a?= Date: Fri, 6 Feb 2026 21:58:44 +0100 Subject: [PATCH 11/14] Fix log --- .../BlockchainProcessorTests.cs | 18 ++++++++++++++++++ .../Nethermind.Blockchain/LogTraceDumper.cs | 12 ++++++++---- .../EngineModuleTests.V5.cs | 3 --- 3 files changed, 26 insertions(+), 7 deletions(-) diff --git a/src/Nethermind/Nethermind.Blockchain.Test/BlockchainProcessorTests.cs b/src/Nethermind/Nethermind.Blockchain.Test/BlockchainProcessorTests.cs index df03c4b2834..64334078b5e 100644 --- a/src/Nethermind/Nethermind.Blockchain.Test/BlockchainProcessorTests.cs +++ b/src/Nethermind/Nethermind.Blockchain.Test/BlockchainProcessorTests.cs @@ -32,6 +32,24 @@ namespace Nethermind.Blockchain.Test; [FixtureLifeCycle(LifeCycle.InstancePerTestCase)] public class BlockchainProcessorTests { + [Test] + public void LogDiagnosticTrace_does_not_throw_for_null_hash_variant() + { + ILogger logger = LimboLogs.Instance.GetClassLogger(); + + Assert.DoesNotThrow(() => + BlockTraceDumper.LogDiagnosticTrace(NullBlockTracer.Instance, (Hash256)null!, logger)); + } + + [Test] + public void LogDiagnosticTrace_does_not_throw_for_default_either_variant() + { + ILogger logger = LimboLogs.Instance.GetClassLogger(); + + Assert.DoesNotThrow(() => + BlockTraceDumper.LogDiagnosticTrace(NullBlockTracer.Instance, default(Either>), logger)); + } + private class ProcessingTestContext { private readonly ILogManager _logManager = LimboLogs.Instance; diff --git a/src/Nethermind/Nethermind.Blockchain/LogTraceDumper.cs b/src/Nethermind/Nethermind.Blockchain/LogTraceDumper.cs index 61b3d14cd41..515df73c9ef 100644 --- a/src/Nethermind/Nethermind.Blockchain/LogTraceDumper.cs +++ b/src/Nethermind/Nethermind.Blockchain/LogTraceDumper.cs @@ -102,21 +102,25 @@ private static bool GetConditionAndHashString(Either> bloc blockHash = failedBlockHash.ToString(); return false; } - else + + if (blocksOrHash.Is(out IList blocks)) { - blocksOrHash.To(out IList blocks); condition = "valid on rerun"; if (blocks.Count == 1) { - blockHash = blocks[0].Hash.ToString(); + blockHash = blocks[0].Hash?.ToString() ?? "unknown"; } else { - blockHash = string.Join("|", blocks.Select(b => b.Hash.ToString())); + blockHash = string.Join("|", blocks.Select(static b => b.Hash?.ToString() ?? "unknown")); } return true; } + + condition = "unknown"; + blockHash = "unknown"; + return false; } private static FileStream GetFileStream(string name) => diff --git a/src/Nethermind/Nethermind.Merge.Plugin.Test/EngineModuleTests.V5.cs b/src/Nethermind/Nethermind.Merge.Plugin.Test/EngineModuleTests.V5.cs index bd2634af699..32f14a58e20 100644 --- a/src/Nethermind/Nethermind.Merge.Plugin.Test/EngineModuleTests.V5.cs +++ b/src/Nethermind/Nethermind.Merge.Plugin.Test/EngineModuleTests.V5.cs @@ -10,14 +10,11 @@ using FluentAssertions; using Nethermind.Consensus.Producers; using Nethermind.Core; -using Nethermind.Core.Crypto; -using Nethermind.Core.ExecutionRequest; using Nethermind.Core.Extensions; using Nethermind.Core.Test.Builders; using Nethermind.Crypto; using Nethermind.Evm; using Nethermind.JsonRpc; -using Nethermind.Int256; using Nethermind.Merge.Plugin.Data; using Nethermind.Specs.Forks; using Nethermind.TxPool; From b265f1e0cadb1f26cd4f64e2be5b3785debe2a11 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kamil=20Chodo=C5=82a?= Date: Sat, 7 Feb 2026 01:37:44 +0100 Subject: [PATCH 12/14] adjust logs --- src/Nethermind/Nethermind.Merge.Plugin/TestingRpcModule.cs | 2 +- src/Nethermind/Nethermind.Trie/Pruning/TrieStore.cs | 6 ++++++ 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/src/Nethermind/Nethermind.Merge.Plugin/TestingRpcModule.cs b/src/Nethermind/Nethermind.Merge.Plugin/TestingRpcModule.cs index b6fdb5188ce..079a03e8ec4 100644 --- a/src/Nethermind/Nethermind.Merge.Plugin/TestingRpcModule.cs +++ b/src/Nethermind/Nethermind.Merge.Plugin/TestingRpcModule.cs @@ -59,7 +59,7 @@ public class TestingRpcModule( return ResultWrapper.Fail("unsupported fork", MergeErrorCodes.UnsupportedFork); } - if (_logger.IsInfo) _logger.Info($"GetPayloadV5 result: {block.Header.ToString(BlockHeader.Format.Short)}."); + if (_logger.IsDebug) _logger.Debug($"testing_buildBlockV1 produced payload for block {processedBlock.Header.ToString(BlockHeader.Format.Short)}."); return ResultWrapper.Success(getPayloadV5Result); } diff --git a/src/Nethermind/Nethermind.Trie/Pruning/TrieStore.cs b/src/Nethermind/Nethermind.Trie/Pruning/TrieStore.cs index a53f33528f5..62065fc084b 100644 --- a/src/Nethermind/Nethermind.Trie/Pruning/TrieStore.cs +++ b/src/Nethermind/Nethermind.Trie/Pruning/TrieStore.cs @@ -1083,6 +1083,12 @@ private void VerifyNewCommitSet(long blockNumber) if (_lastCommitSet.BlockNumber != blockNumber - 1 && blockNumber != 0 && _lastCommitSet.BlockNumber != 0) { + if (_lastCommitSet.BlockNumber == blockNumber) + { + if (_logger.IsDebug) _logger.Debug($"Duplicate block-number commit. Last block commit: {_lastCommitSet.BlockNumber}. New block commit: {blockNumber}."); + return; + } + if (_logger.IsInfo) _logger.Info($"Non consecutive block commit. This is likely a reorg. Last block commit: {_lastCommitSet.BlockNumber}. New block commit: {blockNumber}."); } } From ee942145634a3ff189b7f9b3f64548e20dcd9d89 Mon Sep 17 00:00:00 2001 From: Marcin Sobczak Date: Thu, 19 Feb 2026 13:36:09 +0100 Subject: [PATCH 13/14] add support for amsterdam --- .../ITestingRpcModule.cs | 2 +- .../TestingRpcModule.cs | 19 ++++++++++++++++--- 2 files changed, 17 insertions(+), 4 deletions(-) diff --git a/src/Nethermind/Nethermind.Merge.Plugin/ITestingRpcModule.cs b/src/Nethermind/Nethermind.Merge.Plugin/ITestingRpcModule.cs index 1c2585b4805..24e7efd705d 100644 --- a/src/Nethermind/Nethermind.Merge.Plugin/ITestingRpcModule.cs +++ b/src/Nethermind/Nethermind.Merge.Plugin/ITestingRpcModule.cs @@ -19,5 +19,5 @@ public interface ITestingRpcModule : IRpcModule IsSharable = true, IsImplemented = true)] - public Task> testing_buildBlockV1(Hash256 parentBlockHash, PayloadAttributes payloadAttributes, IEnumerable txRlps, byte[]? extraData); + public Task> testing_buildBlockV1(Hash256 parentBlockHash, PayloadAttributes payloadAttributes, IEnumerable txRlps, byte[]? extraData, string? targetFork = null); } diff --git a/src/Nethermind/Nethermind.Merge.Plugin/TestingRpcModule.cs b/src/Nethermind/Nethermind.Merge.Plugin/TestingRpcModule.cs index 079a03e8ec4..480387913a3 100644 --- a/src/Nethermind/Nethermind.Merge.Plugin/TestingRpcModule.cs +++ b/src/Nethermind/Nethermind.Merge.Plugin/TestingRpcModule.cs @@ -35,7 +35,7 @@ public class TestingRpcModule( private readonly ILogger _logger = logManager.GetClassLogger(); private readonly IBlockchainProcessor _processor = mainProcessingContext.BlockchainProcessor; - public Task> testing_buildBlockV1(Hash256 parentBlockHash, PayloadAttributes payloadAttributes, IEnumerable txRlps, byte[]? extraData) + public Task> testing_buildBlockV1(Hash256 parentBlockHash, PayloadAttributes payloadAttributes, IEnumerable txRlps, byte[]? extraData, string? targetFork = null) { Block? parentBlock = blockFinder.FindBlock(parentBlockHash); @@ -53,9 +53,9 @@ public class TestingRpcModule( { GetPayloadV5Result getPayloadV5Result = new(processedBlock, feesTracer.Fees, new BlobsBundleV2(processedBlock), processedBlock.ExecutionRequests!, shouldOverrideBuilder: false); - if (!getPayloadV5Result.ValidateFork(specProvider)) + if (!ValidateFork(getPayloadV5Result, targetFork)) { - if (_logger.IsWarn) _logger.Warn($"The payload is not supported by the current fork"); + if (_logger.IsWarn) _logger.Warn($"The payload is not supported by the target fork: {targetFork ?? "prague"}"); return ResultWrapper.Fail("unsupported fork", MergeErrorCodes.UnsupportedFork); } @@ -117,4 +117,17 @@ private IEnumerable GetTransactions(IEnumerable txRlps) yield return TxDecoder.Instance.Decode(new RlpStream(txRlp), RlpBehaviors.SkipTypedWrapping); } } + + private bool ValidateFork(GetPayloadV5Result payload, string? targetFork) + { + IReleaseSpec spec = specProvider.GetSpec(payload.ExecutionPayload.BlockNumber, payload.ExecutionPayload.Timestamp); + + return targetFork?.ToLowerInvariant() switch + { + "amsterdam" => spec.IsEip7702Enabled, + "prague" => spec.IsEip7594Enabled, + null => spec.IsEip7594Enabled, + _ => false + }; + } } From 10fa4d6e9be7477546a6a23e345a2d3bd09ccace Mon Sep 17 00:00:00 2001 From: Marcin Sobczak Date: Tue, 24 Feb 2026 17:01:37 +0100 Subject: [PATCH 14/14] fix extraData nullability --- .../MergePluginTests.cs | 79 +++++++++++++++++++ .../ITestingRpcModule.cs | 2 +- .../TestingRpcModule.cs | 2 +- 3 files changed, 81 insertions(+), 2 deletions(-) diff --git a/src/Nethermind/Nethermind.Merge.Plugin.Test/MergePluginTests.cs b/src/Nethermind/Nethermind.Merge.Plugin.Test/MergePluginTests.cs index b39ac8f4f00..d7338e2ee13 100644 --- a/src/Nethermind/Nethermind.Merge.Plugin.Test/MergePluginTests.cs +++ b/src/Nethermind/Nethermind.Merge.Plugin.Test/MergePluginTests.cs @@ -24,6 +24,7 @@ using Nethermind.Int256; using Nethermind.JsonRpc; using Nethermind.JsonRpc.Modules; +using Nethermind.JsonRpc.Test; using Nethermind.Logging; using Nethermind.Merge.Plugin.BlockProduction; using Nethermind.Merge.Plugin.Data; @@ -234,6 +235,84 @@ public async Task Testing_buildBlockV1_sets_excess_blob_gas_for_eip4844() suggestedWithdrawalsRoot.Should().Be(new WithdrawalTrie(payloadAttributes.Withdrawals!).RootHash); } + [Test] + public async Task Testing_buildBlockV1_json_rpc_accepts_omitted_extraData() + { + Hash256 parentHash = Keccak.Compute("parent"); + BlockHeader parentHeader = new( + Keccak.Compute("grandparent"), + Keccak.OfAnEmptySequenceRlp, + Address.Zero, + UInt256.Zero, + 1, + 30_000_000, + 1, + []) + { + Hash = parentHash, + TotalDifficulty = UInt256.Zero, + BaseFeePerGas = UInt256.One, + GasUsed = 0, + StateRoot = Keccak.EmptyTreeHash, + ReceiptsRoot = Keccak.EmptyTreeHash, + Bloom = Bloom.Empty, + BlobGasUsed = 0, + ExcessBlobGas = 0, + }; + + Block parentBlock = new(parentHeader, Array.Empty(), Array.Empty(), Array.Empty()); + IBlockFinder blockFinder = Substitute.For(); + blockFinder.FindBlock(parentHash).Returns(parentBlock); + + IBlockchainProcessor blockchainProcessor = Substitute.For(); + blockchainProcessor + .Process(Arg.Any(), Arg.Any(), Arg.Any(), Arg.Any()) + .Returns(static callInfo => + { + Block block = callInfo.Arg(); + block.Header.StateRoot ??= Keccak.EmptyTreeHash; + block.Header.ReceiptsRoot ??= Keccak.EmptyTreeHash; + block.Header.Bloom ??= Bloom.Empty; + block.Header.GasUsed = 0; + block.Header.Hash ??= Keccak.Compute("produced"); + return block; + }); + + IMainProcessingContext mainProcessingContext = Substitute.For(); + mainProcessingContext.BlockchainProcessor.Returns(blockchainProcessor); + + ISpecProvider specProvider = Substitute.For(); + specProvider.GetSpec(Arg.Any()).Returns(Osaka.Instance); + + IGasLimitCalculator gasLimitCalculator = Substitute.For(); + gasLimitCalculator.GetGasLimit(Arg.Any()).Returns(parentHeader.GasLimit); + + TestingRpcModule module = new( + mainProcessingContext, + gasLimitCalculator, + specProvider, + blockFinder, + LimboLogs.Instance); + + PayloadAttributes payloadAttributes = new() + { + Timestamp = parentHeader.Timestamp + 12, + PrevRandao = Keccak.Compute("randao"), + SuggestedFeeRecipient = Address.Zero, + Withdrawals = [], + ParentBeaconBlockRoot = Keccak.Compute("parentBeaconBlockRoot") + }; + + JsonRpcResponse response = await RpcTest.TestRequest( + module, + nameof(ITestingRpcModule.testing_buildBlockV1), + parentHash, + payloadAttributes, + Array.Empty()); + + response.Should().BeOfType(); + } + [TestCase(true, true)] [TestCase(false, true)] [TestCase(true, false)] diff --git a/src/Nethermind/Nethermind.Merge.Plugin/ITestingRpcModule.cs b/src/Nethermind/Nethermind.Merge.Plugin/ITestingRpcModule.cs index 24e7efd705d..80b97c90d3f 100644 --- a/src/Nethermind/Nethermind.Merge.Plugin/ITestingRpcModule.cs +++ b/src/Nethermind/Nethermind.Merge.Plugin/ITestingRpcModule.cs @@ -19,5 +19,5 @@ public interface ITestingRpcModule : IRpcModule IsSharable = true, IsImplemented = true)] - public Task> testing_buildBlockV1(Hash256 parentBlockHash, PayloadAttributes payloadAttributes, IEnumerable txRlps, byte[]? extraData, string? targetFork = null); + public Task> testing_buildBlockV1(Hash256 parentBlockHash, PayloadAttributes payloadAttributes, IEnumerable txRlps, byte[]? extraData = null, string? targetFork = null); } diff --git a/src/Nethermind/Nethermind.Merge.Plugin/TestingRpcModule.cs b/src/Nethermind/Nethermind.Merge.Plugin/TestingRpcModule.cs index 480387913a3..58f06faca71 100644 --- a/src/Nethermind/Nethermind.Merge.Plugin/TestingRpcModule.cs +++ b/src/Nethermind/Nethermind.Merge.Plugin/TestingRpcModule.cs @@ -35,7 +35,7 @@ public class TestingRpcModule( private readonly ILogger _logger = logManager.GetClassLogger(); private readonly IBlockchainProcessor _processor = mainProcessingContext.BlockchainProcessor; - public Task> testing_buildBlockV1(Hash256 parentBlockHash, PayloadAttributes payloadAttributes, IEnumerable txRlps, byte[]? extraData, string? targetFork = null) + public Task> testing_buildBlockV1(Hash256 parentBlockHash, PayloadAttributes payloadAttributes, IEnumerable txRlps, byte[]? extraData = null, string? targetFork = null) { Block? parentBlock = blockFinder.FindBlock(parentBlockHash);