From ed23eca78ab4e46df7726026716ce1c9b7fb747a Mon Sep 17 00:00:00 2001 From: Marc Harvey-Hill <10379486+Marchhill@users.noreply.github.com> Date: Mon, 17 Aug 2026 12:37:30 +0100 Subject: [PATCH 1/3] test: load the frame transaction fixtures by mapping their fork name The EIP-8141 fixture suites declare their network as Bogota, which SpecNameParser did not map, so every file failed to load. The failure surfaced as an unrelated Hash256 conversion error because ConvertToBlockchainTests wrapped both deserialization and conversion in the HalfBlockchainTestJson shape fallback: the unmapped fork name threw during conversion, and the retry against the trimmed shape then failed on its differently typed postState. Narrow the fallback to deserialization and name the fork in the exception. --- .../Ethereum.Test.Base/JsonToEthereumTest.cs | 14 +++++++--- .../SpecNameParserTests.cs | 27 +++++++++++++++++++ .../Nethermind.Specs/SpecNameParser.cs | 3 ++- 3 files changed, 39 insertions(+), 5 deletions(-) create mode 100644 src/Nethermind/Nethermind.Specs.Test/SpecNameParserTests.cs diff --git a/src/Nethermind/Ethereum.Test.Base/JsonToEthereumTest.cs b/src/Nethermind/Ethereum.Test.Base/JsonToEthereumTest.cs index b3267ec94c1a..07cf28abb655 100644 --- a/src/Nethermind/Ethereum.Test.Base/JsonToEthereumTest.cs +++ b/src/Nethermind/Ethereum.Test.Base/JsonToEthereumTest.cs @@ -417,16 +417,22 @@ private static List ConvertTransactionTests(Dictionary ConvertToBlockchainTests(string json) { - try { return ConvertToBlockchainTests(_serializer.Deserialize>(json)); } - catch (Exception) { return ConvertToBlockchainTests(CoerceFromHalf(_serializer.Deserialize>(json))); } + Dictionary tests; + try { tests = _serializer.Deserialize>(json); } + catch (Exception) { tests = CoerceFromHalf(_serializer.Deserialize>(json)); } + return ConvertToBlockchainTests(tests); } public static IEnumerable ConvertToBlockchainTests(ReadOnlySpan json) { - try { return ConvertToBlockchainTests(_serializer.Deserialize>(json)); } - catch (Exception) { return ConvertToBlockchainTests(CoerceFromHalf(_serializer.Deserialize>(json))); } + Dictionary tests; + try { tests = _serializer.Deserialize>(json); } + catch (Exception) { tests = CoerceFromHalf(_serializer.Deserialize>(json)); } + return ConvertToBlockchainTests(tests); } // Some BAL fixtures use the trimmed HalfBlockchainTestJson shape; coerce on demand. diff --git a/src/Nethermind/Nethermind.Specs.Test/SpecNameParserTests.cs b/src/Nethermind/Nethermind.Specs.Test/SpecNameParserTests.cs new file mode 100644 index 000000000000..01e9e8ff3939 --- /dev/null +++ b/src/Nethermind/Nethermind.Specs.Test/SpecNameParserTests.cs @@ -0,0 +1,27 @@ +// SPDX-FileCopyrightText: 2026 Demerzel Solutions Limited +// SPDX-License-Identifier: LGPL-3.0-only + +using System; +using Nethermind.Core.Specs; +using NUnit.Framework; + +namespace Nethermind.Specs.Test; + +public class SpecNameParserTests +{ + [Test] + public void Parse_maps_Bogota_to_the_frame_transactions_fork() + { + IReleaseSpec spec = SpecNameParser.Parse("Bogota"); + + Assert.That(spec.IsEip8141Enabled, Is.True); + } + + [Test] + public void Parse_names_the_offending_fork_when_unmapped() + { + NotSupportedException e = Assert.Throws(() => SpecNameParser.Parse("NotAFork")); + + Assert.That(e.Message, Does.Contain("NotAFork")); + } +} diff --git a/src/Nethermind/Nethermind.Specs/SpecNameParser.cs b/src/Nethermind/Nethermind.Specs/SpecNameParser.cs index 0ebf9b526966..b0ddb016f03a 100644 --- a/src/Nethermind/Nethermind.Specs/SpecNameParser.cs +++ b/src/Nethermind/Nethermind.Specs/SpecNameParser.cs @@ -62,7 +62,8 @@ private static IReleaseSpec ParseUncached(string specName) "BPO4" => BPO4.Instance, "BPO5" => BPO5.Instance, "Amsterdam" => Amsterdam.Instance, - _ => throw new NotSupportedException() + "Bogota" => Bogota.Instance, + _ => throw new NotSupportedException($"Unknown fork name '{specName}'") }; } } From 692f477a6d4f7af417d4dc971baa354ec3a248ee Mon Sep 17 00:00:00 2001 From: Marc Harvey-Hill <10379486+Marchhill@users.noreply.github.com> Date: Tue, 18 Aug 2026 18:23:54 +0100 Subject: [PATCH 2/3] test: chain both shape errors and name the resolved fork Bind both deserialization failures so a fixture matching neither shape reports both causes, and include the substituted fork name in the parser error. --- .../Ethereum.Test.Base/JsonToEthereumTest.cs | 40 ++++++++++++++++--- .../SpecNameParserTests.cs | 13 +++--- .../Nethermind.Specs/SpecNameParser.cs | 4 +- 3 files changed, 45 insertions(+), 12 deletions(-) diff --git a/src/Nethermind/Ethereum.Test.Base/JsonToEthereumTest.cs b/src/Nethermind/Ethereum.Test.Base/JsonToEthereumTest.cs index 07cf28abb655..07f6ab1f321a 100644 --- a/src/Nethermind/Ethereum.Test.Base/JsonToEthereumTest.cs +++ b/src/Nethermind/Ethereum.Test.Base/JsonToEthereumTest.cs @@ -417,21 +417,49 @@ private static List ConvertTransactionTests(DictionaryOnly deserialization falls back between shapes, so a conversion failure surfaces as itself. public static IEnumerable ConvertToBlockchainTests(string json) { Dictionary tests; - try { tests = _serializer.Deserialize>(json); } - catch (Exception) { tests = CoerceFromHalf(_serializer.Deserialize>(json)); } + try + { + tests = _serializer.Deserialize>(json); + } + catch (Exception standardShapeException) + { + try + { + tests = CoerceFromHalf(_serializer.Deserialize>(json)); + } + catch (Exception trimmedShapeException) + { + throw new AggregateException(NeitherShapeMessage, standardShapeException, trimmedShapeException); + } + } return ConvertToBlockchainTests(tests); } + /// Only deserialization falls back between shapes, so a conversion failure surfaces as itself. public static IEnumerable ConvertToBlockchainTests(ReadOnlySpan json) { Dictionary tests; - try { tests = _serializer.Deserialize>(json); } - catch (Exception) { tests = CoerceFromHalf(_serializer.Deserialize>(json)); } + try + { + tests = _serializer.Deserialize>(json); + } + catch (Exception standardShapeException) + { + try + { + tests = CoerceFromHalf(_serializer.Deserialize>(json)); + } + catch (Exception trimmedShapeException) + { + throw new AggregateException(NeitherShapeMessage, standardShapeException, trimmedShapeException); + } + } return ConvertToBlockchainTests(tests); } diff --git a/src/Nethermind/Nethermind.Specs.Test/SpecNameParserTests.cs b/src/Nethermind/Nethermind.Specs.Test/SpecNameParserTests.cs index 01e9e8ff3939..f724b1e29c56 100644 --- a/src/Nethermind/Nethermind.Specs.Test/SpecNameParserTests.cs +++ b/src/Nethermind/Nethermind.Specs.Test/SpecNameParserTests.cs @@ -3,6 +3,7 @@ using System; using Nethermind.Core.Specs; +using Nethermind.Specs.Forks; using NUnit.Framework; namespace Nethermind.Specs.Test; @@ -14,14 +15,16 @@ public void Parse_maps_Bogota_to_the_frame_transactions_fork() { IReleaseSpec spec = SpecNameParser.Parse("Bogota"); - Assert.That(spec.IsEip8141Enabled, Is.True); + Assert.That(spec, Is.SameAs(Bogota.Instance)); } - [Test] - public void Parse_names_the_offending_fork_when_unmapped() + [TestCase("NotAFork", "NotAFork")] + [TestCase("Merge+9999", "Paris+9999")] + public void Parse_names_the_offending_fork_when_unmapped(string specName, string resolvedSpecName) { - NotSupportedException e = Assert.Throws(() => SpecNameParser.Parse("NotAFork")); + NotSupportedException e = Assert.Throws(() => SpecNameParser.Parse(specName))!; - Assert.That(e.Message, Does.Contain("NotAFork")); + Assert.That(e.Message, Does.Contain(specName)); + Assert.That(e.Message, Does.Contain(resolvedSpecName)); } } diff --git a/src/Nethermind/Nethermind.Specs/SpecNameParser.cs b/src/Nethermind/Nethermind.Specs/SpecNameParser.cs index b0ddb016f03a..6095ab8fd50b 100644 --- a/src/Nethermind/Nethermind.Specs/SpecNameParser.cs +++ b/src/Nethermind/Nethermind.Specs/SpecNameParser.cs @@ -63,7 +63,9 @@ private static IReleaseSpec ParseUncached(string specName) "BPO5" => BPO5.Instance, "Amsterdam" => Amsterdam.Instance, "Bogota" => Bogota.Instance, - _ => throw new NotSupportedException($"Unknown fork name '{specName}'") + _ => throw new NotSupportedException(specName == unambiguousSpecName + ? $"Unknown fork name '{specName}'" + : $"Unknown fork name '{specName}' (resolved to '{unambiguousSpecName}')") }; } } From 89200dcd8c663eae5709404409551bd5dd273668 Mon Sep 17 00:00:00 2001 From: Marc Harvey-Hill <10379486+Marchhill@users.noreply.github.com> Date: Tue, 18 Aug 2026 20:23:16 +0100 Subject: [PATCH 3/3] test: forward the string fixture overload to the span one Both overloads carried the same shape-fallback block verbatim; the string overload has a single caller, so it can transcode and forward instead. --- .../Ethereum.Test.Base/JsonToEthereumTest.cs | 27 +++---------------- 1 file changed, 4 insertions(+), 23 deletions(-) diff --git a/src/Nethermind/Ethereum.Test.Base/JsonToEthereumTest.cs b/src/Nethermind/Ethereum.Test.Base/JsonToEthereumTest.cs index 07f6ab1f321a..81a8a4bb24e8 100644 --- a/src/Nethermind/Ethereum.Test.Base/JsonToEthereumTest.cs +++ b/src/Nethermind/Ethereum.Test.Base/JsonToEthereumTest.cs @@ -6,6 +6,7 @@ using System.Collections.Generic; using System.IO; using System.Linq; +using System.Text; using System.Text.Json; using Nethermind.Config; using Nethermind.Core; @@ -364,6 +365,7 @@ public static BlockchainTest Convert(string name, string category, BlockchainTes private static readonly EthereumJsonSerializer _serializer = new(); private static readonly ConcurrentDictionary _overriddenSpecs = new(); + private const string NeitherShapeMessage = "Fixture matches neither the standard nor the trimmed blockchain test shape."; public static IEnumerable ConvertStateTest(string json) => ConvertStateTests(_serializer.Deserialize>(json)); @@ -417,29 +419,8 @@ private static List ConvertTransactionTests(DictionaryOnly deserialization falls back between shapes, so a conversion failure surfaces as itself. - public static IEnumerable ConvertToBlockchainTests(string json) - { - Dictionary tests; - try - { - tests = _serializer.Deserialize>(json); - } - catch (Exception standardShapeException) - { - try - { - tests = CoerceFromHalf(_serializer.Deserialize>(json)); - } - catch (Exception trimmedShapeException) - { - throw new AggregateException(NeitherShapeMessage, standardShapeException, trimmedShapeException); - } - } - return ConvertToBlockchainTests(tests); - } + public static IEnumerable ConvertToBlockchainTests(string json) => + ConvertToBlockchainTests(Encoding.UTF8.GetBytes(json)); /// Only deserialization falls back between shapes, so a conversion failure surfaces as itself. public static IEnumerable ConvertToBlockchainTests(ReadOnlySpan json)