From 2f48f02865162c978460ebe1f6efb4b2acc5e4e4 Mon Sep 17 00:00:00 2001 From: Alexey Osipov Date: Fri, 28 Nov 2025 19:40:52 +0300 Subject: [PATCH 1/9] Return all forks if needed --- .../Modules/Eth/EthRpcModuleTests.cs | 21 ++++++++++ .../Data/ForkConfigSummary.cs | 2 + .../Modules/Eth/EthRpcModule.cs | 41 ++++++++++++------- .../Modules/Eth/IEthRpcModule.cs | 4 +- src/Nethermind/Nethermind.Network/ForkInfo.cs | 6 +++ .../Nethermind.Network/IForkInfo.cs | 3 ++ 6 files changed, 61 insertions(+), 16 deletions(-) diff --git a/src/Nethermind/Nethermind.JsonRpc.Test/Modules/Eth/EthRpcModuleTests.cs b/src/Nethermind/Nethermind.JsonRpc.Test/Modules/Eth/EthRpcModuleTests.cs index 0e6b8f73d9c..c63fa1b9187 100644 --- a/src/Nethermind/Nethermind.JsonRpc.Test/Modules/Eth/EthRpcModuleTests.cs +++ b/src/Nethermind/Nethermind.JsonRpc.Test/Modules/Eth/EthRpcModuleTests.cs @@ -186,6 +186,27 @@ public async Task Eth_get_transaction_by_block_number_and_index_out_of_bounds() Assert.That(serialized, Is.EqualTo("{\"jsonrpc\":\"2.0\",\"result\":null,\"id\":67}")); } + [Test] + public async Task Eth_config_does_not_include_all_by_default() + { + using Context ctx = await Context.Create(); + string serialized = await ctx.Test.TestEthRpc("eth_config"); + JToken result = JToken.Parse(serialized)["result"]!; + result.Should().NotBeNull(); + result["all"].Should().BeNull(); + } + + [Test] + public async Task Eth_config_returns_all_forks_when_requested() + { + using Context ctx = await Context.Create(); + string serialized = await ctx.Test.TestEthRpc("eth_config", true); + JToken result = JToken.Parse(serialized)["result"]!; + result.Should().NotBeNull(); + JArray allForks = (JArray)result["all"]!; + allForks.Count.Should().BeGreaterThan(0); + } + [TestCase(false, "{\"jsonrpc\":\"2.0\",\"result\":{\"difficulty\":\"0xf4240\",\"extraData\":\"0x010203\",\"gasLimit\":\"0x3d0900\",\"gasUsed\":\"0x0\",\"hash\":\"0xa2a9f03b9493046696099d27b2612b99497aa1f392ec966716ab393c715a5bb6\",\"logsBloom\":\"0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000\",\"miner\":\"0x0000000000000000000000000000000000000000\",\"mixHash\":\"0x2ba5557a4c62a513c7e56d1bf13373e0da6bec016755483e91589fe1c6d212e2\",\"nonce\":\"0x00000000000003e8\",\"number\":\"0x0\",\"parentHash\":\"0xff483e972a04a9a62bb4b7d04ae403c615604e4090521ecc5bb7af67f71be09c\",\"receiptsRoot\":\"0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421\",\"sha3Uncles\":\"0x1dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347\",\"size\":\"0x201\",\"stateRoot\":\"0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421\",\"totalDifficulty\":\"0x0\",\"timestamp\":\"0xf4240\",\"transactions\":[],\"transactionsRoot\":\"0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421\",\"uncles\":[]},\"id\":67}")] [TestCase(true, "{\"jsonrpc\":\"2.0\",\"result\":{\"difficulty\":\"0xf4240\",\"extraData\":\"0x010203\",\"gasLimit\":\"0x3d0900\",\"gasUsed\":\"0x0\",\"hash\":\"0xa2a9f03b9493046696099d27b2612b99497aa1f392ec966716ab393c715a5bb6\",\"logsBloom\":\"0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000\",\"miner\":\"0x0000000000000000000000000000000000000000\",\"mixHash\":\"0x2ba5557a4c62a513c7e56d1bf13373e0da6bec016755483e91589fe1c6d212e2\",\"nonce\":\"0x00000000000003e8\",\"number\":\"0x0\",\"parentHash\":\"0xff483e972a04a9a62bb4b7d04ae403c615604e4090521ecc5bb7af67f71be09c\",\"receiptsRoot\":\"0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421\",\"sha3Uncles\":\"0x1dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347\",\"size\":\"0x201\",\"stateRoot\":\"0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421\",\"totalDifficulty\":\"0x0\",\"timestamp\":\"0xf4240\",\"baseFeePerGas\":\"0x0\",\"transactions\":[],\"transactionsRoot\":\"0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421\",\"uncles\":[]},\"id\":67}")] public async Task Eth_get_uncle_by_block_number_and_index(bool eip1559, string expectedJson) diff --git a/src/Nethermind/Nethermind.JsonRpc/Data/ForkConfigSummary.cs b/src/Nethermind/Nethermind.JsonRpc/Data/ForkConfigSummary.cs index b780186bf2c..5297a2b23d9 100644 --- a/src/Nethermind/Nethermind.JsonRpc/Data/ForkConfigSummary.cs +++ b/src/Nethermind/Nethermind.JsonRpc/Data/ForkConfigSummary.cs @@ -13,6 +13,8 @@ public class ForkConfigSummary [JsonIgnore(Condition = JsonIgnoreCondition.Never)] public required ForkConfig? Last { get; init; } + + public IReadOnlyList? All { get; init; } } public class ForkConfig diff --git a/src/Nethermind/Nethermind.JsonRpc/Modules/Eth/EthRpcModule.cs b/src/Nethermind/Nethermind.JsonRpc/Modules/Eth/EthRpcModule.cs index 6adb304f637..f5bc1c96179 100644 --- a/src/Nethermind/Nethermind.JsonRpc/Modules/Eth/EthRpcModule.cs +++ b/src/Nethermind/Nethermind.JsonRpc/Modules/Eth/EthRpcModule.cs @@ -802,30 +802,43 @@ private ResultWrapper GetStateFailureResult(BlockHeader header }; } - public ResultWrapper eth_config() + public ResultWrapper eth_config(bool? showAllForks = null) { ForkActivationsSummary forks = forkInfo.GetForkActivationsSummary(_blockFinder.Head?.Header); + IReadOnlyList? allForkConfigs = null; - return ResultWrapper.Success(JsonNode.Parse(JsonSerializer.Serialize((new ForkConfigSummary + if (showAllForks is true) { - Current = GetForkConfig(forks.Current, _specProvider)!, - Next = GetForkConfig(forks.Next, _specProvider), - Last = GetForkConfig(forks.Last, _specProvider) - }), UnchangedDictionaryKeyOptions))); + IReadOnlyList forkSchedule = forkInfo.GetAllForks(); + List forkConfigs = new(forkSchedule.Count); - static ForkConfig? GetForkConfig(Fork? fork, ISpecProvider specProvider) - { - if (fork is null) + foreach (Fork scheduledFork in forkSchedule) { - return null; + forkConfigs.Add(BuildForkConfig(scheduledFork, _specProvider)); } - IReleaseSpec? spec = specProvider.GetSpec(fork.Value.Activation.BlockNumber, fork.Value.Activation.Timestamp); + allForkConfigs = forkConfigs; + } + + return ResultWrapper.Success(JsonNode.Parse(JsonSerializer.Serialize(new ForkConfigSummary + { + Current = BuildForkConfig(forks.Current, _specProvider), + Next = GetForkConfigOrNull(forks.Next, _specProvider), + Last = GetForkConfigOrNull(forks.Last, _specProvider), + All = allForkConfigs + }, UnchangedDictionaryKeyOptions))); + + static ForkConfig? GetForkConfigOrNull(Fork? fork, ISpecProvider specProvider) => + fork is null ? null : BuildForkConfig(fork.Value, specProvider); + + static ForkConfig BuildForkConfig(Fork fork, ISpecProvider specProvider) + { + IReleaseSpec spec = specProvider.GetSpec(fork.Activation.BlockNumber, fork.Activation.Timestamp); return new ForkConfig { - ActivationTime = fork.Value.Activation.Timestamp is not null ? (int)fork.Value.Activation.Timestamp : null, - ActivationBlock = fork.Value.Activation.Timestamp is null ? (int)fork.Value.Activation.BlockNumber : null, + ActivationTime = fork.Activation.Timestamp is not null ? (int)fork.Activation.Timestamp : null, + ActivationBlock = fork.Activation.Timestamp is null ? (int)fork.Activation.BlockNumber : null, BlobSchedule = spec.IsEip4844Enabled ? new BlobScheduleSettingsForRpc { BaseFeeUpdateFraction = (int)spec.BlobBaseFeeUpdateFraction, @@ -833,7 +846,7 @@ public ResultWrapper eth_config() Target = (int)spec.TargetBlobCount, } : null, ChainId = specProvider.ChainId, - ForkId = fork.Value.Id.HashBytes, + ForkId = fork.Id.HashBytes, Precompiles = spec.ListPrecompiles(), SystemContracts = spec.ListSystemContracts(), }; diff --git a/src/Nethermind/Nethermind.JsonRpc/Modules/Eth/IEthRpcModule.cs b/src/Nethermind/Nethermind.JsonRpc/Modules/Eth/IEthRpcModule.cs index 3e829e01007..e9ffc369f86 100644 --- a/src/Nethermind/Nethermind.JsonRpc/Modules/Eth/IEthRpcModule.cs +++ b/src/Nethermind/Nethermind.JsonRpc/Modules/Eth/IEthRpcModule.cs @@ -289,7 +289,7 @@ ResultWrapper eth_getTransactionByBlockNumberAndIndex( [JsonRpcMethod(IsImplemented = true, Description = "Retrieves Account with code and no storageRoot via Address and Blocknumber", IsSharable = true)] ResultWrapper eth_getAccountInfo([JsonRpcParameter(ExampleValue = "[\"0xaa00000000000000000000000000000000000000\", \"latest\"]")] Address accountAddress, BlockParameter? blockParameter = null); - [JsonRpcMethod(IsImplemented = true, Description = "Provides configuration data for the current and next fork", IsSharable = true)] - ResultWrapper eth_config(); + [JsonRpcMethod(IsImplemented = true, Description = "Provides configuration data for the current and next fork or the full fork schedule", IsSharable = true)] + ResultWrapper eth_config([JsonRpcParameter(Description = "Returns every known fork when true", ExampleValue = "[true]")] bool? showAllForks = null); } } diff --git a/src/Nethermind/Nethermind.Network/ForkInfo.cs b/src/Nethermind/Nethermind.Network/ForkInfo.cs index 0926e085f6a..fce73160130 100644 --- a/src/Nethermind/Nethermind.Network/ForkInfo.cs +++ b/src/Nethermind/Nethermind.Network/ForkInfo.cs @@ -176,5 +176,11 @@ public ForkActivationsSummary GetForkActivationsSummary(BlockHeader? head) Last = isNextPresent ? new Fork(Forks[^1].Activation, Forks[^1].Id) : null, }; } + + public IReadOnlyList GetAllForks() + { + EnsureInitialized(); + return Forks; + } } } diff --git a/src/Nethermind/Nethermind.Network/IForkInfo.cs b/src/Nethermind/Nethermind.Network/IForkInfo.cs index ed506a4a534..341ac367dca 100644 --- a/src/Nethermind/Nethermind.Network/IForkInfo.cs +++ b/src/Nethermind/Nethermind.Network/IForkInfo.cs @@ -1,6 +1,7 @@ // SPDX-FileCopyrightText: 2025 Demerzel Solutions Limited // SPDX-License-Identifier: LGPL-3.0-only +using System.Collections.Generic; using Nethermind.Core; using Nethermind.Core.Specs; @@ -19,6 +20,8 @@ public interface IForkInfo ValidationResult ValidateForkId(ForkId peerId, BlockHeader? head); ForkActivationsSummary GetForkActivationsSummary(BlockHeader? head); + + IReadOnlyList GetAllForks(); } public readonly record struct Fork(ForkActivation Activation, ForkId Id); From d8fc5893d3786f8f25564accbfdbd5ee164811cf Mon Sep 17 00:00:00 2001 From: Alexey Osipov Date: Mon, 1 Dec 2025 14:14:24 +0300 Subject: [PATCH 2/9] Flag is false by def; return array --- .../Nethermind.JsonRpc/Modules/Eth/EthRpcModule.cs | 10 +++++----- .../Nethermind.JsonRpc/Modules/Eth/IEthRpcModule.cs | 5 ++--- src/Nethermind/Nethermind.Network/ForkInfo.cs | 4 ++-- src/Nethermind/Nethermind.Network/IForkInfo.cs | 3 +-- 4 files changed, 10 insertions(+), 12 deletions(-) diff --git a/src/Nethermind/Nethermind.JsonRpc/Modules/Eth/EthRpcModule.cs b/src/Nethermind/Nethermind.JsonRpc/Modules/Eth/EthRpcModule.cs index f5bc1c96179..9914b22a253 100644 --- a/src/Nethermind/Nethermind.JsonRpc/Modules/Eth/EthRpcModule.cs +++ b/src/Nethermind/Nethermind.JsonRpc/Modules/Eth/EthRpcModule.cs @@ -1,4 +1,4 @@ -// SPDX-FileCopyrightText: 2023 Demerzel Solutions Limited +// SPDX-FileCopyrightText: 2025 Demerzel Solutions Limited // SPDX-License-Identifier: LGPL-3.0-only using System; @@ -802,15 +802,15 @@ private ResultWrapper GetStateFailureResult(BlockHeader header }; } - public ResultWrapper eth_config(bool? showAllForks = null) + public ResultWrapper eth_config(bool showAllForks = false) { ForkActivationsSummary forks = forkInfo.GetForkActivationsSummary(_blockFinder.Head?.Header); IReadOnlyList? allForkConfigs = null; - if (showAllForks is true) + if (showAllForks) { - IReadOnlyList forkSchedule = forkInfo.GetAllForks(); - List forkConfigs = new(forkSchedule.Count); + Fork[] forkSchedule = forkInfo.GetAllForks(); + List forkConfigs = new(forkSchedule.Length); foreach (Fork scheduledFork in forkSchedule) { diff --git a/src/Nethermind/Nethermind.JsonRpc/Modules/Eth/IEthRpcModule.cs b/src/Nethermind/Nethermind.JsonRpc/Modules/Eth/IEthRpcModule.cs index e9ffc369f86..8e033324126 100644 --- a/src/Nethermind/Nethermind.JsonRpc/Modules/Eth/IEthRpcModule.cs +++ b/src/Nethermind/Nethermind.JsonRpc/Modules/Eth/IEthRpcModule.cs @@ -1,7 +1,6 @@ -// SPDX-FileCopyrightText: 2023 Demerzel Solutions Limited +// SPDX-FileCopyrightText: 2025 Demerzel Solutions Limited // SPDX-License-Identifier: LGPL-3.0-only -using System; using System.Collections.Generic; using System.Text.Json.Nodes; using System.Threading.Tasks; @@ -290,6 +289,6 @@ ResultWrapper eth_getTransactionByBlockNumberAndIndex( ResultWrapper eth_getAccountInfo([JsonRpcParameter(ExampleValue = "[\"0xaa00000000000000000000000000000000000000\", \"latest\"]")] Address accountAddress, BlockParameter? blockParameter = null); [JsonRpcMethod(IsImplemented = true, Description = "Provides configuration data for the current and next fork or the full fork schedule", IsSharable = true)] - ResultWrapper eth_config([JsonRpcParameter(Description = "Returns every known fork when true", ExampleValue = "[true]")] bool? showAllForks = null); + ResultWrapper eth_config([JsonRpcParameter(Description = "Returns every known fork when true", ExampleValue = "[true]")] bool showAllForks = false); } } diff --git a/src/Nethermind/Nethermind.Network/ForkInfo.cs b/src/Nethermind/Nethermind.Network/ForkInfo.cs index fce73160130..742385839a3 100644 --- a/src/Nethermind/Nethermind.Network/ForkInfo.cs +++ b/src/Nethermind/Nethermind.Network/ForkInfo.cs @@ -1,4 +1,4 @@ -// SPDX-FileCopyrightText: 2022 Demerzel Solutions Limited +// SPDX-FileCopyrightText: 2025 Demerzel Solutions Limited // SPDX-License-Identifier: LGPL-3.0-only using Force.Crc32; @@ -177,7 +177,7 @@ public ForkActivationsSummary GetForkActivationsSummary(BlockHeader? head) }; } - public IReadOnlyList GetAllForks() + public Fork[] GetAllForks() { EnsureInitialized(); return Forks; diff --git a/src/Nethermind/Nethermind.Network/IForkInfo.cs b/src/Nethermind/Nethermind.Network/IForkInfo.cs index 341ac367dca..993e9a92a2b 100644 --- a/src/Nethermind/Nethermind.Network/IForkInfo.cs +++ b/src/Nethermind/Nethermind.Network/IForkInfo.cs @@ -1,7 +1,6 @@ // SPDX-FileCopyrightText: 2025 Demerzel Solutions Limited // SPDX-License-Identifier: LGPL-3.0-only -using System.Collections.Generic; using Nethermind.Core; using Nethermind.Core.Specs; @@ -21,7 +20,7 @@ public interface IForkInfo ForkActivationsSummary GetForkActivationsSummary(BlockHeader? head); - IReadOnlyList GetAllForks(); + Fork[] GetAllForks(); } public readonly record struct Fork(ForkActivation Activation, ForkId Id); From a54897f7bed999cb4cee61bbcaa06e7e3721002e Mon Sep 17 00:00:00 2001 From: Alexey Osipov Date: Mon, 1 Dec 2025 16:45:53 +0300 Subject: [PATCH 3/9] Cache --- .../Modules/Eth/EthRpcModule.cs | 20 ++++++++++++------- src/Nethermind/Nethermind.Network/ForkInfo.cs | 2 +- .../Nethermind.Network/IForkInfo.cs | 3 ++- 3 files changed, 16 insertions(+), 9 deletions(-) diff --git a/src/Nethermind/Nethermind.JsonRpc/Modules/Eth/EthRpcModule.cs b/src/Nethermind/Nethermind.JsonRpc/Modules/Eth/EthRpcModule.cs index 9914b22a253..ae01e47e6e5 100644 --- a/src/Nethermind/Nethermind.JsonRpc/Modules/Eth/EthRpcModule.cs +++ b/src/Nethermind/Nethermind.JsonRpc/Modules/Eth/EthRpcModule.cs @@ -2,6 +2,7 @@ // SPDX-License-Identifier: LGPL-3.0-only using System; +using System.Collections.Concurrent; using System.Collections.Generic; using System.Linq; using System.Security; @@ -67,6 +68,8 @@ public partial class EthRpcModule( IForkInfo forkInfo, ulong? secondsPerSlot) : IEthRpcModule { + private static readonly ConcurrentDictionary forkConfigCache = []; + protected readonly Encoding _messageEncoding = Encoding.UTF8; protected readonly IJsonRpcConfig _rpcConfig = rpcConfig ?? throw new ArgumentNullException(nameof(rpcConfig)); protected readonly IBlockchainBridge _blockchainBridge = blockchainBridge ?? throw new ArgumentNullException(nameof(blockchainBridge)); @@ -805,19 +808,17 @@ private ResultWrapper GetStateFailureResult(BlockHeader header public ResultWrapper eth_config(bool showAllForks = false) { ForkActivationsSummary forks = forkInfo.GetForkActivationsSummary(_blockFinder.Head?.Header); - IReadOnlyList? allForkConfigs = null; + List? allForkConfigs = null; if (showAllForks) { - Fork[] forkSchedule = forkInfo.GetAllForks(); - List forkConfigs = new(forkSchedule.Length); + ReadOnlySpan forkSchedule = forkInfo.GetAllForks(); + allForkConfigs = new(forkSchedule.Length); foreach (Fork scheduledFork in forkSchedule) { - forkConfigs.Add(BuildForkConfig(scheduledFork, _specProvider)); + allForkConfigs.Add(BuildForkConfig(scheduledFork, _specProvider)); } - - allForkConfigs = forkConfigs; } return ResultWrapper.Success(JsonNode.Parse(JsonSerializer.Serialize(new ForkConfigSummary @@ -833,9 +834,14 @@ public ResultWrapper eth_config(bool showAllForks = false) static ForkConfig BuildForkConfig(Fork fork, ISpecProvider specProvider) { + if (forkConfigCache.TryGetValue(fork.Id, out ForkConfig config)) + { + return config; + } + IReleaseSpec spec = specProvider.GetSpec(fork.Activation.BlockNumber, fork.Activation.Timestamp); - return new ForkConfig + return forkConfigCache[fork.Id] = new ForkConfig { ActivationTime = fork.Activation.Timestamp is not null ? (int)fork.Activation.Timestamp : null, ActivationBlock = fork.Activation.Timestamp is null ? (int)fork.Activation.BlockNumber : null, diff --git a/src/Nethermind/Nethermind.Network/ForkInfo.cs b/src/Nethermind/Nethermind.Network/ForkInfo.cs index 742385839a3..1bc869e806c 100644 --- a/src/Nethermind/Nethermind.Network/ForkInfo.cs +++ b/src/Nethermind/Nethermind.Network/ForkInfo.cs @@ -177,7 +177,7 @@ public ForkActivationsSummary GetForkActivationsSummary(BlockHeader? head) }; } - public Fork[] GetAllForks() + public ReadOnlySpan GetAllForks() { EnsureInitialized(); return Forks; diff --git a/src/Nethermind/Nethermind.Network/IForkInfo.cs b/src/Nethermind/Nethermind.Network/IForkInfo.cs index 993e9a92a2b..d6eb65bcf79 100644 --- a/src/Nethermind/Nethermind.Network/IForkInfo.cs +++ b/src/Nethermind/Nethermind.Network/IForkInfo.cs @@ -3,6 +3,7 @@ using Nethermind.Core; using Nethermind.Core.Specs; +using System; namespace Nethermind.Network; @@ -20,7 +21,7 @@ public interface IForkInfo ForkActivationsSummary GetForkActivationsSummary(BlockHeader? head); - Fork[] GetAllForks(); + ReadOnlySpan GetAllForks(); } public readonly record struct Fork(ForkActivation Activation, ForkId Id); From 6f571025cce0acdee90f5db3e92562ea013fc935 Mon Sep 17 00:00:00 2001 From: Alexey Osipov Date: Mon, 1 Dec 2025 18:35:51 +0300 Subject: [PATCH 4/9] Cache more --- .../Modules/Eth/EthRpcModule.cs | 36 +++++++++++-------- 1 file changed, 21 insertions(+), 15 deletions(-) diff --git a/src/Nethermind/Nethermind.JsonRpc/Modules/Eth/EthRpcModule.cs b/src/Nethermind/Nethermind.JsonRpc/Modules/Eth/EthRpcModule.cs index ae01e47e6e5..ee339f2f808 100644 --- a/src/Nethermind/Nethermind.JsonRpc/Modules/Eth/EthRpcModule.cs +++ b/src/Nethermind/Nethermind.JsonRpc/Modules/Eth/EthRpcModule.cs @@ -2,7 +2,7 @@ // SPDX-License-Identifier: LGPL-3.0-only using System; -using System.Collections.Concurrent; +using System.Collections.Frozen; using System.Collections.Generic; using System.Linq; using System.Security; @@ -68,7 +68,7 @@ public partial class EthRpcModule( IForkInfo forkInfo, ulong? secondsPerSlot) : IEthRpcModule { - private static readonly ConcurrentDictionary forkConfigCache = []; + private static FrozenDictionary? forkConfigCache = null; protected readonly Encoding _messageEncoding = Encoding.UTF8; protected readonly IJsonRpcConfig _rpcConfig = rpcConfig ?? throw new ArgumentNullException(nameof(rpcConfig)); @@ -808,6 +808,20 @@ private ResultWrapper GetStateFailureResult(BlockHeader header public ResultWrapper eth_config(bool showAllForks = false) { ForkActivationsSummary forks = forkInfo.GetForkActivationsSummary(_blockFinder.Head?.Header); + + if (forkConfigCache is null) + { + ReadOnlySpan forkSchedule = forkInfo.GetAllForks(); + Dictionary allForks = new(forkSchedule.Length); + + foreach (Fork scheduledFork in forkSchedule) + { + allForks.Add(scheduledFork.Id, BuildForkConfig(scheduledFork, _specProvider)); + } + + forkConfigCache = allForks.ToFrozenDictionary(); + } + List? allForkConfigs = null; if (showAllForks) @@ -817,31 +831,23 @@ public ResultWrapper eth_config(bool showAllForks = false) foreach (Fork scheduledFork in forkSchedule) { - allForkConfigs.Add(BuildForkConfig(scheduledFork, _specProvider)); + allForkConfigs.Add(forkConfigCache[scheduledFork.Id]); } } return ResultWrapper.Success(JsonNode.Parse(JsonSerializer.Serialize(new ForkConfigSummary { - Current = BuildForkConfig(forks.Current, _specProvider), - Next = GetForkConfigOrNull(forks.Next, _specProvider), - Last = GetForkConfigOrNull(forks.Last, _specProvider), + Current = forkConfigCache[forks.Current.Id], + Next = forks.Next is null ? null : forkConfigCache[forks.Next.Value.Id], + Last = forks.Last is null ? null : forkConfigCache[forks.Last.Value.Id], All = allForkConfigs }, UnchangedDictionaryKeyOptions))); - static ForkConfig? GetForkConfigOrNull(Fork? fork, ISpecProvider specProvider) => - fork is null ? null : BuildForkConfig(fork.Value, specProvider); - static ForkConfig BuildForkConfig(Fork fork, ISpecProvider specProvider) { - if (forkConfigCache.TryGetValue(fork.Id, out ForkConfig config)) - { - return config; - } - IReleaseSpec spec = specProvider.GetSpec(fork.Activation.BlockNumber, fork.Activation.Timestamp); - return forkConfigCache[fork.Id] = new ForkConfig + return new ForkConfig { ActivationTime = fork.Activation.Timestamp is not null ? (int)fork.Activation.Timestamp : null, ActivationBlock = fork.Activation.Timestamp is null ? (int)fork.Activation.BlockNumber : null, From 6639ff5721510098f6eae7f959238b7287f72c78 Mon Sep 17 00:00:00 2001 From: Alexey Osipov Date: Tue, 2 Dec 2025 16:34:19 +0300 Subject: [PATCH 5/9] Fix for OP --- src/Nethermind/Nethermind.Evm.Precompiles/Extensions.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Nethermind/Nethermind.Evm.Precompiles/Extensions.cs b/src/Nethermind/Nethermind.Evm.Precompiles/Extensions.cs index ebe22dcda94..32277fee092 100644 --- a/src/Nethermind/Nethermind.Evm.Precompiles/Extensions.cs +++ b/src/Nethermind/Nethermind.Evm.Precompiles/Extensions.cs @@ -52,7 +52,7 @@ public static OrderedDictionary ListPrecompiles(this IReleaseSp AddPrecompile(); } - if (spec.IsEip7951Enabled) + if (spec.IsRip7212Enabled || spec.IsEip7951Enabled) { AddPrecompile(); } From 4e3961e0a1bc93feaeb797888fa5d72ea3c97373 Mon Sep 17 00:00:00 2001 From: Alexey Osipov Date: Tue, 2 Dec 2025 21:19:17 +0300 Subject: [PATCH 6/9] Added a comment --- src/Nethermind/Nethermind.JsonRpc/Modules/Eth/IEthRpcModule.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Nethermind/Nethermind.JsonRpc/Modules/Eth/IEthRpcModule.cs b/src/Nethermind/Nethermind.JsonRpc/Modules/Eth/IEthRpcModule.cs index 8e033324126..7d4839698b8 100644 --- a/src/Nethermind/Nethermind.JsonRpc/Modules/Eth/IEthRpcModule.cs +++ b/src/Nethermind/Nethermind.JsonRpc/Modules/Eth/IEthRpcModule.cs @@ -289,6 +289,6 @@ ResultWrapper eth_getTransactionByBlockNumberAndIndex( ResultWrapper eth_getAccountInfo([JsonRpcParameter(ExampleValue = "[\"0xaa00000000000000000000000000000000000000\", \"latest\"]")] Address accountAddress, BlockParameter? blockParameter = null); [JsonRpcMethod(IsImplemented = true, Description = "Provides configuration data for the current and next fork or the full fork schedule", IsSharable = true)] - ResultWrapper eth_config([JsonRpcParameter(Description = "Returns every known fork when true", ExampleValue = "[true]")] bool showAllForks = false); + ResultWrapper eth_config([JsonRpcParameter(Description = "[Nethermind only] Additionally returns every known forks when true", ExampleValue = "[true]")] bool showAllForks = false); } } From abb72b3dcad9537f1e395ef5757cacec24109740 Mon Sep 17 00:00:00 2001 From: Alexey Osipov Date: Wed, 3 Dec 2025 17:48:37 +0300 Subject: [PATCH 7/9] Optimize more --- .../Modules/Eth/EthRpcModule.cs | 40 +++++++------------ 1 file changed, 14 insertions(+), 26 deletions(-) diff --git a/src/Nethermind/Nethermind.JsonRpc/Modules/Eth/EthRpcModule.cs b/src/Nethermind/Nethermind.JsonRpc/Modules/Eth/EthRpcModule.cs index ee339f2f808..df5bae2d08b 100644 --- a/src/Nethermind/Nethermind.JsonRpc/Modules/Eth/EthRpcModule.cs +++ b/src/Nethermind/Nethermind.JsonRpc/Modules/Eth/EthRpcModule.cs @@ -1,16 +1,6 @@ // SPDX-FileCopyrightText: 2025 Demerzel Solutions Limited // SPDX-License-Identifier: LGPL-3.0-only -using System; -using System.Collections.Frozen; -using System.Collections.Generic; -using System.Linq; -using System.Security; -using System.Text; -using System.Text.Json; -using System.Text.Json.Nodes; -using System.Threading; -using System.Threading.Tasks; using Nethermind.Blockchain.Filters; using Nethermind.Blockchain.Find; using Nethermind.Blockchain.Receipts; @@ -42,6 +32,17 @@ using Nethermind.Trie; using Nethermind.TxPool; using Nethermind.Wallet; +using System; +using System.Collections.Frozen; +using System.Collections.Generic; +using System.Collections.Immutable; +using System.Linq; +using System.Security; +using System.Text; +using System.Text.Json; +using System.Text.Json.Nodes; +using System.Threading; +using System.Threading.Tasks; using Block = Nethermind.Core.Block; using BlockHeader = Nethermind.Core.BlockHeader; using ResultType = Nethermind.Core.ResultType; @@ -812,35 +813,22 @@ public ResultWrapper eth_config(bool showAllForks = false) if (forkConfigCache is null) { ReadOnlySpan forkSchedule = forkInfo.GetAllForks(); - Dictionary allForks = new(forkSchedule.Length); + ImmutableArray>.Builder allForks = ImmutableArray.CreateBuilder>(forkSchedule.Length); foreach (Fork scheduledFork in forkSchedule) { - allForks.Add(scheduledFork.Id, BuildForkConfig(scheduledFork, _specProvider)); + allForks.Add(new KeyValuePair(scheduledFork.Id, BuildForkConfig(scheduledFork, _specProvider))); } forkConfigCache = allForks.ToFrozenDictionary(); } - List? allForkConfigs = null; - - if (showAllForks) - { - ReadOnlySpan forkSchedule = forkInfo.GetAllForks(); - allForkConfigs = new(forkSchedule.Length); - - foreach (Fork scheduledFork in forkSchedule) - { - allForkConfigs.Add(forkConfigCache[scheduledFork.Id]); - } - } - return ResultWrapper.Success(JsonNode.Parse(JsonSerializer.Serialize(new ForkConfigSummary { Current = forkConfigCache[forks.Current.Id], Next = forks.Next is null ? null : forkConfigCache[forks.Next.Value.Id], Last = forks.Last is null ? null : forkConfigCache[forks.Last.Value.Id], - All = allForkConfigs + All = showAllForks ? forkConfigCache.Values : null, }, UnchangedDictionaryKeyOptions))); static ForkConfig BuildForkConfig(Fork fork, ISpecProvider specProvider) From c29d15827b75581ef4888929fa6e2c0ec581e244 Mon Sep 17 00:00:00 2001 From: Alexey Osipov Date: Wed, 3 Dec 2025 19:49:19 +0300 Subject: [PATCH 8/9] Revert "Optimize more" This reverts commit abb72b3dcad9537f1e395ef5757cacec24109740. --- .../Modules/Eth/EthRpcModule.cs | 40 ++++++++++++------- 1 file changed, 26 insertions(+), 14 deletions(-) diff --git a/src/Nethermind/Nethermind.JsonRpc/Modules/Eth/EthRpcModule.cs b/src/Nethermind/Nethermind.JsonRpc/Modules/Eth/EthRpcModule.cs index df5bae2d08b..ee339f2f808 100644 --- a/src/Nethermind/Nethermind.JsonRpc/Modules/Eth/EthRpcModule.cs +++ b/src/Nethermind/Nethermind.JsonRpc/Modules/Eth/EthRpcModule.cs @@ -1,6 +1,16 @@ // SPDX-FileCopyrightText: 2025 Demerzel Solutions Limited // SPDX-License-Identifier: LGPL-3.0-only +using System; +using System.Collections.Frozen; +using System.Collections.Generic; +using System.Linq; +using System.Security; +using System.Text; +using System.Text.Json; +using System.Text.Json.Nodes; +using System.Threading; +using System.Threading.Tasks; using Nethermind.Blockchain.Filters; using Nethermind.Blockchain.Find; using Nethermind.Blockchain.Receipts; @@ -32,17 +42,6 @@ using Nethermind.Trie; using Nethermind.TxPool; using Nethermind.Wallet; -using System; -using System.Collections.Frozen; -using System.Collections.Generic; -using System.Collections.Immutable; -using System.Linq; -using System.Security; -using System.Text; -using System.Text.Json; -using System.Text.Json.Nodes; -using System.Threading; -using System.Threading.Tasks; using Block = Nethermind.Core.Block; using BlockHeader = Nethermind.Core.BlockHeader; using ResultType = Nethermind.Core.ResultType; @@ -813,22 +812,35 @@ public ResultWrapper eth_config(bool showAllForks = false) if (forkConfigCache is null) { ReadOnlySpan forkSchedule = forkInfo.GetAllForks(); - ImmutableArray>.Builder allForks = ImmutableArray.CreateBuilder>(forkSchedule.Length); + Dictionary allForks = new(forkSchedule.Length); foreach (Fork scheduledFork in forkSchedule) { - allForks.Add(new KeyValuePair(scheduledFork.Id, BuildForkConfig(scheduledFork, _specProvider))); + allForks.Add(scheduledFork.Id, BuildForkConfig(scheduledFork, _specProvider)); } forkConfigCache = allForks.ToFrozenDictionary(); } + List? allForkConfigs = null; + + if (showAllForks) + { + ReadOnlySpan forkSchedule = forkInfo.GetAllForks(); + allForkConfigs = new(forkSchedule.Length); + + foreach (Fork scheduledFork in forkSchedule) + { + allForkConfigs.Add(forkConfigCache[scheduledFork.Id]); + } + } + return ResultWrapper.Success(JsonNode.Parse(JsonSerializer.Serialize(new ForkConfigSummary { Current = forkConfigCache[forks.Current.Id], Next = forks.Next is null ? null : forkConfigCache[forks.Next.Value.Id], Last = forks.Last is null ? null : forkConfigCache[forks.Last.Value.Id], - All = showAllForks ? forkConfigCache.Values : null, + All = allForkConfigs }, UnchangedDictionaryKeyOptions))); static ForkConfig BuildForkConfig(Fork fork, ISpecProvider specProvider) From ce1776f5e5ce2f5e273286d62b5d9f626b6a5f50 Mon Sep 17 00:00:00 2001 From: Alexey Osipov Date: Wed, 3 Dec 2025 20:06:03 +0300 Subject: [PATCH 9/9] Improve --- src/Nethermind/Nethermind.Network.Enr/ForkId.cs | 14 ++++---------- 1 file changed, 4 insertions(+), 10 deletions(-) diff --git a/src/Nethermind/Nethermind.Network.Enr/ForkId.cs b/src/Nethermind/Nethermind.Network.Enr/ForkId.cs index 64b5741f426..447a1c76e79 100644 --- a/src/Nethermind/Nethermind.Network.Enr/ForkId.cs +++ b/src/Nethermind/Nethermind.Network.Enr/ForkId.cs @@ -1,4 +1,4 @@ -// SPDX-FileCopyrightText: 2022 Demerzel Solutions Limited +// SPDX-FileCopyrightText: 2025 Demerzel Solutions Limited // SPDX-License-Identifier: LGPL-3.0-only namespace Nethermind.Network.Enr; @@ -6,21 +6,15 @@ namespace Nethermind.Network.Enr; /// /// Represents the Ethereum Fork ID (hash of a list of fork block numbers and the next fork block number). /// -public struct ForkId +public struct ForkId(byte[] forkHash, long nextBlock) { - public ForkId(byte[] forkHash, long nextBlock) - { - ForkHash = forkHash; - NextBlock = nextBlock; - } - /// /// Hash of a list of the past fork block numbers. /// - public byte[] ForkHash { get; set; } + public byte[] ForkHash { get; set; } = forkHash; /// /// Block number of the next known fork (or 0 if no fork is expected). /// - public long NextBlock { get; set; } + public long NextBlock { get; set; } = nextBlock; }