Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -351,4 +351,71 @@ public void Eip2935_poc_trimmed_hashes()
var result = store.GetBlockHashFromState(current.Header, current.Header.Number - 1);
Assert.That(result, Is.EqualTo(current.Header.ParentHash));
}

[Test, MaxTime(Timeout.MaxTestTime)]
public void BlockhashStore_uses_custom_ring_buffer_size()
{
const int customRingBufferSize = 100;
const int chainLength = 150;

Block genesis = Build.A.Block.Genesis.TestObject;
BlockTree tree = Build.A.BlockTree(genesis).OfHeadersOnly.OfChainLength(chainLength).TestObject;
BlockHeader? head = tree.FindHeader(chainLength - 1, BlockTreeLookupOptions.None);

(IWorldState worldState, Hash256 stateRoot) = CreateWorldState();
Block current = Build.A.Block.WithParent(head!).WithStateRoot(stateRoot).TestObject;
tree.SuggestHeader(current.Header);

// Custom spec with non-standard ring buffer size
ReleaseSpec customSpec = new()
{
IsEip2935Enabled = true,
IsEip7709Enabled = true,
Eip2935RingBufferSize = customRingBufferSize
};

var specProvider = new CustomSpecProvider(
(new ForkActivation(0, genesis.Timestamp), customSpec));
BlockhashStore store = new(specProvider, worldState);

using IDisposable _ = worldState.BeginScope(current.Header);

// Insert code (account already created by CreateWorldState)
byte[] code = [1, 2, 3];
worldState.InsertCode(Eip2935Constants.BlockHashHistoryAddress, ValueKeccak.Compute(code), code, customSpec);

// Process current block (150) - stores block 149's hash
store.ApplyBlockhashStateChanges(current.Header);

// Simulate processing blocks 51-149 to fill the ring buffer
// At block 150 with buffer size 100, we need hashes for blocks 50-149
// Block 150 stores block 149's hash (already done above)
// Now process blocks 51-149 from the tree to store blocks 50-148
for (long blockNum = chainLength - customRingBufferSize + 1; blockNum < chainLength; blockNum++)
{
BlockHeader? header = tree.FindHeader(blockNum, BlockTreeLookupOptions.None);
Assert.That(header, Is.Not.Null, $"Block {blockNum} should exist in tree");

store.ApplyBlockhashStateChanges(header!);
}

// Now verify all blocks behave correctly with custom ring buffer size
// At block 150 with buffer size 100, only blocks [50, 149] should be retrievable
for (long blockNum = 1; blockNum < chainLength - customRingBufferSize; blockNum++)
{
Hash256? result = store.GetBlockHashFromState(current.Header, blockNum, customSpec);

Assert.That(result, Is.Null,
$"Block {blockNum} should be outside custom ring buffer of size {customRingBufferSize} (proves custom size is used, not default 8191)");
}

for (long blockNum = chainLength - customRingBufferSize; blockNum < chainLength; blockNum++)
{
BlockHeader? expectedHeader = tree.FindHeader(blockNum, BlockTreeLookupOptions.None);
Hash256? result = store.GetBlockHashFromState(current.Header, blockNum, customSpec);

Assert.That(result, Is.EqualTo(expectedHeader!.Hash),
$"Block {blockNum} should be retrievable within custom ring buffer of size {customRingBufferSize}");
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -30,22 +30,22 @@ public void ApplyBlockhashStateChanges(BlockHeader blockHeader, IReleaseSpec spe
if (!worldState.IsContract(eip2935Account)) return;

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

public Hash256? GetBlockHashFromState(BlockHeader currentHeader, long requiredBlockNumber)
=> GetBlockHashFromState(currentHeader, requiredBlockNumber, specProvider.GetSpec(currentHeader));

public Hash256? GetBlockHashFromState(BlockHeader currentHeader, long requiredBlockNumber, IReleaseSpec? spec)
public Hash256? GetBlockHashFromState(BlockHeader currentHeader, long requiredBlockNumber, IReleaseSpec spec)
{
if (requiredBlockNumber >= currentHeader.Number ||
requiredBlockNumber + Eip2935Constants.RingBufferSize < currentHeader.Number)
requiredBlockNumber + spec.Eip2935RingBufferSize < currentHeader.Number)
{
return null;
}
var blockIndex = new UInt256((ulong)(requiredBlockNumber % Eip2935Constants.RingBufferSize));
UInt256 blockIndex = new UInt256((ulong)(requiredBlockNumber % spec.Eip2935RingBufferSize));
Address? eip2935Account = spec.Eip2935ContractAddress ?? Eip2935Constants.BlockHashHistoryAddress;
StorageCell blockHashStoreCell = new(eip2935Account, blockIndex);
ReadOnlySpan<byte> data = worldState.Get(blockHashStoreCell);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,5 +12,5 @@ public interface IBlockhashStore
public void ApplyBlockhashStateChanges(BlockHeader blockHeader);
public void ApplyBlockhashStateChanges(BlockHeader blockHeader, IReleaseSpec spec);
public Hash256? GetBlockHashFromState(BlockHeader currentBlockHeader, long requiredBlockNumber);
public Hash256? GetBlockHashFromState(BlockHeader currentBlockHeader, long requiredBlockNumber, IReleaseSpec? spec);
public Hash256? GetBlockHashFromState(BlockHeader currentBlockHeader, long requiredBlockNumber, IReleaseSpec spec);
}
6 changes: 6 additions & 0 deletions src/Nethermind/Nethermind.Core/Specs/IReleaseSpec.cs
Original file line number Diff line number Diff line change
Expand Up @@ -306,6 +306,12 @@ public interface IReleaseSpec : IEip1559Spec, IReceiptSpec
bool IsEip7709Enabled { get; }
Address Eip2935ContractAddress { get; }

/// <summary>
/// EIP-2935 ring buffer size for historical block hash storage.
/// Defaults to 8,191 blocks for Ethereum mainnet.
/// </summary>
long Eip2935RingBufferSize => Eip2935Constants.RingBufferSize;

/// <summary>
/// SELFDESTRUCT only in same transaction
/// </summary>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -129,6 +129,7 @@ public class ChainParameters
public Address Eip7251ContractAddress { get; set; }
public ulong? Eip2935TransitionTimestamp { get; set; }
public Address Eip2935ContractAddress { get; set; }
public long Eip2935RingBufferSize { get; set; } = Eip2935Constants.RingBufferSize;
public ulong? Eip7951TransitionTimestamp { get; set; }
public ulong? Rip7212TransitionTimestamp { get; set; }
public ulong? Eip7692TransitionTimestamp { get; set; }
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -257,6 +257,7 @@ protected virtual ReleaseSpec CreateReleaseSpec(ChainSpec chainSpec, long releas
releaseSpec.IsEip2935Enabled = (chainSpec.Parameters.Eip2935TransitionTimestamp ?? ulong.MaxValue) <= releaseStartTimestamp;
releaseSpec.IsEofEnabled = (chainSpec.Parameters.Eip7692TransitionTimestamp ?? ulong.MaxValue) <= releaseStartTimestamp;
releaseSpec.Eip2935ContractAddress = chainSpec.Parameters.Eip2935ContractAddress;
releaseSpec.Eip2935RingBufferSize = chainSpec.Parameters.Eip2935RingBufferSize;

releaseSpec.IsEip7702Enabled = (chainSpec.Parameters.Eip7702TransitionTimestamp ?? ulong.MaxValue) <= releaseStartTimestamp;
releaseSpec.IsEip7823Enabled = (chainSpec.Parameters.Eip7823TransitionTimestamp ?? ulong.MaxValue) <= releaseStartTimestamp;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -159,6 +159,7 @@ bool GetForInnerPathExistence(KeyValuePair<string, JsonElement> o) =>
Eip4788ContractAddress = chainSpecJson.Params.Eip4788ContractAddress ?? Eip4788Constants.BeaconRootsAddress,
Eip2935TransitionTimestamp = chainSpecJson.Params.Eip2935TransitionTimestamp,
Eip2935ContractAddress = chainSpecJson.Params.Eip2935ContractAddress ?? Eip2935Constants.BlockHashHistoryAddress,
Eip2935RingBufferSize = chainSpecJson.Params.Eip2935RingBufferSize ?? Eip2935Constants.RingBufferSize,
TransactionPermissionContract = chainSpecJson.Params.TransactionPermissionContract,
TransactionPermissionContractTransition = chainSpecJson.Params.TransactionPermissionContractTransition,
ValidateChainIdTransition = chainSpecJson.Params.ValidateChainIdTransition,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -148,6 +148,7 @@ public class ChainSpecParamsJson
public Address Eip4788ContractAddress { get; set; }
public ulong? Eip2935TransitionTimestamp { get; set; }
public Address Eip2935ContractAddress { get; set; }
public long? Eip2935RingBufferSize { get; set; }
public UInt256? Eip4844BlobGasPriceUpdateFraction { get; set; }
public UInt256? Eip4844MinBlobGasPrice { get; set; }
public ulong? Eip4844FeeCollectorTransitionTimestamp { get; set; }
Expand Down
1 change: 1 addition & 0 deletions src/Nethermind/Nethermind.Specs/ReleaseSpec.cs
Original file line number Diff line number Diff line change
Expand Up @@ -166,6 +166,7 @@ public Address Eip2935ContractAddress

private FrozenSet<AddressAsKey>? _precompiles;
FrozenSet<AddressAsKey> IReleaseSpec.Precompiles => _precompiles ??= BuildPrecompilesCache();
public long Eip2935RingBufferSize { get; set; } = Eip2935Constants.RingBufferSize;

public virtual FrozenSet<AddressAsKey> BuildPrecompilesCache()
{
Expand Down