Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
167 changes: 120 additions & 47 deletions src/Nethermind/Ethereum.Test.Base/BlockchainTestBase.cs
Original file line number Diff line number Diff line change
@@ -1,13 +1,6 @@
// SPDX-FileCopyrightText: 2025 Demerzel Solutions Limited
// SPDX-License-Identifier: LGPL-3.0-only

using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Numerics;
using System.Threading;
using System.Threading.Tasks;
using Autofac;
using Nethermind.Blockchain;
using Nethermind.Blockchain.Find;
Expand All @@ -23,19 +16,26 @@
using Nethermind.Core.Specs;
using Nethermind.Core.Test.Modules;
using Nethermind.Crypto;
using Nethermind.Evm.State;
using Nethermind.Init.Modules;
using Nethermind.Int256;
using Nethermind.JsonRpc;
using Nethermind.Logging;
using Nethermind.Merge.Plugin;
using Nethermind.Merge.Plugin.Data;
using Nethermind.Serialization.Rlp;
using Nethermind.Specs;
using Nethermind.Specs.Forks;
using Nethermind.Specs.Test;
using Nethermind.Evm.State;
using Nethermind.Init.Modules;
using NUnit.Framework;
using Nethermind.Merge.Plugin.Data;
using Nethermind.Merge.Plugin;
using Nethermind.JsonRpc;
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Numerics;
using System.Reflection;
using System.Threading;
using System.Threading.Tasks;

namespace Ethereum.Test.Base;

Expand All @@ -50,7 +50,7 @@ static BlockchainTestBase()
{
DifficultyCalculator = new DifficultyCalculatorWrapper();
_logManager ??= LimboLogs.Instance;
_logger = _logManager.GetClassLogger();
_logger = TestLogManager.Instance.GetClassLogger();
}

