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(); } 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..ee339f2f808 100644 --- a/src/Nethermind/Nethermind.JsonRpc/Modules/Eth/EthRpcModule.cs +++ b/src/Nethermind/Nethermind.JsonRpc/Modules/Eth/EthRpcModule.cs @@ -1,7 +1,8 @@ -// SPDX-FileCopyrightText: 2023 Demerzel Solutions Limited +// 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; @@ -67,6 +68,8 @@ public partial class EthRpcModule( IForkInfo forkInfo, ulong? secondsPerSlot) : IEthRpcModule { + private static FrozenDictionary? forkConfigCache = null; + 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)); @@ -802,30 +805,52 @@ private ResultWrapper GetStateFailureResult(BlockHeader header }; } - public ResultWrapper eth_config() + public ResultWrapper eth_config(bool showAllForks = false) { ForkActivationsSummary forks = forkInfo.GetForkActivationsSummary(_blockFinder.Head?.Header); - return ResultWrapper.Success(JsonNode.Parse(JsonSerializer.Serialize((new ForkConfigSummary + if (forkConfigCache is null) { - Current = GetForkConfig(forks.Current, _specProvider)!, - Next = GetForkConfig(forks.Next, _specProvider), - Last = GetForkConfig(forks.Last, _specProvider) - }), UnchangedDictionaryKeyOptions))); + ReadOnlySpan forkSchedule = forkInfo.GetAllForks(); + Dictionary allForks = new(forkSchedule.Length); + + foreach (Fork scheduledFork in forkSchedule) + { + allForks.Add(scheduledFork.Id, BuildForkConfig(scheduledFork, _specProvider)); + } + + forkConfigCache = allForks.ToFrozenDictionary(); + } - static ForkConfig? GetForkConfig(Fork? fork, ISpecProvider specProvider) + List? allForkConfigs = null; + + if (showAllForks) { - if (fork is null) + ReadOnlySpan forkSchedule = forkInfo.GetAllForks(); + allForkConfigs = new(forkSchedule.Length); + + foreach (Fork scheduledFork in forkSchedule) { - return null; + allForkConfigs.Add(forkConfigCache[scheduledFork.Id]); } + } - IReleaseSpec? spec = specProvider.GetSpec(fork.Value.Activation.BlockNumber, fork.Value.Activation.Timestamp); + 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 + }, UnchangedDictionaryKeyOptions))); + + 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 +858,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..7d4839698b8 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; @@ -289,7 +288,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 = "[Nethermind only] Additionally returns every known forks when true", ExampleValue = "[true]")] bool showAllForks = false); } } 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; } diff --git a/src/Nethermind/Nethermind.Network/ForkInfo.cs b/src/Nethermind/Nethermind.Network/ForkInfo.cs index 0926e085f6a..1bc869e806c 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; @@ -176,5 +176,11 @@ public ForkActivationsSummary GetForkActivationsSummary(BlockHeader? head) Last = isNextPresent ? new Fork(Forks[^1].Activation, Forks[^1].Id) : null, }; } + + public ReadOnlySpan GetAllForks() + { + EnsureInitialized(); + return Forks; + } } } diff --git a/src/Nethermind/Nethermind.Network/IForkInfo.cs b/src/Nethermind/Nethermind.Network/IForkInfo.cs index ed506a4a534..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; @@ -19,6 +20,8 @@ public interface IForkInfo ValidationResult ValidateForkId(ForkId peerId, BlockHeader? head); ForkActivationsSummary GetForkActivationsSummary(BlockHeader? head); + + ReadOnlySpan GetAllForks(); } public readonly record struct Fork(ForkActivation Activation, ForkId Id);