Skip to content

Commit 3fa3890

Browse files
authored
Fix inconsistent test. (#9279)
* Maybe I can just parallelize this * What about now * Make add block more consistent * Ensure head is at third block. * Make sure to just run * Please give the stacktrace * Exactly the same tx * Fix the genesis loading * Make it lazy * Address comment
1 parent f91cafc commit 3fa3890

File tree

10 files changed

+265
-80
lines changed

10 files changed

+265
-80
lines changed

src/Nethermind/Nethermind.Blockchain.Test/Producers/BlockProducerBaseTests.BaseFee.cs

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,15 @@
11
// SPDX-FileCopyrightText: 2022 Demerzel Solutions Limited
22
// SPDX-License-Identifier: LGPL-3.0-only
33

4+
using System;
45
using System.IO;
56
using System.Security;
67
using System.Threading.Tasks;
78
using Nethermind.Abi;
89
using Nethermind.Core;
910
using Nethermind.Core.Extensions;
1011
using Nethermind.Core.Specs;
12+
using Nethermind.Core.Test.Blockchain;
1113
using Nethermind.Core.Test.Builders;
1214
using Nethermind.Core.Test.Container;
1315
using Nethermind.Evm;
@@ -168,7 +170,7 @@ private async Task<ScenarioBuilder> BlocksBeforeTransitionShouldHaveZeroBaseFeeA
168170
Assert.That(startingBlock.Header.BaseFeePerGas, Is.EqualTo(UInt256.Zero));
169171
for (long i = startingBlock.Number; i < _eip1559TransitionBlock - 1; ++i)
170172
{
171-
await _testRpcBlockchain.AddBlock();
173+
await _testRpcBlockchain.AddBlock(TestBlockchainUtil.AddBlockFlags.MayHaveExtraTx);
172174
Block currentBlock = blockTree.Head!;
173175
Assert.That(currentBlock.Header.BaseFeePerGas, Is.EqualTo(UInt256.Zero));
174176
}
@@ -180,7 +182,7 @@ private async Task<ScenarioBuilder> AssertNewBlockAsync(UInt256 expectedBaseFee,
180182
params Transaction[] transactions)
181183
{
182184
await ExecuteAntecedentIfNeeded();
183-
await _testRpcBlockchain.AddBlock(transactions);
185+
await _testRpcBlockchain.AddBlock(TestBlockchainUtil.AddBlockFlags.MayHaveExtraTx, transactions);
184186
IBlockTree blockTree = _testRpcBlockchain.BlockTree;
185187
Block headBlock = blockTree.Head!;
186188
Assert.That(headBlock.Header.BaseFeePerGas, Is.EqualTo(expectedBaseFee));

src/Nethermind/Nethermind.Blockchain.Test/Producers/BlockProducerBaseTests.FeeCollector.cs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
using System.Threading.Tasks;
55
using Nethermind.Core;
66
using Nethermind.Core.Extensions;
7+
using Nethermind.Core.Test.Blockchain;
78
using Nethermind.Core.Test.Builders;
89
using Nethermind.Int256;
910
using NUnit.Framework;
@@ -34,7 +35,7 @@ private async Task<ScenarioBuilder> AssertNewBlockFeeCollectedAsync(UInt256 expe
3435
{
3536
await ExecuteAntecedentIfNeeded();
3637
UInt256 balanceBefore = _testRpcBlockchain.ReadOnlyState.GetBalance(_feeCollector);
37-
await _testRpcBlockchain.AddBlock(transactions);
38+
await _testRpcBlockchain.AddBlock(TestBlockchainUtil.AddBlockFlags.MayHaveExtraTx, transactions);
3839
UInt256 balanceAfter = _testRpcBlockchain.ReadOnlyState.GetBalance(_feeCollector);
3940
Assert.That(balanceAfter - balanceBefore, Is.EqualTo(expectedFeeCollected));
4041

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
// SPDX-FileCopyrightText: 2025 Demerzel Solutions Limited
2+
// SPDX-License-Identifier: LGPL-3.0-only
3+
4+
using System;
5+
using System.Threading;
6+
using Autofac;
7+
using Nethermind.Blockchain;
8+
using Nethermind.Consensus.Processing;
9+
using Nethermind.Core.Specs;
10+
using Nethermind.Evm.Tracing;
11+
12+
namespace Nethermind.Core.Test.Blockchain;
13+
14+
public class InvalidBlockDetector
15+
{
16+
public event EventHandler<IBlockchainProcessor.InvalidBlockEventArgs>? OnInvalidBlock;
17+
18+
private void TriggerOnInvalidBlock(Block invalidBlock)
19+
{
20+
OnInvalidBlock?.Invoke(this, new IBlockchainProcessor.InvalidBlockEventArgs()
21+
{
22+
InvalidBlock = invalidBlock
23+
});
24+
}
25+
26+
internal class BlockProcessorInterceptor(IBlockProcessor baseBlockProcessor, InvalidBlockDetector invalidBlockDetector) : IBlockProcessor
27+
{
28+
public (Block Block, TxReceipt[] Receipts) ProcessOne(Block suggestedBlock, ProcessingOptions options,
29+
IBlockTracer blockTracer, IReleaseSpec spec, CancellationToken token = default)
30+
{
31+
try
32+
{
33+
return baseBlockProcessor.ProcessOne(suggestedBlock, options, blockTracer, spec, token);
34+
}
35+
catch (InvalidBlockException)
36+
{
37+
invalidBlockDetector.TriggerOnInvalidBlock(suggestedBlock);
38+
throw;
39+
}
40+
}
41+
}
42+
}

src/Nethermind/Nethermind.Core.Test/Blockchain/TestBlockchain.cs

Lines changed: 79 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
using System.Threading;
1010
using System.Threading.Tasks;
1111
using Autofac;
12+
using FluentAssertions;
1213
using Nethermind.Api;
1314
using Nethermind.Blockchain;
1415
using Nethermind.Blockchain.Find;
@@ -103,8 +104,8 @@ protected TestBlockchain()
103104
protected AutoCancelTokenSource _cts;
104105
public CancellationToken CancellationToken => _cts.Token;
105106

106-
private TestBlockchainUtil TestUtil => _fromContainer.TestBlockchainUtil.Value;
107-
private PoWTestBlockchainUtil PoWTestUtil => _fromContainer.PoWTestBlockchainUtil.Value;
107+
private TestBlockchainUtil TestUtil => _fromContainer.TestBlockchainUtil;
108+
private PoWTestBlockchainUtil PoWTestUtil => _fromContainer.PoWTestBlockchainUtil;
108109
private IManualBlockProductionTrigger BlockProductionTrigger => _fromContainer.BlockProductionTrigger;
109110

110111
public ManualTimestamper Timestamper => _fromContainer.ManualTimestamper;
@@ -126,37 +127,65 @@ protected TestBlockchain()
126127

127128
// Resolving all these component at once is faster.
128129
private FromContainer _fromContainer = null!;
129-
private record FromContainer(
130-
IStateReader StateReader,
131-
IEthereumEcdsa EthereumEcdsa,
132-
INonceManager NonceManager,
133-
IReceiptStorage ReceiptStorage,
134-
ITxPool TxPool,
135-
IWorldStateManager WorldStateManager,
136-
IBlockPreprocessorStep BlockPreprocessorStep,
137-
IBlockTree BlockTree,
138-
IBlockFinder BlockFinder,
139-
ILogFinder LogFinder,
140-
IChainHeadInfoProvider ChainHeadInfoProvider,
141-
IDbProvider DbProvider,
142-
ISpecProvider SpecProvider,
143-
ISealEngine SealEngine,
144-
ITransactionComparerProvider TransactionComparerProvider,
145-
IPoSSwitcher PoSSwitcher,
146-
IChainLevelInfoRepository ChainLevelInfoRepository,
147-
IMainProcessingContext MainProcessingContext,
148-
IReadOnlyTxProcessingEnvFactory ReadOnlyTxProcessingEnvFactory,
149-
IBlockProducerEnvFactory BlockProducerEnvFactory,
150-
Configuration Configuration,
151-
Lazy<TestBlockchainUtil> TestBlockchainUtil,
152-
Lazy<PoWTestBlockchainUtil> PoWTestBlockchainUtil,
153-
ManualTimestamper ManualTimestamper,
154-
IManualBlockProductionTrigger BlockProductionTrigger,
155-
IShareableTxProcessorSource ShareableTxProcessorSource,
156-
ISealer Sealer,
157-
IForkInfo ForkInfo
130+
public class FromContainer(
131+
Lazy<IStateReader> stateReader,
132+
Lazy<IEthereumEcdsa> ethereumEcdsa,
133+
Lazy<INonceManager> nonceManager,
134+
Lazy<IReceiptStorage> receiptStorage,
135+
Lazy<ITxPool> txPool,
136+
Lazy<IWorldStateManager> worldStateManager,
137+
Lazy<IBlockPreprocessorStep> blockPreprocessorStep,
138+
Lazy<IBlockTree> blockTree,
139+
Lazy<IBlockFinder> blockFinder,
140+
Lazy<ILogFinder> logFinder,
141+
Lazy<IChainHeadInfoProvider> chainHeadInfoProvider,
142+
Lazy<IDbProvider> dbProvider,
143+
Lazy<ISpecProvider> specProvider,
144+
Lazy<ISealEngine> sealEngine,
145+
Lazy<ITransactionComparerProvider> transactionComparerProvider,
146+
Lazy<IPoSSwitcher> poSSwitcher,
147+
Lazy<IChainLevelInfoRepository> chainLevelInfoRepository,
148+
Lazy<IMainProcessingContext> mainProcessingContext,
149+
Lazy<IReadOnlyTxProcessingEnvFactory> readOnlyTxProcessingEnvFactory,
150+
Lazy<IBlockProducerEnvFactory> blockProducerEnvFactory,
151+
Lazy<Configuration> configuration,
152+
Lazy<TestBlockchainUtil> testBlockchainUtil,
153+
Lazy<PoWTestBlockchainUtil> poWTestBlockchainUtil,
154+
Lazy<ManualTimestamper> manualTimestamper,
155+
Lazy<IManualBlockProductionTrigger> blockProductionTrigger,
156+
Lazy<IShareableTxProcessorSource> shareableTxProcessorSource,
157+
Lazy<ISealer> sealer,
158+
Lazy<IForkInfo> forkInfo
158159
)
159160
{
161+
public IStateReader StateReader => stateReader.Value;
162+
public IEthereumEcdsa EthereumEcdsa => ethereumEcdsa.Value;
163+
public INonceManager NonceManager => nonceManager.Value;
164+
public IReceiptStorage ReceiptStorage => receiptStorage.Value;
165+
public ITxPool TxPool => txPool.Value;
166+
public IWorldStateManager WorldStateManager => worldStateManager.Value;
167+
public IBlockPreprocessorStep BlockPreprocessorStep => blockPreprocessorStep.Value;
168+
public IBlockTree BlockTree => blockTree.Value;
169+
public IBlockFinder BlockFinder => blockFinder.Value;
170+
public ILogFinder LogFinder => logFinder.Value;
171+
public IChainHeadInfoProvider ChainHeadInfoProvider => chainHeadInfoProvider.Value;
172+
public IDbProvider DbProvider => dbProvider.Value;
173+
public ISpecProvider SpecProvider => specProvider.Value;
174+
public ISealEngine SealEngine => sealEngine.Value;
175+
public ITransactionComparerProvider TransactionComparerProvider => transactionComparerProvider.Value;
176+
public IPoSSwitcher PoSSwitcher => poSSwitcher.Value;
177+
public IChainLevelInfoRepository ChainLevelInfoRepository => chainLevelInfoRepository.Value;
178+
public IMainProcessingContext MainProcessingContext => mainProcessingContext.Value;
179+
public IReadOnlyTxProcessingEnvFactory ReadOnlyTxProcessingEnvFactory => readOnlyTxProcessingEnvFactory.Value;
180+
public IBlockProducerEnvFactory BlockProducerEnvFactory => blockProducerEnvFactory.Value;
181+
public Configuration Configuration => configuration.Value;
182+
public TestBlockchainUtil TestBlockchainUtil => testBlockchainUtil.Value;
183+
public PoWTestBlockchainUtil PoWTestBlockchainUtil => poWTestBlockchainUtil.Value;
184+
public ManualTimestamper ManualTimestamper => manualTimestamper.Value;
185+
public IManualBlockProductionTrigger BlockProductionTrigger => blockProductionTrigger.Value;
186+
public IShareableTxProcessorSource ShareableTxProcessorSource => shareableTxProcessorSource.Value;
187+
public ISealer Sealer => sealer.Value;
188+
public IForkInfo ForkInfo => forkInfo.Value;
160189
}
161190

162191
public class Configuration
@@ -194,7 +223,13 @@ protected virtual async Task<TestBlockchain> Build(Action<ContainerBuilder>? con
194223

195224
_cts = AutoCancelTokenSource.ThatCancelAfter(Debugger.IsAttached ? TimeSpan.FromMilliseconds(-1) : TimeSpan.FromMilliseconds(TestTimout));
196225

197-
if (testConfiguration.SuggestGenesisOnStart) MainProcessingContext.GenesisLoader.Load();
226+
if (testConfiguration.SuggestGenesisOnStart)
227+
{
228+
// The block added event is not waited by genesis, but its needed to wait here so that `AddBlock` wait correctly.
229+
Task newBlockWaiter = BlockTree.WaitForNewBlock(this.CancellationToken);
230+
MainProcessingContext.GenesisLoader.Load();
231+
await newBlockWaiter;
232+
}
198233

199234
if (testConfiguration.AddBlockOnStart)
200235
await AddBlocksOnStart();
@@ -361,6 +396,13 @@ protected virtual async Task AddBlocksOnStart()
361396
await AddBlock();
362397
await AddBlock(CreateTransactionBuilder().WithNonce(0).TestObject);
363398
await AddBlock(CreateTransactionBuilder().WithNonce(1).TestObject, CreateTransactionBuilder().WithNonce(2).TestObject);
399+
400+
while (true)
401+
{
402+
CancellationToken.ThrowIfCancellationRequested();
403+
if (BlockTree.Head?.Number == 3) return;
404+
await Task.Delay(1, CancellationToken);
405+
}
364406
}
365407

366408
private TransactionBuilder<Transaction> CreateTransactionBuilder()
@@ -403,6 +445,11 @@ public virtual async Task AddBlock(params Transaction[] transactions)
403445
await TestUtil.AddBlockAndWaitForHead(false, _cts.Token, transactions);
404446
}
405447

448+
public async Task AddBlock(TestBlockchainUtil.AddBlockFlags flags, params Transaction[] transactions)
449+
{
450+
await TestUtil.AddBlock(flags, _cts.Token, transactions);
451+
}
452+
406453
public async Task AddBlockMayMissTx(params Transaction[] transactions)
407454
{
408455
await TestUtil.AddBlockAndWaitForHead(true, _cts.Token, transactions);

src/Nethermind/Nethermind.Core.Test/Blockchain/TestBlockchainUtil.cs

Lines changed: 64 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,9 @@
99
using FluentAssertions;
1010
using Nethermind.Blockchain;
1111
using Nethermind.Consensus;
12+
using Nethermind.Consensus.Processing;
1213
using Nethermind.Core.Crypto;
14+
using Nethermind.Core.Events;
1315
using Nethermind.Core.Test.Builders;
1416
using Nethermind.TxPool;
1517
using NUnit.Framework;
@@ -18,6 +20,7 @@ namespace Nethermind.Core.Test.Blockchain;
1820

1921
public class TestBlockchainUtil(
2022
IBlockProducer blockProducer,
23+
InvalidBlockDetector invalidBlockDetector,
2124
ManualTimestamper timestamper,
2225
IBlockTree blockTree,
2326
ITxPool txPool,
@@ -28,8 +31,30 @@ public record Config(long SlotTime = 10);
2831

2932
private Task _previousAddBlock = Task.CompletedTask;
3033

31-
public async Task<AcceptTxResult[]> AddBlockDoNotWaitForHead(bool mayMissTx, CancellationToken cancellationToken, params Transaction[] transactions)
34+
public async Task<AcceptTxResult[]> AddBlock(AddBlockFlags flags, CancellationToken cancellationToken, params Transaction[] transactions)
3235
{
36+
Task waitforHead = flags.HasFlag(AddBlockFlags.DoNotWaitForHead)
37+
? Task.CompletedTask
38+
: WaitAsync(blockTree.WaitForNewBlock(cancellationToken), "timeout waiting for new head");
39+
40+
Task txNewHead = flags.HasFlag(AddBlockFlags.DoNotWaitForHead)
41+
? Task.CompletedTask
42+
: Wait.ForEventCondition<Block>(cancellationToken,
43+
(h) => txPool.TxPoolHeadChanged += h,
44+
(h) => txPool.TxPoolHeadChanged -= h,
45+
b => true);
46+
47+
Block? invalidBlock = null;
48+
void OnInvalidBlock(object? sender, IBlockchainProcessor.InvalidBlockEventArgs e)
49+
{
50+
invalidBlock = e.InvalidBlock;
51+
}
52+
53+
invalidBlockDetector.OnInvalidBlock += OnInvalidBlock;
54+
55+
bool mayMissTx = (flags & AddBlockFlags.MayMissTx) != 0;
56+
bool mayHaveExtraTx = (flags & AddBlockFlags.MayHaveExtraTx) != 0;
57+
3358
_previousAddBlock.IsCompleted.Should().BeTrue("Multiple block produced at once. Please make sure this does not happen for test consistency.");
3459
TaskCompletionSource tcs = new();
3560
_previousAddBlock = tcs.Task;
@@ -48,14 +73,22 @@ public async Task<AcceptTxResult[]> AddBlockDoNotWaitForHead(bool mayMissTx, Can
4873
cancellationToken.ThrowIfCancellationRequested();
4974
block = await blockProducer.BuildBlock(parentHeader: blockTree.GetProducedBlockParent(null), cancellationToken: cancellationToken);
5075

76+
if (invalidBlock is not null) Assert.Fail($"Invalid block {invalidBlock} produced");
77+
5178
if (block is not null)
5279
{
5380
HashSet<Hash256> blockTxs = block.Transactions.Select((tx) => tx.Hash!).ToHashSet();
54-
// Note: It is possible that the block can contain more tx.
55-
if (expectedHashes.All((tx) => blockTxs.Contains(tx))) break;
56-
}
5781

58-
if (mayMissTx) break;
82+
int matchingHashes = expectedHashes.Count((tx) => blockTxs.Contains(tx));
83+
bool allExpectedHashAvailable = matchingHashes == expectedHashes.Count;
84+
if (!allExpectedHashAvailable && mayMissTx) break;
85+
86+
bool hasExtraTx = allExpectedHashAvailable && blockTxs.Count > expectedHashes.Count;
87+
if (hasExtraTx && mayHaveExtraTx) break;
88+
89+
bool hasExactlyTheRightTx = expectedHashes.Count == blockTxs.Count;
90+
if (hasExactlyTheRightTx) break;
91+
}
5992

6093
await Task.Yield();
6194
if (iteration > 0)
@@ -71,16 +104,29 @@ public async Task<AcceptTxResult[]> AddBlockDoNotWaitForHead(bool mayMissTx, Can
71104
blockTree.SuggestBlock(block!).Should().Be(AddBlockResult.Added);
72105

73106
tcs.TrySetResult();
107+
108+
await waitforHead;
109+
110+
await txNewHead; // Wait for tx new head event so that processed tx was removed from txpool
111+
112+
invalidBlockDetector.OnInvalidBlock -= OnInvalidBlock;
74113
return txResults;
75114
}
76115

77-
public async Task AddBlockAndWaitForHead(bool mayMissTx, CancellationToken cancellationToken, params Transaction[] transactions)
116+
public async Task<AcceptTxResult[]> AddBlockDoNotWaitForHead(bool mayMissTx, CancellationToken cancellationToken, params Transaction[] transactions)
78117
{
79-
Task waitforHead = WaitAsync(blockTree.WaitForNewBlock(cancellationToken), "timeout waiting for new head");
118+
AddBlockFlags flags = AddBlockFlags.DoNotWaitForHead;
119+
if (mayMissTx) flags |= AddBlockFlags.MayMissTx;
80120

81-
await AddBlockDoNotWaitForHead(mayMissTx, cancellationToken, transactions);
121+
return await AddBlock(flags, cancellationToken, transactions);
122+
}
82123

83-
await waitforHead;
124+
public async Task AddBlockAndWaitForHead(bool mayMissTx, CancellationToken cancellationToken, params Transaction[] transactions)
125+
{
126+
AddBlockFlags flags = AddBlockFlags.None;
127+
if (mayMissTx) flags |= AddBlockFlags.MayMissTx;
128+
129+
await AddBlock(flags, cancellationToken, transactions);
84130
}
85131

86132
private static async Task WaitAsync(Task task, string error)
@@ -94,4 +140,13 @@ private static async Task WaitAsync(Task task, string error)
94140
throw new InvalidOperationException(error);
95141
}
96142
}
143+
144+
[Flags]
145+
public enum AddBlockFlags
146+
{
147+
None = 0,
148+
DoNotWaitForHead = 1,
149+
MayMissTx = 2,
150+
MayHaveExtraTx = 4,
151+
}
97152
}

src/Nethermind/Nethermind.Core.Test/Modules/TestEnvironmentModule.cs

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
using Nethermind.Blockchain.Synchronization;
99
using Nethermind.Config;
1010
using Nethermind.Consensus;
11+
using Nethermind.Consensus.Processing;
1112
using Nethermind.Core.Extensions;
1213
using Nethermind.Core.Specs;
1314
using Nethermind.Core.Test.Blockchain;
@@ -48,7 +49,9 @@ protected override void Load(ContainerBuilder builder)
4849

4950
.AddSingleton<PseudoNethermindRunner>()
5051
.AddSingleton<TestBlockchainUtil>()
51-
.AddSingleton<TestBlockchainUtil.Config>()
52+
.AddSingleton<TestBlockchainUtil.Config>()
53+
.AddSingleton<InvalidBlockDetector>()
54+
.AddDecorator<IBlockProcessor, InvalidBlockDetector.BlockProcessorInterceptor>()
5255

5356
.AddSingleton<ISealer>(new NethDevSealEngine(nodeKey.Address))
5457
.AddSingleton<ITimestamper, ManualTimestamper>()

0 commit comments

Comments
 (0)