diff --git a/src/Nethermind/Ethereum.Blockchain.Pyspec.Test/BerlinBlockChainTests.cs b/src/Nethermind/Ethereum.Blockchain.Pyspec.Test/BerlinBlockChainTests.cs
index 264bc859ca77..5e202049eaf8 100644
--- a/src/Nethermind/Ethereum.Blockchain.Pyspec.Test/BerlinBlockChainTests.cs
+++ b/src/Nethermind/Ethereum.Blockchain.Pyspec.Test/BerlinBlockChainTests.cs
@@ -1,5 +1,7 @@
+// SPDX-FileCopyrightText: 2025 Demerzel Solutions Limited
+// SPDX-License-Identifier: LGPL-3.0-only
+
using System.Collections.Generic;
-using System.Linq;
using System.Threading.Tasks;
using Ethereum.Test.Base;
using NUnit.Framework;
diff --git a/src/Nethermind/Ethereum.Blockchain.Pyspec.Test/BerlinStateTests.cs b/src/Nethermind/Ethereum.Blockchain.Pyspec.Test/BerlinStateTests.cs
index cdc20c50d11a..cc32ed244b0f 100644
--- a/src/Nethermind/Ethereum.Blockchain.Pyspec.Test/BerlinStateTests.cs
+++ b/src/Nethermind/Ethereum.Blockchain.Pyspec.Test/BerlinStateTests.cs
@@ -1,5 +1,7 @@
+// SPDX-FileCopyrightText: 2025 Demerzel Solutions Limited
+// SPDX-License-Identifier: LGPL-3.0-only
+
using System.Collections.Generic;
-using System.Linq;
using Ethereum.Test.Base;
using FluentAssertions;
using NUnit.Framework;
diff --git a/src/Nethermind/Ethereum.Blockchain.Pyspec.Test/ByzantiumBlockChainTests.cs b/src/Nethermind/Ethereum.Blockchain.Pyspec.Test/ByzantiumBlockChainTests.cs
index b6be913b4c5d..3583300d49c2 100644
--- a/src/Nethermind/Ethereum.Blockchain.Pyspec.Test/ByzantiumBlockChainTests.cs
+++ b/src/Nethermind/Ethereum.Blockchain.Pyspec.Test/ByzantiumBlockChainTests.cs
@@ -1,5 +1,7 @@
+// SPDX-FileCopyrightText: 2025 Demerzel Solutions Limited
+// SPDX-License-Identifier: LGPL-3.0-only
+
using System.Collections.Generic;
-using System.Linq;
using System.Threading.Tasks;
using Ethereum.Test.Base;
using NUnit.Framework;
diff --git a/src/Nethermind/Ethereum.Blockchain.Pyspec.Test/ByzantiumStateTests.cs b/src/Nethermind/Ethereum.Blockchain.Pyspec.Test/ByzantiumStateTests.cs
index a650f3c79666..7579dc2ed9fc 100644
--- a/src/Nethermind/Ethereum.Blockchain.Pyspec.Test/ByzantiumStateTests.cs
+++ b/src/Nethermind/Ethereum.Blockchain.Pyspec.Test/ByzantiumStateTests.cs
@@ -1,5 +1,7 @@
+// SPDX-FileCopyrightText: 2025 Demerzel Solutions Limited
+// SPDX-License-Identifier: LGPL-3.0-only
+
using System.Collections.Generic;
-using System.Linq;
using Ethereum.Test.Base;
using FluentAssertions;
using NUnit.Framework;
diff --git a/src/Nethermind/Ethereum.Blockchain.Pyspec.Test/CancunBlockChainTests.cs b/src/Nethermind/Ethereum.Blockchain.Pyspec.Test/CancunBlockChainTests.cs
index 62695ccc71fd..da29277f3419 100644
--- a/src/Nethermind/Ethereum.Blockchain.Pyspec.Test/CancunBlockChainTests.cs
+++ b/src/Nethermind/Ethereum.Blockchain.Pyspec.Test/CancunBlockChainTests.cs
@@ -2,7 +2,6 @@
// SPDX-License-Identifier: LGPL-3.0-only
using System.Collections.Generic;
-using System.Linq;
using System.Threading.Tasks;
using Ethereum.Test.Base;
using FluentAssertions;
diff --git a/src/Nethermind/Ethereum.Blockchain.Pyspec.Test/CancunStateTests.cs b/src/Nethermind/Ethereum.Blockchain.Pyspec.Test/CancunStateTests.cs
index f1f952b6adde..9a7d2402270d 100644
--- a/src/Nethermind/Ethereum.Blockchain.Pyspec.Test/CancunStateTests.cs
+++ b/src/Nethermind/Ethereum.Blockchain.Pyspec.Test/CancunStateTests.cs
@@ -2,7 +2,6 @@
// SPDX-License-Identifier: LGPL-3.0-only
using System.Collections.Generic;
-using System.Linq;
using Ethereum.Test.Base;
using FluentAssertions;
using NUnit.Framework;
diff --git a/src/Nethermind/Ethereum.Blockchain.Pyspec.Test/FrontierBlockChainTests.cs b/src/Nethermind/Ethereum.Blockchain.Pyspec.Test/FrontierBlockChainTests.cs
index fae15df4b213..0b3c9a4a6417 100644
--- a/src/Nethermind/Ethereum.Blockchain.Pyspec.Test/FrontierBlockChainTests.cs
+++ b/src/Nethermind/Ethereum.Blockchain.Pyspec.Test/FrontierBlockChainTests.cs
@@ -1,5 +1,7 @@
+// SPDX-FileCopyrightText: 2025 Demerzel Solutions Limited
+// SPDX-License-Identifier: LGPL-3.0-only
+
using System.Collections.Generic;
-using System.Linq;
using System.Threading.Tasks;
using Ethereum.Test.Base;
using NUnit.Framework;
diff --git a/src/Nethermind/Ethereum.Blockchain.Pyspec.Test/FrontierStateTests.cs b/src/Nethermind/Ethereum.Blockchain.Pyspec.Test/FrontierStateTests.cs
index 6011d779adf7..fbf265a22a8d 100644
--- a/src/Nethermind/Ethereum.Blockchain.Pyspec.Test/FrontierStateTests.cs
+++ b/src/Nethermind/Ethereum.Blockchain.Pyspec.Test/FrontierStateTests.cs
@@ -1,5 +1,7 @@
+// SPDX-FileCopyrightText: 2025 Demerzel Solutions Limited
+// SPDX-License-Identifier: LGPL-3.0-only
+
using System.Collections.Generic;
-using System.Linq;
using Ethereum.Test.Base;
using FluentAssertions;
using NUnit.Framework;
diff --git a/src/Nethermind/Ethereum.Blockchain.Pyspec.Test/HomesteadBlockChainTests.cs b/src/Nethermind/Ethereum.Blockchain.Pyspec.Test/HomesteadBlockChainTests.cs
index eba45011cbb3..fc6bc1c07b95 100644
--- a/src/Nethermind/Ethereum.Blockchain.Pyspec.Test/HomesteadBlockChainTests.cs
+++ b/src/Nethermind/Ethereum.Blockchain.Pyspec.Test/HomesteadBlockChainTests.cs
@@ -1,5 +1,7 @@
+// SPDX-FileCopyrightText: 2025 Demerzel Solutions Limited
+// SPDX-License-Identifier: LGPL-3.0-only
+
using System.Collections.Generic;
-using System.Linq;
using System.Threading.Tasks;
using Ethereum.Test.Base;
using NUnit.Framework;
diff --git a/src/Nethermind/Ethereum.Blockchain.Pyspec.Test/HomesteadStateTests.cs b/src/Nethermind/Ethereum.Blockchain.Pyspec.Test/HomesteadStateTests.cs
index 44ec8d4041f5..0fec88659d02 100644
--- a/src/Nethermind/Ethereum.Blockchain.Pyspec.Test/HomesteadStateTests.cs
+++ b/src/Nethermind/Ethereum.Blockchain.Pyspec.Test/HomesteadStateTests.cs
@@ -1,5 +1,7 @@
+// SPDX-FileCopyrightText: 2025 Demerzel Solutions Limited
+// SPDX-License-Identifier: LGPL-3.0-only
+
using System.Collections.Generic;
-using System.Linq;
using Ethereum.Test.Base;
using FluentAssertions;
using NUnit.Framework;
diff --git a/src/Nethermind/Ethereum.Blockchain.Pyspec.Test/IstanbulBlockChainTests.cs b/src/Nethermind/Ethereum.Blockchain.Pyspec.Test/IstanbulBlockChainTests.cs
index af62090dd9e9..4ad81bb85f3a 100644
--- a/src/Nethermind/Ethereum.Blockchain.Pyspec.Test/IstanbulBlockChainTests.cs
+++ b/src/Nethermind/Ethereum.Blockchain.Pyspec.Test/IstanbulBlockChainTests.cs
@@ -1,5 +1,7 @@
+// SPDX-FileCopyrightText: 2025 Demerzel Solutions Limited
+// SPDX-License-Identifier: LGPL-3.0-only
+
using System.Collections.Generic;
-using System.Linq;
using System.Threading.Tasks;
using Ethereum.Test.Base;
using NUnit.Framework;
diff --git a/src/Nethermind/Ethereum.Blockchain.Pyspec.Test/IstanbulStateTests.cs b/src/Nethermind/Ethereum.Blockchain.Pyspec.Test/IstanbulStateTests.cs
index a4daf560f351..fe0f84a61d36 100644
--- a/src/Nethermind/Ethereum.Blockchain.Pyspec.Test/IstanbulStateTests.cs
+++ b/src/Nethermind/Ethereum.Blockchain.Pyspec.Test/IstanbulStateTests.cs
@@ -1,5 +1,7 @@
+// SPDX-FileCopyrightText: 2025 Demerzel Solutions Limited
+// SPDX-License-Identifier: LGPL-3.0-only
+
using System.Collections.Generic;
-using System.Linq;
using Ethereum.Test.Base;
using FluentAssertions;
using NUnit.Framework;
diff --git a/src/Nethermind/Ethereum.Blockchain.Pyspec.Test/OsakaBlockChainTests.cs b/src/Nethermind/Ethereum.Blockchain.Pyspec.Test/OsakaBlockChainTests.cs
index 6e8cafbada58..99231fdd948d 100644
--- a/src/Nethermind/Ethereum.Blockchain.Pyspec.Test/OsakaBlockChainTests.cs
+++ b/src/Nethermind/Ethereum.Blockchain.Pyspec.Test/OsakaBlockChainTests.cs
@@ -2,7 +2,6 @@
// SPDX-License-Identifier: LGPL-3.0-only
using System.Collections.Generic;
-using System.Linq;
using System.Threading.Tasks;
using Ethereum.Test.Base;
using NUnit.Framework;
diff --git a/src/Nethermind/Ethereum.Blockchain.Pyspec.Test/OsakaEofTests.cs b/src/Nethermind/Ethereum.Blockchain.Pyspec.Test/OsakaEofTests.cs
index 28326dea3909..874f6a40f6bb 100644
--- a/src/Nethermind/Ethereum.Blockchain.Pyspec.Test/OsakaEofTests.cs
+++ b/src/Nethermind/Ethereum.Blockchain.Pyspec.Test/OsakaEofTests.cs
@@ -2,7 +2,6 @@
// SPDX-License-Identifier: LGPL-3.0-only
using System.Collections.Generic;
-using System.Linq;
using Ethereum.Test.Base;
using NUnit.Framework;
diff --git a/src/Nethermind/Ethereum.Blockchain.Pyspec.Test/OsakaStateTests.cs b/src/Nethermind/Ethereum.Blockchain.Pyspec.Test/OsakaStateTests.cs
index 097c9c8f658c..d9c6248b9404 100644
--- a/src/Nethermind/Ethereum.Blockchain.Pyspec.Test/OsakaStateTests.cs
+++ b/src/Nethermind/Ethereum.Blockchain.Pyspec.Test/OsakaStateTests.cs
@@ -2,7 +2,6 @@
// SPDX-License-Identifier: LGPL-3.0-only
using System.Collections.Generic;
-using System.Linq;
using Ethereum.Test.Base;
using FluentAssertions;
using NUnit.Framework;
diff --git a/src/Nethermind/Ethereum.Blockchain.Pyspec.Test/ShanghaiBlockChainTests.cs b/src/Nethermind/Ethereum.Blockchain.Pyspec.Test/ShanghaiBlockChainTests.cs
index 2d273d715659..2e85ba8918d9 100644
--- a/src/Nethermind/Ethereum.Blockchain.Pyspec.Test/ShanghaiBlockChainTests.cs
+++ b/src/Nethermind/Ethereum.Blockchain.Pyspec.Test/ShanghaiBlockChainTests.cs
@@ -2,7 +2,6 @@
// SPDX-License-Identifier: LGPL-3.0-only
using System.Collections.Generic;
-using System.Linq;
using System.Threading.Tasks;
using Ethereum.Test.Base;
using NUnit.Framework;
diff --git a/src/Nethermind/Ethereum.Blockchain.Pyspec.Test/ShanghaiStateTests.cs b/src/Nethermind/Ethereum.Blockchain.Pyspec.Test/ShanghaiStateTests.cs
index 5c75a9b80c63..b8e304a15167 100644
--- a/src/Nethermind/Ethereum.Blockchain.Pyspec.Test/ShanghaiStateTests.cs
+++ b/src/Nethermind/Ethereum.Blockchain.Pyspec.Test/ShanghaiStateTests.cs
@@ -1,5 +1,7 @@
+// SPDX-FileCopyrightText: 2025 Demerzel Solutions Limited
+// SPDX-License-Identifier: LGPL-3.0-only
+
using System.Collections.Generic;
-using System.Linq;
using Ethereum.Test.Base;
using FluentAssertions;
using NUnit.Framework;
diff --git a/src/Nethermind/Ethereum.Test.Base/BlockchainTest.cs b/src/Nethermind/Ethereum.Test.Base/BlockchainTest.cs
index 6329281f660f..6916dd59eb53 100644
--- a/src/Nethermind/Ethereum.Test.Base/BlockchainTest.cs
+++ b/src/Nethermind/Ethereum.Test.Base/BlockchainTest.cs
@@ -20,6 +20,7 @@ public class BlockchainTest : EthereumTest
public TestBlockJson[]? Blocks { get; set; }
public TestBlockHeaderJson? GenesisBlockHeader { get; set; }
+ public TestEngineNewPayloadsJson[]? EngineNewPayloads { get; set; }
public Dictionary
? Pre { get; set; }
public Dictionary? PostState { get; set; }
diff --git a/src/Nethermind/Ethereum.Test.Base/BlockchainTestBase.cs b/src/Nethermind/Ethereum.Test.Base/BlockchainTestBase.cs
index 296f3da23930..374f738ab79d 100644
--- a/src/Nethermind/Ethereum.Test.Base/BlockchainTestBase.cs
+++ b/src/Nethermind/Ethereum.Test.Base/BlockchainTestBase.cs
@@ -31,8 +31,11 @@
using Nethermind.Specs.Test;
using Nethermind.Evm.State;
using Nethermind.Init.Modules;
-using Nethermind.TxPool;
using NUnit.Framework;
+using Nethermind.Merge.Plugin.Data;
+using Nethermind.Merge.Plugin;
+using Nethermind.JsonRpc;
+using System.Reflection;
namespace Ethereum.Test.Base;
@@ -40,14 +43,12 @@ public abstract class BlockchainTestBase
{
private static readonly ILogger _logger;
private static readonly ILogManager _logManager = new TestLogManager(LogLevel.Warn);
- private static ISealValidator Sealer { get; }
private static DifficultyCalculatorWrapper DifficultyCalculator { get; }
+ private const int _genesisProcessingTimeoutMs = 5000;
static BlockchainTestBase()
{
DifficultyCalculator = new DifficultyCalculatorWrapper();
- Sealer = new EthashSealValidator(_logManager, DifficultyCalculator, new CryptoRandom(), new Ethash(_logManager), Timestamper.Default); // temporarily keep reusing the same one as otherwise it would recreate cache for each test
-
_logManager ??= LimboLogs.Instance;
_logger = _logManager.GetClassLogger();
}
@@ -83,8 +84,13 @@ protected async Task RunTest(BlockchainTest test, Stopwatch?
test.Network = ChainUtils.ResolveSpec(test.Network, test.ChainId);
test.NetworkAfterTransition = ChainUtils.ResolveSpec(test.NetworkAfterTransition, test.ChainId);
+ bool isEngineTest = test.Blocks is null && test.EngineNewPayloads is not null;
+
List<(ForkActivation Activation, IReleaseSpec Spec)> transitions =
+ isEngineTest ?
+ [((ForkActivation)0, test.Network)] :
[((ForkActivation)0, test.GenesisSpec), ((ForkActivation)1, test.Network)]; // TODO: this thing took a lot of time to find after it was removed!, genesis block is always initialized with Frontier
+
if (test.NetworkAfterTransition is not null)
{
transitions.Add((test.TransitionForkActivation!.Value, test.NetworkAfterTransition));
@@ -92,7 +98,7 @@ protected async Task RunTest(BlockchainTest test, Stopwatch?
ISpecProvider specProvider = new CustomSpecProvider(test.ChainId, test.ChainId, transitions.ToArray());
- Assert.That(test.ChainId == GnosisSpecProvider.Instance.ChainId || specProvider.GenesisSpec == Frontier.Instance, "Expected genesis spec to be Frontier for blockchain tests");
+ Assert.That(isEngineTest || test.ChainId == GnosisSpecProvider.Instance.ChainId || specProvider.GenesisSpec == Frontier.Instance, "Expected genesis spec to be Frontier for blockchain tests");
if (test.Network is Cancun || test.NetworkAfterTransition is Cancun)
{
@@ -122,18 +128,23 @@ protected async Task RunTest(BlockchainTest test, Stopwatch?
IConfigProvider configProvider = new ConfigProvider();
// configProvider.GetConfig().PreWarmStateOnBlockProcessing = false;
- await using IContainer container = new ContainerBuilder()
+ ContainerBuilder containerBuilder = new ContainerBuilder()
.AddModule(new TestNethermindModule(configProvider))
.AddSingleton(specProvider)
.AddSingleton(_logManager)
.AddSingleton(rewardCalculator)
- .AddSingleton(DifficultyCalculator)
- .AddSingleton(NullTxPool.Instance)
- .Build();
+ .AddSingleton(DifficultyCalculator);
+
+ if (isEngineTest)
+ {
+ containerBuilder.AddModule(new TestMergeModule(configProvider));
+ }
+
+ await using IContainer container = containerBuilder.Build();
IMainProcessingContext mainBlockProcessingContext = container.Resolve();
IWorldState stateProvider = mainBlockProcessingContext.WorldState;
- IBlockchainProcessor blockchainProcessor = mainBlockProcessingContext.BlockchainProcessor;
+ BlockchainProcessor blockchainProcessor = (BlockchainProcessor)mainBlockProcessingContext.BlockchainProcessor;
IBlockTree blockTree = container.Resolve();
IBlockValidator blockValidator = container.Resolve();
blockchainProcessor.Start();
@@ -165,85 +176,66 @@ protected async Task RunTest(BlockchainTest test, Stopwatch?
{
if (args.Block.Number == 0)
{
- Assert.That(stateProvider.HasStateForBlock(genesisBlock.Header), Is.EqualTo(true));
+ Assert.That(stateProvider.HasStateForBlock(genesisBlock.Header), Is.True);
+ genesisProcessed.Set();
+ }
+ };
+
+ blockchainProcessor.BlockRemoved += (_, args) =>
+ {
+ if (args.ProcessingResult != ProcessingResult.Success && args.BlockHash == genesisBlock.Header.Hash)
+ {
+ Assert.Fail($"Failed to process genesis block: {args.Exception}");
genesisProcessed.Set();
}
};
blockTree.SuggestBlock(genesisBlock);
- genesisProcessed.WaitOne();
+ genesisProcessed.WaitOne(_genesisProcessingTimeoutMs);
parentHeader = genesisBlock.Header;
// Dispose genesis block's AccountChanges
genesisBlock.DisposeAccountChanges();
}
- List<(Block Block, string ExpectedException)> correctRlp = DecodeRlps(test, failOnInvalidRlp);
- for (int i = 0; i < correctRlp.Count; i++)
+ if (test.Blocks is not null)
{
- // Mimic the actual behaviour where block goes through validating sync manager
- correctRlp[i].Block.Header.IsPostMerge = correctRlp[i].Block.Difficulty == 0;
-
- // For tests with reorgs, find the actual parent header from block tree
- parentHeader = blockTree.FindHeader(correctRlp[i].Block.ParentHash) ?? parentHeader;
-
- Assert.That(correctRlp[i].Block.Hash is not null, $"null hash in {test.Name} block {i}");
-
- bool expectsException = correctRlp[i].ExpectedException is not null;
- // Validate block structure first (mimics SyncServer validation)
- if (blockValidator.ValidateSuggestedBlock(correctRlp[i].Block, parentHeader, out string? validationError))
- {
- Assert.That(!expectsException, $"Expected block {correctRlp[i].Block.Hash} to fail with '{correctRlp[i].ExpectedException}', but it passed validation");
- try
- {
- // All validations passed, suggest the block
- blockTree.SuggestBlock(correctRlp[i].Block);
-
- }
- catch (InvalidBlockException e)
- {
- // Exception thrown during block processing
- Assert.That(expectsException, $"Unexpected invalid block {correctRlp[i].Block.Hash}: {validationError}, Exception: {e}");
- // else: Expected to fail and did fail via exception → this is correct behavior
- }
- catch (Exception e)
- {
- Assert.Fail($"Unexpected exception during processing: {e}");
- }
- finally
- {
- // Dispose AccountChanges to prevent memory leaks in tests
- correctRlp[i].Block.DisposeAccountChanges();
- }
- }
- else
- {
- // Validation FAILED
- Assert.That(expectsException, $"Unexpected invalid block {correctRlp[i].Block.Hash}: {validationError}");
- // else: Expected to fail and did fail → this is correct behavior
- }
-
- parentHeader = correctRlp[i].Block.Header;
+ // blockchain test
+ parentHeader = SuggestBlocks(test, failOnInvalidRlp, blockValidator, blockTree, parentHeader);
+ }
+ else if (test.EngineNewPayloads is not null)
+ {
+ // engine test
+ IEngineRpcModule engineRpcModule = container.Resolve();
+ await RunNewPayloads(test.EngineNewPayloads, engineRpcModule);
+ }
+ else
+ {
+ Assert.Fail("Invalid blockchain test, did not contain blocks or new payloads.");
}
// NOTE: Tracer removal must happen AFTER StopAsync to ensure all blocks are traced
// Blocks are queued asynchronously, so we need to wait for processing to complete
await blockchainProcessor.StopAsync(true);
-
stopwatch?.Stop();
IBlockCachePreWarmer? preWarmer = container.Resolve().LifetimeScope.ResolveOptional();
- if (preWarmer is not null)
+
+ // Caches are cleared async, which is a problem as read for the MainWorldState with prewarmer is not correct if its not cleared.
+ preWarmer?.ClearCaches();
+
+ Block? headBlock = blockTree.RetrieveHeadBlock();
+
+ Assert.That(headBlock, Is.Not.Null);
+ if (headBlock is null)
{
- // Caches are cleared async, which is a problem as read for the MainWorldState with prewarmer is not correct if its not cleared.
- preWarmer.ClearCaches();
+ return new EthereumTestResult(test.Name, null, false);
}
- Block? headBlock = blockTree.RetrieveHeadBlock();
List differences;
using (stateProvider.BeginScope(headBlock.Header))
{
- differences = RunAssertions(test, blockTree.RetrieveHeadBlock(), stateProvider);
+ differences = RunAssertions(test, headBlock, stateProvider);
}
bool testPassed = differences.Count == 0;
@@ -256,14 +248,8 @@ protected async Task RunTest(BlockchainTest test, Stopwatch?
blockchainProcessor.Tracers.Remove(tracer);
}
- Assert.That(differences.Count, Is.Zero, "differences");
-
- return new EthereumTestResult
- (
- test.Name,
- null,
- testPassed
- );
+ Assert.That(differences, Is.Empty, "differences");
+ return new EthereumTestResult(test.Name, null, testPassed);
}
catch (Exception)
{
@@ -272,9 +258,93 @@ protected async Task RunTest(BlockchainTest test, Stopwatch?
}
}
- private List<(Block Block, string ExpectedException)> DecodeRlps(BlockchainTest test, bool failOnInvalidRlp)
+ private static BlockHeader SuggestBlocks(BlockchainTest test, bool failOnInvalidRlp, IBlockValidator blockValidator, IBlockTree blockTree, BlockHeader parentHeader)
+ {
+ List<(Block Block, string ExpectedException)> correctRlp = DecodeRlps(test, failOnInvalidRlp);
+ for (int i = 0; i < correctRlp.Count; i++)
+ {
+ // Mimic the actual behaviour where block goes through validating sync manager
+ correctRlp[i].Block.Header.IsPostMerge = correctRlp[i].Block.Difficulty == 0;
+
+ // For tests with reorgs, find the actual parent header from block tree
+ parentHeader = blockTree.FindHeader(correctRlp[i].Block.ParentHash) ?? parentHeader;
+
+ Assert.That(correctRlp[i].Block.Hash, Is.Not.Null, $"null hash in {test.Name} block {i}");
+
+ bool expectsException = correctRlp[i].ExpectedException is not null;
+ // Validate block structure first (mimics SyncServer validation)
+ if (blockValidator.ValidateSuggestedBlock(correctRlp[i].Block, parentHeader, out string? validationError))
+ {
+ Assert.That(!expectsException, $"Expected block {correctRlp[i].Block.Hash} to fail with '{correctRlp[i].ExpectedException}', but it passed validation");
+ try
+ {
+ // All validations passed, suggest the block
+ blockTree.SuggestBlock(correctRlp[i].Block);
+
+ }
+ catch (InvalidBlockException e)
+ {
+ // Exception thrown during block processing
+ Assert.That(expectsException, $"Unexpected invalid block {correctRlp[i].Block.Hash}: {validationError}, Exception: {e}");
+ // else: Expected to fail and did fail via exception → this is correct behavior
+ }
+ catch (Exception e)
+ {
+ Assert.Fail($"Unexpected exception during processing: {e}");
+ }
+ finally
+ {
+ // Dispose AccountChanges to prevent memory leaks in tests
+ correctRlp[i].Block.DisposeAccountChanges();
+ }
+ }
+ else
+ {
+ // Validation FAILED
+ Assert.That(expectsException, $"Unexpected invalid block {correctRlp[i].Block.Hash}: {validationError}");
+ // else: Expected to fail and did fail → this is correct behavior
+ }
+
+ parentHeader = correctRlp[i].Block.Header;
+ }
+ return parentHeader;
+ }
+
+ private async static Task RunNewPayloads(TestEngineNewPayloadsJson[]? newPayloads, IEngineRpcModule engineRpcModule)
+ {
+ (ExecutionPayloadV3, string[]?, string[]?, int, int)[] payloads = [.. JsonToEthereumTest.Convert(newPayloads)];
+
+ // blockchain test engine
+ foreach ((ExecutionPayload executionPayload, string[]? blobVersionedHashes, string[]? validationError, int newPayloadVersion, int fcuVersion) in payloads)
+ {
+ ResultWrapper res;
+ byte[]?[] hashes = blobVersionedHashes is null ? [] : [.. blobVersionedHashes.Select(x => Bytes.FromHexString(x))];
+
+ MethodInfo newPayloadMethod = engineRpcModule.GetType().GetMethod($"engine_newPayloadV{newPayloadVersion}");
+ List