[SetUp]
Expand Down Expand Up @@ -133,7 +133,12 @@ protected async Task<EthereumTestResult> RunTest(BlockchainTest test, Stopwatch?
.AddSingleton(specProvider)
.AddSingleton(_logManager)
.AddSingleton(rewardCalculator)
.AddSingleton<IDifficultyCalculator>(DifficultyCalculator);
.AddSingleton<IDifficultyCalculator>(DifficultyCalculator)
.AddSingleton<IRewardCalculatorSource, RewardCalculator>()
.AddSingleton<IEthash, Ethash>()
.AddSingleton<IPoSSwitcher, PoSSwitcher>()
.AddSingleton<ISealValidator, EthashSealValidator>()
.AddDecorator<ISealValidator, MergeSealValidator>();

if (isEngineTest)
{
Expand All @@ -148,6 +153,7 @@ protected async Task<EthereumTestResult> RunTest(BlockchainTest test, Stopwatch?
IBlockTree blockTree = container.Resolve<IBlockTree>();
IBlockValidator blockValidator = container.Resolve<IBlockValidator>();
blockchainProcessor.Start();
IPoSSwitcher poSSwitcher = container.Resolve<IPoSSwitcher>();

// Register tracer if provided for blocktest tracing
if (tracer is not null)
Expand All @@ -157,7 +163,7 @@ protected async Task<EthereumTestResult> RunTest(BlockchainTest test, Stopwatch?

try
{
BlockHeader parentHeader;
Block parentBlock;
// Genesis processing
using (stateProvider.BeginScope(null))
{
Expand Down Expand Up @@ -192,7 +198,7 @@ protected async Task<EthereumTestResult> RunTest(BlockchainTest test, Stopwatch?

blockTree.SuggestBlock(genesisBlock);
genesisProcessed.WaitOne(_genesisProcessingTimeoutMs);
parentHeader = genesisBlock.Header;
parentBlock = genesisBlock;

// Dispose genesis block's AccountChanges
genesisBlock.DisposeAccountChanges();
Expand All @@ -201,7 +207,7 @@ protected async Task<EthereumTestResult> RunTest(BlockchainTest test, Stopwatch?
if (test.Blocks is not null)
{
// blockchain test
parentHeader = SuggestBlocks(test, failOnInvalidRlp, blockValidator, blockTree, parentHeader);
await SuggestBlocks(poSSwitcher, test, failOnInvalidRlp, blockValidator, blockTree, blockchainProcessor, parentBlock, isPostMerge);
}
else if (test.EngineNewPayloads is not null)
{
Expand Down Expand Up @@ -258,56 +264,123 @@ protected async Task<EthereumTestResult> RunTest(BlockchainTest test, Stopwatch?
}
}

private static BlockHeader SuggestBlocks(BlockchainTest test, bool failOnInvalidRlp, IBlockValidator blockValidator, IBlockTree blockTree, BlockHeader parentHeader)
private static async Task<BlockHeader> SuggestBlocks(IPoSSwitcher _poSSwitcher, BlockchainTest test, bool failOnInvalidRlp, IBlockValidator blockValidator, IBlockTree blockTree, BlockchainProcessor blockchainProcessor, Block parentBlock, bool isPostMerge)
{
List<(Block Block, string ExpectedException)> correctRlp = DecodeRlps(test, failOnInvalidRlp);
string? error = null;
string? expectsException = null;
Block head = parentBlock;

for (int i = 0; i < correctRlp.Count; i++)
{
error = null;
// Mimic the actual behaviour where block goes through validating sync manager
correctRlp[i].Block.Header.IsPostMerge = correctRlp[i].Block.Difficulty == 0;
correctRlp[i].Block.Header.IsPostMerge = _poSSwitcher.IsPostMerge(correctRlp[i].Block.Header);
Assert.That(correctRlp[i].Block.Hash, Is.Not.Null, $"null hash in {test.Name} block {i}");
expectsException = correctRlp[i].ExpectedException;
try
{
// For tests with reorgs, find the actual parent header from block tree
if (parentBlock.Hash != correctRlp[i].Block.ParentHash)
{
var oldParentBlock = parentBlock;
parentBlock = blockTree.FindBlock(correctRlp[i].Block.ParentHash);
// repeats new payload handler assertion
if (parentBlock is null)
{
error = $"Parent block {correctRlp[i].Block.ParentHash} not found for block {correctRlp[i].Block.Hash}";
parentBlock = oldParentBlock;
continue;
}
// if (!isPostMerge)

// For tests with reorgs, find the actual parent header from block tree
parentHeader = blockTree.FindHeader(correctRlp[i].Block.ParentHash) ?? parentHeader;
blockTree.UpdateMainChain([parentBlock], true, true);
}

Assert.That(correctRlp[i].Block.Hash, Is.Not.Null, $"null hash in {test.Name} block {i}");
// Validate block structure first (mimics SyncServer validation)
bool validationResult = blockValidator.ValidateSuggestedBlock(correctRlp[i].Block, parentBlock.Header, out string? validationError);

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
if (!validationResult)
{
error = validationError;
continue;
}

//if (blockTree.Head.Number >= correctRlp[i].Block.Number)
//{
// continue;
//}

bool suggested = false;
TaskCompletionSource<BlockRemovedEventArgs> completion = new TaskCompletionSource<BlockRemovedEventArgs>(TaskCreationOptions.RunContinuationsAsynchronously);

void f(object? s, BlockEventArgs e)
{
// All validations passed, suggest the block
blockTree.SuggestBlock(correctRlp[i].Block);
suggested = true;
}

void f2(object? s, BlockRemovedEventArgs e)
{
completion.SetResult(e);
}
catch (InvalidBlockException e)

blockchainProcessor.BlockRemoved += f2;
blockTree.NewBestSuggestedBlock += f;
blockTree.SuggestBlock(correctRlp[i].Block, BlockTreeSuggestOptions.ShouldProcess | BlockTreeSuggestOptions.FillBeaconBlock);
blockTree.NewBestSuggestedBlock -= f;

if (suggested)
{
// 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
await completion.Task;
if (completion.Task.Result.ProcessingResult is not ProcessingResult.Success)
{
error = $"Error processing block {correctRlp[i].Block.Hash}: {completion.Task.Result.Message ?? completion.Task.Result.Exception?.ToString()}";
break;
}
}
catch (Exception e)

blockchainProcessor.BlockRemoved -= f2;

parentBlock = correctRlp[i].Block;

if (!isPostMerge)
{
Assert.Fail($"Unexpected exception during processing: {e}");
if (head.Number < parentBlock.Number || head.TotalDifficulty < parentBlock.TotalDifficulty || (head.TotalDifficulty is not null && head.TotalDifficulty == parentBlock.TotalDifficulty && parentBlock.Timestamp < head.Timestamp))
{
head = parentBlock;
}
}
finally
else
{
// Dispose AccountChanges to prevent memory leaks in tests
correctRlp[i].Block.DisposeAccountChanges();
head = parentBlock;
}
}
else
catch (Exception e)
{
// Validation FAILED
Assert.That(expectsException, $"Unexpected invalid block {correctRlp[i].Block.Hash}: {validationError}");
// else: Expected to fail and did fail → this is correct behavior
Assert.Fail($"Unexpected exception during processing: {e} {e.StackTrace}");
}
finally
{
// Dispose AccountChanges to prevent memory leaks in tests
correctRlp[i].Block.DisposeAccountChanges();

parentHeader = correctRlp[i].Block.Header;
if (error is null)
{
Assert.That(expectsException, Is.Null, $"Unexpected valid block, expected failure: {expectsException}");
}
else
{
Assert.That(expectsException, Is.Not.Null, $"Unexpected invalid block: {error}");
}
}
}
return parentHeader;
blockTree.UpdateMainChain([head], true, true);
return head.Header;
}

private static void BlockTree_NewBestSuggestedBlock(object? sender, BlockEventArgs e)
{
throw new NotImplementedException();
}

private async static Task RunNewPayloads(TestEngineNewPayloadsJson[]? newPayloads, IEngineRpcModule engineRpcModule)
Expand Down Expand Up @@ -361,9 +434,9 @@ private async static Task RunNewPayloads(TestEngineNewPayloadsJson[]? newPayload
{
Assert.That(suggestedBlock.Uncles[uncleIndex].Hash, Is.EqualTo(new Hash256(testBlockJson.UncleHeaders![uncleIndex].Hash)));
}

correctRlp.Add((suggestedBlock, testBlockJson.ExpectedException));
}

correctRlp.Add((suggestedBlock, testBlockJson.ExpectedException));
}
catch (Exception e)
{
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
// SPDX-FileCopyrightText: 2022 Demerzel Solutions Limited
// SPDX-FileCopyrightText: 2025 Demerzel Solutions Limited
// SPDX-License-Identifier: LGPL-3.0-only

using System;
Expand All @@ -22,6 +22,7 @@ public BlockHashEventArgs(Hash256 blockHash, ProcessingResult processingResult,

public enum ProcessingResult
{
None,
/// <summary>
/// Processing was successful
/// </summary>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@

using System.Threading;
using System.Threading.Tasks;
using Nethermind.Core.Crypto;
using Nethermind.Core.Events;

namespace Nethermind.Consensus.Processing;
Expand All @@ -18,4 +19,14 @@ await Wait.ForEvent(cancellationToken,
e => blockProcessingQueue.ProcessingQueueEmpty -= e);
}
}

public static async Task<ProcessingResult> WaitForBlockProcessing(this IBlockProcessingQueue blockProcessingQueue, Hash256 blockHash, CancellationToken cancellationToken = default)
{
var res = await Wait.ForEventCondition<BlockRemovedEventArgs>(cancellationToken,
e => blockProcessingQueue.BlockRemoved += e,
e => blockProcessingQueue.BlockRemoved -= e,
e => e.BlockHash == blockHash).ConfigureAwait(false);

return res.ProcessingResult;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -11,17 +11,13 @@
using Nethermind.Consensus.Rewards;
using Nethermind.Consensus.Validators;
using Nethermind.Consensus.Withdrawals;
using Nethermind.Core;
using Nethermind.Core.Crypto;
using Nethermind.Core.Specs;
using Nethermind.Db;
using Nethermind.Evm;
using Nethermind.Evm.State;
using Nethermind.Evm.TransactionProcessing;
using Nethermind.Logging;
using Nethermind.State;
using Nethermind.Trie;
using Nethermind.Trie.Pruning;

namespace Nethermind.Consensus.Stateless;

Expand Down
Loading
Loading