Skip to content
Open
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
2 changes: 2 additions & 0 deletions src/Nethermind/Nethermind.JsonRpc/Data/ForkConfigSummary.cs
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@ public class ForkConfigSummary

[JsonIgnore(Condition = JsonIgnoreCondition.Never)]
public required ForkConfig? Last { get; init; }

public IReadOnlyList<ForkConfig>? All { get; init; }
}

public class ForkConfig
Expand Down
53 changes: 39 additions & 14 deletions src/Nethermind/Nethermind.JsonRpc/Modules/Eth/EthRpcModule.cs
Original file line number Diff line number Diff line change
@@ -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;
Expand Down Expand Up @@ -67,6 +68,8 @@ public partial class EthRpcModule(
IForkInfo forkInfo,
ulong? secondsPerSlot) : IEthRpcModule
{
private static FrozenDictionary<ForkId, ForkConfig>? 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));
Expand Down Expand Up @@ -802,38 +805,60 @@ private ResultWrapper<TResult> GetStateFailureResult<TResult>(BlockHeader header
};
}

public ResultWrapper<JsonNode> eth_config()
public ResultWrapper<JsonNode> eth_config(bool showAllForks = false)
{
ForkActivationsSummary forks = forkInfo.GetForkActivationsSummary(_blockFinder.Head?.Header);

return ResultWrapper<JsonNode>.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<Fork> forkSchedule = forkInfo.GetAllForks();
Dictionary<ForkId, ForkConfig> 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<ForkConfig>? allForkConfigs = null;

if (showAllForks)
{
if (fork is null)
ReadOnlySpan<Fork> 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<JsonNode>.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,
Max = (int)spec.MaxBlobCount,
Target = (int)spec.TargetBlobCount,
} : null,
ChainId = specProvider.ChainId,
ForkId = fork.Value.Id.HashBytes,
ForkId = fork.Id.HashBytes,
Precompiles = spec.ListPrecompiles(),
SystemContracts = spec.ListSystemContracts(),
};
Expand Down
Original file line number Diff line number Diff line change
@@ -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;
Expand Down Expand Up @@ -289,7 +288,7 @@ ResultWrapper<TransactionForRpc> eth_getTransactionByBlockNumberAndIndex(
[JsonRpcMethod(IsImplemented = true, Description = "Retrieves Account with code and no storageRoot via Address and Blocknumber", IsSharable = true)]
ResultWrapper<AccountInfoForRpc?> 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<JsonNode> eth_config();
[JsonRpcMethod(IsImplemented = true, Description = "Provides configuration data for the current and next fork or the full fork schedule", IsSharable = true)]
ResultWrapper<JsonNode> eth_config([JsonRpcParameter(Description = "Returns every known fork when true", ExampleValue = "[true]")] bool showAllForks = false);
}
}
8 changes: 7 additions & 1 deletion src/Nethermind/Nethermind.Network/ForkInfo.cs
Original file line number Diff line number Diff line change
@@ -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;
Expand Down Expand Up @@ -176,5 +176,11 @@ public ForkActivationsSummary GetForkActivationsSummary(BlockHeader? head)
Last = isNextPresent ? new Fork(Forks[^1].Activation, Forks[^1].Id) : null,
};
}

public ReadOnlySpan<Fork> GetAllForks()
{
EnsureInitialized();
return Forks;
}
}
}
3 changes: 3 additions & 0 deletions src/Nethermind/Nethermind.Network/IForkInfo.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@

using Nethermind.Core;
using Nethermind.Core.Specs;
using System;

namespace Nethermind.Network;

Expand All @@ -19,6 +20,8 @@ public interface IForkInfo
ValidationResult ValidateForkId(ForkId peerId, BlockHeader? head);

ForkActivationsSummary GetForkActivationsSummary(BlockHeader? head);

ReadOnlySpan<Fork> GetAllForks();
}

public readonly record struct Fork(ForkActivation Activation, ForkId Id);
Expand Down
Loading