Skip to content
Original file line number Diff line number Diff line change
Expand Up @@ -224,7 +224,6 @@ static BlockHeader Process(BranchProcessor auRaBlockProcessor, BlockHeader paren
processor,
GnosisSpecProvider.Instance,
stateProvider,
new BeaconBlockRootHandler(transactionProcessor, stateProvider),
blockhashProvider,
LimboLogs.Instance);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -278,7 +278,6 @@ private static (BlockProcessor processor, BranchProcessor branchProcessor, IWorl
processor,
HoodiSpecProvider.Instance,
stateProvider,
new BeaconBlockRootHandler(transactionProcessor, stateProvider),
Substitute.For<IBlockhashProvider>(),
LimboLogs.Instance,
preWarmer);
Expand Down Expand Up @@ -480,7 +479,7 @@ private class TokenCapturingPreWarmer : IBlockCachePreWarmer
public CancellationToken CapturedToken { get; private set; }

public Task PreWarmCaches(Block suggestedBlock, BlockHeader? parent, IReleaseSpec spec,
CancellationToken cancellationToken = default, params ReadOnlySpan<IHasAccessList> systemAccessLists)
CancellationToken cancellationToken = default)
{
CapturedToken = cancellationToken;
return Task.CompletedTask;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
using Nethermind.Specs.Test;
using Nethermind.Evm;
using Nethermind.Evm.State;
using Nethermind.Core.Eip2930;
using Nethermind.Int256;
using NSubstitute;
using NUnit.Framework;
Expand Down Expand Up @@ -52,6 +53,48 @@ private static IWorldState CreateWorldStateWithHistoryContract(IReleaseSpec spec
return worldState;
}


[TestCase(true, false, true, true, TestName = "GetAccessList_WithDeployedContract_CoversTheParentHashSlot")]
[TestCase(false, false, true, false, TestName = "GetAccessList_BeforeEip2935_IsNull")]
[TestCase(true, true, true, false, TestName = "GetAccessList_ForGenesis_IsNull")]
[TestCase(true, false, false, false, TestName = "GetAccessList_WithoutDeployedContract_IsNull")]
public void GetAccessList_AtGivenForkAndState_HintsExactlyTheParentHashSlot(
bool eip2935Enabled, bool isGenesis, bool contractDeployed, bool expectList)
{
IReleaseSpec spec = eip2935Enabled ? Prague.Instance : Cancun.Instance;
(IWorldState worldState, Hash256 stateRoot) = CreateWorldState();
Block parent = Build.A.Block.WithNumber(41).TestObject;
Block current = isGenesis
? Build.A.Block.Genesis.WithStateRoot(stateRoot).TestObject
: Build.A.Block.WithParent(parent).WithStateRoot(stateRoot).TestObject;

using IDisposable scope = worldState.BeginScope(current.Header);
if (contractDeployed)
{
byte[] code = [1, 2, 3];
worldState.InsertCode(Eip2935Constants.BlockHashHistoryAddress, ValueKeccak.Compute(code), code, spec);
}

AccessList? accessList = new BlockhashStore(worldState).GetAccessList(current, spec);

if (!expectList)
{
Assert.That(accessList, Is.Null);
return;
}

UInt256 expectedSlot = new((current.Number - 1) % spec.Eip2935RingBufferSize);
Assert.That(accessList, Is.Not.Null);
foreach ((Address address, AccessList.StorageKeysEnumerable storageKeys) in accessList!)
{
Assert.That(address, Is.EqualTo(Eip2935Constants.BlockHashHistoryAddress));
foreach (UInt256 storageKey in storageKeys)
{
Assert.That(storageKey, Is.EqualTo(expectedSlot), "the hint must cover exactly the ring-buffer slot the block writes");
}
}
}

private static BlockhashProvider CreateBlockHashProvider(IHeaderFinder headerFinder, IReleaseSpec spec)
{
(IWorldState worldState, Hash256 _) = CreateWorldState();
Expand Down
1 change: 0 additions & 1 deletion src/Nethermind/Nethermind.Blockchain.Test/ReorgTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -110,7 +110,6 @@ public void Setup()
blockProcessor,
MainnetSpecProvider.Instance,
stateProvider,
new BeaconBlockRootHandler(transactionProcessor, stateProvider),
blockhashProvider,
LimboLogs.Instance);

Expand Down
30 changes: 21 additions & 9 deletions src/Nethermind/Nethermind.Blockchain/Blocks/BlockhashStore.cs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
using System.Runtime.CompilerServices;
using Nethermind.Core;
using Nethermind.Core.Crypto;
using Nethermind.Core.Eip2930;
using Nethermind.Core.Extensions;
using Nethermind.Core.Specs;
using Nethermind.Evm.State;
Expand All @@ -14,22 +15,33 @@
[assembly: InternalsVisibleTo("Nethermind.Merge.Plugin.Test")]
namespace Nethermind.Blockchain.Blocks;

public class BlockhashStore(IWorldState worldState) : IBlockhashStore
public class BlockhashStore(IWorldState worldState) : IBlockhashStore, IHasAccessList
{
private static readonly byte[] EmptyBytes = [0];

public void ApplyBlockhashStateChanges(BlockHeader blockHeader, IReleaseSpec spec)
{
if (!spec.IsEip2935Enabled || blockHeader.IsGenesis || blockHeader.ParentHash is null) return;
if (!TryGetParentHashCell(blockHeader, spec, out StorageCell blockHashStoreCell)) return;

Address? eip2935Account = spec.Eip2935ContractAddress ?? Eip2935Constants.BlockHashHistoryAddress;
if (!worldState.IsContract(eip2935Account)) return;
worldState.Set(blockHashStoreCell, blockHeader.ParentHash!.Bytes.WithoutLeadingZeros().ToArray());
worldState.RecordBytecodeAccess(blockHashStoreCell.Address);
}

public AccessList? GetAccessList(Block block, IReleaseSpec spec) =>
TryGetParentHashCell(block.Header, spec, out StorageCell blockHashStoreCell)
? AccessList.ForSingleStorageCell(in blockHashStoreCell)
: null;

private bool TryGetParentHashCell(BlockHeader header, IReleaseSpec spec, out StorageCell blockHashStoreCell)
{
blockHashStoreCell = default;
if (!spec.IsEip2935Enabled || header.IsGenesis || header.ParentHash is null) return false;

Address eip2935Account = spec.Eip2935ContractAddress ?? Eip2935Constants.BlockHashHistoryAddress;
if (!worldState.IsContract(eip2935Account)) return false;

Hash256 parentBlockHash = blockHeader.ParentHash;
UInt256 parentBlockIndex = new((blockHeader.Number - 1) % spec.Eip2935RingBufferSize);
StorageCell blockHashStoreCell = new(eip2935Account, parentBlockIndex);
worldState.Set(blockHashStoreCell, parentBlockHash!.Bytes.WithoutLeadingZeros().ToArray());
worldState.RecordBytecodeAccess(eip2935Account);
blockHashStoreCell = new StorageCell(eip2935Account, new UInt256((ulong)(header.Number - 1) % spec.Eip2935RingBufferSize));
return true;
}

public Hash256? GetBlockHashFromState(BlockHeader currentHeader, ulong requiredBlockNumber, IReleaseSpec spec)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -53,13 +53,15 @@ public sealed class BlockCachePreWarmer : IBlockCachePreWarmer
private WarmMarker? _warmMarker;

private readonly PooledSet<Hash256> _warmedTxHashes = [];
private readonly IHasAccessList[] _systemAccessLists;

public BlockCachePreWarmer(
PrewarmerEnvFactory envFactory,
IBlocksConfig blocksConfig,
NodeStorageCache nodeStorageCache,
PreBlockCaches preBlockCaches,
ILogManager logManager
ILogManager logManager,
IHasAccessList[]? systemAccessLists = null
) : this(
new ReadOnlyTxProcessingEnvPooledObjectPolicy(envFactory, preBlockCaches),
Environment.ProcessorCount * 2,
Expand All @@ -68,7 +70,8 @@ ILogManager logManager
nodeStorageCache,
preBlockCaches,
logManager,
blocksConfig.MempoolPreWarmConcurrency) => _parallelExecutionEnabled = blocksConfig.ParallelExecution;
blocksConfig.MempoolPreWarmConcurrency,
systemAccessLists) => _parallelExecutionEnabled = blocksConfig.ParallelExecution;

internal BlockCachePreWarmer(
IPooledObjectPolicy<IReadOnlyTxProcessorSource> poolPolicy,
Expand All @@ -78,8 +81,10 @@ internal BlockCachePreWarmer(
NodeStorageCache nodeStorageCache,
PreBlockCaches preBlockCaches,
ILogManager logManager,
int speculativeConcurrency = 0)
int speculativeConcurrency = 0,
IHasAccessList[]? systemAccessLists = null)
{
_systemAccessLists = systemAccessLists ?? [];
_concurrencyLevel = concurrency == 0 ? Math.Min(Environment.ProcessorCount - 1, 16) : concurrency;
_speculativeConcurrencyLevel = speculativeConcurrency == 0 ? Math.Max(1, _concurrencyLevel / 2) : speculativeConcurrency;
_parallelExecutionBatchRead = parallelExecutionBatchRead;
Expand All @@ -89,7 +94,7 @@ internal BlockCachePreWarmer(
_nodeStorageCache = nodeStorageCache;
}

public Task PreWarmCaches(Block suggestedBlock, BlockHeader? parent, IReleaseSpec spec, CancellationToken cancellationToken = default, params ReadOnlySpan<IHasAccessList> systemAccessLists)
public Task PreWarmCaches(Block suggestedBlock, BlockHeader? parent, IReleaseSpec spec, CancellationToken cancellationToken = default)
{
if (_preBlockCaches is null || !ShouldPreWarm(spec)) return Task.CompletedTask;

Expand All @@ -111,7 +116,7 @@ public Task PreWarmCaches(Block suggestedBlock, BlockHeader? parent, IReleaseSpe
}
}

return WarmCaches(suggestedBlock, parent, spec, speculativelyWarmed, cancellationToken, systemAccessLists);
return WarmCaches(suggestedBlock, parent, spec, speculativelyWarmed, cancellationToken, _systemAccessLists);
}

private Task WarmCaches(Block suggestedBlock, BlockHeader? parent, IReleaseSpec spec, ISet<Hash256>? speculativelyWarmed, CancellationToken cancellationToken, ReadOnlySpan<IHasAccessList> systemAccessLists)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;
using Nethermind.Blockchain.BeaconBlockRoot;

Check warning on line 8 in src/Nethermind/Nethermind.Consensus/Processing/BranchProcessor.cs

View workflow job for this annotation

GitHub Actions / Check code lint

Using directive is unnecessary. (https://learn.microsoft.com/dotnet/fundamentals/code-analysis/style-rules/ide0005) [/home/runner/work/nethermind/nethermind/src/Nethermind/Nethermind.Consensus/Nethermind.Consensus.csproj]
using Nethermind.Core;
using Nethermind.Core.Extensions;
using Nethermind.Core.Specs;
Expand All @@ -20,7 +20,6 @@
IBlockProcessor blockProcessor,
ISpecProvider specProvider,
IWorldState stateProvider,
IBeaconBlockRootHandler beaconBlockRootHandler,
IBlockhashProvider blockhashProvider,
ILogManager logManager,
IBlockCachePreWarmer? preWarmer = null)
Expand Down Expand Up @@ -227,8 +226,7 @@
: preWarmer?.PreWarmCaches(suggestedBlock,
preBlockBaseBlock,
spec,
token,
beaconBlockRootHandler);
token);

