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> RunTestsAsync() { List testResults = new(); - IEnumerable tests = _testsSource.LoadTests(); - foreach (BlockchainTest test in tests) + IEnumerable tests = _testsSource.LoadTests(); + foreach (EthereumTest loadedTest in tests) { + if (loadedTest as FailedToLoadTest is not null) + { + WriteRed(loadedTest.LoadFailure); + testResults.Add(new EthereumTestResult(loadedTest.Name, loadedTest.LoadFailure)); + continue; + } + + BlockchainTest test = loadedTest as BlockchainTest; + if (_filter is not null && !Regex.Match(test.Name, $"^({_filter})").Success) continue; Setup();