From cdad65bca0445c4ff1e2cbc77daaa73b6a32b584 Mon Sep 17 00:00:00 2001 From: Marc Harvey-Hill <10379486+Marchhill@users.noreply.github.com> Date: Wed, 1 Oct 2025 15:35:51 +0100 Subject: [PATCH 01/26] initial commit --- .../Ethereum.Test.Base/BlockchainTest.cs | 1 + .../Ethereum.Test.Base/BlockchainTestBase.cs | 152 +++++++----- .../Ethereum.Test.Base/BlockchainTestJson.cs | 1 + .../Ethereum.Test.Base/JsonToEthereumTest.cs | 232 +++++++++++------- .../Ethereum.Test.Base/TestBlockHeaderJson.cs | 7 + .../TestEngineNewPayloadsJson.cs | 45 ++++ .../BeaconBlockRootHandlerTests.cs | 2 - .../ExecutionRequestsProcessorMock.cs | 1 - .../Nethermind.Merge.Plugin/MergePlugin.cs | 2 +- .../NoEngineRequestTracker.cs | 17 ++ .../StateProviderTests.cs | 2 - .../BlockchainTestsRunner.cs | 13 +- 12 files changed, 326 insertions(+), 149 deletions(-) create mode 100644 src/Nethermind/Ethereum.Test.Base/TestEngineNewPayloadsJson.cs create mode 100644 src/Nethermind/Nethermind.Merge.Plugin/NoEngineRequestTracker.cs diff --git a/src/Nethermind/Ethereum.Test.Base/BlockchainTest.cs b/src/Nethermind/Ethereum.Test.Base/BlockchainTest.cs index 19f62afb8007..b4f78c9a0579 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 55e59e9e10c0..129fef10d8c3 100644 --- a/src/Nethermind/Ethereum.Test.Base/BlockchainTestBase.cs +++ b/src/Nethermind/Ethereum.Test.Base/BlockchainTestBase.cs @@ -9,7 +9,6 @@ using System.Threading; using System.Threading.Tasks; using Autofac; -using Nethermind.Api; using Nethermind.Blockchain; using Nethermind.Blockchain.Find; using Nethermind.Config; @@ -30,26 +29,25 @@ using Nethermind.Specs; using Nethermind.Specs.Forks; using Nethermind.Specs.Test; -using Nethermind.State; 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; namespace Ethereum.Test.Base; public abstract class BlockchainTestBase { - private static ILogger _logger; - private static ILogManager _logManager = new TestLogManager(LogLevel.Warn); - private static ISealValidator Sealer { get; } + private static readonly ILogger _logger; + private static readonly ILogManager _logManager = new TestLogManager(LogLevel.Warn); private static DifficultyCalculatorWrapper DifficultyCalculator { get; } 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(); } @@ -129,6 +127,7 @@ protected async Task RunTest(BlockchainTest test, Stopwatch? // configProvider.GetConfig().PreWarmStateOnBlockProcessing = false; await using IContainer container = new ContainerBuilder() .AddModule(new TestNethermindModule(configProvider)) + .AddModule(new TestMergeModule(configProvider)) .AddSingleton(specProvider) .AddSingleton(_logManager) .AddSingleton(rewardCalculator) @@ -141,9 +140,11 @@ protected async Task RunTest(BlockchainTest test, Stopwatch? IBlockchainProcessor blockchainProcessor = mainBlockProcessingContext.BlockchainProcessor; IBlockTree blockTree = container.Resolve(); IBlockValidator blockValidator = container.Resolve(); + IEngineRpcModule engineRpcModule = container.Resolve(); blockchainProcessor.Start(); BlockHeader parentHeader; + (ExecutionPayload, string[]?, string[]?, string?)[] payloads; // Genesis processing using (stateProvider.BeginScope(null)) { @@ -154,15 +155,18 @@ protected async Task RunTest(BlockchainTest test, Stopwatch? test.GenesisRlp ??= Rlp.Encode(new Block(JsonToEthereumTest.Convert(test.GenesisBlockHeader))); Block genesisBlock = Rlp.Decode(test.GenesisRlp.Bytes); + // Console.WriteLine(genesisBlock.ToString(Block.Format.Full)); Assert.That(genesisBlock.Header.Hash, Is.EqualTo(new Hash256(test.GenesisBlockHeader.Hash))); + payloads = [.. JsonToEthereumTest.Convert(test.EngineNewPayloads)]; + ManualResetEvent genesisProcessed = new(false); blockTree.NewHeadBlock += (_, args) => { if (args.Block.Number == 0) { - Assert.That(stateProvider.HasStateForBlock(genesisBlock.Header), Is.EqualTo(true)); + Assert.That(stateProvider.HasStateForBlock(genesisBlock.Header), Is.True); genesisProcessed.Set(); } }; @@ -170,11 +174,72 @@ protected async Task RunTest(BlockchainTest test, Stopwatch? blockTree.SuggestBlock(genesisBlock); genesisProcessed.WaitOne(); parentHeader = genesisBlock.Header; + } + + if (test.Blocks is not null) + { + // blockchain test + parentHeader = SuggestBlocks(test, failOnInvalidRlp, blockValidator, blockTree, parentHeader); + } + else if (test.EngineNewPayloads is not null) + { + // blockchain test engine + foreach ((ExecutionPayload executionPayload, string[]? blobVersionedHashes, string[]? validationError, string? newPayloadVersion) in payloads) + { + ResultWrapper res; + switch (newPayloadVersion ?? "4") + { + case "1": + res = await engineRpcModule.engine_newPayloadV1(executionPayload); + break; + case "2": + res = await engineRpcModule.engine_newPayloadV2(executionPayload); + break; + case "3": + res = await engineRpcModule.engine_newPayloadV3((ExecutionPayloadV3)executionPayload, [], executionPayload.ParentBeaconBlockRoot); + break; + case "4": + byte[]?[] hashes = blobVersionedHashes is null ? null : [.. blobVersionedHashes.Select(x => Bytes.FromHexString(x))]; + res = await engineRpcModule.engine_newPayloadV4((ExecutionPayloadV3)executionPayload, hashes, executionPayload.ParentBeaconBlockRoot, []); + break; + default: + Assert.Fail("Invalid blockchain engine test, version not recognised."); + break; + } + } + } + else + { + Assert.Fail("Invalid blockchain test, did not contain blocks or new payloads."); + } + + await blockchainProcessor.StopAsync(true); + stopwatch?.Stop(); + + IBlockCachePreWarmer? preWarmer = container.Resolve().LifetimeScope.ResolveOptional(); - // Dispose genesis block's AccountChanges - genesisBlock.DisposeAccountChanges(); + // 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(); + List differences; + using (stateProvider.BeginScope(headBlock.Header)) + { + differences = RunAssertions(test, blockTree.RetrieveHeadBlock(), stateProvider); } + Assert.That(differences, Is.Empty, "differences"); + + return new EthereumTestResult + ( + test.Name, + null, + differences.Count == 0 + ); + } + + 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++) { @@ -210,52 +275,26 @@ protected async Task RunTest(BlockchainTest test, Stopwatch? { Assert.Fail($"Unexpected exception during processing: {e}"); } - finally - { - // Dispose AccountChanges to prevent memory leaks in tests - correctRlp[i].Block.DisposeAccountChanges(); - } parentHeader = correctRlp[i].Block.Header; } - 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(); - List differences; - using (stateProvider.BeginScope(headBlock.Header)) - { - differences = RunAssertions(test, blockTree.RetrieveHeadBlock(), stateProvider); - } - - Assert.That(differences.Count, Is.Zero, "differences"); - - return new EthereumTestResult - ( - test.Name, - null, - differences.Count == 0 - ); + return parentHeader; } - private List<(Block Block, string ExpectedException)> DecodeRlps(BlockchainTest test, bool failOnInvalidRlp) + private static List<(Block Block, string ExpectedException)> DecodeRlps(BlockchainTest test, bool failOnInvalidRlp) { - List<(Block Block, string ExpectedException)> correctRlp = new(); + List<(Block Block, string ExpectedException)> correctRlp = []; for (int i = 0; i < test.Blocks.Length; i++) { TestBlockJson testBlockJson = test.Blocks[i]; try { - var rlpContext = Bytes.FromHexString(testBlockJson.Rlp).AsRlpStream(); + RlpStream rlpContext = Bytes.FromHexString(testBlockJson.Rlp).AsRlpStream(); Block suggestedBlock = Rlp.Decode(rlpContext); + // Console.WriteLine("suggested block:"); + // Console.WriteLine(suggestedBlock.BlockAccessList); + // Hash256 tmp = new(ValueKeccak.Compute(Rlp.Encode(suggestedBlock.BlockAccessList!.Value).Bytes).Bytes); suggestedBlock.Header.SealEngineType = test.SealEngineUsed ? SealEngineType.Ethash : SealEngineType.None; @@ -296,17 +335,20 @@ protected async Task RunTest(BlockchainTest test, Stopwatch? if (correctRlp.Count == 0) { - Assert.That(test.GenesisBlockHeader, Is.Not.Null); - Assert.That(test.LastBlockHash, Is.EqualTo(new Hash256(test.GenesisBlockHeader.Hash))); + using (Assert.EnterMultipleScope()) + { + Assert.That(test.GenesisBlockHeader, Is.Not.Null); + Assert.That(test.LastBlockHash, Is.EqualTo(new Hash256(test.GenesisBlockHeader.Hash))); + } } return correctRlp; } - private void InitializeTestState(BlockchainTest test, IWorldState stateProvider, ISpecProvider specProvider) + private static void InitializeTestState(BlockchainTest test, IWorldState stateProvider, ISpecProvider specProvider) { foreach (KeyValuePair accountState in - ((IEnumerable>)test.Pre ?? Array.Empty>())) + (IEnumerable>)test.Pre ?? Array.Empty>()) { foreach (KeyValuePair storageItem in accountState.Value.Storage) { @@ -324,18 +366,18 @@ private void InitializeTestState(BlockchainTest test, IWorldState stateProvider, stateProvider.Reset(); } - private List RunAssertions(BlockchainTest test, Block headBlock, IWorldState stateProvider) + private static List RunAssertions(BlockchainTest test, Block headBlock, IWorldState stateProvider) { if (test.PostStateRoot is not null) { - return test.PostStateRoot != stateProvider.StateRoot ? new List { "state root mismatch" } : Enumerable.Empty().ToList(); + return test.PostStateRoot != stateProvider.StateRoot ? ["state root mismatch"] : []; } TestBlockHeaderJson testHeaderJson = (test.Blocks? .Where(b => b.BlockHeader is not null) .SingleOrDefault(b => new Hash256(b.BlockHeader.Hash) == headBlock.Hash)?.BlockHeader) ?? test.GenesisBlockHeader; BlockHeader testHeader = JsonToEthereumTest.Convert(testHeaderJson); - List differences = new(); + List differences = []; IEnumerable> deletedAccounts = test.Pre? .Where(pre => !(test.PostState?.ContainsKey(pre.Key) ?? false)) ?? Array.Empty>(); @@ -359,8 +401,8 @@ private List RunAssertions(BlockchainTest test, Block headBlock, IWorldS } bool accountExists = stateProvider.AccountExists(acountAddress); - UInt256? balance = accountExists ? stateProvider.GetBalance(acountAddress) : (UInt256?)null; - UInt256? nonce = accountExists ? stateProvider.GetNonce(acountAddress) : (UInt256?)null; + UInt256? balance = accountExists ? stateProvider.GetBalance(acountAddress) : null; + UInt256? nonce = accountExists ? stateProvider.GetNonce(acountAddress) : null; if (accountState.Balance != balance) { @@ -372,7 +414,7 @@ private List RunAssertions(BlockchainTest test, Block headBlock, IWorldS differences.Add($"{acountAddress} nonce exp: {accountState.Nonce}, actual: {nonce}"); } - byte[] code = accountExists ? stateProvider.GetCode(acountAddress) : new byte[0]; + byte[] code = accountExists ? stateProvider.GetCode(acountAddress) : []; if (!Bytes.AreEqual(accountState.Code, code)) { differences.Add($"{acountAddress} code exp: {accountState.Code?.Length}, actual: {code?.Length}"); @@ -385,10 +427,10 @@ private List RunAssertions(BlockchainTest test, Block headBlock, IWorldS differencesBefore = differences.Count; - KeyValuePair[] clearedStorages = new KeyValuePair[0]; + KeyValuePair[] clearedStorages = []; if (test.Pre.ContainsKey(acountAddress)) { - clearedStorages = test.Pre[acountAddress].Storage.Where(s => !accountState.Storage.ContainsKey(s.Key)).ToArray(); + clearedStorages = [.. test.Pre[acountAddress].Storage.Where(s => !accountState.Storage.ContainsKey(s.Key))]; } foreach (KeyValuePair clearedStorage in clearedStorages) diff --git a/src/Nethermind/Ethereum.Test.Base/BlockchainTestJson.cs b/src/Nethermind/Ethereum.Test.Base/BlockchainTestJson.cs index fdd03858fd8c..c344cd773da7 100644 --- a/src/Nethermind/Ethereum.Test.Base/BlockchainTestJson.cs +++ b/src/Nethermind/Ethereum.Test.Base/BlockchainTestJson.cs @@ -24,6 +24,7 @@ public class BlockchainTestJson 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/JsonToEthereumTest.cs b/src/Nethermind/Ethereum.Test.Base/JsonToEthereumTest.cs index 9a09b17017c1..432156d5dcc2 100644 --- a/src/Nethermind/Ethereum.Test.Base/JsonToEthereumTest.cs +++ b/src/Nethermind/Ethereum.Test.Base/JsonToEthereumTest.cs @@ -5,6 +5,7 @@ using System.Collections.Generic; using System.IO; using System.Linq; +using System.Text.Json; using Nethermind.Config; using Nethermind.Core; using Nethermind.Core.Crypto; @@ -14,6 +15,7 @@ using Nethermind.Crypto; using Nethermind.Evm.EvmObjectFormat; using Nethermind.Int256; +using Nethermind.Merge.Plugin.Data; using Nethermind.Serialization.Json; using Nethermind.Serialization.Rlp; using Nethermind.Specs; @@ -56,36 +58,84 @@ public static BlockHeader Convert(TestBlockHeaderJson? headerJson) (long)Bytes.FromHexString(headerJson.Number).ToUInt256(), (long)Bytes.FromHexString(headerJson.GasLimit).ToUnsignedBigInteger(), (ulong)Bytes.FromHexString(headerJson.Timestamp).ToUnsignedBigInteger(), - Bytes.FromHexString(headerJson.ExtraData) - ); - - header.Bloom = new Bloom(Bytes.FromHexString(headerJson.Bloom)); - header.GasUsed = (long)Bytes.FromHexString(headerJson.GasUsed).ToUnsignedBigInteger(); - header.Hash = new Hash256(headerJson.Hash); - header.MixHash = new Hash256(headerJson.MixHash); - header.Nonce = (ulong)Bytes.FromHexString(headerJson.Nonce).ToUnsignedBigInteger(); - header.ReceiptsRoot = new Hash256(headerJson.ReceiptTrie); - header.StateRoot = new Hash256(headerJson.StateRoot); - header.TxRoot = new Hash256(headerJson.TransactionsTrie); + Bytes.FromHexString(headerJson.ExtraData), + headerJson.BlobGasUsed is null ? null : (ulong)Bytes.FromHexString(headerJson.BlobGasUsed).ToUnsignedBigInteger(), + headerJson.ExcessBlobGas is null ? null : (ulong)Bytes.FromHexString(headerJson.ExcessBlobGas).ToUnsignedBigInteger(), + headerJson.ParentBeaconBlockRoot is null ? null : new Hash256(headerJson.ParentBeaconBlockRoot), + headerJson.RequestsHash is null ? null : new Hash256(headerJson.RequestsHash) + ) + { + Bloom = new Bloom(Bytes.FromHexString(headerJson.Bloom)), + GasUsed = (long)Bytes.FromHexString(headerJson.GasUsed).ToUnsignedBigInteger(), + Hash = new Hash256(headerJson.Hash), + MixHash = new Hash256(headerJson.MixHash), + Nonce = (ulong)Bytes.FromHexString(headerJson.Nonce).ToUnsignedBigInteger(), + ReceiptsRoot = new Hash256(headerJson.ReceiptTrie), + StateRoot = new Hash256(headerJson.StateRoot), + TxRoot = new Hash256(headerJson.TransactionsTrie), + WithdrawalsRoot = headerJson.WithdrawalsRoot is null ? null : new Hash256(headerJson.WithdrawalsRoot), + BlockAccessListHash = headerJson.BlockAccessListHash is null ? null : new Hash256(headerJson.BlockAccessListHash), + BaseFeePerGas = (ulong)Bytes.FromHexString(headerJson.BaseFeePerGas).ToUnsignedBigInteger() + }; return header; } + public static IEnumerable<(ExecutionPayload, string[]?, string[]?, string?)> Convert(TestEngineNewPayloadsJson[]? executionPayloadsJson) + { + if (executionPayloadsJson is null) + { + throw new InvalidDataException("Execution payloads JSON was null when constructing test."); + } + + foreach (TestEngineNewPayloadsJson engineNewPayload in executionPayloadsJson) + { + TestEngineNewPayloadsJson.ParamsExecutionPayload executionPayload = engineNewPayload.Params[0].Deserialize(EthereumJsonSerializer.JsonOptions); + string[]? blobVersionedHashes = engineNewPayload.Params.Length > 1 ? engineNewPayload.Params[1].Deserialize(EthereumJsonSerializer.JsonOptions) : null; + string? parentBeaconBlockRoot = engineNewPayload.Params.Length > 2 ? engineNewPayload.Params[2].Deserialize(EthereumJsonSerializer.JsonOptions) : null; + string[]? validationError = engineNewPayload.Params.Length > 3 ? engineNewPayload.Params[3].Deserialize(EthereumJsonSerializer.JsonOptions) : null; + yield return (new ExecutionPayloadV3() + { + BaseFeePerGas = (ulong)Bytes.FromHexString(executionPayload.BaseFeePerGas).ToUnsignedBigInteger(), + BlockHash = new(executionPayload.BlockHash), + BlockNumber = (long)Bytes.FromHexString(executionPayload.BlockNumber).ToUnsignedBigInteger(), + ExtraData = Bytes.FromHexString(executionPayload.ExtraData), + FeeRecipient = new(executionPayload.FeeRecipient), + GasLimit = (long)Bytes.FromHexString(executionPayload.GasLimit).ToUnsignedBigInteger(), + GasUsed = (long)Bytes.FromHexString(executionPayload.GasUsed).ToUnsignedBigInteger(), + LogsBloom = new(Bytes.FromHexString(executionPayload.LogsBloom)), + ParentHash = new(executionPayload.ParentHash), + PrevRandao = new(executionPayload.PrevRandao), + ReceiptsRoot = new(executionPayload.ReceiptsRoot), + StateRoot = new(executionPayload.StateRoot), + Timestamp = (ulong)Bytes.FromHexString(executionPayload.Timestamp).ToUnsignedBigInteger(), + BlockAccessList = executionPayload.BlockAccessList is null ? null : Bytes.FromHexString(executionPayload.BlockAccessList), + BlobGasUsed = executionPayload.BlobGasUsed is null ? null : (ulong)Bytes.FromHexString(executionPayload.BlobGasUsed).ToUnsignedBigInteger(), + ExcessBlobGas = executionPayload.ExcessBlobGas is null ? null : (ulong)Bytes.FromHexString(executionPayload.ExcessBlobGas).ToUnsignedBigInteger(), + ParentBeaconBlockRoot = parentBeaconBlockRoot is null ? null : new(parentBeaconBlockRoot), + Withdrawals = executionPayload.Withdrawals is null ? null : [.. executionPayload.Withdrawals.Select(x => Rlp.Decode(Bytes.FromHexString(x)))], + Transactions = [.. executionPayload.Transactions.Select(x => Bytes.FromHexString(x))], + ExecutionRequests = [] + }, blobVersionedHashes, validationError, engineNewPayload.NewPayloadVersion); + } + } + public static Transaction Convert(PostStateJson postStateJson, TransactionJson transactionJson) { - Transaction transaction = new(); - - transaction.Type = transactionJson.Type; - transaction.Value = transactionJson.Value[postStateJson.Indexes.Value]; - transaction.GasLimit = transactionJson.GasLimit[postStateJson.Indexes.Gas]; - transaction.GasPrice = transactionJson.GasPrice ?? transactionJson.MaxPriorityFeePerGas ?? 0; - transaction.DecodedMaxFeePerGas = transactionJson.MaxFeePerGas ?? 0; - transaction.Nonce = transactionJson.Nonce; - transaction.To = transactionJson.To; - transaction.Data = transactionJson.Data[postStateJson.Indexes.Data]; - transaction.SenderAddress = new PrivateKey(transactionJson.SecretKey).Address; - transaction.Signature = new Signature(1, 1, 27); - transaction.BlobVersionedHashes = transactionJson.BlobVersionedHashes; - transaction.MaxFeePerBlobGas = transactionJson.MaxFeePerBlobGas; + Transaction transaction = new() + { + Type = transactionJson.Type, + Value = transactionJson.Value[postStateJson.Indexes.Value], + GasLimit = transactionJson.GasLimit[postStateJson.Indexes.Gas], + GasPrice = transactionJson.GasPrice ?? transactionJson.MaxPriorityFeePerGas ?? 0, + DecodedMaxFeePerGas = transactionJson.MaxFeePerGas ?? 0, + Nonce = transactionJson.Nonce, + To = transactionJson.To, + Data = transactionJson.Data[postStateJson.Indexes.Data], + SenderAddress = new PrivateKey(transactionJson.SecretKey).Address, + Signature = new Signature(1, 1, 27), + BlobVersionedHashes = transactionJson.BlobVersionedHashes, + MaxFeePerBlobGas = transactionJson.MaxFeePerBlobGas + }; transaction.Hash = transaction.CalculateHash(); AccessList.Builder builder = new(); @@ -108,7 +158,7 @@ public static Transaction Convert(PostStateJson postStateJson, TransactionJson t if (transactionJson.AuthorizationList is not null) { transaction.AuthorizationList = - transactionJson.AuthorizationList + [.. transactionJson.AuthorizationList .Select(i => { if (i.Nonce > ulong.MaxValue) @@ -148,7 +198,7 @@ public static Transaction Convert(PostStateJson postStateJson, TransactionJson t (byte)i.V, r, s); - }).ToArray(); + })]; if (transaction.AuthorizationList.Any()) { transaction.Type = TxType.SetCode; @@ -172,14 +222,16 @@ public static void ProcessAccessList(AccessListItemJson[]? accessList, AccessLis public static Transaction Convert(LegacyTransactionJson transactionJson) { - Transaction transaction = new(); - transaction.Value = transactionJson.Value; - transaction.GasLimit = transactionJson.GasLimit; - transaction.GasPrice = transactionJson.GasPrice; - transaction.Nonce = transactionJson.Nonce; - transaction.To = transactionJson.To; - transaction.Data = transactionJson.Data; - transaction.Signature = new Signature(transactionJson.R, transactionJson.S, transactionJson.V); + Transaction transaction = new() + { + Value = transactionJson.Value, + GasLimit = transactionJson.GasLimit, + GasPrice = transactionJson.GasPrice, + Nonce = transactionJson.Nonce, + To = transactionJson.To, + Data = transactionJson.Data, + Signature = new Signature(transactionJson.R, transactionJson.S, transactionJson.V) + }; transaction.Hash = transaction.CalculateHash(); return transaction; } @@ -191,41 +243,42 @@ public static IEnumerable Convert(string name, string category return Enumerable.Repeat(new GeneralStateTest { Name = name, Category = category, LoadFailure = testJson.LoadFailure }, 1); } - List blockchainTests = new(); + List blockchainTests = []; foreach (KeyValuePair postStateBySpec in testJson.Post) { int iterationNumber = 0; foreach (PostStateJson stateJson in postStateBySpec.Value) { - GeneralStateTest test = new(); - test.Name = Path.GetFileName(name) + - $"_d{stateJson.Indexes.Data}g{stateJson.Indexes.Gas}v{stateJson.Indexes.Value}_"; + GeneralStateTest test = new() + { + Name = Path.GetFileName(name) + + $"_d{stateJson.Indexes.Data}g{stateJson.Indexes.Gas}v{stateJson.Indexes.Value}_", + Category = category, + ForkName = postStateBySpec.Key, + Fork = SpecNameParser.Parse(postStateBySpec.Key), + PreviousHash = testJson.Env.PreviousHash, + CurrentCoinbase = testJson.Env.CurrentCoinbase, + CurrentDifficulty = testJson.Env.CurrentDifficulty, + CurrentGasLimit = testJson.Env.CurrentGasLimit, + CurrentNumber = testJson.Env.CurrentNumber, + CurrentTimestamp = testJson.Env.CurrentTimestamp, + CurrentBaseFee = testJson.Env.CurrentBaseFee, + CurrentRandom = testJson.Env.CurrentRandom, + CurrentBeaconRoot = testJson.Env.CurrentBeaconRoot, + CurrentWithdrawalsRoot = testJson.Env.CurrentWithdrawalsRoot, + CurrentExcessBlobGas = testJson.Env.CurrentExcessBlobGas, + ParentBlobGasUsed = testJson.Env.ParentBlobGasUsed, + ParentExcessBlobGas = testJson.Env.ParentExcessBlobGas, + PostReceiptsRoot = stateJson.Logs, + PostHash = stateJson.Hash, + Pre = testJson.Pre.ToDictionary(p => p.Key, p => p.Value), + Transaction = Convert(stateJson, testJson.Transaction) + }; + if (testJson.Info?.Labels?.ContainsKey(iterationNumber.ToString()) ?? false) { test.Name += testJson.Info?.Labels?[iterationNumber.ToString()]?.Replace(":label ", string.Empty); } - test.Category = category; - - test.ForkName = postStateBySpec.Key; - test.Fork = SpecNameParser.Parse(postStateBySpec.Key); - test.PreviousHash = testJson.Env.PreviousHash; - test.CurrentCoinbase = testJson.Env.CurrentCoinbase; - test.CurrentDifficulty = testJson.Env.CurrentDifficulty; - test.CurrentGasLimit = testJson.Env.CurrentGasLimit; - test.CurrentNumber = testJson.Env.CurrentNumber; - test.CurrentTimestamp = testJson.Env.CurrentTimestamp; - test.CurrentBaseFee = testJson.Env.CurrentBaseFee; - test.CurrentRandom = testJson.Env.CurrentRandom; - test.CurrentBeaconRoot = testJson.Env.CurrentBeaconRoot; - test.CurrentWithdrawalsRoot = testJson.Env.CurrentWithdrawalsRoot; - test.CurrentExcessBlobGas = testJson.Env.CurrentExcessBlobGas; - test.ParentBlobGasUsed = testJson.Env.ParentBlobGasUsed; - test.ParentExcessBlobGas = testJson.Env.ParentExcessBlobGas; - test.PostReceiptsRoot = stateJson.Logs; - test.PostHash = stateJson.Hash; - test.Pre = testJson.Pre.ToDictionary(p => p.Key, p => p.Value); - test.Transaction = Convert(stateJson, testJson.Transaction); - blockchainTests.Add(test); ++iterationNumber; } @@ -241,17 +294,20 @@ public static BlockchainTest Convert(string name, string category, BlockchainTes return new BlockchainTest { Name = name, Category = category, LoadFailure = testJson.LoadFailure }; } - BlockchainTest test = new(); - test.Name = name; - test.Category = category; - test.Network = testJson.EthereumNetwork; - test.NetworkAfterTransition = testJson.EthereumNetworkAfterTransition; - test.TransitionForkActivation = testJson.TransitionForkActivation; - test.LastBlockHash = new Hash256(testJson.LastBlockHash); - test.GenesisRlp = testJson.GenesisRlp is null ? null : new Rlp(Bytes.FromHexString(testJson.GenesisRlp)); - test.GenesisBlockHeader = testJson.GenesisBlockHeader; - test.Blocks = testJson.Blocks; - test.Pre = testJson.Pre.ToDictionary(p => p.Key, p => p.Value); + BlockchainTest test = new() + { + Name = name, + Category = category, + Network = testJson.EthereumNetwork, + NetworkAfterTransition = testJson.EthereumNetworkAfterTransition, + TransitionForkActivation = testJson.TransitionForkActivation, + LastBlockHash = new Hash256(testJson.LastBlockHash), + GenesisRlp = testJson.GenesisRlp is null ? null : new Rlp(Bytes.FromHexString(testJson.GenesisRlp)), + GenesisBlockHeader = testJson.GenesisBlockHeader, + Blocks = testJson.Blocks, + EngineNewPayloads = testJson.EngineNewPayloads, + Pre = testJson.Pre.ToDictionary(p => p.Key, p => p.Value) + }; HalfBlockchainTestJson half = testJson as HalfBlockchainTestJson; if (half is not null) @@ -272,7 +328,7 @@ public static BlockchainTest Convert(string name, string category, BlockchainTes public static IEnumerable ConvertToEofTests(string json) { Dictionary testsInFile = _serializer.Deserialize>(json); - List tests = new(); + List tests = []; foreach (KeyValuePair namedTest in testsInFile) { (string name, string category) = GetNameAndCategory(namedTest.Key); @@ -281,11 +337,13 @@ public static IEnumerable ConvertToEofTests(string json) foreach (KeyValuePair pair in namedTest.Value.Vectors) { VectorTestJson vectorJson = pair.Value; - VectorTest vector = new(); - vector.Code = Bytes.FromHexString(vectorJson.Code); - vector.ContainerKind = ParseContainerKind(vectorJson.ContainerKind); + VectorTest vector = new() + { + Code = Bytes.FromHexString(vectorJson.Code), + ContainerKind = ParseContainerKind(vectorJson.ContainerKind) + }; - foreach (var result in vectorJson.Results) + foreach (KeyValuePair result in vectorJson.Results) { EofTest test = new() { @@ -293,10 +351,10 @@ public static IEnumerable ConvertToEofTests(string json) Category = $"{category} [{result.Key}]", Url = url, Description = description, - Spec = spec + Spec = spec, + Vector = vector, + Result = result.ToTestResult() }; - test.Vector = vector; - test.Result = result.ToTestResult(); tests.Add(test); } } @@ -305,7 +363,7 @@ public static IEnumerable ConvertToEofTests(string json) return tests; static ValidationStrategy ParseContainerKind(string containerKind) - => ("INITCODE".Equals(containerKind) ? ValidationStrategy.ValidateInitCodeMode : ValidationStrategy.ValidateRuntimeMode); + => "INITCODE".Equals(containerKind) ? ValidationStrategy.ValidateInitCodeMode : ValidationStrategy.ValidateRuntimeMode; static void GetTestMetaData(KeyValuePair namedTest, out string? description, out string? url, out string? spec) { @@ -332,7 +390,7 @@ public static IEnumerable ConvertStateTest(string json) Dictionary testsInFile = _serializer.Deserialize>(json); - List tests = new(); + List tests = []; foreach (KeyValuePair namedTest in testsInFile) { (string name, string category) = GetNameAndCategory(namedTest.Key); @@ -349,17 +407,19 @@ public static IEnumerable ConvertToBlockchainTests(string json) { testsInFile = _serializer.Deserialize>(json); } - catch (Exception) + catch (Exception e) { - var half = _serializer.Deserialize>(json); - testsInFile = new Dictionary(); + Console.WriteLine(e); + Dictionary half = + _serializer.Deserialize>(json); + testsInFile = []; foreach (KeyValuePair pair in half) { testsInFile[pair.Key] = pair.Value; } } - List testsByName = new(); + List testsByName = []; foreach ((string testName, BlockchainTestJson testSpec) in testsInFile) { string[] transitionInfo = testSpec.Network.Split("At"); diff --git a/src/Nethermind/Ethereum.Test.Base/TestBlockHeaderJson.cs b/src/Nethermind/Ethereum.Test.Base/TestBlockHeaderJson.cs index 3508dba38c66..39c11de9c42d 100644 --- a/src/Nethermind/Ethereum.Test.Base/TestBlockHeaderJson.cs +++ b/src/Nethermind/Ethereum.Test.Base/TestBlockHeaderJson.cs @@ -21,5 +21,12 @@ public class TestBlockHeaderJson public string Timestamp { get; set; } public string TransactionsTrie { get; set; } public string UncleHash { get; set; } + public string? WithdrawalsRoot { get; set; } + public string? ParentBeaconBlockRoot { get; set; } + public string? RequestsHash { get; set; } + public string? BlockAccessListHash { get; set; } + public string? BlobGasUsed { get; set; } + public string? ExcessBlobGas { get; set; } + public string? BaseFeePerGas { get; set; } } } diff --git a/src/Nethermind/Ethereum.Test.Base/TestEngineNewPayloadsJson.cs b/src/Nethermind/Ethereum.Test.Base/TestEngineNewPayloadsJson.cs new file mode 100644 index 000000000000..a293b5175ac8 --- /dev/null +++ b/src/Nethermind/Ethereum.Test.Base/TestEngineNewPayloadsJson.cs @@ -0,0 +1,45 @@ +// SPDX-FileCopyrightText: 2025 Demerzel Solutions Limited +// SPDX-License-Identifier: LGPL-3.0-only + +using System.Text.Json; + +namespace Ethereum.Test.Base +{ + public class TestEngineNewPayloadsJson + { + public JsonElement[] Params { get; set; } + // public EngineNewPayloadParams Params { get; set; } + public string? NewPayloadVersion { get; set; } + public string? ForkChoiceUpdatedVersion { get; set; } + + // public class EngineNewPayloadParams + // { + // public ParamsExecutionPayload ExecutionPayload; + // public string[] BlobVersionedHashes; + // public string ParentBeaconBlockRoot; + // public string ValidationError; + // } + + public class ParamsExecutionPayload + { + public string ParentHash { get; set; } + public string FeeRecipient { get; set; } + public string StateRoot { get; set; } + public string ReceiptsRoot { get; set; } + public string LogsBloom { get; set; } + public string BlockNumber { get; set; } + public string GasLimit { get; set; } + public string GasUsed { get; set; } + public string Timestamp { get; set; } + public string ExtraData { get; set; } + public string PrevRandao { get; set; } + public string BaseFeePerGas { get; set; } + public string BlobGasUsed { get; set; } + public string ExcessBlobGas { get; set; } + public string BlockHash { get; set; } + public string[] Transactions { get; set; } + public string[]? Withdrawals { get; set; } + public string? BlockAccessList { get; set; } + } + } +} diff --git a/src/Nethermind/Nethermind.Blockchain.Test/BeaconBlockRootHandlerTests.cs b/src/Nethermind/Nethermind.Blockchain.Test/BeaconBlockRootHandlerTests.cs index a82668f7d741..7288c0d85ac1 100644 --- a/src/Nethermind/Nethermind.Blockchain.Test/BeaconBlockRootHandlerTests.cs +++ b/src/Nethermind/Nethermind.Blockchain.Test/BeaconBlockRootHandlerTests.cs @@ -5,10 +5,8 @@ using Nethermind.Core; using Nethermind.Core.Crypto; using Nethermind.Core.Eip2930; -using Nethermind.Core.Specs; using Nethermind.Core.Test.Builders; using Nethermind.Crypto; -using Nethermind.Evm; using Nethermind.Evm.Tracing; using Nethermind.Evm.TransactionProcessing; using Nethermind.Int256; diff --git a/src/Nethermind/Nethermind.Merge.Plugin.Test/ExecutionRequestsProcessorMock.cs b/src/Nethermind/Nethermind.Merge.Plugin.Test/ExecutionRequestsProcessorMock.cs index e1d8404c53da..8ad25a2ec4ef 100644 --- a/src/Nethermind/Nethermind.Merge.Plugin.Test/ExecutionRequestsProcessorMock.cs +++ b/src/Nethermind/Nethermind.Merge.Plugin.Test/ExecutionRequestsProcessorMock.cs @@ -8,7 +8,6 @@ using Nethermind.Core.ExecutionRequest; using Nethermind.Core.Specs; using Nethermind.Core.Test.Builders; -using Nethermind.Evm; using Nethermind.Evm.State; namespace Nethermind.Merge.Plugin.Test; diff --git a/src/Nethermind/Nethermind.Merge.Plugin/MergePlugin.cs b/src/Nethermind/Nethermind.Merge.Plugin/MergePlugin.cs index 92d6a2961e0b..f5d933da50df 100644 --- a/src/Nethermind/Nethermind.Merge.Plugin/MergePlugin.cs +++ b/src/Nethermind/Nethermind.Merge.Plugin/MergePlugin.cs @@ -22,7 +22,6 @@ using Nethermind.Core; using Nethermind.Core.Crypto; using Nethermind.Core.Exceptions; -using Nethermind.Core.Timers; using Nethermind.Db; using Nethermind.Facade.Proxy; using Nethermind.HealthChecks; @@ -329,6 +328,7 @@ protected override void Load(ContainerBuilder builder) .AddSingleton() .AddSingleton>, GetBlobsHandler>() .AddSingleton?>, GetBlobsHandlerV2>() + .AddSingleton() .AddSingleton() .AddSingleton((ctx) => diff --git a/src/Nethermind/Nethermind.Merge.Plugin/NoEngineRequestTracker.cs b/src/Nethermind/Nethermind.Merge.Plugin/NoEngineRequestTracker.cs new file mode 100644 index 000000000000..e0d1b606bd30 --- /dev/null +++ b/src/Nethermind/Nethermind.Merge.Plugin/NoEngineRequestTracker.cs @@ -0,0 +1,17 @@ +// SPDX-FileCopyrightText: 2025 Demerzel Solutions Limited +// SPDX-License-Identifier: LGPL-3.0-only + +using System.Threading.Tasks; +using Nethermind.Api; + +namespace Nethermind.Merge.Plugin; + +public class NoEngineRequestsTracker : IEngineRequestsTracker +{ + public void OnForkchoiceUpdatedCalled() { } + + public void OnNewPayloadCalled() { } + + public Task StartAsync() + => Task.CompletedTask; +} diff --git a/src/Nethermind/Nethermind.State.Test/StateProviderTests.cs b/src/Nethermind/Nethermind.State.Test/StateProviderTests.cs index 243bb99cd4c6..27f361829033 100644 --- a/src/Nethermind/Nethermind.State.Test/StateProviderTests.cs +++ b/src/Nethermind/Nethermind.State.Test/StateProviderTests.cs @@ -10,13 +10,11 @@ using Nethermind.Specs; using Nethermind.Specs.Forks; using Nethermind.Core.Test.Builders; -using Nethermind.Db; using Nethermind.Int256; using Nethermind.Blockchain.Tracing.ParityStyle; using Nethermind.Logging; using Nethermind.Evm.State; using Nethermind.State; -using Nethermind.Trie; using NUnit.Framework; namespace Nethermind.Store.Test; diff --git a/src/Nethermind/Nethermind.Test.Runner/BlockchainTestsRunner.cs b/src/Nethermind/Nethermind.Test.Runner/BlockchainTestsRunner.cs index 0856b9002fce..b9b3013d39f5 100644 --- a/src/Nethermind/Nethermind.Test.Runner/BlockchainTestsRunner.cs +++ b/src/Nethermind/Nethermind.Test.Runner/BlockchainTestsRunner.cs @@ -29,9 +29,18 @@ public BlockchainTestsRunner(ITestSourceLoader testsSource, string? filter, ulon public async Task> RunTestsAsync() { List testResults = new(); - IEnumerable tests = _testsSource.LoadTests(); - foreach (BlockchainTest test in tests) + IEnumerable tests = _testsSource.LoadTests(); + foreach (EthereumTest loadedTest in tests) { + if (loadedTest as FailedToLoadTest is not null) + { + WriteRed(loadedTest.LoadFailure); + testResults.Add(new EthereumTestResult(loadedTest.Name, loadedTest.LoadFailure)); + continue; + } + + BlockchainTest test = loadedTest as BlockchainTest; + if (_filter is not null && !Regex.Match(test.Name, $"^({_filter})").Success) continue; Setup(); From 3c54497d0c62e27a51a68cc930336e0eaafa77c9 Mon Sep 17 00:00:00 2001 From: Marc Harvey-Hill <10379486+Marchhill@users.noreply.github.com> Date: Wed, 1 Oct 2025 21:34:09 +0100 Subject: [PATCH 02/26] fix normal blockchain tests --- src/Nethermind/Ethereum.Test.Base/BlockchainTestBase.cs | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/Nethermind/Ethereum.Test.Base/BlockchainTestBase.cs b/src/Nethermind/Ethereum.Test.Base/BlockchainTestBase.cs index 129fef10d8c3..49eb21a5888c 100644 --- a/src/Nethermind/Ethereum.Test.Base/BlockchainTestBase.cs +++ b/src/Nethermind/Ethereum.Test.Base/BlockchainTestBase.cs @@ -144,7 +144,6 @@ protected async Task RunTest(BlockchainTest test, Stopwatch? blockchainProcessor.Start(); BlockHeader parentHeader; - (ExecutionPayload, string[]?, string[]?, string?)[] payloads; // Genesis processing using (stateProvider.BeginScope(null)) { @@ -158,8 +157,6 @@ protected async Task RunTest(BlockchainTest test, Stopwatch? // Console.WriteLine(genesisBlock.ToString(Block.Format.Full)); Assert.That(genesisBlock.Header.Hash, Is.EqualTo(new Hash256(test.GenesisBlockHeader.Hash))); - payloads = [.. JsonToEthereumTest.Convert(test.EngineNewPayloads)]; - ManualResetEvent genesisProcessed = new(false); blockTree.NewHeadBlock += (_, args) => @@ -183,6 +180,8 @@ protected async Task RunTest(BlockchainTest test, Stopwatch? } else if (test.EngineNewPayloads is not null) { + (ExecutionPayload, string[]?, string[]?, string?)[] payloads = [.. JsonToEthereumTest.Convert(test.EngineNewPayloads)]; + // blockchain test engine foreach ((ExecutionPayload executionPayload, string[]? blobVersionedHashes, string[]? validationError, string? newPayloadVersion) in payloads) { From c8afd68097c9ed8305131ce005b55f27790860e7 Mon Sep 17 00:00:00 2001 From: Marc Harvey-Hill <10379486+Marchhill@users.noreply.github.com> Date: Thu, 2 Oct 2025 13:20:21 +0100 Subject: [PATCH 03/26] tidy --- src/Nethermind/Ethereum.Test.Base/BlockchainTestBase.cs | 4 ---- src/Nethermind/Ethereum.Test.Base/JsonToEthereumTest.cs | 3 +-- .../Ethereum.Test.Base/TestEngineNewPayloadsJson.cs | 9 --------- 3 files changed, 1 insertion(+), 15 deletions(-) diff --git a/src/Nethermind/Ethereum.Test.Base/BlockchainTestBase.cs b/src/Nethermind/Ethereum.Test.Base/BlockchainTestBase.cs index 49eb21a5888c..af18a524d66b 100644 --- a/src/Nethermind/Ethereum.Test.Base/BlockchainTestBase.cs +++ b/src/Nethermind/Ethereum.Test.Base/BlockchainTestBase.cs @@ -154,7 +154,6 @@ protected async Task RunTest(BlockchainTest test, Stopwatch? test.GenesisRlp ??= Rlp.Encode(new Block(JsonToEthereumTest.Convert(test.GenesisBlockHeader))); Block genesisBlock = Rlp.Decode(test.GenesisRlp.Bytes); - // Console.WriteLine(genesisBlock.ToString(Block.Format.Full)); Assert.That(genesisBlock.Header.Hash, Is.EqualTo(new Hash256(test.GenesisBlockHeader.Hash))); ManualResetEvent genesisProcessed = new(false); @@ -291,9 +290,6 @@ private static BlockHeader SuggestBlocks(BlockchainTest test, bool failOnInvalid { RlpStream rlpContext = Bytes.FromHexString(testBlockJson.Rlp).AsRlpStream(); Block suggestedBlock = Rlp.Decode(rlpContext); - // Console.WriteLine("suggested block:"); - // Console.WriteLine(suggestedBlock.BlockAccessList); - // Hash256 tmp = new(ValueKeccak.Compute(Rlp.Encode(suggestedBlock.BlockAccessList!.Value).Bytes).Bytes); suggestedBlock.Header.SealEngineType = test.SealEngineUsed ? SealEngineType.Ethash : SealEngineType.None; diff --git a/src/Nethermind/Ethereum.Test.Base/JsonToEthereumTest.cs b/src/Nethermind/Ethereum.Test.Base/JsonToEthereumTest.cs index 432156d5dcc2..05be99df487f 100644 --- a/src/Nethermind/Ethereum.Test.Base/JsonToEthereumTest.cs +++ b/src/Nethermind/Ethereum.Test.Base/JsonToEthereumTest.cs @@ -407,9 +407,8 @@ public static IEnumerable ConvertToBlockchainTests(string json) { testsInFile = _serializer.Deserialize>(json); } - catch (Exception e) + catch (Exception) { - Console.WriteLine(e); Dictionary half = _serializer.Deserialize>(json); testsInFile = []; diff --git a/src/Nethermind/Ethereum.Test.Base/TestEngineNewPayloadsJson.cs b/src/Nethermind/Ethereum.Test.Base/TestEngineNewPayloadsJson.cs index a293b5175ac8..b89dcd2869b2 100644 --- a/src/Nethermind/Ethereum.Test.Base/TestEngineNewPayloadsJson.cs +++ b/src/Nethermind/Ethereum.Test.Base/TestEngineNewPayloadsJson.cs @@ -8,18 +8,9 @@ namespace Ethereum.Test.Base public class TestEngineNewPayloadsJson { public JsonElement[] Params { get; set; } - // public EngineNewPayloadParams Params { get; set; } public string? NewPayloadVersion { get; set; } public string? ForkChoiceUpdatedVersion { get; set; } - // public class EngineNewPayloadParams - // { - // public ParamsExecutionPayload ExecutionPayload; - // public string[] BlobVersionedHashes; - // public string ParentBeaconBlockRoot; - // public string ValidationError; - // } - public class ParamsExecutionPayload { public string ParentHash { get; set; } From 309560a01cda265297784d18ba9c9b7d57702415 Mon Sep 17 00:00:00 2001 From: Marc Harvey-Hill <10379486+Marchhill@users.noreply.github.com> Date: Thu, 2 Oct 2025 13:37:01 +0100 Subject: [PATCH 04/26] restore disposes --- src/Nethermind/Ethereum.Test.Base/BlockchainTestBase.cs | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/src/Nethermind/Ethereum.Test.Base/BlockchainTestBase.cs b/src/Nethermind/Ethereum.Test.Base/BlockchainTestBase.cs index af18a524d66b..2caafe54e558 100644 --- a/src/Nethermind/Ethereum.Test.Base/BlockchainTestBase.cs +++ b/src/Nethermind/Ethereum.Test.Base/BlockchainTestBase.cs @@ -219,6 +219,9 @@ protected async Task RunTest(BlockchainTest test, Stopwatch? // Caches are cleared async, which is a problem as read for the MainWorldState with prewarmer is not correct if its not cleared. preWarmer?.ClearCaches(); + // Dispose genesis block's AccountChanges + genesisBlock.DisposeAccountChanges(); + Block? headBlock = blockTree.RetrieveHeadBlock(); List differences; using (stateProvider.BeginScope(headBlock.Header)) @@ -273,6 +276,11 @@ private static BlockHeader SuggestBlocks(BlockchainTest test, bool failOnInvalid { Assert.Fail($"Unexpected exception during processing: {e}"); } + finally + { + // Dispose AccountChanges to prevent memory leaks in tests + correctRlp[i].Block.DisposeAccountChanges(); + } parentHeader = correctRlp[i].Block.Header; } From 11e0cbfc98714c3533bd3d5141fb0f8d47aebb3d Mon Sep 17 00:00:00 2001 From: Marc Harvey-Hill <10379486+Marchhill@users.noreply.github.com> Date: Thu, 2 Oct 2025 13:41:05 +0100 Subject: [PATCH 05/26] comment out BALs --- src/Nethermind/Ethereum.Test.Base/JsonToEthereumTest.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Nethermind/Ethereum.Test.Base/JsonToEthereumTest.cs b/src/Nethermind/Ethereum.Test.Base/JsonToEthereumTest.cs index 05be99df487f..1345c9cf96e6 100644 --- a/src/Nethermind/Ethereum.Test.Base/JsonToEthereumTest.cs +++ b/src/Nethermind/Ethereum.Test.Base/JsonToEthereumTest.cs @@ -74,7 +74,7 @@ public static BlockHeader Convert(TestBlockHeaderJson? headerJson) StateRoot = new Hash256(headerJson.StateRoot), TxRoot = new Hash256(headerJson.TransactionsTrie), WithdrawalsRoot = headerJson.WithdrawalsRoot is null ? null : new Hash256(headerJson.WithdrawalsRoot), - BlockAccessListHash = headerJson.BlockAccessListHash is null ? null : new Hash256(headerJson.BlockAccessListHash), + // BlockAccessListHash = headerJson.BlockAccessListHash is null ? null : new Hash256(headerJson.BlockAccessListHash), BaseFeePerGas = (ulong)Bytes.FromHexString(headerJson.BaseFeePerGas).ToUnsignedBigInteger() }; return header; @@ -108,7 +108,7 @@ public static BlockHeader Convert(TestBlockHeaderJson? headerJson) ReceiptsRoot = new(executionPayload.ReceiptsRoot), StateRoot = new(executionPayload.StateRoot), Timestamp = (ulong)Bytes.FromHexString(executionPayload.Timestamp).ToUnsignedBigInteger(), - BlockAccessList = executionPayload.BlockAccessList is null ? null : Bytes.FromHexString(executionPayload.BlockAccessList), + // BlockAccessList = executionPayload.BlockAccessList is null ? null : Bytes.FromHexString(executionPayload.BlockAccessList), BlobGasUsed = executionPayload.BlobGasUsed is null ? null : (ulong)Bytes.FromHexString(executionPayload.BlobGasUsed).ToUnsignedBigInteger(), ExcessBlobGas = executionPayload.ExcessBlobGas is null ? null : (ulong)Bytes.FromHexString(executionPayload.ExcessBlobGas).ToUnsignedBigInteger(), ParentBeaconBlockRoot = parentBeaconBlockRoot is null ? null : new(parentBeaconBlockRoot), From ae3c660234b8a89c8f31d2e3a486d5ebf2b4cad7 Mon Sep 17 00:00:00 2001 From: Marc Harvey-Hill <10379486+Marchhill@users.noreply.github.com> Date: Thu, 2 Oct 2025 13:47:02 +0100 Subject: [PATCH 06/26] fix var declaration --- src/Nethermind/Ethereum.Test.Base/BlockchainTestBase.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/Nethermind/Ethereum.Test.Base/BlockchainTestBase.cs b/src/Nethermind/Ethereum.Test.Base/BlockchainTestBase.cs index 2caafe54e558..b12370be2bb6 100644 --- a/src/Nethermind/Ethereum.Test.Base/BlockchainTestBase.cs +++ b/src/Nethermind/Ethereum.Test.Base/BlockchainTestBase.cs @@ -143,6 +143,7 @@ protected async Task RunTest(BlockchainTest test, Stopwatch? IEngineRpcModule engineRpcModule = container.Resolve(); blockchainProcessor.Start(); + Block genesisBlock; BlockHeader parentHeader; // Genesis processing using (stateProvider.BeginScope(null)) @@ -153,7 +154,7 @@ protected async Task RunTest(BlockchainTest test, Stopwatch? test.GenesisRlp ??= Rlp.Encode(new Block(JsonToEthereumTest.Convert(test.GenesisBlockHeader))); - Block genesisBlock = Rlp.Decode(test.GenesisRlp.Bytes); + genesisBlock = Rlp.Decode(test.GenesisRlp.Bytes); Assert.That(genesisBlock.Header.Hash, Is.EqualTo(new Hash256(test.GenesisBlockHeader.Hash))); ManualResetEvent genesisProcessed = new(false); From 1c14e934ec298569201db2508c96d6052b052e3f Mon Sep 17 00:00:00 2001 From: Marc Harvey-Hill <10379486+Marchhill@users.noreply.github.com> Date: Thu, 2 Oct 2025 14:23:35 +0100 Subject: [PATCH 07/26] don't set basefeepergas if null --- src/Nethermind/Ethereum.Test.Base/JsonToEthereumTest.cs | 7 ++++++- src/Nethermind/Ethereum.Test.Base/TestBlockHeaderJson.cs | 2 +- 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/src/Nethermind/Ethereum.Test.Base/JsonToEthereumTest.cs b/src/Nethermind/Ethereum.Test.Base/JsonToEthereumTest.cs index 1345c9cf96e6..5ab865e202f3 100644 --- a/src/Nethermind/Ethereum.Test.Base/JsonToEthereumTest.cs +++ b/src/Nethermind/Ethereum.Test.Base/JsonToEthereumTest.cs @@ -75,8 +75,13 @@ public static BlockHeader Convert(TestBlockHeaderJson? headerJson) TxRoot = new Hash256(headerJson.TransactionsTrie), WithdrawalsRoot = headerJson.WithdrawalsRoot is null ? null : new Hash256(headerJson.WithdrawalsRoot), // BlockAccessListHash = headerJson.BlockAccessListHash is null ? null : new Hash256(headerJson.BlockAccessListHash), - BaseFeePerGas = (ulong)Bytes.FromHexString(headerJson.BaseFeePerGas).ToUnsignedBigInteger() }; + + if (headerJson.BaseFeePerGas is not null) + { + header.BaseFeePerGas = (ulong)Bytes.FromHexString(headerJson.BaseFeePerGas).ToUnsignedBigInteger(); + } + return header; } diff --git a/src/Nethermind/Ethereum.Test.Base/TestBlockHeaderJson.cs b/src/Nethermind/Ethereum.Test.Base/TestBlockHeaderJson.cs index 39c11de9c42d..c154f53e5743 100644 --- a/src/Nethermind/Ethereum.Test.Base/TestBlockHeaderJson.cs +++ b/src/Nethermind/Ethereum.Test.Base/TestBlockHeaderJson.cs @@ -21,12 +21,12 @@ public class TestBlockHeaderJson public string Timestamp { get; set; } public string TransactionsTrie { get; set; } public string UncleHash { get; set; } + public string? BaseFeePerGas { get; set; } public string? WithdrawalsRoot { get; set; } public string? ParentBeaconBlockRoot { get; set; } public string? RequestsHash { get; set; } public string? BlockAccessListHash { get; set; } public string? BlobGasUsed { get; set; } public string? ExcessBlobGas { get; set; } - public string? BaseFeePerGas { get; set; } } } From 32f3e5340ccd603c1ed2d840d68bfcd6e8709b4d Mon Sep 17 00:00:00 2001 From: Marc Harvey-Hill <10379486+Marchhill@users.noreply.github.com> Date: Thu, 9 Oct 2025 17:06:53 +0100 Subject: [PATCH 08/26] use network from genesis in blockchain test --- .../Ethereum.Test.Base/BlockchainTestBase.cs | 8 +--- .../Ethereum.Test.Base/GeneralTestBase.cs | 44 +++++++++---------- .../Interfaces/EthereumTest.cs | 6 +-- 3 files changed, 24 insertions(+), 34 deletions(-) diff --git a/src/Nethermind/Ethereum.Test.Base/BlockchainTestBase.cs b/src/Nethermind/Ethereum.Test.Base/BlockchainTestBase.cs index b12370be2bb6..2df80720367a 100644 --- a/src/Nethermind/Ethereum.Test.Base/BlockchainTestBase.cs +++ b/src/Nethermind/Ethereum.Test.Base/BlockchainTestBase.cs @@ -83,8 +83,7 @@ protected async Task RunTest(BlockchainTest test, Stopwatch? test.Network = ChainUtils.ResolveSpec(test.Network, test.ChainId); test.NetworkAfterTransition = ChainUtils.ResolveSpec(test.NetworkAfterTransition, test.ChainId); - List<(ForkActivation Activation, IReleaseSpec Spec)> transitions = - [((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 + List<(ForkActivation Activation, IReleaseSpec Spec)> transitions = [((ForkActivation)0, test.Network)]; if (test.NetworkAfterTransition is not null) { transitions.Add((test.TransitionForkActivation!.Value, test.NetworkAfterTransition)); @@ -92,11 +91,6 @@ protected async Task RunTest(BlockchainTest test, Stopwatch? ISpecProvider specProvider = new CustomSpecProvider(test.ChainId, test.ChainId, transitions.ToArray()); - if (test.ChainId != GnosisSpecProvider.Instance.ChainId && specProvider.GenesisSpec != Frontier.Instance) - { - Assert.Fail("Expected genesis spec to be Frontier for blockchain tests"); - } - if (test.Network is Cancun || test.NetworkAfterTransition is Cancun) { await KzgPolynomialCommitments.InitializeAsync(); diff --git a/src/Nethermind/Ethereum.Test.Base/GeneralTestBase.cs b/src/Nethermind/Ethereum.Test.Base/GeneralTestBase.cs index 998319565324..16e85265b851 100644 --- a/src/Nethermind/Ethereum.Test.Base/GeneralTestBase.cs +++ b/src/Nethermind/Ethereum.Test.Base/GeneralTestBase.cs @@ -44,7 +44,7 @@ static GeneralStateTestBase() } [SetUp] - public void Setup() + public static void Setup() { } @@ -68,15 +68,7 @@ protected EthereumTestResult RunTest(GeneralStateTest test, ITxTracer txTracer) test.Fork = ChainUtils.ResolveSpec(test.Fork, test.ChainId); - ISpecProvider specProvider = - new CustomSpecProvider(test.ChainId, test.ChainId, - ((ForkActivation)0, test.GenesisSpec), // TODO: this thing took a lot of time to find after it was removed!, genesis block is always initialized with Frontier - ((ForkActivation)1, test.Fork)); - - if (test.ChainId != GnosisSpecProvider.Instance.ChainId && specProvider.GenesisSpec != Frontier.Instance) - { - Assert.Fail("Expected genesis spec to be Frontier for blockchain tests"); - } + ISpecProvider specProvider = new CustomSpecProvider(test.ChainId, test.ChainId, ((ForkActivation)0, test.Fork)); IConfigProvider configProvider = new ConfigProvider(); using IContainer container = new ContainerBuilder() @@ -101,17 +93,19 @@ protected EthereumTestResult RunTest(GeneralStateTest test, ITxTracer txTracer) test.CurrentNumber, test.CurrentGasLimit, test.CurrentTimestamp, - []); - header.BaseFeePerGas = test.Fork.IsEip1559Enabled ? test.CurrentBaseFee ?? _defaultBaseFeeForStateTest : UInt256.Zero; - header.StateRoot = test.PostHash; + []) + { + BaseFeePerGas = test.Fork.IsEip1559Enabled ? test.CurrentBaseFee ?? _defaultBaseFeeForStateTest : UInt256.Zero, + StateRoot = test.PostHash, + IsPostMerge = test.CurrentRandom is not null, + MixHash = test.CurrentRandom, + WithdrawalsRoot = test.CurrentWithdrawalsRoot, + ParentBeaconBlockRoot = test.CurrentBeaconRoot, + ExcessBlobGas = test.CurrentExcessBlobGas ?? (test.Fork is Cancun ? 0ul : null), + BlobGasUsed = BlobGasCalculator.CalculateBlobGas(test.Transaction), + RequestsHash = test.RequestsHash + }; header.Hash = header.CalculateHash(); - header.IsPostMerge = test.CurrentRandom is not null; - header.MixHash = test.CurrentRandom; - header.WithdrawalsRoot = test.CurrentWithdrawalsRoot; - header.ParentBeaconBlockRoot = test.CurrentBeaconRoot; - header.ExcessBlobGas = test.CurrentExcessBlobGas ?? (test.Fork is Cancun ? 0ul : null); - header.BlobGasUsed = BlobGasCalculator.CalculateBlobGas(test.Transaction); - header.RequestsHash = test.RequestsHash; Stopwatch stopwatch = Stopwatch.StartNew(); IReleaseSpec? spec = specProvider.GetSpec((ForkActivation)test.CurrentNumber); @@ -170,16 +164,18 @@ protected EthereumTestResult RunTest(GeneralStateTest test, ITxTracer txTracer) } List differences = RunAssertions(test, stateProvider); - EthereumTestResult testResult = new(test.Name, test.ForkName, differences.Count == 0); - testResult.TimeInMs = stopwatch.Elapsed.TotalMilliseconds; - testResult.StateRoot = stateProvider.StateRoot; + EthereumTestResult testResult = new(test.Name, test.ForkName, differences.Count == 0) + { + TimeInMs = stopwatch.Elapsed.TotalMilliseconds, + StateRoot = stateProvider.StateRoot + }; if (differences.Count > 0) { _logger.Info($"\nDifferences from expected\n{string.Join("\n", differences)}"); } - // Assert.Zero(differences.Count, "differences"); + Assert.That(differences, Is.Empty, "differences"); return testResult; } diff --git a/src/Nethermind/Ethereum.Test.Base/Interfaces/EthereumTest.cs b/src/Nethermind/Ethereum.Test.Base/Interfaces/EthereumTest.cs index d64cf0a6d6da..03e55c585a0e 100644 --- a/src/Nethermind/Ethereum.Test.Base/Interfaces/EthereumTest.cs +++ b/src/Nethermind/Ethereum.Test.Base/Interfaces/EthereumTest.cs @@ -9,8 +9,8 @@ public abstract class EthereumTest public string? Name { get; set; } public string? LoadFailure { get; set; } public ulong ChainId { get; set; } = MainnetSpecProvider.Instance.ChainId; - public IReleaseSpec GenesisSpec => ChainId == MainnetSpecProvider.Instance.ChainId - ? MainnetSpecProvider.Instance.GenesisSpec - : GnosisSpecProvider.Instance.GenesisSpec; + // public IReleaseSpec GenesisSpec => ChainId == MainnetSpecProvider.Instance.ChainId + // ? MainnetSpecProvider.Instance.GenesisSpec + // : GnosisSpecProvider.Instance.GenesisSpec; } } From b8330ce6787cf9a54c08b150e2e30371753aedce Mon Sep 17 00:00:00 2001 From: Marc Harvey-Hill <10379486+Marchhill@users.noreply.github.com> Date: Mon, 20 Oct 2025 17:06:55 +0100 Subject: [PATCH 09/26] update blockchain test base --- .../Ethereum.Test.Base/BlockchainTestBase.cs | 299 ++++++++++-------- 1 file changed, 173 insertions(+), 126 deletions(-) diff --git a/src/Nethermind/Ethereum.Test.Base/BlockchainTestBase.cs b/src/Nethermind/Ethereum.Test.Base/BlockchainTestBase.cs index 2df80720367a..67e0e0324121 100644 --- a/src/Nethermind/Ethereum.Test.Base/BlockchainTestBase.cs +++ b/src/Nethermind/Ethereum.Test.Base/BlockchainTestBase.cs @@ -30,6 +30,7 @@ using Nethermind.Specs.Forks; using Nethermind.Specs.Test; using Nethermind.Evm.State; +using Nethermind.Evm.Tracing; using Nethermind.Init.Modules; using Nethermind.TxPool; using NUnit.Framework; @@ -42,7 +43,8 @@ namespace Ethereum.Test.Base; public abstract class BlockchainTestBase { private static readonly ILogger _logger; - private static readonly ILogManager _logManager = new TestLogManager(LogLevel.Warn); + private static readonly ILogManager _logManager = new TestLogManager(); + // private static ISealValidator Sealer { get; } private static DifficultyCalculatorWrapper DifficultyCalculator { get; } static BlockchainTestBase() @@ -73,9 +75,9 @@ public UInt256 Calculate(BlockHeader header, BlockHeader parent) } } - protected async Task RunTest(BlockchainTest test, Stopwatch? stopwatch = null, bool failOnInvalidRlp = true) + protected async Task RunTest(BlockchainTest test, Stopwatch? stopwatch = null, bool failOnInvalidRlp = true, ITestBlockTracer? tracer = null) { - _logger.Info($"Running {test.Name}, Network: [{test.Network.Name}] at {DateTime.UtcNow:HH:mm:ss.ffffff}"); + _logger.Info($"Running {test.Name}, Network: [{test.Network!.Name}] at {DateTime.UtcNow:HH:mm:ss.ffffff}"); if (test.NetworkAfterTransition is not null) _logger.Info($"Network after transition: [{test.NetworkAfterTransition.Name}] at {test.TransitionForkActivation}"); Assert.That(test.LoadFailure, Is.Null, "test data loading failure"); @@ -120,7 +122,7 @@ protected async Task RunTest(BlockchainTest test, Stopwatch? IConfigProvider configProvider = new ConfigProvider(); // configProvider.GetConfig().PreWarmStateOnBlockProcessing = false; await using IContainer container = new ContainerBuilder() - .AddModule(new TestNethermindModule(configProvider)) + .AddModule(new TestNethermindModule(configProvider)) // check if two different modules must be fixed together .AddModule(new TestMergeModule(configProvider)) .AddSingleton(specProvider) .AddSingleton(_logManager) @@ -137,101 +139,102 @@ protected async Task RunTest(BlockchainTest test, Stopwatch? IEngineRpcModule engineRpcModule = container.Resolve(); blockchainProcessor.Start(); - Block genesisBlock; - BlockHeader parentHeader; - // Genesis processing - using (stateProvider.BeginScope(null)) + // Register tracer if provided for blocktest tracing + if (tracer is not null) { - InitializeTestState(test, stateProvider, specProvider); + blockchainProcessor.Tracers.Add(tracer); + } - stopwatch?.Start(); + try + { + BlockHeader parentHeader; + // Genesis processing + using (stateProvider.BeginScope(null)) + { + InitializeTestState(test, stateProvider, specProvider); - test.GenesisRlp ??= Rlp.Encode(new Block(JsonToEthereumTest.Convert(test.GenesisBlockHeader))); + stopwatch?.Start(); - genesisBlock = Rlp.Decode(test.GenesisRlp.Bytes); - Assert.That(genesisBlock.Header.Hash, Is.EqualTo(new Hash256(test.GenesisBlockHeader.Hash))); + test.GenesisRlp ??= Rlp.Encode(new Block(JsonToEthereumTest.Convert(test.GenesisBlockHeader))); - ManualResetEvent genesisProcessed = new(false); + Block genesisBlock = Rlp.Decode(test.GenesisRlp.Bytes); + // Console.WriteLine(genesisBlock.ToString(Block.Format.Full)); + Assert.That(genesisBlock.Header.Hash, Is.EqualTo(new Hash256(test.GenesisBlockHeader.Hash))); - blockTree.NewHeadBlock += (_, args) => - { - if (args.Block.Number == 0) + ManualResetEvent genesisProcessed = new(false); + + blockTree.NewHeadBlock += (_, args) => { - Assert.That(stateProvider.HasStateForBlock(genesisBlock.Header), Is.True); - genesisProcessed.Set(); - } - }; + if (args.Block.Number == 0) + { + Assert.That(stateProvider.HasStateForBlock(genesisBlock.Header), Is.True); + genesisProcessed.Set(); + } + }; - blockTree.SuggestBlock(genesisBlock); - genesisProcessed.WaitOne(); - parentHeader = genesisBlock.Header; - } + blockTree.SuggestBlock(genesisBlock); + genesisProcessed.WaitOne(); + parentHeader = genesisBlock.Header; - if (test.Blocks is not null) - { - // blockchain test - parentHeader = SuggestBlocks(test, failOnInvalidRlp, blockValidator, blockTree, parentHeader); - } - else if (test.EngineNewPayloads is not null) - { - (ExecutionPayload, string[]?, string[]?, string?)[] payloads = [.. JsonToEthereumTest.Convert(test.EngineNewPayloads)]; + // Dispose genesis block's AccountChanges + genesisBlock.DisposeAccountChanges(); + } - // blockchain test engine - foreach ((ExecutionPayload executionPayload, string[]? blobVersionedHashes, string[]? validationError, string? newPayloadVersion) in payloads) + if (test.Blocks is not null) { - ResultWrapper res; - switch (newPayloadVersion ?? "4") - { - case "1": - res = await engineRpcModule.engine_newPayloadV1(executionPayload); - break; - case "2": - res = await engineRpcModule.engine_newPayloadV2(executionPayload); - break; - case "3": - res = await engineRpcModule.engine_newPayloadV3((ExecutionPayloadV3)executionPayload, [], executionPayload.ParentBeaconBlockRoot); - break; - case "4": - byte[]?[] hashes = blobVersionedHashes is null ? null : [.. blobVersionedHashes.Select(x => Bytes.FromHexString(x))]; - res = await engineRpcModule.engine_newPayloadV4((ExecutionPayloadV3)executionPayload, hashes, executionPayload.ParentBeaconBlockRoot, []); - break; - default: - Assert.Fail("Invalid blockchain engine test, version not recognised."); - break; - } + // blockchain test + parentHeader = SuggestBlocks(test, failOnInvalidRlp, blockValidator, blockTree, parentHeader); + } + else if (test.EngineNewPayloads is not null) + { + RunNewPayloads(test.EngineNewPayloads, engineRpcModule); + } + else + { + Assert.Fail("Invalid blockchain test, did not contain blocks or new payloads."); } - } - else - { - Assert.Fail("Invalid blockchain test, did not contain blocks or new payloads."); - } - await blockchainProcessor.StopAsync(true); - stopwatch?.Stop(); + // 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(); + IBlockCachePreWarmer? preWarmer = container.Resolve().LifetimeScope.ResolveOptional(); - // Caches are cleared async, which is a problem as read for the MainWorldState with prewarmer is not correct if its not cleared. - preWarmer?.ClearCaches(); + // Caches are cleared async, which is a problem as read for the MainWorldState with prewarmer is not correct if its not cleared. + preWarmer?.ClearCaches(); - // Dispose genesis block's AccountChanges - genesisBlock.DisposeAccountChanges(); + Block? headBlock = blockTree.RetrieveHeadBlock(); + List differences; + using (stateProvider.BeginScope(headBlock.Header)) + { + differences = RunAssertions(test, headBlock, stateProvider); + } - Block? headBlock = blockTree.RetrieveHeadBlock(); - List differences; - using (stateProvider.BeginScope(headBlock.Header)) - { - differences = RunAssertions(test, blockTree.RetrieveHeadBlock(), stateProvider); - } + bool testPassed = differences.Count == 0; - Assert.That(differences, Is.Empty, "differences"); + // Write test end marker if using streaming tracer (JSONL format) + // This must be done BEFORE removing tracer and BEFORE Assert to ensure marker is written even on failure + if (tracer is not null) + { + tracer.TestFinished(test.Name, testPassed, test.Network, stopwatch?.Elapsed, headBlock?.StateRoot); + blockchainProcessor.Tracers.Remove(tracer); + } - return new EthereumTestResult - ( - test.Name, - null, - differences.Count == 0 - ); + Assert.That(differences, Is.Empty, "differences"); + + return new EthereumTestResult + ( + test.Name, + null, + differences.Count == 0 + ); + } + catch (Exception) + { + await blockchainProcessor.StopAsync(true); + throw; + } } private static BlockHeader SuggestBlocks(BlockchainTest test, bool failOnInvalidRlp, IBlockValidator blockValidator, IBlockTree blockTree, BlockHeader parentHeader) @@ -248,7 +251,7 @@ private static BlockHeader SuggestBlocks(BlockchainTest test, bool failOnInvalid { // TODO: mimic the actual behaviour where block goes through validating sync manager? correctRlp[i].Block.Header.IsPostMerge = correctRlp[i].Block.Difficulty == 0; - if (!test.SealEngineUsed || blockValidator.ValidateSuggestedBlock(correctRlp[i].Block, parentHeader, out var _error)) + if (!test.SealEngineUsed || blockValidator.ValidateSuggestedBlock(correctRlp[i].Block, parentHeader, out _)) { blockTree.SuggestBlock(correctRlp[i].Block); } @@ -279,19 +282,58 @@ private static BlockHeader SuggestBlocks(BlockchainTest test, bool failOnInvalid parentHeader = correctRlp[i].Block.Header; } - return parentHeader; } + private async static void RunNewPayloads(TestEngineNewPayloadsJson[]? newPayloads, IEngineRpcModule engineRpcModule) + { + (ExecutionPayload, string[]?, string[]?, string?)[] payloads = [.. JsonToEthereumTest.Convert(newPayloads)]; + + // blockchain test engine + foreach ((ExecutionPayload executionPayload, string[]? blobVersionedHashes, string[]? validationError, string? newPayloadVersion) in payloads) + { + ResultWrapper res; + byte[]?[] hashes = blobVersionedHashes is null ? null : [.. blobVersionedHashes.Select(x => Bytes.FromHexString(x))]; + + switch (newPayloadVersion ?? "5") + { + case "1": + res = await engineRpcModule.engine_newPayloadV1(executionPayload); + break; + case "2": + res = await engineRpcModule.engine_newPayloadV2(executionPayload); + break; + case "3": + res = await engineRpcModule.engine_newPayloadV3((ExecutionPayloadV3)executionPayload, [], executionPayload.ParentBeaconBlockRoot); + break; + case "4": + res = await engineRpcModule.engine_newPayloadV4((ExecutionPayloadV3)executionPayload, hashes, executionPayload.ParentBeaconBlockRoot, []); + break; + // case "5": + // res = await engineRpcModule.engine_newPayloadV5((ExecutionPayloadV3)executionPayload, hashes, executionPayload.ParentBeaconBlockRoot, []); + // break; + default: + Assert.Fail("Invalid blockchain engine test, version not recognised."); + continue; + } + + if (res.Result.ResultType == ResultType.Success) + { + ForkchoiceStateV1 fcuState = new(executionPayload.BlockHash, executionPayload.BlockHash, executionPayload.BlockHash); + await engineRpcModule.engine_forkchoiceUpdatedV3(fcuState); + } + } + } + private static List<(Block Block, string ExpectedException)> DecodeRlps(BlockchainTest test, bool failOnInvalidRlp) { - List<(Block Block, string ExpectedException)> correctRlp = []; - for (int i = 0; i < test.Blocks.Length; i++) + List<(Block Block, string ExpectedException)> correctRlp = new(); + for (int i = 0; i < test.Blocks!.Length; i++) { TestBlockJson testBlockJson = test.Blocks[i]; try { - RlpStream rlpContext = Bytes.FromHexString(testBlockJson.Rlp).AsRlpStream(); + RlpStream rlpContext = Bytes.FromHexString(testBlockJson.Rlp!).AsRlpStream(); Block suggestedBlock = Rlp.Decode(rlpContext); suggestedBlock.Header.SealEngineType = test.SealEngineUsed ? SealEngineType.Ethash : SealEngineType.None; @@ -302,7 +344,7 @@ private static BlockHeader SuggestBlocks(BlockchainTest test, bool failOnInvalid for (int uncleIndex = 0; uncleIndex < suggestedBlock.Uncles.Length; uncleIndex++) { - Assert.That(suggestedBlock.Uncles[uncleIndex].Hash, Is.EqualTo(new Hash256(testBlockJson.UncleHeaders[uncleIndex].Hash))); + Assert.That(suggestedBlock.Uncles[uncleIndex].Hash, Is.EqualTo(new Hash256(testBlockJson.UncleHeaders![uncleIndex].Hash))); } correctRlp.Add((suggestedBlock, testBlockJson.ExpectedException)); @@ -368,13 +410,9 @@ private static List RunAssertions(BlockchainTest test, Block headBlock, { if (test.PostStateRoot is not null) { - return test.PostStateRoot != stateProvider.StateRoot ? ["state root mismatch"] : []; + return test.PostStateRoot != stateProvider.StateRoot ? ["state root mismatch"] : Enumerable.Empty().ToList(); } - TestBlockHeaderJson testHeaderJson = (test.Blocks? - .Where(b => b.BlockHeader is not null) - .SingleOrDefault(b => new Hash256(b.BlockHeader.Hash) == headBlock.Hash)?.BlockHeader) ?? test.GenesisBlockHeader; - BlockHeader testHeader = JsonToEthereumTest.Convert(testHeaderJson); List differences = []; IEnumerable> deletedAccounts = test.Pre? @@ -388,7 +426,7 @@ private static List RunAssertions(BlockchainTest test, Block headBlock, } } - foreach ((Address acountAddress, AccountState accountState) in test.PostState) + foreach ((Address accountAddress, AccountState accountState) in test.PostState!) { int differencesBefore = differences.Count; @@ -398,87 +436,96 @@ private static List RunAssertions(BlockchainTest test, Block headBlock, break; } - bool accountExists = stateProvider.AccountExists(acountAddress); - UInt256? balance = accountExists ? stateProvider.GetBalance(acountAddress) : null; - UInt256? nonce = accountExists ? stateProvider.GetNonce(acountAddress) : null; + bool accountExists = stateProvider.AccountExists(accountAddress); + UInt256? balance = accountExists ? stateProvider.GetBalance(accountAddress) : null; + UInt256? nonce = accountExists ? stateProvider.GetNonce(accountAddress) : null; if (accountState.Balance != balance) { - differences.Add($"{acountAddress} balance exp: {accountState.Balance}, actual: {balance}, diff: {(balance > accountState.Balance ? balance - accountState.Balance : accountState.Balance - balance)}"); + differences.Add($"{accountAddress} balance exp: {accountState.Balance}, actual: {balance}, diff: {(balance > accountState.Balance ? balance - accountState.Balance : accountState.Balance - balance)}"); } if (accountState.Nonce != nonce) { - differences.Add($"{acountAddress} nonce exp: {accountState.Nonce}, actual: {nonce}"); + differences.Add($"{accountAddress} nonce exp: {accountState.Nonce}, actual: {nonce}"); } - byte[] code = accountExists ? stateProvider.GetCode(acountAddress) : []; + byte[] code = accountExists ? stateProvider.GetCode(accountAddress) : []; if (!Bytes.AreEqual(accountState.Code, code)) { - differences.Add($"{acountAddress} code exp: {accountState.Code?.Length}, actual: {code?.Length}"); + differences.Add($"{accountAddress} code exp: {accountState.Code?.Length}, actual: {code?.Length}"); } if (differences.Count != differencesBefore) { - _logger.Info($"ACCOUNT STATE ({acountAddress}) HAS DIFFERENCES"); + _logger.Info($"ACCOUNT STATE ({accountAddress}) HAS DIFFERENCES"); } differencesBefore = differences.Count; KeyValuePair[] clearedStorages = []; - if (test.Pre.ContainsKey(acountAddress)) + if (test.Pre.ContainsKey(accountAddress)) { - clearedStorages = [.. test.Pre[acountAddress].Storage.Where(s => !accountState.Storage.ContainsKey(s.Key))]; + clearedStorages = [.. test.Pre[accountAddress].Storage.Where(s => !accountState.Storage.ContainsKey(s.Key))]; } foreach (KeyValuePair clearedStorage in clearedStorages) { - ReadOnlySpan value = !stateProvider.AccountExists(acountAddress) ? Bytes.Empty : stateProvider.Get(new StorageCell(acountAddress, clearedStorage.Key)); + ReadOnlySpan value = !stateProvider.AccountExists(accountAddress) ? Bytes.Empty : stateProvider.Get(new StorageCell(accountAddress, clearedStorage.Key)); if (!value.IsZero()) { - differences.Add($"{acountAddress} storage[{clearedStorage.Key}] exp: 0x00, actual: {value.ToHexString(true)}"); + differences.Add($"{accountAddress} storage[{clearedStorage.Key}] exp: 0x00, actual: {value.ToHexString(true)}"); } } foreach (KeyValuePair storageItem in accountState.Storage) { - ReadOnlySpan value = !stateProvider.AccountExists(acountAddress) ? Bytes.Empty : stateProvider.Get(new StorageCell(acountAddress, storageItem.Key)); + ReadOnlySpan value = !stateProvider.AccountExists(accountAddress) ? Bytes.Empty : stateProvider.Get(new StorageCell(accountAddress, storageItem.Key)); if (!Bytes.AreEqual(storageItem.Value, value)) { - differences.Add($"{acountAddress} storage[{storageItem.Key}] exp: {storageItem.Value.ToHexString(true)}, actual: {value.ToHexString(true)}"); + differences.Add($"{accountAddress} storage[{storageItem.Key}] exp: {storageItem.Value.ToHexString(true)}, actual: {value.ToHexString(true)}"); } } if (differences.Count != differencesBefore) { - _logger.Info($"ACCOUNT STORAGE ({acountAddress}) HAS DIFFERENCES"); + _logger.Info($"ACCOUNT STORAGE ({accountAddress}) HAS DIFFERENCES"); } } - BigInteger gasUsed = headBlock.Header.GasUsed; - if ((testHeader?.GasUsed ?? 0) != gasUsed) - { - differences.Add($"GAS USED exp: {testHeader?.GasUsed ?? 0}, actual: {gasUsed}"); - } + TestBlockHeaderJson? testHeaderJson = test.Blocks? + .Where(b => b.BlockHeader is not null) + .SingleOrDefault(b => new Hash256(b.BlockHeader.Hash) == headBlock.Hash)?.BlockHeader; - if (headBlock.Transactions.Any() && testHeader.Bloom.ToString() != headBlock.Header.Bloom.ToString()) + if (testHeaderJson is not null) { - differences.Add($"BLOOM exp: {testHeader.Bloom}, actual: {headBlock.Header.Bloom}"); - } + BlockHeader testHeader = JsonToEthereumTest.Convert(testHeaderJson); - if (testHeader.StateRoot != stateProvider.StateRoot) - { - differences.Add($"STATE ROOT exp: {testHeader.StateRoot}, actual: {stateProvider.StateRoot}"); - } + BigInteger gasUsed = headBlock.Header.GasUsed; + if ((testHeader?.GasUsed ?? 0) != gasUsed) + { + differences.Add($"GAS USED exp: {testHeader?.GasUsed ?? 0}, actual: {gasUsed}"); + } - if (testHeader.TxRoot != headBlock.Header.TxRoot) - { - differences.Add($"TRANSACTIONS ROOT exp: {testHeader.TxRoot}, actual: {headBlock.Header.TxRoot}"); - } + if (headBlock.Transactions.Length != 0 && testHeader.Bloom.ToString() != headBlock.Header.Bloom.ToString()) + { + differences.Add($"BLOOM exp: {testHeader.Bloom}, actual: {headBlock.Header.Bloom}"); + } - if (testHeader.ReceiptsRoot != headBlock.Header.ReceiptsRoot) - { - differences.Add($"RECEIPT ROOT exp: {testHeader.ReceiptsRoot}, actual: {headBlock.Header.ReceiptsRoot}"); + if (testHeader.StateRoot != stateProvider.StateRoot) + { + differences.Add($"STATE ROOT exp: {testHeader.StateRoot}, actual: {stateProvider.StateRoot}"); + } + + if (testHeader.TxRoot != headBlock.Header.TxRoot) + { + differences.Add($"TRANSACTIONS ROOT exp: {testHeader.TxRoot}, actual: {headBlock.Header.TxRoot}"); + } + + if (testHeader.ReceiptsRoot != headBlock.Header.ReceiptsRoot) + { + differences.Add($"RECEIPT ROOT exp: {testHeader.ReceiptsRoot}, actual: {headBlock.Header.ReceiptsRoot}"); + } } if (test.LastBlockHash != headBlock.Hash) From 40c168d1676c8ad79ab45eaf47c3173de7fad722 Mon Sep 17 00:00:00 2001 From: Marc Harvey-Hill <10379486+Marchhill@users.noreply.github.com> Date: Mon, 20 Oct 2025 17:08:46 +0100 Subject: [PATCH 10/26] add tracer to blockchain tests runner --- .../BlockchainTestsRunner.cs | 38 ++++++++++--------- 1 file changed, 21 insertions(+), 17 deletions(-) diff --git a/src/Nethermind/Nethermind.Test.Runner/BlockchainTestsRunner.cs b/src/Nethermind/Nethermind.Test.Runner/BlockchainTestsRunner.cs index b9b3013d39f5..9f055bf779a7 100644 --- a/src/Nethermind/Nethermind.Test.Runner/BlockchainTestsRunner.cs +++ b/src/Nethermind/Nethermind.Test.Runner/BlockchainTestsRunner.cs @@ -3,28 +3,26 @@ using System; using System.Collections.Generic; -using System.IO; using System.Text.RegularExpressions; using System.Threading.Tasks; using Ethereum.Test.Base; using Ethereum.Test.Base.Interfaces; +using Nethermind.Blockchain.Tracing.GethStyle; +using Nethermind.Evm.Tracing; namespace Nethermind.Test.Runner; -public class BlockchainTestsRunner : BlockchainTestBase, IBlockchainTestRunner +public class BlockchainTestsRunner( + ITestSourceLoader testsSource, + string? filter, + ulong chainId, + bool trace = false, + bool traceMemory = false, + bool traceNoStack = false) + : BlockchainTestBase, IBlockchainTestRunner { - private readonly ConsoleColor _defaultColour; - private readonly ITestSourceLoader _testsSource; - private readonly string? _filter; - private readonly ulong _chainId; - - public BlockchainTestsRunner(ITestSourceLoader testsSource, string? filter, ulong chainId) - { - _testsSource = testsSource ?? throw new ArgumentNullException(nameof(testsSource)); - _defaultColour = Console.ForegroundColor; - _filter = filter; - _chainId = chainId; - } + private readonly ConsoleColor _defaultColour = Console.ForegroundColor; + private readonly ITestSourceLoader _testsSource = testsSource ?? throw new ArgumentNullException(nameof(testsSource)); public async Task> RunTestsAsync() { @@ -39,9 +37,14 @@ public async Task> RunTestsAsync() continue; } + // Create a streaming tracer once for all tests if tracing is enabled + using BlockchainTestStreamingTracer? tracer = trace + ? new BlockchainTestStreamingTracer(new() { EnableMemory = traceMemory, DisableStack = traceNoStack }) + : null; + BlockchainTest test = loadedTest as BlockchainTest; - if (_filter is not null && !Regex.Match(test.Name, $"^({_filter})").Success) + if (filter is not null && test.Name is not null && !Regex.Match(test.Name, $"^({filter})").Success) continue; Setup(); @@ -53,8 +56,9 @@ public async Task> RunTestsAsync() } else { - test.ChainId = _chainId; - EthereumTestResult result = await RunTest(test); + test.ChainId = chainId; + + EthereumTestResult result = await RunTest(test, tracer: tracer); testResults.Add(result); if (result.Pass) WriteGreen("PASS"); From 6ed4f1f723bda899a810692f08b8c155876a3efe Mon Sep 17 00:00:00 2001 From: Marc Harvey-Hill <10379486+Marchhill@users.noreply.github.com> Date: Mon, 20 Oct 2025 17:31:34 +0100 Subject: [PATCH 11/26] tidy --- .../Ethereum.Test.Base/BlockchainTestBase.cs | 17 +++++------------ .../Ethereum.Test.Base/FileTestsSource.cs | 13 ++++--------- .../LoadBlockchainTestFileStrategy.cs | 1 - 3 files changed, 9 insertions(+), 22 deletions(-) diff --git a/src/Nethermind/Ethereum.Test.Base/BlockchainTestBase.cs b/src/Nethermind/Ethereum.Test.Base/BlockchainTestBase.cs index 72a34b70137a..6d4a1b20c8c9 100644 --- a/src/Nethermind/Ethereum.Test.Base/BlockchainTestBase.cs +++ b/src/Nethermind/Ethereum.Test.Base/BlockchainTestBase.cs @@ -155,7 +155,6 @@ protected async Task RunTest(BlockchainTest test, Stopwatch? test.GenesisRlp ??= Rlp.Encode(new Block(JsonToEthereumTest.Convert(test.GenesisBlockHeader))); Block genesisBlock = Rlp.Decode(test.GenesisRlp.Bytes); - // Console.WriteLine(genesisBlock.ToString(Block.Format.Full)); Assert.That(genesisBlock.Header.Hash, Is.EqualTo(new Hash256(test.GenesisBlockHeader.Hash))); ManualResetEvent genesisProcessed = new(false); @@ -328,7 +327,7 @@ private async static void RunNewPayloads(TestEngineNewPayloadsJson[]? newPayload private static List<(Block Block, string ExpectedException)> DecodeRlps(BlockchainTest test, bool failOnInvalidRlp) { - List<(Block Block, string ExpectedException)> correctRlp = new(); + List<(Block Block, string ExpectedException)> correctRlp = []; for (int i = 0; i < test.Blocks!.Length; i++) { TestBlockJson testBlockJson = test.Blocks[i]; @@ -354,16 +353,10 @@ private async static void RunNewPayloads(TestEngineNewPayloadsJson[]? newPayload if (testBlockJson.ExpectedException is null) { string invalidRlpMessage = $"Invalid RLP ({i}) {e}"; - if (failOnInvalidRlp) - { - Assert.Fail(invalidRlpMessage); - } - else - { - // ForgedTests don't have ExpectedException and at the same time have invalid rlps - // Don't fail here. If test executed incorrectly will fail at last check - _logger.Warn(invalidRlpMessage); - } + Assert.That(!failOnInvalidRlp, invalidRlpMessage); + // ForgedTests don't have ExpectedException and at the same time have invalid rlps + // Don't fail here. If test executed incorrectly will fail at last check + _logger.Warn(invalidRlpMessage); } else { diff --git a/src/Nethermind/Ethereum.Test.Base/FileTestsSource.cs b/src/Nethermind/Ethereum.Test.Base/FileTestsSource.cs index 3e85b70087f4..45e0b041ae02 100644 --- a/src/Nethermind/Ethereum.Test.Base/FileTestsSource.cs +++ b/src/Nethermind/Ethereum.Test.Base/FileTestsSource.cs @@ -9,16 +9,10 @@ namespace Ethereum.Test.Base { - public class FileTestsSource + public class FileTestsSource(string fileName, string? wildcard = null) { - private readonly string _fileName; - private readonly string? _wildcard; - - public FileTestsSource(string fileName, string? wildcard = null) - { - _fileName = fileName ?? throw new ArgumentNullException(nameof(fileName)); - _wildcard = wildcard; - } + private readonly string _fileName = fileName ?? throw new ArgumentNullException(nameof(fileName)); + private readonly string? _wildcard = wildcard; public IEnumerable LoadTests(TestType testType) { @@ -34,6 +28,7 @@ public IEnumerable LoadTests(TestType testType) return []; } + Console.WriteLine("Loading test " + _fileName); string json = File.ReadAllText(_fileName, Encoding.Default); return testType switch diff --git a/src/Nethermind/Ethereum.Test.Base/LoadBlockchainTestFileStrategy.cs b/src/Nethermind/Ethereum.Test.Base/LoadBlockchainTestFileStrategy.cs index 6f7aad55a2c3..71c23519184d 100644 --- a/src/Nethermind/Ethereum.Test.Base/LoadBlockchainTestFileStrategy.cs +++ b/src/Nethermind/Ethereum.Test.Base/LoadBlockchainTestFileStrategy.cs @@ -1,7 +1,6 @@ // SPDX-FileCopyrightText: 2022 Demerzel Solutions Limited // SPDX-License-Identifier: LGPL-3.0-only -using System; using System.Collections.Generic; using Ethereum.Test.Base.Interfaces; From a4eb1754c5bfca802376c8c7a64711300c9015b0 Mon Sep 17 00:00:00 2001 From: Marc Harvey-Hill <10379486+Marchhill@users.noreply.github.com> Date: Wed, 29 Oct 2025 19:25:57 +0000 Subject: [PATCH 12/26] tidy --- .../Ethereum.Blockchain.Pyspec.Test/BerlinBlockChainTests.cs | 4 +++- .../Ethereum.Blockchain.Pyspec.Test/BerlinStateTests.cs | 4 +++- .../ByzantiumBlockChainTests.cs | 4 +++- .../Ethereum.Blockchain.Pyspec.Test/ByzantiumStateTests.cs | 4 +++- .../Ethereum.Blockchain.Pyspec.Test/CancunBlockChainTests.cs | 1 - .../Ethereum.Blockchain.Pyspec.Test/CancunStateTests.cs | 1 - .../FrontierBlockChainTests.cs | 4 +++- .../Ethereum.Blockchain.Pyspec.Test/FrontierStateTests.cs | 4 +++- .../HomesteadBlockChainTests.cs | 4 +++- .../Ethereum.Blockchain.Pyspec.Test/HomesteadStateTests.cs | 4 +++- .../IstanbulBlockChainTests.cs | 4 +++- .../Ethereum.Blockchain.Pyspec.Test/IstanbulStateTests.cs | 4 +++- .../Ethereum.Blockchain.Pyspec.Test/OsakaBlockChainTests.cs | 1 - .../Ethereum.Blockchain.Pyspec.Test/OsakaEofTests.cs | 1 - .../Ethereum.Blockchain.Pyspec.Test/OsakaStateTests.cs | 1 - .../ShanghaiBlockChainTests.cs | 1 - .../Ethereum.Blockchain.Pyspec.Test/ShanghaiStateTests.cs | 4 +++- src/Nethermind/Ethereum.Test.Base/GeneralTestBase.cs | 3 --- 18 files changed, 33 insertions(+), 20 deletions(-) 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/GeneralTestBase.cs b/src/Nethermind/Ethereum.Test.Base/GeneralTestBase.cs index 16e85265b851..6e7ad62e6356 100644 --- a/src/Nethermind/Ethereum.Test.Base/GeneralTestBase.cs +++ b/src/Nethermind/Ethereum.Test.Base/GeneralTestBase.cs @@ -5,7 +5,6 @@ using System.Collections.Generic; using System.Diagnostics; using Autofac; -using Nethermind.Api; using NUnit.Framework; using Nethermind.Config; using Nethermind.Consensus.Processing; @@ -23,10 +22,8 @@ using Nethermind.Evm.State; using Nethermind.Evm.TransactionProcessing; using Nethermind.Logging; -using Nethermind.Specs; using Nethermind.Specs.Forks; using Nethermind.Specs.Test; -using Nethermind.State; namespace Ethereum.Test.Base { From a55521308a6d678875adc02ab7d4ac152b2a0326 Mon Sep 17 00:00:00 2001 From: Marc Harvey-Hill <10379486+Marchhill@users.noreply.github.com> Date: Wed, 29 Oct 2025 21:26:41 +0000 Subject: [PATCH 13/26] add genesis processing timeout --- src/Nethermind/Ethereum.Test.Base/BlockchainTestBase.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Nethermind/Ethereum.Test.Base/BlockchainTestBase.cs b/src/Nethermind/Ethereum.Test.Base/BlockchainTestBase.cs index 6d4a1b20c8c9..b43e7be9fff7 100644 --- a/src/Nethermind/Ethereum.Test.Base/BlockchainTestBase.cs +++ b/src/Nethermind/Ethereum.Test.Base/BlockchainTestBase.cs @@ -169,7 +169,7 @@ protected async Task RunTest(BlockchainTest test, Stopwatch? }; blockTree.SuggestBlock(genesisBlock); - genesisProcessed.WaitOne(); + genesisProcessed.WaitOne(10000); parentHeader = genesisBlock.Header; // Dispose genesis block's AccountChanges From b5d37c888be8574d4f526b74fe2c7f27eb92c320 Mon Sep 17 00:00:00 2001 From: Marc Harvey-Hill <10379486+Marchhill@users.noreply.github.com> Date: Thu, 30 Oct 2025 10:35:45 +0000 Subject: [PATCH 14/26] check for null head block --- .../Ethereum.Test.Base/BlockchainTestBase.cs | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/src/Nethermind/Ethereum.Test.Base/BlockchainTestBase.cs b/src/Nethermind/Ethereum.Test.Base/BlockchainTestBase.cs index b43e7be9fff7..e17569cfcbd8 100644 --- a/src/Nethermind/Ethereum.Test.Base/BlockchainTestBase.cs +++ b/src/Nethermind/Ethereum.Test.Base/BlockchainTestBase.cs @@ -169,7 +169,7 @@ protected async Task RunTest(BlockchainTest test, Stopwatch? }; blockTree.SuggestBlock(genesisBlock); - genesisProcessed.WaitOne(10000); + genesisProcessed.WaitOne(5000); parentHeader = genesisBlock.Header; // Dispose genesis block's AccountChanges @@ -201,6 +201,13 @@ protected async Task RunTest(BlockchainTest test, Stopwatch? preWarmer?.ClearCaches(); Block? headBlock = blockTree.RetrieveHeadBlock(); + + Assert.That(headBlock, Is.Not.Null); + if (headBlock is null) + { + return new EthereumTestResult(test.Name, null, false); + } + List differences; using (stateProvider.BeginScope(headBlock.Header)) { @@ -218,13 +225,7 @@ protected async Task RunTest(BlockchainTest test, Stopwatch? } Assert.That(differences, Is.Empty, "differences"); - - return new EthereumTestResult - ( - test.Name, - null, - differences.Count == 0 - ); + return new EthereumTestResult(test.Name, null, differences.Count == 0); } catch (Exception) { From eba035c108789a7781fc3c7b01686ff337ffb3de Mon Sep 17 00:00:00 2001 From: Marc Harvey-Hill <10379486+Marchhill@users.noreply.github.com> Date: Thu, 30 Oct 2025 10:57:31 +0000 Subject: [PATCH 15/26] try undo some changes --- src/Nethermind/Ethereum.Test.Base/BlockchainTestBase.cs | 6 +++++- src/Nethermind/Ethereum.Test.Base/GeneralTestBase.cs | 6 +++++- .../Ethereum.Test.Base/Interfaces/EthereumTest.cs | 6 +++--- 3 files changed, 13 insertions(+), 5 deletions(-) diff --git a/src/Nethermind/Ethereum.Test.Base/BlockchainTestBase.cs b/src/Nethermind/Ethereum.Test.Base/BlockchainTestBase.cs index e17569cfcbd8..635bc6057ed5 100644 --- a/src/Nethermind/Ethereum.Test.Base/BlockchainTestBase.cs +++ b/src/Nethermind/Ethereum.Test.Base/BlockchainTestBase.cs @@ -42,11 +42,13 @@ public abstract class BlockchainTestBase { private static readonly ILogger _logger; private static readonly ILogManager _logManager = new TestLogManager(); + private static ISealValidator Sealer { get; } private static DifficultyCalculatorWrapper DifficultyCalculator { get; } 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(); } @@ -82,7 +84,9 @@ protected async Task RunTest(BlockchainTest test, Stopwatch? test.Network = ChainUtils.ResolveSpec(test.Network, test.ChainId); test.NetworkAfterTransition = ChainUtils.ResolveSpec(test.NetworkAfterTransition, test.ChainId); - List<(ForkActivation Activation, IReleaseSpec Spec)> transitions = [((ForkActivation)0, test.Network)]; + // List<(ForkActivation Activation, IReleaseSpec Spec)> transitions = [((ForkActivation)0, test.Network)]; + List<(ForkActivation Activation, IReleaseSpec Spec)> transitions = + [((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)); diff --git a/src/Nethermind/Ethereum.Test.Base/GeneralTestBase.cs b/src/Nethermind/Ethereum.Test.Base/GeneralTestBase.cs index 6e7ad62e6356..a98d9360e7fc 100644 --- a/src/Nethermind/Ethereum.Test.Base/GeneralTestBase.cs +++ b/src/Nethermind/Ethereum.Test.Base/GeneralTestBase.cs @@ -65,7 +65,11 @@ protected EthereumTestResult RunTest(GeneralStateTest test, ITxTracer txTracer) test.Fork = ChainUtils.ResolveSpec(test.Fork, test.ChainId); - ISpecProvider specProvider = new CustomSpecProvider(test.ChainId, test.ChainId, ((ForkActivation)0, test.Fork)); + // ISpecProvider specProvider = new CustomSpecProvider(test.ChainId, test.ChainId, ((ForkActivation)0, test.Fork)); + ISpecProvider specProvider = + new CustomSpecProvider(test.ChainId, test.ChainId, + ((ForkActivation)0, test.GenesisSpec), // TODO: this thing took a lot of time to find after it was removed!, genesis block is always initialized with Frontier + ((ForkActivation)1, test.Fork)); IConfigProvider configProvider = new ConfigProvider(); using IContainer container = new ContainerBuilder() diff --git a/src/Nethermind/Ethereum.Test.Base/Interfaces/EthereumTest.cs b/src/Nethermind/Ethereum.Test.Base/Interfaces/EthereumTest.cs index 03e55c585a0e..d64cf0a6d6da 100644 --- a/src/Nethermind/Ethereum.Test.Base/Interfaces/EthereumTest.cs +++ b/src/Nethermind/Ethereum.Test.Base/Interfaces/EthereumTest.cs @@ -9,8 +9,8 @@ public abstract class EthereumTest public string? Name { get; set; } public string? LoadFailure { get; set; } public ulong ChainId { get; set; } = MainnetSpecProvider.Instance.ChainId; - // public IReleaseSpec GenesisSpec => ChainId == MainnetSpecProvider.Instance.ChainId - // ? MainnetSpecProvider.Instance.GenesisSpec - // : GnosisSpecProvider.Instance.GenesisSpec; + public IReleaseSpec GenesisSpec => ChainId == MainnetSpecProvider.Instance.ChainId + ? MainnetSpecProvider.Instance.GenesisSpec + : GnosisSpecProvider.Instance.GenesisSpec; } } From 802abc9ce0e337fdb0cf5708528fd5b553b9263a Mon Sep 17 00:00:00 2001 From: Marc Harvey-Hill <10379486+Marchhill@users.noreply.github.com> Date: Thu, 30 Oct 2025 11:42:36 +0000 Subject: [PATCH 16/26] detect failure to process genesis --- .../Ethereum.Test.Base/BlockchainTestBase.cs | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/src/Nethermind/Ethereum.Test.Base/BlockchainTestBase.cs b/src/Nethermind/Ethereum.Test.Base/BlockchainTestBase.cs index 635bc6057ed5..99f20986a6b2 100644 --- a/src/Nethermind/Ethereum.Test.Base/BlockchainTestBase.cs +++ b/src/Nethermind/Ethereum.Test.Base/BlockchainTestBase.cs @@ -134,7 +134,7 @@ protected async Task RunTest(BlockchainTest test, Stopwatch? 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(); IEngineRpcModule engineRpcModule = container.Resolve(); @@ -172,6 +172,15 @@ protected async Task RunTest(BlockchainTest test, Stopwatch? } }; + blockchainProcessor.BlockRemoved += (_, args) => + { + if (args.BlockHash == genesisBlock.Header.Hash) + { + Assert.Fail($"Failed to process genesis block: {args.Exception}"); + genesisProcessed.Set(); + } + }; + blockTree.SuggestBlock(genesisBlock); genesisProcessed.WaitOne(5000); parentHeader = genesisBlock.Header; From bfbcfbba99681ade131ad131133ebdaf63b70401 Mon Sep 17 00:00:00 2001 From: Marc Harvey-Hill <10379486+Marchhill@users.noreply.github.com> Date: Thu, 30 Oct 2025 11:50:43 +0000 Subject: [PATCH 17/26] check removal is error --- src/Nethermind/Ethereum.Test.Base/BlockchainTestBase.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Nethermind/Ethereum.Test.Base/BlockchainTestBase.cs b/src/Nethermind/Ethereum.Test.Base/BlockchainTestBase.cs index 99f20986a6b2..b86b90832f32 100644 --- a/src/Nethermind/Ethereum.Test.Base/BlockchainTestBase.cs +++ b/src/Nethermind/Ethereum.Test.Base/BlockchainTestBase.cs @@ -174,7 +174,7 @@ protected async Task RunTest(BlockchainTest test, Stopwatch? blockchainProcessor.BlockRemoved += (_, args) => { - if (args.BlockHash == genesisBlock.Header.Hash) + if (args.ProcessingResult != ProcessingResult.Success && args.BlockHash == genesisBlock.Header.Hash) { Assert.Fail($"Failed to process genesis block: {args.Exception}"); genesisProcessed.Set(); From c1f5bc3111c22925f566a271e4af3dbabfcde2f6 Mon Sep 17 00:00:00 2001 From: Marc Harvey-Hill <10379486+Marchhill@users.noreply.github.com> Date: Thu, 30 Oct 2025 11:59:04 +0000 Subject: [PATCH 18/26] add back checks for genesis spec --- .../Ethereum.Test.Base/BlockchainTestBase.cs | 10 +++++++++- src/Nethermind/Ethereum.Test.Base/GeneralTestBase.cs | 9 +++++++-- 2 files changed, 16 insertions(+), 3 deletions(-) diff --git a/src/Nethermind/Ethereum.Test.Base/BlockchainTestBase.cs b/src/Nethermind/Ethereum.Test.Base/BlockchainTestBase.cs index b86b90832f32..4d7b5ac07a07 100644 --- a/src/Nethermind/Ethereum.Test.Base/BlockchainTestBase.cs +++ b/src/Nethermind/Ethereum.Test.Base/BlockchainTestBase.cs @@ -26,6 +26,7 @@ using Nethermind.Int256; using Nethermind.Logging; using Nethermind.Serialization.Rlp; +using Nethermind.Specs; using Nethermind.Specs.Forks; using Nethermind.Specs.Test; using Nethermind.Evm.State; @@ -84,9 +85,13 @@ protected async Task RunTest(BlockchainTest test, Stopwatch? test.Network = ChainUtils.ResolveSpec(test.Network, test.ChainId); test.NetworkAfterTransition = ChainUtils.ResolveSpec(test.NetworkAfterTransition, test.ChainId); - // List<(ForkActivation Activation, IReleaseSpec Spec)> transitions = [((ForkActivation)0, test.Network)]; + 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)); @@ -94,6 +99,8 @@ protected async Task RunTest(BlockchainTest test, Stopwatch? ISpecProvider specProvider = new CustomSpecProvider(test.ChainId, test.ChainId, transitions.ToArray()); + 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) { await KzgPolynomialCommitments.InitializeAsync(); @@ -196,6 +203,7 @@ protected async Task RunTest(BlockchainTest test, Stopwatch? } else if (test.EngineNewPayloads is not null) { + // engine test RunNewPayloads(test.EngineNewPayloads, engineRpcModule); } else diff --git a/src/Nethermind/Ethereum.Test.Base/GeneralTestBase.cs b/src/Nethermind/Ethereum.Test.Base/GeneralTestBase.cs index a98d9360e7fc..f0167769b2fa 100644 --- a/src/Nethermind/Ethereum.Test.Base/GeneralTestBase.cs +++ b/src/Nethermind/Ethereum.Test.Base/GeneralTestBase.cs @@ -22,6 +22,7 @@ using Nethermind.Evm.State; using Nethermind.Evm.TransactionProcessing; using Nethermind.Logging; +using Nethermind.Specs; using Nethermind.Specs.Forks; using Nethermind.Specs.Test; @@ -41,7 +42,7 @@ static GeneralStateTestBase() } [SetUp] - public static void Setup() + public void Setup() { } @@ -65,12 +66,16 @@ protected EthereumTestResult RunTest(GeneralStateTest test, ITxTracer txTracer) test.Fork = ChainUtils.ResolveSpec(test.Fork, test.ChainId); - // ISpecProvider specProvider = new CustomSpecProvider(test.ChainId, test.ChainId, ((ForkActivation)0, test.Fork)); ISpecProvider specProvider = new CustomSpecProvider(test.ChainId, test.ChainId, ((ForkActivation)0, test.GenesisSpec), // TODO: this thing took a lot of time to find after it was removed!, genesis block is always initialized with Frontier ((ForkActivation)1, test.Fork)); + if (test.ChainId != GnosisSpecProvider.Instance.ChainId && specProvider.GenesisSpec != Frontier.Instance) + { + Assert.Fail("Expected genesis spec to be Frontier for blockchain tests"); + } + IConfigProvider configProvider = new ConfigProvider(); using IContainer container = new ContainerBuilder() .AddModule(new TestNethermindModule(configProvider)) From a213860f69a4ed5df68a3edc0b678415fabead90 Mon Sep 17 00:00:00 2001 From: Marc Harvey-Hill <10379486+Marchhill@users.noreply.github.com> Date: Thu, 30 Oct 2025 12:02:35 +0000 Subject: [PATCH 19/26] only add noenginerequeststracker in tests --- .../Nethermind.Core.Test/Modules/TestMergeModule.cs | 4 +++- src/Nethermind/Nethermind.Merge.Plugin/MergePlugin.cs | 1 - 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/src/Nethermind/Nethermind.Core.Test/Modules/TestMergeModule.cs b/src/Nethermind/Nethermind.Core.Test/Modules/TestMergeModule.cs index 18347180f0fa..2a930771c265 100644 --- a/src/Nethermind/Nethermind.Core.Test/Modules/TestMergeModule.cs +++ b/src/Nethermind/Nethermind.Core.Test/Modules/TestMergeModule.cs @@ -11,7 +11,6 @@ using Nethermind.Consensus.Rewards; using Nethermind.Merge.Plugin; using Nethermind.Merge.Plugin.BlockProduction; -using Nethermind.Merge.Plugin.InvalidChainTracker; using Nethermind.TxPool; namespace Nethermind.Core.Test.Modules; @@ -43,6 +42,9 @@ protected override void Load(ContainerBuilder builder) .AddDecorator() .AddScoped() .AddDecorator() + + // Engine rpc + .AddSingleton() ; if (txPoolConfig.BlobsSupport.SupportsReorgs()) diff --git a/src/Nethermind/Nethermind.Merge.Plugin/MergePlugin.cs b/src/Nethermind/Nethermind.Merge.Plugin/MergePlugin.cs index f5d933da50df..5a350188359c 100644 --- a/src/Nethermind/Nethermind.Merge.Plugin/MergePlugin.cs +++ b/src/Nethermind/Nethermind.Merge.Plugin/MergePlugin.cs @@ -328,7 +328,6 @@ protected override void Load(ContainerBuilder builder) .AddSingleton() .AddSingleton>, GetBlobsHandler>() .AddSingleton?>, GetBlobsHandlerV2>() - .AddSingleton() .AddSingleton() .AddSingleton((ctx) => From 176cf575586e898851ddfefb352928b7a335ac19 Mon Sep 17 00:00:00 2001 From: Marc Harvey-Hill <10379486+Marchhill@users.noreply.github.com> Date: Thu, 30 Oct 2025 12:15:54 +0000 Subject: [PATCH 20/26] comment sealed block check --- src/Nethermind/Nethermind.Trie/Pruning/TrieStore.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Nethermind/Nethermind.Trie/Pruning/TrieStore.cs b/src/Nethermind/Nethermind.Trie/Pruning/TrieStore.cs index c203a906dce6..2279e6438249 100644 --- a/src/Nethermind/Nethermind.Trie/Pruning/TrieStore.cs +++ b/src/Nethermind/Nethermind.Trie/Pruning/TrieStore.cs @@ -935,7 +935,7 @@ private void VerifyNewCommitSet(long blockNumber) { if (_lastCommitSet is not null) { - Debug.Assert(_lastCommitSet.IsSealed, "Not sealed when beginning new block"); + // Debug.Assert(_lastCommitSet.IsSealed, "Not sealed when beginning new block"); if (_lastCommitSet.BlockNumber != blockNumber - 1 && blockNumber != 0 && _lastCommitSet.BlockNumber != 0) { From 5d4cd477bf53ba0b10fed603ceb82886fe71ca77 Mon Sep 17 00:00:00 2001 From: Marc Harvey-Hill <10379486+Marchhill@users.noreply.github.com> Date: Thu, 30 Oct 2025 12:38:54 +0000 Subject: [PATCH 21/26] try remove timeout --- src/Nethermind/Ethereum.Test.Base/BlockchainTestBase.cs | 2 +- src/Nethermind/Nethermind.Trie/Pruning/TrieStore.cs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Nethermind/Ethereum.Test.Base/BlockchainTestBase.cs b/src/Nethermind/Ethereum.Test.Base/BlockchainTestBase.cs index 4d7b5ac07a07..3a023070193e 100644 --- a/src/Nethermind/Ethereum.Test.Base/BlockchainTestBase.cs +++ b/src/Nethermind/Ethereum.Test.Base/BlockchainTestBase.cs @@ -189,7 +189,7 @@ protected async Task RunTest(BlockchainTest test, Stopwatch? }; blockTree.SuggestBlock(genesisBlock); - genesisProcessed.WaitOne(5000); + genesisProcessed.WaitOne(); parentHeader = genesisBlock.Header; // Dispose genesis block's AccountChanges diff --git a/src/Nethermind/Nethermind.Trie/Pruning/TrieStore.cs b/src/Nethermind/Nethermind.Trie/Pruning/TrieStore.cs index 2279e6438249..c203a906dce6 100644 --- a/src/Nethermind/Nethermind.Trie/Pruning/TrieStore.cs +++ b/src/Nethermind/Nethermind.Trie/Pruning/TrieStore.cs @@ -935,7 +935,7 @@ private void VerifyNewCommitSet(long blockNumber) { if (_lastCommitSet is not null) { - // Debug.Assert(_lastCommitSet.IsSealed, "Not sealed when beginning new block"); + Debug.Assert(_lastCommitSet.IsSealed, "Not sealed when beginning new block"); if (_lastCommitSet.BlockNumber != blockNumber - 1 && blockNumber != 0 && _lastCommitSet.BlockNumber != 0) { From 954c1329a515f4aeb06e1bdaabec3c5fa6b98406 Mon Sep 17 00:00:00 2001 From: Marc Harvey-Hill <10379486+Marchhill@users.noreply.github.com> Date: Thu, 30 Oct 2025 12:47:35 +0000 Subject: [PATCH 22/26] only configure merge for engine tests --- .../Ethereum.Test.Base/BlockchainTestBase.cs | 17 +++++++++++------ 1 file changed, 11 insertions(+), 6 deletions(-) diff --git a/src/Nethermind/Ethereum.Test.Base/BlockchainTestBase.cs b/src/Nethermind/Ethereum.Test.Base/BlockchainTestBase.cs index 3a023070193e..cade76f24165 100644 --- a/src/Nethermind/Ethereum.Test.Base/BlockchainTestBase.cs +++ b/src/Nethermind/Ethereum.Test.Base/BlockchainTestBase.cs @@ -129,22 +129,26 @@ 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)) .AddModule(new TestMergeModule(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; BlockchainProcessor blockchainProcessor = (BlockchainProcessor)mainBlockProcessingContext.BlockchainProcessor; IBlockTree blockTree = container.Resolve(); IBlockValidator blockValidator = container.Resolve(); - IEngineRpcModule engineRpcModule = container.Resolve(); blockchainProcessor.Start(); // Register tracer if provided for blocktest tracing @@ -204,6 +208,7 @@ protected async Task RunTest(BlockchainTest test, Stopwatch? else if (test.EngineNewPayloads is not null) { // engine test + IEngineRpcModule engineRpcModule = container.Resolve(); RunNewPayloads(test.EngineNewPayloads, engineRpcModule); } else @@ -246,7 +251,7 @@ protected async Task RunTest(BlockchainTest test, Stopwatch? } Assert.That(differences, Is.Empty, "differences"); - return new EthereumTestResult(test.Name, null, differences.Count == 0); + return new EthereumTestResult(test.Name, null, testPassed); } catch (Exception) { From c1ef3537a47d948d6affd57d000e2bee52295eab Mon Sep 17 00:00:00 2001 From: Marc Harvey-Hill <10379486+Marchhill@users.noreply.github.com> Date: Thu, 30 Oct 2025 12:49:15 +0000 Subject: [PATCH 23/26] fix merge module init --- src/Nethermind/Ethereum.Test.Base/BlockchainTestBase.cs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/Nethermind/Ethereum.Test.Base/BlockchainTestBase.cs b/src/Nethermind/Ethereum.Test.Base/BlockchainTestBase.cs index cade76f24165..20ec4f56082e 100644 --- a/src/Nethermind/Ethereum.Test.Base/BlockchainTestBase.cs +++ b/src/Nethermind/Ethereum.Test.Base/BlockchainTestBase.cs @@ -131,12 +131,11 @@ protected async Task RunTest(BlockchainTest test, Stopwatch? // configProvider.GetConfig().PreWarmStateOnBlockProcessing = false; ContainerBuilder containerBuilder = new ContainerBuilder() .AddModule(new TestNethermindModule(configProvider)) - .AddModule(new TestMergeModule(configProvider)) .AddSingleton(specProvider) .AddSingleton(_logManager) .AddSingleton(rewardCalculator) .AddSingleton(DifficultyCalculator); - + if (isEngineTest) { containerBuilder.AddModule(new TestMergeModule(configProvider)); From 063f70d9245ed6ae7988f261b09ffb00968c348e Mon Sep 17 00:00:00 2001 From: Marc Harvey-Hill <10379486+Marchhill@users.noreply.github.com> Date: Thu, 30 Oct 2025 12:54:51 +0000 Subject: [PATCH 24/26] add back timeout and remove sealer --- src/Nethermind/Ethereum.Test.Base/BlockchainTestBase.cs | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/src/Nethermind/Ethereum.Test.Base/BlockchainTestBase.cs b/src/Nethermind/Ethereum.Test.Base/BlockchainTestBase.cs index 20ec4f56082e..53fb9866be2a 100644 --- a/src/Nethermind/Ethereum.Test.Base/BlockchainTestBase.cs +++ b/src/Nethermind/Ethereum.Test.Base/BlockchainTestBase.cs @@ -31,7 +31,6 @@ 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; @@ -43,13 +42,12 @@ public abstract class BlockchainTestBase { private static readonly ILogger _logger; private static readonly ILogManager _logManager = new TestLogManager(); - 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(); } @@ -192,7 +190,7 @@ protected async Task RunTest(BlockchainTest test, Stopwatch? }; blockTree.SuggestBlock(genesisBlock); - genesisProcessed.WaitOne(); + genesisProcessed.WaitOne(_genesisProcessingTimeoutMs); parentHeader = genesisBlock.Header; // Dispose genesis block's AccountChanges From 1c6ae5180ff7b8549fa945996952eef955e8abc6 Mon Sep 17 00:00:00 2001 From: Marc Harvey-Hill <10379486+Marchhill@users.noreply.github.com> Date: Fri, 31 Oct 2025 12:56:58 +0000 Subject: [PATCH 25/26] await new payloads --- src/Nethermind/Ethereum.Test.Base/BlockchainTestBase.cs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/Nethermind/Ethereum.Test.Base/BlockchainTestBase.cs b/src/Nethermind/Ethereum.Test.Base/BlockchainTestBase.cs index 53fb9866be2a..baeeec596c05 100644 --- a/src/Nethermind/Ethereum.Test.Base/BlockchainTestBase.cs +++ b/src/Nethermind/Ethereum.Test.Base/BlockchainTestBase.cs @@ -97,7 +97,7 @@ protected async Task RunTest(BlockchainTest test, Stopwatch? ISpecProvider specProvider = new CustomSpecProvider(test.ChainId, test.ChainId, transitions.ToArray()); - Assert.That(!isEngineTest && (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) { @@ -206,7 +206,7 @@ protected async Task RunTest(BlockchainTest test, Stopwatch? { // engine test IEngineRpcModule engineRpcModule = container.Resolve(); - RunNewPayloads(test.EngineNewPayloads, engineRpcModule); + await RunNewPayloads(test.EngineNewPayloads, engineRpcModule); } else { @@ -309,7 +309,7 @@ private static BlockHeader SuggestBlocks(BlockchainTest test, bool failOnInvalid return parentHeader; } - private async static void RunNewPayloads(TestEngineNewPayloadsJson[]? newPayloads, IEngineRpcModule engineRpcModule) + private async static Task RunNewPayloads(TestEngineNewPayloadsJson[]? newPayloads, IEngineRpcModule engineRpcModule) { (ExecutionPayload, string[]?, string[]?, string?)[] payloads = [.. JsonToEthereumTest.Convert(newPayloads)]; @@ -319,7 +319,7 @@ private async static void RunNewPayloads(TestEngineNewPayloadsJson[]? newPayload ResultWrapper res; byte[]?[] hashes = blobVersionedHashes is null ? null : [.. blobVersionedHashes.Select(x => Bytes.FromHexString(x))]; - switch (newPayloadVersion ?? "5") + switch (newPayloadVersion ?? "4") { case "1": res = await engineRpcModule.engine_newPayloadV1(executionPayload); From 684d01413e3494ac94434da7d41da2afda09a1ad Mon Sep 17 00:00:00 2001 From: Marc Harvey-Hill <10379486+Marchhill@users.noreply.github.com> Date: Fri, 31 Oct 2025 13:28:08 +0000 Subject: [PATCH 26/26] use reflection for engine rpc method calling --- .../Ethereum.Test.Base/BlockchainTestBase.cs | 43 ++++++++----------- .../Ethereum.Test.Base/JsonToEthereumTest.cs | 4 +- 2 files changed, 20 insertions(+), 27 deletions(-) diff --git a/src/Nethermind/Ethereum.Test.Base/BlockchainTestBase.cs b/src/Nethermind/Ethereum.Test.Base/BlockchainTestBase.cs index baeeec596c05..ee72db8fead6 100644 --- a/src/Nethermind/Ethereum.Test.Base/BlockchainTestBase.cs +++ b/src/Nethermind/Ethereum.Test.Base/BlockchainTestBase.cs @@ -35,6 +35,7 @@ using Nethermind.Merge.Plugin.Data; using Nethermind.Merge.Plugin; using Nethermind.JsonRpc; +using System.Reflection; namespace Ethereum.Test.Base; @@ -311,40 +312,32 @@ private static BlockHeader SuggestBlocks(BlockchainTest test, bool failOnInvalid private async static Task RunNewPayloads(TestEngineNewPayloadsJson[]? newPayloads, IEngineRpcModule engineRpcModule) { - (ExecutionPayload, string[]?, string[]?, string?)[] payloads = [.. JsonToEthereumTest.Convert(newPayloads)]; + (ExecutionPayloadV3, string[]?, string[]?, int, int)[] payloads = [.. JsonToEthereumTest.Convert(newPayloads)]; // blockchain test engine - foreach ((ExecutionPayload executionPayload, string[]? blobVersionedHashes, string[]? validationError, string? newPayloadVersion) in payloads) + foreach ((ExecutionPayload executionPayload, string[]? blobVersionedHashes, string[]? validationError, int newPayloadVersion, int fcuVersion) in payloads) { ResultWrapper res; - byte[]?[] hashes = blobVersionedHashes is null ? null : [.. blobVersionedHashes.Select(x => Bytes.FromHexString(x))]; - - switch (newPayloadVersion ?? "4") - { - case "1": - res = await engineRpcModule.engine_newPayloadV1(executionPayload); - break; - case "2": - res = await engineRpcModule.engine_newPayloadV2(executionPayload); - break; - case "3": - res = await engineRpcModule.engine_newPayloadV3((ExecutionPayloadV3)executionPayload, [], executionPayload.ParentBeaconBlockRoot); - break; - case "4": - res = await engineRpcModule.engine_newPayloadV4((ExecutionPayloadV3)executionPayload, hashes, executionPayload.ParentBeaconBlockRoot, []); - break; - // case "5": - // res = await engineRpcModule.engine_newPayloadV5((ExecutionPayloadV3)executionPayload, hashes, executionPayload.ParentBeaconBlockRoot, []); - // break; - default: - Assert.Fail("Invalid blockchain engine test, version not recognised."); - continue; + byte[]?[] hashes = blobVersionedHashes is null ? [] : [.. blobVersionedHashes.Select(x => Bytes.FromHexString(x))]; + + MethodInfo newPayloadMethod = engineRpcModule.GetType().GetMethod($"engine_newPayloadV{newPayloadVersion}"); + List newPayloadParams = [executionPayload]; + if (newPayloadVersion >= 3) + { + newPayloadParams.AddRange([hashes, executionPayload.ParentBeaconBlockRoot]); } + if (newPayloadVersion >= 4) + { + newPayloadParams.Add(executionPayload.ExecutionRequests); + } + + res = await (Task>)newPayloadMethod.Invoke(engineRpcModule, [.. newPayloadParams]); if (res.Result.ResultType == ResultType.Success) { ForkchoiceStateV1 fcuState = new(executionPayload.BlockHash, executionPayload.BlockHash, executionPayload.BlockHash); - await engineRpcModule.engine_forkchoiceUpdatedV3(fcuState); + MethodInfo fcuMethod = engineRpcModule.GetType().GetMethod($"engine_forkchoiceUpdatedV{fcuVersion}"); + await (Task>)fcuMethod.Invoke(engineRpcModule, [fcuState, null]); } } } diff --git a/src/Nethermind/Ethereum.Test.Base/JsonToEthereumTest.cs b/src/Nethermind/Ethereum.Test.Base/JsonToEthereumTest.cs index 5ab865e202f3..36b05b9fb0ea 100644 --- a/src/Nethermind/Ethereum.Test.Base/JsonToEthereumTest.cs +++ b/src/Nethermind/Ethereum.Test.Base/JsonToEthereumTest.cs @@ -85,7 +85,7 @@ public static BlockHeader Convert(TestBlockHeaderJson? headerJson) return header; } - public static IEnumerable<(ExecutionPayload, string[]?, string[]?, string?)> Convert(TestEngineNewPayloadsJson[]? executionPayloadsJson) + public static IEnumerable<(ExecutionPayloadV3, string[]?, string[]?, int, int)> Convert(TestEngineNewPayloadsJson[]? executionPayloadsJson) { if (executionPayloadsJson is null) { @@ -120,7 +120,7 @@ public static BlockHeader Convert(TestBlockHeaderJson? headerJson) Withdrawals = executionPayload.Withdrawals is null ? null : [.. executionPayload.Withdrawals.Select(x => Rlp.Decode(Bytes.FromHexString(x)))], Transactions = [.. executionPayload.Transactions.Select(x => Bytes.FromHexString(x))], ExecutionRequests = [] - }, blobVersionedHashes, validationError, engineNewPayload.NewPayloadVersion); + }, blobVersionedHashes, validationError, int.Parse(engineNewPayload.NewPayloadVersion ?? "4"), int.Parse(engineNewPayload.ForkChoiceUpdatedVersion ?? "3")); } }