// Tiny blocks normally don't justify prewarming overhead — except when the prewarmer
// would run in BAL read-warming mode, which is cheap and worthwhile regardless of tx count.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
using System.Threading;
using System.Threading.Tasks;
using Nethermind.Core;
using Nethermind.Core.Eip2930;

Check warning on line 8 in src/Nethermind/Nethermind.Consensus/Processing/IBlockCachePreWarmer.cs

View workflow job for this annotation

GitHub Actions / Check code lint

Using directive is unnecessary. (https://learn.microsoft.com/dotnet/fundamentals/code-analysis/style-rules/ide0005) [/home/runner/work/nethermind/nethermind/src/Nethermind/Nethermind.Consensus/Nethermind.Consensus.csproj]
using Nethermind.Core.Specs;
using Nethermind.Evm.State;

Expand All @@ -13,7 +13,7 @@

public interface IBlockCachePreWarmer : IDisposable
{
Task PreWarmCaches(Block suggestedBlock, BlockHeader? parent, IReleaseSpec spec, CancellationToken cancellationToken = default, params ReadOnlySpan<IHasAccessList> systemAccessLists);
Task PreWarmCaches(Block suggestedBlock, BlockHeader? parent, IReleaseSpec spec, CancellationToken cancellationToken = default);
CacheType ClearCaches();
bool IsBalReadWarmingEnabled(IReleaseSpec spec);

Expand Down
4 changes: 4 additions & 0 deletions src/Nethermind/Nethermind.Core/Eip2930/AccessList.cs
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,10 @@ private AccessList(List<(Address address, int count)> addresses, List<UInt256> k

public static AccessList Empty { get; } = new([], []);

/// <summary>Exactly-sized single-entry list, for hint producers on per-block paths.</summary>
public static AccessList ForSingleStorageCell(in StorageCell cell) =>
new([(cell.Address, 1)], [cell.Index]);

public bool IsEmpty => _addresses.Count == 0;
public (int AddressesCount, int StorageKeysCount) Count => (_addresses.Count, _keys.Count);

Expand Down
6 changes: 6 additions & 0 deletions src/Nethermind/Nethermind.Init/Modules/PrewarmerModule.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,9 @@

using Autofac;
using Nethermind.Blockchain;
using Nethermind.Blockchain.BeaconBlockRoot;
using Nethermind.Blockchain.Blocks;
using Nethermind.Core.Eip2930;
using Nethermind.Config;
using Nethermind.Consensus.Processing;
using Nethermind.Core;
Expand Down Expand Up @@ -46,6 +49,9 @@ protected override void Load(ContainerBuilder builder)
// module, so singleton here is like scoped but exclude inner prewarmer lifetime.
.AddSingleton<PreBlockCaches>()
.AddScoped<IBlockCachePreWarmer, BlockCachePreWarmer>()
// System-contract access-list hints the prewarmer warms alongside tx addresses.
.AddScoped<IHasAccessList>(ctx => ctx.Resolve<IBeaconBlockRootHandler>())
.AddScoped<IHasAccessList>(ctx => (IHasAccessList)ctx.Resolve<IBlockhashStore>())

// This class create the block processing env with worldstate that populate the cache
.Add<PrewarmerEnvFactory>()
Expand Down
Loading