Skip to content

Commit 91ae218

Browse files
committed
System case
1 parent 52b537b commit 91ae218

4 files changed

Lines changed: 161 additions & 10 deletions

File tree

src/Nethermind/Nethermind.Blockchain.Test/BlockProcessorTests.cs

Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -291,6 +291,84 @@ public void ParallelWorldState_bal_validation_rejects_missing_account_only_reads
291291
Assert.Throws<ParallelWorldState.InvalidBlockLevelAccessListException>(act);
292292
}
293293

294+
[Test, MaxTime(Timeout.MaxTestTime)]
295+
public void ParallelWorldState_bal_generation_skips_pre_execution_system_account_read()
296+
{
297+
ParallelWorldState stateProvider = new(TestWorldStateFactory.CreateForTest())
298+
{
299+
TracingEnabled = true
300+
};
301+
302+
using (stateProvider.BeginSystemAccountReadSuppression())
303+
{
304+
stateProvider.AddAccountRead(Address.SystemUser);
305+
}
306+
307+
Assert.That(stateProvider.GeneratedBlockAccessList.AccountChanges, Is.Empty);
308+
}
309+
310+
[Test, MaxTime(Timeout.MaxTestTime)]
311+
public void ParallelWorldState_bal_generation_suppresses_system_account_read_across_multiple_system_call_indexes()
312+
{
313+
ParallelWorldState stateProvider = new(TestWorldStateFactory.CreateForTest())
314+
{
315+
TracingEnabled = true
316+
};
317+
318+
AddSystemCallAccountReads(TestItem.AddressA);
319+
AddSystemCallAccountReads(TestItem.AddressB);
320+
321+
stateProvider.GeneratedBlockAccessList.IncrementBlockAccessIndex();
322+
stateProvider.GeneratedBlockAccessList.IncrementBlockAccessIndex();
323+
324+
AddSystemCallAccountReads(TestItem.AddressC);
325+
AddSystemCallAccountReads(TestItem.AddressD);
326+
327+
Assert.That(stateProvider.GeneratedBlockAccessList.GetAccountChanges(Address.SystemUser), Is.Null);
328+
Assert.That(stateProvider.GeneratedBlockAccessList.GetAccountChanges(TestItem.AddressA), Is.Not.Null);
329+
Assert.That(stateProvider.GeneratedBlockAccessList.GetAccountChanges(TestItem.AddressB), Is.Not.Null);
330+
Assert.That(stateProvider.GeneratedBlockAccessList.GetAccountChanges(TestItem.AddressC), Is.Not.Null);
331+
Assert.That(stateProvider.GeneratedBlockAccessList.GetAccountChanges(TestItem.AddressD), Is.Not.Null);
332+
333+
void AddSystemCallAccountReads(Address target)
334+
{
335+
using (stateProvider.BeginSystemAccountReadSuppression())
336+
{
337+
stateProvider.AddAccountRead(Address.SystemUser);
338+
stateProvider.AddAccountRead(target);
339+
}
340+
}
341+
}
342+
343+
[Test, MaxTime(Timeout.MaxTestTime)]
344+
public void ParallelWorldState_bal_validation_rejects_surplus_pre_execution_system_account_read()
345+
{
346+
ParallelWorldState stateProvider = new(TestWorldStateFactory.CreateForTest());
347+
BlockAccessList suggestedBlockAccessList = new();
348+
suggestedBlockAccessList.AddAccountRead(Address.SystemUser);
349+
stateProvider.LoadSuggestedBlockAccessList(suggestedBlockAccessList, 10_000);
350+
351+
TestDelegate act = () => stateProvider.ValidateBlockAccessList(Build.A.BlockHeader.TestObject, 0, 10_000);
352+
353+
Assert.Throws<ParallelWorldState.InvalidBlockLevelAccessListException>(act);
354+
}
355+
356+
[Test, MaxTime(Timeout.MaxTestTime)]
357+
public void ParallelWorldState_bal_validation_rejects_missing_explicit_system_account_read()
358+
{
359+
ParallelWorldState stateProvider = new(TestWorldStateFactory.CreateForTest())
360+
{
361+
TracingEnabled = true
362+
};
363+
stateProvider.LoadSuggestedBlockAccessList(new BlockAccessList(), 10_000);
364+
stateProvider.GeneratedBlockAccessList.IncrementBlockAccessIndex();
365+
stateProvider.AddAccountRead(Address.SystemUser);
366+
367+
TestDelegate act = () => stateProvider.ValidateBlockAccessList(Build.A.BlockHeader.TestObject, 1, 10_000);
368+
369+
Assert.Throws<ParallelWorldState.InvalidBlockLevelAccessListException>(act);
370+
}
371+
294372
[Test, MaxTime(Timeout.MaxTestTime)]
295373
public void BranchProcessor_no_prewarmer_still_processes_successfully()
296374
{
@@ -385,6 +463,13 @@ public void ValidateBlockAccessList(BlockHeader block, ushort index, long gasRem
385463
=> ValidatedGasRemaining.Add(gasRemaining);
386464

387465
public void SetBlockAccessList(Block block, IReleaseSpec spec) { }
466+
public IDisposable BeginSystemAccountReadSuppression() => EmptyDisposable.Instance;
467+
468+
private sealed class EmptyDisposable : IDisposable
469+
{
470+
public static EmptyDisposable Instance { get; } = new();
471+
public void Dispose() { }
472+
}
388473
}
389474

390475
public static IEnumerable<TestCaseData> BlockValidationTransactionsExecutor_bal_validation_cases()

src/Nethermind/Nethermind.Evm/State/IBlockAccessListBuilder.cs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
// SPDX-FileCopyrightText: 2026 Demerzel Solutions Limited
22
// SPDX-License-Identifier: LGPL-3.0-only
33

4+
using System;
45
using Nethermind.Core;
56
using Nethermind.Core.BlockAccessLists;
67
using Nethermind.Core.Specs;
@@ -12,6 +13,7 @@ public interface IBlockAccessListBuilder
1213
public bool TracingEnabled { get; set; }
1314
public BlockAccessList GeneratedBlockAccessList { get; set; }
1415
public void AddAccountRead(Address address);
16+
public IDisposable BeginSystemAccountReadSuppression();
1517
public void LoadSuggestedBlockAccessList(BlockAccessList suggested, long gasUsed);
1618
public long GasUsed();
1719
public void ValidateBlockAccessList(BlockHeader block, ushort index, long gasRemaining);

src/Nethermind/Nethermind.Evm/TransactionProcessing/SystemTransactionProcessor.cs

Lines changed: 26 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,23 @@
11
// SPDX-FileCopyrightText: 2025 Demerzel Solutions Limited
22
// SPDX-License-Identifier: LGPL-3.0-only
33

4+
using System;
45
using Nethermind.Core;
56
using Nethermind.Core.Specs;
7+
using Nethermind.Evm.State;
68
using Nethermind.Evm.GasPolicy;
79
using Nethermind.Evm.Tracing;
810
using Nethermind.Int256;
911
using Nethermind.Logging;
1012
using Nethermind.Specs;
11-
using Nethermind.Evm.State;
1213

1314
namespace Nethermind.Evm.TransactionProcessing;
1415

1516
public sealed class SystemTransactionProcessor<TGasPolicy> : TransactionProcessorBase<TGasPolicy>
1617
where TGasPolicy : struct, IGasPolicy<TGasPolicy>
1718
{
1819
private readonly bool _isAura;
20+
private readonly IBlockAccessListBuilder? _balBuilder;
1921

2022
/// <summary>
2123
/// Hacky flag to execution options, to pass information how original validate should behave.
@@ -30,19 +32,34 @@ public SystemTransactionProcessor(
3032
IVirtualMachine<TGasPolicy>? virtualMachine,
3133
ICodeInfoRepository? codeInfoRepository,
3234
ILogManager? logManager)
33-
: base(blobBaseFeeCalculator, specProvider, worldState, virtualMachine, codeInfoRepository, logManager) => _isAura = SpecProvider.SealEngine == SealEngineType.AuRa;
35+
: base(blobBaseFeeCalculator, specProvider, worldState, virtualMachine, codeInfoRepository, logManager)
36+
{
37+
_isAura = SpecProvider.SealEngine == SealEngineType.AuRa;
38+
_balBuilder = worldState as IBlockAccessListBuilder;
39+
}
3440

3541
protected override TransactionResult Execute(Transaction tx, ITxTracer tracer, ExecutionOptions opts)
3642
{
37-
if (_isAura && !VirtualMachine.BlockExecutionContext.IsGenesis)
43+
// EIP-7928 excludes the synthetic SYSTEM_ADDRESS caller from BALs for system contract calls.
44+
bool suppressSystemAccountReads = !_isAura && tx.SenderAddress == Address.SystemUser && _balBuilder is { TracingEnabled: true };
45+
IDisposable? systemAccountReadSuppression = suppressSystemAccountReads ? _balBuilder!.BeginSystemAccountReadSuppression() : null;
46+
47+
try
3848
{
39-
WorldState.CreateAccountIfNotExists(Address.SystemUser, UInt256.Zero, UInt256.Zero);
49+
if (_isAura && !VirtualMachine.BlockExecutionContext.IsGenesis)
50+
{
51+
WorldState.CreateAccountIfNotExists(Address.SystemUser, UInt256.Zero, UInt256.Zero);
52+
}
53+
54+
ExecutionOptions coreOpts = opts & ~ExecutionOptions.Warmup;
55+
return base.Execute(tx, tracer, ((coreOpts & ExecutionOptions.SkipValidation) != ExecutionOptions.SkipValidation && !coreOpts.HasFlag(ExecutionOptions.SkipValidationAndCommit))
56+
? opts | (ExecutionOptions)OriginalValidate | ExecutionOptions.SkipValidationAndCommit
57+
: opts);
58+
}
59+
finally
60+
{
61+
systemAccountReadSuppression?.Dispose();
4062
}
41-
42-
ExecutionOptions coreOpts = opts & ~ExecutionOptions.Warmup;
43-
return base.Execute(tx, tracer, ((coreOpts & ExecutionOptions.SkipValidation) != ExecutionOptions.SkipValidation && !coreOpts.HasFlag(ExecutionOptions.SkipValidationAndCommit))
44-
? opts | (ExecutionOptions)OriginalValidate | ExecutionOptions.SkipValidationAndCommit
45-
: opts);
4663
}
4764

4865
protected override TransactionResult BuyGas(Transaction tx, IReleaseSpec spec, ITxTracer tracer, ExecutionOptions opts,

src/Nethermind/Nethermind.State/ParallelWorldState.cs

Lines changed: 48 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ public class InvalidBlockLevelAccessListException(BlockHeader block, string mess
2525

2626
private BlockAccessList _suggestedBlockAccessList;
2727
private long _gasUsed;
28+
private int _systemAccountReadSuppressionDepth;
2829

2930
public void LoadSuggestedBlockAccessList(BlockAccessList suggested, long gasUsed)
3031
{
@@ -196,12 +197,37 @@ public override bool TryGetAccount(Address address, out AccountStruct account)
196197

197198
public void AddAccountRead(Address address)
198199
{
199-
if (TracingEnabled)
200+
if (TracingEnabled && (_systemAccountReadSuppressionDepth == 0 || address != Address.SystemUser))
200201
{
201202
GeneratedBlockAccessList.AddAccountRead(address);
202203
}
203204
}
204205

206+
public IDisposable BeginSystemAccountReadSuppression() => new SystemAccountReadSuppressionScope(this);
207+
208+
private sealed class SystemAccountReadSuppressionScope : IDisposable
209+
{
210+
private ParallelWorldState? _stateProvider;
211+
212+
public SystemAccountReadSuppressionScope(ParallelWorldState stateProvider)
213+
{
214+
_stateProvider = stateProvider;
215+
stateProvider._systemAccountReadSuppressionDepth++;
216+
}
217+
218+
public void Dispose()
219+
{
220+
ParallelWorldState? stateProvider = _stateProvider;
221+
if (stateProvider is null)
222+
{
223+
return;
224+
}
225+
226+
_stateProvider = null;
227+
stateProvider._systemAccountReadSuppressionDepth--;
228+
}
229+
}
230+
205231
public override void Restore(Snapshot snapshot)
206232
{
207233
if (TracingEnabled)
@@ -280,6 +306,11 @@ void AdvanceSuggested()
280306
{
281307
if (suggestedHead is null)
282308
{
309+
if (IsSyntheticSystemAccountRead(generatedHead.Value, index))
310+
{
311+
AdvanceGenerated();
312+
continue;
313+
}
283314
if (HasOptionalStorageReads(generatedHead.Value))
284315
{
285316
AdvanceGenerated();
@@ -289,6 +320,10 @@ void AdvanceSuggested()
289320
}
290321
else if (generatedHead is null)
291322
{
323+
if (IsSyntheticSystemAccountRead(suggestedHead.Value, index))
324+
{
325+
throw new InvalidBlockLevelAccessListException(block, $"Suggested block-level access list contained surplus changes for {suggestedHead.Value.Address} at index {index}.");
326+
}
292327
if (HasNoChanges(suggestedHead.Value))
293328
{
294329
AdvanceSuggested();
@@ -311,6 +346,10 @@ void AdvanceSuggested()
311346
}
312347
else if (cmp > 0)
313348
{
349+
if (IsSyntheticSystemAccountRead(suggestedHead.Value, index))
350+
{
351+
throw new InvalidBlockLevelAccessListException(block, $"Suggested block-level access list contained surplus changes for {suggestedHead.Value.Address} at index {index}.");
352+
}
314353
if (HasNoChanges(suggestedHead.Value))
315354
{
316355
AdvanceSuggested();
@@ -320,6 +359,11 @@ void AdvanceSuggested()
320359
}
321360
else
322361
{
362+
if (IsSyntheticSystemAccountRead(generatedHead.Value, index))
363+
{
364+
AdvanceGenerated();
365+
continue;
366+
}
323367
if (HasOptionalStorageReads(generatedHead.Value))
324368
{
325369
AdvanceGenerated();
@@ -370,4 +414,7 @@ c.CodeChange is null &&
370414

371415
private static bool HasOptionalStorageReads(in ChangeAtIndex c)
372416
=> HasNoChanges(c) && c.Reads > 0;
417+
418+
private static bool IsSyntheticSystemAccountRead(in ChangeAtIndex c, ushort index)
419+
=> index == 0 && c.Address == Address.SystemUser && HasNoChanges(c) && c.Reads == 0;
373420
}

0 commit comments

Comments
 (0)