diff --git a/src/Nethermind/Ethereum.Test.Base/BlockchainTest.cs b/src/Nethermind/Ethereum.Test.Base/BlockchainTest.cs
index 19f62afb800..b4f78c9a057 100644
--- a/src/Nethermind/Ethereum.Test.Base/BlockchainTest.cs
+++ b/src/Nethermind/Ethereum.Test.Base/BlockchainTest.cs
@@ -20,6 +20,7 @@ public class BlockchainTest : EthereumTest
public TestBlockJson[]? Blocks { get; set; }
public TestBlockHeaderJson? GenesisBlockHeader { get; set; }
+ public TestEngineNewPayloadsJson[]? EngineNewPayloads { get; set; }
public Dictionary
? Pre { get; set; }
public Dictionary? PostState { get; set; }
diff --git a/src/Nethermind/Ethereum.Test.Base/BlockchainTestBase.cs b/src/Nethermind/Ethereum.Test.Base/BlockchainTestBase.cs
index 413fd4c60f4..e3be2767047 100644
--- a/src/Nethermind/Ethereum.Test.Base/BlockchainTestBase.cs
+++ b/src/Nethermind/Ethereum.Test.Base/BlockchainTestBase.cs
@@ -9,7 +9,6 @@
using System.Threading;
using System.Threading.Tasks;
using Autofac;
-using Nethermind.Api;
using Nethermind.Blockchain;
using Nethermind.Blockchain.Find;
using Nethermind.Config;
@@ -30,26 +29,25 @@
using Nethermind.Specs;
using Nethermind.Specs.Forks;
using Nethermind.Specs.Test;
-using Nethermind.State;
using Nethermind.Evm.State;
using Nethermind.Init.Modules;
using Nethermind.TxPool;
using NUnit.Framework;
+using Nethermind.Merge.Plugin.Data;
+using Nethermind.Merge.Plugin;
+using Nethermind.JsonRpc;
namespace Ethereum.Test.Base;
public abstract class BlockchainTestBase
{
- private static ILogger _logger;
- private static ILogManager _logManager = new TestLogManager(LogLevel.Warn);
- private static ISealValidator Sealer { get; }
+ private static readonly ILogger _logger;
+ private static readonly ILogManager _logManager = new TestLogManager(LogLevel.Warn);
private static DifficultyCalculatorWrapper DifficultyCalculator { get; }
static BlockchainTestBase()
{
DifficultyCalculator = new DifficultyCalculatorWrapper();
- Sealer = new EthashSealValidator(_logManager, DifficultyCalculator, new CryptoRandom(), new Ethash(_logManager), Timestamper.Default); // temporarily keep reusing the same one as otherwise it would recreate cache for each test
-
_logManager ??= LimboLogs.Instance;
_logger = _logManager.GetClassLogger();
}
@@ -129,6 +127,7 @@ protected async Task RunTest(BlockchainTest test, Stopwatch?
// configProvider.GetConfig().PreWarmStateOnBlockProcessing = false;
await using IContainer container = new ContainerBuilder()
.AddModule(new TestNethermindModule(configProvider))
+ .AddModule(new TestMergeModule(configProvider))
.AddSingleton(specProvider)
.AddSingleton(_logManager)
.AddSingleton(rewardCalculator)
@@ -141,6 +140,7 @@ protected async Task RunTest(BlockchainTest test, Stopwatch?
IBlockchainProcessor blockchainProcessor = mainBlockProcessingContext.BlockchainProcessor;
IBlockTree blockTree = container.Resolve();
IBlockValidator blockValidator = container.Resolve();
+ IEngineRpcModule engineRpcModule = container.Resolve();
blockchainProcessor.Start();
BlockHeader parentHeader;
@@ -154,6 +154,7 @@ protected async Task RunTest(BlockchainTest test, Stopwatch?
test.GenesisRlp ??= Rlp.Encode(new Block(JsonToEthereumTest.Convert(test.GenesisBlockHeader)));
Block genesisBlock = Rlp.Decode(test.GenesisRlp.Bytes);
+ // Console.WriteLine(genesisBlock.ToString(Block.Format.Full));
Assert.That(genesisBlock.Header.Hash, Is.EqualTo(new Hash256(test.GenesisBlockHeader.Hash)));
ManualResetEvent genesisProcessed = new(false);
@@ -162,7 +163,7 @@ protected async Task RunTest(BlockchainTest test, Stopwatch?
{
if (args.Block.Number == 0)
{
- Assert.That(stateProvider.HasStateForBlock(genesisBlock.Header), Is.EqualTo(true));
+ Assert.That(stateProvider.HasStateForBlock(genesisBlock.Header), Is.True);
genesisProcessed.Set();
}
};
@@ -172,6 +173,76 @@ protected async Task RunTest(BlockchainTest test, Stopwatch?
parentHeader = genesisBlock.Header;
}
+ if (test.Blocks is not null)
+ {
+ // blockchain test
+ parentHeader = SuggestBlocks(test, failOnInvalidRlp, blockValidator, blockTree, parentHeader);
+ }
+ else if (test.EngineNewPayloads is not null)
+ {
+ (ExecutionPayload, string[]?, string[]?, string?)[] payloads = [.. JsonToEthereumTest.Convert(test.EngineNewPayloads)];
+
+ // blockchain test engine
+ foreach ((ExecutionPayload executionPayload, string[]? blobVersionedHashes, string[]? validationError, string? newPayloadVersion) in payloads)
+ {
+ ResultWrapper res;
+ byte[]?[] hashes = blobVersionedHashes is null ? null : [.. blobVersionedHashes.Select(x => Bytes.FromHexString(x))];
+
+ switch (newPayloadVersion ?? "5")
+ {
+ case "1":
+ res = await engineRpcModule.engine_newPayloadV1(executionPayload);
+ break;
+ case "2":
+ res = await engineRpcModule.engine_newPayloadV2(executionPayload);
+ break;
+ case "3":
+ res = await engineRpcModule.engine_newPayloadV3((ExecutionPayloadV3)executionPayload, [], executionPayload.ParentBeaconBlockRoot);
+ break;
+ case "4":
+ res = await engineRpcModule.engine_newPayloadV4((ExecutionPayloadV3)executionPayload, hashes, executionPayload.ParentBeaconBlockRoot, []);
+ break;
+ case "5":
+ res = await engineRpcModule.engine_newPayloadV4((ExecutionPayloadV3)executionPayload, hashes, executionPayload.ParentBeaconBlockRoot, []);
+ break;
+ default:
+ Assert.Fail("Invalid blockchain engine test, version not recognised.");
+ break;
+ }
+ }
+ }
+ else
+ {
+ Assert.Fail("Invalid blockchain test, did not contain blocks or new payloads.");
+ }
+
+ await blockchainProcessor.StopAsync(true);
+ stopwatch?.Stop();
+
+ IBlockCachePreWarmer? preWarmer = container.Resolve().LifetimeScope.ResolveOptional();
+
+ // Caches are cleared async, which is a problem as read for the MainWorldState with prewarmer is not correct if its not cleared.
+ preWarmer?.ClearCaches();
+
+ Block? headBlock = blockTree.RetrieveHeadBlock();
+ List differences;
+ using (stateProvider.BeginScope(headBlock.Header))
+ {
+ differences = RunAssertions(test, blockTree.RetrieveHeadBlock(), stateProvider);
+ }
+
+ Assert.That(differences, Is.Empty, "differences");
+
+ return new EthereumTestResult
+ (
+ test.Name,
+ null,
+ differences.Count == 0
+ );
+ }
+
+ private static BlockHeader SuggestBlocks(BlockchainTest test, bool failOnInvalidRlp, IBlockValidator blockValidator, IBlockTree blockTree, BlockHeader parentHeader)
+ {
List<(Block Block, string ExpectedException)> correctRlp = DecodeRlps(test, failOnInvalidRlp);
for (int i = 0; i < correctRlp.Count; i++)
{
@@ -211,43 +282,22 @@ protected async Task RunTest(BlockchainTest test, Stopwatch?
parentHeader = correctRlp[i].Block.Header;
}
- await blockchainProcessor.StopAsync(true);
- stopwatch?.Stop();
-
- IBlockCachePreWarmer? preWarmer = container.Resolve().LifetimeScope.ResolveOptional();
- if (preWarmer is not null)
- {
- // Caches are cleared async, which is a problem as read for the MainWorldState with prewarmer is not correct if its not cleared.
- preWarmer.ClearCaches();
- }
-
- Block? headBlock = blockTree.RetrieveHeadBlock();
- List differences;
- using (stateProvider.BeginScope(headBlock.Header))
- {
- differences = RunAssertions(test, blockTree.RetrieveHeadBlock(), stateProvider);
- }
-
- Assert.That(differences.Count, Is.Zero, "differences");
-
- return new EthereumTestResult
- (
- test.Name,
- null,
- differences.Count == 0
- );
+ return parentHeader;
}
- private List<(Block Block, string ExpectedException)> DecodeRlps(BlockchainTest test, bool failOnInvalidRlp)
+ private static List<(Block Block, string ExpectedException)> DecodeRlps(BlockchainTest test, bool failOnInvalidRlp)
{
- List<(Block Block, string ExpectedException)> correctRlp = new();
+ List<(Block Block, string ExpectedException)> correctRlp = [];
for (int i = 0; i < test.Blocks.Length; i++)
{
TestBlockJson testBlockJson = test.Blocks[i];
try
{
- var rlpContext = Bytes.FromHexString(testBlockJson.Rlp).AsRlpStream();
+ RlpStream rlpContext = Bytes.FromHexString(testBlockJson.Rlp).AsRlpStream();
Block suggestedBlock = Rlp.Decode(rlpContext);
+ // Console.WriteLine("suggested block:");
+ // Console.WriteLine(suggestedBlock.BlockAccessList);
+ // Hash256 tmp = new(ValueKeccak.Compute(Rlp.Encode(suggestedBlock.BlockAccessList!.Value).Bytes).Bytes);
suggestedBlock.Header.SealEngineType =
test.SealEngineUsed ? SealEngineType.Ethash : SealEngineType.None;
@@ -288,17 +338,20 @@ protected async Task RunTest(BlockchainTest test, Stopwatch?
if (correctRlp.Count == 0)
{
- Assert.That(test.GenesisBlockHeader, Is.Not.Null);
- Assert.That(test.LastBlockHash, Is.EqualTo(new Hash256(test.GenesisBlockHeader.Hash)));
+ using (Assert.EnterMultipleScope())
+ {
+ Assert.That(test.GenesisBlockHeader, Is.Not.Null);
+ Assert.That(test.LastBlockHash, Is.EqualTo(new Hash256(test.GenesisBlockHeader.Hash)));
+ }
}
return correctRlp;
}
- private void InitializeTestState(BlockchainTest test, IWorldState stateProvider, ISpecProvider specProvider)
+ private static void InitializeTestState(BlockchainTest test, IWorldState stateProvider, ISpecProvider specProvider)
{
foreach (KeyValuePair accountState in
- ((IEnumerable>)test.Pre ?? Array.Empty>()))
+ (IEnumerable>)test.Pre ?? Array.Empty>())
{
foreach (KeyValuePair storageItem in accountState.Value.Storage)
{
@@ -316,18 +369,18 @@ private void InitializeTestState(BlockchainTest test, IWorldState stateProvider,
stateProvider.Reset();
}
- private List RunAssertions(BlockchainTest test, Block headBlock, IWorldState stateProvider)
+ private static List RunAssertions(BlockchainTest test, Block headBlock, IWorldState stateProvider)
{
if (test.PostStateRoot is not null)
{
- return test.PostStateRoot != stateProvider.StateRoot ? new List { "state root mismatch" } : Enumerable.Empty().ToList();
+ return test.PostStateRoot != stateProvider.StateRoot ? ["state root mismatch"] : [];
}
TestBlockHeaderJson testHeaderJson = (test.Blocks?
.Where(b => b.BlockHeader is not null)
.SingleOrDefault(b => new Hash256(b.BlockHeader.Hash) == headBlock.Hash)?.BlockHeader) ?? test.GenesisBlockHeader;
BlockHeader testHeader = JsonToEthereumTest.Convert(testHeaderJson);
- List differences = new();
+ List differences = [];
IEnumerable> deletedAccounts = test.Pre?
.Where(pre => !(test.PostState?.ContainsKey(pre.Key) ?? false)) ?? Array.Empty>();
@@ -351,8 +404,8 @@ private List RunAssertions(BlockchainTest test, Block headBlock, IWorldS
}
bool accountExists = stateProvider.AccountExists(acountAddress);
- UInt256? balance = accountExists ? stateProvider.GetBalance(acountAddress) : (UInt256?)null;
- UInt256? nonce = accountExists ? stateProvider.GetNonce(acountAddress) : (UInt256?)null;
+ UInt256? balance = accountExists ? stateProvider.GetBalance(acountAddress) : null;
+ UInt256? nonce = accountExists ? stateProvider.GetNonce(acountAddress) : null;
if (accountState.Balance != balance)
{
@@ -364,7 +417,7 @@ private List RunAssertions(BlockchainTest test, Block headBlock, IWorldS
differences.Add($"{acountAddress} nonce exp: {accountState.Nonce}, actual: {nonce}");
}
- byte[] code = accountExists ? stateProvider.GetCode(acountAddress) : new byte[0];
+ byte[] code = accountExists ? stateProvider.GetCode(acountAddress) : [];
if (!Bytes.AreEqual(accountState.Code, code))
{
differences.Add($"{acountAddress} code exp: {accountState.Code?.Length}, actual: {code?.Length}");
@@ -377,10 +430,10 @@ private List RunAssertions(BlockchainTest test, Block headBlock, IWorldS
differencesBefore = differences.Count;
- KeyValuePair[] clearedStorages = new KeyValuePair[0];
+ KeyValuePair[] clearedStorages = [];
if (test.Pre.ContainsKey(acountAddress))
{
- clearedStorages = test.Pre[acountAddress].Storage.Where(s => !accountState.Storage.ContainsKey(s.Key)).ToArray();
+ clearedStorages = [.. test.Pre[acountAddress].Storage.Where(s => !accountState.Storage.ContainsKey(s.Key))];
}
foreach (KeyValuePair clearedStorage in clearedStorages)
diff --git a/src/Nethermind/Ethereum.Test.Base/BlockchainTestJson.cs b/src/Nethermind/Ethereum.Test.Base/BlockchainTestJson.cs
index fdd03858fd8..c344cd773da 100644
--- a/src/Nethermind/Ethereum.Test.Base/BlockchainTestJson.cs
+++ b/src/Nethermind/Ethereum.Test.Base/BlockchainTestJson.cs
@@ -24,6 +24,7 @@ public class BlockchainTestJson
public TestBlockJson[]? Blocks { get; set; }
public TestBlockHeaderJson? GenesisBlockHeader { get; set; }
+ public TestEngineNewPayloadsJson[]? EngineNewPayloads { get; set; }
public Dictionary? Pre { get; set; }
public Dictionary? PostState { get; set; }
diff --git a/src/Nethermind/Ethereum.Test.Base/JsonToEthereumTest.cs b/src/Nethermind/Ethereum.Test.Base/JsonToEthereumTest.cs
index 9a09b17017c..432156d5dcc 100644
--- a/src/Nethermind/Ethereum.Test.Base/JsonToEthereumTest.cs
+++ b/src/Nethermind/Ethereum.Test.Base/JsonToEthereumTest.cs
@@ -5,6 +5,7 @@
using System.Collections.Generic;
using System.IO;
using System.Linq;
+using System.Text.Json;
using Nethermind.Config;
using Nethermind.Core;
using Nethermind.Core.Crypto;
@@ -14,6 +15,7 @@
using Nethermind.Crypto;
using Nethermind.Evm.EvmObjectFormat;
using Nethermind.Int256;
+using Nethermind.Merge.Plugin.Data;
using Nethermind.Serialization.Json;
using Nethermind.Serialization.Rlp;
using Nethermind.Specs;
@@ -56,36 +58,84 @@ public static BlockHeader Convert(TestBlockHeaderJson? headerJson)
(long)Bytes.FromHexString(headerJson.Number).ToUInt256(),
(long)Bytes.FromHexString(headerJson.GasLimit).ToUnsignedBigInteger(),
(ulong)Bytes.FromHexString(headerJson.Timestamp).ToUnsignedBigInteger(),
- Bytes.FromHexString(headerJson.ExtraData)
- );
-
- header.Bloom = new Bloom(Bytes.FromHexString(headerJson.Bloom));
- header.GasUsed = (long)Bytes.FromHexString(headerJson.GasUsed).ToUnsignedBigInteger();
- header.Hash = new Hash256(headerJson.Hash);
- header.MixHash = new Hash256(headerJson.MixHash);
- header.Nonce = (ulong)Bytes.FromHexString(headerJson.Nonce).ToUnsignedBigInteger();
- header.ReceiptsRoot = new Hash256(headerJson.ReceiptTrie);
- header.StateRoot = new Hash256(headerJson.StateRoot);
- header.TxRoot = new Hash256(headerJson.TransactionsTrie);
+ Bytes.FromHexString(headerJson.ExtraData),
+ headerJson.BlobGasUsed is null ? null : (ulong)Bytes.FromHexString(headerJson.BlobGasUsed).ToUnsignedBigInteger(),
+ headerJson.ExcessBlobGas is null ? null : (ulong)Bytes.FromHexString(headerJson.ExcessBlobGas).ToUnsignedBigInteger(),
+ headerJson.ParentBeaconBlockRoot is null ? null : new Hash256(headerJson.ParentBeaconBlockRoot),
+ headerJson.RequestsHash is null ? null : new Hash256(headerJson.RequestsHash)
+ )
+ {
+ Bloom = new Bloom(Bytes.FromHexString(headerJson.Bloom)),
+ GasUsed = (long)Bytes.FromHexString(headerJson.GasUsed).ToUnsignedBigInteger(),
+ Hash = new Hash256(headerJson.Hash),
+ MixHash = new Hash256(headerJson.MixHash),
+ Nonce = (ulong)Bytes.FromHexString(headerJson.Nonce).ToUnsignedBigInteger(),
+ ReceiptsRoot = new Hash256(headerJson.ReceiptTrie),
+ StateRoot = new Hash256(headerJson.StateRoot),
+ TxRoot = new Hash256(headerJson.TransactionsTrie),
+ WithdrawalsRoot = headerJson.WithdrawalsRoot is null ? null : new Hash256(headerJson.WithdrawalsRoot),
+ BlockAccessListHash = headerJson.BlockAccessListHash is null ? null : new Hash256(headerJson.BlockAccessListHash),
+ BaseFeePerGas = (ulong)Bytes.FromHexString(headerJson.BaseFeePerGas).ToUnsignedBigInteger()
+ };
return header;
}
+ public static IEnumerable<(ExecutionPayload, string[]?, string[]?, string?)> Convert(TestEngineNewPayloadsJson[]? executionPayloadsJson)
+ {
+ if (executionPayloadsJson is null)
+ {
+ throw new InvalidDataException("Execution payloads JSON was null when constructing test.");
+ }
+
+ foreach (TestEngineNewPayloadsJson engineNewPayload in executionPayloadsJson)
+ {
+ TestEngineNewPayloadsJson.ParamsExecutionPayload executionPayload = engineNewPayload.Params[0].Deserialize(EthereumJsonSerializer.JsonOptions);
+ string[]? blobVersionedHashes = engineNewPayload.Params.Length > 1 ? engineNewPayload.Params[1].Deserialize(EthereumJsonSerializer.JsonOptions) : null;
+ string? parentBeaconBlockRoot = engineNewPayload.Params.Length > 2 ? engineNewPayload.Params[2].Deserialize(EthereumJsonSerializer.JsonOptions) : null;
+ string[]? validationError = engineNewPayload.Params.Length > 3 ? engineNewPayload.Params[3].Deserialize(EthereumJsonSerializer.JsonOptions) : null;
+ yield return (new ExecutionPayloadV3()
+ {
+ BaseFeePerGas = (ulong)Bytes.FromHexString(executionPayload.BaseFeePerGas).ToUnsignedBigInteger(),
+ BlockHash = new(executionPayload.BlockHash),
+ BlockNumber = (long)Bytes.FromHexString(executionPayload.BlockNumber).ToUnsignedBigInteger(),
+ ExtraData = Bytes.FromHexString(executionPayload.ExtraData),
+ FeeRecipient = new(executionPayload.FeeRecipient),
+ GasLimit = (long)Bytes.FromHexString(executionPayload.GasLimit).ToUnsignedBigInteger(),
+ GasUsed = (long)Bytes.FromHexString(executionPayload.GasUsed).ToUnsignedBigInteger(),
+ LogsBloom = new(Bytes.FromHexString(executionPayload.LogsBloom)),
+ ParentHash = new(executionPayload.ParentHash),
+ PrevRandao = new(executionPayload.PrevRandao),
+ ReceiptsRoot = new(executionPayload.ReceiptsRoot),
+ StateRoot = new(executionPayload.StateRoot),
+ Timestamp = (ulong)Bytes.FromHexString(executionPayload.Timestamp).ToUnsignedBigInteger(),
+ BlockAccessList = executionPayload.BlockAccessList is null ? null : Bytes.FromHexString(executionPayload.BlockAccessList),
+ BlobGasUsed = executionPayload.BlobGasUsed is null ? null : (ulong)Bytes.FromHexString(executionPayload.BlobGasUsed).ToUnsignedBigInteger(),
+ ExcessBlobGas = executionPayload.ExcessBlobGas is null ? null : (ulong)Bytes.FromHexString(executionPayload.ExcessBlobGas).ToUnsignedBigInteger(),
+ ParentBeaconBlockRoot = parentBeaconBlockRoot is null ? null : new(parentBeaconBlockRoot),
+ Withdrawals = executionPayload.Withdrawals is null ? null : [.. executionPayload.Withdrawals.Select(x => Rlp.Decode(Bytes.FromHexString(x)))],
+ Transactions = [.. executionPayload.Transactions.Select(x => Bytes.FromHexString(x))],
+ ExecutionRequests = []
+ }, blobVersionedHashes, validationError, engineNewPayload.NewPayloadVersion);
+ }
+ }
+
public static Transaction Convert(PostStateJson postStateJson, TransactionJson transactionJson)
{
- Transaction transaction = new();
-
- transaction.Type = transactionJson.Type;
- transaction.Value = transactionJson.Value[postStateJson.Indexes.Value];
- transaction.GasLimit = transactionJson.GasLimit[postStateJson.Indexes.Gas];
- transaction.GasPrice = transactionJson.GasPrice ?? transactionJson.MaxPriorityFeePerGas ?? 0;
- transaction.DecodedMaxFeePerGas = transactionJson.MaxFeePerGas ?? 0;
- transaction.Nonce = transactionJson.Nonce;
- transaction.To = transactionJson.To;
- transaction.Data = transactionJson.Data[postStateJson.Indexes.Data];
- transaction.SenderAddress = new PrivateKey(transactionJson.SecretKey).Address;
- transaction.Signature = new Signature(1, 1, 27);
- transaction.BlobVersionedHashes = transactionJson.BlobVersionedHashes;
- transaction.MaxFeePerBlobGas = transactionJson.MaxFeePerBlobGas;
+ Transaction transaction = new()
+ {
+ Type = transactionJson.Type,
+ Value = transactionJson.Value[postStateJson.Indexes.Value],
+ GasLimit = transactionJson.GasLimit[postStateJson.Indexes.Gas],
+ GasPrice = transactionJson.GasPrice ?? transactionJson.MaxPriorityFeePerGas ?? 0,
+ DecodedMaxFeePerGas = transactionJson.MaxFeePerGas ?? 0,
+ Nonce = transactionJson.Nonce,
+ To = transactionJson.To,
+ Data = transactionJson.Data[postStateJson.Indexes.Data],
+ SenderAddress = new PrivateKey(transactionJson.SecretKey).Address,
+ Signature = new Signature(1, 1, 27),
+ BlobVersionedHashes = transactionJson.BlobVersionedHashes,
+ MaxFeePerBlobGas = transactionJson.MaxFeePerBlobGas
+ };
transaction.Hash = transaction.CalculateHash();
AccessList.Builder builder = new();
@@ -108,7 +158,7 @@ public static Transaction Convert(PostStateJson postStateJson, TransactionJson t
if (transactionJson.AuthorizationList is not null)
{
transaction.AuthorizationList =
- transactionJson.AuthorizationList
+ [.. transactionJson.AuthorizationList
.Select(i =>
{
if (i.Nonce > ulong.MaxValue)
@@ -148,7 +198,7 @@ public static Transaction Convert(PostStateJson postStateJson, TransactionJson t
(byte)i.V,
r,
s);
- }).ToArray();
+ })];
if (transaction.AuthorizationList.Any())
{
transaction.Type = TxType.SetCode;
@@ -172,14 +222,16 @@ public static void ProcessAccessList(AccessListItemJson[]? accessList, AccessLis
public static Transaction Convert(LegacyTransactionJson transactionJson)
{
- Transaction transaction = new();
- transaction.Value = transactionJson.Value;
- transaction.GasLimit = transactionJson.GasLimit;
- transaction.GasPrice = transactionJson.GasPrice;
- transaction.Nonce = transactionJson.Nonce;
- transaction.To = transactionJson.To;
- transaction.Data = transactionJson.Data;
- transaction.Signature = new Signature(transactionJson.R, transactionJson.S, transactionJson.V);
+ Transaction transaction = new()
+ {
+ Value = transactionJson.Value,
+ GasLimit = transactionJson.GasLimit,
+ GasPrice = transactionJson.GasPrice,
+ Nonce = transactionJson.Nonce,
+ To = transactionJson.To,
+ Data = transactionJson.Data,
+ Signature = new Signature(transactionJson.R, transactionJson.S, transactionJson.V)
+ };
transaction.Hash = transaction.CalculateHash();
return transaction;
}
@@ -191,41 +243,42 @@ public static IEnumerable Convert(string name, string category
return Enumerable.Repeat(new GeneralStateTest { Name = name, Category = category, LoadFailure = testJson.LoadFailure }, 1);
}
- List blockchainTests = new();
+ List blockchainTests = [];
foreach (KeyValuePair postStateBySpec in testJson.Post)
{
int iterationNumber = 0;
foreach (PostStateJson stateJson in postStateBySpec.Value)
{
- GeneralStateTest test = new();
- test.Name = Path.GetFileName(name) +
- $"_d{stateJson.Indexes.Data}g{stateJson.Indexes.Gas}v{stateJson.Indexes.Value}_";
+ GeneralStateTest test = new()
+ {
+ Name = Path.GetFileName(name) +
+ $"_d{stateJson.Indexes.Data}g{stateJson.Indexes.Gas}v{stateJson.Indexes.Value}_",
+ Category = category,
+ ForkName = postStateBySpec.Key,
+ Fork = SpecNameParser.Parse(postStateBySpec.Key),
+ PreviousHash = testJson.Env.PreviousHash,
+ CurrentCoinbase = testJson.Env.CurrentCoinbase,
+ CurrentDifficulty = testJson.Env.CurrentDifficulty,
+ CurrentGasLimit = testJson.Env.CurrentGasLimit,
+ CurrentNumber = testJson.Env.CurrentNumber,
+ CurrentTimestamp = testJson.Env.CurrentTimestamp,
+ CurrentBaseFee = testJson.Env.CurrentBaseFee,
+ CurrentRandom = testJson.Env.CurrentRandom,
+ CurrentBeaconRoot = testJson.Env.CurrentBeaconRoot,
+ CurrentWithdrawalsRoot = testJson.Env.CurrentWithdrawalsRoot,
+ CurrentExcessBlobGas = testJson.Env.CurrentExcessBlobGas,
+ ParentBlobGasUsed = testJson.Env.ParentBlobGasUsed,
+ ParentExcessBlobGas = testJson.Env.ParentExcessBlobGas,
+ PostReceiptsRoot = stateJson.Logs,
+ PostHash = stateJson.Hash,
+ Pre = testJson.Pre.ToDictionary(p => p.Key, p => p.Value),
+ Transaction = Convert(stateJson, testJson.Transaction)
+ };
+
if (testJson.Info?.Labels?.ContainsKey(iterationNumber.ToString()) ?? false)
{
test.Name += testJson.Info?.Labels?[iterationNumber.ToString()]?.Replace(":label ", string.Empty);
}
- test.Category = category;
-
- test.ForkName = postStateBySpec.Key;
- test.Fork = SpecNameParser.Parse(postStateBySpec.Key);
- test.PreviousHash = testJson.Env.PreviousHash;
- test.CurrentCoinbase = testJson.Env.CurrentCoinbase;
- test.CurrentDifficulty = testJson.Env.CurrentDifficulty;
- test.CurrentGasLimit = testJson.Env.CurrentGasLimit;
- test.CurrentNumber = testJson.Env.CurrentNumber;
- test.CurrentTimestamp = testJson.Env.CurrentTimestamp;
- test.CurrentBaseFee = testJson.Env.CurrentBaseFee;
- test.CurrentRandom = testJson.Env.CurrentRandom;
- test.CurrentBeaconRoot = testJson.Env.CurrentBeaconRoot;
- test.CurrentWithdrawalsRoot = testJson.Env.CurrentWithdrawalsRoot;
- test.CurrentExcessBlobGas = testJson.Env.CurrentExcessBlobGas;
- test.ParentBlobGasUsed = testJson.Env.ParentBlobGasUsed;
- test.ParentExcessBlobGas = testJson.Env.ParentExcessBlobGas;
- test.PostReceiptsRoot = stateJson.Logs;
- test.PostHash = stateJson.Hash;
- test.Pre = testJson.Pre.ToDictionary(p => p.Key, p => p.Value);
- test.Transaction = Convert(stateJson, testJson.Transaction);
-
blockchainTests.Add(test);
++iterationNumber;
}
@@ -241,17 +294,20 @@ public static BlockchainTest Convert(string name, string category, BlockchainTes
return new BlockchainTest { Name = name, Category = category, LoadFailure = testJson.LoadFailure };
}
- BlockchainTest test = new();
- test.Name = name;
- test.Category = category;
- test.Network = testJson.EthereumNetwork;
- test.NetworkAfterTransition = testJson.EthereumNetworkAfterTransition;
- test.TransitionForkActivation = testJson.TransitionForkActivation;
- test.LastBlockHash = new Hash256(testJson.LastBlockHash);
- test.GenesisRlp = testJson.GenesisRlp is null ? null : new Rlp(Bytes.FromHexString(testJson.GenesisRlp));
- test.GenesisBlockHeader = testJson.GenesisBlockHeader;
- test.Blocks = testJson.Blocks;
- test.Pre = testJson.Pre.ToDictionary(p => p.Key, p => p.Value);
+ BlockchainTest test = new()
+ {
+ Name = name,
+ Category = category,
+ Network = testJson.EthereumNetwork,
+ NetworkAfterTransition = testJson.EthereumNetworkAfterTransition,
+ TransitionForkActivation = testJson.TransitionForkActivation,
+ LastBlockHash = new Hash256(testJson.LastBlockHash),
+ GenesisRlp = testJson.GenesisRlp is null ? null : new Rlp(Bytes.FromHexString(testJson.GenesisRlp)),
+ GenesisBlockHeader = testJson.GenesisBlockHeader,
+ Blocks = testJson.Blocks,
+ EngineNewPayloads = testJson.EngineNewPayloads,
+ Pre = testJson.Pre.ToDictionary(p => p.Key, p => p.Value)
+ };
HalfBlockchainTestJson half = testJson as HalfBlockchainTestJson;
if (half is not null)
@@ -272,7 +328,7 @@ public static BlockchainTest Convert(string name, string category, BlockchainTes
public static IEnumerable ConvertToEofTests(string json)
{
Dictionary testsInFile = _serializer.Deserialize>(json);
- List tests = new();
+ List tests = [];
foreach (KeyValuePair namedTest in testsInFile)
{
(string name, string category) = GetNameAndCategory(namedTest.Key);
@@ -281,11 +337,13 @@ public static IEnumerable ConvertToEofTests(string json)
foreach (KeyValuePair pair in namedTest.Value.Vectors)
{
VectorTestJson vectorJson = pair.Value;
- VectorTest vector = new();
- vector.Code = Bytes.FromHexString(vectorJson.Code);
- vector.ContainerKind = ParseContainerKind(vectorJson.ContainerKind);
+ VectorTest vector = new()
+ {
+ Code = Bytes.FromHexString(vectorJson.Code),
+ ContainerKind = ParseContainerKind(vectorJson.ContainerKind)
+ };
- foreach (var result in vectorJson.Results)
+ foreach (KeyValuePair result in vectorJson.Results)
{
EofTest test = new()
{
@@ -293,10 +351,10 @@ public static IEnumerable ConvertToEofTests(string json)
Category = $"{category} [{result.Key}]",
Url = url,
Description = description,
- Spec = spec
+ Spec = spec,
+ Vector = vector,
+ Result = result.ToTestResult()
};
- test.Vector = vector;
- test.Result = result.ToTestResult();
tests.Add(test);
}
}
@@ -305,7 +363,7 @@ public static IEnumerable ConvertToEofTests(string json)
return tests;
static ValidationStrategy ParseContainerKind(string containerKind)
- => ("INITCODE".Equals(containerKind) ? ValidationStrategy.ValidateInitCodeMode : ValidationStrategy.ValidateRuntimeMode);
+ => "INITCODE".Equals(containerKind) ? ValidationStrategy.ValidateInitCodeMode : ValidationStrategy.ValidateRuntimeMode;
static void GetTestMetaData(KeyValuePair namedTest, out string? description, out string? url, out string? spec)
{
@@ -332,7 +390,7 @@ public static IEnumerable ConvertStateTest(string json)
Dictionary testsInFile =
_serializer.Deserialize>(json);
- List tests = new();
+ List tests = [];
foreach (KeyValuePair namedTest in testsInFile)
{
(string name, string category) = GetNameAndCategory(namedTest.Key);
@@ -349,17 +407,19 @@ public static IEnumerable ConvertToBlockchainTests(string json)
{
testsInFile = _serializer.Deserialize>(json);
}
- catch (Exception)
+ catch (Exception e)
{
- var half = _serializer.Deserialize>(json);
- testsInFile = new Dictionary();
+ Console.WriteLine(e);
+ Dictionary half =
+ _serializer.Deserialize>(json);
+ testsInFile = [];
foreach (KeyValuePair pair in half)
{
testsInFile[pair.Key] = pair.Value;
}
}
- List testsByName = new();
+ List testsByName = [];
foreach ((string testName, BlockchainTestJson testSpec) in testsInFile)
{
string[] transitionInfo = testSpec.Network.Split("At");
diff --git a/src/Nethermind/Ethereum.Test.Base/TestBlockHeaderJson.cs b/src/Nethermind/Ethereum.Test.Base/TestBlockHeaderJson.cs
index 3508dba38c6..39c11de9c42 100644
--- a/src/Nethermind/Ethereum.Test.Base/TestBlockHeaderJson.cs
+++ b/src/Nethermind/Ethereum.Test.Base/TestBlockHeaderJson.cs
@@ -21,5 +21,12 @@ public class TestBlockHeaderJson
public string Timestamp { get; set; }
public string TransactionsTrie { get; set; }
public string UncleHash { get; set; }
+ public string? WithdrawalsRoot { get; set; }
+ public string? ParentBeaconBlockRoot { get; set; }
+ public string? RequestsHash { get; set; }
+ public string? BlockAccessListHash { get; set; }
+ public string? BlobGasUsed { get; set; }
+ public string? ExcessBlobGas { get; set; }
+ public string? BaseFeePerGas { get; set; }
}
}
diff --git a/src/Nethermind/Ethereum.Test.Base/TestEngineNewPayloadsJson.cs b/src/Nethermind/Ethereum.Test.Base/TestEngineNewPayloadsJson.cs
new file mode 100644
index 00000000000..a293b5175ac
--- /dev/null
+++ b/src/Nethermind/Ethereum.Test.Base/TestEngineNewPayloadsJson.cs
@@ -0,0 +1,45 @@
+// SPDX-FileCopyrightText: 2025 Demerzel Solutions Limited
+// SPDX-License-Identifier: LGPL-3.0-only
+
+using System.Text.Json;
+
+namespace Ethereum.Test.Base
+{
+ public class TestEngineNewPayloadsJson
+ {
+ public JsonElement[] Params { get; set; }
+ // public EngineNewPayloadParams Params { get; set; }
+ public string? NewPayloadVersion { get; set; }
+ public string? ForkChoiceUpdatedVersion { get; set; }
+
+ // public class EngineNewPayloadParams
+ // {
+ // public ParamsExecutionPayload ExecutionPayload;
+ // public string[] BlobVersionedHashes;
+ // public string ParentBeaconBlockRoot;
+ // public string ValidationError;
+ // }
+
+ public class ParamsExecutionPayload
+ {
+ public string ParentHash { get; set; }
+ public string FeeRecipient { get; set; }
+ public string StateRoot { get; set; }
+ public string ReceiptsRoot { get; set; }
+ public string LogsBloom { get; set; }
+ public string BlockNumber { get; set; }
+ public string GasLimit { get; set; }
+ public string GasUsed { get; set; }
+ public string Timestamp { get; set; }
+ public string ExtraData { get; set; }
+ public string PrevRandao { get; set; }
+ public string BaseFeePerGas { get; set; }
+ public string BlobGasUsed { get; set; }
+ public string ExcessBlobGas { get; set; }
+ public string BlockHash { get; set; }
+ public string[] Transactions { get; set; }
+ public string[]? Withdrawals { get; set; }
+ public string? BlockAccessList { get; set; }
+ }
+ }
+}
diff --git a/src/Nethermind/Nethermind.Blockchain.Test/BeaconBlockRootHandlerTests.cs b/src/Nethermind/Nethermind.Blockchain.Test/BeaconBlockRootHandlerTests.cs
index a82668f7d74..7288c0d85ac 100644
--- a/src/Nethermind/Nethermind.Blockchain.Test/BeaconBlockRootHandlerTests.cs
+++ b/src/Nethermind/Nethermind.Blockchain.Test/BeaconBlockRootHandlerTests.cs
@@ -5,10 +5,8 @@
using Nethermind.Core;
using Nethermind.Core.Crypto;
using Nethermind.Core.Eip2930;
-using Nethermind.Core.Specs;
using Nethermind.Core.Test.Builders;
using Nethermind.Crypto;
-using Nethermind.Evm;
using Nethermind.Evm.Tracing;
using Nethermind.Evm.TransactionProcessing;
using Nethermind.Int256;
diff --git a/src/Nethermind/Nethermind.Blockchain.Test/BlockAccessListTests.cs b/src/Nethermind/Nethermind.Blockchain.Test/BlockAccessListTests.cs
new file mode 100644
index 00000000000..29e04adfcd0
--- /dev/null
+++ b/src/Nethermind/Nethermind.Blockchain.Test/BlockAccessListTests.cs
@@ -0,0 +1,654 @@
+// SPDX-FileCopyrightText: 2025 Demerzel Solutions Limited
+// SPDX-License-Identifier: LGPL-3.0-only
+
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Numerics;
+using System.Threading;
+using System.Threading.Tasks;
+using Autofac;
+using Nethermind.Blockchain.Tracing;
+using Nethermind.Consensus.Processing;
+using Nethermind.Core;
+using Nethermind.Core.BlockAccessLists;
+using Nethermind.Core.Crypto;
+using Nethermind.Core.Extensions;
+using Nethermind.Core.Specs;
+using Nethermind.Core.Test;
+using Nethermind.Core.Test.Blockchain;
+using Nethermind.Core.Test.Builders;
+using Nethermind.Evm.State;
+using Nethermind.Int256;
+using Nethermind.Serialization.Rlp;
+using Nethermind.Serialization.Rlp.Eip7928;
+using Nethermind.Specs;
+using Nethermind.Specs.Forks;
+using Nethermind.Specs.Test;
+using Nethermind.State;
+using NUnit.Framework;
+
+//move all to correct folder
+namespace Nethermind.Evm.Test;
+
+[TestFixture]
+public class BlockAccessListTests()
+{
+ private static readonly OverridableReleaseSpec _spec = new(Prague.Instance)
+ {
+ IsEip7928Enabled = true
+ };
+
+ private static readonly ISpecProvider _specProvider = new TestSpecProvider(_spec);
+ private static readonly UInt256 _accountBalance = 10.Ether();
+
+ // todo: move to RLP tests?
+ [TestCaseSource(nameof(BlockAccessListTestSource))]
+ public void Can_decode_then_encode(string rlp, BlockAccessList expected)
+ {
+ BlockAccessList bal = Rlp.Decode(Bytes.FromHexString(rlp).AsRlpStream());
+
+ // Console.WriteLine(bal);
+ // Console.WriteLine(expected);
+ Assert.That(bal, Is.EqualTo(expected));
+
+ string encoded = "0x" + Bytes.ToHexString(Rlp.Encode(bal).Bytes);
+ Console.WriteLine(encoded);
+ Console.WriteLine(rlp);
+ Assert.That(encoded, Is.EqualTo(rlp));
+ }
+
+ [Test]
+ public void Can_decode_then_encode_balance_change()
+ {
+ const string rlp = "0xc801861319718811c8";
+ Rlp.ValueDecoderContext ctx = new(Bytes.FromHexString(rlp));
+ BalanceChange balanceChange = BalanceChangeDecoder.Instance.Decode(ref ctx, RlpBehaviors.None);
+ BalanceChange expected = new(1, 0x1319718811c8);
+ Assert.That(balanceChange, Is.EqualTo(expected));
+
+ string encoded = "0x" + Bytes.ToHexString(Rlp.Encode(balanceChange).Bytes);
+ Console.WriteLine(encoded);
+ Console.WriteLine(rlp);
+ Assert.That(encoded, Is.EqualTo(rlp));
+ }
+
+ [Test]
+ public void Can_decode_then_encode_nonce_change()
+ {
+ const string rlp = "0xc20101";
+ Rlp.ValueDecoderContext ctx = new(Bytes.FromHexString(rlp));
+ NonceChange nonceChange = NonceChangeDecoder.Instance.Decode(ref ctx, RlpBehaviors.None);
+ NonceChange expected = new(1, 1);
+ Assert.That(nonceChange, Is.EqualTo(expected));
+
+ string encoded = "0x" + Bytes.ToHexString(Rlp.Encode(nonceChange).Bytes);
+ Console.WriteLine(encoded);
+ Console.WriteLine(rlp);
+ Assert.That(encoded, Is.EqualTo(rlp));
+ }
+
+ [Test]
+ public void Can_decode_then_encode_slot_change()
+ {
+ byte[] slot0 = ToStorageSlot(0);
+ StorageChange parentHashStorageChange = new(0, Bytes32.Wrap(Bytes.FromHexString("0xc382836f81d7e4055a0e280268371e17cc69a531efe2abee082e9b922d6050fd")));
+ const string rlp = "0xf845a00000000000000000000000000000000000000000000000000000000000000000e3e280a0c382836f81d7e4055a0e280268371e17cc69a531efe2abee082e9b922d6050fd";
+
+ Rlp.ValueDecoderContext ctx = new(Bytes.FromHexString(rlp));
+ SlotChanges slotChange = SlotChangesDecoder.Instance.Decode(ref ctx, RlpBehaviors.None);
+ SlotChanges expected = new(slot0, [parentHashStorageChange]);
+ Assert.That(slotChange, Is.EqualTo(expected));
+
+ string encoded = "0x" + Bytes.ToHexString(Rlp.Encode(slotChange).Bytes);
+ Console.WriteLine(encoded);
+ Console.WriteLine(rlp);
+ Assert.That(encoded, Is.EqualTo(rlp));
+ }
+
+ [Test]
+ public void Can_decode_then_encode_storage_change()
+ {
+ const string rlp = "0xe280a0c382836f81d7e4055a0e280268371e17cc69a531efe2abee082e9b922d6050fd";
+
+ Rlp.ValueDecoderContext ctx = new(Bytes.FromHexString(rlp));
+ StorageChange storageChange = StorageChangeDecoder.Instance.Decode(ref ctx, RlpBehaviors.None);
+ StorageChange expected = new(0, Bytes32.Wrap(Bytes.FromHexString("0xc382836f81d7e4055a0e280268371e17cc69a531efe2abee082e9b922d6050fd")));
+ Assert.That(storageChange, Is.EqualTo(expected));
+
+ string encoded = "0x" + Bytes.ToHexString(Rlp.Encode(storageChange).Bytes);
+ Console.WriteLine(encoded);
+ Console.WriteLine(rlp);
+ Assert.That(encoded, Is.EqualTo(rlp));
+ }
+
+ [Test]
+ public void Can_decode_then_encode_code_change()
+ {
+ const string rlp = "0xc20100";
+
+ Rlp.ValueDecoderContext ctx = new(Bytes.FromHexString(rlp));
+ CodeChange codeChange = CodeChangeDecoder.Instance.Decode(ref ctx, RlpBehaviors.None);
+ CodeChange expected = new(1, [0x0]);
+ Assert.That(codeChange, Is.EqualTo(expected));
+
+ string encoded = "0x" + Bytes.ToHexString(Rlp.Encode(codeChange).Bytes);
+ Console.WriteLine(encoded);
+ Console.WriteLine(rlp);
+ Assert.That(encoded, Is.EqualTo(rlp));
+ }
+
+
+ [TestCaseSource(nameof(AccountChangesTestSource))]
+ public void Can_decode_then_encode_account_change(string rlp, AccountChanges expected)
+ {
+ Rlp.ValueDecoderContext ctx = new(Bytes.FromHexString(rlp));
+ AccountChanges accountChange = AccountChangesDecoder.Instance.Decode(ref ctx, RlpBehaviors.None);
+
+ Assert.That(accountChange, Is.EqualTo(expected));
+
+ string encoded = "0x" + Bytes.ToHexString(Rlp.Encode(accountChange).Bytes);
+ Console.WriteLine(encoded);
+ Console.WriteLine(rlp);
+ Assert.That(encoded, Is.EqualTo(rlp));
+ }
+
+ [Test]
+ public void Can_encode_then_decode()
+ {
+ StorageChange storageChange = new()
+ {
+ BlockAccessIndex = 10,
+ NewValue = new([.. Enumerable.Repeat(50, 32)])
+ };
+ byte[] storageChangeBytes = Rlp.Encode(storageChange, RlpBehaviors.None).Bytes;
+ StorageChange storageChangeDecoded = Rlp.Decode(storageChangeBytes, RlpBehaviors.None);
+ Assert.That(storageChange, Is.EqualTo(storageChangeDecoded));
+
+ SlotChanges slotChanges = new()
+ {
+ Slot = [.. Enumerable.Repeat(100, 32)],
+ Changes = [storageChange, storageChange]
+ };
+ byte[] slotChangesBytes = Rlp.Encode(slotChanges, RlpBehaviors.None).Bytes;
+ SlotChanges slotChangesDecoded = Rlp.Decode(slotChangesBytes, RlpBehaviors.None);
+ Assert.That(slotChanges, Is.EqualTo(slotChangesDecoded));
+
+ StorageRead storageRead = new(new Bytes32([.. Enumerable.Repeat(50, 32)]));
+ StorageRead storageRead2 = new(new Bytes32([.. Enumerable.Repeat(60, 32)]));
+ byte[] storageReadBytes = Rlp.Encode(storageRead, RlpBehaviors.None).Bytes;
+ StorageRead storageReadDecoded = Rlp.Decode(storageReadBytes, RlpBehaviors.None);
+ Assert.That(storageRead, Is.EqualTo(storageReadDecoded));
+
+ BalanceChange balanceChange = new()
+ {
+ BlockAccessIndex = 10,
+ PostBalance = 0
+ };
+ BalanceChange balanceChange2 = new()
+ {
+ BlockAccessIndex = 11,
+ PostBalance = 1
+ };
+ byte[] balanceChangeBytes = Rlp.Encode(balanceChange, RlpBehaviors.None).Bytes;
+ BalanceChange balanceChangeDecoded = Rlp.Decode(balanceChangeBytes, RlpBehaviors.None);
+ Assert.That(balanceChange, Is.EqualTo(balanceChangeDecoded));
+
+ NonceChange nonceChange = new()
+ {
+ BlockAccessIndex = 10,
+ NewNonce = 0
+ };
+ NonceChange nonceChange2 = new()
+ {
+ BlockAccessIndex = 11,
+ NewNonce = 0
+ };
+ byte[] nonceChangeBytes = Rlp.Encode(nonceChange, RlpBehaviors.None).Bytes;
+ NonceChange nonceChangeDecoded = Rlp.Decode(nonceChangeBytes, RlpBehaviors.None);
+ Assert.That(nonceChange, Is.EqualTo(nonceChangeDecoded));
+
+ CodeChange codeChange = new()
+ {
+ BlockAccessIndex = 10,
+ NewCode = [0, 50]
+ };
+ byte[] codeChangeBytes = Rlp.Encode(codeChange, RlpBehaviors.None).Bytes;
+ CodeChange codeChangeDecoded = Rlp.Decode(codeChangeBytes, RlpBehaviors.None);
+ Assert.That(codeChange, Is.EqualTo(codeChangeDecoded));
+
+ SortedDictionary storageChangesDict = new()
+ {
+ { slotChanges.Slot, slotChanges }
+ };
+
+ SortedList balanceChangesList = new()
+ {
+ { balanceChange.BlockAccessIndex, balanceChange },
+ { balanceChange2.BlockAccessIndex, balanceChange2 }
+ };
+
+ SortedList nonceChangesList = new()
+ {
+ { nonceChange.BlockAccessIndex, nonceChange },
+ { nonceChange2.BlockAccessIndex, nonceChange2 }
+ };
+
+ SortedList codeChangesList = new()
+ {
+ { codeChange.BlockAccessIndex, codeChange },
+ };
+
+ AccountChanges accountChanges = new()
+ {
+ Address = TestItem.AddressA,
+ StorageChanges = storageChangesDict,
+ StorageReads = [storageRead, storageRead2],
+ BalanceChanges = balanceChangesList,
+ NonceChanges = nonceChangesList,
+ CodeChanges = codeChangesList
+ };
+ byte[] accountChangesBytes = Rlp.Encode(accountChanges, RlpBehaviors.None).Bytes;
+ AccountChanges accountChangesDecoded = Rlp.Decode(accountChangesBytes, RlpBehaviors.None);
+ Assert.That(accountChanges, Is.EqualTo(accountChangesDecoded));
+
+ SortedDictionary accountChangesDict = new()
+ {
+ { accountChanges.Address, accountChanges }
+ };
+
+ BlockAccessList blockAccessList = new(accountChangesDict);
+ byte[] blockAccessListBytes = Rlp.Encode(blockAccessList, RlpBehaviors.None).Bytes;
+ BlockAccessList blockAccessListDecoded = Rlp.Decode(blockAccessListBytes, RlpBehaviors.None);
+ Assert.That(blockAccessList, Is.EqualTo(blockAccessListDecoded));
+ }
+
+ [Test]
+ public async Task Can_construct_BAL()
+ {
+ using BasicTestBlockchain testBlockchain = await BasicTestBlockchain.Create(BuildContainer());
+
+ IWorldState worldState = testBlockchain.WorldStateManager.GlobalWorldState;
+ using IDisposable _ = worldState.BeginScope(IWorldState.PreGenesis);
+ InitWorldState(worldState);
+
+ (worldState as TracedAccessWorldState)!.BlockAccessList = new();
+
+ const long gasUsed = 167340;
+ const long gasUsedBeforeFinal = 92100;
+ const ulong gasPrice = 2;
+ const long gasLimit = 100000;
+ const ulong timestamp = 1000000;
+ Hash256 parentHash = new("0xff483e972a04a9a62bb4b7d04ae403c615604e4090521ecc5bb7af67f71be09c");
+ // Hash256 parentHash = new("0x2971654f1af575a158b8541be71bea738a64d0c715c190e9c99ae5207c108d7d");
+
+ Transaction tx = Build.A.Transaction
+ .WithTo(TestItem.AddressB)
+ .WithSenderAddress(TestItem.AddressA)
+ .WithValue(0)
+ .WithGasPrice(gasPrice)
+ .WithGasLimit(gasLimit)
+ .TestObject;
+
+ Transaction tx2 = Build.A.Transaction
+ .WithTo(null)
+ .WithSenderAddress(TestItem.AddressA)
+ .WithValue(0)
+ .WithNonce(1)
+ .WithGasPrice(gasPrice)
+ .WithGasLimit(gasLimit)
+ .WithCode(Eip2935TestConstants.InitCode)
+ .TestObject;
+
+ /*
+ Store followed by revert should undo storage change
+ PUSH1 1
+ PUSH1 1
+ SSTORE
+ PUSH0
+ PUSH0
+ REVERT
+ */
+ byte[] code = Bytes.FromHexString("0x60016001555f5ffd");
+ Transaction tx3 = Build.A.Transaction
+ .WithTo(null)
+ .WithSenderAddress(TestItem.AddressA)
+ .WithValue(0)
+ .WithNonce(2)
+ .WithGasPrice(gasPrice)
+ .WithGasLimit(gasLimit)
+ .WithCode(code)
+ .TestObject;
+
+ BlockHeader header = Build.A.BlockHeader
+ .WithBaseFee(1)
+ .WithNumber(1)
+ .WithGasUsed(gasUsed)
+ .WithReceiptsRoot(new("0x3d4548dff4e45f6e7838b223bf9476cd5ba4fd05366e8cb4e6c9b65763209569"))
+ .WithStateRoot(new("0x9399acd9f2603778c11646f05f7827509b5319815da74b5721a07defb6285c8d"))
+ .WithBlobGasUsed(0)
+ .WithBeneficiary(TestItem.AddressC)
+ .WithParentBeaconBlockRoot(Hash256.Zero)
+ .WithRequestsHash(new("0xe3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"))
+ .WithTimestamp(timestamp)
+ .WithParentHash(parentHash)
+ // .WithTotalDifficulty(1000000000L)
+ .TestObject;
+
+ Withdrawal withdrawal = new()
+ {
+ Index = 0,
+ ValidatorIndex = 0,
+ Address = TestItem.AddressD,
+ AmountInGwei = 1
+ };
+
+ Block block = Build.A.Block
+ .WithTransactions([tx, tx2, tx3])
+ .WithBaseFeePerGas(1)
+ .WithWithdrawals([withdrawal])
+ .WithHeader(header).TestObject;
+
+ (Block processedBlock, TxReceipt[] _) = testBlockchain.BlockProcessor.ProcessOne(block, ProcessingOptions.None, NullBlockTracer.Instance, _spec, CancellationToken.None);
+ // Block processedBlock = testBlockchain.BlockchainProcessor.Process(block, ProcessingOptions.None, NullBlockTracer.Instance)!;
+ // Block[] res = testBlockchain.BranchProcessor.Process(header, [block], ProcessingOptions.None, NullBlockTracer.Instance, CancellationToken.None);
+ // Blockchain.AddBlockResult res = testBlockchain.BlockTree.SuggestBlock(block);
+ // testBlockchain.BlockTree.UpdateMainChain([block], true);
+ // Block processedBlock = res[0];
+ // Block processedBlock = Build.A.Block.TestObject;
+
+ // BlockAccessList blockAccessList = Rlp.Decode(processedBlock.BlockAccessList);
+ BlockAccessList blockAccessList = processedBlock.BlockAccessList!.Value;
+ Assert.That(blockAccessList.GetAccountChanges().Count, Is.EqualTo(10));
+
+ Address newContractAddress = ContractAddress.From(TestItem.AddressA, 1);
+ Address newContractAddress2 = ContractAddress.From(TestItem.AddressA, 2);
+
+ AccountChanges addressAChanges = blockAccessList.GetAccountChanges(TestItem.AddressA)!.Value;
+ AccountChanges addressBChanges = blockAccessList.GetAccountChanges(TestItem.AddressB)!.Value;
+ AccountChanges addressCChanges = blockAccessList.GetAccountChanges(TestItem.AddressC)!.Value;
+ AccountChanges addressDChanges = blockAccessList.GetAccountChanges(TestItem.AddressD)!.Value;
+ AccountChanges newContractChanges = blockAccessList.GetAccountChanges(newContractAddress)!.Value;
+ AccountChanges newContractChanges2 = blockAccessList.GetAccountChanges(newContractAddress2)!.Value;
+ AccountChanges eip2935Changes = blockAccessList.GetAccountChanges(Eip2935Constants.BlockHashHistoryAddress)!.Value;
+ AccountChanges eip4788Changes = blockAccessList.GetAccountChanges(Eip4788Constants.BeaconRootsAddress)!.Value;
+ AccountChanges eip7002Changes = blockAccessList.GetAccountChanges(Eip7002Constants.WithdrawalRequestPredeployAddress)!.Value;
+ AccountChanges eip7251Changes = blockAccessList.GetAccountChanges(Eip7251Constants.ConsolidationRequestPredeployAddress)!.Value;
+
+ byte[] slot0 = ToStorageSlot(0);
+ byte[] slot1 = ToStorageSlot(1);
+ byte[] slot2 = ToStorageSlot(2);
+ byte[] slot3 = ToStorageSlot(3);
+ byte[] eip4788Slot1 = ToStorageSlot(timestamp % Eip4788Constants.RingBufferSize);
+ byte[] eip4788Slot2 = ToStorageSlot((timestamp % Eip4788Constants.RingBufferSize) + Eip4788Constants.RingBufferSize);
+ StorageChange parentHashStorageChange = new(0, Bytes32.Wrap(parentHash.BytesToArray()));
+ StorageChange calldataStorageChange = new(0, Bytes32.Zero);
+ StorageChange timestampStorageChange = new(0, Bytes32.Wrap(Bytes.FromHexString("0x00000000000000000000000000000000000000000000000000000000000F4240")));
+ StorageChange zeroStorageChangeEnd = new(3, Bytes32.Zero);
+
+ UInt256 addressABalance = _accountBalance - gasPrice * GasCostOf.Transaction;
+ UInt256 addressABalance2 = _accountBalance - gasPrice * gasUsedBeforeFinal;
+ UInt256 addressABalance3 = _accountBalance - gasPrice * gasUsed;
+
+ using (Assert.EnterMultipleScope())
+ {
+ Assert.That(addressAChanges, Is.EqualTo(new AccountChanges()
+ {
+ Address = TestItem.AddressA,
+ StorageChanges = [],
+ StorageReads = [],
+ BalanceChanges = new SortedList { { 1, new(1, addressABalance) }, { 2, new(2, addressABalance2) }, { 3, new(3, addressABalance3) } },
+ NonceChanges = new SortedList { { 1, new(1, 1) }, { 2, new(2, 2) }, { 3, new(3, 3) } },
+ CodeChanges = []
+ }));
+
+ Assert.That(addressBChanges, Is.EqualTo(new AccountChanges()
+ {
+ Address = TestItem.AddressB,
+ StorageChanges = [],
+ StorageReads = [],
+ BalanceChanges = [],
+ NonceChanges = [],
+ CodeChanges = []
+ }));
+
+ Assert.That(addressCChanges, Is.EqualTo(new AccountChanges()
+ {
+ Address = TestItem.AddressC,
+ StorageChanges = [],
+ StorageReads = [],
+ BalanceChanges = new SortedList { { 1, new(1, new UInt256(GasCostOf.Transaction)) }, { 2, new(2, new UInt256(gasUsedBeforeFinal)) }, { 3, new(3, new UInt256(gasUsed)) } },
+ NonceChanges = [],
+ CodeChanges = []
+ }));
+
+ Assert.That(addressDChanges, Is.EqualTo(new AccountChanges()
+ {
+ Address = TestItem.AddressD,
+ StorageChanges = [],
+ StorageReads = [],
+ BalanceChanges = new SortedList { { 4, new(4, 1.GWei()) } },
+ NonceChanges = [],
+ CodeChanges = []
+ }));
+
+ Assert.That(newContractChanges, Is.EqualTo(new AccountChanges()
+ {
+ Address = newContractAddress,
+ StorageChanges = [],
+ StorageReads = [],
+ BalanceChanges = [],
+ NonceChanges = new SortedList { { 2, new(2, 1) } },
+ CodeChanges = new SortedList { { 2, new(2, Eip2935TestConstants.Code) } }
+ }));
+
+ Assert.That(newContractChanges2, Is.EqualTo(new AccountChanges()
+ {
+ Address = newContractAddress2,
+ StorageChanges = [],
+ StorageReads = [ToStorageRead(slot1)],
+ BalanceChanges = [],
+ NonceChanges = [],
+ CodeChanges = []
+ }));
+
+ Assert.That(eip2935Changes, Is.EqualTo(new AccountChanges()
+ {
+ Address = Eip2935Constants.BlockHashHistoryAddress,
+ StorageChanges = new(Bytes.Comparer) { { slot0, new SlotChanges(slot0, [parentHashStorageChange]) } },
+ StorageReads = [],
+ BalanceChanges = [],
+ NonceChanges = [],
+ CodeChanges = []
+ }));
+
+ // second storage read is not a change, so not recorded
+ Assert.That(eip4788Changes, Is.EqualTo(new AccountChanges()
+ {
+ Address = Eip4788Constants.BeaconRootsAddress,
+ StorageChanges = new(Bytes.Comparer) { { eip4788Slot1, new SlotChanges(eip4788Slot1, [timestampStorageChange]) } },
+ StorageReads = [ToStorageRead(eip4788Slot1), ToStorageRead(eip4788Slot2)],
+ BalanceChanges = [],
+ NonceChanges = [],
+ CodeChanges = []
+ }));
+
+ // storage reads make no changes
+ Assert.That(eip7002Changes, Is.EqualTo(new AccountChanges()
+ {
+ Address = Eip7002Constants.WithdrawalRequestPredeployAddress,
+ StorageChanges = [],
+ StorageReads = [
+ ToStorageRead(slot0),
+ ToStorageRead(slot1),
+ ToStorageRead(slot2),
+ ToStorageRead(slot3),
+ ],
+ BalanceChanges = [],
+ NonceChanges = [],
+ CodeChanges = []
+ }));
+
+ // storage reads make no changes
+ Assert.That(eip7251Changes, Is.EqualTo(new AccountChanges()
+ {
+ Address = Eip7251Constants.ConsolidationRequestPredeployAddress,
+ StorageChanges = [],
+ StorageReads = [
+ ToStorageRead(slot0),
+ ToStorageRead(slot1),
+ ToStorageRead(slot2),
+ ToStorageRead(slot3),
+ ],
+ BalanceChanges = [],
+ NonceChanges = [],
+ CodeChanges = []
+ }));
+ }
+ }
+
+ private static Action BuildContainer()
+ => containerBuilder => containerBuilder.AddSingleton(_specProvider);
+
+ private static void InitWorldState(IWorldState worldState)
+ {
+ worldState.CreateAccount(TestItem.AddressA, _accountBalance);
+
+ worldState.CreateAccount(Eip2935Constants.BlockHashHistoryAddress, 0, Eip2935TestConstants.Nonce);
+ worldState.InsertCode(Eip2935Constants.BlockHashHistoryAddress, Eip2935TestConstants.CodeHash, Eip2935TestConstants.Code, _specProvider.GenesisSpec);
+
+ worldState.CreateAccount(Eip4788Constants.BeaconRootsAddress, 0, Eip4788TestConstants.Nonce);
+ worldState.InsertCode(Eip4788Constants.BeaconRootsAddress, Eip4788TestConstants.CodeHash, Eip4788TestConstants.Code, _specProvider.GenesisSpec);
+
+ worldState.CreateAccount(Eip7002Constants.WithdrawalRequestPredeployAddress, 0, Eip7002TestConstants.Nonce);
+ worldState.InsertCode(Eip7002Constants.WithdrawalRequestPredeployAddress, Eip7002TestConstants.CodeHash, Eip7002TestConstants.Code, _specProvider.GenesisSpec);
+
+ worldState.CreateAccount(Eip7251Constants.ConsolidationRequestPredeployAddress, 0, Eip7251TestConstants.Nonce);
+ worldState.InsertCode(Eip7251Constants.ConsolidationRequestPredeployAddress, Eip7251TestConstants.CodeHash, Eip7251TestConstants.Code, _specProvider.GenesisSpec);
+
+ worldState.Commit(_specProvider.GenesisSpec);
+ worldState.CommitTree(0);
+ worldState.RecalculateStateRoot();
+ // Hash256 stateRoot = worldState.StateRoot;
+ }
+
+ // should hash?
+ private static byte[] ToStorageSlot(ulong x)
+ => new BigInteger(x).ToBytes32(true);
+ // => ValueKeccak.Compute(new BigInteger(x).ToBytes32(true)).ToByteArray();
+
+ private static StorageRead ToStorageRead(byte[] x)
+ {
+ Span newValue = new byte[32];
+ newValue.Clear();
+ x.CopyTo(newValue[(32 - x.Length)..]);
+ return new(Bytes32.Wrap([.. newValue]));
+ }
+
+ private static IEnumerable AccountChangesTestSource
+ {
+ get
+ {
+ yield return new TestCaseData(
+ "0xf89f9400000961ef480eb55e80d19ad83579a64c007002c0f884a00000000000000000000000000000000000000000000000000000000000000000a00000000000000000000000000000000000000000000000000000000000000001a00000000000000000000000000000000000000000000000000000000000000002a00000000000000000000000000000000000000000000000000000000000000003c0c0c0",
+ new AccountChanges()
+ {
+ Address = Eip7002Constants.WithdrawalRequestPredeployAddress,
+ StorageReads = [
+ ToStorageRead(ToStorageSlot(0)),
+ ToStorageRead(ToStorageSlot(1)),
+ ToStorageRead(ToStorageSlot(2)),
+ ToStorageRead(ToStorageSlot(3))
+ ],
+ })
+ { TestName = "storage_reads" };
+
+ yield return new TestCaseData(
+ "0xf862940000f90827f1c53a10cb7a02335b175320002935f847f845a00000000000000000000000000000000000000000000000000000000000000000e3e280a0c382836f81d7e4055a0e280268371e17cc69a531efe2abee082e9b922d6050fdc0c0c0c0",
+ new AccountChanges()
+ {
+ Address = Eip2935Constants.BlockHashHistoryAddress,
+ StorageChanges = new(Bytes.Comparer) { { ToStorageSlot(0), new SlotChanges(ToStorageSlot(0), [new(0, Bytes32.Wrap(Bytes.FromHexString("0xc382836f81d7e4055a0e280268371e17cc69a531efe2abee082e9b922d6050fd")))]) } },
+ })
+ { TestName = "storage_changes" };
+ }
+ }
+
+ private static IEnumerable BlockAccessListTestSource
+ {
+ get
+ {
+ byte[] slot0 = ToStorageSlot(0);
+ byte[] slot1 = ToStorageSlot(1);
+ byte[] slot2 = ToStorageSlot(2);
+ byte[] slot3 = ToStorageSlot(3);
+ byte[] eip4788Slot1 = ToStorageSlot(0xc);
+ StorageChange parentHashStorageChange = new(0, Bytes32.Wrap(Bytes.FromHexString("0xc382836f81d7e4055a0e280268371e17cc69a531efe2abee082e9b922d6050fd")));
+ StorageChange timestampStorageChange = new(0, Bytes32.Wrap(Bytes.FromHexString("0x000000000000000000000000000000000000000000000000000000000000000c")));
+ SortedDictionary expectedAccountChanges = new()
+ {
+ {Eip7002Constants.WithdrawalRequestPredeployAddress, new()
+ {
+ Address = Eip7002Constants.WithdrawalRequestPredeployAddress,
+ StorageReads = [
+ ToStorageRead(slot0),
+ ToStorageRead(slot1),
+ ToStorageRead(slot2),
+ ToStorageRead(slot3),
+ ],
+ }},
+ {Eip7251Constants.ConsolidationRequestPredeployAddress, new()
+ {
+ Address = Eip7251Constants.ConsolidationRequestPredeployAddress,
+ StorageReads = [
+ ToStorageRead(slot0),
+ ToStorageRead(slot1),
+ ToStorageRead(slot2),
+ ToStorageRead(slot3),
+ ],
+ }},
+ {Eip2935Constants.BlockHashHistoryAddress, new()
+ {
+ Address = Eip2935Constants.BlockHashHistoryAddress,
+ StorageChanges = new(Bytes.Comparer) { { slot0, new SlotChanges(slot0, [parentHashStorageChange]) } },
+ }},
+ {Eip4788Constants.BeaconRootsAddress, new()
+ {
+ Address = Eip4788Constants.BeaconRootsAddress,
+ StorageChanges = new(Bytes.Comparer) { { eip4788Slot1, new SlotChanges(eip4788Slot1, [timestampStorageChange]) } },
+ StorageReads = [
+ ToStorageRead([0x20, 0x0b])
+ ],
+ }},
+ {new("0x2adc25665018aa1fe0e6bc666dac8fc2697ff9ba"), new()
+ {
+ Address = new("0x2adc25665018aa1fe0e6bc666dac8fc2697ff9ba"),
+ BalanceChanges = new SortedList { { 1, new(1, 0x1319718811c8) } },
+ }},
+ {new("0xaccc7d92b051544a255b8a899071040739bada75"), new()
+ {
+ Address = new("0xaccc7d92b051544a255b8a899071040739bada75"),
+ NonceChanges = new SortedList { { 1, new(1, 1) } },
+ BalanceChanges = new SortedList { { 1, new(1, new(Bytes.FromHexString("0x3635c99aac6d15af9c"))) } },
+ }},
+ {new("0xd9c0e57d447779673b236c7423aeab84e931f3ba"), new()
+ {
+ Address = new("0xd9c0e57d447779673b236c7423aeab84e931f3ba"),
+ BalanceChanges = new SortedList { { 1, new(1, 0x64) } },
+ }},
+ };
+ BlockAccessList expected = new(expectedAccountChanges);
+ yield return new TestCaseData(
+ "0xf90297f89f9400000961ef480eb55e80d19ad83579a64c007002c0f884a00000000000000000000000000000000000000000000000000000000000000000a00000000000000000000000000000000000000000000000000000000000000001a00000000000000000000000000000000000000000000000000000000000000002a00000000000000000000000000000000000000000000000000000000000000003c0c0c0f89f940000bbddc7ce488642fb579f8b00f3a590007251c0f884a00000000000000000000000000000000000000000000000000000000000000000a00000000000000000000000000000000000000000000000000000000000000001a00000000000000000000000000000000000000000000000000000000000000002a00000000000000000000000000000000000000000000000000000000000000003c0c0c0f862940000f90827f1c53a10cb7a02335b175320002935f847f845a00000000000000000000000000000000000000000000000000000000000000000e3e280a0c382836f81d7e4055a0e280268371e17cc69a531efe2abee082e9b922d6050fdc0c0c0c0f88394000f3df6d732807ef1319fb7b8bb8522d0beac02f847f845a0000000000000000000000000000000000000000000000000000000000000000ce3e280a0000000000000000000000000000000000000000000000000000000000000000ce1a0000000000000000000000000000000000000000000000000000000000000200bc0c0c0e3942adc25665018aa1fe0e6bc666dac8fc2697ff9bac0c0c9c801861319718811c8c0c0e994accc7d92b051544a255b8a899071040739bada75c0c0cccb01893635c99aac6d15af9cc3c20101c0dd94d9c0e57d447779673b236c7423aeab84e931f3bac0c0c3c20164c0c0",
+ expected)
+ { TestName = "balance_changes" };
+
+ yield return new TestCaseData(
+ "0xf902b5f89f9400000961ef480eb55e80d19ad83579a64c007002c0f884a00000000000000000000000000000000000000000000000000000000000000000a00000000000000000000000000000000000000000000000000000000000000001a00000000000000000000000000000000000000000000000000000000000000002a00000000000000000000000000000000000000000000000000000000000000003c0c0c0f89f940000bbddc7ce488642fb579f8b00f3a590007251c0f884a00000000000000000000000000000000000000000000000000000000000000000a00000000000000000000000000000000000000000000000000000000000000001a00000000000000000000000000000000000000000000000000000000000000002a00000000000000000000000000000000000000000000000000000000000000003c0c0c0f862940000f90827f1c53a10cb7a02335b175320002935f847f845a00000000000000000000000000000000000000000000000000000000000000000e3e280a0a0f0963cedb68172fe724f0b345a86ee23926af1874e31a5fc50e9cc521d1556c0c0c0c0f88394000f3df6d732807ef1319fb7b8bb8522d0beac02f847f845a0000000000000000000000000000000000000000000000000000000000000000ce3e280a0000000000000000000000000000000000000000000000000000000000000000ce1a0000000000000000000000000000000000000000000000000000000000000200bc0c0c0dd941a7d50de1c4dc7d5b696f53b65594f21aa55a826c0c0c0c3c20102c0e0942adc25665018aa1fe0e6bc666dac8fc2697ff9bac0c0c6c50183026ffdc0c0e0947a8a0e14723feddb342a0273c72c07b88b25a5ffc0c0c0c3c20101c3c20100e994eed26eb981405168f24f2c9ad9cf427e1e39de43c0c0cccb01893635c9adc5de97e00ac3c20101c0",
+ expected
+ )
+ { TestName = "code_changes" };
+ }
+ }
+
+}
diff --git a/src/Nethermind/Nethermind.Blockchain.Test/TransactionProcessorTests.cs b/src/Nethermind/Nethermind.Blockchain.Test/TransactionProcessorTests.cs
index 21e1b6e493f..1d60e969032 100644
--- a/src/Nethermind/Nethermind.Blockchain.Test/TransactionProcessorTests.cs
+++ b/src/Nethermind/Nethermind.Blockchain.Test/TransactionProcessorTests.cs
@@ -11,7 +11,6 @@
using Nethermind.Specs;
using Nethermind.Core.Test.Builders;
using Nethermind.Crypto;
-using Nethermind.Db;
using Nethermind.Int256;
using Nethermind.Evm.Tracing;
using Nethermind.Blockchain.Tracing.GethStyle;
@@ -21,7 +20,6 @@
using Nethermind.Serialization.Json;
using Nethermind.Specs.Forks;
using Nethermind.Evm.State;
-using Nethermind.Trie.Pruning;
using NUnit.Framework;
using Nethermind.Config;
using System.Collections.Generic;
@@ -36,13 +34,13 @@ namespace Nethermind.Evm.Test;
[TestFixture(false)]
[Todo(Improve.Refactor, "Check why fixture test cases did not work")]
[Parallelizable(ParallelScope.Self)]
-public class TransactionProcessorTests
+public abstract class TransactionProcessorTests
{
private readonly bool _isEip155Enabled;
private readonly ISpecProvider _specProvider;
private IEthereumEcdsa _ethereumEcdsa;
- private ITransactionProcessor _transactionProcessor;
- private IWorldState _stateProvider;
+ protected ITransactionProcessor _transactionProcessor;
+ protected IWorldState _stateProvider;
private BlockHeader _baseBlock = null!;
private IDisposable _stateCloser;
@@ -52,7 +50,7 @@ public TransactionProcessorTests(bool eip155Enabled)
_specProvider = MainnetSpecProvider.Instance;
}
- private static readonly UInt256 AccountBalance = 1.Ether();
+ protected static readonly UInt256 AccountBalance = 1.Ether();
[SetUp]
public void Setup()
@@ -756,7 +754,7 @@ private BlockReceiptsTracer BuildTracer(Block block, Transaction tx, bool stateD
return tracer;
}
- private TransactionResult Execute(Transaction tx, Block block, BlockReceiptsTracer? tracer = null)
+ protected TransactionResult Execute(Transaction tx, Block block, BlockReceiptsTracer? tracer = null)
{
tracer?.StartNewBlockTrace(block);
tracer?.StartNewTxTrace(tx);
diff --git a/src/Nethermind/Nethermind.Blockchain/BeaconBlockRoot/IBeaconBlockRootHandler.cs b/src/Nethermind/Nethermind.Blockchain/BeaconBlockRoot/IBeaconBlockRootHandler.cs
index 3b1a204a9b3..633bafd7c61 100644
--- a/src/Nethermind/Nethermind.Blockchain/BeaconBlockRoot/IBeaconBlockRootHandler.cs
+++ b/src/Nethermind/Nethermind.Blockchain/BeaconBlockRoot/IBeaconBlockRootHandler.cs
@@ -4,7 +4,6 @@
using Nethermind.Core;
using Nethermind.Core.Eip2930;
using Nethermind.Core.Specs;
-using Nethermind.Evm;
using Nethermind.Evm.Tracing;
namespace Nethermind.Blockchain.BeaconBlockRoot;
diff --git a/src/Nethermind/Nethermind.Blockchain/Blocks/BlockhashStore.cs b/src/Nethermind/Nethermind.Blockchain/Blocks/BlockhashStore.cs
index 00d7a44cda8..c8dbf66b1d3 100644
--- a/src/Nethermind/Nethermind.Blockchain/Blocks/BlockhashStore.cs
+++ b/src/Nethermind/Nethermind.Blockchain/Blocks/BlockhashStore.cs
@@ -8,6 +8,7 @@
using Nethermind.Core.Extensions;
using Nethermind.Core.Specs;
using Nethermind.Evm.State;
+using Nethermind.Evm.Tracing;
using Nethermind.Int256;
[assembly: InternalsVisibleTo("Nethermind.Blockchain.Test")]
@@ -19,10 +20,10 @@ public class BlockhashStore(ISpecProvider specProvider, IWorldState worldState)
{
private static readonly byte[] EmptyBytes = [0];
- public void ApplyBlockhashStateChanges(BlockHeader blockHeader)
- => ApplyBlockhashStateChanges(blockHeader, specProvider.GetSpec(blockHeader));
+ public void ApplyBlockhashStateChanges(BlockHeader blockHeader, ITxTracer? tracer = null)
+ => ApplyBlockhashStateChanges(blockHeader, specProvider.GetSpec(blockHeader), tracer);
- public void ApplyBlockhashStateChanges(BlockHeader blockHeader, IReleaseSpec spec)
+ public void ApplyBlockhashStateChanges(BlockHeader blockHeader, IReleaseSpec spec, ITxTracer? tracer = null)
{
if (!spec.IsEip2935Enabled || blockHeader.IsGenesis || blockHeader.ParentHash is null) return;
@@ -32,7 +33,12 @@ public void ApplyBlockhashStateChanges(BlockHeader blockHeader, IReleaseSpec spe
Hash256 parentBlockHash = blockHeader.ParentHash;
var parentBlockIndex = new UInt256((ulong)((blockHeader.Number - 1) % Eip2935Constants.RingBufferSize));
StorageCell blockHashStoreCell = new(eip2935Account, parentBlockIndex);
- worldState.Set(blockHashStoreCell, parentBlockHash!.Bytes.WithoutLeadingZeros().ToArray());
+ byte[] newValue = parentBlockHash!.Bytes.WithoutLeadingZeros().ToArray();
+ worldState.Set(blockHashStoreCell, newValue);
+ if (tracer is not null && tracer.IsTracingStorage)
+ {
+ tracer.ReportStorageChange(blockHashStoreCell, null, newValue);
+ }
}
public Hash256? GetBlockHashFromState(BlockHeader currentHeader, long requiredBlockNumber)
diff --git a/src/Nethermind/Nethermind.Blockchain/Blocks/IBlockhashStore.cs b/src/Nethermind/Nethermind.Blockchain/Blocks/IBlockhashStore.cs
index 77cc6b214f5..035f1a543f3 100644
--- a/src/Nethermind/Nethermind.Blockchain/Blocks/IBlockhashStore.cs
+++ b/src/Nethermind/Nethermind.Blockchain/Blocks/IBlockhashStore.cs
@@ -4,13 +4,14 @@
using Nethermind.Core;
using Nethermind.Core.Crypto;
using Nethermind.Core.Specs;
+using Nethermind.Evm.Tracing;
namespace Nethermind.Blockchain.Blocks;
public interface IBlockhashStore
{
- public void ApplyBlockhashStateChanges(BlockHeader blockHeader);
- public void ApplyBlockhashStateChanges(BlockHeader blockHeader, IReleaseSpec spec);
+ public void ApplyBlockhashStateChanges(BlockHeader blockHeader, ITxTracer? tracer = null);
+ public void ApplyBlockhashStateChanges(BlockHeader blockHeader, IReleaseSpec spec, ITxTracer? tracer = null);
public Hash256? GetBlockHashFromState(BlockHeader currentBlockHeader, long requiredBlockNumber);
public Hash256? GetBlockHashFromState(BlockHeader currentBlockHeader, long requiredBlockNumber, IReleaseSpec? spec);
}
diff --git a/src/Nethermind/Nethermind.Consensus/EngineApiVersions.cs b/src/Nethermind/Nethermind.Consensus/EngineApiVersions.cs
index 145810a7364..c924f581f27 100644
--- a/src/Nethermind/Nethermind.Consensus/EngineApiVersions.cs
+++ b/src/Nethermind/Nethermind.Consensus/EngineApiVersions.cs
@@ -9,4 +9,5 @@ public static class EngineApiVersions
public const int Shanghai = 2;
public const int Cancun = 3;
public const int Prague = 4;
+ public const int Amsterdam = 5;
}
diff --git a/src/Nethermind/Nethermind.Consensus/ExecutionRequests/ExecutionRequestProcessor.cs b/src/Nethermind/Nethermind.Consensus/ExecutionRequests/ExecutionRequestProcessor.cs
index b45d4c7299b..2eb0f8a6dbb 100644
--- a/src/Nethermind/Nethermind.Consensus/ExecutionRequests/ExecutionRequestProcessor.cs
+++ b/src/Nethermind/Nethermind.Consensus/ExecutionRequests/ExecutionRequestProcessor.cs
@@ -12,6 +12,7 @@
using Nethermind.Crypto;
using Nethermind.Evm;
using Nethermind.Evm.State;
+using Nethermind.Evm.Tracing;
using Nethermind.Evm.TransactionProcessing;
using Nethermind.Int256;
using System;
@@ -161,7 +162,7 @@ static void Validate(Block block, object obj, string name, int expectedSize)
}
private void ReadRequests(Block block, IWorldState state, Address contractAddress, ArrayPoolList requests,
- Transaction systemTx, ExecutionRequestType type, string contractEmptyError, string contractFailedError)
+ Transaction systemTx, ExecutionRequestType type, string contractEmptyError, string contractFailedError, ITxTracer? additionalTracer = null)
{
if (!state.HasCode(contractAddress))
{
@@ -170,7 +171,7 @@ private void ReadRequests(Block block, IWorldState state, Address contractAddres
CallOutputTracer tracer = new();
- _transactionProcessor.Execute(systemTx, tracer);
+ _transactionProcessor.Execute(systemTx, additionalTracer is null ? tracer : new CompositeTxTracer(tracer, additionalTracer));
if (tracer.StatusCode == StatusCode.Failure)
{
diff --git a/src/Nethermind/Nethermind.Consensus/Processing/BlockProcessor.BlockValidationTransactionsExecutor.cs b/src/Nethermind/Nethermind.Consensus/Processing/BlockProcessor.BlockValidationTransactionsExecutor.cs
index aea4f0a2e09..42d7b38f117 100644
--- a/src/Nethermind/Nethermind.Consensus/Processing/BlockProcessor.BlockValidationTransactionsExecutor.cs
+++ b/src/Nethermind/Nethermind.Consensus/Processing/BlockProcessor.BlockValidationTransactionsExecutor.cs
@@ -4,7 +4,6 @@
using System;
using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;
-using System.Linq;
using System.Threading;
using Nethermind.Blockchain;
using Nethermind.Blockchain.Tracing;
@@ -12,7 +11,7 @@
using Nethermind.Evm;
using Nethermind.Evm.State;
using Nethermind.Evm.TransactionProcessing;
-
+using Nethermind.State;
using Metrics = Nethermind.Evm.Metrics;
namespace Nethermind.Consensus.Processing
@@ -36,10 +35,13 @@ public TxReceipt[] ProcessTransactions(Block block, ProcessingOptions processing
for (int i = 0; i < block.Transactions.Length; i++)
{
+ (stateProvider as TracedAccessWorldState)?.BlockAccessList.IncrementBlockAccessIndex();
Transaction currentTx = block.Transactions[i];
ProcessTransaction(block, currentTx, i, receiptsTracer, processingOptions);
}
- return receiptsTracer.TxReceipts.ToArray();
+ (stateProvider as TracedAccessWorldState)?.BlockAccessList.IncrementBlockAccessIndex();
+
+ return [.. receiptsTracer.TxReceipts];
}
protected virtual void ProcessTransaction(Block block, Transaction currentTx, int index, BlockReceiptsTracer receiptsTracer, ProcessingOptions processingOptions)
@@ -50,7 +52,7 @@ protected virtual void ProcessTransaction(Block block, Transaction currentTx, in
}
[DoesNotReturn, StackTraceHidden]
- private void ThrowInvalidBlockException(TransactionResult result, BlockHeader header, Transaction currentTx, int index)
+ private static void ThrowInvalidBlockException(TransactionResult result, BlockHeader header, Transaction currentTx, int index)
{
throw new InvalidBlockException(header, $"Transaction {currentTx.Hash} at index {index} failed with error {result.Error}");
}
diff --git a/src/Nethermind/Nethermind.Consensus/Processing/BlockProcessor.cs b/src/Nethermind/Nethermind.Consensus/Processing/BlockProcessor.cs
index f9b5d930d6e..69d1bc18248 100644
--- a/src/Nethermind/Nethermind.Consensus/Processing/BlockProcessor.cs
+++ b/src/Nethermind/Nethermind.Consensus/Processing/BlockProcessor.cs
@@ -15,6 +15,7 @@
using Nethermind.Consensus.Validators;
using Nethermind.Consensus.Withdrawals;
using Nethermind.Core;
+using Nethermind.Core.Crypto;
using Nethermind.Core.Specs;
using Nethermind.Core.Threading;
using Nethermind.Crypto;
@@ -23,29 +24,30 @@
using Nethermind.Evm.Tracing;
using Nethermind.Int256;
using Nethermind.Logging;
+using Nethermind.Serialization.Rlp;
using Nethermind.Specs.Forks;
using Nethermind.State;
using static Nethermind.Consensus.Processing.IBlockProcessor;
namespace Nethermind.Consensus.Processing;
-public partial class BlockProcessor(
- ISpecProvider specProvider,
- IBlockValidator blockValidator,
- IRewardCalculator rewardCalculator,
- IBlockTransactionsExecutor blockTransactionsExecutor,
- IWorldState stateProvider,
- IReceiptStorage receiptStorage,
- IBeaconBlockRootHandler beaconBlockRootHandler,
- IBlockhashStore blockHashStore,
- ILogManager logManager,
- IWithdrawalProcessor withdrawalProcessor,
- IExecutionRequestsProcessor executionRequestsProcessor)
+public partial class BlockProcessor
: IBlockProcessor
{
- private readonly ILogger _logger = logManager.GetClassLogger();
- protected readonly WorldStateMetricsDecorator _stateProvider = new WorldStateMetricsDecorator(stateProvider);
+ private readonly ILogger _logger;
+ private readonly TracedAccessWorldState? _tracedAccessWorldState;
+ protected readonly WorldStateMetricsDecorator _stateProvider;
private readonly IReceiptsRootCalculator _receiptsRootCalculator = ReceiptsRootCalculator.Instance;
+ private readonly ISpecProvider _specProvider;
+ private readonly IBlockValidator _blockValidator;
+ private readonly IRewardCalculator _rewardCalculator;
+ private readonly IBlockTransactionsExecutor _blockTransactionsExecutor;
+ private readonly IReceiptStorage _receiptStorage;
+ private readonly IBeaconBlockRootHandler _beaconBlockRootHandler;
+ private readonly IBlockhashStore _blockHashStore;
+ private readonly ILogManager _logManager;
+ private readonly IWithdrawalProcessor _withdrawalProcessor;
+ private readonly IExecutionRequestsProcessor _executionRequestsProcessor;
///
/// We use a single receipt tracer for all blocks. Internally receipt tracer forwards most of the calls
@@ -53,6 +55,35 @@ public partial class BlockProcessor(
///
protected BlockReceiptsTracer ReceiptsTracer { get; set; } = new();
+ public BlockProcessor(
+ ISpecProvider specProvider,
+ IBlockValidator blockValidator,
+ IRewardCalculator rewardCalculator,
+ IBlockTransactionsExecutor blockTransactionsExecutor,
+ IWorldState stateProvider,
+ IReceiptStorage receiptStorage,
+ IBeaconBlockRootHandler beaconBlockRootHandler,
+ IBlockhashStore blockHashStore,
+ ILogManager logManager,
+ IWithdrawalProcessor withdrawalProcessor,
+ IExecutionRequestsProcessor executionRequestsProcessor)
+ {
+ _specProvider = specProvider;
+ _blockValidator = blockValidator;
+ _rewardCalculator = rewardCalculator;
+ _blockTransactionsExecutor = blockTransactionsExecutor;
+ _receiptStorage = receiptStorage;
+ _beaconBlockRootHandler = beaconBlockRootHandler;
+ _blockHashStore = blockHashStore;
+ _logManager = logManager;
+ _withdrawalProcessor = withdrawalProcessor;
+ _executionRequestsProcessor = executionRequestsProcessor;
+
+ _logger = _logManager.GetClassLogger();
+ _tracedAccessWorldState = stateProvider as TracedAccessWorldState;
+ _stateProvider = new(stateProvider);
+ }
+
public (Block Block, TxReceipt[] Receipts) ProcessOne(Block suggestedBlock, ProcessingOptions options, IBlockTracer blockTracer, IReleaseSpec spec, CancellationToken token)
{
if (_logger.IsTrace) _logger.Trace($"Processing block {suggestedBlock.ToString(Block.Format.Short)} ({options})");
@@ -71,7 +102,7 @@ public partial class BlockProcessor(
private void ValidateProcessedBlock(Block suggestedBlock, ProcessingOptions options, Block block, TxReceipt[] receipts)
{
- if (!options.ContainsFlag(ProcessingOptions.NoValidation) && !blockValidator.ValidateProcessedBlock(block, receipts, suggestedBlock, out string? error))
+ if (!options.ContainsFlag(ProcessingOptions.NoValidation) && !_blockValidator.ValidateProcessedBlock(block, receipts, suggestedBlock, out string? error))
{
if (_logger.IsWarn) _logger.Warn(InvalidBlockHelper.GetMessage(suggestedBlock, "invalid block after processing"));
throw new InvalidBlockException(suggestedBlock, error);
@@ -83,7 +114,7 @@ private void ValidateProcessedBlock(Block suggestedBlock, ProcessingOptions opti
}
private bool ShouldComputeStateRoot(BlockHeader header) =>
- !header.IsGenesis || !specProvider.GenesisStateUnavailable;
+ !header.IsGenesis || !_specProvider.GenesisStateUnavailable;
protected virtual TxReceipt[] ProcessBlock(
Block block,
@@ -92,18 +123,24 @@ protected virtual TxReceipt[] ProcessBlock(
IReleaseSpec spec,
CancellationToken token)
{
+ BlockBody body = block.Body;
BlockHeader header = block.Header;
ReceiptsTracer.SetOtherTracer(blockTracer);
ReceiptsTracer.StartNewBlockTrace(block);
- blockTransactionsExecutor.SetBlockExecutionContext(new BlockExecutionContext(block.Header, spec));
+ _blockTransactionsExecutor.SetBlockExecutionContext(new BlockExecutionContext(block.Header, spec));
+
+ if (_tracedAccessWorldState is not null)
+ {
+ _tracedAccessWorldState.Enabled = spec.BlockLevelAccessListsEnabled;
+ }
StoreBeaconRoot(block, spec);
- blockHashStore.ApplyBlockhashStateChanges(header, spec);
+ _blockHashStore.ApplyBlockhashStateChanges(header, spec);
_stateProvider.Commit(spec, commitRoots: false);
- TxReceipt[] receipts = blockTransactionsExecutor.ProcessTransactions(block, options, ReceiptsTracer, token);
+ TxReceipt[] receipts = _blockTransactionsExecutor.ProcessTransactions(block, options, ReceiptsTracer, token);
_stateProvider.Commit(spec, commitRoots: false);
@@ -116,14 +153,14 @@ protected virtual TxReceipt[] ProcessBlock(
header.ReceiptsRoot = _receiptsRootCalculator.GetReceiptsRoot(receipts, spec, block.ReceiptsRoot);
ApplyMinerRewards(block, blockTracer, spec);
- withdrawalProcessor.ProcessWithdrawals(block, spec);
+ _withdrawalProcessor.ProcessWithdrawals(block, spec);
// We need to do a commit here as in _executionRequestsProcessor while executing system transactions
// we do WorldState.Commit(SystemTransactionReleaseSpec.Instance). In SystemTransactionReleaseSpec
// Eip158Enabled=false, so we end up persisting empty accounts created while processing withdrawals.
_stateProvider.Commit(spec, commitRoots: false);
- executionRequestsProcessor.ProcessExecutionRequests(block, _stateProvider, receipts, spec);
+ _executionRequestsProcessor.ProcessExecutionRequests(block, _stateProvider, receipts, spec);
ReceiptsTracer.EndBlockTrace();
@@ -141,6 +178,16 @@ protected virtual TxReceipt[] ProcessBlock(
header.StateRoot = _stateProvider.StateRoot;
}
+ if (_tracedAccessWorldState is not null && spec.BlockLevelAccessListsEnabled && !block.IsGenesis)
+ {
+ body.BlockAccessList = _tracedAccessWorldState.BlockAccessList;
+ header.BlockAccessListHash = new(ValueKeccak.Compute(Rlp.Encode(_tracedAccessWorldState.BlockAccessList).Bytes).Bytes);
+ }
+ else
+ {
+ header.BlockAccessListHash = Keccak.OfAnEmptySequenceRlp;
+ }
+
header.Hash = header.CalculateHash();
return receipts;
@@ -165,7 +212,7 @@ private void StoreBeaconRoot(Block block, IReleaseSpec spec)
{
try
{
- beaconBlockRootHandler.StoreBeaconRoot(block, spec, NullTxTracer.Instance);
+ _beaconBlockRootHandler.StoreBeaconRoot(block, spec, NullTxTracer.Instance);
}
catch (Exception e)
{
@@ -176,7 +223,7 @@ private void StoreBeaconRoot(Block block, IReleaseSpec spec)
private void StoreTxReceipts(Block block, TxReceipt[] txReceipts, IReleaseSpec spec)
{
// Setting canonical is done when the BlockAddedToMain event is fired
- receiptStorage.Insert(block, txReceipts, spec, false);
+ _receiptStorage.Insert(block, txReceipts, spec, false);
}
private Block PrepareBlockForProcessing(Block suggestedBlock)
@@ -223,7 +270,7 @@ private Block PrepareBlockForProcessing(Block suggestedBlock)
private void ApplyMinerRewards(Block block, IBlockTracer tracer, IReleaseSpec spec)
{
if (_logger.IsTrace) _logger.Trace("Applying miner rewards:");
- BlockReward[] rewards = rewardCalculator.CalculateRewards(block);
+ BlockReward[] rewards = _rewardCalculator.CalculateRewards(block);
for (int i = 0; i < rewards.Length; i++)
{
BlockReward reward = rewards[i];
@@ -256,7 +303,7 @@ private void ApplyMinerReward(Block block, BlockReward reward, IReleaseSpec spec
private void ApplyDaoTransition(Block block)
{
- long? daoBlockNumber = specProvider.DaoBlockNumber;
+ long? daoBlockNumber = _specProvider.DaoBlockNumber;
if (daoBlockNumber.HasValue && daoBlockNumber.Value == block.Header.Number)
{
ApplyTransition();
diff --git a/src/Nethermind/Nethermind.Consensus/Validators/BlockValidator.cs b/src/Nethermind/Nethermind.Consensus/Validators/BlockValidator.cs
index 4b468240290..a21e5277cf3 100644
--- a/src/Nethermind/Nethermind.Consensus/Validators/BlockValidator.cs
+++ b/src/Nethermind/Nethermind.Consensus/Validators/BlockValidator.cs
@@ -3,8 +3,10 @@
using System;
using System.Text;
+using System.Text.Json;
using Nethermind.Blockchain;
using Nethermind.Core;
+using Nethermind.Core.BlockAccessLists;
using Nethermind.Core.Crypto;
using Nethermind.Core.Extensions;
using Nethermind.Core.Messages;
@@ -132,6 +134,27 @@ public bool ValidateSuggestedBlock(Block block, BlockHeader? parent, out string?
}
}
+ if (spec.BlockLevelAccessListsEnabled)
+ {
+ if (block.BlockAccessList is null || block.BlockAccessListHash is null)
+ {
+ if (_logger.IsDebug) _logger.Debug($"{Invalid(block)} Block-level access list was missing or empty");
+ errorMessage = BlockErrorMessages.InvalidBlockLevelAccessList;
+ return false;
+ }
+
+ // try
+ // {
+ // block.DecodedBlockAccessList = Rlp.Decode(block.BlockAccessList);
+ // }
+ // catch (RlpException e)
+ // {
+ // if (_logger.IsDebug) _logger.Debug($"{Invalid(block)} Block-level access list could not be decoded: {e}");
+ // errorMessage = BlockErrorMessages.InvalidBlockLevelAccessList;
+ // return false;
+ // }
+ }
+
return true;
}
@@ -214,6 +237,20 @@ public bool ValidateProcessedBlock(Block processedBlock, TxReceipt[] receipts, B
error ??= BlockErrorMessages.InvalidRequestsHash(suggestedBlock.Header.RequestsHash, processedBlock.Header.RequestsHash);
}
+ if (processedBlock.Header.WithdrawalsRoot != suggestedBlock.Header.WithdrawalsRoot)
+ {
+ if (_logger.IsWarn) _logger.Warn($"- withdrawals root : expected {suggestedBlock.Header.WithdrawalsRoot}, got {processedBlock.Header.WithdrawalsRoot}");
+ error ??= BlockErrorMessages.InvalidWithdrawalsRoot(suggestedBlock.Header.WithdrawalsRoot, processedBlock.Header.WithdrawalsRoot);
+ }
+
+ if (processedBlock.Header.BlockAccessListHash != suggestedBlock.Header.BlockAccessListHash)
+ {
+ // Console.WriteLine("processed block:");
+ // Console.WriteLine(processedBlock.BlockAccessList.ToString());
+ if (_logger.IsWarn) _logger.Warn($"- block access list hash : expected {suggestedBlock.Header.BlockAccessListHash}, got {processedBlock.Header.BlockAccessListHash}");
+ error ??= BlockErrorMessages.InvalidBlockLevelAccessListRoot(suggestedBlock.Header.BlockAccessListHash, processedBlock.Header.BlockAccessListHash);
+ }
+
for (int i = 0; i < processedBlock.Transactions.Length; i++)
{
if (receipts[i].Error is not null && receipts[i].GasUsed == 0 && receipts[i].Error == "invalid")
@@ -378,6 +415,43 @@ public virtual bool ValidateBodyAgainstHeader(BlockHeader header, BlockBody toBe
return true;
}
+ public virtual bool ValidateBlockLevelAccessList(Block block, IReleaseSpec spec, out string? error)
+ {
+ if (spec.BlockLevelAccessListsEnabled && block.BlockAccessList is null)
+ {
+ error = BlockErrorMessages.MissingBlockLevelAccessList;
+
+ if (_logger.IsWarn) _logger.Warn($"Block level access list cannot be null in block {block.Hash} when EIP-7928 activated.");
+
+ return false;
+ }
+
+ if (!spec.BlockLevelAccessListsEnabled && block.BlockAccessList is not null)
+ {
+ error = BlockErrorMessages.BlockLevelAccessListNotEnabled;
+
+ if (_logger.IsWarn) _logger.Warn($"Block level access list must be null in block {block.Hash} when EIP-7928 not activated.");
+
+ return false;
+ }
+
+ if (block.BlockAccessList is not null)
+ {
+ if (!ValidateBlockLevelAccessListHashMatches(block, out Hash256 blockLevelAccessListRoot))
+ {
+ error = BlockErrorMessages.InvalidBlockLevelAccessListRoot(block.Header.BlockAccessListHash, blockLevelAccessListRoot);
+ if (_logger.IsWarn) _logger.Warn($"Block level access list root hash mismatch in block {block.ToString(Block.Format.FullHashAndNumber)}: expected {block.Header.BlockAccessListHash}, got {blockLevelAccessListRoot}");
+
+ return false;
+ }
+ }
+
+ error = null;
+
+ return true;
+
+ }
+
public static bool ValidateTxRootMatchesTxs(Block block, out Hash256 txRoot) =>
ValidateTxRootMatchesTxs(block.Header, block.Body, out txRoot);
@@ -404,6 +478,21 @@ public static bool ValidateWithdrawalsHashMatches(BlockHeader header, BlockBody
return (withdrawalsRoot = new WithdrawalTrie(body.Withdrawals).RootHash) == header.WithdrawalsRoot;
}
+ public static bool ValidateBlockLevelAccessListHashMatches(Block block, out Hash256? blockLevelAccessListRoot)
+ {
+ BlockBody body = block.Body;
+ BlockHeader header = block.Header;
+ if (body.BlockAccessList is null)
+ {
+ blockLevelAccessListRoot = null;
+ return header.BlockAccessListHash is null;
+ }
+
+ blockLevelAccessListRoot = new(ValueKeccak.Compute(block.EncodedBlockAccessList!).Bytes);
+
+ return blockLevelAccessListRoot == header.BlockAccessListHash;
+ }
+
private static string Invalid(Block block) =>
$"Invalid block {block.ToString(Block.Format.FullHashAndNumber)}:";
}
diff --git a/src/Nethermind/Nethermind.Consensus/Withdrawals/BlockProductionWithdrawalProcessor.cs b/src/Nethermind/Nethermind.Consensus/Withdrawals/BlockProductionWithdrawalProcessor.cs
index c03f0702002..12f2e512a3f 100644
--- a/src/Nethermind/Nethermind.Consensus/Withdrawals/BlockProductionWithdrawalProcessor.cs
+++ b/src/Nethermind/Nethermind.Consensus/Withdrawals/BlockProductionWithdrawalProcessor.cs
@@ -9,12 +9,9 @@
namespace Nethermind.Consensus.Withdrawals;
-public class BlockProductionWithdrawalProcessor : IWithdrawalProcessor
+public class BlockProductionWithdrawalProcessor(IWithdrawalProcessor processor) : IWithdrawalProcessor
{
- private readonly IWithdrawalProcessor _processor;
-
- public BlockProductionWithdrawalProcessor(IWithdrawalProcessor processor) =>
- _processor = processor ?? throw new ArgumentNullException(nameof(processor));
+ private readonly IWithdrawalProcessor _processor = processor ?? throw new ArgumentNullException(nameof(processor));
public void ProcessWithdrawals(Block block, IReleaseSpec spec)
{
diff --git a/src/Nethermind/Nethermind.Consensus/Withdrawals/WithdrawalProcessor.cs b/src/Nethermind/Nethermind.Consensus/Withdrawals/WithdrawalProcessor.cs
index 3b526813f89..190f2ff47af 100644
--- a/src/Nethermind/Nethermind.Consensus/Withdrawals/WithdrawalProcessor.cs
+++ b/src/Nethermind/Nethermind.Consensus/Withdrawals/WithdrawalProcessor.cs
@@ -5,6 +5,8 @@
using Nethermind.Core;
using Nethermind.Core.Specs;
using Nethermind.Evm.State;
+using Nethermind.Evm.Tracing;
+using Nethermind.Int256;
using Nethermind.Logging;
namespace Nethermind.Consensus.Withdrawals;
@@ -35,10 +37,12 @@ public void ProcessWithdrawals(Block block, IReleaseSpec spec)
for (int i = 0; i < blockWithdrawals.Length; i++)
{
Withdrawal withdrawal = blockWithdrawals[i];
+ Address address = withdrawal.Address;
+ UInt256 amount = withdrawal.AmountInWei;
if (_logger.IsTrace) _logger.Trace($" {withdrawal.AmountInGwei} GWei to account {withdrawal.Address}");
// Consensus clients are using Gwei for withdrawals amount. We need to convert it to Wei before applying state changes https://github.com/ethereum/execution-apis/pull/354
- _stateProvider.AddToBalanceAndCreateIfNotExists(withdrawal.Address, withdrawal.AmountInWei, spec);
+ _stateProvider.AddToBalanceAndCreateIfNotExists(address, amount, spec);
}
}
diff --git a/src/Nethermind/Nethermind.Core.Test/Blockchain/BasicTestBlockchain.cs b/src/Nethermind/Nethermind.Core.Test/Blockchain/BasicTestBlockchain.cs
index 2e389cf1893..e6149b94d56 100644
--- a/src/Nethermind/Nethermind.Core.Test/Blockchain/BasicTestBlockchain.cs
+++ b/src/Nethermind/Nethermind.Core.Test/Blockchain/BasicTestBlockchain.cs
@@ -2,11 +2,8 @@
// SPDX-License-Identifier: LGPL-3.0-only
using System;
-using System.Collections;
-using System.Collections.Generic;
using System.Threading.Tasks;
using Autofac;
-using Nethermind.Config;
using Nethermind.Core.Test.Builders;
namespace Nethermind.Core.Test.Blockchain;
diff --git a/src/Nethermind/Nethermind.Core.Test/Blockchain/TestBlockchain.cs b/src/Nethermind/Nethermind.Core.Test/Blockchain/TestBlockchain.cs
index 077fd67b2f6..a7e4aa5d7d4 100644
--- a/src/Nethermind/Nethermind.Core.Test/Blockchain/TestBlockchain.cs
+++ b/src/Nethermind/Nethermind.Core.Test/Blockchain/TestBlockchain.cs
@@ -3,14 +3,11 @@
using System;
using System.Collections.Generic;
-using System.Configuration;
using System.Diagnostics;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using Autofac;
-using FluentAssertions;
-using Nethermind.Api;
using Nethermind.Blockchain;
using Nethermind.Blockchain.Find;
using Nethermind.Blockchain.Receipts;
@@ -43,7 +40,6 @@
using Nethermind.State;
using Nethermind.State.Repositories;
using Nethermind.TxPool;
-using Nethermind.Blockchain.Blocks;
using Nethermind.Init.Modules;
namespace Nethermind.Core.Test.Blockchain;
@@ -64,6 +60,7 @@ public class TestBlockchain : IDisposable
public IReadOnlyTxProcessingEnvFactory ReadOnlyTxProcessingEnvFactory => _fromContainer.ReadOnlyTxProcessingEnvFactory;
public IShareableTxProcessorSource ShareableTxProcessorSource => _fromContainer.ShareableTxProcessorSource;
public IBranchProcessor BranchProcessor => _fromContainer.MainProcessingContext.BranchProcessor;
+ public IBlockProcessor BlockProcessor => _fromContainer.MainProcessingContext.BlockProcessor;
public IBlockchainProcessor BlockchainProcessor => _fromContainer.MainProcessingContext.BlockchainProcessor;
public IBlockPreprocessorStep BlockPreprocessorStep => _fromContainer.BlockPreprocessorStep;
@@ -336,7 +333,7 @@ public Block Build()
state.CreateAccount(TestItem.AddressC, testConfiguration.AccountInitialValue);
byte[] code = Bytes.FromHexString("0xabcd");
- state.InsertCode(TestItem.AddressA, code, specProvider.GenesisSpec!);
+ state.InsertCode(TestItem.AddressA, code, specProvider.GenesisSpec);
state.Set(new StorageCell(TestItem.AddressA, UInt256.One), Bytes.FromHexString("0xabcdef"));
IReleaseSpec? finalSpec = specProvider.GetFinalSpec();
@@ -344,13 +341,13 @@ public Block Build()
if (finalSpec?.WithdrawalsEnabled is true)
{
state.CreateAccount(Eip7002Constants.WithdrawalRequestPredeployAddress, 0, Eip7002TestConstants.Nonce);
- state.InsertCode(Eip7002Constants.WithdrawalRequestPredeployAddress, Eip7002TestConstants.CodeHash, Eip7002TestConstants.Code, specProvider.GenesisSpec!);
+ state.InsertCode(Eip7002Constants.WithdrawalRequestPredeployAddress, Eip7002TestConstants.CodeHash, Eip7002TestConstants.Code, specProvider.GenesisSpec);
}
if (finalSpec?.ConsolidationRequestsEnabled is true)
{
state.CreateAccount(Eip7251Constants.ConsolidationRequestPredeployAddress, 0, Eip7251TestConstants.Nonce);
- state.InsertCode(Eip7251Constants.ConsolidationRequestPredeployAddress, Eip7251TestConstants.CodeHash, Eip7251TestConstants.Code, specProvider.GenesisSpec!);
+ state.InsertCode(Eip7251Constants.ConsolidationRequestPredeployAddress, Eip7251TestConstants.CodeHash, Eip7251TestConstants.Code, specProvider.GenesisSpec);
}
BlockBuilder genesisBlockBuilder = Builders.Build.A.Block.Genesis;
diff --git a/src/Nethermind/Nethermind.Core.Test/Builders/BlockBuilder.cs b/src/Nethermind/Nethermind.Core.Test/Builders/BlockBuilder.cs
index dfb6db340be..0da252922be 100644
--- a/src/Nethermind/Nethermind.Core.Test/Builders/BlockBuilder.cs
+++ b/src/Nethermind/Nethermind.Core.Test/Builders/BlockBuilder.cs
@@ -301,5 +301,11 @@ public BlockBuilder WithEncodedSize(int? encodedSize)
TestObjectInternal.EncodedSize = encodedSize;
return this;
}
+
+ public BlockBuilder WithBlockAccessListHash(Hash256? hash)
+ {
+ TestObjectInternal.Header.BlockAccessListHash = hash;
+ return this;
+ }
}
}
diff --git a/src/Nethermind/Nethermind.Core.Test/Eip2935TestConstants.cs b/src/Nethermind/Nethermind.Core.Test/Eip2935TestConstants.cs
new file mode 100644
index 00000000000..189ec1c9136
--- /dev/null
+++ b/src/Nethermind/Nethermind.Core.Test/Eip2935TestConstants.cs
@@ -0,0 +1,18 @@
+// SPDX-FileCopyrightText: 2025 Demerzel Solutions Limited
+// SPDX-License-Identifier: LGPL-3.0-only
+
+using Nethermind.Core.Crypto;
+using Nethermind.Core.Extensions;
+using Nethermind.Int256;
+
+namespace Nethermind.Core.Test;
+
+public static class Eip2935TestConstants
+{
+ public static readonly byte[] Code = Bytes.FromHexString("0x3373fffffffffffffffffffffffffffffffffffffffe14604657602036036042575f35600143038111604257611fff81430311604257611fff9006545f5260205ff35b5f5ffd5b5f35611fff60014303065500");
+
+ public static readonly byte[] InitCode = Bytes.FromHexString("0x60538060095f395ff33373fffffffffffffffffffffffffffffffffffffffe14604657602036036042575f35600143038111604257611fff81430311604257611fff9006545f5260205ff35b5f5ffd5b5f35611fff60014303065500");
+ public static readonly ValueHash256 CodeHash = ValueKeccak.Compute(Code);
+
+ public static readonly UInt256 Nonce = 1;
+}
diff --git a/src/Nethermind/Nethermind.Core.Test/Eip4788TestConstants.cs b/src/Nethermind/Nethermind.Core.Test/Eip4788TestConstants.cs
new file mode 100644
index 00000000000..a581508379e
--- /dev/null
+++ b/src/Nethermind/Nethermind.Core.Test/Eip4788TestConstants.cs
@@ -0,0 +1,17 @@
+// SPDX-FileCopyrightText: 2025 Demerzel Solutions Limited
+// SPDX-License-Identifier: LGPL-3.0-only
+
+using Nethermind.Core.Crypto;
+using Nethermind.Core.Extensions;
+using Nethermind.Int256;
+
+namespace Nethermind.Core.Test;
+
+public static class Eip4788TestConstants
+{
+ public static readonly byte[] Code = Bytes.FromHexString("0x3373fffffffffffffffffffffffffffffffffffffffe14604d57602036146024575f5ffd5b5f35801560495762001fff810690815414603c575f5ffd5b62001fff01545f5260205ff35b5f5ffd5b62001fff42064281555f359062001fff015500");
+
+ public static readonly ValueHash256 CodeHash = ValueKeccak.Compute(Code);
+
+ public static readonly UInt256 Nonce = 1;
+}
diff --git a/src/Nethermind/Nethermind.Core/Block.cs b/src/Nethermind/Nethermind.Core/Block.cs
index 8d53097d519..78931055c6a 100644
--- a/src/Nethermind/Nethermind.Core/Block.cs
+++ b/src/Nethermind/Nethermind.Core/Block.cs
@@ -12,6 +12,7 @@
using Nethermind.Core.Crypto;
using Nethermind.Core.Extensions;
using Nethermind.Int256;
+using Nethermind.Core.BlockAccessLists;
namespace Nethermind.Core;
@@ -27,10 +28,11 @@ public Block(BlockHeader header, BlockBody body)
public Block(BlockHeader header,
IEnumerable transactions,
IEnumerable uncles,
- IEnumerable? withdrawals = null)
+ IEnumerable? withdrawals = null,
+ BlockAccessList? blockAccessList = null)
{
Header = header ?? throw new ArgumentNullException(nameof(header));
- Body = new(transactions.ToArray(), uncles.ToArray(), withdrawals?.ToArray());
+ Body = new(transactions.ToArray(), uncles.ToArray(), withdrawals?.ToArray(), blockAccessList);
}
public Block(BlockHeader header) : this(
@@ -38,7 +40,8 @@ public Block(BlockHeader header) : this(
new(
null,
null,
- header.WithdrawalsRoot is null ? null : [])
+ header.WithdrawalsRoot is null ? null : [],
+ header.BlockAccessListHash is null ? null : new())
)
{ }
@@ -116,6 +119,8 @@ public Transaction[] Transactions
public Hash256? ParentBeaconBlockRoot => Header.ParentBeaconBlockRoot; // do not add setter here
public Hash256? RequestsHash => Header.RequestsHash; // do not add setter here
+ public Hash256? BlockAccessListHash => Header.BlockAccessListHash; // do not add setter here
+ public BlockAccessList? BlockAccessList => Body.BlockAccessList; // do not add setter here
[JsonIgnore]
public byte[][]? ExecutionRequests { get; set; }
@@ -126,6 +131,13 @@ public Transaction[] Transactions
[JsonIgnore]
public int? EncodedSize { get; set; }
+
+ // [JsonIgnore]
+ // public BlockAccessList? DecodedBlockAccessList { get; set; }
+
+ [JsonIgnore]
+ public byte[]? EncodedBlockAccessList { get; set; }
+
public override string ToString() => ToString(Format.Short);
public string ToString(Format format) => format switch
diff --git a/src/Nethermind/Nethermind.Core/BlockAccessLists/AccountChanges.cs b/src/Nethermind/Nethermind.Core/BlockAccessLists/AccountChanges.cs
new file mode 100644
index 00000000000..edb2e54ba85
--- /dev/null
+++ b/src/Nethermind/Nethermind.Core/BlockAccessLists/AccountChanges.cs
@@ -0,0 +1,85 @@
+
+// SPDX-FileCopyrightText: 2025 Demerzel Solutions Limited
+// SPDX-License-Identifier: LGPL-3.0-only
+
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using Nethermind.Core.Extensions;
+
+namespace Nethermind.Core.BlockAccessLists;
+
+public readonly struct AccountChanges : IEquatable
+{
+ public Address Address { get; init; }
+ public SortedDictionary StorageChanges { get; init; }
+ public SortedSet StorageReads { get; init; }
+ public SortedList BalanceChanges { get; init; }
+ public SortedList NonceChanges { get; init; }
+ public SortedList CodeChanges { get; init; }
+
+ public AccountChanges()
+ {
+ Address = Address.Zero;
+ StorageChanges = new(Bytes.Comparer);
+ StorageReads = [];
+ BalanceChanges = [];
+ NonceChanges = [];
+ CodeChanges = [];
+ }
+
+ public AccountChanges(Address address)
+ {
+ Address = address;
+ StorageChanges = new(Bytes.Comparer);
+ StorageReads = [];
+ BalanceChanges = [];
+ NonceChanges = [];
+ CodeChanges = [];
+ }
+
+ public AccountChanges(Address address, SortedDictionary storageChanges, SortedSet storageReads, SortedList balanceChanges, SortedList nonceChanges, SortedList codeChanges)
+ {
+ Address = address;
+ StorageChanges = storageChanges;
+ StorageReads = storageReads;
+ BalanceChanges = balanceChanges;
+ NonceChanges = nonceChanges;
+ CodeChanges = codeChanges;
+ }
+
+ public readonly bool Equals(AccountChanges other) =>
+ Address == other.Address &&
+ StorageChanges.Values.SequenceEqual(other.StorageChanges.Values) &&
+ StorageReads.SequenceEqual(other.StorageReads) &&
+ BalanceChanges.SequenceEqual(other.BalanceChanges) &&
+ NonceChanges.SequenceEqual(other.NonceChanges) &&
+ CodeChanges.SequenceEqual(other.CodeChanges);
+
+ public override readonly bool Equals(object? obj) =>
+ obj is AccountChanges other && Equals(other);
+
+ public override readonly int GetHashCode() =>
+ Address.GetHashCode();
+
+ public static bool operator ==(AccountChanges left, AccountChanges right) =>
+ left.Equals(right);
+
+ public static bool operator !=(AccountChanges left, AccountChanges right) =>
+ !(left == right);
+
+ public override readonly string? ToString()
+ {
+ string storageChangesList = string.Join(",\n\t\t\t", [.. StorageChanges.Values.Select(s => s.ToString())]);
+ string storageChanges = StorageChanges.Count == 0 ? "[] #storage_changes" : $"[ #storage_changes\n\t\t\t{storageChangesList}\n\t\t]";
+ string storageReadsList = string.Join(",\n\t\t\t", [.. StorageReads.Select(s => s.ToString())]);
+ string storageReads = StorageReads.Count == 0 ? "[] #storage_reads" : $"[ #storage_reads\n\t\t\t{storageReadsList}\n\t\t]";
+ string balanceChangesList = string.Join(",\n\t\t\t", [.. BalanceChanges.Values.Select(s => s.ToString())]);
+ string balanceChanges = BalanceChanges.Count == 0 ? "[] #balance_changes" : $"[ #balance_changes\n\t\t\t{balanceChangesList}\n\t\t]";
+ string nonceChangesList = string.Join(",\n\t\t\t", [.. NonceChanges.Values.Select(s => s.ToString())]);
+ string nonceChanges = NonceChanges.Count == 0 ? "[] #nonce_changes" : $"[ #nonce_changes\n\t\t\t{nonceChangesList}\n\t\t]";
+ string codeChangesList = string.Join(",\n\t\t\t", [.. CodeChanges.Values.Select(s => s.ToString())]);
+ string codeChanges = CodeChanges.Count == 0 ? "[] #code_changes" : $"[ #code_changes\n\t\t\t{codeChangesList}\n\t\t]";
+ return $"\t[\n\t\t{Address},\n\t\t{storageChanges},\n\t\t{storageReads},\n\t\t{balanceChanges},\n\t\t{nonceChanges},\n\t\t{codeChanges}\n\t]";
+ }
+}
diff --git a/src/Nethermind/Nethermind.Core/BlockAccessLists/BalanceChange.cs b/src/Nethermind/Nethermind.Core/BlockAccessLists/BalanceChange.cs
new file mode 100644
index 00000000000..67408d2a81d
--- /dev/null
+++ b/src/Nethermind/Nethermind.Core/BlockAccessLists/BalanceChange.cs
@@ -0,0 +1,32 @@
+// SPDX-FileCopyrightText: 2025 Demerzel Solutions Limited
+// SPDX-License-Identifier: LGPL-3.0-only
+
+using System;
+using Nethermind.Int256;
+
+namespace Nethermind.Core.BlockAccessLists;
+
+public readonly struct BalanceChange(ushort blockAccessIndex, UInt256 postBalance) : IEquatable, IIndexedChange
+{
+ public ushort BlockAccessIndex { get; init; } = blockAccessIndex;
+ public UInt256 PostBalance { get; init; } = postBalance;
+
+ public readonly bool Equals(BalanceChange other) =>
+ BlockAccessIndex == other.BlockAccessIndex &&
+ PostBalance == other.PostBalance;
+
+ public override readonly bool Equals(object? obj) =>
+ obj is BalanceChange other && Equals(other);
+
+ public override readonly int GetHashCode() =>
+ HashCode.Combine(BlockAccessIndex, PostBalance);
+
+ public static bool operator ==(BalanceChange left, BalanceChange right) =>
+ left.Equals(right);
+
+ public static bool operator !=(BalanceChange left, BalanceChange right) =>
+ !(left == right);
+
+ public override readonly string? ToString()
+ => $"{BlockAccessIndex}, {PostBalance}";
+}
diff --git a/src/Nethermind/Nethermind.Core/BlockAccessLists/BlockAccessList.cs b/src/Nethermind/Nethermind.Core/BlockAccessLists/BlockAccessList.cs
new file mode 100644
index 00000000000..5bd8bac1986
--- /dev/null
+++ b/src/Nethermind/Nethermind.Core/BlockAccessLists/BlockAccessList.cs
@@ -0,0 +1,427 @@
+// SPDX-FileCopyrightText: 2025 Demerzel Solutions Limited
+// SPDX-License-Identifier: LGPL-3.0-only
+
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using Nethermind.Core.Extensions;
+using Nethermind.Int256;
+
+namespace Nethermind.Core.BlockAccessLists;
+
+public struct BlockAccessList : IEquatable, IJournal
+{
+ public ushort Index = 0;
+ private readonly SortedDictionary _accountChanges;
+ private readonly Stack _changes;
+
+ public BlockAccessList()
+ {
+ _accountChanges = [];
+ _changes = new();
+ }
+
+ public BlockAccessList(SortedDictionary accountChanges)
+ {
+ _accountChanges = accountChanges;
+ _changes = new();
+ }
+
+ public readonly bool Equals(BlockAccessList other) =>
+ _accountChanges.SequenceEqual(other._accountChanges);
+
+ public override readonly bool Equals(object? obj) =>
+ obj is BlockAccessList other && Equals(other);
+
+ public override readonly int GetHashCode() =>
+ _accountChanges.Count.GetHashCode();
+
+ public static bool operator ==(BlockAccessList left, BlockAccessList right) =>
+ left.Equals(right);
+
+ public static bool operator !=(BlockAccessList left, BlockAccessList right) =>
+ !(left == right);
+
+ public readonly IEnumerable GetAccountChanges() => _accountChanges.Values;
+ public readonly AccountChanges? GetAccountChanges(Address address) => _accountChanges.TryGetValue(address, out AccountChanges value) ? value : null;
+
+ public void IncrementBlockAccessIndex()
+ {
+ _changes.Clear();
+ Index++;
+ }
+
+ public void AddBalanceChange(Address address, UInt256 before, UInt256 after)
+ {
+ if (address == Address.SystemUser)
+ {
+ return;
+ }
+
+ BalanceChange balanceChange = new()
+ {
+ BlockAccessIndex = Index,
+ PostBalance = after
+ };
+
+ if (!_accountChanges.TryGetValue(address, out AccountChanges accountChanges))
+ {
+ accountChanges = new(address);
+ _accountChanges.Add(address, accountChanges);
+ }
+
+ // don't add zero balance transfers, but add empty account changes
+ if (before == after)
+ {
+ return;
+ }
+
+ SortedList balanceChanges = accountChanges.BalanceChanges;
+
+ // selfdestruct edge case
+ // todo: check this couldn't happen in any other case than selfdestruct
+ if (after == 0 && !WasFundedPreTx(address, before))
+ {
+ if (balanceChanges.Count != 0 && balanceChanges.Last().Key == Index)
+ {
+ balanceChanges.RemoveAt(balanceChanges.Count - 1);
+ }
+ return;
+ }
+
+ if (balanceChanges.Count != 0 && balanceChanges.Last().Key == Index)
+ {
+ _changes.Push(new()
+ {
+ Address = address,
+ Type = ChangeType.BalanceChange,
+ PreviousValue = balanceChanges.Last().Value
+ });
+
+ balanceChanges.RemoveAt(balanceChanges.Count - 1);
+ }
+ else
+ {
+ _changes.Push(new()
+ {
+ Address = address,
+ Type = ChangeType.BalanceChange,
+ PreviousValue = null,
+ WasPreFunded = before != 0
+ });
+ }
+
+ balanceChanges.Add(balanceChange.BlockAccessIndex, balanceChange);
+ }
+
+ public void AddCodeChange(Address address, byte[] after)
+ {
+ CodeChange codeChange = new()
+ {
+ BlockAccessIndex = Index,
+ NewCode = after
+ };
+
+ if (!_accountChanges.TryGetValue(address, out AccountChanges accountChanges))
+ {
+ accountChanges = new(address);
+ _accountChanges.Add(address, accountChanges);
+ }
+
+ SortedList codeChanges = accountChanges.CodeChanges;
+ if (codeChanges.Count != 0 && codeChanges.Last().Key == Index)
+ {
+ _changes.Push(new()
+ {
+ Address = address,
+ Type = ChangeType.CodeChange,
+ PreviousValue = codeChanges.Last().Value
+ });
+ codeChanges.RemoveAt(codeChanges.Count - 1);
+ }
+ else
+ {
+ _changes.Push(new()
+ {
+ Address = address,
+ Type = ChangeType.CodeChange,
+ PreviousValue = null
+ });
+ }
+ codeChanges.Add(codeChange.BlockAccessIndex, codeChange);
+ }
+
+ public void AddNonceChange(Address address, ulong newNonce)
+ {
+ if (newNonce == 0)
+ {
+ return;
+ }
+
+ NonceChange nonceChange = new()
+ {
+ BlockAccessIndex = Index,
+ NewNonce = newNonce
+ };
+
+ if (!_accountChanges.TryGetValue(address, out AccountChanges accountChanges))
+ {
+ accountChanges = new(address);
+ _accountChanges.Add(address, accountChanges);
+ }
+
+ SortedList nonceChanges = accountChanges.NonceChanges;
+ if (nonceChanges.Count != 0 && nonceChanges.Last().Key == Index)
+ {
+ _changes.Push(new()
+ {
+ Address = address,
+ Type = ChangeType.NonceChange,
+ PreviousValue = nonceChanges.Last().Value
+ });
+ nonceChanges.RemoveAt(nonceChanges.Count - 1);
+ }
+ else
+ {
+ _changes.Push(new()
+ {
+ Address = address,
+ Type = ChangeType.NonceChange,
+ PreviousValue = null
+ });
+ }
+ nonceChanges.Add(nonceChange.BlockAccessIndex, nonceChange);
+ }
+
+ public readonly void AddAccountRead(Address address)
+ {
+ if (!_accountChanges.ContainsKey(address))
+ {
+ _accountChanges.Add(address, new(address));
+ }
+ }
+
+ public void AddStorageChange(Address address, UInt256 storageIndex, ReadOnlySpan before, ReadOnlySpan after)
+ {
+ if (!_accountChanges.TryGetValue(address, out AccountChanges accountChanges))
+ {
+ accountChanges = new(address);
+ _accountChanges.Add(address, accountChanges);
+ }
+
+ if (before != after)
+ {
+ Span key = new byte[32];
+ storageIndex.ToBigEndian(key);
+ StorageChange(accountChanges, key, after);
+ }
+ }
+
+ public void AddStorageChange(in StorageCell storageCell, byte[] before, byte[] after)
+ {
+ Address address = storageCell.Address;
+
+ if (!_accountChanges.TryGetValue(address, out AccountChanges accountChanges))
+ {
+ accountChanges = new(address);
+ _accountChanges.Add(address, accountChanges);
+ }
+
+ if (before is null || !Enumerable.SequenceEqual(before, after))
+ {
+ Span key = new byte[32];
+ storageCell.Index.ToBigEndian(key);
+ StorageChange(accountChanges, key, after.AsSpan());
+ }
+ }
+
+ public void AddStorageRead(in StorageCell storageCell)
+ {
+ byte[] key = new byte[32];
+ storageCell.Index.ToBigEndian(key);
+ StorageRead storageRead = new()
+ {
+ Key = new(key)
+ };
+ Address address = storageCell.Address;
+
+ if (!_accountChanges.TryGetValue(address, out AccountChanges accountChanges))
+ {
+ accountChanges = new(address);
+ _accountChanges.Add(address, accountChanges);
+ }
+
+ if (!accountChanges.StorageChanges.ContainsKey(key))
+ {
+ accountChanges.StorageReads.Add(storageRead);
+ }
+ }
+
+ public readonly void DeleteAccount(Address address)
+ {
+ if (!_accountChanges.TryGetValue(address, out AccountChanges accountChanges))
+ {
+ accountChanges = new(address);
+ _accountChanges.Add(address, accountChanges);
+ return;
+ }
+
+ foreach (byte[] key in accountChanges.StorageChanges.Keys)
+ {
+ accountChanges.StorageReads.Add(new(Bytes32.Wrap(key)));
+ }
+
+ accountChanges.StorageChanges.Clear();
+ accountChanges.NonceChanges.Clear();
+ accountChanges.CodeChanges.Clear();
+ }
+
+ private void StorageChange(AccountChanges accountChanges, in ReadOnlySpan key, in ReadOnlySpan value)
+ {
+ Span newValue = stackalloc byte[32];
+ newValue.Clear();
+ value.CopyTo(newValue[(32 - value.Length)..]);
+ StorageChange storageChange = new()
+ {
+ BlockAccessIndex = Index,
+ NewValue = new(newValue.ToArray())
+ };
+
+ byte[] storageKey = [.. key];
+ StorageChange? previousStorage = null;
+
+ if (!accountChanges.StorageChanges.TryGetValue(storageKey, out SlotChanges storageChanges))
+ {
+ storageChanges = new(storageKey);
+ }
+ else if (storageChanges.Changes is not [] && storageChanges.Changes[^1].BlockAccessIndex == Index)
+ {
+ previousStorage = storageChanges.Changes[^1];
+ storageChanges.Changes.RemoveAt(storageChanges.Changes.Count - 1);
+ }
+ storageChanges.Changes.Add(storageChange);
+ accountChanges.StorageChanges[storageKey] = storageChanges;
+ _changes.Push(new()
+ {
+ Address = accountChanges.Address,
+ Slot = storageKey,
+ Type = ChangeType.StorageChange,
+ PreviousValue = previousStorage
+ });
+
+ accountChanges.StorageReads.Remove(new(Bytes32.Wrap(storageKey)));
+ }
+
+ public readonly int TakeSnapshot()
+ => _changes.Count;
+
+ public readonly void Restore(int snapshot)
+ {
+ snapshot = int.Max(0, snapshot);
+ while (_changes.Count > snapshot)
+ {
+ Change change = _changes.Pop();
+ switch (change.Type)
+ {
+ case ChangeType.BalanceChange:
+ BalanceChange? previousBalance = change.PreviousValue is null ? null : (BalanceChange)change.PreviousValue;
+ SortedList balanceChanges = _accountChanges[change.Address].BalanceChanges;
+
+ balanceChanges.RemoveAt(balanceChanges.Count - 1);
+ if (previousBalance is not null)
+ {
+ balanceChanges.Add(Index, previousBalance.Value);
+ }
+ break;
+ case ChangeType.CodeChange:
+ CodeChange? previousCode = change.PreviousValue is null ? null : (CodeChange)change.PreviousValue;
+ SortedList codeChanges = _accountChanges[change.Address].CodeChanges;
+
+ codeChanges.RemoveAt(codeChanges.Count - 1);
+ if (previousCode is not null)
+ {
+ codeChanges.Add(Index, previousCode.Value);
+ }
+ break;
+ case ChangeType.NonceChange:
+ NonceChange? previousNode = change.PreviousValue is null ? null : (NonceChange)change.PreviousValue;
+ SortedList nonceChanges = _accountChanges[change.Address].NonceChanges;
+
+ nonceChanges.RemoveAt(nonceChanges.Count - 1);
+ if (previousNode is not null)
+ {
+ nonceChanges.Add(Index, previousNode.Value);
+ }
+ break;
+ case ChangeType.StorageChange:
+ StorageChange? previousStorage = change.PreviousValue is null ? null : (StorageChange)change.PreviousValue;
+ SlotChanges storageChanges = _accountChanges[change.Address].StorageChanges[change.Slot!];
+
+ storageChanges.Changes.RemoveAt(storageChanges.Changes.Count - 1);
+ if (previousStorage is not null)
+ {
+ storageChanges.Changes.Add(previousStorage.Value);
+ }
+
+ if (storageChanges.Changes.Count == 0)
+ {
+ _accountChanges[change.Address].StorageChanges.Remove(change.Slot!);
+ }
+ break;
+ }
+ }
+ }
+
+ public override readonly string? ToString()
+ => "[\n" + string.Join(",\n", [.. _accountChanges.Values.Select(account => account.ToString())]) + "\n]";
+
+ private readonly bool WasFundedPreTx(Address address, UInt256 before)
+ {
+ AccountChanges accountChanges = _accountChanges[address];
+ int count = accountChanges.BalanceChanges.Count;
+
+ if (count == 0)
+ {
+ // first balance change of block
+ // return balance prior to this selfdestruct instruction
+ return before == 0;
+ }
+
+ foreach (BalanceChange balanceChange in accountChanges.BalanceChanges.Values.Reverse())
+ {
+ if (balanceChange.BlockAccessIndex != Index)
+ {
+ // balance changed in previous tx in block
+ return balanceChange.PostBalance == 0;
+ }
+ }
+
+ // balance only changed within this transaction
+ foreach (Change change in _changes)
+ {
+ if (change.Type == ChangeType.BalanceChange && change.Address == address && change.PreviousValue is null)
+ {
+ // first change of this transaction & block
+ return change.WasPreFunded!.Value;
+ }
+ }
+
+ throw new Exception("Error calculting pre tx balance");
+ }
+
+ private enum ChangeType
+ {
+ BalanceChange = 0,
+ CodeChange = 1,
+ NonceChange = 2,
+ StorageChange = 3
+ }
+ private readonly struct Change
+ {
+ public Address Address { get; init; }
+ public byte[]? Slot { get; init; }
+ public ChangeType Type { get; init; }
+ public IIndexedChange? PreviousValue { get; init; }
+ public bool? WasPreFunded { get; init; }
+ }
+}
diff --git a/src/Nethermind/Nethermind.Core/BlockAccessLists/CodeChange.cs b/src/Nethermind/Nethermind.Core/BlockAccessLists/CodeChange.cs
new file mode 100644
index 00000000000..32767d88ca6
--- /dev/null
+++ b/src/Nethermind/Nethermind.Core/BlockAccessLists/CodeChange.cs
@@ -0,0 +1,43 @@
+
+// SPDX-FileCopyrightText: 2025 Demerzel Solutions Limited
+// SPDX-License-Identifier: LGPL-3.0-only
+
+using System;
+using System.Linq;
+using Nethermind.Core.Extensions;
+
+namespace Nethermind.Core.BlockAccessLists;
+
+public readonly struct CodeChange(ushort blockAccessIndex, byte[] newCode) : IEquatable, IIndexedChange
+{
+ public ushort BlockAccessIndex { get; init; } = blockAccessIndex;
+ public byte[] NewCode { get; init; } = newCode;
+
+ public readonly bool Equals(CodeChange other) =>
+ BlockAccessIndex == other.BlockAccessIndex &&
+ CompareByteArrays(NewCode, other.NewCode);
+
+ public override readonly bool Equals(object? obj) =>
+ obj is CodeChange other && Equals(other);
+
+ public override readonly int GetHashCode() =>
+ HashCode.Combine(BlockAccessIndex, NewCode);
+
+ private static bool CompareByteArrays(byte[]? left, byte[]? right) =>
+ left switch
+ {
+ null when right == null => true,
+ null => false,
+ _ when right == null => false,
+ _ => left.SequenceEqual(right)
+ };
+
+ public static bool operator ==(CodeChange left, CodeChange right) =>
+ left.Equals(right);
+
+ public static bool operator !=(CodeChange left, CodeChange right) =>
+ !(left == right);
+
+ public override readonly string? ToString()
+ => $"{BlockAccessIndex}, 0x{Bytes.ToHexString(NewCode)}";
+}
diff --git a/src/Nethermind/Nethermind.Core/BlockAccessLists/IIndexedChange.cs b/src/Nethermind/Nethermind.Core/BlockAccessLists/IIndexedChange.cs
new file mode 100644
index 00000000000..abeb28f06b3
--- /dev/null
+++ b/src/Nethermind/Nethermind.Core/BlockAccessLists/IIndexedChange.cs
@@ -0,0 +1,9 @@
+// SPDX-FileCopyrightText: 2025 Demerzel Solutions Limited
+// SPDX-License-Identifier: LGPL-3.0-only
+
+namespace Nethermind.Core.BlockAccessLists;
+
+public interface IIndexedChange
+{
+ public ushort BlockAccessIndex { get; init; }
+}
diff --git a/src/Nethermind/Nethermind.Core/BlockAccessLists/NonceChange.cs b/src/Nethermind/Nethermind.Core/BlockAccessLists/NonceChange.cs
new file mode 100644
index 00000000000..d43d6c8c8c4
--- /dev/null
+++ b/src/Nethermind/Nethermind.Core/BlockAccessLists/NonceChange.cs
@@ -0,0 +1,31 @@
+// SPDX-FileCopyrightText: 2025 Demerzel Solutions Limited
+// SPDX-License-Identifier: LGPL-3.0-only
+
+using System;
+
+namespace Nethermind.Core.BlockAccessLists;
+
+public readonly struct NonceChange(ushort blockAccessIndex, ulong newNonce) : IEquatable, IIndexedChange
+{
+ public ushort BlockAccessIndex { get; init; } = blockAccessIndex;
+ public ulong NewNonce { get; init; } = newNonce;
+
+ public readonly bool Equals(NonceChange other) =>
+ BlockAccessIndex == other.BlockAccessIndex &&
+ NewNonce == other.NewNonce;
+
+ public override readonly bool Equals(object? obj) =>
+ obj is NonceChange other && Equals(other);
+
+ public override readonly int GetHashCode() =>
+ HashCode.Combine(BlockAccessIndex, NewNonce);
+
+ public static bool operator ==(NonceChange left, NonceChange right) =>
+ left.Equals(right);
+
+ public static bool operator !=(NonceChange left, NonceChange right) =>
+ !(left == right);
+
+ public override readonly string? ToString()
+ => $"{BlockAccessIndex}, {NewNonce}";
+}
diff --git a/src/Nethermind/Nethermind.Core/BlockAccessLists/SlotChanges.cs b/src/Nethermind/Nethermind.Core/BlockAccessLists/SlotChanges.cs
new file mode 100644
index 00000000000..9f1368097ef
--- /dev/null
+++ b/src/Nethermind/Nethermind.Core/BlockAccessLists/SlotChanges.cs
@@ -0,0 +1,51 @@
+
+// SPDX-FileCopyrightText: 2025 Demerzel Solutions Limited
+// SPDX-License-Identifier: LGPL-3.0-only
+
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using Nethermind.Core.Extensions;
+
+namespace Nethermind.Core.BlockAccessLists;
+
+public readonly struct SlotChanges(byte[] slot, List changes) : IEquatable
+{
+ public byte[] Slot { get; init; } = slot;
+ public List Changes { get; init; } = changes;
+
+ public SlotChanges(byte[] slot) : this(slot, [])
+ {
+ }
+
+ public readonly bool Equals(SlotChanges other) =>
+ CompareByteArrays(Slot, other.Slot) &&
+ Changes.SequenceEqual(other.Changes);
+
+ public override readonly bool Equals(object? obj) =>
+ obj is SlotChanges other && Equals(other);
+
+ public override readonly int GetHashCode() =>
+ HashCode.Combine(Slot, Changes);
+
+ private static bool CompareByteArrays(byte[]? left, byte[]? right) =>
+ left switch
+ {
+ null when right == null => true,
+ null => false,
+ _ when right == null => false,
+ _ => left.SequenceEqual(right)
+ };
+
+ public static bool operator ==(SlotChanges left, SlotChanges right) =>
+ left.Equals(right);
+
+ public static bool operator !=(SlotChanges left, SlotChanges right) =>
+ !(left == right);
+
+ public override readonly string? ToString()
+ {
+ string changes = string.Join(", ", [.. Changes.Select(s => s.ToString())]);
+ return $"[0x{Bytes.ToHexString(Slot)}, [{changes}]]";
+ }
+}
\ No newline at end of file
diff --git a/src/Nethermind/Nethermind.Core/BlockAccessLists/StorageChange.cs b/src/Nethermind/Nethermind.Core/BlockAccessLists/StorageChange.cs
new file mode 100644
index 00000000000..d8f06595771
--- /dev/null
+++ b/src/Nethermind/Nethermind.Core/BlockAccessLists/StorageChange.cs
@@ -0,0 +1,33 @@
+
+// SPDX-FileCopyrightText: 2025 Demerzel Solutions Limited
+// SPDX-License-Identifier: LGPL-3.0-only
+
+using System;
+using System.Linq;
+
+namespace Nethermind.Core.BlockAccessLists;
+
+public readonly struct StorageChange(ushort blockAccessIndex, Bytes32 newValue) : IEquatable, IIndexedChange
+{
+ public ushort BlockAccessIndex { get; init; } = blockAccessIndex;
+ public Bytes32 NewValue { get; init; } = newValue;
+
+ public readonly bool Equals(StorageChange other) =>
+ BlockAccessIndex == other.BlockAccessIndex &&
+ NewValue.Unwrap().SequenceEqual(other.NewValue.Unwrap());
+
+ public override readonly bool Equals(object? obj) =>
+ obj is StorageChange other && Equals(other);
+
+ public override readonly int GetHashCode() =>
+ HashCode.Combine(BlockAccessIndex, NewValue);
+
+ public static bool operator ==(StorageChange left, StorageChange right) =>
+ left.Equals(right);
+
+ public static bool operator !=(StorageChange left, StorageChange right) =>
+ !(left == right);
+
+ public override readonly string? ToString()
+ => $"{BlockAccessIndex}, {NewValue}";
+}
diff --git a/src/Nethermind/Nethermind.Core/BlockAccessLists/StorageRead.cs b/src/Nethermind/Nethermind.Core/BlockAccessLists/StorageRead.cs
new file mode 100644
index 00000000000..9183c2aa12b
--- /dev/null
+++ b/src/Nethermind/Nethermind.Core/BlockAccessLists/StorageRead.cs
@@ -0,0 +1,34 @@
+// SPDX-FileCopyrightText: 2025 Demerzel Solutions Limited
+// SPDX-License-Identifier: LGPL-3.0-only
+
+using System;
+using System.Linq;
+using Nethermind.Core.Extensions;
+
+namespace Nethermind.Core.BlockAccessLists;
+
+public readonly struct StorageRead(Bytes32 key) : IEquatable, IComparable
+{
+ public Bytes32 Key { get; init; } = key;
+
+ public int CompareTo(StorageRead other)
+ => Bytes.BytesComparer.Compare(Key.Unwrap(), other.Key.Unwrap());
+
+ public readonly bool Equals(StorageRead other) =>
+ Key.Unwrap().SequenceEqual(other.Key.Unwrap());
+
+ public override readonly bool Equals(object? obj) =>
+ obj is StorageRead other && Equals(other);
+
+ public override readonly int GetHashCode() =>
+ Key.GetHashCode();
+
+ public static bool operator ==(StorageRead left, StorageRead right) =>
+ left.Equals(right);
+
+ public static bool operator !=(StorageRead left, StorageRead right) =>
+ !(left == right);
+
+ public override readonly string? ToString()
+ => $"0x{Bytes.ToHexString(Key.Unwrap())}";
+}
diff --git a/src/Nethermind/Nethermind.Core/BlockBody.cs b/src/Nethermind/Nethermind.Core/BlockBody.cs
index 619a0edd8ac..6ea1d038ef6 100644
--- a/src/Nethermind/Nethermind.Core/BlockBody.cs
+++ b/src/Nethermind/Nethermind.Core/BlockBody.cs
@@ -1,17 +1,12 @@
// SPDX-FileCopyrightText: 2022 Demerzel Solutions Limited
// SPDX-License-Identifier: LGPL-3.0-only
+using Nethermind.Core.BlockAccessLists;
+
namespace Nethermind.Core
{
- public class BlockBody
+ public class BlockBody(Transaction[]? transactions, BlockHeader[]? uncles, Withdrawal[]? withdrawals = null, BlockAccessList? blockLevelAccessList = null)
{
- public BlockBody(Transaction[]? transactions, BlockHeader[]? uncles, Withdrawal[]? withdrawals = null)
- {
- Transactions = transactions ?? [];
- Uncles = uncles ?? [];
- Withdrawals = withdrawals;
- }
-
public BlockBody() : this(null, null, null) { }
public BlockBody WithChangedTransactions(Transaction[] transactions) => new(transactions, Uncles, Withdrawals);
@@ -20,13 +15,14 @@ public BlockBody() : this(null, null, null) { }
public BlockBody WithChangedWithdrawals(Withdrawal[]? withdrawals) => new(Transactions, Uncles, withdrawals);
- public static BlockBody WithOneTransactionOnly(Transaction tx) => new(new[] { tx }, null, null);
+ public static BlockBody WithOneTransactionOnly(Transaction tx) => new([tx], null, null);
- public Transaction[] Transactions { get; internal set; }
+ public Transaction[] Transactions { get; internal set; } = transactions ?? [];
- public BlockHeader[] Uncles { get; }
+ public BlockHeader[] Uncles { get; } = uncles ?? [];
- public Withdrawal[]? Withdrawals { get; }
+ public Withdrawal[]? Withdrawals { get; } = withdrawals;
+ public BlockAccessList? BlockAccessList { get; internal set; } = blockLevelAccessList;
public bool IsEmpty => Transactions.Length == 0 && Uncles.Length == 0 && (Withdrawals?.Length ?? 0) == 0;
}
diff --git a/src/Nethermind/Nethermind.Core/BlockHeader.cs b/src/Nethermind/Nethermind.Core/BlockHeader.cs
index 3183f88d2d3..2c06a7d2629 100644
--- a/src/Nethermind/Nethermind.Core/BlockHeader.cs
+++ b/src/Nethermind/Nethermind.Core/BlockHeader.cs
@@ -72,13 +72,15 @@ public BlockHeader(
public Hash256? WithdrawalsRoot { get; set; }
public Hash256? ParentBeaconBlockRoot { get; set; }
public Hash256? RequestsHash { get; set; }
+ public Hash256? BlockAccessListHash { get; set; }
public ulong? BlobGasUsed { get; set; }
public ulong? ExcessBlobGas { get; set; }
public bool HasBody => (TxRoot is not null && TxRoot != Keccak.EmptyTreeHash)
|| (UnclesHash is not null && UnclesHash != Keccak.OfAnEmptySequenceRlp)
- || (WithdrawalsRoot is not null && WithdrawalsRoot != Keccak.EmptyTreeHash);
+ || (WithdrawalsRoot is not null && WithdrawalsRoot != Keccak.EmptyTreeHash)
+ || (BlockAccessListHash is not null && BlockAccessListHash != Keccak.OfAnEmptySequenceRlp);
- public bool HasTransactions => (TxRoot is not null && TxRoot != Keccak.EmptyTreeHash);
+ public bool HasTransactions => TxRoot is not null && TxRoot != Keccak.EmptyTreeHash;
public string SealEngineType { get; set; } = Core.SealEngineType.Ethash;
public bool IsPostMerge { get; set; }
@@ -121,6 +123,10 @@ public string ToString(string indent)
{
builder.AppendLine($"{indent}RequestsHash: {RequestsHash}");
}
+ if (BlockAccessListHash is not null)
+ {
+ builder.AppendLine($"{indent}BlockAccessListHash: {BlockAccessListHash}");
+ }
return builder.ToString();
}
diff --git a/src/Nethermind/Nethermind.Core/Eip4788Constants.cs b/src/Nethermind/Nethermind.Core/Eip4788Constants.cs
index af10e162a95..2995bceea8a 100644
--- a/src/Nethermind/Nethermind.Core/Eip4788Constants.cs
+++ b/src/Nethermind/Nethermind.Core/Eip4788Constants.cs
@@ -14,4 +14,9 @@ public static class Eip4788Constants
/// Gets the BEACON_ROOTS_ADDRESS parameter.
///
public static readonly Address BeaconRootsAddress = new("0x000F3df6D732807Ef1319fB7B8bB8522d0Beac02");
+
+ ///
+ /// The HISTORY_SERVE_WINDOW parameter.
+ ///
+ public static readonly ulong RingBufferSize = 8191;
}
diff --git a/src/Nethermind/Nethermind.Core/Eip7928Constants.cs b/src/Nethermind/Nethermind.Core/Eip7928Constants.cs
new file mode 100644
index 00000000000..cafa85007c4
--- /dev/null
+++ b/src/Nethermind/Nethermind.Core/Eip7928Constants.cs
@@ -0,0 +1,13 @@
+// SPDX-FileCopyrightText: 2025 Demerzel Solutions Limited
+// SPDX-License-Identifier: LGPL-3.0-only
+
+namespace Nethermind.Core;
+
+public static class Eip7928Constants
+{
+ public const int MaxTxs = 30_000;
+ public const int MaxSlots = 300_000;
+ public const int MaxAccounts = 300_000;
+ public const int MaxCodeSize = 24_576;
+ public const int MaxCodeChanges = 1;
+}
diff --git a/src/Nethermind/Nethermind.Core/Messages/BlockErrorMessages.cs b/src/Nethermind/Nethermind.Core/Messages/BlockErrorMessages.cs
index b0863c6da05..0732072920a 100644
--- a/src/Nethermind/Nethermind.Core/Messages/BlockErrorMessages.cs
+++ b/src/Nethermind/Nethermind.Core/Messages/BlockErrorMessages.cs
@@ -149,4 +149,16 @@ public static string InvalidDepositEventLayout(string error) =>
public static string ExceededBlockSizeLimit(int limit) =>
$"ExceededBlockSizeLimit: Exceeded block size limit of {limit} bytes.";
+
+ public const string MissingBlockLevelAccessList = "MissingBlockLevelAccessList: Must be present in block body.";
+
+ public const string InvalidBlockLevelAccessList =
+ $"InvalidBlockLevelAccessList: Unable to decode.";
+
+ public const string BlockLevelAccessListNotEnabled =
+ "BlockLevelAccessListNotEnabled: Block body cannot have block level access list.";
+
+ public static string InvalidBlockLevelAccessListRoot(Hash256 expected, Hash256 actual) =>
+ $"InvalidBlockLevelAccessListRoot: Expected {expected}, got {actual}";
+
}
diff --git a/src/Nethermind/Nethermind.Core/Specs/IReleaseSpec.cs b/src/Nethermind/Nethermind.Core/Specs/IReleaseSpec.cs
index 2960b6443c7..862b9c6ffa6 100644
--- a/src/Nethermind/Nethermind.Core/Specs/IReleaseSpec.cs
+++ b/src/Nethermind/Nethermind.Core/Specs/IReleaseSpec.cs
@@ -468,6 +468,8 @@ public interface IReleaseSpec : IEip1559Spec, IReceiptSpec
public bool RequestsEnabled => ConsolidationRequestsEnabled || WithdrawalRequestsEnabled || DepositsEnabled;
+ bool BlockLevelAccessListsEnabled => IsEip7928Enabled;
+
public bool IsEip7594Enabled { get; }
///
@@ -528,5 +530,10 @@ public interface IReleaseSpec : IEip1559Spec, IReceiptSpec
/// RIP-7728: L1SLOAD precompile for reading L1 storage from L2
///
public bool IsRip7728Enabled { get; }
+
+ ///
+ /// EIP-7928: Block-Level Access Lists
+ ///
+ public bool IsEip7928Enabled { get; }
}
}
diff --git a/src/Nethermind/Nethermind.Core/Specs/ReleaseSpecDecorator.cs b/src/Nethermind/Nethermind.Core/Specs/ReleaseSpecDecorator.cs
index 37ec9b2c548..0c0100c1cb9 100644
--- a/src/Nethermind/Nethermind.Core/Specs/ReleaseSpecDecorator.cs
+++ b/src/Nethermind/Nethermind.Core/Specs/ReleaseSpecDecorator.cs
@@ -155,4 +155,5 @@ public class ReleaseSpecDecorator(IReleaseSpec spec) : IReleaseSpec
public bool IsEip7939Enabled => spec.IsEip7939Enabled;
public bool IsEip7907Enabled => spec.IsEip7907Enabled;
public bool IsRip7728Enabled => spec.IsRip7728Enabled;
+ public bool IsEip7928Enabled => spec.IsEip7928Enabled;
}
diff --git a/src/Nethermind/Nethermind.Evm/CodeInfoRepository.cs b/src/Nethermind/Nethermind.Evm/CodeInfoRepository.cs
index 163029357db..c8dd9d0a207 100644
--- a/src/Nethermind/Nethermind.Evm/CodeInfoRepository.cs
+++ b/src/Nethermind/Nethermind.Evm/CodeInfoRepository.cs
@@ -49,7 +49,7 @@ public ICodeInfo GetCachedCodeInfo(Address codeSource, bool followDelegation, IR
private ICodeInfo InternalGetCachedCode(Address codeSource, IReleaseSpec vmSpec)
{
- ref readonly ValueHash256 codeHash = ref _worldState.GetCodeHash(codeSource);
+ ValueHash256 codeHash = _worldState.GetCodeHash(codeSource);
return InternalGetCachedCode(_worldState, in codeHash, vmSpec);
}
diff --git a/src/Nethermind/Nethermind.Evm/Instructions/EvmInstructions.Environment.cs b/src/Nethermind/Nethermind.Evm/Instructions/EvmInstructions.Environment.cs
index 38c3a328440..2c79ebbb965 100644
--- a/src/Nethermind/Nethermind.Evm/Instructions/EvmInstructions.Environment.cs
+++ b/src/Nethermind/Nethermind.Evm/Instructions/EvmInstructions.Environment.cs
@@ -507,7 +507,7 @@ public static EvmExceptionType InstructionBalance(VirtualMachine v
// Charge gas for account access. If insufficient gas remains, abort.
if (!EvmCalculations.ChargeAccountAccessGas(ref gasAvailable, vm, address)) goto OutOfGas;
- ref readonly UInt256 result = ref vm.WorldState.GetBalance(address);
+ UInt256 result = vm.WorldState.GetBalance(address);
stack.PushUInt256(in result);
return EvmExceptionType.None;
@@ -535,7 +535,7 @@ public static EvmExceptionType InstructionSelfBalance(VirtualMachi
gasAvailable -= GasCostOf.SelfBalance;
// Get balance for currently executing account.
- ref readonly UInt256 result = ref vm.WorldState.GetBalance(vm.EvmState.Env.ExecutingAccount);
+ UInt256 result = vm.WorldState.GetBalance(vm.EvmState.Env.ExecutingAccount);
stack.PushUInt256(in result);
return EvmExceptionType.None;
@@ -575,7 +575,7 @@ public static EvmExceptionType InstructionExtCodeHash(VirtualMachi
else
{
// Otherwise, push the account's code hash.
- ref readonly ValueHash256 hash = ref state.GetCodeHash(address);
+ ValueHash256 hash = state.GetCodeHash(address);
stack.Push32Bytes(in hash);
}
diff --git a/src/Nethermind/Nethermind.Evm/State/IWorldState.cs b/src/Nethermind/Nethermind.Evm/State/IWorldState.cs
index 0a50362bedd..29f3afb96ab 100644
--- a/src/Nethermind/Nethermind.Evm/State/IWorldState.cs
+++ b/src/Nethermind/Nethermind.Evm/State/IWorldState.cs
@@ -24,8 +24,8 @@ public interface IWorldState : IJournal, IReadOnlyStateProvider
IDisposable BeginScope(BlockHeader? baseBlock);
bool IsInScope { get; }
- new ref readonly UInt256 GetBalance(Address address);
- new ref readonly ValueHash256 GetCodeHash(Address address);
+ new UInt256 GetBalance(Address address);
+ new ValueHash256 GetCodeHash(Address address);
bool HasStateForBlock(BlockHeader? baseBlock);
///
@@ -114,11 +114,18 @@ public interface IWorldState : IJournal, IReadOnlyStateProvider
void AddToBalance(Address address, in UInt256 balanceChange, IReleaseSpec spec);
+ void AddToBalance(Address address, in UInt256 balanceChange, IReleaseSpec spec, out UInt256 oldBalance);
+
bool AddToBalanceAndCreateIfNotExists(Address address, in UInt256 balanceChange, IReleaseSpec spec);
+ bool AddToBalanceAndCreateIfNotExists(Address address, in UInt256 balanceChange, IReleaseSpec spec, out UInt256 oldBalance);
+
void SubtractFromBalance(Address address, in UInt256 balanceChange, IReleaseSpec spec);
+ void SubtractFromBalance(Address address, in UInt256 balanceChange, IReleaseSpec spec, out UInt256 oldBalance);
+
void IncrementNonce(Address address, UInt256 delta);
+ void IncrementNonce(Address address, UInt256 delta, out UInt256 oldNonce);
void DecrementNonce(Address address, UInt256 delta);
diff --git a/src/Nethermind/Nethermind.Evm/State/Snapshot.cs b/src/Nethermind/Nethermind.Evm/State/Snapshot.cs
index 7f5a123bc6d..50234115347 100644
--- a/src/Nethermind/Nethermind.Evm/State/Snapshot.cs
+++ b/src/Nethermind/Nethermind.Evm/State/Snapshot.cs
@@ -7,9 +7,23 @@ namespace Nethermind.Evm.State;
/// Stores state and storage snapshots (as the change index that we can revert to)
/// At the beginning and after each commit the snapshot is set to the value of EmptyPosition
///
-public readonly struct Snapshot(in Snapshot.Storage storageSnapshot, int stateSnapshot)
+public readonly struct Snapshot
{
- public static readonly Snapshot Empty = new(Storage.Empty, EmptyPosition);
+ public Snapshot(in Storage storageSnapshot, int stateSnapshot, int balSnapshot)
+ {
+ StorageSnapshot = storageSnapshot;
+ StateSnapshot = stateSnapshot;
+ BlockAccessListSnapshot = balSnapshot;
+ }
+
+ public Snapshot(in Storage storageSnapshot, int stateSnapshot)
+ {
+ StorageSnapshot = storageSnapshot;
+ StateSnapshot = stateSnapshot;
+ BlockAccessListSnapshot = EmptyPosition;
+ }
+
+ public static readonly Snapshot Empty = new(Storage.Empty, EmptyPosition, EmptyPosition);
///
/// Tracks snapshot positions for Persistent and Transient storage
@@ -22,8 +36,9 @@ public readonly struct Storage(int storageSnapshot, int transientStorageSnapshot
public int TransientStorageSnapshot { get; } = transientStorageSnapshot;
}
- public Storage StorageSnapshot { get; } = storageSnapshot;
- public int StateSnapshot { get; } = stateSnapshot;
+ public Storage StorageSnapshot { get; }
+ public int StateSnapshot { get; }
+ public int BlockAccessListSnapshot { get; }
public const int EmptyPosition = -1;
}
diff --git a/src/Nethermind/Nethermind.Evm/TransactionProcessing/TransactionProcessor.cs b/src/Nethermind/Nethermind.Evm/TransactionProcessing/TransactionProcessor.cs
index 2d57b79f10d..f1c084fa67b 100644
--- a/src/Nethermind/Nethermind.Evm/TransactionProcessing/TransactionProcessor.cs
+++ b/src/Nethermind/Nethermind.Evm/TransactionProcessing/TransactionProcessor.cs
@@ -149,7 +149,7 @@ protected virtual TransactionResult Execute(Transaction tx, ITxTracer tracer, Ex
// restore is CallAndRestore - previous call, we will restore state after the execution
bool restore = opts.HasFlag(ExecutionOptions.Restore);
- // commit - is for standard execute, we will commit thee state after execution
+ // commit - is for standard execute, we will commit the state after execution
// !commit - is for build up during block production, we won't commit state after each transaction to support rollbacks
// we commit only after all block is constructed
bool commit = opts.HasFlag(ExecutionOptions.Commit) || (!opts.HasFlag(ExecutionOptions.SkipValidation) && !spec.IsEip658Enabled);
@@ -885,6 +885,7 @@ protected virtual GasConsumed Refund(Transaction tx, BlockHeader header, IReleas
spentGas = Math.Max(spentGas, floorGas);
// If noValidation we didn't charge for gas, so do not refund
+ // report to tracer??
if (!opts.HasFlag(ExecutionOptions.SkipValidation))
WorldState.AddToBalance(tx.SenderAddress!, (ulong)(tx.GasLimit - spentGas) * gasPrice, spec);
diff --git a/src/Nethermind/Nethermind.Init/PruningTrieStateFactory.cs b/src/Nethermind/Nethermind.Init/PruningTrieStateFactory.cs
index 55707205ddb..3edcaa1615a 100644
--- a/src/Nethermind/Nethermind.Init/PruningTrieStateFactory.cs
+++ b/src/Nethermind/Nethermind.Init/PruningTrieStateFactory.cs
@@ -11,7 +11,6 @@
using Nethermind.Blockchain.Utils;
using Nethermind.Config;
using Nethermind.Core;
-using Nethermind.Core.Crypto;
using Nethermind.Core.Exceptions;
using Nethermind.Core.Extensions;
using Nethermind.Core.Timers;
@@ -52,7 +51,7 @@ ILogManager logManager
public (IWorldStateManager, IPruningTrieStateAdminRpcModule) Build()
{
- CompositePruningTrigger compositePruningTrigger = new CompositePruningTrigger();
+ CompositePruningTrigger compositePruningTrigger = new();
IPruningTrieStore trieStore = mainPruningTrieStoreFactory.PruningTrieStore;
ITrieStore mainWorldTrieStore = trieStore;
@@ -82,8 +81,8 @@ ILogManager logManager
// Main thread should only read from prewarm caches, not spend extra time updating them.
populatePreBlockCache: false);
- IWorldStateManager stateManager = new WorldStateManager(
- worldState,
+ WorldStateManager stateManager = new(
+ new TracedAccessWorldState(worldState),
trieStore,
dbProvider,
logManager,
@@ -106,10 +105,10 @@ ILogManager logManager
preBlockCaches
);
- var verifyTrieStarter = new VerifyTrieStarter(stateManager, processExit!, logManager);
+ VerifyTrieStarter verifyTrieStarter = new(stateManager, processExit!, logManager);
ManualPruningTrigger pruningTrigger = new();
compositePruningTrigger.Add(pruningTrigger);
- PruningTrieStateAdminRpcModule adminRpcModule = new PruningTrieStateAdminRpcModule(
+ PruningTrieStateAdminRpcModule adminRpcModule = new(
pruningTrigger,
blockTree,
stateManager.GlobalStateReader,
diff --git a/src/Nethermind/Nethermind.Merge.AuRa/Withdrawals/AuraWithdrawalProcessor.cs b/src/Nethermind/Nethermind.Merge.AuRa/Withdrawals/AuraWithdrawalProcessor.cs
index 85776645e9a..7e43af445ad 100644
--- a/src/Nethermind/Nethermind.Merge.AuRa/Withdrawals/AuraWithdrawalProcessor.cs
+++ b/src/Nethermind/Nethermind.Merge.AuRa/Withdrawals/AuraWithdrawalProcessor.cs
@@ -9,6 +9,7 @@
using Nethermind.Core.Collections;
using Nethermind.Core.Specs;
using Nethermind.Evm;
+using Nethermind.Evm.Tracing;
using Nethermind.Int256;
using Nethermind.Logging;
using Nethermind.Merge.AuRa.Contracts;
@@ -50,6 +51,7 @@ public void ProcessWithdrawals(Block block, IReleaseSpec spec)
if (_logger.IsTrace) _logger.Trace($" {(BigInteger)withdrawal.AmountInWei / (BigInteger)Unit.Ether:N3}GNO to account {withdrawal.Address}");
}
+ // todo: trace state changes
try
{
_contract.ExecuteWithdrawals(block.Header, _failedWithdrawalsMaxCount, amounts, addresses);
diff --git a/src/Nethermind/Nethermind.Merge.Plugin.Test/ExecutionRequestsProcessorMock.cs b/src/Nethermind/Nethermind.Merge.Plugin.Test/ExecutionRequestsProcessorMock.cs
index e1d8404c53d..8ad25a2ec4e 100644
--- a/src/Nethermind/Nethermind.Merge.Plugin.Test/ExecutionRequestsProcessorMock.cs
+++ b/src/Nethermind/Nethermind.Merge.Plugin.Test/ExecutionRequestsProcessorMock.cs
@@ -8,7 +8,6 @@
using Nethermind.Core.ExecutionRequest;
using Nethermind.Core.Specs;
using Nethermind.Core.Test.Builders;
-using Nethermind.Evm;
using Nethermind.Evm.State;
namespace Nethermind.Merge.Plugin.Test;
diff --git a/src/Nethermind/Nethermind.Merge.Plugin/Data/ExecutionPayload.cs b/src/Nethermind/Nethermind.Merge.Plugin/Data/ExecutionPayload.cs
index d0eae608228..11964709978 100644
--- a/src/Nethermind/Nethermind.Merge.Plugin/Data/ExecutionPayload.cs
+++ b/src/Nethermind/Nethermind.Merge.Plugin/Data/ExecutionPayload.cs
@@ -12,6 +12,7 @@
using Nethermind.State.Proofs;
using System.Text.Json.Serialization;
using Nethermind.Core.ExecutionRequest;
+using Nethermind.Core.BlockAccessLists;
namespace Nethermind.Merge.Plugin.Data;
@@ -51,6 +52,8 @@ public class ExecutionPayload : IForkValidator, IExecutionPayloadParams, IExecut
public ulong Timestamp { get; set; }
+ public byte[]? BlockAccessList { get; set; } = [];
+
protected byte[][] _encodedTransactions = [];
///
@@ -124,6 +127,7 @@ public byte[][] Transactions
Timestamp = block.Timestamp,
BaseFeePerGas = block.BaseFeePerGas,
Withdrawals = block.Withdrawals,
+ BlockAccessList = block.EncodedBlockAccessList!,
};
executionPayload.SetTransactions(block.Transactions);
return executionPayload;
@@ -140,7 +144,7 @@ public virtual BlockDecodingResult TryGetBlock(UInt256? totalDifficulty = null)
TransactionDecodingResult transactions = TryGetTransactions();
if (transactions.Error is not null)
{
- return new BlockDecodingResult(transactions.Error);
+ return new(transactions.Error);
}
BlockHeader header = new(
@@ -166,9 +170,25 @@ public virtual BlockDecodingResult TryGetBlock(UInt256? totalDifficulty = null)
TotalDifficulty = totalDifficulty,
TxRoot = TxTrie.CalculateRoot(transactions.Transactions),
WithdrawalsRoot = BuildWithdrawalsRoot(),
+ BlockAccessListHash = BlockAccessList is null || BlockAccessList.Length == 0 ? null : new(ValueKeccak.Compute(BlockAccessList).Bytes)
};
- return new BlockDecodingResult(new Block(header, transactions.Transactions, Array.Empty(), Withdrawals));
+ BlockAccessList? blockAccessList = null;
+
+ if (BlockAccessList is not null)
+ {
+ try
+ {
+ blockAccessList = Rlp.Decode(BlockAccessList);
+ }
+ catch (RlpException)
+ {
+ return new("Could not decode block access list.");
+ }
+ }
+
+ Block block = new(header, transactions.Transactions, Array.Empty(), Withdrawals, blockAccessList);
+ return new(block);
}
protected virtual Hash256? BuildWithdrawalsRoot()
diff --git a/src/Nethermind/Nethermind.Merge.Plugin/EngineRpcModule.Amsterdam.cs b/src/Nethermind/Nethermind.Merge.Plugin/EngineRpcModule.Amsterdam.cs
new file mode 100644
index 00000000000..ad49280cd17
--- /dev/null
+++ b/src/Nethermind/Nethermind.Merge.Plugin/EngineRpcModule.Amsterdam.cs
@@ -0,0 +1,16 @@
+// SPDX-FileCopyrightText: 2025 Demerzel Solutions Limited
+// SPDX-License-Identifier: LGPL-3.0-only
+
+using System.Threading.Tasks;
+using Nethermind.Consensus;
+using Nethermind.Core.Crypto;
+using Nethermind.JsonRpc;
+using Nethermind.Merge.Plugin.Data;
+
+namespace Nethermind.Merge.Plugin;
+
+public partial class EngineRpcModule : IEngineRpcModule
+{
+ public Task> engine_newPayloadV5(ExecutionPayloadV3 executionPayload, byte[]?[] blobVersionedHashes, Hash256? parentBeaconBlockRoot, byte[][]? executionRequests)
+ => NewPayload(new ExecutionPayloadParams(executionPayload, blobVersionedHashes, parentBeaconBlockRoot, executionRequests), EngineApiVersions.Amsterdam);
+}
diff --git a/src/Nethermind/Nethermind.Merge.Plugin/IEngineRpcModule.Amsterdam.cs b/src/Nethermind/Nethermind.Merge.Plugin/IEngineRpcModule.Amsterdam.cs
new file mode 100644
index 00000000000..f480e28e18a
--- /dev/null
+++ b/src/Nethermind/Nethermind.Merge.Plugin/IEngineRpcModule.Amsterdam.cs
@@ -0,0 +1,20 @@
+// SPDX-FileCopyrightText: 2025 Demerzel Solutions Limited
+// SPDX-License-Identifier: LGPL-3.0-only
+
+using System.Collections.Generic;
+using System.Threading.Tasks;
+using Nethermind.Core.Crypto;
+using Nethermind.JsonRpc;
+using Nethermind.JsonRpc.Modules;
+using Nethermind.Merge.Plugin.Data;
+
+namespace Nethermind.Merge.Plugin;
+
+public partial interface IEngineRpcModule : IRpcModule
+{
+ [JsonRpcMethod(
+ Description = "Returns the most recent version of an execution payload and fees with respect to the transaction set contained by the mempool.",
+ IsSharable = true,
+ IsImplemented = true)]
+ Task> engine_newPayloadV5(ExecutionPayloadV3 executionPayload, byte[]?[] blobVersionedHashes, Hash256? parentBeaconBlockRoot, byte[][]? executionRequests);
+}
diff --git a/src/Nethermind/Nethermind.Merge.Plugin/MergePlugin.cs b/src/Nethermind/Nethermind.Merge.Plugin/MergePlugin.cs
index 92d6a2961e0..f5d933da50d 100644
--- a/src/Nethermind/Nethermind.Merge.Plugin/MergePlugin.cs
+++ b/src/Nethermind/Nethermind.Merge.Plugin/MergePlugin.cs
@@ -22,7 +22,6 @@
using Nethermind.Core;
using Nethermind.Core.Crypto;
using Nethermind.Core.Exceptions;
-using Nethermind.Core.Timers;
using Nethermind.Db;
using Nethermind.Facade.Proxy;
using Nethermind.HealthChecks;
@@ -329,6 +328,7 @@ protected override void Load(ContainerBuilder builder)
.AddSingleton()
.AddSingleton>, GetBlobsHandler>()
.AddSingleton?>, GetBlobsHandlerV2>()
+ .AddSingleton()
.AddSingleton()
.AddSingleton((ctx) =>
diff --git a/src/Nethermind/Nethermind.Merge.Plugin/NoEngineRequestTracker.cs b/src/Nethermind/Nethermind.Merge.Plugin/NoEngineRequestTracker.cs
new file mode 100644
index 00000000000..e0d1b606bd3
--- /dev/null
+++ b/src/Nethermind/Nethermind.Merge.Plugin/NoEngineRequestTracker.cs
@@ -0,0 +1,17 @@
+// SPDX-FileCopyrightText: 2025 Demerzel Solutions Limited
+// SPDX-License-Identifier: LGPL-3.0-only
+
+using System.Threading.Tasks;
+using Nethermind.Api;
+
+namespace Nethermind.Merge.Plugin;
+
+public class NoEngineRequestsTracker : IEngineRequestsTracker
+{
+ public void OnForkchoiceUpdatedCalled() { }
+
+ public void OnNewPayloadCalled() { }
+
+ public Task StartAsync()
+ => Task.CompletedTask;
+}
diff --git a/src/Nethermind/Nethermind.Optimism/OptimismWithdrawals.cs b/src/Nethermind/Nethermind.Optimism/OptimismWithdrawals.cs
index 7cdcc83d9dd..c465ff0b963 100644
--- a/src/Nethermind/Nethermind.Optimism/OptimismWithdrawals.cs
+++ b/src/Nethermind/Nethermind.Optimism/OptimismWithdrawals.cs
@@ -7,6 +7,7 @@
using Nethermind.Core.Crypto;
using Nethermind.Core.Specs;
using Nethermind.Evm.State;
+using Nethermind.Evm.Tracing;
using Nethermind.Logging;
namespace Nethermind.Optimism;
@@ -18,28 +19,21 @@ namespace Nethermind.Optimism;
/// Constructed over the world state so that it can construct the proper withdrawals hash just before commitment.
/// https://github.com/ethereum-optimism/specs/blob/main/specs/protocol/isthmus/exec-engine.md#l2tol1messagepasser-storage-root-in-header
///
-public class OptimismWithdrawalProcessor : IWithdrawalProcessor
+public class OptimismWithdrawalProcessor(IWorldState state, ILogManager logManager, IOptimismSpecHelper specHelper) : IWithdrawalProcessor
{
- private readonly IWorldState _state;
- private readonly IOptimismSpecHelper _specHelper;
- private readonly ILogger _logger;
-
- public OptimismWithdrawalProcessor(IWorldState state, ILogManager logManager, IOptimismSpecHelper specHelper)
- {
- _state = state;
- _specHelper = specHelper;
- _logger = logManager.GetClassLogger();
- }
+ private readonly IWorldState _state = state;
+ private readonly IOptimismSpecHelper _specHelper = specHelper;
+ private readonly ILogger _logger = logManager.GetClassLogger();
public void ProcessWithdrawals(Block block, IReleaseSpec spec)
{
- var header = block.Header;
+ BlockHeader header = block.Header;
if (_specHelper.IsIsthmus(header))
{
_state.Commit(spec, commitRoots: true);
- if (_state.TryGetAccount(PreDeploys.L2ToL1MessagePasser, out var account))
+ if (_state.TryGetAccount(PreDeploys.L2ToL1MessagePasser, out AccountStruct account))
{
if (_logger.IsDebug)
_logger.Debug($"Setting {nameof(BlockHeader.WithdrawalsRoot)} to {account.StorageRoot}");
diff --git a/src/Nethermind/Nethermind.Serialization.Rlp/BlockBodyDecoder.cs b/src/Nethermind/Nethermind.Serialization.Rlp/BlockBodyDecoder.cs
index f7fc6028159..b6dfa96aa3c 100644
--- a/src/Nethermind/Nethermind.Serialization.Rlp/BlockBodyDecoder.cs
+++ b/src/Nethermind/Nethermind.Serialization.Rlp/BlockBodyDecoder.cs
@@ -3,6 +3,8 @@
using System;
using Nethermind.Core;
+using Nethermind.Core.BlockAccessLists;
+using Nethermind.Serialization.Rlp.Eip7928;
namespace Nethermind.Serialization.Rlp;
@@ -11,6 +13,7 @@ public class BlockBodyDecoder : IRlpValueDecoder, IRlpStreamDecoder _instance ??= new BlockBodyDecoder();
@@ -27,17 +30,19 @@ public int GetLength(BlockBody item, RlpBehaviors rlpBehaviors)
public int GetBodyLength(BlockBody b)
{
- (int txs, int uncles, int? withdrawals) = GetBodyComponentLength(b);
+ (int txs, int uncles, int? withdrawals, int? blockAccessList) = GetBodyComponentLength(b);
return Rlp.LengthOfSequence(txs) +
Rlp.LengthOfSequence(uncles) +
- (withdrawals is not null ? Rlp.LengthOfSequence(withdrawals.Value) : 0);
+ (withdrawals is not null ? Rlp.LengthOfSequence(withdrawals.Value) : 0) +
+ (blockAccessList is not null ? Rlp.LengthOfSequence(blockAccessList.Value) : 0);
}
- public (int Txs, int Uncles, int? Withdrawals) GetBodyComponentLength(BlockBody b) =>
+ public (int Txs, int Uncles, int? Withdrawals, int? BlockAccessList) GetBodyComponentLength(BlockBody b) =>
(
GetTxLength(b.Transactions),
GetUnclesLength(b.Uncles),
- b.Withdrawals is not null ? GetWithdrawalsLength(b.Withdrawals) : null
+ b.Withdrawals is not null ? GetWithdrawalsLength(b.Withdrawals) : null,
+ b.BlockAccessList is not null ? _blockAccessListDecoder.GetLength(b.BlockAccessList.Value, RlpBehaviors.None) : null
);
private int GetTxLength(Transaction[] transactions)
@@ -91,21 +96,27 @@ private int GetWithdrawalsLength(Withdrawal[] withdrawals)
return DecodeUnwrapped(ref ctx, startingPosition + sequenceLength);
}
- public BlockBody? DecodeUnwrapped(ref Rlp.ValueDecoderContext ctx, int lastPosition)
+ public BlockBody? DecodeUnwrapped(ref Rlp.ValueDecoderContext ctx, int lastPosition, RlpBehaviors rlpBehaviors = RlpBehaviors.None)
{
-
// quite significant allocations (>0.5%) here based on a sample 3M blocks sync
// (just on these delegates)
Transaction[] transactions = ctx.DecodeArray(_txDecoder);
BlockHeader[] uncles = ctx.DecodeArray(_headerDecoder);
Withdrawal[]? withdrawals = null;
+ BlockAccessList? blockAccessList = null;
- if (ctx.PeekNumberOfItemsRemaining(lastPosition, 1) > 0)
+ int remaining = ctx.PeekNumberOfItemsRemaining(lastPosition, 2);
+ if (remaining > 0)
{
withdrawals = ctx.DecodeArray(_withdrawalDecoderDecoder);
}
- return new BlockBody(transactions, uncles, withdrawals);
+ if (remaining > 1)
+ {
+ blockAccessList = _blockAccessListDecoder.Decode(ref ctx, rlpBehaviors);
+ }
+
+ return new BlockBody(transactions, uncles, withdrawals, blockAccessList);
}
public BlockBody Decode(RlpStream rlpStream, RlpBehaviors rlpBehaviors = RlpBehaviors.None)
@@ -141,5 +152,10 @@ public void Encode(RlpStream stream, BlockBody body, RlpBehaviors rlpBehaviors =
stream.Encode(withdrawal);
}
}
+
+ if (body.BlockAccessList is not null)
+ {
+ stream.Encode(body.BlockAccessList.Value);
+ }
}
}
diff --git a/src/Nethermind/Nethermind.Serialization.Rlp/BlockDecoder.cs b/src/Nethermind/Nethermind.Serialization.Rlp/BlockDecoder.cs
index 50704bb6b42..87fff22af6f 100644
--- a/src/Nethermind/Nethermind.Serialization.Rlp/BlockDecoder.cs
+++ b/src/Nethermind/Nethermind.Serialization.Rlp/BlockDecoder.cs
@@ -34,18 +34,19 @@ public BlockDecoder() : this(new HeaderDecoder()) { }
return decoded;
}
- private (int Total, int Txs, int Uncles, int? Withdrawals) GetContentLength(Block item, RlpBehaviors rlpBehaviors)
+ private (int Total, int Txs, int Uncles, int? Withdrawals, int? BlockAccessList) GetContentLength(Block item, RlpBehaviors rlpBehaviors)
{
int headerLength = _headerDecoder.GetLength(item.Header, rlpBehaviors);
- (int txs, int uncles, int? withdrawals) = _blockBodyDecoder.GetBodyComponentLength(item.Body);
+ (int txs, int uncles, int? withdrawals, int? blockAccessList) = _blockBodyDecoder.GetBodyComponentLength(item.Body);
int contentLength =
headerLength +
Rlp.LengthOfSequence(txs) +
Rlp.LengthOfSequence(uncles) +
- (withdrawals is not null ? Rlp.LengthOfSequence(withdrawals.Value) : 0);
- return (contentLength, txs, uncles, withdrawals);
+ (withdrawals is not null ? Rlp.LengthOfSequence(withdrawals.Value) : 0) +
+ (blockAccessList is not null ? Rlp.LengthOfSequence(blockAccessList.Value) : 0);
+ return (contentLength, txs, uncles, withdrawals, blockAccessList);
}
public int GetLength(Block? item, RlpBehaviors rlpBehaviors)
@@ -74,7 +75,8 @@ public int GetLength(Block? item, RlpBehaviors rlpBehaviors)
Block block = new(header, body)
{
- EncodedSize = Rlp.LengthOfSequence(sequenceLength)
+ EncodedSize = Rlp.LengthOfSequence(sequenceLength),
+ EncodedBlockAccessList = body.BlockAccessList is null ? null : Rlp.Encode(body.BlockAccessList.Value).Bytes // todo: possible without reencoding?
};
return block;
@@ -100,7 +102,7 @@ public void Encode(RlpStream stream, Block? item, RlpBehaviors rlpBehaviors = Rl
return;
}
- (int contentLength, int txsLength, int unclesLength, int? withdrawalsLength) = GetContentLength(item, rlpBehaviors);
+ (int contentLength, int txsLength, int unclesLength, int? withdrawalsLength, int? _) = GetContentLength(item, rlpBehaviors);
stream.StartSequence(contentLength);
stream.Encode(item.Header);
stream.StartSequence(txsLength);
@@ -124,6 +126,11 @@ public void Encode(RlpStream stream, Block? item, RlpBehaviors rlpBehaviors = Rl
stream.Encode(item.Withdrawals[i]);
}
}
+
+ if (item.BlockAccessList is not null)
+ {
+ stream.Encode(item.BlockAccessList.Value);
+ }
}
public static ReceiptRecoveryBlock? DecodeToReceiptRecoveryBlock(MemoryManager? memoryManager, Memory memory, RlpBehaviors rlpBehaviors)
diff --git a/src/Nethermind/Nethermind.Serialization.Rlp/Eip7928/AccountChangesDecoder.cs b/src/Nethermind/Nethermind.Serialization.Rlp/Eip7928/AccountChangesDecoder.cs
new file mode 100644
index 00000000000..ea2d5f1a124
--- /dev/null
+++ b/src/Nethermind/Nethermind.Serialization.Rlp/Eip7928/AccountChangesDecoder.cs
@@ -0,0 +1,179 @@
+// SPDX-FileCopyrightText: 2025 Demerzel Solutions Limited
+// SPDX-License-Identifier: LGPL-3.0-only
+
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using Nethermind.Core;
+using Nethermind.Core.BlockAccessLists;
+using Nethermind.Core.Extensions;
+
+namespace Nethermind.Serialization.Rlp.Eip7928;
+
+public class AccountChangesDecoder : IRlpValueDecoder, IRlpStreamDecoder
+{
+ private static AccountChangesDecoder? _instance = null;
+ public static AccountChangesDecoder Instance => _instance ??= new();
+
+ public AccountChanges Decode(ref Rlp.ValueDecoderContext ctx, RlpBehaviors rlpBehaviors)
+ {
+ // var tmp = ctx.Data[ctx.Position..].ToArray();
+
+ // Console.WriteLine("account change uncut:");
+ // Console.WriteLine(Bytes.ToHexString(tmp));
+
+ int length = ctx.ReadSequenceLength();
+ int check = length + ctx.Position;
+
+ // tmp = tmp[..(length + 1)];
+ // Console.WriteLine("account change:" + length);
+ // Console.WriteLine(Bytes.ToHexString(tmp));
+
+ Address address = ctx.DecodeAddress();
+
+ SlotChanges[] slotChanges = ctx.DecodeArray(SlotChangesDecoder.Instance);
+ byte[]? lastSlot = null;
+ SortedDictionary slotChangesMap = new(slotChanges.ToDictionary(s =>
+ {
+ byte[] slot = s.Slot;
+ if (lastSlot is not null && Bytes.BytesComparer.Compare(slot, lastSlot) <= 0)
+ {
+ throw new RlpException("Storage changes were in incorrect order.");
+ }
+ lastSlot = slot;
+ return slot;
+ }, s => s), Bytes.Comparer);
+
+ StorageRead[] storageReads = ctx.DecodeArray(StorageReadDecoder.Instance);
+ SortedSet storareReadsList = [];
+ StorageRead? lastRead = null;
+ foreach (StorageRead storageRead in storageReads)
+ {
+ if (lastRead is not null && storageRead.CompareTo(lastRead.Value) <= 0)
+ {
+ throw new RlpException("Storage reads were in incorrect order.");
+ }
+ storareReadsList.Add(storageRead);
+ lastRead = storageRead;
+ }
+
+ BalanceChange[] balanceChanges = ctx.DecodeArray(BalanceChangeDecoder.Instance);
+ ushort lastIndex = 0;
+ SortedList balanceChangesList = new(balanceChanges.ToDictionary(s =>
+ {
+ ushort index = s.BlockAccessIndex;
+ if (index <= lastIndex)
+ {
+ throw new RlpException("Balance changes were in incorrect order.");
+ }
+ lastIndex = index;
+ return index;
+ }, s => s));
+
+ lastIndex = 0;
+ NonceChange[] nonceChanges = ctx.DecodeArray(NonceChangeDecoder.Instance);
+ SortedList nonceChangesList = new(nonceChanges.ToDictionary(s =>
+ {
+ ushort index = s.BlockAccessIndex;
+ if (index <= lastIndex)
+ {
+ throw new RlpException("Nonce changes were in incorrect order.");
+ }
+ lastIndex = index;
+ return index;
+ }, s => s));
+
+ CodeChange[] codeChanges = ctx.DecodeArray(CodeChangeDecoder.Instance);
+
+ if (codeChanges.Length > Eip7928Constants.MaxCodeChanges)
+ {
+ throw new RlpException("Number of code changes exceeded maximum.");
+ }
+
+ lastIndex = 0;
+ SortedList codeChangesList = new(codeChanges.ToDictionary(s =>
+ {
+ ushort index = s.BlockAccessIndex;
+ if (index <= lastIndex)
+ {
+ throw new RlpException("Code changes were in incorrect order.");
+ }
+ lastIndex = index;
+ return index;
+ }, s => s));
+
+ return new()
+ {
+ Address = address,
+ StorageChanges = slotChangesMap,
+ StorageReads = storareReadsList,
+ BalanceChanges = balanceChangesList,
+ NonceChanges = nonceChangesList,
+ CodeChanges = codeChangesList
+ };
+ }
+
+ public int GetLength(AccountChanges item, RlpBehaviors rlpBehaviors)
+ => Rlp.LengthOfSequence(GetContentLength(item, rlpBehaviors));
+
+ public AccountChanges Decode(RlpStream rlpStream, RlpBehaviors rlpBehaviors)
+ {
+ Span span = rlpStream.PeekNextItem();
+ Rlp.ValueDecoderContext ctx = new(span);
+ AccountChanges res = Decode(ref ctx, rlpBehaviors);
+ rlpStream.SkipItem();
+
+ return res;
+ }
+
+ public void Encode(RlpStream stream, AccountChanges item, RlpBehaviors rlpBehaviors = RlpBehaviors.None)
+ {
+ stream.StartSequence(GetContentLength(item, rlpBehaviors));
+ stream.Encode(item.Address);
+ stream.EncodeArray([.. item.StorageChanges.Values], rlpBehaviors);
+ stream.EncodeArray([.. item.StorageReads], rlpBehaviors);
+ stream.EncodeArray([.. item.BalanceChanges.Values], rlpBehaviors);
+ stream.EncodeArray([.. item.NonceChanges.Values], rlpBehaviors);
+ stream.EncodeArray([.. item.CodeChanges.Values], rlpBehaviors);
+ }
+
+ private static int GetContentLength(AccountChanges item, RlpBehaviors rlpBehaviors)
+ {
+ int slotChangesLen = 0;
+ foreach (SlotChanges slotChanges in item.StorageChanges.Values)
+ {
+ slotChangesLen += SlotChangesDecoder.Instance.GetLength(slotChanges, rlpBehaviors);
+ }
+ slotChangesLen = Rlp.LengthOfSequence(slotChangesLen);
+
+ int storageReadsLen = 0;
+ foreach (StorageRead storageRead in item.StorageReads)
+ {
+ storageReadsLen += StorageReadDecoder.Instance.GetLength(storageRead, rlpBehaviors);
+ }
+ storageReadsLen = Rlp.LengthOfSequence(storageReadsLen);
+
+ int balanceChangesLen = 0;
+ foreach (BalanceChange balanceChange in item.BalanceChanges.Values)
+ {
+ balanceChangesLen += BalanceChangeDecoder.Instance.GetLength(balanceChange, rlpBehaviors);
+ }
+ balanceChangesLen = Rlp.LengthOfSequence(balanceChangesLen);
+
+ int nonceChangesLen = 0;
+ foreach (NonceChange nonceChange in item.NonceChanges.Values)
+ {
+ nonceChangesLen += NonceChangeDecoder.Instance.GetLength(nonceChange, rlpBehaviors);
+ }
+ nonceChangesLen = Rlp.LengthOfSequence(nonceChangesLen);
+
+ int codeChangesLen = 0;
+ foreach (CodeChange codeChange in item.CodeChanges.Values)
+ {
+ codeChangesLen += CodeChangeDecoder.Instance.GetLength(codeChange, rlpBehaviors);
+ }
+ codeChangesLen = Rlp.LengthOfSequence(codeChangesLen);
+
+ return Rlp.LengthOfAddressRlp + slotChangesLen + storageReadsLen + balanceChangesLen + nonceChangesLen + codeChangesLen;
+ }
+}
diff --git a/src/Nethermind/Nethermind.Serialization.Rlp/Eip7928/BalanceChangeDecoder.cs b/src/Nethermind/Nethermind.Serialization.Rlp/Eip7928/BalanceChangeDecoder.cs
new file mode 100644
index 00000000000..ba570dec95b
--- /dev/null
+++ b/src/Nethermind/Nethermind.Serialization.Rlp/Eip7928/BalanceChangeDecoder.cs
@@ -0,0 +1,62 @@
+// SPDX-FileCopyrightText: 2025 Demerzel Solutions Limited
+// SPDX-License-Identifier: LGPL-3.0-only
+
+using System;
+using Nethermind.Core.BlockAccessLists;
+using Nethermind.Core.Extensions;
+
+namespace Nethermind.Serialization.Rlp.Eip7928;
+
+public class BalanceChangeDecoder : IRlpValueDecoder, IRlpStreamDecoder
+{
+ private static BalanceChangeDecoder? _instance = null;
+ public static BalanceChangeDecoder Instance => _instance ??= new();
+
+ public int GetLength(BalanceChange item, RlpBehaviors rlpBehaviors)
+ => Rlp.LengthOfSequence(GetContentLength(item, rlpBehaviors));
+
+ public BalanceChange Decode(ref Rlp.ValueDecoderContext ctx, RlpBehaviors rlpBehaviors)
+ {
+ // var tmp = ctx.Data[ctx.Position..].ToArray();
+
+ int length = ctx.ReadSequenceLength();
+ int check = length + ctx.Position;
+
+ // tmp = tmp[..(length + 1)];
+ // Console.WriteLine("balance change:" + length);
+ // Console.WriteLine(Bytes.ToHexString(tmp));
+
+ BalanceChange balanceChange = new()
+ {
+ BlockAccessIndex = ctx.DecodeUShort(),
+ PostBalance = ctx.DecodeUInt256()
+ };
+
+ if (!rlpBehaviors.HasFlag(RlpBehaviors.AllowExtraBytes))
+ {
+ ctx.Check(check);
+ }
+
+ return balanceChange;
+ }
+
+ public BalanceChange Decode(RlpStream rlpStream, RlpBehaviors rlpBehaviors)
+ {
+ Span span = rlpStream.PeekNextItem();
+ Rlp.ValueDecoderContext ctx = new(span);
+ BalanceChange res = Decode(ref ctx, rlpBehaviors);
+ rlpStream.SkipItem();
+
+ return res;
+ }
+
+ public void Encode(RlpStream stream, BalanceChange item, RlpBehaviors rlpBehaviors = RlpBehaviors.None)
+ {
+ stream.StartSequence(GetContentLength(item, rlpBehaviors));
+ stream.Encode(item.BlockAccessIndex);
+ stream.Encode(item.PostBalance);
+ }
+
+ public static int GetContentLength(BalanceChange item, RlpBehaviors rlpBehaviors)
+ => Rlp.LengthOf(item.BlockAccessIndex) + Rlp.LengthOf(item.PostBalance);
+}
diff --git a/src/Nethermind/Nethermind.Serialization.Rlp/Eip7928/BlockAccessListDecoder.cs b/src/Nethermind/Nethermind.Serialization.Rlp/Eip7928/BlockAccessListDecoder.cs
new file mode 100644
index 00000000000..cac12e0dea7
--- /dev/null
+++ b/src/Nethermind/Nethermind.Serialization.Rlp/Eip7928/BlockAccessListDecoder.cs
@@ -0,0 +1,66 @@
+// SPDX-FileCopyrightText: 2025 Demerzel Solutions Limited
+// SPDX-License-Identifier: LGPL-3.0-only
+
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using Nethermind.Core;
+using Nethermind.Core.BlockAccessLists;
+
+namespace Nethermind.Serialization.Rlp.Eip7928;
+
+public class BlockAccessListDecoder : IRlpValueDecoder, IRlpStreamDecoder
+{
+ private static BlockAccessListDecoder? _instance = null;
+ public static BlockAccessListDecoder Instance => _instance ??= new();
+
+ public int GetLength(BlockAccessList item, RlpBehaviors rlpBehaviors)
+ => Rlp.LengthOfSequence(GetContentLength(item, rlpBehaviors));
+
+ public BlockAccessList Decode(ref Rlp.ValueDecoderContext ctx, RlpBehaviors rlpBehaviors)
+ {
+ AccountChanges[] accountChanges = ctx.DecodeArray(AccountChangesDecoder.Instance);
+ if (accountChanges.Length > Eip7928Constants.MaxAccounts)
+ {
+ throw new RlpException("Number of accounts exceeded maximum.");
+ }
+
+ Address lastAddress = Address.Zero;
+ SortedDictionary accountChangesMap = new(accountChanges.ToDictionary(a =>
+ {
+ Address address = a.Address;
+ if (address.CompareTo(lastAddress) <= 0)
+ {
+ throw new RlpException("Account changes were in incorrect order.");
+ }
+ lastAddress = address;
+ return address;
+ }, a => a));
+ BlockAccessList blockAccessList = new(accountChangesMap);
+
+ if (!accountChanges.SequenceEqual(accountChangesMap.Values))
+ {
+ throw new RlpException("Accounts were in incorrect order.");
+ }
+
+ return blockAccessList;
+ }
+
+ public BlockAccessList Decode(RlpStream rlpStream, RlpBehaviors rlpBehaviors)
+ {
+ Span span = rlpStream.PeekNextItem();
+ Rlp.ValueDecoderContext ctx = new(span);
+ BlockAccessList res = Decode(ref ctx, rlpBehaviors);
+ rlpStream.SkipItem();
+
+ return res;
+ }
+
+ public void Encode(RlpStream stream, BlockAccessList item, RlpBehaviors rlpBehaviors = RlpBehaviors.None)
+ {
+ stream.EncodeArray([.. item.GetAccountChanges()], rlpBehaviors);
+ }
+
+ private static int GetContentLength(BlockAccessList item, RlpBehaviors rlpBehaviors)
+ => AccountChangesDecoder.Instance.GetContentLength([.. item.GetAccountChanges()], rlpBehaviors);
+}
diff --git a/src/Nethermind/Nethermind.Serialization.Rlp/Eip7928/CodeChangeDecoder.cs b/src/Nethermind/Nethermind.Serialization.Rlp/Eip7928/CodeChangeDecoder.cs
new file mode 100644
index 00000000000..1dfad9d7532
--- /dev/null
+++ b/src/Nethermind/Nethermind.Serialization.Rlp/Eip7928/CodeChangeDecoder.cs
@@ -0,0 +1,70 @@
+// SPDX-FileCopyrightText: 2025 Demerzel Solutions Limited
+// SPDX-License-Identifier: LGPL-3.0-only
+
+using System;
+using Nethermind.Core;
+using Nethermind.Core.BlockAccessLists;
+using Nethermind.Core.Extensions;
+
+namespace Nethermind.Serialization.Rlp.Eip7928;
+
+public class CodeChangeDecoder : IRlpValueDecoder, IRlpStreamDecoder
+{
+ private static CodeChangeDecoder? _instance = null;
+ public static CodeChangeDecoder Instance => _instance ??= new();
+
+ public CodeChange Decode(ref Rlp.ValueDecoderContext ctx, RlpBehaviors rlpBehaviors)
+ {
+ // var tmp = ctx.Data[ctx.Position..].ToArray();
+
+ int length = ctx.ReadSequenceLength();
+ int check = length + ctx.Position;
+
+ // tmp = tmp[..(length + 1)];
+ // Console.WriteLine("code change:" + length);
+ // Console.WriteLine(Bytes.ToHexString(tmp));
+
+ ushort blockAccessIndex = ctx.DecodeUShort();
+ byte[] newCode = ctx.DecodeByteArray();
+ if (newCode.Length > Eip7928Constants.MaxCodeSize)
+ {
+ throw new RlpException("New code exceeded maxium length.");
+ }
+
+ CodeChange codeChange = new()
+ {
+ BlockAccessIndex = blockAccessIndex,
+ NewCode = newCode
+ };
+
+ if (!rlpBehaviors.HasFlag(RlpBehaviors.AllowExtraBytes))
+ {
+ ctx.Check(check);
+ }
+
+ return codeChange;
+ }
+
+ public int GetLength(CodeChange item, RlpBehaviors rlpBehaviors)
+ => Rlp.LengthOfSequence(GetContentLength(item, rlpBehaviors));
+
+ public CodeChange Decode(RlpStream rlpStream, RlpBehaviors rlpBehaviors)
+ {
+ Span span = rlpStream.PeekNextItem();
+ Rlp.ValueDecoderContext ctx = new(span);
+ CodeChange res = Decode(ref ctx, rlpBehaviors);
+ rlpStream.SkipItem();
+
+ return res;
+ }
+
+ public void Encode(RlpStream stream, CodeChange item, RlpBehaviors rlpBehaviors = RlpBehaviors.None)
+ {
+ stream.StartSequence(GetContentLength(item, rlpBehaviors));
+ stream.Encode(item.BlockAccessIndex);
+ stream.Encode(item.NewCode);
+ }
+
+ public static int GetContentLength(CodeChange item, RlpBehaviors rlpBehaviors)
+ => Rlp.LengthOf(item.BlockAccessIndex) + Rlp.LengthOf(item.NewCode);
+}
diff --git a/src/Nethermind/Nethermind.Serialization.Rlp/Eip7928/NonceChangeDecoder.cs b/src/Nethermind/Nethermind.Serialization.Rlp/Eip7928/NonceChangeDecoder.cs
new file mode 100644
index 00000000000..468fd1488d2
--- /dev/null
+++ b/src/Nethermind/Nethermind.Serialization.Rlp/Eip7928/NonceChangeDecoder.cs
@@ -0,0 +1,62 @@
+// SPDX-FileCopyrightText: 2025 Demerzel Solutions Limited
+// SPDX-License-Identifier: LGPL-3.0-only
+
+using System;
+using Nethermind.Core.BlockAccessLists;
+using Nethermind.Core.Extensions;
+
+namespace Nethermind.Serialization.Rlp.Eip7928;
+
+public class NonceChangeDecoder : IRlpValueDecoder, IRlpStreamDecoder
+{
+ private static NonceChangeDecoder? _instance = null;
+ public static NonceChangeDecoder Instance => _instance ??= new();
+
+ public NonceChange Decode(ref Rlp.ValueDecoderContext ctx, RlpBehaviors rlpBehaviors)
+ {
+ // var tmp = ctx.Data[ctx.Position..].ToArray();
+
+ int length = ctx.ReadSequenceLength();
+ int check = length + ctx.Position;
+
+ // tmp = tmp[..(length + 1)];
+ // Console.WriteLine("nonce change:" + length);
+ // Console.WriteLine(Bytes.ToHexString(tmp));
+
+ NonceChange nonceChange = new()
+ {
+ BlockAccessIndex = ctx.DecodeUShort(),
+ NewNonce = ctx.DecodeULong()
+ };
+
+ if (!rlpBehaviors.HasFlag(RlpBehaviors.AllowExtraBytes))
+ {
+ ctx.Check(check);
+ }
+
+ return nonceChange;
+ }
+
+ public int GetLength(NonceChange item, RlpBehaviors rlpBehaviors)
+ => Rlp.LengthOfSequence(GetContentLength(item, rlpBehaviors));
+
+ public NonceChange Decode(RlpStream rlpStream, RlpBehaviors rlpBehaviors)
+ {
+ Span span = rlpStream.PeekNextItem();
+ Rlp.ValueDecoderContext ctx = new(span);
+ NonceChange res = Decode(ref ctx, rlpBehaviors);
+ rlpStream.SkipItem();
+
+ return res;
+ }
+
+ public void Encode(RlpStream stream, NonceChange item, RlpBehaviors rlpBehaviors = RlpBehaviors.None)
+ {
+ stream.StartSequence(GetContentLength(item, rlpBehaviors));
+ stream.Encode(item.BlockAccessIndex);
+ stream.Encode(item.NewNonce);
+ }
+
+ public static int GetContentLength(NonceChange item, RlpBehaviors rlpBehaviors)
+ => Rlp.LengthOf(item.BlockAccessIndex) + Rlp.LengthOf(item.NewNonce);
+}
diff --git a/src/Nethermind/Nethermind.Serialization.Rlp/Eip7928/SlotChangesDecoder.cs b/src/Nethermind/Nethermind.Serialization.Rlp/Eip7928/SlotChangesDecoder.cs
new file mode 100644
index 00000000000..471578f153b
--- /dev/null
+++ b/src/Nethermind/Nethermind.Serialization.Rlp/Eip7928/SlotChangesDecoder.cs
@@ -0,0 +1,88 @@
+// SPDX-FileCopyrightText: 2025 Demerzel Solutions Limited
+// SPDX-License-Identifier: LGPL-3.0-only
+
+using System;
+using Nethermind.Core;
+using Nethermind.Core.BlockAccessLists;
+
+namespace Nethermind.Serialization.Rlp.Eip7928;
+
+public class SlotChangesDecoder : IRlpValueDecoder, IRlpStreamDecoder
+{
+ private static SlotChangesDecoder? _instance = null;
+ public static SlotChangesDecoder Instance => _instance ??= new();
+
+
+ public SlotChanges Decode(ref Rlp.ValueDecoderContext ctx, RlpBehaviors rlpBehaviors)
+ {
+ // var tmp = ctx.Data[ctx.Position..].ToArray();
+
+ // Console.WriteLine("slot change uncut:");
+ // Console.WriteLine(Bytes.ToHexString(tmp));
+
+ int length = ctx.ReadSequenceLength();
+ int check = length + ctx.Position;
+
+ // tmp = tmp[..(length + 2)];
+ // Console.WriteLine("slot change:" + length);
+ // Console.WriteLine(Bytes.ToHexString(tmp));
+
+ byte[] slot = ctx.DecodeByteArray();
+ if (slot.Length != 32)
+ {
+ throw new RlpException("Invalid storage key, should be 32 bytes.");
+ }
+
+ StorageChange[] changes = ctx.DecodeArray(StorageChangeDecoder.Instance);
+ if (changes.Length > Eip7928Constants.MaxSlots)
+ {
+ throw new RlpException("Number of slot changes exceeded maximum.");
+ }
+
+ SlotChanges slotChanges = new()
+ {
+ Slot = slot,
+ Changes = [.. changes]
+ };
+
+ if (!rlpBehaviors.HasFlag(RlpBehaviors.AllowExtraBytes))
+ {
+ ctx.Check(check);
+ }
+
+ return slotChanges;
+ }
+
+ public int GetLength(SlotChanges item, RlpBehaviors rlpBehaviors)
+ => Rlp.LengthOfSequence(GetContentLength(item, rlpBehaviors));
+
+ public SlotChanges Decode(RlpStream rlpStream, RlpBehaviors rlpBehaviors)
+ {
+ Span span = rlpStream.PeekNextItem();
+ Rlp.ValueDecoderContext ctx = new(span);
+ SlotChanges res = Decode(ref ctx, rlpBehaviors);
+ rlpStream.SkipItem();
+
+ return res;
+ }
+
+ public void Encode(RlpStream stream, SlotChanges item, RlpBehaviors rlpBehaviors = RlpBehaviors.None)
+ {
+ stream.StartSequence(GetContentLength(item, rlpBehaviors));
+ stream.Encode(item.Slot);
+ stream.EncodeArray([.. item.Changes], rlpBehaviors);
+ }
+
+ public static int GetContentLength(SlotChanges item, RlpBehaviors rlpBehaviors)
+ {
+ int storageChangesLen = 0;
+
+ foreach (StorageChange slotChange in item.Changes)
+ {
+ storageChangesLen += StorageChangeDecoder.Instance.GetLength(slotChange, rlpBehaviors);
+ }
+ storageChangesLen = Rlp.LengthOfSequence(storageChangesLen);
+
+ return storageChangesLen + Rlp.LengthOf(item.Slot);
+ }
+}
diff --git a/src/Nethermind/Nethermind.Serialization.Rlp/Eip7928/StorageChangeDecoder.cs b/src/Nethermind/Nethermind.Serialization.Rlp/Eip7928/StorageChangeDecoder.cs
new file mode 100644
index 00000000000..8516d3f1415
--- /dev/null
+++ b/src/Nethermind/Nethermind.Serialization.Rlp/Eip7928/StorageChangeDecoder.cs
@@ -0,0 +1,69 @@
+// SPDX-FileCopyrightText: 2025 Demerzel Solutions Limited
+// SPDX-License-Identifier: LGPL-3.0-only
+
+using System;
+using Nethermind.Core.BlockAccessLists;
+using Nethermind.Core.Extensions;
+
+namespace Nethermind.Serialization.Rlp.Eip7928;
+
+public class StorageChangeDecoder : IRlpValueDecoder, IRlpStreamDecoder
+{
+ private static StorageChangeDecoder? _instance = null;
+ public static StorageChangeDecoder Instance => _instance ??= new();
+
+ public StorageChange Decode(ref Rlp.ValueDecoderContext ctx, RlpBehaviors rlpBehaviors)
+ {
+ // var tmp = ctx.Data[ctx.Position..].ToArray();
+
+ int length = ctx.ReadSequenceLength();
+ int check = length + ctx.Position;
+
+ // tmp = tmp[..(length + 1)];
+ // Console.WriteLine("storage change:" + length);
+ // Console.WriteLine(Bytes.ToHexString(tmp));
+
+ ushort blockAccessIndex = ctx.DecodeUShort();
+ byte[] newValue = ctx.DecodeByteArray();
+ if (newValue.Length != 32)
+ {
+ throw new RlpException("Invalid storage value, should be 32 bytes.");
+ }
+
+ StorageChange storageChange = new()
+ {
+ BlockAccessIndex = blockAccessIndex,
+ NewValue = new(newValue)
+ };
+
+ if (!rlpBehaviors.HasFlag(RlpBehaviors.AllowExtraBytes))
+ {
+ ctx.Check(check);
+ }
+
+ return storageChange;
+ }
+
+ public int GetLength(StorageChange item, RlpBehaviors rlpBehaviors)
+ => Rlp.LengthOfSequence(GetContentLength(item, rlpBehaviors));
+
+ public StorageChange Decode(RlpStream rlpStream, RlpBehaviors rlpBehaviors)
+ {
+ Span span = rlpStream.PeekNextItem();
+ Rlp.ValueDecoderContext ctx = new(span);
+ StorageChange res = Decode(ref ctx, rlpBehaviors);
+ rlpStream.SkipItem();
+
+ return res;
+ }
+
+ public void Encode(RlpStream stream, StorageChange item, RlpBehaviors rlpBehaviors = RlpBehaviors.None)
+ {
+ stream.StartSequence(GetContentLength(item, rlpBehaviors));
+ stream.Encode(item.BlockAccessIndex);
+ stream.Encode(item.NewValue.Unwrap());
+ }
+
+ public static int GetContentLength(StorageChange item, RlpBehaviors rlpBehaviors)
+ => Rlp.LengthOf(item.BlockAccessIndex) + Rlp.LengthOf(item.NewValue.Unwrap());
+}
diff --git a/src/Nethermind/Nethermind.Serialization.Rlp/Eip7928/StorageReadDecoder.cs b/src/Nethermind/Nethermind.Serialization.Rlp/Eip7928/StorageReadDecoder.cs
new file mode 100644
index 00000000000..6574f9c56e0
--- /dev/null
+++ b/src/Nethermind/Nethermind.Serialization.Rlp/Eip7928/StorageReadDecoder.cs
@@ -0,0 +1,48 @@
+// SPDX-FileCopyrightText: 2025 Demerzel Solutions Limited
+// SPDX-License-Identifier: LGPL-3.0-only
+
+using System;
+using Nethermind.Core.BlockAccessLists;
+
+namespace Nethermind.Serialization.Rlp.Eip7928;
+
+public class StorageReadDecoder : IRlpValueDecoder, IRlpStreamDecoder
+{
+ private static StorageReadDecoder? _instance = null;
+ public static StorageReadDecoder Instance => _instance ??= new();
+
+ public int GetLength(StorageRead item, RlpBehaviors rlpBehaviors)
+ => GetContentLength(item, rlpBehaviors);
+
+ public StorageRead Decode(ref Rlp.ValueDecoderContext ctx, RlpBehaviors rlpBehaviors)
+ {
+ byte[] key = ctx.DecodeByteArray();
+ if (key.Length != 32)
+ {
+ throw new RlpException("Invalid storage key, should be 32 bytes.");
+ }
+
+ StorageRead storageRead = new()
+ {
+ Key = new(key),
+ };
+
+ return storageRead;
+ }
+
+ public StorageRead Decode(RlpStream rlpStream, RlpBehaviors rlpBehaviors)
+ {
+ Span span = rlpStream.PeekNextItem();
+ Rlp.ValueDecoderContext ctx = new(span);
+ StorageRead res = Decode(ref ctx, rlpBehaviors);
+ rlpStream.SkipItem();
+
+ return res;
+ }
+
+ public void Encode(RlpStream stream, StorageRead item, RlpBehaviors rlpBehaviors = RlpBehaviors.None)
+ => stream.Encode(item.Key.Unwrap());
+
+ public static int GetContentLength(StorageRead item, RlpBehaviors rlpBehaviors)
+ => Rlp.LengthOf(item.Key.Unwrap());
+}
diff --git a/src/Nethermind/Nethermind.Serialization.Rlp/HeaderDecoder.cs b/src/Nethermind/Nethermind.Serialization.Rlp/HeaderDecoder.cs
index e4e824bfddf..70f0a3bc1d6 100644
--- a/src/Nethermind/Nethermind.Serialization.Rlp/HeaderDecoder.cs
+++ b/src/Nethermind/Nethermind.Serialization.Rlp/HeaderDecoder.cs
@@ -76,6 +76,7 @@ public class HeaderDecoder : IHeaderDecoder
if (decoderContext.Position != headerCheck) blockHeader.ExcessBlobGas = decoderContext.DecodeULong();
if (decoderContext.Position != headerCheck) blockHeader.ParentBeaconBlockRoot = decoderContext.DecodeKeccak();
if (decoderContext.Position != headerCheck) blockHeader.RequestsHash = decoderContext.DecodeKeccak();
+ if (decoderContext.Position != headerCheck) blockHeader.BlockAccessListHash = decoderContext.DecodeKeccak();
if ((rlpBehaviors & RlpBehaviors.AllowExtraBytes) != RlpBehaviors.AllowExtraBytes)
{
@@ -146,6 +147,7 @@ public class HeaderDecoder : IHeaderDecoder
if (rlpStream.Position != headerCheck) blockHeader.ExcessBlobGas = rlpStream.DecodeULong();
if (rlpStream.Position != headerCheck) blockHeader.ParentBeaconBlockRoot = rlpStream.DecodeKeccak();
if (rlpStream.Position != headerCheck) blockHeader.RequestsHash = rlpStream.DecodeKeccak();
+ if (rlpStream.Position != headerCheck) blockHeader.BlockAccessListHash = rlpStream.DecodeKeccak();
if ((rlpBehaviors & RlpBehaviors.AllowExtraBytes) != RlpBehaviors.AllowExtraBytes)
{
@@ -194,15 +196,16 @@ public void Encode(RlpStream rlpStream, BlockHeader? header, RlpBehaviors rlpBeh
}
}
- Span requiredItems = stackalloc bool[6];
+ Span requiredItems = stackalloc bool[7];
requiredItems[0] = !header.BaseFeePerGas.IsZero;
- requiredItems[1] = (header.WithdrawalsRoot is not null);
- requiredItems[2] = (header.BlobGasUsed is not null);
- requiredItems[3] = (header.BlobGasUsed is not null || header.ExcessBlobGas is not null);
- requiredItems[4] = (header.ParentBeaconBlockRoot is not null);
- requiredItems[5] = (header.RequestsHash is not null);
-
- for (int i = 4; i >= 0; i--)
+ requiredItems[1] = header.WithdrawalsRoot is not null;
+ requiredItems[2] = header.BlobGasUsed is not null;
+ requiredItems[3] = header.BlobGasUsed is not null || header.ExcessBlobGas is not null;
+ requiredItems[4] = header.ParentBeaconBlockRoot is not null;
+ requiredItems[5] = header.RequestsHash is not null;
+ requiredItems[6] = header.BlockAccessListHash is not null;
+
+ for (int i = 5; i >= 0; i--)
{
requiredItems[i] |= requiredItems[i + 1];
}
@@ -213,6 +216,7 @@ public void Encode(RlpStream rlpStream, BlockHeader? header, RlpBehaviors rlpBeh
if (requiredItems[3]) rlpStream.Encode(header.ExcessBlobGas.GetValueOrDefault());
if (requiredItems[4]) rlpStream.Encode(header.ParentBeaconBlockRoot);
if (requiredItems[5]) rlpStream.Encode(header.RequestsHash);
+ if (requiredItems[6]) rlpStream.Encode(header.BlockAccessListHash);
}
public Rlp Encode(BlockHeader? item, RlpBehaviors rlpBehaviors = RlpBehaviors.None)
@@ -267,15 +271,16 @@ private static int GetContentLength(BlockHeader? item, RlpBehaviors rlpBehaviors
}
- Span requiredItems = stackalloc bool[6];
+ Span requiredItems = stackalloc bool[7];
requiredItems[0] = !item.BaseFeePerGas.IsZero;
- requiredItems[1] = (item.WithdrawalsRoot is not null);
- requiredItems[2] = (item.BlobGasUsed is not null);
- requiredItems[3] = (item.BlobGasUsed is not null || item.ExcessBlobGas is not null);
- requiredItems[4] = (item.ParentBeaconBlockRoot is not null);
- requiredItems[5] = (item.RequestsHash is not null);
-
- for (int i = 4; i >= 0; i--)
+ requiredItems[1] = item.WithdrawalsRoot is not null;
+ requiredItems[2] = item.BlobGasUsed is not null;
+ requiredItems[3] = item.BlobGasUsed is not null || item.ExcessBlobGas is not null;
+ requiredItems[4] = item.ParentBeaconBlockRoot is not null;
+ requiredItems[5] = item.RequestsHash is not null;
+ requiredItems[6] = item.BlockAccessListHash is not null;
+
+ for (int i = 5; i >= 0; i--)
{
requiredItems[i] |= requiredItems[i + 1];
}
@@ -286,12 +291,12 @@ private static int GetContentLength(BlockHeader? item, RlpBehaviors rlpBehaviors
if (requiredItems[3]) contentLength += Rlp.LengthOf(item.ExcessBlobGas.GetValueOrDefault());
if (requiredItems[4]) contentLength += Rlp.LengthOf(item.ParentBeaconBlockRoot);
if (requiredItems[5]) contentLength += Rlp.LengthOf(item.RequestsHash);
+ if (requiredItems[6]) contentLength += Rlp.LengthOf(item.BlockAccessListHash);
+
return contentLength;
}
public int GetLength(BlockHeader? item, RlpBehaviors rlpBehaviors)
- {
- return Rlp.LengthOfSequence(GetContentLength(item, rlpBehaviors));
- }
+ => Rlp.LengthOfSequence(GetContentLength(item, rlpBehaviors));
}
}
diff --git a/src/Nethermind/Nethermind.Serialization.Rlp/Rlp.cs b/src/Nethermind/Nethermind.Serialization.Rlp/Rlp.cs
index 76c1e122689..20c60d48ac4 100644
--- a/src/Nethermind/Nethermind.Serialization.Rlp/Rlp.cs
+++ b/src/Nethermind/Nethermind.Serialization.Rlp/Rlp.cs
@@ -1078,7 +1078,7 @@ public void DecodeZeroPrefixedKeccakStructRef(out Hash256StructRef keccak, Span<
static void ThrowInvalidPrefix(ref ValueDecoderContext ctx, int prefix)
{
- throw new RlpException($"Unexpected prefix of {prefix} when decoding {nameof(Hash256)} at position {ctx.Position} in the message of length {ctx.Data.Length} starting with {ctx.Data[..Math.Min(DebugMessageContentLength, ctx.Data.Length)].ToHexString()}");
+ throw new RlpException($"Unexpected prefix of {prefix} when decoding {nameof(Address)} at position {ctx.Position} in the message of length {ctx.Data.Length} starting with {ctx.Data[..Math.Min(DebugMessageContentLength, ctx.Data.Length)].ToHexString()}");
}
}
@@ -1573,6 +1573,45 @@ public byte[][] DecodeByteArrays()
return result;
}
+ public ushort DecodeUShort()
+ {
+ int prefix = ReadByte();
+
+ switch (prefix)
+ {
+ case 0:
+ throw new RlpException($"Non-canonical ushort (leading zero bytes) at position {Position}");
+ case < 128:
+ return (ushort)prefix;
+ case 128:
+ return 0;
+ }
+
+ int length = prefix - 128;
+ if (length > 8)
+ {
+ throw new RlpException($"Unexpected length of ushort value: {length}");
+ }
+
+ ushort result = 0;
+ for (int i = 2; i > 0; i--)
+ {
+ result <<= 8;
+ if (i <= length)
+ {
+ result |= PeekByte(length - i);
+ if (result == 0)
+ {
+ throw new RlpException($"Non-canonical ushort (leading zero bytes) at position {Position}");
+ }
+ }
+ }
+
+ SkipBytes(length);
+
+ return result;
+ }
+
public byte DecodeByte()
{
byte byteValue = PeekByte();
@@ -1728,6 +1767,19 @@ public static int LengthOf(long value)
public static int LengthOf(int value) => LengthOf((long)value);
+ public static int LengthOf(ushort value)
+ {
+ if (value < 128)
+ {
+ return 1;
+ }
+ else
+ {
+ // everything has a length prefix
+ return 1 + sizeof(ushort) - (BitOperations.LeadingZeroCount(value) / 2);
+ }
+ }
+
public static int LengthOf(Hash256? item)
{
return item is null ? 1 : 33;
diff --git a/src/Nethermind/Nethermind.Serialization.Rlp/RlpStream.cs b/src/Nethermind/Nethermind.Serialization.Rlp/RlpStream.cs
index 28ea90b7b2a..bb7f833285b 100644
--- a/src/Nethermind/Nethermind.Serialization.Rlp/RlpStream.cs
+++ b/src/Nethermind/Nethermind.Serialization.Rlp/RlpStream.cs
@@ -11,11 +11,13 @@
using System.Runtime.InteropServices;
using System.Text;
using Nethermind.Core;
+using Nethermind.Core.BlockAccessLists;
using Nethermind.Core.Buffers;
using Nethermind.Core.Collections;
using Nethermind.Core.Crypto;
using Nethermind.Core.Extensions;
using Nethermind.Int256;
+using Nethermind.Serialization.Rlp.Eip7928;
namespace Nethermind.Serialization.Rlp
{
@@ -28,6 +30,7 @@ public class RlpStream
private static readonly TxDecoder _txDecoder = TxDecoder.Instance;
private static readonly ReceiptMessageDecoder _receiptDecoder = new();
private static readonly WithdrawalDecoder _withdrawalDecoder = new();
+ private static readonly BlockAccessListDecoder _blockAccessListDecoder = BlockAccessListDecoder.Instance;
private static readonly LogEntryDecoder _logEntryDecoder = LogEntryDecoder.Instance;
private readonly CappedArray _data;
@@ -68,37 +71,32 @@ public void EncodeArray(T?[]? items, RlpBehaviors rlpBehaviors = RlpBehaviors
StartSequence(contentLength);
- foreach (var item in items)
+ foreach (T item in items)
{
decoder.Encode(this, item, rlpBehaviors);
}
}
+
public void Encode(Block value)
- {
- _blockDecoder.Encode(this, value);
- }
+ => _blockDecoder.Encode(this, value);
public void Encode(BlockHeader value)
- {
- _headerDecoder.Encode(this, value);
- }
+ => _headerDecoder.Encode(this, value);
public void Encode(Transaction value, RlpBehaviors rlpBehaviors = RlpBehaviors.None)
- {
- _txDecoder.Encode(this, value, rlpBehaviors);
- }
+ => _txDecoder.Encode(this, value, rlpBehaviors);
- public void Encode(Withdrawal value) => _withdrawalDecoder.Encode(this, value);
+ public void Encode(Withdrawal value)
+ => _withdrawalDecoder.Encode(this, value);
public void Encode(LogEntry value)
- {
- _logEntryDecoder.Encode(this, value);
- }
+ => _logEntryDecoder.Encode(this, value);
public void Encode(BlockInfo value)
- {
- _blockInfoDecoder.Encode(this, value);
- }
+ => _blockInfoDecoder.Encode(this, value);
+
+ public void Encode(BlockAccessList value)
+ => _blockAccessListDecoder.Encode(this, value);
public void StartByteArray(int contentLength, bool firstByteLessThan128)
{
@@ -207,9 +205,7 @@ public virtual int Position
public virtual bool HasBeenRead => Position >= Data!.Length;
public bool IsSequenceNext()
- {
- return PeekByte() >= 192;
- }
+ => PeekByte() >= 192;
public void Encode(Hash256? keccak)
{
diff --git a/src/Nethermind/Nethermind.Specs.Test/ChainParametersTests.cs b/src/Nethermind/Nethermind.Specs.Test/ChainParametersTests.cs
index a1f74e3bca3..15840bf7f84 100644
--- a/src/Nethermind/Nethermind.Specs.Test/ChainParametersTests.cs
+++ b/src/Nethermind/Nethermind.Specs.Test/ChainParametersTests.cs
@@ -40,7 +40,8 @@ public void ChainParameters_should_be_loaded_from_chainSpecParamsJson()
"MaxCodeSizeTransitionTimestamp",
"Eip4844FeeCollectorTransitionTimestamp",
"Eip6110TransitionTimestamp",
- "Eip7692TransitionTimestamp"
+ "Eip7692TransitionTimestamp",
+ "Eip7928TransitionTimestamp" // tmp
];
const ulong testValue = 1ul;
diff --git a/src/Nethermind/Nethermind.Specs.Test/OverridableReleaseSpec.cs b/src/Nethermind/Nethermind.Specs.Test/OverridableReleaseSpec.cs
index fbac042868e..433374f543c 100644
--- a/src/Nethermind/Nethermind.Specs.Test/OverridableReleaseSpec.cs
+++ b/src/Nethermind/Nethermind.Specs.Test/OverridableReleaseSpec.cs
@@ -199,6 +199,7 @@ public ulong Eip4844TransitionTimestamp
public bool IsEip7939Enabled => spec.IsEip7939Enabled;
public bool IsEip7907Enabled => spec.IsEip7907Enabled;
public bool IsRip7728Enabled => spec.IsRip7728Enabled;
+ public bool IsEip7928Enabled { get; set; } = spec.IsEip7928Enabled;
FrozenSet IReleaseSpec.Precompiles => spec.Precompiles;
}
}
diff --git a/src/Nethermind/Nethermind.Specs/ChainSpecStyle/ChainParameters.cs b/src/Nethermind/Nethermind.Specs/ChainSpecStyle/ChainParameters.cs
index ff14862f117..5f5a2dba887 100644
--- a/src/Nethermind/Nethermind.Specs/ChainSpecStyle/ChainParameters.cs
+++ b/src/Nethermind/Nethermind.Specs/ChainSpecStyle/ChainParameters.cs
@@ -173,4 +173,5 @@ public class ChainParameters
#endregion
public ulong? Rip7728TransitionTimestamp { get; set; }
+ public ulong? Eip7928TransitionTimestamp { get; set; }
}
diff --git a/src/Nethermind/Nethermind.Specs/ChainSpecStyle/ChainSpecBasedSpecProvider.cs b/src/Nethermind/Nethermind.Specs/ChainSpecStyle/ChainSpecBasedSpecProvider.cs
index 0b4fdc3c8fa..f6fe905de0c 100644
--- a/src/Nethermind/Nethermind.Specs/ChainSpecStyle/ChainSpecBasedSpecProvider.cs
+++ b/src/Nethermind/Nethermind.Specs/ChainSpecStyle/ChainSpecBasedSpecProvider.cs
@@ -293,6 +293,8 @@ protected virtual ReleaseSpec CreateReleaseSpec(ChainSpec chainSpec, long releas
releaseSpec.IsRip7728Enabled = (chainSpec.Parameters.Rip7728TransitionTimestamp ?? ulong.MaxValue) <= releaseStartTimestamp;
+ releaseSpec.IsEip7928Enabled = (chainSpec.Parameters.Eip7928TransitionTimestamp ?? ulong.MaxValue) <= releaseStartTimestamp;
+
foreach (IChainSpecEngineParameters item in _chainSpec.EngineChainSpecParametersProvider
.AllChainSpecParameters)
{
diff --git a/src/Nethermind/Nethermind.Specs/ChainSpecStyle/ChainSpecLoader.cs b/src/Nethermind/Nethermind.Specs/ChainSpecStyle/ChainSpecLoader.cs
index 7374ae2341d..b7512dc20a8 100644
--- a/src/Nethermind/Nethermind.Specs/ChainSpecStyle/ChainSpecLoader.cs
+++ b/src/Nethermind/Nethermind.Specs/ChainSpecStyle/ChainSpecLoader.cs
@@ -196,6 +196,8 @@ bool GetForInnerPathExistence(KeyValuePair o) =>
Eip7934MaxRlpBlockSize = chainSpecJson.Params.Eip7934MaxRlpBlockSize ?? Eip7934Constants.DefaultMaxRlpBlockSize,
Rip7728TransitionTimestamp = chainSpecJson.Params.Rip7728TransitionTimestamp,
+
+ Eip7928TransitionTimestamp = chainSpecJson.Params.Eip7928TransitionTimestamp,
};
chainSpec.Parameters.Eip152Transition ??= GetTransitionForExpectedPricing("blake2_f", "price.blake2_f.gas_per_round", 1);
@@ -327,27 +329,35 @@ private static void LoadGenesis(ChainSpecJson chainSpecJson, ChainSpec chainSpec
0,
(long)gasLimit,
timestamp,
- extraData);
-
- genesisHeader.Author = beneficiary;
- genesisHeader.Hash = Keccak.Zero; // need to run the block to know the actual hash
- genesisHeader.Bloom = Bloom.Empty;
- genesisHeader.MixHash = mixHash;
- genesisHeader.Nonce = (ulong)nonce;
- genesisHeader.ReceiptsRoot = Keccak.EmptyTreeHash;
- genesisHeader.StateRoot = stateRoot;
- genesisHeader.TxRoot = Keccak.EmptyTreeHash;
- genesisHeader.BaseFeePerGas = baseFee;
+ extraData)
+ {
+ Author = beneficiary,
+ Hash = Keccak.Zero, // need to run the block to know the actual hash
+ Bloom = Bloom.Empty,
+ MixHash = mixHash,
+ Nonce = (ulong)nonce,
+ ReceiptsRoot = Keccak.EmptyTreeHash,
+ StateRoot = stateRoot,
+ TxRoot = Keccak.EmptyTreeHash,
+ BaseFeePerGas = baseFee
+ };
+
bool withdrawalsEnabled = chainSpecJson.Params.Eip4895TransitionTimestamp is not null && genesisHeader.Timestamp >= chainSpecJson.Params.Eip4895TransitionTimestamp;
bool depositsEnabled = chainSpecJson.Params.Eip6110TransitionTimestamp is not null && genesisHeader.Timestamp >= chainSpecJson.Params.Eip6110TransitionTimestamp;
bool withdrawalRequestsEnabled = chainSpecJson.Params.Eip7002TransitionTimestamp is not null && genesisHeader.Timestamp >= chainSpecJson.Params.Eip7002TransitionTimestamp;
bool consolidationRequestsEnabled = chainSpecJson.Params.Eip7251TransitionTimestamp is not null && genesisHeader.Timestamp >= chainSpecJson.Params.Eip7251TransitionTimestamp;
+ bool blockAccessListsEnabled = chainSpecJson.Params.Eip7928TransitionTimestamp is not null && genesisHeader.Timestamp >= chainSpecJson.Params.Eip7928TransitionTimestamp;
+
if (withdrawalsEnabled)
+ {
genesisHeader.WithdrawalsRoot = Keccak.EmptyTreeHash;
+ }
var requestsEnabled = depositsEnabled || withdrawalRequestsEnabled || consolidationRequestsEnabled;
if (requestsEnabled)
+ {
genesisHeader.RequestsHash = ExecutionRequestExtensions.EmptyRequestsHash;
+ }
bool isEip4844Enabled = chainSpecJson.Params.Eip4844TransitionTimestamp is not null && genesisHeader.Timestamp >= chainSpecJson.Params.Eip4844TransitionTimestamp;
if (isEip4844Enabled)
@@ -367,16 +377,19 @@ private static void LoadGenesis(ChainSpecJson chainSpecJson, ChainSpec chainSpec
genesisHeader.ReceiptsRoot = Keccak.EmptyTreeHash;
}
+ if (blockAccessListsEnabled)
+ {
+ genesisHeader.BlockAccessListHash = Keccak.OfAnEmptySequenceRlp;
+ }
+
genesisHeader.AuRaStep = step;
genesisHeader.AuRaSignature = auRaSignature;
- chainSpec.Genesis = !withdrawalsEnabled
- ? new Block(genesisHeader)
- : new Block(
- genesisHeader,
- Array.Empty(),
- Array.Empty(),
- Array.Empty());
+ chainSpec.Genesis = !blockAccessListsEnabled ?
+ (!withdrawalsEnabled
+ ? new Block(genesisHeader)
+ : new Block(genesisHeader, [], [], []))
+ : new Block(genesisHeader, [], [], [], new());
}
private static void LoadAllocations(ChainSpecJson chainSpecJson, ChainSpec chainSpec)
diff --git a/src/Nethermind/Nethermind.Specs/ChainSpecStyle/Json/ChainSpecParamsJson.cs b/src/Nethermind/Nethermind.Specs/ChainSpecStyle/Json/ChainSpecParamsJson.cs
index 6a80e4a902e..dfcabe9b4eb 100644
--- a/src/Nethermind/Nethermind.Specs/ChainSpecStyle/Json/ChainSpecParamsJson.cs
+++ b/src/Nethermind/Nethermind.Specs/ChainSpecStyle/Json/ChainSpecParamsJson.cs
@@ -177,4 +177,5 @@ public class ChainSpecParamsJson
public ulong? Eip7594TransitionTimestamp { get; set; }
public ulong? Eip7939TransitionTimestamp { get; set; }
public ulong? Rip7728TransitionTimestamp { get; set; }
+ public ulong? Eip7928TransitionTimestamp { get; set; }
}
diff --git a/src/Nethermind/Nethermind.Specs/Forks/20_Amsterdam.cs b/src/Nethermind/Nethermind.Specs/Forks/20_Amsterdam.cs
new file mode 100644
index 00000000000..ed01d5d6ea3
--- /dev/null
+++ b/src/Nethermind/Nethermind.Specs/Forks/20_Amsterdam.cs
@@ -0,0 +1,21 @@
+// SPDX-FileCopyrightText: 2025 Demerzel Solutions Limited
+// SPDX-License-Identifier: LGPL-3.0-only
+
+using System.Threading;
+using Nethermind.Core.Specs;
+
+namespace Nethermind.Specs.Forks;
+
+public class Amsterdam : Osaka
+{
+ private static IReleaseSpec _instance;
+
+ public Amsterdam()
+ {
+ Name = "Amsterdam";
+ IsEip7928Enabled = true;
+ Released = false;
+ }
+
+ public new static IReleaseSpec Instance => LazyInitializer.EnsureInitialized(ref _instance, static () => new Amsterdam());
+}
diff --git a/src/Nethermind/Nethermind.Specs/ReleaseSpec.cs b/src/Nethermind/Nethermind.Specs/ReleaseSpec.cs
index ea60eb262bf..95526fd74d5 100644
--- a/src/Nethermind/Nethermind.Specs/ReleaseSpec.cs
+++ b/src/Nethermind/Nethermind.Specs/ReleaseSpec.cs
@@ -163,6 +163,7 @@ public Address Eip2935ContractAddress
Array? IReleaseSpec.EvmInstructionsTraced { get; set; }
public bool IsEip7939Enabled { get; set; }
public bool IsRip7728Enabled { get; set; }
+ public bool IsEip7928Enabled { get; set; }
private FrozenSet? _precompiles;
FrozenSet IReleaseSpec.Precompiles => _precompiles ??= BuildPrecompilesCache();
diff --git a/src/Nethermind/Nethermind.Specs/SpecNameParser.cs b/src/Nethermind/Nethermind.Specs/SpecNameParser.cs
index 0b4abd07435..a3022fe5487 100644
--- a/src/Nethermind/Nethermind.Specs/SpecNameParser.cs
+++ b/src/Nethermind/Nethermind.Specs/SpecNameParser.cs
@@ -54,6 +54,7 @@ public static IReleaseSpec Parse(string specName)
"Paris" => Paris.Instance,
"Prague" => Prague.Instance,
"Osaka" => Osaka.Instance,
+ "Amsterdam" => Amsterdam.Instance,
_ => throw new NotSupportedException()
};
}
diff --git a/src/Nethermind/Nethermind.State.Test/StateProviderTests.cs b/src/Nethermind/Nethermind.State.Test/StateProviderTests.cs
index 243bb99cd4c..27f36182903 100644
--- a/src/Nethermind/Nethermind.State.Test/StateProviderTests.cs
+++ b/src/Nethermind/Nethermind.State.Test/StateProviderTests.cs
@@ -10,13 +10,11 @@
using Nethermind.Specs;
using Nethermind.Specs.Forks;
using Nethermind.Core.Test.Builders;
-using Nethermind.Db;
using Nethermind.Int256;
using Nethermind.Blockchain.Tracing.ParityStyle;
using Nethermind.Logging;
using Nethermind.Evm.State;
using Nethermind.State;
-using Nethermind.Trie;
using NUnit.Framework;
namespace Nethermind.Store.Test;
diff --git a/src/Nethermind/Nethermind.State/StateProvider.cs b/src/Nethermind/Nethermind.State/StateProvider.cs
index 39981c9c5a9..5539dedea02 100644
--- a/src/Nethermind/Nethermind.State/StateProvider.cs
+++ b/src/Nethermind/Nethermind.State/StateProvider.cs
@@ -29,13 +29,13 @@
namespace Nethermind.State
{
- internal class StateProvider
+ internal class StateProvider : IJournal
{
private static readonly UInt256 _zero = UInt256.Zero;
- private readonly Dictionary> _intraTxCache = new();
- private readonly HashSet _committedThisRound = new();
- private readonly HashSet _nullAccountReads = new();
+ private readonly Dictionary> _intraTxCache = [];
+ private readonly HashSet _committedThisRound = [];
+ private readonly HashSet _nullAccountReads = [];
// Only guarding against hot duplicates so filter doesn't need to be too big
// Note:
// False negatives are fine as they will just result in a overwrite set
@@ -44,7 +44,7 @@ internal class StateProvider
private readonly Dictionary _blockChanges = new(4_096);
private readonly ConcurrentDictionary? _preBlockCache;
- private readonly List _keptInCache = new();
+ private readonly List _keptInCache = [];
private readonly ILogger _logger;
private readonly IKeyValueStoreWithBatching _codeDb;
private Dictionary _codeBatch;
@@ -205,7 +205,7 @@ static Account ThrowIfNull(Address address)
=> throw new InvalidOperationException($"Account {address} is null when updating code hash");
}
- private void SetNewBalance(Address address, in UInt256 balanceChange, IReleaseSpec releaseSpec, bool isSubtracting)
+ private void SetNewBalance(Address address, in UInt256 balanceChange, IReleaseSpec releaseSpec, bool isSubtracting, out UInt256 oldBalance)
{
_needsStateRootUpdate = true;
@@ -241,6 +241,7 @@ static void ThrowNonExistingAccount()
}
}
+ oldBalance = 0;
return;
}
@@ -251,6 +252,7 @@ static void ThrowNonExistingAccount()
ThrowInsufficientBalanceException(address);
}
+ oldBalance = account.Balance;
UInt256 newBalance = isSubtracting ? account.Balance - balanceChange : account.Balance + balanceChange;
Account changedAccount = account.WithChangedBalance(newBalance);
@@ -271,15 +273,20 @@ static void ThrowInsufficientBalanceException(Address address)
}
public void SubtractFromBalance(Address address, in UInt256 balanceChange, IReleaseSpec releaseSpec)
+ => SubtractFromBalance(address, balanceChange, releaseSpec, out _);
+ public void SubtractFromBalance(Address address, in UInt256 balanceChange, IReleaseSpec releaseSpec, out UInt256 oldBalance)
{
_needsStateRootUpdate = true;
- SetNewBalance(address, balanceChange, releaseSpec, true);
+ SetNewBalance(address, balanceChange, releaseSpec, true, out oldBalance);
}
public void AddToBalance(Address address, in UInt256 balanceChange, IReleaseSpec releaseSpec)
+ => AddToBalance(address, balanceChange, releaseSpec, out _);
+
+ public void AddToBalance(Address address, in UInt256 balanceChange, IReleaseSpec releaseSpec, out UInt256 oldBalance)
{
_needsStateRootUpdate = true;
- SetNewBalance(address, balanceChange, releaseSpec, false);
+ SetNewBalance(address, balanceChange, releaseSpec, false, out oldBalance);
}
///
@@ -309,10 +316,14 @@ static Account ThrowNullAccount(Address address)
}
public void IncrementNonce(Address address, UInt256 delta)
+ => IncrementNonce(address, delta, out _);
+
+ public void IncrementNonce(Address address, UInt256 delta, out UInt256 oldNonce)
{
_needsStateRootUpdate = true;
Account account = GetThroughCache(address) ?? ThrowNullAccount(address);
- Account changedAccount = account.WithChangedNonce(account.Nonce + delta);
+ oldNonce = account.Nonce;
+ Account changedAccount = account.WithChangedNonce(oldNonce + delta);
if (_logger.IsTrace) Trace(address, account, changedAccount);
PushUpdate(address, changedAccount);
@@ -515,20 +526,24 @@ public void CreateAccountIfNotExists(Address address, in UInt256 balance, in UIn
}
}
- public bool AddToBalanceAndCreateIfNotExists(Address address, in UInt256 balance, IReleaseSpec spec)
+ public bool AddToBalanceAndCreateIfNotExists(Address address, in UInt256 balance, IReleaseSpec spec, out UInt256 oldBalance)
{
if (AccountExists(address))
{
- AddToBalance(address, balance, spec);
+ AddToBalance(address, balance, spec, out oldBalance);
return false;
}
else
{
+ oldBalance = 0;
CreateAccount(address, balance);
return true;
}
}
+ public bool AddToBalanceAndCreateIfNotExists(Address address, in UInt256 balance, IReleaseSpec spec)
+ => AddToBalanceAndCreateIfNotExists(address, balance, spec, out _);
+
public void Commit(IReleaseSpec releaseSpec, bool commitRoots, bool isGenesis)
=> Commit(releaseSpec, NullStateTracer.Instance, commitRoots, isGenesis);
diff --git a/src/Nethermind/Nethermind.State/TracedAccessWorldState.cs b/src/Nethermind/Nethermind.State/TracedAccessWorldState.cs
new file mode 100644
index 00000000000..3a81f7b47ac
--- /dev/null
+++ b/src/Nethermind/Nethermind.State/TracedAccessWorldState.cs
@@ -0,0 +1,172 @@
+// SPDX-FileCopyrightText: 2025 Demerzel Solutions Limited
+// SPDX-License-Identifier: LGPL-3.0-only
+
+using System;
+using Nethermind.Core;
+using Nethermind.Core.BlockAccessLists;
+using Nethermind.Core.Crypto;
+using Nethermind.Core.Specs;
+using Nethermind.Evm.State;
+using Nethermind.Int256;
+
+namespace Nethermind.State;
+
+public class TracedAccessWorldState(IWorldState innerWorldState) : WrappedWorldState(innerWorldState), IPreBlockCaches
+{
+ public bool Enabled { get; set; } = false;
+ public BlockAccessList BlockAccessList = new();
+
+ public PreBlockCaches Caches => (_innerWorldState as IPreBlockCaches).Caches;
+
+ public bool IsWarmWorldState => (_innerWorldState as IPreBlockCaches).IsWarmWorldState;
+
+ public override void AddToBalance(Address address, in UInt256 balanceChange, IReleaseSpec spec)
+ => AddToBalance(address, balanceChange, spec, out _);
+
+ public override void AddToBalance(Address address, in UInt256 balanceChange, IReleaseSpec spec, out UInt256 oldBalance)
+ {
+ _innerWorldState.AddToBalance(address, balanceChange, spec, out oldBalance);
+
+ if (Enabled)
+ {
+ UInt256 newBalance = oldBalance + balanceChange;
+ BlockAccessList.AddBalanceChange(address, oldBalance, newBalance);
+ }
+ }
+
+ public override bool AddToBalanceAndCreateIfNotExists(Address address, in UInt256 balanceChange, IReleaseSpec spec)
+ => AddToBalanceAndCreateIfNotExists(address, balanceChange, spec, out _);
+
+ public override bool AddToBalanceAndCreateIfNotExists(Address address, in UInt256 balanceChange, IReleaseSpec spec, out UInt256 oldBalance)
+ {
+ bool res = _innerWorldState.AddToBalanceAndCreateIfNotExists(address, balanceChange, spec, out oldBalance);
+
+ if (Enabled)
+ {
+ UInt256 newBalance = oldBalance + balanceChange;
+ BlockAccessList.AddBalanceChange(address, oldBalance, newBalance);
+ }
+
+ return res;
+ }
+
+ public override IDisposable BeginScope(BlockHeader? baseBlock)
+ {
+ BlockAccessList = new();
+ return _innerWorldState.BeginScope(baseBlock);
+ }
+
+ public override ReadOnlySpan Get(in StorageCell storageCell)
+ {
+ if (Enabled)
+ {
+ BlockAccessList.AddStorageRead(storageCell);
+ }
+ return _innerWorldState.Get(storageCell);
+ }
+
+ public override void IncrementNonce(Address address, UInt256 delta)
+ => IncrementNonce(address, delta, out _);
+
+ public override void IncrementNonce(Address address, UInt256 delta, out UInt256 oldNonce)
+ {
+ _innerWorldState.IncrementNonce(address, delta, out oldNonce);
+
+ if (Enabled)
+ {
+ BlockAccessList.AddNonceChange(address, (ulong)(oldNonce + delta));
+ }
+ }
+
+ public override bool InsertCode(Address address, in ValueHash256 codeHash, ReadOnlyMemory code, IReleaseSpec spec, bool isGenesis = false)
+ {
+ if (Enabled)
+ {
+ BlockAccessList.AddCodeChange(address, code.ToArray());
+ }
+ return _innerWorldState.InsertCode(address, codeHash, code, spec, isGenesis);
+ }
+
+ public override void Set(in StorageCell storageCell, byte[] newValue)
+ {
+ if (Enabled)
+ {
+ ReadOnlySpan oldValue = _innerWorldState.Get(storageCell);
+ BlockAccessList.AddStorageChange(storageCell, [.. oldValue], newValue);
+ }
+ _innerWorldState.Set(storageCell, newValue);
+ }
+
+ public override UInt256 GetBalance(Address address)
+ {
+ BlockAccessList.AddAccountRead(address);
+ return _innerWorldState.GetBalance(address);
+ }
+
+ public override ValueHash256 GetCodeHash(Address address)
+ {
+ BlockAccessList.AddAccountRead(address);
+ return _innerWorldState.GetCodeHash(address);
+ }
+
+ public override void SubtractFromBalance(Address address, in UInt256 balanceChange, IReleaseSpec spec)
+ {
+ UInt256 before = _innerWorldState.GetBalance(address);
+ UInt256 after = before - balanceChange;
+ _innerWorldState.SubtractFromBalance(address, balanceChange, spec);
+
+ if (Enabled)
+ {
+ BlockAccessList.AddBalanceChange(address, before, after);
+ }
+ }
+
+ public override void DeleteAccount(Address address)
+ {
+ BlockAccessList.DeleteAccount(address);
+ _innerWorldState.DeleteAccount(address);
+ }
+
+ public override void CreateAccount(Address address, in UInt256 balance, in UInt256 nonce = default)
+ {
+ BlockAccessList.AddAccountRead(address);
+ if (balance != 0)
+ {
+ BlockAccessList.AddBalanceChange(address, 0, balance);
+ }
+ _innerWorldState.CreateAccount(address, balance, nonce);
+ }
+
+ public override void CreateAccountIfNotExists(Address address, in UInt256 balance, in UInt256 nonce = default)
+ {
+ if (!_innerWorldState.AccountExists(address))
+ {
+ CreateAccount(address, balance, nonce);
+ }
+ }
+
+ public override bool TryGetAccount(Address address, out AccountStruct account)
+ {
+ if (Enabled)
+ {
+ BlockAccessList.AddAccountRead(address);
+ }
+ return _innerWorldState.TryGetAccount(address, out account);
+ }
+
+ public override void Restore(Snapshot snapshot)
+ {
+ if (Enabled)
+ {
+ BlockAccessList.Restore(snapshot.BlockAccessListSnapshot);
+ }
+ _innerWorldState.Restore(snapshot);
+ }
+
+ public override Snapshot TakeSnapshot(bool newTransactionStart = false)
+ {
+ int blockAccessListSnapshot = BlockAccessList.TakeSnapshot();
+ Snapshot snapshot = _innerWorldState.TakeSnapshot(newTransactionStart);
+ return new(snapshot.StorageSnapshot, snapshot.StateSnapshot, blockAccessListSnapshot);
+ }
+}
diff --git a/src/Nethermind/Nethermind.State/WorldState.cs b/src/Nethermind/Nethermind.State/WorldState.cs
index 26bdb9f5f22..84d47ce0652 100644
--- a/src/Nethermind/Nethermind.State/WorldState.cs
+++ b/src/Nethermind/Nethermind.State/WorldState.cs
@@ -14,7 +14,6 @@
using Nethermind.Evm.Tracing.State;
using Nethermind.Int256;
using Nethermind.Logging;
-using Nethermind.Trie;
using Nethermind.Trie.Pruning;
[assembly: InternalsVisibleTo("Ethereum.Test.Base")]
@@ -198,30 +197,38 @@ public bool InsertCode(Address address, in ValueHash256 codeHash, ReadOnlyMemory
DebugGuardInScope();
return _stateProvider.InsertCode(address, codeHash, code, spec, isGenesis);
}
- public void AddToBalance(Address address, in UInt256 balanceChange, IReleaseSpec spec)
+ public void AddToBalance(Address address, in UInt256 balanceChange, IReleaseSpec spec, out UInt256 oldBalance)
{
DebugGuardInScope();
- _stateProvider.AddToBalance(address, balanceChange, spec);
+ _stateProvider.AddToBalance(address, balanceChange, spec, out oldBalance);
}
- public bool AddToBalanceAndCreateIfNotExists(Address address, in UInt256 balanceChange, IReleaseSpec spec)
+ public void AddToBalance(Address address, in UInt256 balanceChange, IReleaseSpec spec)
+ => AddToBalance(address, balanceChange, spec, out _);
+ public bool AddToBalanceAndCreateIfNotExists(Address address, in UInt256 balanceChange, IReleaseSpec spec, out UInt256 oldBalance)
{
DebugGuardInScope();
- return _stateProvider.AddToBalanceAndCreateIfNotExists(address, balanceChange, spec);
+ return _stateProvider.AddToBalanceAndCreateIfNotExists(address, balanceChange, spec, out oldBalance);
}
- public void SubtractFromBalance(Address address, in UInt256 balanceChange, IReleaseSpec spec)
+ public bool AddToBalanceAndCreateIfNotExists(Address address, in UInt256 balanceChange, IReleaseSpec spec)
+ => AddToBalanceAndCreateIfNotExists(address, balanceChange, spec, out _);
+ public void SubtractFromBalance(Address address, in UInt256 balanceChange, IReleaseSpec spec, out UInt256 oldBalance)
{
DebugGuardInScope();
- _stateProvider.SubtractFromBalance(address, balanceChange, spec);
+ _stateProvider.SubtractFromBalance(address, balanceChange, spec, out oldBalance);
}
+ public void SubtractFromBalance(Address address, in UInt256 balanceChange, IReleaseSpec spec)
+ => SubtractFromBalance(address, balanceChange, spec, out _);
public void UpdateStorageRoot(Address address, Hash256 storageRoot)
{
DebugGuardInScope();
_stateProvider.UpdateStorageRoot(address, storageRoot);
}
public void IncrementNonce(Address address, UInt256 delta)
+ => IncrementNonce(address, delta, out _);
+ public void IncrementNonce(Address address, UInt256 delta, out UInt256 oldNonce)
{
DebugGuardInScope();
- _stateProvider.IncrementNonce(address, delta);
+ _stateProvider.IncrementNonce(address, delta, out oldNonce);
}
public void DecrementNonce(Address address, UInt256 delta)
{
@@ -268,16 +275,16 @@ public IDisposable BeginScope(BlockHeader? baseBlock)
public bool IsInScope => _isInScope;
- public ref readonly UInt256 GetBalance(Address address)
+ public UInt256 GetBalance(Address address)
{
DebugGuardInScope();
- return ref _stateProvider.GetBalance(address);
+ return _stateProvider.GetBalance(address);
}
public ValueHash256 GetStorageRoot(Address address)
{
DebugGuardInScope();
- if (address == null) throw new ArgumentNullException(nameof(address));
+ ArgumentNullException.ThrowIfNull(address);
return _stateProvider.GetStorageRoot(address);
}
@@ -293,10 +300,10 @@ public byte[] GetCode(in ValueHash256 codeHash)
return _stateProvider.GetCode(in codeHash);
}
- public ref readonly ValueHash256 GetCodeHash(Address address)
+ public ValueHash256 GetCodeHash(Address address)
{
DebugGuardInScope();
- return ref _stateProvider.GetCodeHash(address);
+ return _stateProvider.GetCodeHash(address);
}
ValueHash256 IAccountStateProvider.GetCodeHash(Address address)
@@ -342,9 +349,9 @@ public Snapshot TakeSnapshot(bool newTransactionStart = false)
DebugGuardInScope();
int persistentSnapshot = _persistentStorageProvider.TakeSnapshot(newTransactionStart);
int transientSnapshot = _transientStorageProvider.TakeSnapshot(newTransactionStart);
- Snapshot.Storage storageSnapshot = new Snapshot.Storage(persistentSnapshot, transientSnapshot);
+ Snapshot.Storage storageSnapshot = new(persistentSnapshot, transientSnapshot);
int stateSnapshot = _stateProvider.TakeSnapshot();
- return new Snapshot(storageSnapshot, stateSnapshot);
+ return new Snapshot(storageSnapshot, stateSnapshot, -1);
}
public void Restore(Snapshot snapshot)
@@ -358,7 +365,7 @@ public void Restore(Snapshot snapshot)
internal void Restore(int state, int persistentStorage, int transientStorage)
{
DebugGuardInScope();
- Restore(new Snapshot(new Snapshot.Storage(persistentStorage, transientStorage), state));
+ Restore(new Snapshot(new Snapshot.Storage(persistentStorage, transientStorage), state, -1));
}
public void SetNonce(Address address, in UInt256 nonce)
diff --git a/src/Nethermind/Nethermind.State/WorldStateMetricsDecorator.cs b/src/Nethermind/Nethermind.State/WorldStateMetricsDecorator.cs
index 799ef5d0709..9a9142d8b56 100644
--- a/src/Nethermind/Nethermind.State/WorldStateMetricsDecorator.cs
+++ b/src/Nethermind/Nethermind.State/WorldStateMetricsDecorator.cs
@@ -4,9 +4,7 @@
using System;
using System.Diagnostics;
using Nethermind.Core;
-using Nethermind.Core.Collections;
using Nethermind.Core.Crypto;
-using Nethermind.Core.Eip2930;
using Nethermind.Core.Specs;
using Nethermind.Evm.State;
using Nethermind.Evm.Tracing.State;
@@ -14,123 +12,76 @@
namespace Nethermind.State;
-public class WorldStateMetricsDecorator(IWorldState innerState) : IWorldState
+public class WorldStateMetricsDecorator(IWorldState innerWorldState) : WrappedWorldState(innerWorldState)
{
- public void Restore(Snapshot snapshot) => innerState.Restore(snapshot);
-
- public bool TryGetAccount(Address address, out AccountStruct account) => innerState.TryGetAccount(address, out account);
-
- public byte[] GetOriginal(in StorageCell storageCell) => innerState.GetOriginal(in storageCell);
-
- public ReadOnlySpan Get(in StorageCell storageCell) => innerState.Get(in storageCell);
-
- public void Set(in StorageCell storageCell, byte[] newValue) => innerState.Set(in storageCell, newValue);
-
- public ReadOnlySpan GetTransientState(in StorageCell storageCell) => innerState.GetTransientState(in storageCell);
-
- public void SetTransientState(in StorageCell storageCell, byte[] newValue) => innerState.SetTransientState(in storageCell, newValue);
-
- public void Reset(bool resetBlockChanges = true)
+ public override void Reset(bool resetBlockChanges = true)
{
StateMerkleizationTime = 0d;
- innerState.Reset(resetBlockChanges);
+ _innerWorldState.Reset(resetBlockChanges);
}
- public Snapshot TakeSnapshot(bool newTransactionStart = false) => innerState.TakeSnapshot(newTransactionStart);
-
- public void WarmUp(AccessList? accessList) => innerState.WarmUp(accessList);
-
- public void WarmUp(Address address) => innerState.WarmUp(address);
-
- public void ClearStorage(Address address) => innerState.ClearStorage(address);
-
- public void RecalculateStateRoot()
+ public override void RecalculateStateRoot()
{
long start = Stopwatch.GetTimestamp();
- innerState.RecalculateStateRoot();
+ _innerWorldState.RecalculateStateRoot();
StateMerkleizationTime += Stopwatch.GetElapsedTime(start).TotalMilliseconds;
}
- public Hash256 StateRoot => innerState.StateRoot;
-
public double StateMerkleizationTime { get; private set; }
- public void DeleteAccount(Address address) => innerState.DeleteAccount(address);
+ public override void DeleteAccount(Address address)
+ => _innerWorldState.DeleteAccount(address);
- public void CreateAccount(Address address, in UInt256 balance, in UInt256 nonce = default) =>
- innerState.CreateAccount(address, in balance, in nonce);
+ public override void CreateAccount(Address address, in UInt256 balance, in UInt256 nonce = default)
+ => _innerWorldState.CreateAccount(address, in balance, in nonce);
- public void CreateAccountIfNotExists(Address address, in UInt256 balance, in UInt256 nonce = default) =>
- innerState.CreateAccountIfNotExists(address, in balance, in nonce);
+ public override void CreateAccountIfNotExists(Address address, in UInt256 balance, in UInt256 nonce = default) =>
+ _innerWorldState.CreateAccountIfNotExists(address, in balance, in nonce);
- public void CreateEmptyAccountIfDeleted(Address address) =>
- innerState.CreateEmptyAccountIfDeleted(address);
+ public override void CreateEmptyAccountIfDeleted(Address address) =>
+ _innerWorldState.CreateEmptyAccountIfDeleted(address);
- public bool InsertCode(Address address, in ValueHash256 codeHash, ReadOnlyMemory code, IReleaseSpec spec, bool isGenesis = false) =>
- innerState.InsertCode(address, in codeHash, code, spec, isGenesis);
+ public override bool InsertCode(Address address, in ValueHash256 codeHash, ReadOnlyMemory code, IReleaseSpec spec, bool isGenesis = false)
+ => _innerWorldState.InsertCode(address, in codeHash, code, spec, isGenesis);
- public void AddToBalance(Address address, in UInt256 balanceChange, IReleaseSpec spec) =>
- innerState.AddToBalance(address, in balanceChange, spec);
+ public override void AddToBalance(Address address, in UInt256 balanceChange, IReleaseSpec spec)
+ => _innerWorldState.AddToBalance(address, in balanceChange, spec);
- public bool AddToBalanceAndCreateIfNotExists(Address address, in UInt256 balanceChange, IReleaseSpec spec) =>
- innerState.AddToBalanceAndCreateIfNotExists(address, in balanceChange, spec);
+ public override bool AddToBalanceAndCreateIfNotExists(Address address, in UInt256 balanceChange, IReleaseSpec spec)
+ => _innerWorldState.AddToBalanceAndCreateIfNotExists(address, in balanceChange, spec);
- public void SubtractFromBalance(Address address, in UInt256 balanceChange, IReleaseSpec spec) =>
- innerState.SubtractFromBalance(address, in balanceChange, spec);
+ public override void SubtractFromBalance(Address address, in UInt256 balanceChange, IReleaseSpec spec)
+ => _innerWorldState.SubtractFromBalance(address, in balanceChange, spec);
- public void IncrementNonce(Address address, UInt256 delta) => innerState.IncrementNonce(address, delta);
+ public override void IncrementNonce(Address address, UInt256 delta)
+ => _innerWorldState.IncrementNonce(address, delta);
- public void DecrementNonce(Address address, UInt256 delta) => innerState.DecrementNonce(address, delta);
+ public override void DecrementNonce(Address address, UInt256 delta)
+ => _innerWorldState.DecrementNonce(address, delta);
- public void SetNonce(Address address, in UInt256 nonce) => innerState.SetNonce(address, nonce);
+ public override void SetNonce(Address address, in UInt256 nonce)
+ => _innerWorldState.SetNonce(address, nonce);
- public void Commit(IReleaseSpec releaseSpec, bool isGenesis = false, bool commitRoots = true)
+ public override void Commit(IReleaseSpec releaseSpec, bool isGenesis = false, bool commitRoots = true)
{
long start = Stopwatch.GetTimestamp();
- innerState.Commit(releaseSpec, isGenesis, commitRoots);
+ _innerWorldState.Commit(releaseSpec, isGenesis, commitRoots);
if (commitRoots)
StateMerkleizationTime += Stopwatch.GetElapsedTime(start).TotalMilliseconds;
}
- public void Commit(IReleaseSpec releaseSpec, IWorldStateTracer tracer, bool isGenesis = false, bool commitRoots = true)
+ public override void Commit(IReleaseSpec releaseSpec, IWorldStateTracer tracer, bool isGenesis = false, bool commitRoots = true)
{
long start = Stopwatch.GetTimestamp();
- innerState.Commit(releaseSpec, tracer, isGenesis, commitRoots);
+ _innerWorldState.Commit(releaseSpec, tracer, isGenesis, commitRoots);
if (commitRoots)
StateMerkleizationTime += Stopwatch.GetElapsedTime(start).TotalMilliseconds;
}
- public void CommitTree(long blockNumber)
+ public override void CommitTree(long blockNumber)
{
long start = Stopwatch.GetTimestamp();
- innerState.CommitTree(blockNumber);
+ _innerWorldState.CommitTree(blockNumber);
StateMerkleizationTime += Stopwatch.GetElapsedTime(start).TotalMilliseconds;
}
-
- public ArrayPoolList? GetAccountChanges() => innerState.GetAccountChanges();
-
- public void ResetTransient() => innerState.ResetTransient();
-
- public byte[]? GetCode(Address address) => innerState.GetCode(address);
-
- public byte[]? GetCode(in ValueHash256 codeHash) => innerState.GetCode(in codeHash);
-
- public bool IsContract(Address address) => innerState.IsContract(address);
-
- public bool AccountExists(Address address) => innerState.AccountExists(address);
-
- public bool IsDeadAccount(Address address) => innerState.IsDeadAccount(address);
-
- public bool HasStateForBlock(BlockHeader? stateRoot) => innerState.HasStateForBlock(stateRoot);
-
- public IDisposable BeginScope(BlockHeader? baseBlock) => innerState.BeginScope(baseBlock);
- public bool IsInScope => innerState.IsInScope;
-
- public ref readonly UInt256 GetBalance(Address account) => ref innerState.GetBalance(account);
-
- UInt256 IAccountStateProvider.GetBalance(Address address) => innerState.GetBalance(address);
-
- public ref readonly ValueHash256 GetCodeHash(Address address) => ref innerState.GetCodeHash(address);
-
- ValueHash256 IAccountStateProvider.GetCodeHash(Address address) => innerState.GetCodeHash(address);
}
diff --git a/src/Nethermind/Nethermind.State/WrappedWorldState.cs b/src/Nethermind/Nethermind.State/WrappedWorldState.cs
new file mode 100644
index 00000000000..662a26727f4
--- /dev/null
+++ b/src/Nethermind/Nethermind.State/WrappedWorldState.cs
@@ -0,0 +1,148 @@
+// SPDX-FileCopyrightText: 2025 Demerzel Solutions Limited
+// SPDX-License-Identifier: LGPL-3.0-only
+
+using System;
+using Nethermind.Core;
+using Nethermind.Core.Collections;
+using Nethermind.Core.Crypto;
+using Nethermind.Core.Eip2930;
+using Nethermind.Core.Specs;
+using Nethermind.Evm.State;
+using Nethermind.Evm.Tracing.State;
+using Nethermind.Int256;
+
+namespace Nethermind.State;
+
+public class WrappedWorldState(IWorldState innerWorldState) : IWorldState
+{
+ protected IWorldState _innerWorldState = innerWorldState;
+ public bool IsInScope => _innerWorldState.IsInScope;
+
+ public Hash256 StateRoot => _innerWorldState.StateRoot;
+
+ public bool AccountExists(Address address)
+ => _innerWorldState.AccountExists(address);
+
+ public virtual void AddToBalance(Address address, in UInt256 balanceChange, IReleaseSpec spec)
+ => _innerWorldState.AddToBalance(address, balanceChange, spec);
+
+ public virtual void AddToBalance(Address address, in UInt256 balanceChange, IReleaseSpec spec, out UInt256 oldBalance)
+ => _innerWorldState.AddToBalance(address, balanceChange, spec, out oldBalance);
+
+ public virtual bool AddToBalanceAndCreateIfNotExists(Address address, in UInt256 balanceChange, IReleaseSpec spec)
+ => _innerWorldState.AddToBalanceAndCreateIfNotExists(address, balanceChange, spec);
+
+ public virtual bool AddToBalanceAndCreateIfNotExists(Address address, in UInt256 balanceChange, IReleaseSpec spec, out UInt256 oldBalance)
+ => _innerWorldState.AddToBalanceAndCreateIfNotExists(address, balanceChange, spec, out oldBalance);
+
+ public virtual IDisposable BeginScope(BlockHeader? baseBlock)
+ => _innerWorldState.BeginScope(baseBlock);
+
+ public void ClearStorage(Address address)
+ => _innerWorldState.ClearStorage(address);
+
+ public virtual void Commit(IReleaseSpec releaseSpec, bool isGenesis = false, bool commitRoots = true)
+ => _innerWorldState.Commit(releaseSpec, isGenesis, commitRoots);
+
+ public virtual void Commit(IReleaseSpec releaseSpec, IWorldStateTracer tracer, bool isGenesis = false, bool commitRoots = true)
+ => _innerWorldState.Commit(releaseSpec, tracer, isGenesis, commitRoots);
+
+ public virtual void CommitTree(long blockNumber)
+ => _innerWorldState.CommitTree(blockNumber);
+
+ public virtual void CreateAccount(Address address, in UInt256 balance, in UInt256 nonce = default)
+ => _innerWorldState.CreateAccount(address, balance, nonce);
+
+ public virtual void CreateAccountIfNotExists(Address address, in UInt256 balance, in UInt256 nonce = default)
+ => _innerWorldState.CreateAccountIfNotExists(address, balance, nonce);
+
+ public virtual void CreateEmptyAccountIfDeleted(Address address) =>
+ _innerWorldState.CreateEmptyAccountIfDeleted(address);
+
+ public virtual void DecrementNonce(Address address, UInt256 delta)
+ => _innerWorldState.DecrementNonce(address, delta);
+
+ public virtual void DeleteAccount(Address address)
+ => _innerWorldState.DeleteAccount(address);
+
+ public virtual ReadOnlySpan Get(in StorageCell storageCell)
+ => _innerWorldState.Get(storageCell);
+
+ public ArrayPoolList? GetAccountChanges()
+ => _innerWorldState.GetAccountChanges();
+
+ public virtual UInt256 GetBalance(Address address)
+ => _innerWorldState.GetBalance(address);
+
+ public byte[]? GetCode(Address address)
+ => _innerWorldState.GetCode(address);
+
+ public byte[]? GetCode(in ValueHash256 codeHash)
+ => _innerWorldState.GetCode(codeHash);
+
+ public virtual ValueHash256 GetCodeHash(Address address)
+ => _innerWorldState.GetCodeHash(address);
+
+ public byte[] GetOriginal(in StorageCell storageCell)
+ => _innerWorldState.GetOriginal(storageCell);
+
+ public ReadOnlySpan GetTransientState(in StorageCell storageCell)
+ => _innerWorldState.GetTransientState(storageCell);
+
+ public bool HasStateForBlock(BlockHeader? baseBlock)
+ => _innerWorldState.HasStateForBlock(baseBlock);
+
+ public virtual void IncrementNonce(Address address, UInt256 delta)
+ => _innerWorldState.IncrementNonce(address, delta);
+
+ public virtual void IncrementNonce(Address address, UInt256 delta, out UInt256 oldNonce)
+ => _innerWorldState.IncrementNonce(address, delta, out oldNonce);
+
+ public virtual bool InsertCode(Address address, in ValueHash256 codeHash, ReadOnlyMemory code, IReleaseSpec spec, bool isGenesis = false)
+ => _innerWorldState.InsertCode(address, codeHash, code, spec, isGenesis);
+
+ public bool IsContract(Address address)
+ => _innerWorldState.IsContract(address);
+
+ public bool IsDeadAccount(Address address)
+ => _innerWorldState.IsDeadAccount(address);
+
+ public virtual void RecalculateStateRoot()
+ => _innerWorldState.RecalculateStateRoot();
+
+ public virtual void Reset(bool resetBlockChanges = true)
+ => _innerWorldState.Reset(resetBlockChanges);
+
+ public void ResetTransient()
+ => _innerWorldState.ResetTransient();
+
+ public virtual void Restore(Snapshot snapshot)
+ => _innerWorldState.Restore(snapshot);
+
+ public virtual void Set(in StorageCell storageCell, byte[] newValue)
+ => _innerWorldState.Set(storageCell, newValue);
+
+ public virtual void SetNonce(Address address, in UInt256 nonce)
+ => _innerWorldState.SetNonce(address, nonce);
+
+ public void SetTransientState(in StorageCell storageCell, byte[] newValue)
+ => _innerWorldState.SetTransientState(storageCell, newValue);
+
+ public virtual void SubtractFromBalance(Address address, in UInt256 balanceChange, IReleaseSpec spec)
+ => _innerWorldState.SubtractFromBalance(address, balanceChange, spec);
+
+ public virtual void SubtractFromBalance(Address address, in UInt256 balanceChange, IReleaseSpec spec, out UInt256 oldBalance)
+ => _innerWorldState.SubtractFromBalance(address, balanceChange, spec, out oldBalance);
+
+ public virtual Snapshot TakeSnapshot(bool newTransactionStart = false)
+ => _innerWorldState.TakeSnapshot(newTransactionStart);
+
+ public virtual bool TryGetAccount(Address address, out AccountStruct account)
+ => _innerWorldState.TryGetAccount(address, out account);
+
+ public void WarmUp(AccessList? accessList)
+ => _innerWorldState.WarmUp(accessList);
+
+ public void WarmUp(Address address)
+ => _innerWorldState.WarmUp(address);
+}
diff --git a/src/Nethermind/Nethermind.Test.Runner/BlockchainTestsRunner.cs b/src/Nethermind/Nethermind.Test.Runner/BlockchainTestsRunner.cs
index 0856b9002fc..b9b3013d39f 100644
--- a/src/Nethermind/Nethermind.Test.Runner/BlockchainTestsRunner.cs
+++ b/src/Nethermind/Nethermind.Test.Runner/BlockchainTestsRunner.cs
@@ -29,9 +29,18 @@ public BlockchainTestsRunner(ITestSourceLoader testsSource, string? filter, ulon
public async Task