Skip to content
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
using System;
using System.Collections.Generic;
using Nethermind.Blockchain;
using Nethermind.Blockchain.Blocks;
using Nethermind.Blockchain.BeaconBlockRoot;
using Nethermind.Config;
using Nethermind.Blockchain.Receipts;
Expand Down Expand Up @@ -225,6 +226,7 @@ static BlockHeader Process(BranchProcessor auRaBlockProcessor, BlockHeader paren
GnosisSpecProvider.Instance,
stateProvider,
new BeaconBlockRootHandler(transactionProcessor, stateProvider),
new BlockhashStore(stateProvider),
blockhashProvider,
LimboLogs.Instance);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -279,6 +279,7 @@ private static (BlockProcessor processor, BranchProcessor branchProcessor, IWorl
HoodiSpecProvider.Instance,
stateProvider,
new BeaconBlockRootHandler(transactionProcessor, stateProvider),
new BlockhashStore(stateProvider),
Substitute.For<IBlockhashProvider>(),
LimboLogs.Instance,
preWarmer);
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: 1 addition & 0 deletions src/Nethermind/Nethermind.Blockchain.Test/ReorgTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -111,6 +111,7 @@ public void Setup()
MainnetSpecProvider.Instance,
stateProvider,
new BeaconBlockRootHandler(transactionProcessor, stateProvider),
new BlockhashStore(stateProvider),
blockhashProvider,
LimboLogs.Instance);

Expand Down
31 changes: 23 additions & 8 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 @@ -20,16 +21,30 @@ public class BlockhashStore(IWorldState worldState) : IBlockhashStore

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)
? new AccessList.Builder()
.AddAddress(blockHashStoreCell.Address)
.AddStorage(blockHashStoreCell.Index)
.Build()
: 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 @@ -3,11 +3,12 @@

using Nethermind.Core;
using Nethermind.Core.Crypto;
using Nethermind.Core.Eip2930;
using Nethermind.Core.Specs;

namespace Nethermind.Blockchain.Blocks;

public interface IBlockhashStore
public interface IBlockhashStore : IHasAccessList
Comment thread
svlachakis marked this conversation as resolved.
Outdated
{
public void ApplyBlockhashStateChanges(BlockHeader blockHeader, IReleaseSpec spec);
public Hash256? GetBlockHashFromState(BlockHeader currentBlockHeader, ulong requiredBlockNumber, IReleaseSpec spec);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
using System.Threading;
using System.Threading.Tasks;
using Nethermind.Blockchain.BeaconBlockRoot;
using Nethermind.Blockchain.Blocks;
using Nethermind.Core;
using Nethermind.Core.Extensions;
using Nethermind.Core.Specs;
Expand All @@ -21,6 +22,7 @@ public class BranchProcessor(
ISpecProvider specProvider,
IWorldState stateProvider,
IBeaconBlockRootHandler beaconBlockRootHandler,
IBlockhashStore blockhashStore,
Comment thread
svlachakis marked this conversation as resolved.
Outdated
IBlockhashProvider blockhashProvider,
ILogManager logManager,
IBlockCachePreWarmer? preWarmer = null)
Expand Down Expand Up @@ -228,7 +230,8 @@ static void WaitAndClear(ref Task? task)
preBlockBaseBlock,
spec,
token,
beaconBlockRootHandler);
beaconBlockRootHandler,
blockhashStore);

// 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
Loading