diff --git a/src/Nethermind/Ethereum.Test.Base/BlockchainTestBase.cs b/src/Nethermind/Ethereum.Test.Base/BlockchainTestBase.cs index 374f738ab79..ca5450e797f 100644 --- a/src/Nethermind/Ethereum.Test.Base/BlockchainTestBase.cs +++ b/src/Nethermind/Ethereum.Test.Base/BlockchainTestBase.cs @@ -86,10 +86,10 @@ protected async Task RunTest(BlockchainTest test, Stopwatch? bool isEngineTest = test.Blocks is null && test.EngineNewPayloads is not null; - List<(ForkActivation Activation, IReleaseSpec Spec)> transitions = - isEngineTest ? - [((ForkActivation)0, test.Network)] : - [((ForkActivation)0, test.GenesisSpec), ((ForkActivation)1, test.Network)]; // TODO: this thing took a lot of time to find after it was removed!, genesis block is always initialized with Frontier + List<(ForkActivation Activation, IReleaseSpec Spec)> transitions = [((ForkActivation)0, test.Network)]; + // isEngineTest ? + // [((ForkActivation)0, test.Network)] : + // [((ForkActivation)0, test.GenesisSpec), ((ForkActivation)1, test.Network)]; // TODO: this thing took a lot of time to find after it was removed!, genesis block is always initialized with Frontier if (test.NetworkAfterTransition is not null) { @@ -98,7 +98,7 @@ protected async Task RunTest(BlockchainTest test, Stopwatch? ISpecProvider specProvider = new CustomSpecProvider(test.ChainId, test.ChainId, transitions.ToArray()); - Assert.That(isEngineTest || test.ChainId == GnosisSpecProvider.Instance.ChainId || specProvider.GenesisSpec == Frontier.Instance, "Expected genesis spec to be Frontier for blockchain tests"); + // Assert.That(isEngineTest || test.ChainId == GnosisSpecProvider.Instance.ChainId || specProvider.GenesisSpec == Frontier.Instance, "Expected genesis spec to be Frontier for blockchain tests"); if (test.Network is Cancun || test.NetworkAfterTransition is Cancun) { @@ -168,6 +168,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); @@ -352,6 +353,9 @@ private async static Task RunNewPayloads(TestEngineNewPayloadsJson[]? newPayload { 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); if (testBlockJson.BlockHeader is not null) { diff --git a/src/Nethermind/Ethereum.Test.Base/BlockchainTestJson.cs b/src/Nethermind/Ethereum.Test.Base/BlockchainTestJson.cs index c344cd773da..2727692ee16 100644 --- a/src/Nethermind/Ethereum.Test.Base/BlockchainTestJson.cs +++ b/src/Nethermind/Ethereum.Test.Base/BlockchainTestJson.cs @@ -20,6 +20,7 @@ public class BlockchainTestJson public IReleaseSpec? EthereumNetworkAfterTransition { get; set; } public ForkActivation? TransitionForkActivation { get; set; } public string? LastBlockHash { get; set; } + public ConfigJson? Config { get; set; } public string? GenesisRlp { get; set; } public TestBlockJson[]? Blocks { get; set; } @@ -34,4 +35,18 @@ public class BlockchainTestJson public string? SealEngine { get; set; } public string? LoadFailure { get; set; } } + + public class ConfigJson + { + public string? Network { get; set; } + public string? Chainid { get; set; } + public Dictionary? BlobSchedule; + } + + public class BlobScheduleEntryJson + { + public string? Target; + public string? Max; + public string? BaseFeeUpdateFraction; + } } diff --git a/src/Nethermind/Ethereum.Test.Base/FileTestsSource.cs b/src/Nethermind/Ethereum.Test.Base/FileTestsSource.cs index 67b713e570e..1d0d8cc7691 100644 --- a/src/Nethermind/Ethereum.Test.Base/FileTestsSource.cs +++ b/src/Nethermind/Ethereum.Test.Base/FileTestsSource.cs @@ -28,6 +28,7 @@ public IEnumerable LoadTests(TestType testType) return []; } + Console.WriteLine("loaded " + _fileName); string json = File.ReadAllText(_fileName, Encoding.Default); return testType switch diff --git a/src/Nethermind/Ethereum.Test.Base/JsonToEthereumTest.cs b/src/Nethermind/Ethereum.Test.Base/JsonToEthereumTest.cs index 36b05b9fb0e..3ee3c5a74d7 100644 --- a/src/Nethermind/Ethereum.Test.Base/JsonToEthereumTest.cs +++ b/src/Nethermind/Ethereum.Test.Base/JsonToEthereumTest.cs @@ -19,6 +19,7 @@ using Nethermind.Serialization.Json; using Nethermind.Serialization.Rlp; using Nethermind.Specs; +using Nethermind.Specs.Test; namespace Ethereum.Test.Base { @@ -74,7 +75,7 @@ public static BlockHeader Convert(TestBlockHeaderJson? headerJson) 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), + BlockAccessListHash = headerJson.BlockAccessListHash is null ? null : new Hash256(headerJson.BlockAccessListHash), }; if (headerJson.BaseFeePerGas is not null) @@ -113,7 +114,7 @@ public static BlockHeader Convert(TestBlockHeaderJson? headerJson) 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), + 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), @@ -412,8 +413,9 @@ public static IEnumerable ConvertToBlockchainTests(string json) { testsInFile = _serializer.Deserialize>(json); } - catch (Exception) + catch (Exception e) { + Console.WriteLine(e); Dictionary half = _serializer.Deserialize>(json); testsInFile = []; @@ -429,11 +431,11 @@ public static IEnumerable ConvertToBlockchainTests(string json) string[] transitionInfo = testSpec.Network.Split("At"); string[] networks = transitionInfo[0].Split("To"); - testSpec.EthereumNetwork = SpecNameParser.Parse(networks[0]); + testSpec.EthereumNetwork = LoadSpec(networks[0], testSpec.Config?.BlobSchedule); if (transitionInfo.Length > 1) { testSpec.TransitionForkActivation = TransitionForkActivation(transitionInfo[1]); - testSpec.EthereumNetworkAfterTransition = SpecNameParser.Parse(networks[1]); + testSpec.EthereumNetworkAfterTransition = LoadSpec(networks[1], testSpec.Config?.BlobSchedule); } (string name, string category) = GetNameAndCategory(testName); @@ -443,6 +445,23 @@ public static IEnumerable ConvertToBlockchainTests(string json) return testsByName; } + private static IReleaseSpec LoadSpec(string name, Dictionary? blobSchedule) + { + IReleaseSpec spec = SpecNameParser.Parse(name); + if (blobSchedule is null) + { + return spec; + } + + BlobScheduleEntryJson blobCount = blobSchedule[name]; + return new OverridableReleaseSpec(spec) + { + MaxBlobCount = System.Convert.ToUInt64(blobCount.Max, 16), + TargetBlobCount = System.Convert.ToUInt64(blobCount.Target, 16), + BlobBaseFeeUpdateFraction = System.Convert.ToUInt64(blobCount.BaseFeeUpdateFraction, 16) + }; + } + private static (string name, string category) GetNameAndCategory(string key) { key = key.Replace('\\', '/'); diff --git a/src/Nethermind/Nethermind.Blockchain.Test/BlockAccessListTests.cs b/src/Nethermind/Nethermind.Blockchain.Test/BlockAccessListTests.cs new file mode 100644 index 00000000000..6f97e0f270a --- /dev/null +++ b/src/Nethermind/Nethermind.Blockchain.Test/BlockAccessListTests.cs @@ -0,0 +1,663 @@ +// 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.Reflection; +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 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, 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, 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 = [.. 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([.. Enumerable.Repeat(100, 32)], [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([.. Enumerable.Repeat(50, 32)]); + StorageRead storageRead2 = new([.. 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( + TestItem.AddressA, + storageChangesDict, + [storageRead, storageRead2], + balanceChangesList, + nonceChangesList, + 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.AccountChanges.Count, Is.EqualTo(10)); + + Address newContractAddress = ContractAddress.From(TestItem.AddressA, 1); + Address newContractAddress2 = ContractAddress.From(TestItem.AddressA, 2); + + AccountChanges addressAChanges = blockAccessList.GetAccountChanges(TestItem.AddressA)!; + AccountChanges addressBChanges = blockAccessList.GetAccountChanges(TestItem.AddressB)!; + AccountChanges addressCChanges = blockAccessList.GetAccountChanges(TestItem.AddressC)!; + AccountChanges addressDChanges = blockAccessList.GetAccountChanges(TestItem.AddressD)!; + AccountChanges newContractChanges = blockAccessList.GetAccountChanges(newContractAddress)!; + AccountChanges newContractChanges2 = blockAccessList.GetAccountChanges(newContractAddress2)!; + AccountChanges eip2935Changes = blockAccessList.GetAccountChanges(Eip2935Constants.BlockHashHistoryAddress)!; + AccountChanges eip4788Changes = blockAccessList.GetAccountChanges(Eip4788Constants.BeaconRootsAddress)!; + AccountChanges eip7002Changes = blockAccessList.GetAccountChanges(Eip7002Constants.WithdrawalRequestPredeployAddress)!; + AccountChanges eip7251Changes = blockAccessList.GetAccountChanges(Eip7251Constants.ConsolidationRequestPredeployAddress)!; + + 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, parentHash.BytesToArray()); + StorageChange calldataStorageChange = new(0, Bytes32.Zero.Unwrap()); + StorageChange timestampStorageChange = new(0, Bytes.FromHexString("0x00000000000000000000000000000000000000000000000000000000000F4240")); + StorageChange zeroStorageChangeEnd = new(3, Bytes32.Zero.Unwrap()); + + 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( + TestItem.AddressA, + [], + [], + new SortedList { { 1, new(1, addressABalance) }, { 2, new(2, addressABalance2) }, { 3, new(3, addressABalance3) } }, + new SortedList { { 1, new(1, 1) }, { 2, new(2, 2) }, { 3, new(3, 3) } }, + [] + ))); + + Assert.That(addressBChanges, Is.EqualTo(new AccountChanges( + TestItem.AddressB, + [], + [], + [], + [], + [] + ))); + + Assert.That(addressCChanges, Is.EqualTo(new AccountChanges( + TestItem.AddressC, + [], + [], + new SortedList { { 1, new(1, new UInt256(GasCostOf.Transaction)) }, { 2, new(2, new UInt256(gasUsedBeforeFinal)) }, { 3, new(3, new UInt256(gasUsed)) } }, + [], + [] + ))); + + Assert.That(addressDChanges, Is.EqualTo(new AccountChanges( + TestItem.AddressD, + [], + [], + new SortedList { { 4, new(4, 1.GWei()) } }, + [], + [] + ))); + + Assert.That(newContractChanges, Is.EqualTo(new AccountChanges( + newContractAddress, + [], + [], + [], + new SortedList { { 2, new(2, 1) } }, + new SortedList { { 2, new(2, Eip2935TestConstants.Code) } } + ))); + + Assert.That(newContractChanges2, Is.EqualTo(new AccountChanges( + newContractAddress2, + [], + new SortedSet { ToStorageRead(slot1) }, + [], + [], + [] + ))); + + Assert.That(eip2935Changes, Is.EqualTo(new AccountChanges( + Eip2935Constants.BlockHashHistoryAddress, + new SortedDictionary(Bytes.Comparer) { { slot0, new SlotChanges(slot0, [parentHashStorageChange]) } }, + [], + [], + [], + [] + ))); + + // second storage read is not a change, so not recorded + Assert.That(eip4788Changes, Is.EqualTo(new AccountChanges( + Eip4788Constants.BeaconRootsAddress, + new SortedDictionary(Bytes.Comparer) { { eip4788Slot1, new SlotChanges(eip4788Slot1, [timestampStorageChange]) } }, + new SortedSet { ToStorageRead(eip4788Slot1), ToStorageRead(eip4788Slot2) }, + [], + [], + [] + ))); + + // storage reads make no changes + Assert.That(eip7002Changes, Is.EqualTo(new AccountChanges( + Eip7002Constants.WithdrawalRequestPredeployAddress, + [], + new SortedSet { + ToStorageRead(slot0), + ToStorageRead(slot1), + ToStorageRead(slot2), + ToStorageRead(slot3), + }, + [], + [], + [] + ))); + + // storage reads make no changes + Assert.That(eip7251Changes, Is.EqualTo(new AccountChanges( + Eip7251Constants.ConsolidationRequestPredeployAddress, + [], + new SortedSet { + ToStorageRead(slot0), + ToStorageRead(slot1), + ToStorageRead(slot2), + ToStorageRead(slot3), + }, + [], + [], + [] + ))); + } + } + + 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([.. newValue]); + } + + private static IEnumerable AccountChangesTestSource + { + get + { + yield return new TestCaseData( + "0xf89f9400000961ef480eb55e80d19ad83579a64c007002c0f884a00000000000000000000000000000000000000000000000000000000000000000a00000000000000000000000000000000000000000000000000000000000000001a00000000000000000000000000000000000000000000000000000000000000002a00000000000000000000000000000000000000000000000000000000000000003c0c0c0", + new AccountChanges( + Eip7002Constants.WithdrawalRequestPredeployAddress, + [], + new SortedSet { + ToStorageRead(ToStorageSlot(0)), + ToStorageRead(ToStorageSlot(1)), + ToStorageRead(ToStorageSlot(2)), + ToStorageRead(ToStorageSlot(3)) + }, + [], + [], + [] + )) + { TestName = "storage_reads" }; + + yield return new TestCaseData( + "0xf862940000f90827f1c53a10cb7a02335b175320002935f847f845a00000000000000000000000000000000000000000000000000000000000000000e3e280a0c382836f81d7e4055a0e280268371e17cc69a531efe2abee082e9b922d6050fdc0c0c0c0", + new AccountChanges( + Eip2935Constants.BlockHashHistoryAddress, + new SortedDictionary(Bytes.Comparer) { { ToStorageSlot(0), new SlotChanges(ToStorageSlot(0), [new(0, 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, Bytes.FromHexString("0xc382836f81d7e4055a0e280268371e17cc69a531efe2abee082e9b922d6050fd")); + StorageChange timestampStorageChange = new(0, Bytes.FromHexString("0x000000000000000000000000000000000000000000000000000000000000000c")); + SortedDictionary expectedAccountChanges = new() + { + {Eip7002Constants.WithdrawalRequestPredeployAddress, new( + Eip7002Constants.WithdrawalRequestPredeployAddress, + [], + new SortedSet { + ToStorageRead(slot0), + ToStorageRead(slot1), + ToStorageRead(slot2), + ToStorageRead(slot3), + }, + [], + [], + [] + )}, + {Eip7251Constants.ConsolidationRequestPredeployAddress, new( + Eip7251Constants.ConsolidationRequestPredeployAddress, + [], + new SortedSet { + ToStorageRead(slot0), + ToStorageRead(slot1), + ToStorageRead(slot2), + ToStorageRead(slot3), + }, + [], + [], + [] + )}, + {Eip2935Constants.BlockHashHistoryAddress, new( + Eip2935Constants.BlockHashHistoryAddress, + new SortedDictionary(Bytes.Comparer) { { slot0, new SlotChanges(slot0, [parentHashStorageChange]) } }, + [], + [], + [], + [] + )}, + {Eip4788Constants.BeaconRootsAddress, new( + Eip4788Constants.BeaconRootsAddress, + new SortedDictionary(Bytes.Comparer) { { eip4788Slot1, new SlotChanges(eip4788Slot1, [timestampStorageChange]) } }, + new SortedSet { + ToStorageRead([0x20, 0x0b]) + }, + [], + [], + [] + )}, + {new("0x2adc25665018aa1fe0e6bc666dac8fc2697ff9ba"), new( + new("0x2adc25665018aa1fe0e6bc666dac8fc2697ff9ba"), + [], + [], + new SortedList { { 1, new(1, 0x1319718811c8) } }, + [], + [] + )}, + {new("0xaccc7d92b051544a255b8a899071040739bada75"), new( + new("0xaccc7d92b051544a255b8a899071040739bada75"), + [], + [], + new SortedList { { 1, new(1, new(Bytes.FromHexString("0x3635c99aac6d15af9c"))) } }, + new SortedList { { 1, new(1, 1) } }, + [] + )}, + {new("0xd9c0e57d447779673b236c7423aeab84e931f3ba"), new( + new("0xd9c0e57d447779673b236c7423aeab84e931f3ba"), + [], + [], + 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 43c0b7632b5..0aaeb4f66c6 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() @@ -744,7 +742,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.Test/TransactionsExecutorTests.cs b/src/Nethermind/Nethermind.Blockchain.Test/TransactionsExecutorTests.cs index f61ef983831..8e36c8c6f49 100644 --- a/src/Nethermind/Nethermind.Blockchain.Test/TransactionsExecutorTests.cs +++ b/src/Nethermind/Nethermind.Blockchain.Test/TransactionsExecutorTests.cs @@ -367,6 +367,7 @@ public void BlockProductionTransactionsExecutor_calculates_block_size_using_prop public class WorldStateStab() : WorldState(Substitute.For(), Substitute.For(), LimboLogs.Instance), IWorldState { // we cannot mock ref methods - ref readonly UInt256 IWorldState.GetBalance(Address address) => ref UInt256.MaxValue; + // ref readonly UInt256 IWorldState.GetBalance(Address address) => ref UInt256.MaxValue; + UInt256 IWorldState.GetBalance(Address address) => UInt256.MaxValue; } } diff --git a/src/Nethermind/Nethermind.Blockchain/BeaconBlockRoot/IBeaconBlockRootHandler.cs b/src/Nethermind/Nethermind.Blockchain/BeaconBlockRoot/IBeaconBlockRootHandler.cs index b576b89c44b..5cfa5fafc6d 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 68c338bda3c..13815cb9ddd 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; UInt256 parentBlockIndex = new UInt256((ulong)((blockHeader.Number - 1) % spec.Eip2935RingBufferSize)); 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 0c19c64986d..db36e4851e5 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 757382c354d..0033ed60d44 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; @@ -170,7 +171,7 @@ static void Validate(Block block, object obj, string name, int expectedSize) } private void ReadRequests(Block block, IWorldState state, Address contractAddress, ref ArrayPoolListRef requests, - Transaction systemTx, ExecutionRequestType type, string contractEmptyError, string contractFailedError) + Transaction systemTx, ExecutionRequestType type, string contractEmptyError, string contractFailedError, ITxTracer? additionalTracer = null) { if (!state.HasCode(contractAddress)) { @@ -179,7 +180,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.BlockProductionTransactionsExecutor.cs b/src/Nethermind/Nethermind.Consensus/Processing/BlockProcessor.BlockProductionTransactionsExecutor.cs index 1418fe28401..fdddfd350b3 100644 --- a/src/Nethermind/Nethermind.Consensus/Processing/BlockProcessor.BlockProductionTransactionsExecutor.cs +++ b/src/Nethermind/Nethermind.Consensus/Processing/BlockProcessor.BlockProductionTransactionsExecutor.cs @@ -31,6 +31,7 @@ public class BlockProductionTransactionsExecutor( ILogManager logManager) : IBlockProductionTransactionsExecutor { + private readonly TracedAccessWorldState? _tracedAccessWorldState = stateProvider as TracedAccessWorldState; private readonly ILogger _logger = logManager.GetClassLogger(); protected EventHandler? _transactionProcessed; @@ -65,6 +66,7 @@ public virtual TxReceipt[] ProcessTransactions(Block block, ProcessingOptions pr // Check if we have gone over time or the payload has been requested if (token.IsCancellationRequested) break; + _tracedAccessWorldState?.BlockAccessList.IncrementBlockAccessIndex(); TxAction action = ProcessTransaction(block, currentTx, i++, receiptsTracer, processingOptions, consideredTx); if (action == TxAction.Stop) break; @@ -78,6 +80,8 @@ public virtual TxReceipt[] ProcessTransactions(Block block, ProcessingOptions pr } } } + _tracedAccessWorldState?.BlockAccessList.IncrementBlockAccessIndex(); + // Console.WriteLine($"Built block {i}, balIndex={(_tracedAccessWorldState is null ? "null" : _tracedAccessWorldState.BlockAccessList.Index)}"); block.Header.TxRoot = TxTrie.CalculateRoot(includedTx.AsSpan()); if (blockToProduce is not null) diff --git a/src/Nethermind/Nethermind.Consensus/Processing/BlockProcessor.BlockValidationTransactionsExecutor.cs b/src/Nethermind/Nethermind.Consensus/Processing/BlockProcessor.BlockValidationTransactionsExecutor.cs index d40b4067e56..7688908a513 100644 --- a/src/Nethermind/Nethermind.Consensus/Processing/BlockProcessor.BlockValidationTransactionsExecutor.cs +++ b/src/Nethermind/Nethermind.Consensus/Processing/BlockProcessor.BlockValidationTransactionsExecutor.cs @@ -3,7 +3,6 @@ using System.Diagnostics; using System.Diagnostics.CodeAnalysis; -using System.Linq; using System.Threading; using Nethermind.Blockchain; using Nethermind.Blockchain.Tracing; @@ -11,7 +10,6 @@ using Nethermind.Evm; using Nethermind.Evm.State; using Nethermind.Evm.TransactionProcessing; - using Metrics = Nethermind.Evm.Metrics; namespace Nethermind.Consensus.Processing @@ -24,6 +22,8 @@ public class BlockValidationTransactionsExecutor( BlockValidationTransactionsExecutor.ITransactionProcessedEventHandler? transactionProcessedEventHandler = null) : IBlockProcessor.IBlockTransactionsExecutor { + private readonly TracedAccessWorldState? _tracedAccessWorldState = stateProvider as TracedAccessWorldState; + public void SetBlockExecutionContext(in BlockExecutionContext blockExecutionContext) { transactionProcessor.SetBlockExecutionContext(in blockExecutionContext); @@ -35,10 +35,13 @@ public TxReceipt[] ProcessTransactions(Block block, ProcessingOptions processing for (int i = 0; i < block.Transactions.Length; i++) { + _tracedAccessWorldState?.BlockAccessList.IncrementBlockAccessIndex(); Transaction currentTx = block.Transactions[i]; ProcessTransaction(block, currentTx, i, receiptsTracer, processingOptions); } - return receiptsTracer.TxReceipts.ToArray(); + _tracedAccessWorldState?.BlockAccessList.IncrementBlockAccessIndex(); + + return [.. receiptsTracer.TxReceipts]; } protected virtual void ProcessTransaction(Block block, Transaction currentTx, int index, BlockReceiptsTracer receiptsTracer, ProcessingOptions processingOptions) diff --git a/src/Nethermind/Nethermind.Consensus/Processing/BlockProcessor.cs b/src/Nethermind/Nethermind.Consensus/Processing/BlockProcessor.cs index 7d79cfab55c..ca0d7f1c248 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 } protected bool ShouldComputeStateRoot(BlockHeader header) => - !header.IsGenesis || !specProvider.GenesisStateUnavailable; + !header.IsGenesis || !_specProvider.GenesisStateUnavailable; protected virtual TxReceipt[] ProcessBlock( Block block, @@ -92,18 +123,25 @@ 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; + _tracedAccessWorldState.BlockAccessList.ResetBlockAccessIndex(); + } 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 +154,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 +179,20 @@ protected virtual TxReceipt[] ProcessBlock( header.StateRoot = _stateProvider.StateRoot; } + if (_tracedAccessWorldState is not null && spec.BlockLevelAccessListsEnabled) + { + if (block.IsGenesis) + { + header.BlockAccessListHash = Keccak.OfAnEmptySequenceRlp; + } + else + { + block.GeneratedBlockAccessList = _tracedAccessWorldState.BlockAccessList; + block.EncodedBlockAccessList = Rlp.Encode(_tracedAccessWorldState.BlockAccessList).Bytes; + header.BlockAccessListHash = new(ValueKeccak.Compute(block.EncodedBlockAccessList).Bytes); + } + } + header.Hash = header.CalculateHash(); return receipts; @@ -165,7 +217,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 +228,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); } protected virtual Block PrepareBlockForProcessing(Block suggestedBlock) @@ -223,7 +275,7 @@ protected virtual 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 +308,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/Processing/IBlockCachePreWarmer.cs b/src/Nethermind/Nethermind.Consensus/Processing/IBlockCachePreWarmer.cs index a62b840652a..8775f11d72a 100644 --- a/src/Nethermind/Nethermind.Consensus/Processing/IBlockCachePreWarmer.cs +++ b/src/Nethermind/Nethermind.Consensus/Processing/IBlockCachePreWarmer.cs @@ -5,10 +5,9 @@ using System.Threading; using System.Threading.Tasks; using Nethermind.Core; -using Nethermind.Core.Crypto; using Nethermind.Core.Eip2930; using Nethermind.Core.Specs; -using Nethermind.State; +using Nethermind.Evm.State; namespace Nethermind.Consensus.Processing; diff --git a/src/Nethermind/Nethermind.Consensus/Producers/BlockProducerEnvFactory.cs b/src/Nethermind/Nethermind.Consensus/Producers/BlockProducerEnvFactory.cs index 83b40ae45eb..2f7b3f960db 100644 --- a/src/Nethermind/Nethermind.Consensus/Producers/BlockProducerEnvFactory.cs +++ b/src/Nethermind/Nethermind.Consensus/Producers/BlockProducerEnvFactory.cs @@ -31,7 +31,7 @@ protected virtual ContainerBuilder ConfigureBuilder(ContainerBuilder builder) => public IBlockProducerEnv Create() { - IWorldState worldState = worldStateManager.CreateResettableWorldState(); + IWorldState worldState = new TracedAccessWorldState(worldStateManager.CreateResettableWorldState()); ILifetimeScope lifetimeScope = rootLifetime.BeginLifetimeScope(builder => ConfigureBuilder(builder) .AddScoped(worldState)); diff --git a/src/Nethermind/Nethermind.Consensus/Validators/BlockValidator.cs b/src/Nethermind/Nethermind.Consensus/Validators/BlockValidator.cs index 95332e5d22a..3eea3faf471 100644 --- a/src/Nethermind/Nethermind.Consensus/Validators/BlockValidator.cs +++ b/src/Nethermind/Nethermind.Consensus/Validators/BlockValidator.cs @@ -130,6 +130,27 @@ private bool ValidateBlockSize(Block block, IReleaseSpec spec, ref string? error } } + 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; } @@ -212,6 +233,13 @@ public bool ValidateProcessedBlock(Block processedBlock, TxReceipt[] receipts, B error ??= BlockErrorMessages.InvalidRequestsHash(suggestedBlock.Header.RequestsHash, processedBlock.Header.RequestsHash); } + if (processedBlock.Header.BlockAccessListHash != suggestedBlock.Header.BlockAccessListHash) + { + 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); + if (_logger.IsWarn) _logger.Warn($"Generated block access list:\n{processedBlock.GeneratedBlockAccessList}\nSuggested block access list:\n{processedBlock.BlockAccessList}"); + } + if (receipts.Length != processedBlock.Transactions.Length) { if (_logger.IsWarn) _logger.Warn($"- receipt count mismatch: expected {processedBlock.Transactions.Length} receipts to match transaction count, got {receipts.Length}"); @@ -381,6 +409,45 @@ 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); private bool ValidateTxRootMatchesTxs(Block block, bool validateHashes, [NotNullWhen(false)] ref string? errorMessage) { if (validateHashes && !ValidateTxRootMatchesTxs(block.Header, block.Body, out Hash256 txRoot)) @@ -416,5 +483,20 @@ 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 83888e0deea..e4edcd794fe 100644 --- a/src/Nethermind/Nethermind.Core.Test/Blockchain/BasicTestBlockchain.cs +++ b/src/Nethermind/Nethermind.Core.Test/Blockchain/BasicTestBlockchain.cs @@ -2,15 +2,11 @@ // 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.Specs; using Nethermind.Core.Test.Builders; using Nethermind.Evm; -using Nethermind.Specs; using Nethermind.State; 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 f3f89bcb17f..071359c6657 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 IBlockProcessingQueue BlockProcessingQueue => _fromContainer.MainProcessingContext.BlockProcessingQueue; public IBlockPreprocessorStep BlockPreprocessorStep => _fromContainer.BlockPreprocessorStep; @@ -334,7 +331,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(); @@ -342,13 +339,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; @@ -374,6 +371,11 @@ public Block Build() genesisBlockBuilder.WithEmptyRequestsHash(); } + if (specProvider.GenesisSpec.BlockLevelAccessListsEnabled) + { + genesisBlockBuilder.WithBlockAccessListHash(Keccak.OfAnEmptySequenceRlp); + } + Block genesisBlock = genesisBlockBuilder.TestObject; foreach (IGenesisPostProcessor genesisPostProcessor in postProcessors) 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..b49989d0db4 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,12 @@ 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 + + // for debugging by rpc + [JsonIgnore] + public BlockAccessList? GeneratedBlockAccessList { get; set; } [JsonIgnore] public byte[][]? ExecutionRequests { get; set; } @@ -126,6 +135,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..f9f24114183 --- /dev/null +++ b/src/Nethermind/Nethermind.Core/BlockAccessLists/AccountChanges.cs @@ -0,0 +1,195 @@ + +// SPDX-FileCopyrightText: 2025 Demerzel Solutions Limited +// SPDX-License-Identifier: LGPL-3.0-only + +using System; +using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; +using System.Linq; +using System.Text.Json.Serialization; +using Nethermind.Core.Extensions; +using Nethermind.Serialization.Json; + +namespace Nethermind.Core.BlockAccessLists; + +public class AccountChanges : IEquatable +{ + [JsonConverter(typeof(AddressConverter))] + public Address Address { get; init; } + + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingDefault)] + public IEnumerable StorageChanges => _storageChanges.Values; + + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingDefault)] + public IEnumerable StorageReads => _storageReads; + + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingDefault)] + public IEnumerable BalanceChanges => _balanceChanges.Values; + + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingDefault)] + public IEnumerable NonceChanges => _nonceChanges.Values; + + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingDefault)] + public IEnumerable CodeChanges => _codeChanges.Values; + + private readonly SortedDictionary _storageChanges; + private readonly SortedSet _storageReads; + private readonly SortedList _balanceChanges; + private readonly SortedList _nonceChanges; + private readonly SortedList _codeChanges; + + 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 bool Equals(AccountChanges? other) => + other is not null && + Address == other.Address && + StorageChanges.SequenceEqual(other.StorageChanges) && + StorageReads.SequenceEqual(other.StorageReads) && + BalanceChanges.SequenceEqual(other.BalanceChanges) && + NonceChanges.SequenceEqual(other.NonceChanges) && + CodeChanges.SequenceEqual(other.CodeChanges); + + public override bool Equals(object? obj) => + obj is AccountChanges other && Equals(other); + public override 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); + + // n.b. implies that length of changes is zero + public bool HasStorageChange(byte[] key) + => _storageChanges.ContainsKey(key); + + public bool TryGetSlotChanges(byte[] key, [NotNullWhen(true)] out SlotChanges? slotChanges) + => _storageChanges.TryGetValue(key, out slotChanges); + + public void ClearSlotChangesIfEmpty(byte[] key) + { + if (TryGetSlotChanges(key, out SlotChanges? slotChanges) && slotChanges.Changes.Count == 0) + { + _storageChanges.Remove(key); + } + } + + public SlotChanges GetOrAddSlotChanges(byte[] key) + { + if (!_storageChanges.TryGetValue(key, out SlotChanges? existing)) + { + SlotChanges slotChanges = new(key); + _storageChanges.Add(key, slotChanges); + return slotChanges; + } + return existing; + } + + public void AddStorageRead(byte[] key) + => _storageReads.Add(new(key)); + + public void RemoveStorageRead(byte[] key) + => _storageReads.Remove(new(key)); + + public void SelfDestruct() + { + foreach (byte[] key in _storageChanges.Keys) + { + AddStorageRead(key); + } + + _storageChanges.Clear(); + _nonceChanges.Clear(); + _codeChanges.Clear(); + } + + public void AddBalanceChange(BalanceChange balanceChange) + => _balanceChanges.Add(balanceChange.BlockAccessIndex, balanceChange); + + public bool PopBalanceChange(ushort index, [NotNullWhen(true)] out BalanceChange? balanceChange) + { + balanceChange = null; + if (PopChange(_balanceChanges, index, out BalanceChange change)) + { + balanceChange = change; + return true; + } + return false; + } + + public void AddNonceChange(NonceChange nonceChange) + => _nonceChanges.Add(nonceChange.BlockAccessIndex, nonceChange); + + public bool PopNonceChange(ushort index, [NotNullWhen(true)] out NonceChange? nonceChange) + { + nonceChange = null; + if (PopChange(_nonceChanges, index, out NonceChange change)) + { + nonceChange = change; + return true; + } + return false; + } + + public void AddCodeChange(CodeChange codeChange) + => _codeChanges.Add(codeChange.BlockAccessIndex, codeChange); + + public bool PopCodeChange(ushort index, [NotNullWhen(true)] out CodeChange? codeChange) + { + codeChange = null; + if (PopChange(_codeChanges, index, out CodeChange change)) + { + codeChange = change; + return true; + } + return false; + } + + private static bool PopChange(SortedList changes, ushort index, [NotNullWhen(true)] out T? change) where T : IIndexedChange + { + change = default; + + if (changes.Count == 0) + return false; + + KeyValuePair lastChange = changes.Last(); + + if (lastChange.Key == index) + { + changes.RemoveAt(changes.Count - 1); + change = lastChange.Value; + return true; + } + + return false; + } +} diff --git a/src/Nethermind/Nethermind.Core/BlockAccessLists/BalanceChange.cs b/src/Nethermind/Nethermind.Core/BlockAccessLists/BalanceChange.cs new file mode 100644 index 00000000000..06ea44b4a93 --- /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 System.Text.Json.Serialization; +using Nethermind.Int256; +using Nethermind.Serialization.Json; + +namespace Nethermind.Core.BlockAccessLists; + +public readonly struct BalanceChange(ushort blockAccessIndex, UInt256 postBalance) : IEquatable, IIndexedChange +{ + public ushort BlockAccessIndex { get; init; } = blockAccessIndex; + [JsonConverter(typeof(UInt256Converter))] + 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); +} diff --git a/src/Nethermind/Nethermind.Core/BlockAccessLists/BlockAccessList.cs b/src/Nethermind/Nethermind.Core/BlockAccessLists/BlockAccessList.cs new file mode 100644 index 00000000000..693056aa054 --- /dev/null +++ b/src/Nethermind/Nethermind.Core/BlockAccessLists/BlockAccessList.cs @@ -0,0 +1,416 @@ +// SPDX-FileCopyrightText: 2025 Demerzel Solutions Limited +// SPDX-License-Identifier: LGPL-3.0-only + +using System; +using System.Collections.Generic; +using System.Data; +using System.Diagnostics; +using System.Linq; +using System.Text.Json; +using System.Text.Json.Serialization; +using Nethermind.Int256; + +namespace Nethermind.Core.BlockAccessLists; + +public struct BlockAccessList : IEquatable, IJournal +{ + [JsonIgnore] + public ushort Index = 0; + public readonly IEnumerable AccountChanges => _accountChanges.Values; + + 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 AccountChanges? GetAccountChanges(Address address) => _accountChanges.TryGetValue(address, out AccountChanges? value) ? value : null; + + public void IncrementBlockAccessIndex() + { + _changes.Clear(); + Index++; + } + + public void ResetBlockAccessIndex() + { + _changes.Clear(); + Index = 0; + } + + public void AddBalanceChange(Address address, UInt256 before, UInt256 after) + { + if (address == Address.SystemUser) + { + return; + } + + BalanceChange balanceChange = new() + { + BlockAccessIndex = Index, + PostBalance = after + }; + + AccountChanges accountChanges = GetOrAddAccountChanges(address); + + // don't add zero balance transfers, but add empty account changes + if (before == after) + { + return; + } + + bool changedDuringTx = HasBalanceChangedDuringTx(address, before, after); + accountChanges.PopBalanceChange(Index, out BalanceChange? oldBalanceChange); + + _changes.Push(new() + { + Address = address, + Type = ChangeType.BalanceChange, + PreviousValue = oldBalanceChange, + PreTxBalance = before, + BlockAccessIndex = Index + }); + + if (changedDuringTx) + { + accountChanges.AddBalanceChange(balanceChange); + } + } + + public void AddCodeChange(Address address, byte[] before, byte[] after) + { + CodeChange codeChange = new() + { + BlockAccessIndex = Index, + NewCode = after + }; + + AccountChanges accountChanges = GetOrAddAccountChanges(address); + + if (Enumerable.SequenceEqual(before, after)) + { + return; + } + + accountChanges.PopCodeChange(Index, out CodeChange? oldCodeChange); + _changes.Push(new() + { + Address = address, + Type = ChangeType.CodeChange, + PreviousValue = oldCodeChange + }); + + accountChanges.AddCodeChange(codeChange); + } + + public void AddNonceChange(Address address, ulong newNonce) + { + if (newNonce == 0) + { + return; + } + + NonceChange nonceChange = new() + { + BlockAccessIndex = Index, + NewNonce = newNonce + }; + + AccountChanges accountChanges = GetOrAddAccountChanges(address); + + accountChanges.PopNonceChange(Index, out NonceChange? oldNonceChange); + _changes.Push(new() + { + Address = address, + Type = ChangeType.NonceChange, + PreviousValue = oldNonceChange + }); + + accountChanges.AddNonceChange(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) + { + AccountChanges accountChanges = GetOrAddAccountChanges(address); + + if (before != after) + { + Span key = new byte[32]; + storageIndex.ToBigEndian(key); + StorageChange(accountChanges, key, before, after); + } + } + + public void AddStorageChange(in StorageCell storageCell, byte[] before, byte[] after) + { + Address address = storageCell.Address; + AccountChanges accountChanges = GetOrAddAccountChanges(address); + + if (before is null || !Enumerable.SequenceEqual(before, after)) + { + Span key = new byte[32]; + storageCell.Index.ToBigEndian(key); + StorageChange(accountChanges, key, before.AsSpan(), after.AsSpan()); + } + } + + public readonly void AddStorageRead(in StorageCell storageCell) + { + byte[] key = new byte[32]; + storageCell.Index.ToBigEndian(key); + AddStorageRead(storageCell.Address, key); + } + + public readonly void AddStorageRead(Address address, byte[] key) + { + AccountChanges accountChanges = GetOrAddAccountChanges(address); + + if (!accountChanges.HasStorageChange(key)) + { + accountChanges.AddStorageRead(key); + } + } + + public readonly void DeleteAccount(Address address) + { + AccountChanges accountChanges = GetOrAddAccountChanges(address); + accountChanges.SelfDestruct(); + } + + private void StorageChange(AccountChanges accountChanges, in ReadOnlySpan key, in ReadOnlySpan before, in ReadOnlySpan after) + { + byte[] storageKey = [.. key]; + SlotChanges slotChanges = accountChanges.GetOrAddSlotChanges(storageKey); + + bool changedDuringTx = HasStorageChangedDuringTx(accountChanges.Address, storageKey, before, after); + slotChanges.PopStorageChange(Index, out StorageChange? oldStorageChange); + + _changes.Push(new() + { + Address = accountChanges.Address, + BlockAccessIndex = Index, + Slot = storageKey, + Type = ChangeType.StorageChange, + PreviousValue = oldStorageChange, + PreTxStorage = [.. before] + }); + + if (changedDuringTx) + { + byte[] newValue = new byte[32]; + after.CopyTo(newValue.AsSpan()[(32 - after.Length)..]); + StorageChange storageChange = new() + { + BlockAccessIndex = Index, + NewValue = newValue + }; + + slotChanges.Changes.Add(storageChange); + accountChanges.RemoveStorageRead(storageKey); + } + else + { + accountChanges.ClearSlotChangesIfEmpty(storageKey); + if (!accountChanges.HasStorageChange(storageKey)) + { + accountChanges.AddStorageRead(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(); + AccountChanges accountChanges = _accountChanges[change.Address]; + switch (change.Type) + { + case ChangeType.BalanceChange: + BalanceChange? previousBalance = change.PreviousValue is null ? null : (BalanceChange)change.PreviousValue; + + // balance could have gone back to pre-tx value + // so would already be empty + accountChanges.PopBalanceChange(change.BlockAccessIndex, out _); // todo: this index must be the same? + if (previousBalance is not null) + { + accountChanges.AddBalanceChange(previousBalance.Value); + } + break; + case ChangeType.CodeChange: + CodeChange? previousCode = change.PreviousValue is null ? null : (CodeChange)change.PreviousValue; + + accountChanges.PopCodeChange(Index, out _); + if (previousCode is not null) + { + accountChanges.AddCodeChange(previousCode.Value); + } + break; + case ChangeType.NonceChange: + NonceChange? previousNonce = change.PreviousValue is null ? null : (NonceChange)change.PreviousValue; + + accountChanges.PopNonceChange(Index, out _); + if (previousNonce is not null) + { + accountChanges.AddNonceChange(previousNonce.Value); + } + break; + case ChangeType.StorageChange: + StorageChange? previousStorage = change.PreviousValue is null ? null : (StorageChange)change.PreviousValue; + accountChanges.TryGetSlotChanges(change.Slot!, out SlotChanges? slotChanges); + + // replace change with read + accountChanges.AddStorageRead(change.Slot!); + + slotChanges!.PopStorageChange(Index, out _); + if (previousStorage is not null) + { + slotChanges.Changes.Add(previousStorage.Value); + } + + accountChanges.ClearSlotChangesIfEmpty(change.Slot!); + break; + } + } + } + + public override readonly string? ToString() + => JsonSerializer.Serialize(this); + + private readonly bool HasBalanceChangedDuringTx(Address address, UInt256 beforeInstr, UInt256 afterInstr) + { + AccountChanges accountChanges = _accountChanges[address]; + int count = accountChanges.BalanceChanges.Count(); + + if (count == 0) + { + // first balance change of block + // return balance prior to this instruction + return beforeInstr != afterInstr; + } + + foreach (BalanceChange balanceChange in accountChanges.BalanceChanges.Reverse()) + { + if (balanceChange.BlockAccessIndex != Index) + { + // balance changed in previous tx in block + return balanceChange.PostBalance != afterInstr; + } + } + + // 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.PreTxBalance!.Value != afterInstr; + } + } + + // should never happen + Debug.Fail("Error calculating pre tx balance"); + return true; + } + + private readonly bool HasStorageChangedDuringTx(Address address, byte[] key, in ReadOnlySpan beforeInstr, in ReadOnlySpan afterInstr) + { + AccountChanges accountChanges = _accountChanges[address]; + + if (!accountChanges.TryGetSlotChanges(key, out SlotChanges? slotChanges) || slotChanges.Changes.Count == 0) + { + // first storage change of block + // return storage prior to this instruction + return !Enumerable.SequenceEqual(beforeInstr.ToArray(), afterInstr.ToArray()); + } + + foreach (StorageChange storageChange in slotChanges.Changes.AsEnumerable().Reverse()) + { + if (storageChange.BlockAccessIndex != Index) + { + // storage changed in previous tx in block + return !Enumerable.SequenceEqual(storageChange.NewValue, afterInstr.ToArray()); + } + } + + // storage only changed within this transaction + foreach (Change change in _changes) + { + if (change.Type == ChangeType.StorageChange && change.Address == address && Enumerable.SequenceEqual(change.Slot!, key) && change.PreviousValue is null) + { + // first change of this transaction & block + return change.PreTxStorage is null || !Enumerable.SequenceEqual(change.PreTxStorage, afterInstr.ToArray()); + } + } + + // should never happen + Debug.Fail("Error calculating pre tx storage"); + return true; + } + + private readonly AccountChanges GetOrAddAccountChanges(Address address) + { + if (!_accountChanges.TryGetValue(address, out AccountChanges? existing)) + { + AccountChanges accountChanges = new(address); + _accountChanges.Add(address, accountChanges); + return accountChanges; + } + return existing; + } + + 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 UInt256? PreTxBalance { get; init; } + public byte[]? PreTxStorage { get; init; } + public ushort BlockAccessIndex { 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..14be8de3145 --- /dev/null +++ b/src/Nethermind/Nethermind.Core/BlockAccessLists/CodeChange.cs @@ -0,0 +1,42 @@ + +// SPDX-FileCopyrightText: 2025 Demerzel Solutions Limited +// SPDX-License-Identifier: LGPL-3.0-only + +using System; +using System.Linq; +using System.Text.Json.Serialization; +using Nethermind.Serialization.Json; + +namespace Nethermind.Core.BlockAccessLists; + +public readonly struct CodeChange(ushort blockAccessIndex, byte[] newCode) : IEquatable, IIndexedChange +{ + public ushort BlockAccessIndex { get; init; } = blockAccessIndex; + [JsonConverter(typeof(ByteArrayConverter))] + 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); +} 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..3907f1c218f --- /dev/null +++ b/src/Nethermind/Nethermind.Core/BlockAccessLists/NonceChange.cs @@ -0,0 +1,28 @@ +// 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); +} diff --git a/src/Nethermind/Nethermind.Core/BlockAccessLists/SlotChanges.cs b/src/Nethermind/Nethermind.Core/BlockAccessLists/SlotChanges.cs new file mode 100644 index 00000000000..41a5cafa4b8 --- /dev/null +++ b/src/Nethermind/Nethermind.Core/BlockAccessLists/SlotChanges.cs @@ -0,0 +1,68 @@ + +// SPDX-FileCopyrightText: 2025 Demerzel Solutions Limited +// SPDX-License-Identifier: LGPL-3.0-only + +using System; +using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; +using System.Linq; +using System.Text.Json.Serialization; +using Nethermind.Serialization.Json; + +namespace Nethermind.Core.BlockAccessLists; + +public class SlotChanges(byte[] slot, List changes) : IEquatable +{ + [JsonConverter(typeof(ByteArrayConverter))] + public byte[] Slot { get; init; } = slot; + public List Changes { get; init; } = changes; + + public SlotChanges(byte[] slot) : this(slot, []) + { + } + + public bool Equals(SlotChanges? other) => + other is not null && + CompareByteArrays(Slot, other.Slot) && + Changes.SequenceEqual(other.Changes); + + public override bool Equals(object? obj) => + obj is SlotChanges other && Equals(other); + + public override 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 bool PopStorageChange(ushort index, [NotNullWhen(true)] out StorageChange? storageChange) + { + storageChange = null; + + if (Changes.Count == 0) + return false; + + StorageChange lastChange = Changes.Last(); + + if (lastChange.BlockAccessIndex == index) + { + Changes.RemoveAt(Changes.Count - 1); + storageChange = lastChange; + return true; + } + + return false; + } +} \ 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..0c0fe976961 --- /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; +using System.Text.Json.Serialization; +using Nethermind.Serialization.Json; + +namespace Nethermind.Core.BlockAccessLists; + +public readonly struct StorageChange(ushort blockAccessIndex, byte[] newValue) : IEquatable, IIndexedChange +{ + public ushort BlockAccessIndex { get; init; } = blockAccessIndex; + [JsonConverter(typeof(ByteArrayConverter))] + public byte[] NewValue { get; init; } = newValue; + + public readonly bool Equals(StorageChange other) => + BlockAccessIndex == other.BlockAccessIndex && + NewValue.SequenceEqual(other.NewValue); + + 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); +} diff --git a/src/Nethermind/Nethermind.Core/BlockAccessLists/StorageRead.cs b/src/Nethermind/Nethermind.Core/BlockAccessLists/StorageRead.cs new file mode 100644 index 00000000000..9eead94f52d --- /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 System.Text.Json.Serialization; +using Nethermind.Core.Extensions; +using Nethermind.Serialization.Json; + +namespace Nethermind.Core.BlockAccessLists; + +public readonly struct StorageRead(byte[] key) : IEquatable, IComparable +{ + [JsonConverter(typeof(ByteArrayConverter))] + public byte[] Key { get; init; } = key; + + public int CompareTo(StorageRead other) + => Bytes.BytesComparer.Compare(Key, other.Key); + + public readonly bool Equals(StorageRead other) => + Key.SequenceEqual(other.Key); + + 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); +} 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 92bcc5d1eab..a352395a5d0 100644 --- a/src/Nethermind/Nethermind.Core/BlockHeader.cs +++ b/src/Nethermind/Nethermind.Core/BlockHeader.cs @@ -73,13 +73,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 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..9ea4fc2787e --- /dev/null +++ b/src/Nethermind/Nethermind.Core/Eip7928Constants.cs @@ -0,0 +1,21 @@ +// SPDX-FileCopyrightText: 2025 Demerzel Solutions Limited +// SPDX-License-Identifier: LGPL-3.0-only + +namespace Nethermind.Core; + +public static class Eip7928Constants +{ + // Max buffer lengths for RLP decoding + + // max number of transactions per block + public const int MaxTxs = 100_000; + + // max number of slots changed / read in one account + public const int MaxSlots = 1_000_000; + + // max number of accounts accessed per block + public const int MaxAccounts = 1_000_000; + + // max code size in bytes + public const int MaxCodeSize = 1_000_000; +} diff --git a/src/Nethermind/Nethermind.Serialization.Json/ForcedNumberConversion.cs b/src/Nethermind/Nethermind.Core/JsonConverters/ForcedNumberConversion.cs similarity index 100% rename from src/Nethermind/Nethermind.Serialization.Json/ForcedNumberConversion.cs rename to src/Nethermind/Nethermind.Core/JsonConverters/ForcedNumberConversion.cs diff --git a/src/Nethermind/Nethermind.Serialization.Json/NumberConversion.cs b/src/Nethermind/Nethermind.Core/JsonConverters/NumberConversion.cs similarity index 100% rename from src/Nethermind/Nethermind.Serialization.Json/NumberConversion.cs rename to src/Nethermind/Nethermind.Core/JsonConverters/NumberConversion.cs diff --git a/src/Nethermind/Nethermind.Serialization.Json/UInt256Converter.cs b/src/Nethermind/Nethermind.Core/JsonConverters/UInt256Converter.cs similarity index 100% rename from src/Nethermind/Nethermind.Serialization.Json/UInt256Converter.cs rename to src/Nethermind/Nethermind.Core/JsonConverters/UInt256Converter.cs diff --git a/src/Nethermind/Nethermind.Core/Messages/BlockErrorMessages.cs b/src/Nethermind/Nethermind.Core/Messages/BlockErrorMessages.cs index 8cb8db2d83c..62c7580725d 100644 --- a/src/Nethermind/Nethermind.Core/Messages/BlockErrorMessages.cs +++ b/src/Nethermind/Nethermind.Core/Messages/BlockErrorMessages.cs @@ -150,6 +150,17 @@ 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}"; + public static string ReceiptCountMismatch(int expectedCount, int actualCount) => $"ReceiptCountMismatch: Expected {expectedCount} receipts to match transaction count, but got {actualCount}."; } diff --git a/src/Nethermind/Nethermind.Core/Specs/IReleaseSpec.cs b/src/Nethermind/Nethermind.Core/Specs/IReleaseSpec.cs index c2b7221bde4..44338567a83 100644 --- a/src/Nethermind/Nethermind.Core/Specs/IReleaseSpec.cs +++ b/src/Nethermind/Nethermind.Core/Specs/IReleaseSpec.cs @@ -474,6 +474,8 @@ public interface IReleaseSpec : IEip1559Spec, IReceiptSpec public bool RequestsEnabled => ConsolidationRequestsEnabled || WithdrawalRequestsEnabled || DepositsEnabled; + bool BlockLevelAccessListsEnabled => IsEip7928Enabled; + public bool IsEip7594Enabled { get; } /// @@ -534,5 +536,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 959ed46ff4c..b0af9711fbc 100644 --- a/src/Nethermind/Nethermind.Evm/CodeInfoRepository.cs +++ b/src/Nethermind/Nethermind.Evm/CodeInfoRepository.cs @@ -21,11 +21,13 @@ public class CodeInfoRepository : ICodeInfoRepository private static readonly CodeLruCache _codeCache = new(); private readonly FrozenDictionary _localPrecompiles; private readonly IWorldState _worldState; + private readonly TracedAccessWorldState? _tracedAccessWorldState; public CodeInfoRepository(IWorldState worldState, IPrecompileProvider precompileProvider) { _localPrecompiles = precompileProvider.GetPrecompiles(); _worldState = worldState; + _tracedAccessWorldState = _worldState as TracedAccessWorldState; } public ICodeInfo GetCachedCodeInfo(Address codeSource, bool followDelegation, IReleaseSpec vmSpec, out Address? delegationAddress) @@ -33,6 +35,10 @@ public ICodeInfo GetCachedCodeInfo(Address codeSource, bool followDelegation, IR delegationAddress = null; if (vmSpec.IsPrecompile(codeSource)) // _localPrecompiles have to have all precompiles { + if (_tracedAccessWorldState is not null && _tracedAccessWorldState.Enabled) + { + _tracedAccessWorldState.AddAccountRead(codeSource); + } return _localPrecompiles[codeSource]; } @@ -49,7 +55,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.CodeCopy.cs b/src/Nethermind/Nethermind.Evm/Instructions/EvmInstructions.CodeCopy.cs index 04602858408..ce5ff2061b7 100644 --- a/src/Nethermind/Nethermind.Evm/Instructions/EvmInstructions.CodeCopy.cs +++ b/src/Nethermind/Nethermind.Evm/Instructions/EvmInstructions.CodeCopy.cs @@ -11,6 +11,7 @@ namespace Nethermind.Evm; using Int256; +using Nethermind.Evm.State; internal static partial class EvmInstructions { @@ -152,6 +153,8 @@ public static EvmExceptionType InstructionExtCodeCopy( if (!EvmCalculations.ChargeAccountAccessGas(ref gasAvailable, vm, address)) goto OutOfGas; + (vm.WorldState as TracedAccessWorldState)?.AddAccountRead(address); + if (!result.IsZero) { // Update memory cost if the destination region requires expansion. @@ -232,6 +235,8 @@ public static EvmExceptionType InstructionExtCodeSize( if (!EvmCalculations.ChargeAccountAccessGas(ref gasAvailable, vm, address)) goto OutOfGas; + (vm.WorldState as TracedAccessWorldState)?.AddAccountRead(address); + // Attempt a peephole optimization when tracing is not active and code is available. ReadOnlySpan codeSection = vm.EvmState.Env.CodeInfo.CodeSpan; if (!TTracingInst.IsActive && programCounter < codeSection.Length) diff --git a/src/Nethermind/Nethermind.Evm/Instructions/EvmInstructions.Environment.cs b/src/Nethermind/Nethermind.Evm/Instructions/EvmInstructions.Environment.cs index e8679333e2d..1e81b3cee4d 100644 --- a/src/Nethermind/Nethermind.Evm/Instructions/EvmInstructions.Environment.cs +++ b/src/Nethermind/Nethermind.Evm/Instructions/EvmInstructions.Environment.cs @@ -508,7 +508,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; @@ -536,7 +536,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; @@ -576,7 +576,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/Nethermind.Evm.csproj b/src/Nethermind/Nethermind.Evm/Nethermind.Evm.csproj index 968541ac1aa..37f9f5a0895 100644 --- a/src/Nethermind/Nethermind.Evm/Nethermind.Evm.csproj +++ b/src/Nethermind/Nethermind.Evm/Nethermind.Evm.csproj @@ -9,5 +9,6 @@ + diff --git a/src/Nethermind/Nethermind.State/IPreBlockCaches.cs b/src/Nethermind/Nethermind.Evm/State/IPreBlockCaches.cs similarity index 86% rename from src/Nethermind/Nethermind.State/IPreBlockCaches.cs rename to src/Nethermind/Nethermind.Evm/State/IPreBlockCaches.cs index 1526663b148..2e29278e9c6 100644 --- a/src/Nethermind/Nethermind.State/IPreBlockCaches.cs +++ b/src/Nethermind/Nethermind.Evm/State/IPreBlockCaches.cs @@ -1,7 +1,7 @@ // SPDX-FileCopyrightText: 2024 Demerzel Solutions Limited // SPDX-License-Identifier: LGPL-3.0-only -namespace Nethermind.State; +namespace Nethermind.Evm.State; public interface IPreBlockCaches { 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.State/PreBlockCaches.cs b/src/Nethermind/Nethermind.Evm/State/PreBlockCaches.cs similarity index 97% rename from src/Nethermind/Nethermind.State/PreBlockCaches.cs rename to src/Nethermind/Nethermind.Evm/State/PreBlockCaches.cs index 5b5e1635560..1af7e330e67 100644 --- a/src/Nethermind/Nethermind.State/PreBlockCaches.cs +++ b/src/Nethermind/Nethermind.Evm/State/PreBlockCaches.cs @@ -6,12 +6,11 @@ using Nethermind.Core; using Nethermind.Core.Extensions; using Nethermind.Core.Collections; -using Nethermind.Evm.State; using Nethermind.Trie; using CollectionExtensions = Nethermind.Core.Collections.CollectionExtensions; -namespace Nethermind.State; +namespace Nethermind.Evm.State; public class PreBlockCaches { 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/State/TracedAccessWorldState.cs b/src/Nethermind/Nethermind.Evm/State/TracedAccessWorldState.cs new file mode 100644 index 00000000000..40818b04db1 --- /dev/null +++ b/src/Nethermind/Nethermind.Evm/State/TracedAccessWorldState.cs @@ -0,0 +1,228 @@ +// 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.Int256; + +namespace Nethermind.Evm.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 void SetNonce(Address address, in UInt256 nonce) + { + _innerWorldState.SetNonce(address, nonce); + + if (Enabled) + { + BlockAccessList.AddNonceChange(address, (ulong)nonce); + } + } + + public override bool InsertCode(Address address, in ValueHash256 codeHash, ReadOnlyMemory code, IReleaseSpec spec, bool isGenesis = false) + { + if (Enabled) + { + byte[] oldCode = _innerWorldState.GetCode(address) ?? []; + BlockAccessList.AddCodeChange(address, oldCode, 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) + { + AddAccountRead(address); + return _innerWorldState.GetBalance(address); + } + + public override ValueHash256 GetCodeHash(Address address) + { + AddAccountRead(address); + return _innerWorldState.GetCodeHash(address); + } + + public override byte[]? GetCode(Address address) + { + AddAccountRead(address); + return _innerWorldState.GetCode(address); + } + + public override void SubtractFromBalance(Address address, in UInt256 balanceChange, IReleaseSpec spec) + { + if (Enabled) + { + UInt256 before = _innerWorldState.GetBalance(address); + UInt256 after = before - balanceChange; + BlockAccessList.AddBalanceChange(address, before, after); + } + _innerWorldState.SubtractFromBalance(address, balanceChange, spec); + } + + public override void DeleteAccount(Address address) + { + if (Enabled) + { + BlockAccessList.DeleteAccount(address); + } + _innerWorldState.DeleteAccount(address); + } + + public override void CreateAccount(Address address, in UInt256 balance, in UInt256 nonce = default) + { + if (Enabled) + { + BlockAccessList.AddAccountRead(address); + if (balance != 0) + { + BlockAccessList.AddBalanceChange(address, 0, balance); + } + if (nonce != 0) + { + BlockAccessList.AddNonceChange(address, (ulong)nonce); + } + } + _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) + { + AddAccountRead(address); + return _innerWorldState.TryGetAccount(address, out account); + } + + public void AddAccountRead(Address address) + { + if (Enabled) + { + BlockAccessList.AddAccountRead(address); + } + } + + 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); + } + + public override bool AccountExists(Address address) + { + AddAccountRead(address); + return _innerWorldState.AccountExists(address); + } + + public override bool IsContract(Address address) + { + AddAccountRead(address); + return _innerWorldState.IsContract(address); + } + + public override bool IsDeadAccount(Address address) + { + AddAccountRead(address); + return _innerWorldState.IsDeadAccount(address); + } + + public override void ClearStorage(Address address) + { + // todo: change all storage slots to nothing + // consensus issue? + AddAccountRead(address); + _innerWorldState.ClearStorage(address); + } +} diff --git a/src/Nethermind/Nethermind.Evm/State/WrappedWorldState.cs b/src/Nethermind/Nethermind.Evm/State/WrappedWorldState.cs new file mode 100644 index 00000000000..d07e28624e8 --- /dev/null +++ b/src/Nethermind/Nethermind.Evm/State/WrappedWorldState.cs @@ -0,0 +1,147 @@ +// 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.Tracing.State; +using Nethermind.Int256; + +namespace Nethermind.Evm.State; + +public class WrappedWorldState(IWorldState innerWorldState) : IWorldState +{ + protected IWorldState _innerWorldState = innerWorldState; + public bool IsInScope => _innerWorldState.IsInScope; + + public Hash256 StateRoot => _innerWorldState.StateRoot; + + public virtual 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 virtual 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 virtual 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 virtual bool IsContract(Address address) + => _innerWorldState.IsContract(address); + + public virtual 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.Evm/TransactionProcessing/TransactionProcessor.cs b/src/Nethermind/Nethermind.Evm/TransactionProcessing/TransactionProcessor.cs index 21580b779de..ab148a9b784 100644 --- a/src/Nethermind/Nethermind.Evm/TransactionProcessing/TransactionProcessor.cs +++ b/src/Nethermind/Nethermind.Evm/TransactionProcessing/TransactionProcessor.cs @@ -51,6 +51,7 @@ public abstract class TransactionProcessorBase : ITransactionProcessor private SystemTransactionProcessor? _systemTransactionProcessor; private readonly ITransactionProcessor.IBlobBaseFeeCalculator _blobBaseFeeCalculator; private readonly ILogManager _logManager; + private readonly TracedAccessWorldState? _tracedAccessWorldState; [Flags] protected enum ExecutionOptions @@ -106,6 +107,7 @@ protected TransactionProcessorBase( WorldState = worldState; VirtualMachine = virtualMachine; _codeInfoRepository = codeInfoRepository; + _tracedAccessWorldState = worldState as TracedAccessWorldState; _blobBaseFeeCalculator = blobBaseFeeCalculator; Ecdsa = new EthereumEcdsa(specProvider.ChainId); @@ -163,7 +165,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); @@ -269,9 +271,15 @@ private int ProcessDelegations(Transaction tx, IReleaseSpec spec, in StackAccess { Address authority = (authTuple.Authority ??= Ecdsa.RecoverAddress(authTuple))!; - if (!IsValidForExecution(authTuple, accessTracker, out string? error)) + AuthorizationTupleResult res = IsValidForExecution(authTuple, accessTracker, spec, out string? error); + if (res != AuthorizationTupleResult.Valid) { if (Logger.IsDebug) Logger.Debug($"Delegation {authTuple} is invalid with error: {error}"); + + if (_tracedAccessWorldState is not null && _tracedAccessWorldState.Enabled && IncludeAccountRead(res)) + { + _tracedAccessWorldState.AddAccountRead(authority); + } } else { @@ -290,52 +298,66 @@ private int ProcessDelegations(Transaction tx, IReleaseSpec spec, in StackAccess } return refunds; + } - bool IsValidForExecution( - AuthorizationTuple authorizationTuple, - StackAccessTracker accessTracker, - [NotNullWhen(false)] out string? error) - { - if (authorizationTuple.ChainId != 0 && SpecProvider.ChainId != authorizationTuple.ChainId) - { - error = $"Chain id ({authorizationTuple.ChainId}) does not match."; - return false; - } + private bool IncludeAccountRead(AuthorizationTupleResult res) + => res == AuthorizationTupleResult.IncorrectNonce || res == AuthorizationTupleResult.InvalidAsCodeDeployed; - if (authorizationTuple.Nonce == ulong.MaxValue) - { - error = $"Nonce ({authorizationTuple.Nonce}) must be less than 2**64 - 1."; - return false; - } + private enum AuthorizationTupleResult + { + Valid, + IncorrectNonce, + InvalidNonce, + InvalidChainId, + InvalidSignature, + InvalidAsCodeDeployed + } - UInt256 s = new(authorizationTuple.AuthoritySignature.SAsSpan, isBigEndian: true); - if (authorizationTuple.Authority is null - || s > Secp256K1Curve.HalfN - //V minus the offset can only be 1 or 0 since eip-155 does not apply to Setcode signatures - || authorizationTuple.AuthoritySignature.V - Signature.VOffset > 1) - { - error = "Bad signature."; - return false; - } + private AuthorizationTupleResult IsValidForExecution( + AuthorizationTuple authorizationTuple, + StackAccessTracker accessTracker, + IReleaseSpec spec, + [NotNullWhen(false)] out string? error) + { + if (authorizationTuple.ChainId != 0 && SpecProvider.ChainId != authorizationTuple.ChainId) + { + error = $"Chain id ({authorizationTuple.ChainId}) does not match."; + return AuthorizationTupleResult.InvalidChainId; + } - accessTracker.WarmUp(authorizationTuple.Authority); + if (authorizationTuple.Nonce == ulong.MaxValue) + { + error = $"Nonce ({authorizationTuple.Nonce}) must be less than 2**64 - 1."; + return AuthorizationTupleResult.InvalidNonce; + } - if (WorldState.HasCode(authorizationTuple.Authority) && !_codeInfoRepository.TryGetDelegation(authorizationTuple.Authority, spec, out _)) - { - error = $"Authority ({authorizationTuple.Authority}) has code deployed."; - return false; - } + UInt256 s = new(authorizationTuple.AuthoritySignature.SAsSpan, isBigEndian: true); + if (authorizationTuple.Authority is null + || s > Secp256K1Curve.HalfN + //V minus the offset can only be 1 or 0 since eip-155 does not apply to Setcode signatures + || authorizationTuple.AuthoritySignature.V - Signature.VOffset > 1) + { + error = "Bad signature."; + return AuthorizationTupleResult.InvalidSignature; + } - UInt256 authNonce = WorldState.GetNonce(authorizationTuple.Authority); - if (authNonce != authorizationTuple.Nonce) - { - error = $"Skipping tuple in authorization_list because nonce is set to {authorizationTuple.Nonce}, but authority ({authorizationTuple.Authority}) has {authNonce}."; - return false; - } + accessTracker.WarmUp(authorizationTuple.Authority); - error = null; - return true; + if (WorldState.HasCode(authorizationTuple.Authority) && !_codeInfoRepository.TryGetDelegation(authorizationTuple.Authority, spec, out _)) + { + error = $"Authority ({authorizationTuple.Authority}) has code deployed."; + return AuthorizationTupleResult.InvalidAsCodeDeployed; } + + UInt256 authNonce = WorldState.GetNonce(authorizationTuple.Authority); + if (authNonce != authorizationTuple.Nonce) + { + error = $"Skipping tuple in authorization_list because nonce is set to {authorizationTuple.Nonce}, but authority ({authorizationTuple.Authority}) has {authNonce}."; + return AuthorizationTupleResult.IncorrectNonce; + } + + error = null; + return AuthorizationTupleResult.Valid; } protected virtual IReleaseSpec GetSpec(BlockHeader header) => VirtualMachine.BlockExecutionContext.Spec; @@ -913,6 +935,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.Facade/Eth/BlockForRpc.cs b/src/Nethermind/Nethermind.Facade/Eth/BlockForRpc.cs index 6bafdd3d166..9b906afa334 100644 --- a/src/Nethermind/Nethermind.Facade/Eth/BlockForRpc.cs +++ b/src/Nethermind/Nethermind.Facade/Eth/BlockForRpc.cs @@ -12,6 +12,7 @@ using System.Text.Json.Serialization; using System.Runtime.CompilerServices; using Nethermind.Facade.Eth.RpcTransaction; +using Nethermind.Core.BlockAccessLists; namespace Nethermind.Facade.Eth; @@ -91,6 +92,8 @@ public BlockForRpc(Block block, bool includeFullTransactionData, ISpecProvider s Withdrawals = block.Withdrawals; WithdrawalsRoot = block.Header.WithdrawalsRoot; RequestsHash = block.Header.RequestsHash; + BlockAccessList = block.BlockAccessList; + GeneratedBlockAccessList = block.GeneratedBlockAccessList; } [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] @@ -154,6 +157,12 @@ public BlockForRpc(Block block, bool includeFullTransactionData, ISpecProvider s [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] public Hash256? RequestsHash { get; set; } + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + public BlockAccessList? BlockAccessList { get; set; } + + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + public BlockAccessList? GeneratedBlockAccessList { get; set; } + private static object[] GetTransactionHashes(Transaction[] transactions) { if (transactions.Length == 0) return Array.Empty(); diff --git a/src/Nethermind/Nethermind.Init/PruningTrieStateFactory.cs b/src/Nethermind/Nethermind.Init/PruningTrieStateFactory.cs index 4153e411934..8bae02f04f7 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.JsonRpc/JsonRpcProcessor.cs b/src/Nethermind/Nethermind.JsonRpc/JsonRpcProcessor.cs index db3c5edb739..3cc3fe059c5 100644 --- a/src/Nethermind/Nethermind.JsonRpc/JsonRpcProcessor.cs +++ b/src/Nethermind/Nethermind.JsonRpc/JsonRpcProcessor.cs @@ -380,11 +380,12 @@ static bool HasNonWhitespace(ReadOnlySpan span) bool isSuccess = localErrorResponse is null; if (!isSuccess) { - if (localErrorResponse?.Error?.SuppressWarning == false) - { - if (_logger.IsWarn) _logger.Warn($"Error response handling JsonRpc Id:{request.Id} Method:{request.Method} | Code: {localErrorResponse.Error.Code} Message: {localErrorResponse.Error.Message}"); - if (_logger.IsTrace) _logger.Trace($"Error when handling {request} | {JsonSerializer.Serialize(localErrorResponse, EthereumJsonSerializer.JsonOptionsIndented)}"); - } + // tmp + // if (localErrorResponse?.Error?.SuppressWarning == false) + // { + // if (_logger.IsWarn) _logger.Warn($"Error response handling JsonRpc Id:{request.Id} Method:{request.Method} | Code: {localErrorResponse.Error.Code} Message: {localErrorResponse.Error.Message}"); + // if (_logger.IsTrace) _logger.Trace($"Error when handling {request} | {JsonSerializer.Serialize(localErrorResponse, EthereumJsonSerializer.JsonOptionsIndented)}"); + // } Metrics.JsonRpcErrors++; } else 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/Data/ExecutionPayload.cs b/src/Nethermind/Nethermind.Merge.Plugin/Data/ExecutionPayload.cs index d0eae608228..ff268c8371b 100644 --- a/src/Nethermind/Nethermind.Merge.Plugin/Data/ExecutionPayload.cs +++ b/src/Nethermind/Nethermind.Merge.Plugin/Data/ExecutionPayload.cs @@ -12,6 +12,8 @@ using Nethermind.State.Proofs; using System.Text.Json.Serialization; using Nethermind.Core.ExecutionRequest; +using Nethermind.Core.BlockAccessLists; +using Nethermind.Core.Extensions; namespace Nethermind.Merge.Plugin.Data; @@ -51,6 +53,8 @@ public class ExecutionPayload : IForkValidator, IExecutionPayloadParams, IExecut public ulong Timestamp { get; set; } + public byte[]? BlockAccessList { get; set; } + protected byte[][] _encodedTransactions = []; /// @@ -108,6 +112,7 @@ public byte[][] Transactions protected static TExecutionPayload Create(Block block) where TExecutionPayload : ExecutionPayload, new() { + // Console.WriteLine("Created block access list:\n" + (block.BlockAccessList is null ? "null" : block.BlockAccessList.ToString())); TExecutionPayload executionPayload = new() { BlockHash = block.Hash!, @@ -124,6 +129,7 @@ public byte[][] Transactions Timestamp = block.Timestamp, BaseFeePerGas = block.BaseFeePerGas, Withdrawals = block.Withdrawals, + BlockAccessList = block.EncodedBlockAccessList ?? (block.BlockAccessList is null ? null : Rlp.Encode(block.BlockAccessList!.Value).Bytes), }; executionPayload.SetTransactions(block.Transactions); return executionPayload; @@ -140,7 +146,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,16 +172,32 @@ 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; - protected virtual Hash256? BuildWithdrawalsRoot() - { - return Withdrawals is null ? null : new WithdrawalTrie(Withdrawals).RootHash; + if (BlockAccessList is not null) + { + //tmp + // Console.WriteLine("Decoding BAL from execution payload:\n" + Bytes.ToHexString(BlockAccessList)); + try + { + blockAccessList = Rlp.Decode(BlockAccessList); + } + catch (RlpException e) + { + Console.Error.Write("Could not decode block access list from execution payload: " + e); + 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() => Withdrawals is null ? null : new WithdrawalTrie(Withdrawals).RootHash; + protected Transaction[]? _transactions = null; /// 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..1fa595a8c0f --- /dev/null +++ b/src/Nethermind/Nethermind.Merge.Plugin/EngineRpcModule.Amsterdam.cs @@ -0,0 +1,19 @@ +// 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_getPayloadV6(byte[] payloadId) + => _getPayloadHandlerV5.HandleAsync(payloadId); + + 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/Handlers/EngineRpcCapabilitiesProvider.cs b/src/Nethermind/Nethermind.Merge.Plugin/Handlers/EngineRpcCapabilitiesProvider.cs index 123ddd1a6f3..c552b1f80ea 100644 --- a/src/Nethermind/Nethermind.Merge.Plugin/Handlers/EngineRpcCapabilitiesProvider.cs +++ b/src/Nethermind/Nethermind.Merge.Plugin/Handlers/EngineRpcCapabilitiesProvider.cs @@ -9,21 +9,16 @@ namespace Nethermind.HealthChecks; -public class EngineRpcCapabilitiesProvider : IRpcCapabilitiesProvider +public class EngineRpcCapabilitiesProvider(ISpecProvider specProvider) : IRpcCapabilitiesProvider { private readonly ConcurrentDictionary _capabilities = new(); - private readonly ISpecProvider _specProvider; - public EngineRpcCapabilitiesProvider(ISpecProvider specProvider) - { - _specProvider = specProvider; - } public IReadOnlyDictionary GetEngineCapabilities() { if (_capabilities.IsEmpty) { - IReleaseSpec spec = _specProvider.GetFinalSpec(); + IReleaseSpec spec = specProvider.GetFinalSpec(); // The Merge _capabilities[nameof(IEngineRpcModule.engine_exchangeTransitionConfigurationV1)] = (true, false); @@ -53,6 +48,10 @@ public EngineRpcCapabilitiesProvider(ISpecProvider specProvider) // Osaka _capabilities[nameof(IEngineRpcModule.engine_getPayloadV5)] = (spec.IsEip7594Enabled, spec.IsEip7594Enabled); _capabilities[nameof(IEngineRpcModule.engine_getBlobsV2)] = (spec.IsEip7594Enabled, false); + + // Amsterdam + _capabilities[nameof(IEngineRpcModule.engine_getPayloadV6)] = (spec.IsEip7928Enabled, spec.IsEip7928Enabled); + _capabilities[nameof(IEngineRpcModule.engine_newPayloadV5)] = (spec.IsEip7928Enabled, spec.IsEip7928Enabled); } return _capabilities; diff --git a/src/Nethermind/Nethermind.Merge.Plugin/Handlers/NewPayloadHandler.cs b/src/Nethermind/Nethermind.Merge.Plugin/Handlers/NewPayloadHandler.cs index d9143e6d2d8..2a54cb725d7 100644 --- a/src/Nethermind/Nethermind.Merge.Plugin/Handlers/NewPayloadHandler.cs +++ b/src/Nethermind/Nethermind.Merge.Plugin/Handlers/NewPayloadHandler.cs @@ -122,6 +122,9 @@ public async Task> HandleAsync(ExecutionPayload r return NewPayloadV1Result.Invalid(null, $"Block {request} could not be parsed as a block: {decodingResult.Error}"); } + // Console.WriteLine("suggested block:"); + // Console.WriteLine(block.BlockAccessList); + string requestStr = $"New Block: {request}"; if (_logger.IsInfo) { 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..3e40d959032 --- /dev/null +++ b/src/Nethermind/Nethermind.Merge.Plugin/IEngineRpcModule.Amsterdam.cs @@ -0,0 +1,26 @@ +// 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)] + public Task> engine_getPayloadV6(byte[] payloadId); + + [JsonRpcMethod( + Description = "Verifies the payload according to the execution environment rules and returns the verification status and hash of the last valid block.", + 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 5a350188359..f5d933da50d 100644 --- a/src/Nethermind/Nethermind.Merge.Plugin/MergePlugin.cs +++ b/src/Nethermind/Nethermind.Merge.Plugin/MergePlugin.cs @@ -328,6 +328,7 @@ protected override void Load(ContainerBuilder builder) .AddSingleton() .AddSingleton>, GetBlobsHandler>() .AddSingleton?>, GetBlobsHandlerV2>() + .AddSingleton() .AddSingleton() .AddSingleton((ctx) => 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.Runner/packages.lock.json b/src/Nethermind/Nethermind.Runner/packages.lock.json index 66249443c4c..683dbaa0f14 100644 --- a/src/Nethermind/Nethermind.Runner/packages.lock.json +++ b/src/Nethermind/Nethermind.Runner/packages.lock.json @@ -833,7 +833,8 @@ "Nethermind.Core": "[1.36.0-unstable, )", "Nethermind.Crypto": "[1.36.0-unstable, )", "Nethermind.Serialization.Rlp": "[1.36.0-unstable, )", - "Nethermind.Specs": "[1.36.0-unstable, )" + "Nethermind.Specs": "[1.36.0-unstable, )", + "Nethermind.Trie": "[1.36.0-unstable, )" } }, "nethermind.evm.precompiles": { diff --git a/src/Nethermind/Nethermind.Serialization.Rlp/BlockBodyDecoder.cs b/src/Nethermind/Nethermind.Serialization.Rlp/BlockBodyDecoder.cs index b2287601a82..a02f2cdbc3e 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 sealed class BlockBodyDecoder : RlpValueDecoder private readonly TxDecoder _txDecoder = TxDecoder.Instance; private readonly IHeaderDecoder _headerDecoder; private readonly WithdrawalDecoder _withdrawalDecoderDecoder = new(); + private readonly BlockAccessListDecoder _blockAccessListDecoder = new(); private static BlockBodyDecoder? _instance = null; public static BlockBodyDecoder Instance => _instance ??= new BlockBodyDecoder(); @@ -28,17 +31,19 @@ public override 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) @@ -92,18 +97,25 @@ 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) { 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); } protected override BlockBody DecodeInternal(RlpStream rlpStream, RlpBehaviors rlpBehaviors = RlpBehaviors.None) @@ -139,5 +151,10 @@ public override void Encode(RlpStream stream, BlockBody body, RlpBehaviors rlpBe 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 13603552159..264be4fd8f7 100644 --- a/src/Nethermind/Nethermind.Serialization.Rlp/BlockDecoder.cs +++ b/src/Nethermind/Nethermind.Serialization.Rlp/BlockDecoder.cs @@ -4,13 +4,15 @@ using System; using System.Buffers; using Nethermind.Core; +using Nethermind.Serialization.Rlp.Eip7928; namespace Nethermind.Serialization.Rlp { public sealed class BlockDecoder(IHeaderDecoder headerDecoder) : RlpValueDecoder { private readonly IHeaderDecoder _headerDecoder = headerDecoder ?? throw new ArgumentNullException(nameof(headerDecoder)); - private readonly BlockBodyDecoder _blockBodyDecoder = new BlockBodyDecoder(headerDecoder); + private readonly BlockBodyDecoder _blockBodyDecoder = new(headerDecoder); + private readonly BlockAccessListDecoder _blockAccessListDecoder = new(); public BlockDecoder() : this(new HeaderDecoder()) { } @@ -21,6 +23,9 @@ public BlockDecoder() : this(new HeaderDecoder()) { } throw new RlpException($"Received a 0 length stream when decoding a {nameof(Block)}"); } + // Console.WriteLine("DECODING BLOCK"); + // Console.WriteLine(Convert.ToHexString(rlpStream.Data)); + if (rlpStream.IsNextItemNull()) { rlpStream.ReadByte(); @@ -34,18 +39,20 @@ 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, int? GeneratedBlockAccessList) 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? generatedBlockAccessList = item.GeneratedBlockAccessList is null ? null : _blockAccessListDecoder.GetLength(item.GeneratedBlockAccessList.Value, rlpBehaviors); 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 ? blockAccessList.Value : 0) + + (generatedBlockAccessList is not null ? generatedBlockAccessList.Value : 0); + return (contentLength, txs, uncles, withdrawals, blockAccessList, generatedBlockAccessList); } public override int GetLength(Block? item, RlpBehaviors rlpBehaviors) @@ -74,7 +81,9 @@ public override int GetLength(Block? item, RlpBehaviors rlpBehaviors) Block block = new(header, body) { - EncodedSize = Rlp.LengthOfSequence(sequenceLength) + EncodedSize = Rlp.LengthOfSequence(sequenceLength), + GeneratedBlockAccessList = decoderContext.PeekNumberOfItemsRemaining() == 0 ? null : _blockAccessListDecoder.Decode(ref decoderContext, rlpBehaviors), + EncodedBlockAccessList = body.BlockAccessList is null ? null : Rlp.Encode(body.BlockAccessList.Value).Bytes // todo: possible without reencoding? }; return block; @@ -100,7 +109,7 @@ public override void Encode(RlpStream stream, Block? item, RlpBehaviors rlpBehav return; } - (int contentLength, int txsLength, int unclesLength, int? withdrawalsLength) = GetContentLength(item, rlpBehaviors); + (int contentLength, int txsLength, int unclesLength, int? withdrawalsLength, int? balLength, int? genBalLength) = GetContentLength(item, rlpBehaviors); stream.StartSequence(contentLength); _headerDecoder.Encode(stream, item.Header); stream.StartSequence(txsLength); @@ -124,6 +133,18 @@ public override void Encode(RlpStream stream, Block? item, RlpBehaviors rlpBehav stream.Encode(item.Withdrawals[i]); } } + + if (item.BlockAccessList is not null) + { + // stream.StartSequence(balLength.Value); + stream.Encode(item.BlockAccessList.Value); + } + + if (item.GeneratedBlockAccessList is not null) + { + // stream.StartSequence(genBalLength.Value); + stream.Encode(item.GeneratedBlockAccessList.Value); + } } public 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..c86d00bca66 --- /dev/null +++ b/src/Nethermind/Nethermind.Serialization.Rlp/Eip7928/AccountChangesDecoder.cs @@ -0,0 +1,161 @@ +// 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(); + + private static readonly RlpLimit _slotsLimit = new(Eip7928Constants.MaxSlots, "", ReadOnlyMemory.Empty); + private static readonly RlpLimit _txLimit = new(Eip7928Constants.MaxTxs, "", ReadOnlyMemory.Empty); + + public AccountChanges Decode(ref Rlp.ValueDecoderContext ctx, RlpBehaviors rlpBehaviors) + { + int length = ctx.ReadSequenceLength(); + int check = length + ctx.Position; + + Address address = ctx.DecodeAddress(); + + SlotChanges[] slotChanges = ctx.DecodeArray(SlotChangesDecoder.Instance, true, default, _slotsLimit); + 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, true, default, _slotsLimit); + SortedSet storageReadsList = []; + 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."); + } + storageReadsList.Add(storageRead); + lastRead = storageRead; + } + + BalanceChange[] balanceChanges = ctx.DecodeArray(BalanceChangeDecoder.Instance, true, default, _txLimit); + ushort? lastIndex = null; + SortedList balanceChangesList = new(balanceChanges.ToDictionary(s => + { + ushort index = s.BlockAccessIndex; + if (lastIndex is not null && index <= lastIndex) + { + Console.WriteLine($"Balance changes were in incorrect order. index={index}, lastIndex={lastIndex}"); + throw new RlpException("Balance changes were in incorrect order."); + } + lastIndex = index; + return index; + }, s => s)); + + lastIndex = null; + NonceChange[] nonceChanges = ctx.DecodeArray(NonceChangeDecoder.Instance, true, default, _txLimit); + SortedList nonceChangesList = new(nonceChanges.ToDictionary(s => + { + ushort index = s.BlockAccessIndex; + if (lastIndex is not null && index <= lastIndex) + { + throw new RlpException("Nonce changes were in incorrect order."); + } + lastIndex = index; + return index; + }, s => s)); + + CodeChange[] codeChanges = ctx.DecodeArray(CodeChangeDecoder.Instance, true, default, _txLimit); + + lastIndex = null; + SortedList codeChangesList = new(codeChanges.ToDictionary(s => + { + ushort index = s.BlockAccessIndex; + if (lastIndex is not null && index <= lastIndex) + { + throw new RlpException("Code changes were in incorrect order."); + } + lastIndex = index; + return index; + }, s => s)); + + return new(address, slotChangesMap, storageReadsList, balanceChangesList, nonceChangesList, 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], rlpBehaviors); + stream.EncodeArray([.. item.StorageReads], rlpBehaviors); + stream.EncodeArray([.. item.BalanceChanges], rlpBehaviors); + stream.EncodeArray([.. item.NonceChanges], rlpBehaviors); + stream.EncodeArray([.. item.CodeChanges], rlpBehaviors); + } + + private static int GetContentLength(AccountChanges item, RlpBehaviors rlpBehaviors) + { + int slotChangesLen = 0; + foreach (SlotChanges slotChanges in item.StorageChanges) + { + 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) + { + balanceChangesLen += BalanceChangeDecoder.Instance.GetLength(balanceChange, rlpBehaviors); + } + balanceChangesLen = Rlp.LengthOfSequence(balanceChangesLen); + + int nonceChangesLen = 0; + foreach (NonceChange nonceChange in item.NonceChanges) + { + nonceChangesLen += NonceChangeDecoder.Instance.GetLength(nonceChange, rlpBehaviors); + } + nonceChangesLen = Rlp.LengthOfSequence(nonceChangesLen); + + int codeChangesLen = 0; + foreach (CodeChange codeChange in item.CodeChanges) + { + 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..46b699292c8 --- /dev/null +++ b/src/Nethermind/Nethermind.Serialization.Rlp/Eip7928/BalanceChangeDecoder.cs @@ -0,0 +1,56 @@ +// 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) + { + int length = ctx.ReadSequenceLength(); + int check = length + ctx.Position; + + 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..5f187644cc9 --- /dev/null +++ b/src/Nethermind/Nethermind.Serialization.Rlp/Eip7928/BlockAccessListDecoder.cs @@ -0,0 +1,57 @@ +// 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(); + + private static readonly RlpLimit _accountsLimit = new(Eip7928Constants.MaxAccounts, "", ReadOnlyMemory.Empty); + + 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, true, default, _accountsLimit); + + Address? lastAddress = null; + SortedDictionary accountChangesMap = new(accountChanges.ToDictionary(a => + { + Address address = a.Address; + if (lastAddress is not null && address.CompareTo(lastAddress) <= 0) + { + throw new RlpException("Account changes were in incorrect order."); + } + lastAddress = address; + return address; + }, a => a)); + BlockAccessList blockAccessList = new(accountChangesMap); + + 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.AccountChanges], rlpBehaviors); + + private static int GetContentLength(BlockAccessList item, RlpBehaviors rlpBehaviors) + => AccountChangesDecoder.Instance.GetContentLength([.. item.AccountChanges], 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..a6ac2c2de6f --- /dev/null +++ b/src/Nethermind/Nethermind.Serialization.Rlp/Eip7928/CodeChangeDecoder.cs @@ -0,0 +1,61 @@ +// 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(); + private static readonly RlpLimit _codeLimit = new(Eip7928Constants.MaxCodeSize, "", ReadOnlyMemory.Empty); + + public CodeChange Decode(ref Rlp.ValueDecoderContext ctx, RlpBehaviors rlpBehaviors) + { + int length = ctx.ReadSequenceLength(); + int check = length + ctx.Position; + + ushort blockAccessIndex = ctx.DecodeUShort(); + byte[] newCode = ctx.DecodeByteArray(_codeLimit); + + 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..352bd7ccedd --- /dev/null +++ b/src/Nethermind/Nethermind.Serialization.Rlp/Eip7928/NonceChangeDecoder.cs @@ -0,0 +1,55 @@ +// 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 NonceChangeDecoder : IRlpValueDecoder, IRlpStreamDecoder +{ + private static NonceChangeDecoder? _instance = null; + public static NonceChangeDecoder Instance => _instance ??= new(); + + public NonceChange Decode(ref Rlp.ValueDecoderContext ctx, RlpBehaviors rlpBehaviors) + { + int length = ctx.ReadSequenceLength(); + int check = length + ctx.Position; + + 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..409d7a82510 --- /dev/null +++ b/src/Nethermind/Nethermind.Serialization.Rlp/Eip7928/SlotChangesDecoder.cs @@ -0,0 +1,71 @@ +// 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(); + + private static readonly RlpLimit _codeLimit = new(Eip7928Constants.MaxCodeSize, "", ReadOnlyMemory.Empty); + + public SlotChanges Decode(ref Rlp.ValueDecoderContext ctx, RlpBehaviors rlpBehaviors) + { + int length = ctx.ReadSequenceLength(); + int check = length + ctx.Position; + + byte[] slot = ctx.DecodeByteArray(RlpLimit.L32); + if (slot.Length != 32) + { + throw new RlpException("Invalid storage key, should be 32 bytes."); + } + + StorageChange[] changes = ctx.DecodeArray(StorageChangeDecoder.Instance, true, default, _codeLimit); + SlotChanges slotChanges = new(slot, [.. 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..0e6e1945571 --- /dev/null +++ b/src/Nethermind/Nethermind.Serialization.Rlp/Eip7928/StorageChangeDecoder.cs @@ -0,0 +1,62 @@ +// 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 StorageChangeDecoder : IRlpValueDecoder, IRlpStreamDecoder +{ + private static StorageChangeDecoder? _instance = null; + public static StorageChangeDecoder Instance => _instance ??= new(); + + public StorageChange Decode(ref Rlp.ValueDecoderContext ctx, RlpBehaviors rlpBehaviors) + { + int length = ctx.ReadSequenceLength(); + int check = length + ctx.Position; + + ushort blockAccessIndex = ctx.DecodeUShort(); + byte[] newValue = ctx.DecodeByteArray(RlpLimit.L32); + if (newValue.Length != 32) + { + throw new RlpException("Invalid storage value, should be 32 bytes."); + } + + StorageChange storageChange = new() + { + BlockAccessIndex = blockAccessIndex, + NewValue = 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); + } + + public static int GetContentLength(StorageChange item, RlpBehaviors rlpBehaviors) + => Rlp.LengthOf(item.BlockAccessIndex) + Rlp.LengthOf(item.NewValue); +} 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..e1c18321eab --- /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(RlpLimit.L32); + if (key.Length != 32) + { + throw new RlpException("Invalid storage key, should be 32 bytes."); + } + + StorageRead storageRead = new() + { + Key = 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); + + public static int GetContentLength(StorageRead item, RlpBehaviors rlpBehaviors) + => Rlp.LengthOf(item.Key); +} diff --git a/src/Nethermind/Nethermind.Serialization.Rlp/HeaderDecoder.cs b/src/Nethermind/Nethermind.Serialization.Rlp/HeaderDecoder.cs index 6a7164d46b9..88d1fa7972d 100644 --- a/src/Nethermind/Nethermind.Serialization.Rlp/HeaderDecoder.cs +++ b/src/Nethermind/Nethermind.Serialization.Rlp/HeaderDecoder.cs @@ -76,6 +76,7 @@ public sealed class HeaderDecoder : RlpValueDecoder, 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 sealed class HeaderDecoder : RlpValueDecoder, 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 override void Encode(RlpStream rlpStream, BlockHeader? header, RlpBehavio } } - 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 override void Encode(RlpStream rlpStream, BlockHeader? header, RlpBehavio 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 override 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 5f796e5db26..425b10a85b5 100644 --- a/src/Nethermind/Nethermind.Serialization.Rlp/Rlp.cs +++ b/src/Nethermind/Nethermind.Serialization.Rlp/Rlp.cs @@ -1113,6 +1113,11 @@ public void DecodeZeroPrefixedKeccakStructRef(out Hash256StructRef keccak, Span< byte[] buffer = Read(20).ToArray(); return new Address(buffer); + + // static void ThrowInvalidPrefix(ref ValueDecoderContext ctx, int prefix) + // { + // 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()}"); + // } } public void DecodeAddressStructRef(out AddressStructRef address) @@ -1547,6 +1552,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(); @@ -1689,6 +1733,21 @@ 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(ushort value) => LengthOf((long)value); + public static int LengthOf(Hash256? item) => item is null ? 1 : 33; public static int LengthOf(in ValueHash256? item) => item is null ? 1 : 33; diff --git a/src/Nethermind/Nethermind.Serialization.Rlp/RlpStream.cs b/src/Nethermind/Nethermind.Serialization.Rlp/RlpStream.cs index ab853eadbe9..a00feadfe15 100644 --- a/src/Nethermind/Nethermind.Serialization.Rlp/RlpStream.cs +++ b/src/Nethermind/Nethermind.Serialization.Rlp/RlpStream.cs @@ -13,11 +13,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 { @@ -30,6 +32,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; internal static ReadOnlySpan SingleBytes => [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, 80, 81, 82, 83, 84, 85, 86, 87, 88, 89, 90, 91, 92, 93, 94, 95, 96, 97, 98, 99, 100, 101, 102, 103, 104, 105, 106, 107, 108, 109, 110, 111, 112, 113, 114, 115, 116, 117, 118, 119, 120, 121, 122, 123, 124, 125, 126, 127]; @@ -73,7 +76,7 @@ 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); } @@ -85,12 +88,15 @@ public void EncodeArray(T?[]? items, RlpBehaviors rlpBehaviors = RlpBehaviors public void Encode(Transaction value, RlpBehaviors rlpBehaviors = RlpBehaviors.None) => _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); public void Encode(BlockInfo value) => _blockInfoDecoder.Encode(this, value); + public void Encode(BlockAccessList value) => _blockAccessListDecoder.Encode(this, value); + public void StartByteArray(int contentLength, bool firstByteLessThan128) { switch (contentLength) diff --git a/src/Nethermind/Nethermind.Shutter.Test/ShutterIntegrationTests.cs b/src/Nethermind/Nethermind.Shutter.Test/ShutterIntegrationTests.cs index 81c18d20d89..17d42c07f7d 100644 --- a/src/Nethermind/Nethermind.Shutter.Test/ShutterIntegrationTests.cs +++ b/src/Nethermind/Nethermind.Shutter.Test/ShutterIntegrationTests.cs @@ -12,6 +12,11 @@ using Nethermind.Core; using System.Threading; using Nethermind.Merge.Plugin.Test; +using Nethermind.Specs.Forks; +using Nethermind.Specs; +using Nethermind.Core.Crypto; +using Nethermind.JsonRpc; +using Nethermind.Core.Extensions; namespace Nethermind.Shutter.Test; @@ -116,4 +121,26 @@ public async Task Can_increment_metric_on_missed_keys() Assert.That(Metrics.ShutterKeysMissed, Is.EqualTo(5)); } + + // todo: move + [Test] + public async Task Can_construct_BAL() + { + using MergeTestBlockchain chain = await new MergeTestBlockchain().Build(new TestSpecProvider(Amsterdam.Instance)); + + Block genesis = chain.BlockFinder.FindGenesisBlock()!; + PayloadAttributes payloadAttributes = + new() { Timestamp = 12, PrevRandao = genesis.Header.Random!, SuggestedFeeRecipient = Address.Zero }; + + // we're using payloadService directly, because we can't use fcU for branch + string payloadId = chain.PayloadPreparationService!.StartPreparingPayload(genesis.Header, payloadAttributes)!; + + await Task.Delay(1000); + + ResultWrapper getPayloadResult = + await chain.EngineRpcModule.engine_getPayloadV6(Bytes.FromHexString(payloadId)); + var res = getPayloadResult.Data!; + Assert.That(res.ExecutionPayload.BlockAccessList, Is.Not.Null); + } + } 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..f3ad9df5036 100644 --- a/src/Nethermind/Nethermind.Specs.Test/OverridableReleaseSpec.cs +++ b/src/Nethermind/Nethermind.Specs.Test/OverridableReleaseSpec.cs @@ -160,10 +160,43 @@ public ulong Eip4844TransitionTimestamp } } - public ulong TargetBlobCount => spec.TargetBlobCount; - public ulong MaxBlobCount => spec.MaxBlobCount; + private ulong? _overridenTargetBlobCount; + public ulong TargetBlobCount + { + get + { + return _overridenTargetBlobCount ?? spec.TargetBlobCount; + } + set + { + _overridenTargetBlobCount = value; + } + } + private ulong? _overridenMaxBlobCount; + public ulong MaxBlobCount + { + get + { + return _overridenMaxBlobCount ?? spec.MaxBlobCount; + } + set + { + _overridenMaxBlobCount = value; + } + } public ulong MaxBlobsPerTx => spec.MaxBlobsPerTx; - public UInt256 BlobBaseFeeUpdateFraction => spec.BlobBaseFeeUpdateFraction; + private UInt256? _overridenBlobBaseFeeUpdateFraction; + public UInt256 BlobBaseFeeUpdateFraction + { + get + { + return _overridenBlobBaseFeeUpdateFraction ?? spec.BlobBaseFeeUpdateFraction; + } + set + { + _overridenBlobBaseFeeUpdateFraction = value; + } + } public bool IsEip1153Enabled => spec.IsEip1153Enabled; public bool IsEip3651Enabled => spec.IsEip3651Enabled; public bool IsEip3855Enabled => spec.IsEip3855Enabled; @@ -199,6 +232,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 a1a02a58ea9..2614f80914b 100644 --- a/src/Nethermind/Nethermind.Specs/ChainSpecStyle/ChainParameters.cs +++ b/src/Nethermind/Nethermind.Specs/ChainSpecStyle/ChainParameters.cs @@ -174,4 +174,5 @@ public class ChainParameters #endregion public ulong? Rip7728TransitionTimestamp { get; set; } + public ulong? Eip7928TransitionTimestamp { get; set; } } diff --git a/src/Nethermind/Nethermind.Specs/ChainSpecStyle/ChainSpec.cs b/src/Nethermind/Nethermind.Specs/ChainSpecStyle/ChainSpec.cs index 94e47e9ab6f..6d290f008e7 100644 --- a/src/Nethermind/Nethermind.Specs/ChainSpecStyle/ChainSpec.cs +++ b/src/Nethermind/Nethermind.Specs/ChainSpecStyle/ChainSpec.cs @@ -81,5 +81,6 @@ public class ChainSpec public ulong? PragueTimestamp { get; set; } public ulong? OsakaTimestamp { get; set; } + public ulong? AmsterdamTimestamp { get; set; } } } diff --git a/src/Nethermind/Nethermind.Specs/ChainSpecStyle/ChainSpecBasedSpecProvider.cs b/src/Nethermind/Nethermind.Specs/ChainSpecStyle/ChainSpecBasedSpecProvider.cs index 9ab4f76e408..5c98217818e 100644 --- a/src/Nethermind/Nethermind.Specs/ChainSpecStyle/ChainSpecBasedSpecProvider.cs +++ b/src/Nethermind/Nethermind.Specs/ChainSpecStyle/ChainSpecBasedSpecProvider.cs @@ -294,6 +294,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 13a2f5994e3..7b52dbd56ea 100644 --- a/src/Nethermind/Nethermind.Specs/ChainSpecStyle/ChainSpecLoader.cs +++ b/src/Nethermind/Nethermind.Specs/ChainSpecStyle/ChainSpecLoader.cs @@ -197,6 +197,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); @@ -257,6 +259,7 @@ chainSpec.Parameters.Eip1283DisableTransition is null chainSpec.CancunTimestamp = chainSpec.Parameters.Eip4844TransitionTimestamp; chainSpec.PragueTimestamp = chainSpec.Parameters.Eip7002TransitionTimestamp; chainSpec.OsakaTimestamp = chainSpec.Parameters.Eip7594TransitionTimestamp; + chainSpec.AmsterdamTimestamp = chainSpec.Parameters.Eip7928TransitionTimestamp; // TheMerge parameters chainSpec.MergeForkIdBlockNumber = chainSpec.Parameters.MergeForkIdTransition; @@ -328,27 +331,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) @@ -368,16 +379,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 4611a4f6f7e..fb09697ee2f 100644 --- a/src/Nethermind/Nethermind.Specs/ChainSpecStyle/Json/ChainSpecParamsJson.cs +++ b/src/Nethermind/Nethermind.Specs/ChainSpecStyle/Json/ChainSpecParamsJson.cs @@ -178,4 +178,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/21_BPO12.cs b/src/Nethermind/Nethermind.Specs/Forks/21_BPO2.cs similarity index 100% rename from src/Nethermind/Nethermind.Specs/Forks/21_BPO12.cs rename to src/Nethermind/Nethermind.Specs/Forks/21_BPO2.cs diff --git a/src/Nethermind/Nethermind.Specs/Forks/22_BPO3.cs b/src/Nethermind/Nethermind.Specs/Forks/22_BPO3.cs new file mode 100644 index 00000000000..4993ba7ca6c --- /dev/null +++ b/src/Nethermind/Nethermind.Specs/Forks/22_BPO3.cs @@ -0,0 +1,23 @@ +// 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 BPO3 : BPO2 +{ + private static IReleaseSpec _instance; + + public BPO3() + { + Name = "bpo3"; + MaxBlobCount = 32; + TargetBlobCount = 21; + BlobBaseFeeUpdateFraction = 17805213; + Released = false; + } + + public new static IReleaseSpec Instance => LazyInitializer.EnsureInitialized(ref _instance, static () => new BPO3()); +} diff --git a/src/Nethermind/Nethermind.Specs/Forks/23_BPO4.cs b/src/Nethermind/Nethermind.Specs/Forks/23_BPO4.cs new file mode 100644 index 00000000000..4e0b500f294 --- /dev/null +++ b/src/Nethermind/Nethermind.Specs/Forks/23_BPO4.cs @@ -0,0 +1,23 @@ +// 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 BPO4 : BPO3 +{ + private static IReleaseSpec _instance; + + public BPO4() + { + Name = "bpo4"; + MaxBlobCount = 48; + TargetBlobCount = 32; + BlobBaseFeeUpdateFraction = 26707819; + Released = false; + } + + public new static IReleaseSpec Instance => LazyInitializer.EnsureInitialized(ref _instance, static () => new BPO4()); +} diff --git a/src/Nethermind/Nethermind.Specs/Forks/24_BPO5.cs b/src/Nethermind/Nethermind.Specs/Forks/24_BPO5.cs new file mode 100644 index 00000000000..44dcdc5fcf1 --- /dev/null +++ b/src/Nethermind/Nethermind.Specs/Forks/24_BPO5.cs @@ -0,0 +1,23 @@ +// 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 BPO5 : BPO4 +{ + private static IReleaseSpec _instance; + + public BPO5() + { + Name = "bpo5"; + MaxBlobCount = 72; + TargetBlobCount = 48; + BlobBaseFeeUpdateFraction = 40061729; + Released = false; + } + + public new static IReleaseSpec Instance => LazyInitializer.EnsureInitialized(ref _instance, static () => new BPO5()); +} diff --git a/src/Nethermind/Nethermind.Specs/Forks/25_Amsterdam.cs b/src/Nethermind/Nethermind.Specs/Forks/25_Amsterdam.cs new file mode 100644 index 00000000000..24d71e16554 --- /dev/null +++ b/src/Nethermind/Nethermind.Specs/Forks/25_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 : BPO5 +{ + 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 9eb2c4d5f7b..b2ee66bd8d2 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..a4030e1274c 100644 --- a/src/Nethermind/Nethermind.Specs/SpecNameParser.cs +++ b/src/Nethermind/Nethermind.Specs/SpecNameParser.cs @@ -54,6 +54,12 @@ public static IReleaseSpec Parse(string specName) "Paris" => Paris.Instance, "Prague" => Prague.Instance, "Osaka" => Osaka.Instance, + "BPO1" => BPO1.Instance, + "BPO2" => BPO2.Instance, + "BPO3" => BPO3.Instance, + "BPO4" => BPO4.Instance, + "BPO5" => BPO5.Instance, + "Amsterdam" => Amsterdam.Instance, _ => throw new NotSupportedException() }; } diff --git a/src/Nethermind/Nethermind.State/Healing/HealingWorldState.cs b/src/Nethermind/Nethermind.State/Healing/HealingWorldState.cs index 78c6097e413..b6170591623 100644 --- a/src/Nethermind/Nethermind.State/Healing/HealingWorldState.cs +++ b/src/Nethermind/Nethermind.State/Healing/HealingWorldState.cs @@ -5,6 +5,7 @@ using Nethermind.Core; using Nethermind.Logging; using Nethermind.Trie.Pruning; +using Nethermind.Evm.State; namespace Nethermind.State.Healing; diff --git a/src/Nethermind/Nethermind.State/StateProvider.cs b/src/Nethermind/Nethermind.State/StateProvider.cs index 5abb93111b2..861f6231d6f 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 @@ -45,7 +45,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; @@ -206,7 +206,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; @@ -242,6 +242,7 @@ static void ThrowNonExistingAccount() } } + oldBalance = 0; return; } @@ -252,6 +253,7 @@ static void ThrowNonExistingAccount() ThrowInsufficientBalanceException(address); } + oldBalance = account.Balance; UInt256 newBalance = isSubtracting ? account.Balance - balanceChange : account.Balance + balanceChange; Account changedAccount = account.WithChangedBalance(newBalance); @@ -272,15 +274,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); } /// @@ -310,10 +317,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); @@ -516,20 +527,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/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); }