diff --git a/src/Nethermind/Ethereum.Blockchain.Pyspec.Test/BerlinBlockChainTests.cs b/src/Nethermind/Ethereum.Blockchain.Pyspec.Test/BerlinBlockChainTests.cs index 264bc859ca77..5e202049eaf8 100644 --- a/src/Nethermind/Ethereum.Blockchain.Pyspec.Test/BerlinBlockChainTests.cs +++ b/src/Nethermind/Ethereum.Blockchain.Pyspec.Test/BerlinBlockChainTests.cs @@ -1,5 +1,7 @@ +// SPDX-FileCopyrightText: 2025 Demerzel Solutions Limited +// SPDX-License-Identifier: LGPL-3.0-only + using System.Collections.Generic; -using System.Linq; using System.Threading.Tasks; using Ethereum.Test.Base; using NUnit.Framework; diff --git a/src/Nethermind/Ethereum.Blockchain.Pyspec.Test/BerlinStateTests.cs b/src/Nethermind/Ethereum.Blockchain.Pyspec.Test/BerlinStateTests.cs index cdc20c50d11a..cc32ed244b0f 100644 --- a/src/Nethermind/Ethereum.Blockchain.Pyspec.Test/BerlinStateTests.cs +++ b/src/Nethermind/Ethereum.Blockchain.Pyspec.Test/BerlinStateTests.cs @@ -1,5 +1,7 @@ +// SPDX-FileCopyrightText: 2025 Demerzel Solutions Limited +// SPDX-License-Identifier: LGPL-3.0-only + using System.Collections.Generic; -using System.Linq; using Ethereum.Test.Base; using FluentAssertions; using NUnit.Framework; diff --git a/src/Nethermind/Ethereum.Blockchain.Pyspec.Test/ByzantiumBlockChainTests.cs b/src/Nethermind/Ethereum.Blockchain.Pyspec.Test/ByzantiumBlockChainTests.cs index b6be913b4c5d..3583300d49c2 100644 --- a/src/Nethermind/Ethereum.Blockchain.Pyspec.Test/ByzantiumBlockChainTests.cs +++ b/src/Nethermind/Ethereum.Blockchain.Pyspec.Test/ByzantiumBlockChainTests.cs @@ -1,5 +1,7 @@ +// SPDX-FileCopyrightText: 2025 Demerzel Solutions Limited +// SPDX-License-Identifier: LGPL-3.0-only + using System.Collections.Generic; -using System.Linq; using System.Threading.Tasks; using Ethereum.Test.Base; using NUnit.Framework; diff --git a/src/Nethermind/Ethereum.Blockchain.Pyspec.Test/ByzantiumStateTests.cs b/src/Nethermind/Ethereum.Blockchain.Pyspec.Test/ByzantiumStateTests.cs index a650f3c79666..7579dc2ed9fc 100644 --- a/src/Nethermind/Ethereum.Blockchain.Pyspec.Test/ByzantiumStateTests.cs +++ b/src/Nethermind/Ethereum.Blockchain.Pyspec.Test/ByzantiumStateTests.cs @@ -1,5 +1,7 @@ +// SPDX-FileCopyrightText: 2025 Demerzel Solutions Limited +// SPDX-License-Identifier: LGPL-3.0-only + using System.Collections.Generic; -using System.Linq; using Ethereum.Test.Base; using FluentAssertions; using NUnit.Framework; diff --git a/src/Nethermind/Ethereum.Blockchain.Pyspec.Test/CancunBlockChainTests.cs b/src/Nethermind/Ethereum.Blockchain.Pyspec.Test/CancunBlockChainTests.cs index 62695ccc71fd..da29277f3419 100644 --- a/src/Nethermind/Ethereum.Blockchain.Pyspec.Test/CancunBlockChainTests.cs +++ b/src/Nethermind/Ethereum.Blockchain.Pyspec.Test/CancunBlockChainTests.cs @@ -2,7 +2,6 @@ // SPDX-License-Identifier: LGPL-3.0-only using System.Collections.Generic; -using System.Linq; using System.Threading.Tasks; using Ethereum.Test.Base; using FluentAssertions; diff --git a/src/Nethermind/Ethereum.Blockchain.Pyspec.Test/CancunStateTests.cs b/src/Nethermind/Ethereum.Blockchain.Pyspec.Test/CancunStateTests.cs index f1f952b6adde..9a7d2402270d 100644 --- a/src/Nethermind/Ethereum.Blockchain.Pyspec.Test/CancunStateTests.cs +++ b/src/Nethermind/Ethereum.Blockchain.Pyspec.Test/CancunStateTests.cs @@ -2,7 +2,6 @@ // SPDX-License-Identifier: LGPL-3.0-only using System.Collections.Generic; -using System.Linq; using Ethereum.Test.Base; using FluentAssertions; using NUnit.Framework; diff --git a/src/Nethermind/Ethereum.Blockchain.Pyspec.Test/FrontierBlockChainTests.cs b/src/Nethermind/Ethereum.Blockchain.Pyspec.Test/FrontierBlockChainTests.cs index fae15df4b213..0b3c9a4a6417 100644 --- a/src/Nethermind/Ethereum.Blockchain.Pyspec.Test/FrontierBlockChainTests.cs +++ b/src/Nethermind/Ethereum.Blockchain.Pyspec.Test/FrontierBlockChainTests.cs @@ -1,5 +1,7 @@ +// SPDX-FileCopyrightText: 2025 Demerzel Solutions Limited +// SPDX-License-Identifier: LGPL-3.0-only + using System.Collections.Generic; -using System.Linq; using System.Threading.Tasks; using Ethereum.Test.Base; using NUnit.Framework; diff --git a/src/Nethermind/Ethereum.Blockchain.Pyspec.Test/FrontierStateTests.cs b/src/Nethermind/Ethereum.Blockchain.Pyspec.Test/FrontierStateTests.cs index 6011d779adf7..fbf265a22a8d 100644 --- a/src/Nethermind/Ethereum.Blockchain.Pyspec.Test/FrontierStateTests.cs +++ b/src/Nethermind/Ethereum.Blockchain.Pyspec.Test/FrontierStateTests.cs @@ -1,5 +1,7 @@ +// SPDX-FileCopyrightText: 2025 Demerzel Solutions Limited +// SPDX-License-Identifier: LGPL-3.0-only + using System.Collections.Generic; -using System.Linq; using Ethereum.Test.Base; using FluentAssertions; using NUnit.Framework; diff --git a/src/Nethermind/Ethereum.Blockchain.Pyspec.Test/HomesteadBlockChainTests.cs b/src/Nethermind/Ethereum.Blockchain.Pyspec.Test/HomesteadBlockChainTests.cs index eba45011cbb3..fc6bc1c07b95 100644 --- a/src/Nethermind/Ethereum.Blockchain.Pyspec.Test/HomesteadBlockChainTests.cs +++ b/src/Nethermind/Ethereum.Blockchain.Pyspec.Test/HomesteadBlockChainTests.cs @@ -1,5 +1,7 @@ +// SPDX-FileCopyrightText: 2025 Demerzel Solutions Limited +// SPDX-License-Identifier: LGPL-3.0-only + using System.Collections.Generic; -using System.Linq; using System.Threading.Tasks; using Ethereum.Test.Base; using NUnit.Framework; diff --git a/src/Nethermind/Ethereum.Blockchain.Pyspec.Test/HomesteadStateTests.cs b/src/Nethermind/Ethereum.Blockchain.Pyspec.Test/HomesteadStateTests.cs index 44ec8d4041f5..0fec88659d02 100644 --- a/src/Nethermind/Ethereum.Blockchain.Pyspec.Test/HomesteadStateTests.cs +++ b/src/Nethermind/Ethereum.Blockchain.Pyspec.Test/HomesteadStateTests.cs @@ -1,5 +1,7 @@ +// SPDX-FileCopyrightText: 2025 Demerzel Solutions Limited +// SPDX-License-Identifier: LGPL-3.0-only + using System.Collections.Generic; -using System.Linq; using Ethereum.Test.Base; using FluentAssertions; using NUnit.Framework; diff --git a/src/Nethermind/Ethereum.Blockchain.Pyspec.Test/IstanbulBlockChainTests.cs b/src/Nethermind/Ethereum.Blockchain.Pyspec.Test/IstanbulBlockChainTests.cs index af62090dd9e9..4ad81bb85f3a 100644 --- a/src/Nethermind/Ethereum.Blockchain.Pyspec.Test/IstanbulBlockChainTests.cs +++ b/src/Nethermind/Ethereum.Blockchain.Pyspec.Test/IstanbulBlockChainTests.cs @@ -1,5 +1,7 @@ +// SPDX-FileCopyrightText: 2025 Demerzel Solutions Limited +// SPDX-License-Identifier: LGPL-3.0-only + using System.Collections.Generic; -using System.Linq; using System.Threading.Tasks; using Ethereum.Test.Base; using NUnit.Framework; diff --git a/src/Nethermind/Ethereum.Blockchain.Pyspec.Test/IstanbulStateTests.cs b/src/Nethermind/Ethereum.Blockchain.Pyspec.Test/IstanbulStateTests.cs index a4daf560f351..fe0f84a61d36 100644 --- a/src/Nethermind/Ethereum.Blockchain.Pyspec.Test/IstanbulStateTests.cs +++ b/src/Nethermind/Ethereum.Blockchain.Pyspec.Test/IstanbulStateTests.cs @@ -1,5 +1,7 @@ +// SPDX-FileCopyrightText: 2025 Demerzel Solutions Limited +// SPDX-License-Identifier: LGPL-3.0-only + using System.Collections.Generic; -using System.Linq; using Ethereum.Test.Base; using FluentAssertions; using NUnit.Framework; diff --git a/src/Nethermind/Ethereum.Blockchain.Pyspec.Test/OsakaBlockChainTests.cs b/src/Nethermind/Ethereum.Blockchain.Pyspec.Test/OsakaBlockChainTests.cs index 6e8cafbada58..99231fdd948d 100644 --- a/src/Nethermind/Ethereum.Blockchain.Pyspec.Test/OsakaBlockChainTests.cs +++ b/src/Nethermind/Ethereum.Blockchain.Pyspec.Test/OsakaBlockChainTests.cs @@ -2,7 +2,6 @@ // SPDX-License-Identifier: LGPL-3.0-only using System.Collections.Generic; -using System.Linq; using System.Threading.Tasks; using Ethereum.Test.Base; using NUnit.Framework; diff --git a/src/Nethermind/Ethereum.Blockchain.Pyspec.Test/OsakaEofTests.cs b/src/Nethermind/Ethereum.Blockchain.Pyspec.Test/OsakaEofTests.cs index 28326dea3909..874f6a40f6bb 100644 --- a/src/Nethermind/Ethereum.Blockchain.Pyspec.Test/OsakaEofTests.cs +++ b/src/Nethermind/Ethereum.Blockchain.Pyspec.Test/OsakaEofTests.cs @@ -2,7 +2,6 @@ // SPDX-License-Identifier: LGPL-3.0-only using System.Collections.Generic; -using System.Linq; using Ethereum.Test.Base; using NUnit.Framework; diff --git a/src/Nethermind/Ethereum.Blockchain.Pyspec.Test/OsakaStateTests.cs b/src/Nethermind/Ethereum.Blockchain.Pyspec.Test/OsakaStateTests.cs index 097c9c8f658c..d9c6248b9404 100644 --- a/src/Nethermind/Ethereum.Blockchain.Pyspec.Test/OsakaStateTests.cs +++ b/src/Nethermind/Ethereum.Blockchain.Pyspec.Test/OsakaStateTests.cs @@ -2,7 +2,6 @@ // SPDX-License-Identifier: LGPL-3.0-only using System.Collections.Generic; -using System.Linq; using Ethereum.Test.Base; using FluentAssertions; using NUnit.Framework; diff --git a/src/Nethermind/Ethereum.Blockchain.Pyspec.Test/ShanghaiBlockChainTests.cs b/src/Nethermind/Ethereum.Blockchain.Pyspec.Test/ShanghaiBlockChainTests.cs index 2d273d715659..2e85ba8918d9 100644 --- a/src/Nethermind/Ethereum.Blockchain.Pyspec.Test/ShanghaiBlockChainTests.cs +++ b/src/Nethermind/Ethereum.Blockchain.Pyspec.Test/ShanghaiBlockChainTests.cs @@ -2,7 +2,6 @@ // SPDX-License-Identifier: LGPL-3.0-only using System.Collections.Generic; -using System.Linq; using System.Threading.Tasks; using Ethereum.Test.Base; using NUnit.Framework; diff --git a/src/Nethermind/Ethereum.Blockchain.Pyspec.Test/ShanghaiStateTests.cs b/src/Nethermind/Ethereum.Blockchain.Pyspec.Test/ShanghaiStateTests.cs index 5c75a9b80c63..b8e304a15167 100644 --- a/src/Nethermind/Ethereum.Blockchain.Pyspec.Test/ShanghaiStateTests.cs +++ b/src/Nethermind/Ethereum.Blockchain.Pyspec.Test/ShanghaiStateTests.cs @@ -1,5 +1,7 @@ +// SPDX-FileCopyrightText: 2025 Demerzel Solutions Limited +// SPDX-License-Identifier: LGPL-3.0-only + using System.Collections.Generic; -using System.Linq; using Ethereum.Test.Base; using FluentAssertions; using NUnit.Framework; diff --git a/src/Nethermind/Ethereum.Test.Base/BlockchainTest.cs b/src/Nethermind/Ethereum.Test.Base/BlockchainTest.cs index 6329281f660f..6916dd59eb53 100644 --- a/src/Nethermind/Ethereum.Test.Base/BlockchainTest.cs +++ b/src/Nethermind/Ethereum.Test.Base/BlockchainTest.cs @@ -20,6 +20,7 @@ public class BlockchainTest : EthereumTest public TestBlockJson[]? Blocks { get; set; } public TestBlockHeaderJson? GenesisBlockHeader { get; set; } + public TestEngineNewPayloadsJson[]? EngineNewPayloads { get; set; } public Dictionary? Pre { get; set; } public Dictionary? PostState { get; set; } diff --git a/src/Nethermind/Ethereum.Test.Base/BlockchainTestBase.cs b/src/Nethermind/Ethereum.Test.Base/BlockchainTestBase.cs index 296f3da23930..374f738ab79d 100644 --- a/src/Nethermind/Ethereum.Test.Base/BlockchainTestBase.cs +++ b/src/Nethermind/Ethereum.Test.Base/BlockchainTestBase.cs @@ -31,8 +31,11 @@ using Nethermind.Specs.Test; using Nethermind.Evm.State; using Nethermind.Init.Modules; -using Nethermind.TxPool; using NUnit.Framework; +using Nethermind.Merge.Plugin.Data; +using Nethermind.Merge.Plugin; +using Nethermind.JsonRpc; +using System.Reflection; namespace Ethereum.Test.Base; @@ -40,14 +43,12 @@ public abstract class BlockchainTestBase { private static readonly ILogger _logger; private static readonly ILogManager _logManager = new TestLogManager(LogLevel.Warn); - private static ISealValidator Sealer { get; } private static DifficultyCalculatorWrapper DifficultyCalculator { get; } + private const int _genesisProcessingTimeoutMs = 5000; static BlockchainTestBase() { DifficultyCalculator = new DifficultyCalculatorWrapper(); - Sealer = new EthashSealValidator(_logManager, DifficultyCalculator, new CryptoRandom(), new Ethash(_logManager), Timestamper.Default); // temporarily keep reusing the same one as otherwise it would recreate cache for each test - _logManager ??= LimboLogs.Instance; _logger = _logManager.GetClassLogger(); } @@ -83,8 +84,13 @@ protected async Task RunTest(BlockchainTest test, Stopwatch? test.Network = ChainUtils.ResolveSpec(test.Network, test.ChainId); test.NetworkAfterTransition = ChainUtils.ResolveSpec(test.NetworkAfterTransition, test.ChainId); + bool isEngineTest = test.Blocks is null && test.EngineNewPayloads is not null; + List<(ForkActivation Activation, IReleaseSpec Spec)> transitions = + isEngineTest ? + [((ForkActivation)0, test.Network)] : [((ForkActivation)0, test.GenesisSpec), ((ForkActivation)1, test.Network)]; // TODO: this thing took a lot of time to find after it was removed!, genesis block is always initialized with Frontier + if (test.NetworkAfterTransition is not null) { transitions.Add((test.TransitionForkActivation!.Value, test.NetworkAfterTransition)); @@ -92,7 +98,7 @@ protected async Task RunTest(BlockchainTest test, Stopwatch? ISpecProvider specProvider = new CustomSpecProvider(test.ChainId, test.ChainId, transitions.ToArray()); - Assert.That(test.ChainId == GnosisSpecProvider.Instance.ChainId || specProvider.GenesisSpec == Frontier.Instance, "Expected genesis spec to be Frontier for blockchain tests"); + Assert.That(isEngineTest || test.ChainId == GnosisSpecProvider.Instance.ChainId || specProvider.GenesisSpec == Frontier.Instance, "Expected genesis spec to be Frontier for blockchain tests"); if (test.Network is Cancun || test.NetworkAfterTransition is Cancun) { @@ -122,18 +128,23 @@ protected async Task RunTest(BlockchainTest test, Stopwatch? IConfigProvider configProvider = new ConfigProvider(); // configProvider.GetConfig().PreWarmStateOnBlockProcessing = false; - await using IContainer container = new ContainerBuilder() + ContainerBuilder containerBuilder = new ContainerBuilder() .AddModule(new TestNethermindModule(configProvider)) .AddSingleton(specProvider) .AddSingleton(_logManager) .AddSingleton(rewardCalculator) - .AddSingleton(DifficultyCalculator) - .AddSingleton(NullTxPool.Instance) - .Build(); + .AddSingleton(DifficultyCalculator); + + if (isEngineTest) + { + containerBuilder.AddModule(new TestMergeModule(configProvider)); + } + + await using IContainer container = containerBuilder.Build(); IMainProcessingContext mainBlockProcessingContext = container.Resolve(); IWorldState stateProvider = mainBlockProcessingContext.WorldState; - IBlockchainProcessor blockchainProcessor = mainBlockProcessingContext.BlockchainProcessor; + BlockchainProcessor blockchainProcessor = (BlockchainProcessor)mainBlockProcessingContext.BlockchainProcessor; IBlockTree blockTree = container.Resolve(); IBlockValidator blockValidator = container.Resolve(); blockchainProcessor.Start(); @@ -165,85 +176,66 @@ protected async Task RunTest(BlockchainTest test, Stopwatch? { if (args.Block.Number == 0) { - Assert.That(stateProvider.HasStateForBlock(genesisBlock.Header), Is.EqualTo(true)); + Assert.That(stateProvider.HasStateForBlock(genesisBlock.Header), Is.True); + genesisProcessed.Set(); + } + }; + + blockchainProcessor.BlockRemoved += (_, args) => + { + if (args.ProcessingResult != ProcessingResult.Success && args.BlockHash == genesisBlock.Header.Hash) + { + Assert.Fail($"Failed to process genesis block: {args.Exception}"); genesisProcessed.Set(); } }; blockTree.SuggestBlock(genesisBlock); - genesisProcessed.WaitOne(); + genesisProcessed.WaitOne(_genesisProcessingTimeoutMs); parentHeader = genesisBlock.Header; // Dispose genesis block's AccountChanges genesisBlock.DisposeAccountChanges(); } - List<(Block Block, string ExpectedException)> correctRlp = DecodeRlps(test, failOnInvalidRlp); - for (int i = 0; i < correctRlp.Count; i++) + if (test.Blocks is not null) { - // Mimic the actual behaviour where block goes through validating sync manager - correctRlp[i].Block.Header.IsPostMerge = correctRlp[i].Block.Difficulty == 0; - - // For tests with reorgs, find the actual parent header from block tree - parentHeader = blockTree.FindHeader(correctRlp[i].Block.ParentHash) ?? parentHeader; - - Assert.That(correctRlp[i].Block.Hash is not null, $"null hash in {test.Name} block {i}"); - - bool expectsException = correctRlp[i].ExpectedException is not null; - // Validate block structure first (mimics SyncServer validation) - if (blockValidator.ValidateSuggestedBlock(correctRlp[i].Block, parentHeader, out string? validationError)) - { - Assert.That(!expectsException, $"Expected block {correctRlp[i].Block.Hash} to fail with '{correctRlp[i].ExpectedException}', but it passed validation"); - try - { - // All validations passed, suggest the block - blockTree.SuggestBlock(correctRlp[i].Block); - - } - catch (InvalidBlockException e) - { - // Exception thrown during block processing - Assert.That(expectsException, $"Unexpected invalid block {correctRlp[i].Block.Hash}: {validationError}, Exception: {e}"); - // else: Expected to fail and did fail via exception → this is correct behavior - } - catch (Exception e) - { - Assert.Fail($"Unexpected exception during processing: {e}"); - } - finally - { - // Dispose AccountChanges to prevent memory leaks in tests - correctRlp[i].Block.DisposeAccountChanges(); - } - } - else - { - // Validation FAILED - Assert.That(expectsException, $"Unexpected invalid block {correctRlp[i].Block.Hash}: {validationError}"); - // else: Expected to fail and did fail → this is correct behavior - } - - parentHeader = correctRlp[i].Block.Header; + // blockchain test + parentHeader = SuggestBlocks(test, failOnInvalidRlp, blockValidator, blockTree, parentHeader); + } + else if (test.EngineNewPayloads is not null) + { + // engine test + IEngineRpcModule engineRpcModule = container.Resolve(); + await RunNewPayloads(test.EngineNewPayloads, engineRpcModule); + } + else + { + Assert.Fail("Invalid blockchain test, did not contain blocks or new payloads."); } // NOTE: Tracer removal must happen AFTER StopAsync to ensure all blocks are traced // Blocks are queued asynchronously, so we need to wait for processing to complete await blockchainProcessor.StopAsync(true); - stopwatch?.Stop(); IBlockCachePreWarmer? preWarmer = container.Resolve().LifetimeScope.ResolveOptional(); - if (preWarmer is not null) + + // Caches are cleared async, which is a problem as read for the MainWorldState with prewarmer is not correct if its not cleared. + preWarmer?.ClearCaches(); + + Block? headBlock = blockTree.RetrieveHeadBlock(); + + Assert.That(headBlock, Is.Not.Null); + if (headBlock is null) { - // Caches are cleared async, which is a problem as read for the MainWorldState with prewarmer is not correct if its not cleared. - preWarmer.ClearCaches(); + return new EthereumTestResult(test.Name, null, false); } - Block? headBlock = blockTree.RetrieveHeadBlock(); List differences; using (stateProvider.BeginScope(headBlock.Header)) { - differences = RunAssertions(test, blockTree.RetrieveHeadBlock(), stateProvider); + differences = RunAssertions(test, headBlock, stateProvider); } bool testPassed = differences.Count == 0; @@ -256,14 +248,8 @@ protected async Task RunTest(BlockchainTest test, Stopwatch? blockchainProcessor.Tracers.Remove(tracer); } - Assert.That(differences.Count, Is.Zero, "differences"); - - return new EthereumTestResult - ( - test.Name, - null, - testPassed - ); + Assert.That(differences, Is.Empty, "differences"); + return new EthereumTestResult(test.Name, null, testPassed); } catch (Exception) { @@ -272,9 +258,93 @@ protected async Task RunTest(BlockchainTest test, Stopwatch? } } - private List<(Block Block, string ExpectedException)> DecodeRlps(BlockchainTest test, bool failOnInvalidRlp) + private static BlockHeader SuggestBlocks(BlockchainTest test, bool failOnInvalidRlp, IBlockValidator blockValidator, IBlockTree blockTree, BlockHeader parentHeader) + { + List<(Block Block, string ExpectedException)> correctRlp = DecodeRlps(test, failOnInvalidRlp); + for (int i = 0; i < correctRlp.Count; i++) + { + // Mimic the actual behaviour where block goes through validating sync manager + correctRlp[i].Block.Header.IsPostMerge = correctRlp[i].Block.Difficulty == 0; + + // For tests with reorgs, find the actual parent header from block tree + parentHeader = blockTree.FindHeader(correctRlp[i].Block.ParentHash) ?? parentHeader; + + Assert.That(correctRlp[i].Block.Hash, Is.Not.Null, $"null hash in {test.Name} block {i}"); + + bool expectsException = correctRlp[i].ExpectedException is not null; + // Validate block structure first (mimics SyncServer validation) + if (blockValidator.ValidateSuggestedBlock(correctRlp[i].Block, parentHeader, out string? validationError)) + { + Assert.That(!expectsException, $"Expected block {correctRlp[i].Block.Hash} to fail with '{correctRlp[i].ExpectedException}', but it passed validation"); + try + { + // All validations passed, suggest the block + blockTree.SuggestBlock(correctRlp[i].Block); + + } + catch (InvalidBlockException e) + { + // Exception thrown during block processing + Assert.That(expectsException, $"Unexpected invalid block {correctRlp[i].Block.Hash}: {validationError}, Exception: {e}"); + // else: Expected to fail and did fail via exception → this is correct behavior + } + catch (Exception e) + { + Assert.Fail($"Unexpected exception during processing: {e}"); + } + finally + { + // Dispose AccountChanges to prevent memory leaks in tests + correctRlp[i].Block.DisposeAccountChanges(); + } + } + else + { + // Validation FAILED + Assert.That(expectsException, $"Unexpected invalid block {correctRlp[i].Block.Hash}: {validationError}"); + // else: Expected to fail and did fail → this is correct behavior + } + + parentHeader = correctRlp[i].Block.Header; + } + return parentHeader; + } + + private async static Task RunNewPayloads(TestEngineNewPayloadsJson[]? newPayloads, IEngineRpcModule engineRpcModule) + { + (ExecutionPayloadV3, string[]?, string[]?, int, int)[] payloads = [.. JsonToEthereumTest.Convert(newPayloads)]; + + // blockchain test engine + foreach ((ExecutionPayload executionPayload, string[]? blobVersionedHashes, string[]? validationError, int newPayloadVersion, int fcuVersion) in payloads) + { + ResultWrapper res; + byte[]?[] hashes = blobVersionedHashes is null ? [] : [.. blobVersionedHashes.Select(x => Bytes.FromHexString(x))]; + + MethodInfo newPayloadMethod = engineRpcModule.GetType().GetMethod($"engine_newPayloadV{newPayloadVersion}"); + List 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); + MethodInfo fcuMethod = engineRpcModule.GetType().GetMethod($"engine_forkchoiceUpdatedV{fcuVersion}"); + await (Task>)fcuMethod.Invoke(engineRpcModule, [fcuState, null]); + } + } + } + + 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]; @@ -282,6 +352,7 @@ protected async Task RunTest(BlockchainTest test, Stopwatch? { RlpStream rlpContext = Bytes.FromHexString(testBlockJson.Rlp!).AsRlpStream(); Block suggestedBlock = Rlp.Decode(rlpContext); + if (testBlockJson.BlockHeader is not null) { Assert.That(suggestedBlock.Header.Hash, Is.EqualTo(new Hash256(testBlockJson.BlockHeader.Hash))); @@ -313,8 +384,11 @@ 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; @@ -323,7 +397,7 @@ protected async Task RunTest(BlockchainTest test, Stopwatch? 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) { @@ -339,18 +413,14 @@ private static void InitializeTestState(BlockchainTest test, IWorldState statePr 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 ? ["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 = new(); + List differences = []; IEnumerable> deletedAccounts = test.Pre? .Where(pre => !(test.PostState?.ContainsKey(pre.Key) ?? false)) ?? Array.Empty>(); @@ -363,7 +433,7 @@ private List RunAssertions(BlockchainTest test, Block headBlock, IWorldS } } - foreach ((Address acountAddress, AccountState accountState) in test.PostState!) + foreach ((Address accountAddress, AccountState accountState) in test.PostState!) { int differencesBefore = differences.Count; @@ -373,87 +443,96 @@ private List RunAssertions(BlockchainTest test, Block headBlock, IWorldS 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.TryGetValue(acountAddress, out AccountState? state)) + if (test.Pre.ContainsKey(accountAddress)) { - clearedStorages = state.Storage.Where(s => !accountState.Storage.ContainsKey(s.Key)).ToArray(); + 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) 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/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/GeneralTestBase.cs b/src/Nethermind/Ethereum.Test.Base/GeneralTestBase.cs index b9aa74da334a..39dbc1517905 100644 --- a/src/Nethermind/Ethereum.Test.Base/GeneralTestBase.cs +++ b/src/Nethermind/Ethereum.Test.Base/GeneralTestBase.cs @@ -2,7 +2,6 @@ // SPDX-License-Identifier: LGPL-3.0-only using Autofac; -using Nethermind.Api; using Nethermind.Config; using Nethermind.Consensus.Processing; using Nethermind.Consensus.Validators; @@ -100,17 +99,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); @@ -161,9 +162,11 @@ 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) { diff --git a/src/Nethermind/Ethereum.Test.Base/JsonToEthereumTest.cs b/src/Nethermind/Ethereum.Test.Base/JsonToEthereumTest.cs index 9a09b17017c1..36b05b9fb0ea 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,89 @@ 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), + }; + + if (headerJson.BaseFeePerGas is not null) + { + header.BaseFeePerGas = (ulong)Bytes.FromHexString(headerJson.BaseFeePerGas).ToUnsignedBigInteger(); + } + return header; } + public static IEnumerable<(ExecutionPayloadV3, string[]?, string[]?, int, int)> 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, int.Parse(engineNewPayload.NewPayloadVersion ?? "4"), int.Parse(engineNewPayload.ForkChoiceUpdatedVersion ?? "3")); + } + } + 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 +163,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 +203,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 +227,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 +248,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 +299,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 +333,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 +342,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 +356,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 +368,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 +395,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); @@ -351,15 +414,16 @@ public static IEnumerable ConvertToBlockchainTests(string json) } catch (Exception) { - var half = _serializer.Deserialize>(json); - testsInFile = new Dictionary(); + 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/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; diff --git a/src/Nethermind/Ethereum.Test.Base/TestBlockHeaderJson.cs b/src/Nethermind/Ethereum.Test.Base/TestBlockHeaderJson.cs index 3508dba38c66..c154f53e5743 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? 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; } } } diff --git a/src/Nethermind/Ethereum.Test.Base/TestEngineNewPayloadsJson.cs b/src/Nethermind/Ethereum.Test.Base/TestEngineNewPayloadsJson.cs new file mode 100644 index 000000000000..b89dcd2869b2 --- /dev/null +++ b/src/Nethermind/Ethereum.Test.Base/TestEngineNewPayloadsJson.cs @@ -0,0 +1,36 @@ +// 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 string? NewPayloadVersion { get; set; } + public string? ForkChoiceUpdatedVersion { get; set; } + + 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.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.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..5a350188359c 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; 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 51da785e1a6b..a596548ec393 100644 --- a/src/Nethermind/Nethermind.Test.Runner/BlockchainTestsRunner.cs +++ b/src/Nethermind/Nethermind.Test.Runner/BlockchainTestsRunner.cs @@ -7,8 +7,6 @@ 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; @@ -21,21 +19,29 @@ public class BlockchainTestsRunner( bool traceNoStack = false) : BlockchainTestBase, IBlockchainTestRunner { - private readonly ConsoleColor _defaultColour = Console.ForegroundColor; + private readonly ConsoleColor _defaultColor = Console.ForegroundColor; private readonly ITestSourceLoader _testsSource = testsSource ?? throw new ArgumentNullException(nameof(testsSource)); public async Task> RunTestsAsync() { - List testResults = new(); - IEnumerable tests = _testsSource.LoadTests(); + List testResults = []; + 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; + } - // Create a streaming tracer once for all tests if tracing is enabled - using BlockchainTestStreamingTracer? tracer = trace - ? new BlockchainTestStreamingTracer(new() { EnableMemory = traceMemory, DisableStack = traceNoStack }) - : null; + // 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; - foreach (BlockchainTest test in tests) - { if (filter is not null && test.Name is not null && !Regex.Match(test.Name, $"^({filter})").Success) continue; Setup(); @@ -66,13 +72,13 @@ private void WriteRed(string text) { Console.ForegroundColor = ConsoleColor.Red; Console.WriteLine(text); - Console.ForegroundColor = _defaultColour; + Console.ForegroundColor = _defaultColor; } private void WriteGreen(string text) { Console.ForegroundColor = ConsoleColor.Green; Console.WriteLine(text); - Console.ForegroundColor = _defaultColour; + Console.ForegroundColor = _defaultColor; } }