11// SPDX-FileCopyrightText: 2025 Demerzel Solutions Limited
22// SPDX-License-Identifier: LGPL-3.0-only
33
4- using System ;
5- using System . Collections . Generic ;
6- using System . Diagnostics ;
7- using System . Linq ;
8- using System . Numerics ;
9- using System . Threading ;
10- using System . Threading . Tasks ;
114using Autofac ;
125using Nethermind . Blockchain ;
136using Nethermind . Blockchain . Find ;
2316using Nethermind . Core . Specs ;
2417using Nethermind . Core . Test . Modules ;
2518using Nethermind . Crypto ;
19+ using Nethermind . Evm . State ;
20+ using Nethermind . Init . Modules ;
2621using Nethermind . Int256 ;
22+ using Nethermind . JsonRpc ;
2723using Nethermind . Logging ;
24+ using Nethermind . Merge . Plugin ;
25+ using Nethermind . Merge . Plugin . Data ;
2826using Nethermind . Serialization . Rlp ;
2927using Nethermind . Specs ;
3028using Nethermind . Specs . Forks ;
3129using Nethermind . Specs . Test ;
32- using Nethermind . Evm . State ;
33- using Nethermind . Init . Modules ;
3430using NUnit . Framework ;
35- using Nethermind . Merge . Plugin . Data ;
36- using Nethermind . Merge . Plugin ;
37- using Nethermind . JsonRpc ;
31+ using System ;
32+ using System . Collections . Generic ;
33+ using System . Diagnostics ;
34+ using System . Linq ;
35+ using System . Numerics ;
3836using System . Reflection ;
37+ using System . Threading ;
38+ using System . Threading . Tasks ;
3939
4040namespace Ethereum . Test . Base ;
4141
@@ -50,7 +50,7 @@ static BlockchainTestBase()
5050 {
5151 DifficultyCalculator = new DifficultyCalculatorWrapper ( ) ;
5252 _logManager ??= LimboLogs . Instance ;
53- _logger = _logManager . GetClassLogger ( ) ;
53+ _logger = TestLogManager . Instance . GetClassLogger ( ) ;
5454 }
5555
5656 [ SetUp ]
@@ -133,7 +133,12 @@ protected async Task<EthereumTestResult> RunTest(BlockchainTest test, Stopwatch?
133133 . AddSingleton ( specProvider )
134134 . AddSingleton ( _logManager )
135135 . AddSingleton ( rewardCalculator )
136- . AddSingleton < IDifficultyCalculator > ( DifficultyCalculator ) ;
136+ . AddSingleton < IDifficultyCalculator > ( DifficultyCalculator )
137+ . AddSingleton < IRewardCalculatorSource , RewardCalculator > ( )
138+ . AddSingleton < IEthash , Ethash > ( )
139+ . AddSingleton < IPoSSwitcher , PoSSwitcher > ( )
140+ . AddSingleton < ISealValidator , EthashSealValidator > ( )
141+ . AddDecorator < ISealValidator , MergeSealValidator > ( ) ;
137142
138143 if ( isEngineTest )
139144 {
@@ -148,6 +153,7 @@ protected async Task<EthereumTestResult> RunTest(BlockchainTest test, Stopwatch?
148153 IBlockTree blockTree = container . Resolve < IBlockTree > ( ) ;
149154 IBlockValidator blockValidator = container . Resolve < IBlockValidator > ( ) ;
150155 blockchainProcessor . Start ( ) ;
156+ IPoSSwitcher poSSwitcher = container . Resolve < IPoSSwitcher > ( ) ;
151157
152158 // Register tracer if provided for blocktest tracing
153159 if ( tracer is not null )
@@ -157,7 +163,7 @@ protected async Task<EthereumTestResult> RunTest(BlockchainTest test, Stopwatch?
157163
158164 try
159165 {
160- BlockHeader parentHeader ;
166+ Block parentBlock ;
161167 // Genesis processing
162168 using ( stateProvider . BeginScope ( null ) )
163169 {
@@ -192,7 +198,7 @@ protected async Task<EthereumTestResult> RunTest(BlockchainTest test, Stopwatch?
192198
193199 blockTree . SuggestBlock ( genesisBlock ) ;
194200 genesisProcessed . WaitOne ( _genesisProcessingTimeoutMs ) ;
195- parentHeader = genesisBlock . Header ;
201+ parentBlock = genesisBlock ;
196202
197203 // Dispose genesis block's AccountChanges
198204 genesisBlock . DisposeAccountChanges ( ) ;
@@ -201,7 +207,7 @@ protected async Task<EthereumTestResult> RunTest(BlockchainTest test, Stopwatch?
201207 if ( test . Blocks is not null )
202208 {
203209 // blockchain test
204- parentHeader = SuggestBlocks ( test , failOnInvalidRlp , blockValidator , blockTree , parentHeader ) ;
210+ await SuggestBlocks ( poSSwitcher , test , failOnInvalidRlp , blockValidator , blockTree , blockchainProcessor , parentBlock , isPostMerge ) ;
205211 }
206212 else if ( test . EngineNewPayloads is not null )
207213 {
@@ -258,56 +264,123 @@ protected async Task<EthereumTestResult> RunTest(BlockchainTest test, Stopwatch?
258264 }
259265 }
260266
261- private static BlockHeader SuggestBlocks ( BlockchainTest test , bool failOnInvalidRlp , IBlockValidator blockValidator , IBlockTree blockTree , BlockHeader parentHeader )
267+ private static async Task < BlockHeader > SuggestBlocks ( IPoSSwitcher _poSSwitcher , BlockchainTest test , bool failOnInvalidRlp , IBlockValidator blockValidator , IBlockTree blockTree , BlockchainProcessor blockchainProcessor , Block parentBlock , bool isPostMerge )
262268 {
263269 List < ( Block Block , string ExpectedException ) > correctRlp = DecodeRlps ( test , failOnInvalidRlp ) ;
270+ string ? error = null ;
271+ string ? expectsException = null ;
272+ Block head = parentBlock ;
273+
264274 for ( int i = 0 ; i < correctRlp . Count ; i ++ )
265275 {
276+ error = null ;
266277 // Mimic the actual behaviour where block goes through validating sync manager
267- correctRlp [ i ] . Block . Header . IsPostMerge = correctRlp [ i ] . Block . Difficulty == 0 ;
278+ correctRlp [ i ] . Block . Header . IsPostMerge = _poSSwitcher . IsPostMerge ( correctRlp [ i ] . Block . Header ) ;
279+ Assert . That ( correctRlp [ i ] . Block . Hash , Is . Not . Null , $ "null hash in { test . Name } block { i } ") ;
280+ expectsException = correctRlp [ i ] . ExpectedException ;
281+ try
282+ {
283+ // For tests with reorgs, find the actual parent header from block tree
284+ if ( parentBlock . Hash != correctRlp [ i ] . Block . ParentHash )
285+ {
286+ var oldParentBlock = parentBlock ;
287+ parentBlock = blockTree . FindBlock ( correctRlp [ i ] . Block . ParentHash ) ;
288+ // repeats new payload handler assertion
289+ if ( parentBlock is null )
290+ {
291+ error = $ "Parent block { correctRlp [ i ] . Block . ParentHash } not found for block { correctRlp [ i ] . Block . Hash } ";
292+ parentBlock = oldParentBlock ;
293+ continue ;
294+ }
295+ // if (!isPostMerge)
268296
269- // For tests with reorgs, find the actual parent header from block tree
270- parentHeader = blockTree . FindHeader ( correctRlp [ i ] . Block . ParentHash ) ?? parentHeader ;
297+ blockTree . UpdateMainChain ( [ parentBlock ] , true , true ) ;
298+ }
271299
272- Assert . That ( correctRlp [ i ] . Block . Hash , Is . Not . Null , $ "null hash in { test . Name } block { i } ") ;
300+ // Validate block structure first (mimics SyncServer validation)
301+ bool validationResult = blockValidator . ValidateSuggestedBlock ( correctRlp [ i ] . Block , parentBlock . Header , out string ? validationError ) ;
273302
274- bool expectsException = correctRlp [ i ] . ExpectedException is not null ;
275- // Validate block structure first (mimics SyncServer validation)
276- if ( blockValidator . ValidateSuggestedBlock ( correctRlp [ i ] . Block , parentHeader , out string ? validationError ) )
277- {
278- Assert . That ( ! expectsException , $ "Expected block { correctRlp [ i ] . Block . Hash } to fail with '{ correctRlp [ i ] . ExpectedException } ', but it passed validation") ;
279- try
303+ if ( ! validationResult )
304+ {
305+ error = validationError ;
306+ continue ;
307+ }
308+
309+ //if (blockTree.Head.Number >= correctRlp[i].Block.Number)
310+ //{
311+ // continue;
312+ //}
313+
314+ bool suggested = false ;
315+ TaskCompletionSource < BlockRemovedEventArgs > completion = new TaskCompletionSource < BlockRemovedEventArgs > ( TaskCreationOptions . RunContinuationsAsynchronously ) ;
316+
317+ void f ( object ? s , BlockEventArgs e )
280318 {
281- // All validations passed, suggest the block
282- blockTree . SuggestBlock ( correctRlp [ i ] . Block ) ;
319+ suggested = true ;
320+ }
283321
322+ void f2 ( object ? s , BlockRemovedEventArgs e )
323+ {
324+ completion . SetResult ( e ) ;
284325 }
285- catch ( InvalidBlockException e )
326+
327+ blockchainProcessor . BlockRemoved += f2 ;
328+ blockTree . NewBestSuggestedBlock += f ;
329+ blockTree . SuggestBlock ( correctRlp [ i ] . Block , BlockTreeSuggestOptions . ShouldProcess | BlockTreeSuggestOptions . FillBeaconBlock ) ;
330+ blockTree . NewBestSuggestedBlock -= f ;
331+
332+ if ( suggested )
286333 {
287- // Exception thrown during block processing
288- Assert . That ( expectsException , $ "Unexpected invalid block { correctRlp [ i ] . Block . Hash } : { validationError } , Exception: { e } ") ;
289- // else: Expected to fail and did fail via exception → this is correct behavior
334+ await completion . Task ;
335+ if ( completion . Task . Result . ProcessingResult is not ProcessingResult . Success )
336+ {
337+ error = $ "Error processing block { correctRlp [ i ] . Block . Hash } : { completion . Task . Result . Message ?? completion . Task . Result . Exception ? . ToString ( ) } ";
338+ break ;
339+ }
290340 }
291- catch ( Exception e )
341+
342+ blockchainProcessor . BlockRemoved -= f2 ;
343+
344+ parentBlock = correctRlp [ i ] . Block ;
345+
346+ if ( ! isPostMerge )
292347 {
293- Assert . Fail ( $ "Unexpected exception during processing: { e } ") ;
348+ if ( head . Number < parentBlock . Number || head . TotalDifficulty < parentBlock . TotalDifficulty || ( head . TotalDifficulty is not null && head . TotalDifficulty == parentBlock . TotalDifficulty && parentBlock . Timestamp < head . Timestamp ) )
349+ {
350+ head = parentBlock ;
351+ }
294352 }
295- finally
353+ else
296354 {
297- // Dispose AccountChanges to prevent memory leaks in tests
298- correctRlp [ i ] . Block . DisposeAccountChanges ( ) ;
355+ head = parentBlock ;
299356 }
300357 }
301- else
358+ catch ( Exception e )
302359 {
303- // Validation FAILED
304- Assert . That ( expectsException , $ "Unexpected invalid block { correctRlp [ i ] . Block . Hash } : { validationError } ") ;
305- // else: Expected to fail and did fail → this is correct behavior
360+ Assert . Fail ( $ "Unexpected exception during processing: { e } { e . StackTrace } ") ;
306361 }
362+ finally
363+ {
364+ // Dispose AccountChanges to prevent memory leaks in tests
365+ correctRlp [ i ] . Block . DisposeAccountChanges ( ) ;
307366
308- parentHeader = correctRlp [ i ] . Block . Header ;
367+ if ( error is null )
368+ {
369+ Assert . That ( expectsException , Is . Null , $ "Unexpected valid block, expected failure: { expectsException } ") ;
370+ }
371+ else
372+ {
373+ Assert . That ( expectsException , Is . Not . Null , $ "Unexpected invalid block: { error } ") ;
374+ }
375+ }
309376 }
310- return parentHeader ;
377+ blockTree . UpdateMainChain ( [ head ] , true , true ) ;
378+ return head . Header ;
379+ }
380+
381+ private static void BlockTree_NewBestSuggestedBlock ( object ? sender , BlockEventArgs e )
382+ {
383+ throw new NotImplementedException ( ) ;
311384 }
312385
313386 private async static Task RunNewPayloads ( TestEngineNewPayloadsJson [ ] ? newPayloads , IEngineRpcModule engineRpcModule )
@@ -361,9 +434,9 @@ private async static Task RunNewPayloads(TestEngineNewPayloadsJson[]? newPayload
361434 {
362435 Assert . That ( suggestedBlock . Uncles [ uncleIndex ] . Hash , Is . EqualTo ( new Hash256 ( testBlockJson . UncleHeaders ! [ uncleIndex ] . Hash ) ) ) ;
363436 }
364-
365- correctRlp . Add ( ( suggestedBlock , testBlockJson . ExpectedException ) ) ;
366437 }
438+
439+ correctRlp . Add ( ( suggestedBlock , testBlockJson . ExpectedException ) ) ;
367440 }
368441 catch ( Exception e )
369442 {
0 commit comments