diff --git a/src/Nethermind/Chains/hive.json b/src/Nethermind/Chains/hive.json index 805c6aaa175..0e0d1f800a6 100644 --- a/src/Nethermind/Chains/hive.json +++ b/src/Nethermind/Chains/hive.json @@ -26,7 +26,7 @@ "eip155Transition": "0x0", "maxCodeSizeTransition": "0x0", "maxCodeSize": 24576, - "maximumExtraDataSize": 102400, + "maximumExtraDataSize": "0x400", "eip140Transition": "0x0", "eip211Transition": "0x0", "eip214Transition": "0x0", @@ -51,57 +51,45 @@ "eip3529Transition": "0x0", "eip3541Transition": "0x0", "eip3198Transition": "0x0", - "MergeForkIdTransition": "0x64", - "networkID": "0x7", - "chainID": "0x7" + "eip3651TransitionTimestamp": "0x0", + "eip3855TransitionTimestamp": "0x0", + "eip3860TransitionTimestamp": "0x0", + "eip4895TransitionTimestamp": "0x0", + "eip4844TransitionTimestamp": "0x0", + "eip4788TransitionTimestamp": "0x0", + "eip1153TransitionTimestamp": "0x0", + "eip5656TransitionTimestamp": "0x0", + "eip6780TransitionTimestamp": "0x0", + "eip7702TransitionTimestamp": "0x0", + "chainID": "0x1" }, "genesis": { "seal": { "ethereum": { - "nonce": "0x0000000000000000" + "nonce": "0x0000000000000000", + "mixHash": "0x0000000000000000000000000000000000000000000000000000000000000000" } }, - "difficulty": "0x30000", + "difficulty": "0x00", "author": "0x0000000000000000000000000000000000000000", - "timestamp": "0x1234", - "extraData": "0x0000000000000000000000000000000000000000000000000000000000000000658bdf435d810c91414ec09147daa6db624063790000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", - "gasLimit": "0x2fefd8" + "timestamp": "0x00", + "parentHash": "0x0000000000000000000000000000000000000000000000000000000000000000", + "extraData": "0x00", + "gasLimit": "0x016345785d8a0000", + "baseFeePerGas": "0x07" }, "accounts": { - "0xcf49fda3be353c69b41ed96333cd24302da4556f": { - "balance": "0x123450000000000000000" - }, - "0x0161e041aad467a890839d5b08b138c1e6373072": { - "balance": "0x123450000000000000000" - }, - "0x87da6a8c6e9eff15d703fc2773e32f6af8dbe301": { - "balance": "0x123450000000000000000" - }, - "0xb97de4b8c857e4f6bc354f226dc3249aaee49209": { - "balance": "0x123450000000000000000" - }, - "0xc5065c9eeebe6df2c2284d046bfc906501846c51": { - "balance": "0x123450000000000000000" - }, - "0x0000000000000000000000000000000000000314": { - "balance": "0x0", - "code": "0x60606040526000357c0100000000000000000000000000000000000000000000000000000000900463ffffffff168063a223e05d1461006a578063abd1a0cf1461008d578063abfced1d146100d4578063e05c914a14610110578063e6768b451461014c575b610000565b346100005761007761019d565b6040518082815260200191505060405180910390f35b34610000576100be600480803573ffffffffffffffffffffffffffffffffffffffff169060200190919050506101a3565b6040518082815260200191505060405180910390f35b346100005761010e600480803573ffffffffffffffffffffffffffffffffffffffff169060200190919080359060200190919050506101ed565b005b346100005761014a600480803590602001909190803573ffffffffffffffffffffffffffffffffffffffff16906020019091905050610236565b005b346100005761017960048080359060200190919080359060200190919080359060200190919050506103c4565b60405180848152602001838152602001828152602001935050505060405180910390f35b60005481565b6000600160008373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020016000205490505b919050565b80600160008473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001908152602001600020819055505b5050565b7f6031a8d62d7c95988fa262657cd92107d90ed96e08d8f867d32f26edfe85502260405180905060405180910390a17f47e2689743f14e97f7dcfa5eec10ba1dff02f83b3d1d4b9c07b206cbbda66450826040518082815260200191505060405180910390a1817fa48a6b249a5084126c3da369fbc9b16827ead8cb5cdc094b717d3f1dcd995e2960405180905060405180910390a27f7890603b316f3509577afd111710f9ebeefa15e12f72347d9dffd0d65ae3bade81604051808273ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200191505060405180910390a18073ffffffffffffffffffffffffffffffffffffffff167f7efef9ea3f60ddc038e50cccec621f86a0195894dc0520482abf8b5c6b659e4160405180905060405180910390a28181604051808381526020018273ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019250505060405180910390a05b5050565b6000600060008585859250925092505b935093509390505600a165627a7a72305820aaf842d0d0c35c45622c5263cbb54813d2974d3999c8c38551d7c613ea2bc1170029", - "storage": { - "0x0000000000000000000000000000000000000000000000000000000000000000": "0x1234", - "0x6661e9d6d8b923d5bbaab1b96e1dd51ff6ea2a93520fdc9eb75d059238b8c5e9": "0x01" - } - }, - "0x0000000000000000000000000000000000000315": { - "balance": "0x9999999999999999999999999999999", - "code": "0x60606040526000357c0100000000000000000000000000000000000000000000000000000000900463ffffffff168063ef2769ca1461003e575b610000565b3461000057610078600480803573ffffffffffffffffffffffffffffffffffffffff1690602001909190803590602001909190505061007a565b005b8173ffffffffffffffffffffffffffffffffffffffff166108fc829081150290604051809050600060405180830381858888f1935050505015610106578173ffffffffffffffffffffffffffffffffffffffff167f30a3c50752f2552dcc2b93f5b96866280816a986c0c0408cb6778b9fa198288f826040518082815260200191505060405180910390a25b5b50505600a165627a7a72305820637991fabcc8abad4294bf2bb615db78fbec4edff1635a2647d3894e2daf6a610029" - }, - "0x0000000000000000000000000000000000000316": { - "balance": "0x0", - "code": "0x444355" - }, - "0x0000000000000000000000000000000000000317": { - "balance": "0x0", - "code": "0x600160003555" + "0x000f3df6d732807ef1319fb7b8bb8522d0beac02": { + "nonce": "0x01", + "balance": "0x00", + "code": "0x3373fffffffffffffffffffffffffffffffffffffffe14604d57602036146024575f5ffd5b5f35801560495762001fff810690815414603c575f5ffd5b62001fff01545f5260205ff35b5f5ffd5b62001fff42064281555f359062001fff015500", + "storage": {} + }, + "0xa94f5374fce5edbc8e2a8697c15331677e6ebf0b": { + "nonce": "0x00", + "balance": "0x1d6329f1c35ca4bfabb9f5610000000000", + "code": "0x", + "storage": {} }, "0x0000000000000000000000000000000000000001": { "builtin": { @@ -206,4 +194,4 @@ } } } -} \ No newline at end of file +} diff --git a/src/Nethermind/Ethereum.Blockchain.Pyspec.Test/PragueBlockChainTests.cs b/src/Nethermind/Ethereum.Blockchain.Pyspec.Test/PragueBlockChainTests.cs new file mode 100644 index 00000000000..2474c0cfa2b --- /dev/null +++ b/src/Nethermind/Ethereum.Blockchain.Pyspec.Test/PragueBlockChainTests.cs @@ -0,0 +1,25 @@ +// SPDX-FileCopyrightText: 2024 Demerzel Solutions Limited +// SPDX-License-Identifier: LGPL-3.0-only + +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using Ethereum.Test.Base; +using NUnit.Framework; + +namespace Ethereum.Blockchain.Pyspec.Test; + +[TestFixture] +[Parallelizable(ParallelScope.All)] +[Explicit("These tests are not ready yet")] +public class PragueBlockChainTests : BlockchainTestBase +{ + [TestCaseSource(nameof(LoadTests))] + public async Task Test(BlockchainTest test) => await RunTest(test); + + private static IEnumerable LoadTests() + { + TestsSourceLoader loader = new(new LoadPyspecTestsStrategy(), $"fixtures/blockchain_tests/prague"); + return loader.LoadTests().OfType(); + } +} diff --git a/src/Nethermind/Ethereum.Blockchain.Pyspec.Test/PragueStateTests.cs b/src/Nethermind/Ethereum.Blockchain.Pyspec.Test/PragueStateTests.cs new file mode 100644 index 00000000000..107b4e249f1 --- /dev/null +++ b/src/Nethermind/Ethereum.Blockchain.Pyspec.Test/PragueStateTests.cs @@ -0,0 +1,22 @@ +using System.Collections.Generic; +using System.Linq; +using Ethereum.Test.Base; +using FluentAssertions; +using NUnit.Framework; + +namespace Ethereum.Blockchain.Pyspec.Test; + +[TestFixture] +[Parallelizable(ParallelScope.All)] +[Explicit("These tests are not ready yet")] +public class PragueStateTests : GeneralStateTestBase +{ + [TestCaseSource(nameof(LoadTests))] + public void Test(GeneralStateTest test) => RunTest(test).Pass.Should().BeTrue(); + + private static IEnumerable LoadTests() + { + TestsSourceLoader loader = new(new LoadPyspecTestsStrategy(), $"fixtures/state_tests/prague"); + return loader.LoadTests().Cast(); + } +} diff --git a/src/Nethermind/Ethereum.Test.Base/AuthorizationListJson.cs b/src/Nethermind/Ethereum.Test.Base/AuthorizationListJson.cs new file mode 100644 index 00000000000..a2b8b6cfcbc --- /dev/null +++ b/src/Nethermind/Ethereum.Test.Base/AuthorizationListJson.cs @@ -0,0 +1,16 @@ +// SPDX-FileCopyrightText: 2024 Demerzel Solutions Limited +// SPDX-License-Identifier: LGPL-3.0-only + +using Nethermind.Core; + +namespace Ethereum.Test.Base; +public class AuthorizationListJson +{ + public ulong ChainId { get; set; } + public Address Address { get; set; } + public ulong Nonce { get; set; } + public ulong V { get; set; } + public byte[] R { get; set; } + public byte[] S { get; set; } + public Address Signer { get; set; } +} diff --git a/src/Nethermind/Ethereum.Test.Base/GeneralTestBase.cs b/src/Nethermind/Ethereum.Test.Base/GeneralTestBase.cs index 120c146c6d3..47487e7e212 100644 --- a/src/Nethermind/Ethereum.Test.Base/GeneralTestBase.cs +++ b/src/Nethermind/Ethereum.Test.Base/GeneralTestBase.cs @@ -137,12 +137,16 @@ protected EthereumTestResult RunTest(GeneralStateTest test, ITxTracer txTracer) Block block = Build.A.Block.WithTransactions(test.Transaction).WithHeader(header).TestObject; - bool isValid = _txValidator.IsWellFormed(test.Transaction, spec) && IsValidBlock(block, specProvider); + ValidationResult txIsValid = _txValidator.IsWellFormed(test.Transaction, spec); - if (isValid) + if (txIsValid) { transactionProcessor.Execute(test.Transaction, new BlockExecutionContext(header), txTracer); } + else + { + _logger.Info($"Skipping invalid tx with error: {txIsValid.Error}"); + } stopwatch.Stop(); diff --git a/src/Nethermind/Ethereum.Test.Base/JsonToEthereumTest.cs b/src/Nethermind/Ethereum.Test.Base/JsonToEthereumTest.cs index 8ac509a9672..7d41065cd52 100644 --- a/src/Nethermind/Ethereum.Test.Base/JsonToEthereumTest.cs +++ b/src/Nethermind/Ethereum.Test.Base/JsonToEthereumTest.cs @@ -127,6 +127,7 @@ public static Transaction Convert(PostStateJson postStateJson, TransactionJson t { Transaction transaction = new(); + transaction.Type = transactionJson.Type; transaction.Value = transactionJson.Value[postStateJson.Indexes.Value]; transaction.GasLimit = transactionJson.GasLimit[postStateJson.Indexes.Gas]; transaction.GasPrice = transactionJson.GasPrice ?? transactionJson.MaxPriorityFeePerGas ?? 0; @@ -157,6 +158,23 @@ public static Transaction Convert(PostStateJson postStateJson, TransactionJson t if (transaction.BlobVersionedHashes?.Length > 0) transaction.Type = TxType.Blob; + if (transactionJson.AuthorizationList is not null) + { + transaction.AuthorizationList = + transactionJson.AuthorizationList + .Select(i => new AuthorizationTuple( + i.ChainId, + i.Address, + i.Nonce, + i.V, + i.R, + i.S)).ToArray(); + if (transaction.AuthorizationList.Any()) + { + transaction.Type = TxType.SetCode; + } + } + return transaction; } @@ -291,6 +309,7 @@ public static IEnumerable Convert(string json) List tests = new(); foreach (KeyValuePair namedTest in testsInFile) { + Console.WriteLine($"Loading {namedTest.Key}\n {namedTest.Value.Post}"); tests.AddRange(Convert(namedTest.Key, namedTest.Value)); } diff --git a/src/Nethermind/Ethereum.Test.Base/LegacyTransactionJson.cs b/src/Nethermind/Ethereum.Test.Base/LegacyTransactionJson.cs index db932e48913..0d93af017d5 100644 --- a/src/Nethermind/Ethereum.Test.Base/LegacyTransactionJson.cs +++ b/src/Nethermind/Ethereum.Test.Base/LegacyTransactionJson.cs @@ -14,6 +14,7 @@ public class LegacyTransactionJson public UInt256 Nonce { get; set; } public Address To { get; set; } public UInt256 Value { get; set; } + public string Sender { get; set; } public byte[] R { get; set; } public byte[] S { get; set; } public ulong V { get; set; } diff --git a/src/Nethermind/Ethereum.Test.Base/TransactionJson.cs b/src/Nethermind/Ethereum.Test.Base/TransactionJson.cs index 59658f8e876..83c4884f835 100644 --- a/src/Nethermind/Ethereum.Test.Base/TransactionJson.cs +++ b/src/Nethermind/Ethereum.Test.Base/TransactionJson.cs @@ -8,6 +8,8 @@ namespace Ethereum.Test.Base { public class TransactionJson { + public TxType Type { get; set; } + public Address Sender { get; set; } public byte[][]? Data { get; set; } public long[]? GasLimit { get; set; } public UInt256? GasPrice { get; set; } @@ -19,6 +21,7 @@ public class TransactionJson public byte[]? SecretKey { get; set; } public AccessListItemJson[]?[]? AccessLists { get; set; } public AccessListItemJson[]? AccessList { get; set; } + public AuthorizationListJson[]? AuthorizationList { get; set; } public byte[]?[]? BlobVersionedHashes { get; set; } public UInt256? MaxFeePerBlobGas { get; set; } } diff --git a/src/Nethermind/Nethermind.Benchmark/Core/RecoverSignaturesBenchmark.cs b/src/Nethermind/Nethermind.Benchmark/Core/RecoverSignaturesBenchmark.cs new file mode 100644 index 00000000000..0b7655985fe --- /dev/null +++ b/src/Nethermind/Nethermind.Benchmark/Core/RecoverSignaturesBenchmark.cs @@ -0,0 +1,203 @@ +using BenchmarkDotNet.Attributes; +using Nethermind.Core.Crypto; +using Nethermind.Core.Specs; +using Nethermind.Core.Test.Builders; +using Nethermind.Core; +using Nethermind.Crypto; +using Nethermind.Logging; +using Nethermind.Serialization.Rlp; +using Nethermind.Specs; +using System; +using System.Collections.Generic; +using Nethermind.Consensus.Processing; +using Nethermind.TxPool; +using Nethermind.Int256; +using System.Diagnostics.CodeAnalysis; +using System.Linq; +using System.Threading.Tasks; + +namespace Nethermind.Benchmarks.Core +{ + [MemoryDiagnoser] + public class RecoverSignaturesBenchmark + { + private ISpecProvider _specProvider = MainnetSpecProvider.Instance; + + private static EthereumEcdsa _ethereumEcdsa; + private static RecoverSignatures _sut; + + private Block _block100TxWith100AuthSigs; + private Block _block100TxWith10AuthSigs; + private Block _block100TxWith1AuthSigs; + private Block _block3TxWith1AuthSigs; + private Block _block10TxWith0AuthSigs; + private Block _block10TxWith10AuthSigs; + + private static PrivateKey[] _privateKeys = Enumerable.Range(0, 1000) + .Select(i => Build.A.PrivateKey.TestObject) + .ToArray(); + + [GlobalSetup] + public void GlobalSetup() + { + _ethereumEcdsa = new(_specProvider.ChainId); + _sut = new(_ethereumEcdsa, NullTxPool.Instance, _specProvider, NullLogManager.Instance); + + var rnd = new Random(); + + _block100TxWith100AuthSigs = Build.A.Block + .WithHeader(new BlockHeader() + { + Timestamp = ulong.MaxValue, + Number = long.MaxValue + }) + .WithTransactions(CreateTransactions(100, 100)) + .TestObject; + _block100TxWith10AuthSigs = Build.A.Block + .WithHeader(new BlockHeader() + { + Timestamp = ulong.MaxValue, + Number = long.MaxValue + }) + .WithTransactions(CreateTransactions(100, 10)) + .TestObject; + + _block100TxWith1AuthSigs = Build.A.Block + .WithHeader(new BlockHeader() + { + Timestamp = ulong.MaxValue, + Number = long.MaxValue + }) + .WithTransactions(CreateTransactions(100, 1)) + .TestObject; + + _block10TxWith10AuthSigs = Build.A.Block + .WithHeader(new BlockHeader() + { + Timestamp = ulong.MaxValue, + Number = long.MaxValue + }) + .WithTransactions(CreateTransactions(10, 10)) + .TestObject; + + _block3TxWith1AuthSigs = Build.A.Block + .WithHeader(new BlockHeader() + { + Timestamp = ulong.MaxValue, + Number = long.MaxValue + }) + .WithTransactions(CreateTransactions(3, 1)) + .TestObject; + + _block10TxWith0AuthSigs = Build.A.Block + .WithHeader(new BlockHeader() + { + Timestamp = ulong.MaxValue, + Number = long.MaxValue + }) + .WithTransactions(CreateTransactions(10, 0)) + .TestObject; + + Transaction[] CreateTransactions(int txCount, int authPerTx) + { + var list = new List(); + for (int i = 0; i < txCount; i++) + { + PrivateKey signer = _privateKeys[i]; + + Transaction tx = Build.A.Transaction + .WithType(TxType.SetCode) + .WithAuthorizationCode( + Enumerable.Range(0, authPerTx).Select(y => + { + PrivateKey authority = _privateKeys[i + y + _privateKeys.Length / 2]; + return CreateAuthorizationTuple( + authority, + (ulong)rnd.NextInt64(), + Address.Zero, + (ulong)rnd.NextInt64()); + }).ToArray() + ) + .SignedAndResolved(signer) + .WithSenderAddress(null) + .TestObject; + list.Add(tx); + } + return list.ToArray(); + } + + static AuthorizationTuple CreateAuthorizationTuple(PrivateKey signer, ulong chainId, Address codeAddress, ulong nonce) + { + AuthorizationTupleDecoder decoder = new(); + RlpStream rlp = decoder.EncodeWithoutSignature(chainId, codeAddress, nonce); + Span code = stackalloc byte[rlp.Length + 1]; + code[0] = Eip7702Constants.Magic; + rlp.Data.AsSpan().CopyTo(code.Slice(1)); + + Signature sig = _ethereumEcdsa.Sign(signer, Keccak.Compute(code)); + + return new AuthorizationTuple(chainId, codeAddress, nonce, sig); + } + } + + [IterationCleanup] + public void IterationCleanup() + { + ResetSigs(_block100TxWith100AuthSigs); + ResetSigs(_block100TxWith10AuthSigs); + ResetSigs(_block100TxWith1AuthSigs); + ResetSigs(_block10TxWith10AuthSigs); + ResetSigs(_block10TxWith0AuthSigs); + ResetSigs(_block3TxWith1AuthSigs); + + void ResetSigs(Block block) + { + Parallel.ForEach(block.Transactions, (t) => + { + t.SenderAddress = null; + t.Hash = null; + Parallel.ForEach(t.AuthorizationList, (tuple) => + { + tuple.Authority = null; + }); + }); + } + } + + [Benchmark] + public void Recover100TxSignatureswith100AuthoritySignatures() + { + _sut.RecoverData(_block100TxWith100AuthSigs); + } + + [Benchmark] + public void Recover100TxSignatureswith10AuthoritySignatures() + { + _sut.RecoverData(_block100TxWith10AuthSigs); + } + + [Benchmark] + public void Recover100TxSignaturesWith1AuthoritySignatures() + { + _sut.RecoverData(_block100TxWith1AuthSigs); + } + + [Benchmark] + public void Recover10TxSignaturesWith10AuthoritySignatures() + { + _sut.RecoverData(_block10TxWith10AuthSigs); + } + + [Benchmark] + public void Recover3TxSignaturesWith1AuthoritySignatures() + { + _sut.RecoverData(_block3TxWith1AuthSigs); + } + + [Benchmark] + public void Recover10TxSignaturesWith0AuthoritySignatures() + { + _sut.RecoverData(_block10TxWith0AuthSigs); + } + } +} diff --git a/src/Nethermind/Nethermind.Blockchain.Test/ReorgTests.cs b/src/Nethermind/Nethermind.Blockchain.Test/ReorgTests.cs index 840c63e6f9f..88274cfd17b 100644 --- a/src/Nethermind/Nethermind.Blockchain.Test/ReorgTests.cs +++ b/src/Nethermind/Nethermind.Blockchain.Test/ReorgTests.cs @@ -48,16 +48,16 @@ public void Setup() .WithSpecProvider(specProvider) .TestObject; + CodeInfoRepository codeInfoRepository = new(); TxPool.TxPool txPool = new( ecdsa, new BlobTxStorage(), - new ChainHeadInfoProvider(specProvider, _blockTree, stateProvider), + new ChainHeadInfoProvider(specProvider, _blockTree, stateProvider, codeInfoRepository), new TxPoolConfig(), new TxValidator(specProvider.ChainId), LimboLogs.Instance, transactionComparerProvider.GetDefaultComparer()); BlockhashProvider blockhashProvider = new(_blockTree, specProvider, stateProvider, LimboLogs.Instance); - CodeInfoRepository codeInfoRepository = new(); VirtualMachine virtualMachine = new( blockhashProvider, specProvider, diff --git a/src/Nethermind/Nethermind.Blockchain.Test/Validators/TxValidatorTests.cs b/src/Nethermind/Nethermind.Blockchain.Test/Validators/TxValidatorTests.cs index c7cd07b95e1..f52adce9ba7 100644 --- a/src/Nethermind/Nethermind.Blockchain.Test/Validators/TxValidatorTests.cs +++ b/src/Nethermind/Nethermind.Blockchain.Test/Validators/TxValidatorTests.cs @@ -6,6 +6,7 @@ using System.Linq; using System.Numerics; using FluentAssertions; +using Nethermind.Consensus.Messages; using Nethermind.Consensus.Validators; using Nethermind.Core; using Nethermind.Core.Crypto; @@ -58,7 +59,7 @@ public void Zero_r_is_not_valid() txValidator.IsWellFormed(tx, MuirGlacier.Instance).AsBool().Should().BeFalse(); } - private static byte CalculateV() => (byte)EthereumEcdsa.CalculateV(TestBlockchainIds.ChainId); + private static byte CalculateV() => (byte)EthereumEcdsaExtensions.CalculateV(TestBlockchainIds.ChainId); [Test, MaxTime(Timeout.MaxTestTime)] public void Zero_s_is_not_valid() @@ -522,6 +523,129 @@ public void IsWellFormed_BlobTxHasProofOverTheSizeLimit_ReturnFalse() Assert.That(txValidator.IsWellFormed(tx, Cancun.Instance).AsBool(), Is.False); } + [Test] + public void IsWellFormed_CreateTxInSetCode_ReturnsFalse() + { + TransactionBuilder txBuilder = Build.A.Transaction + .WithType(TxType.SetCode) + .WithAuthorizationCode(new AuthorizationTuple(0, TestItem.AddressA, 0, 0, [], [])) + .WithMaxFeePerGas(100000) + .WithGasLimit(1000000) + .WithChainId(TestBlockchainIds.ChainId) + .SignedAndResolved() + .WithTo(null); + + Transaction tx = txBuilder.TestObject; + TxValidator txValidator = new(TestBlockchainIds.ChainId); + + Assert.Multiple(() => + { + ValidationResult validationResult = txValidator.IsWellFormed(tx, Prague.Instance); + Assert.That(validationResult.AsBool(), Is.False); + Assert.That(validationResult.Error, Is.EqualTo(TxErrorMessages.NotAllowedCreateTransaction)); + }); + } + + [Test] + public void IsWellFormed_AuthorizationListTxInPragueSpec_ReturnsTrue() + { + TransactionBuilder txBuilder = Build.A.Transaction + .WithType(TxType.SetCode) + .WithTo(TestItem.AddressA) + .WithAuthorizationCode(new AuthorizationTuple(0, TestItem.AddressA, 0, 0, [], [])) + .WithMaxFeePerGas(100000) + .WithGasLimit(1000000) + .WithChainId(TestBlockchainIds.ChainId) + .SignedAndResolved(); + + Transaction tx = txBuilder.TestObject; + TxValidator txValidator = new(TestBlockchainIds.ChainId); + + Assert.That(txValidator.IsWellFormed(tx, Prague.Instance).AsBool, Is.True); + } + + [Test] + public void IsWellFormed_EmptyAuthorizationList_ReturnsFalse() + { + TransactionBuilder txBuilder = Build.A.Transaction + .WithType(TxType.SetCode) + .WithTo(TestItem.AddressA) + .WithAuthorizationCode([]) + .WithMaxFeePerGas(100000) + .WithGasLimit(1000000) + .WithChainId(TestBlockchainIds.ChainId) + .SignedAndResolved(); + + Transaction tx = txBuilder.TestObject; + TxValidator txValidator = new(TestBlockchainIds.ChainId); + + Assert.That(txValidator.IsWellFormed(tx, Prague.Instance).AsBool, Is.False); + } + [Test] + public void IsWellFormed_NullAuthorizationList_ReturnsFalse() + { + TransactionBuilder txBuilder = Build.A.Transaction + .WithType(TxType.SetCode) + .WithTo(TestItem.AddressA) + .WithAuthorizationCode((AuthorizationTuple[])null!) + .WithMaxFeePerGas(100000) + .WithGasLimit(1000000) + .WithChainId(TestBlockchainIds.ChainId) + .SignedAndResolved(); + + Transaction tx = txBuilder.TestObject; + TxValidator txValidator = new(TestBlockchainIds.ChainId); + + Assert.That(txValidator.IsWellFormed(tx, Prague.Instance).AsBool, Is.False); + } + + private static object[] BadSignatures = + { + new object[] { 1ul, (UInt256)1, Secp256K1Curve.HalfNPlusOne, false}, + new object[] { 1ul, UInt256.Zero, Secp256K1Curve.HalfN, true }, + new object[] { 0ul, UInt256.Zero, UInt256.Zero, true }, + }; + [TestCaseSource(nameof(BadSignatures))] + public void IsWellFormed_AuthorizationTupleHasBadSignature_ReturnsFalse(ulong yParity, UInt256 r, UInt256 s, bool expected) + { + TransactionBuilder txBuilder = Build.A.Transaction + .WithType(TxType.SetCode) + .WithTo(TestItem.AddressA) + .WithAuthorizationCode(new AuthorizationTuple(0, Address.Zero, 0, new Signature(r, s, yParity + Signature.VOffset))) + .WithMaxFeePerGas(100000) + .WithGasLimit(1000000) + .WithChainId(TestBlockchainIds.ChainId) + .SignedAndResolved(); + + Transaction tx = txBuilder.TestObject; + TxValidator txValidator = new(TestBlockchainIds.ChainId); + + Assert.That(txValidator.IsWellFormed(tx, Prague.Instance).AsBool, Is.EqualTo(expected)); + } + + private static IEnumerable NonSetCodeTypes() => + Enum.GetValues().Where(t => t != TxType.SetCode && t != TxType.DepositTx); + + [TestCaseSource(nameof(NonSetCodeTypes))] + public void IsWellFormed_NonSetCodeTxHasAuthorizationList_ReturnsFalse(TxType type) + { + var x = Enum.GetValues().Where(t => t != TxType.SetCode); + TransactionBuilder txBuilder = Build.A.Transaction + .WithType(type) + .WithTo(TestItem.AddressA) + .WithMaxFeePerGas(100000) + .WithGasLimit(1000000) + .WithChainId(TestBlockchainIds.ChainId) + .WithShardBlobTxTypeAndFieldsIfBlobTx() + .WithAuthorizationCode(new AuthorizationTuple(TestBlockchainIds.ChainId, TestItem.AddressA, 0, new Signature(new byte[65]))) + .SignedAndResolved(); + + Transaction tx = txBuilder.TestObject; + TxValidator txValidator = new(TestBlockchainIds.ChainId); + + Assert.That(txValidator.IsWellFormed(tx, Prague.Instance).Error, Is.EqualTo(TxErrorMessages.NotAllowedAuthorizationList)); + } + private static byte[] MakeArray(int count, params byte[] elements) => elements.Take(Math.Min(count, elements.Length)) .Concat(new byte[Math.Max(0, count - elements.Length)]) diff --git a/src/Nethermind/Nethermind.Blockchain/ChainHeadInfoProvider.cs b/src/Nethermind/Nethermind.Blockchain/ChainHeadInfoProvider.cs index f843345bc38..3c90e3dc736 100644 --- a/src/Nethermind/Nethermind.Blockchain/ChainHeadInfoProvider.cs +++ b/src/Nethermind/Nethermind.Blockchain/ChainHeadInfoProvider.cs @@ -17,28 +17,31 @@ namespace Nethermind.Blockchain { public class ChainHeadInfoProvider : IChainHeadInfoProvider { - public ChainHeadInfoProvider(ISpecProvider specProvider, IBlockTree blockTree, IStateReader stateReader) - : this(new ChainHeadSpecProvider(specProvider, blockTree), blockTree, new ChainHeadReadOnlyStateProvider(blockTree, stateReader)) + public ChainHeadInfoProvider(ISpecProvider specProvider, IBlockTree blockTree, IStateReader stateReader, ICodeInfoRepository codeInfoRepository) + : this(new ChainHeadSpecProvider(specProvider, blockTree), blockTree, new ChainHeadReadOnlyStateProvider(blockTree, stateReader), codeInfoRepository) { } - public ChainHeadInfoProvider(ISpecProvider specProvider, IBlockTree blockTree, IAccountStateProvider stateProvider) - : this(new ChainHeadSpecProvider(specProvider, blockTree), blockTree, stateProvider) + public ChainHeadInfoProvider(ISpecProvider specProvider, IBlockTree blockTree, IReadOnlyStateProvider stateProvider, ICodeInfoRepository codeInfoRepository) + : this(new ChainHeadSpecProvider(specProvider, blockTree), blockTree, stateProvider, codeInfoRepository) { } - public ChainHeadInfoProvider(IChainHeadSpecProvider specProvider, IBlockTree blockTree, IAccountStateProvider stateProvider) + public ChainHeadInfoProvider(IChainHeadSpecProvider specProvider, IBlockTree blockTree, IReadOnlyStateProvider stateProvider, ICodeInfoRepository codeInfoRepository) { SpecProvider = specProvider; - AccountStateProvider = stateProvider; + ReadOnlyStateProvider = stateProvider; HeadNumber = blockTree.BestKnownNumber; + CodeInfoRepository = codeInfoRepository; blockTree.BlockAddedToMain += OnHeadChanged; } public IChainHeadSpecProvider SpecProvider { get; } - public IAccountStateProvider AccountStateProvider { get; } + public IReadOnlyStateProvider ReadOnlyStateProvider { get; } + + public ICodeInfoRepository CodeInfoRepository { get; } public long HeadNumber { get; private set; } diff --git a/src/Nethermind/Nethermind.Clique.Test/CliqueBlockProducerTests.cs b/src/Nethermind/Nethermind.Clique.Test/CliqueBlockProducerTests.cs index 2769e365735..a9f2092be3e 100644 --- a/src/Nethermind/Nethermind.Clique.Test/CliqueBlockProducerTests.cs +++ b/src/Nethermind/Nethermind.Clique.Test/CliqueBlockProducerTests.cs @@ -107,9 +107,10 @@ public On CreateNode(PrivateKey privateKey, bool withGenesisAlreadyProcessed = f ITransactionComparerProvider transactionComparerProvider = new TransactionComparerProvider(specProvider, blockTree); + CodeInfoRepository codeInfoRepository = new(); TxPool.TxPool txPool = new(_ethereumEcdsa, new BlobTxStorage(), - new ChainHeadInfoProvider(new FixedForkActivationChainHeadSpecProvider(GoerliSpecProvider.Instance), blockTree, stateProvider), + new ChainHeadInfoProvider(new FixedForkActivationChainHeadSpecProvider(GoerliSpecProvider.Instance), blockTree, stateProvider, codeInfoRepository), new TxPoolConfig(), new TxValidator(goerliSpecProvider.ChainId), _logManager, @@ -127,7 +128,6 @@ public On CreateNode(PrivateKey privateKey, bool withGenesisAlreadyProcessed = f _genesis.Header.Hash = _genesis.Header.CalculateHash(); _genesis3Validators.Header.Hash = _genesis3Validators.Header.CalculateHash(); - CodeInfoRepository codeInfoRepository = new(); TransactionProcessor transactionProcessor = new(goerliSpecProvider, stateProvider, new VirtualMachine(blockhashProvider, specProvider, codeInfoRepository, nodeLogManager), codeInfoRepository, diff --git a/src/Nethermind/Nethermind.Consensus.AuRa/InitializationSteps/InitializeBlockchainAuRa.cs b/src/Nethermind/Nethermind.Consensus.AuRa/InitializationSteps/InitializeBlockchainAuRa.cs index cf7dbf413fc..d2a3952e8f0 100644 --- a/src/Nethermind/Nethermind.Consensus.AuRa/InitializationSteps/InitializeBlockchainAuRa.cs +++ b/src/Nethermind/Nethermind.Consensus.AuRa/InitializationSteps/InitializeBlockchainAuRa.cs @@ -24,6 +24,7 @@ using Nethermind.Consensus.Validators; using Nethermind.Consensus.Withdrawals; using Nethermind.Core; +using Nethermind.Evm; using Nethermind.Init.Steps; using Nethermind.Logging; using Nethermind.State; @@ -256,24 +257,25 @@ private IComparer CreateTxPoolTxComparer(TxPriorityContract? txPrio return CreateTxPoolTxComparer(); } - protected override TxPool.TxPool CreateTxPool() + protected override TxPool.TxPool CreateTxPool(CodeInfoRepository codeInfoRepository) { // This has to be different object than the _processingReadOnlyTransactionProcessorSource as this is in separate thread - var txPriorityContract = TxAuRaFilterBuilders.CreateTxPrioritySources(_api); - var localDataSource = _api.TxPriorityContractLocalDataSource; + TxPriorityContract txPriorityContract = TxAuRaFilterBuilders.CreateTxPrioritySources(_api); + TxPriorityContract.LocalDataSource? localDataSource = _api.TxPriorityContractLocalDataSource; ReportTxPriorityRules(txPriorityContract, localDataSource); - var minGasPricesContractDataStore = TxAuRaFilterBuilders.CreateMinGasPricesDataStore(_api, txPriorityContract, localDataSource); + DictionaryContractDataStore? minGasPricesContractDataStore + = TxAuRaFilterBuilders.CreateMinGasPricesDataStore(_api, txPriorityContract, localDataSource); ITxFilter txPoolFilter = TxAuRaFilterBuilders.CreateAuRaTxFilterForProducer(_api, minGasPricesContractDataStore); return new TxPool.TxPool( - _api.EthereumEcdsa, + _api.EthereumEcdsa!, _api.BlobTxStorage ?? NullBlobTxStorage.Instance, - new ChainHeadInfoProvider(_api.SpecProvider, _api.BlockTree, _api.StateReader), + new ChainHeadInfoProvider(_api.SpecProvider!, _api.BlockTree!, _api.StateReader!, codeInfoRepository), NethermindApi.Config(), - _api.TxValidator, + _api.TxValidator!, _api.LogManager, CreateTxPoolTxComparer(txPriorityContract, localDataSource), _api.TxGossipPolicy, diff --git a/src/Nethermind/Nethermind.Consensus.Clique/CliqueBlockProducer.cs b/src/Nethermind/Nethermind.Consensus.Clique/CliqueBlockProducer.cs index 470c9ed17f5..f85842a83ca 100644 --- a/src/Nethermind/Nethermind.Consensus.Clique/CliqueBlockProducer.cs +++ b/src/Nethermind/Nethermind.Consensus.Clique/CliqueBlockProducer.cs @@ -501,7 +501,7 @@ ILogManager logManager selectedTxs, Array.Empty(), spec.WithdrawalsEnabled ? Enumerable.Empty() : null, - spec.ConsensusRequestsEnabled ? Enumerable.Empty() : null + spec.RequestsEnabled ? Enumerable.Empty() : null ); header.TxRoot = TxTrie.CalculateRoot(block.Transactions); block.Header.Author = _sealer.Address; diff --git a/src/Nethermind/Nethermind.Consensus.Test/CensorshipDetectorTests.cs b/src/Nethermind/Nethermind.Consensus.Test/CensorshipDetectorTests.cs index 4e8ba989451..a3ead8f5cff 100644 --- a/src/Nethermind/Nethermind.Consensus.Test/CensorshipDetectorTests.cs +++ b/src/Nethermind/Nethermind.Consensus.Test/CensorshipDetectorTests.cs @@ -16,6 +16,7 @@ using Nethermind.Core.Test.Builders; using Nethermind.Crypto; using Nethermind.Db; +using Nethermind.Evm; using Nethermind.Logging; using Nethermind.Specs; using Nethermind.Specs.Forks; @@ -256,7 +257,7 @@ private TxPool.TxPool CreatePool(bool eip1559Enabled = true) return new( _ethereumEcdsa, new BlobTxStorage(), - new ChainHeadInfoProvider(_specProvider, _blockTree, _stateProvider), + new ChainHeadInfoProvider(_specProvider, _blockTree, _stateProvider, new CodeInfoRepository()), new TxPoolConfig(), new TxValidator(_specProvider.ChainId), _logManager, diff --git a/src/Nethermind/Nethermind.Consensus/Messages/TxErrorMessages.cs b/src/Nethermind/Nethermind.Consensus/Messages/TxErrorMessages.cs index 8eab24494cf..2a8082895ff 100644 --- a/src/Nethermind/Nethermind.Consensus/Messages/TxErrorMessages.cs +++ b/src/Nethermind/Nethermind.Consensus/Messages/TxErrorMessages.cs @@ -16,6 +16,8 @@ public static string InvalidTxType(string name) => $"InvalidTxType: Transaction type in {name} is not supported."; public const string IntrinsicGasTooLow = "IntrinsicGasTooLow: Gas limit is too low."; + public const string TxMissingTo = + "TxMissingTo: Must be set."; public const string InvalidTxSignature = "InvalidTxSignature: Signature is invalid."; @@ -37,8 +39,8 @@ public static string InvalidTxChainId(ulong expected, ulong? actual) => public const string InvalidTransaction = $"InvalidTransaction: Cannot be {nameof(ShardBlobNetworkWrapper)}."; - public const string TxMissingTo = - "TxMissingTo: Must be set."; + public const string NotAllowedCreateTransaction = + "NotAllowedCreateTransaction: To must be set."; public const string BlobTxMissingMaxFeePerBlobGas = "BlobTxMissingMaxFeePerBlobGas: Must be set."; @@ -70,6 +72,12 @@ public static string InvalidTxChainId(ulong expected, ulong? actual) => public static readonly string InvalidBlobProofSize = $"InvalidBlobProofSize: Cannot be more than {Ckzg.Ckzg.BytesPerProof}."; + public const string NotAllowedAuthorizationList = $"NotAllowedAuthorizationList: Only transactions with type {nameof(TxType.SetCode)} can have authorization_list."; + + public const string MissingAuthorizationList = "MissingAuthorizationList: Must be set."; + + public const string InvalidAuthoritySignature = "InvalidAuthoritySignature: Invalid signature in authorization list."; + public const string InvalidBlobCommitmentHash = "InvalidBlobCommitmentHash: Commitment hash does not match."; diff --git a/src/Nethermind/Nethermind.Consensus/Processing/RecoverSignature.cs b/src/Nethermind/Nethermind.Consensus/Processing/RecoverSignature.cs index 64b4dcd0cbb..1799871edd7 100644 --- a/src/Nethermind/Nethermind.Consensus/Processing/RecoverSignature.cs +++ b/src/Nethermind/Nethermind.Consensus/Processing/RecoverSignature.cs @@ -2,12 +2,14 @@ // SPDX-License-Identifier: LGPL-3.0-only using System; +using System.Linq; using System.Runtime.CompilerServices; using System.Threading.Tasks; using Nethermind.Core; using Nethermind.Core.Specs; using Nethermind.Crypto; using Nethermind.Logging; +using Nethermind.Serialization.Rlp; using Nethermind.TxPool; namespace Nethermind.Consensus.Processing @@ -60,7 +62,7 @@ public void RecoverData(Block block) // Don't access txPool in Parallel loop as increases contention foreach (Transaction tx in txs) { - if (!ShouldRecoverSender(tx)) + if (!ShouldRecoverSignatures(tx)) continue; Transaction? poolTx = null; @@ -84,22 +86,38 @@ public void RecoverData(Block block) { recoverFromEcdsa++; } + + if (tx.HasAuthorizationList) + { + for (int i = 0; i < tx.AuthorizationList.Length; i++) + { + if (poolTx.AuthorizationList[i].Authority is not null) + { + tx.AuthorizationList[i].Authority = poolTx.AuthorizationList[i].Authority; + } + else + { + recoverFromEcdsa++; + } + } + } } if (recoverFromEcdsa == 0) return; - bool useSignatureChainId = !_specProvider.GetSpec(block.Header).ValidateChainId; + IReleaseSpec releaseSpec = _specProvider.GetSpec(block.Header); + bool useSignatureChainId = !releaseSpec.ValidateChainId; if (recoverFromEcdsa > 3) { // Recover ecdsa in Parallel Parallel.For(0, txs.Length, i => { Transaction tx = txs[i]; - if (!ShouldRecoverSender(tx)) return; - - tx.SenderAddress = _ecdsa.RecoverAddress(tx, useSignatureChainId); + if (!ShouldRecoverSignatures(tx)) return; + tx.SenderAddress ??= _ecdsa.RecoverAddress(tx, useSignatureChainId); + RecoverAuthorities(tx); if (_logger.IsTrace) _logger.Trace($"Recovered {tx.SenderAddress} sender for {tx.Hash}"); }); } @@ -107,17 +125,41 @@ public void RecoverData(Block block) { foreach (Transaction tx in txs) { - if (!ShouldRecoverSender(tx)) continue; - - tx.SenderAddress = _ecdsa.RecoverAddress(tx, useSignatureChainId); + if (!ShouldRecoverSignatures(tx)) continue; + tx.SenderAddress ??= _ecdsa.RecoverAddress(tx, useSignatureChainId); + RecoverAuthorities(tx); if (_logger.IsTrace) _logger.Trace($"Recovered {tx.SenderAddress} sender for {tx.Hash}"); } } + + void RecoverAuthorities(Transaction tx) + { + if (!releaseSpec.IsAuthorizationListEnabled + || !tx.HasAuthorizationList) + { + return; + } + + if (tx.AuthorizationList.Length > 3) + { + Parallel.ForEach(tx.AuthorizationList.Where(t => t.Authority is null), (tuple) => + { + tuple.Authority = _ecdsa.RecoverAddress(tuple); + }); + } + else + { + foreach (AuthorizationTuple tuple in tx.AuthorizationList.AsSpan()) + { + tuple.Authority ??= _ecdsa.RecoverAddress(tuple); + } + } + } } [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static bool ShouldRecoverSender(Transaction tx) - => tx.IsSigned && tx.SenderAddress is null; + private static bool ShouldRecoverSignatures(Transaction tx) + => tx.IsSigned && (tx.SenderAddress is null || (tx.HasAuthorizationList && tx.AuthorizationList.Any(a => a.Authority is null))); } } diff --git a/src/Nethermind/Nethermind.Consensus/Requests/ConsensusRequestsProcessor.cs b/src/Nethermind/Nethermind.Consensus/Requests/ConsensusRequestsProcessor.cs index d44a33466a1..cbc155d1d9c 100644 --- a/src/Nethermind/Nethermind.Consensus/Requests/ConsensusRequestsProcessor.cs +++ b/src/Nethermind/Nethermind.Consensus/Requests/ConsensusRequestsProcessor.cs @@ -16,22 +16,24 @@ namespace Nethermind.Consensus.Requests; public class ConsensusRequestsProcessor(ITransactionProcessor transactionProcessor) : IConsensusRequestsProcessor { + private readonly ConsolidationRequestsProcessor _consolidationRequestsProcessor = new(transactionProcessor); private readonly WithdrawalRequestsProcessor _withdrawalRequestsProcessor = new(transactionProcessor); private readonly IDepositsProcessor _depositsProcessor = new DepositsProcessor(); public void ProcessRequests(Block block, IWorldState state, TxReceipt[] receipts, IReleaseSpec spec) { - if (spec.DepositsEnabled || spec.WithdrawalRequestsEnabled) - { - using ArrayPoolList requestsList = new(receipts.Length * 2); + if (!spec.RequestsEnabled) + return; - requestsList.AddRange(_depositsProcessor.ProcessDeposits(block, receipts, spec)); - requestsList.AddRange(_withdrawalRequestsProcessor.ReadWithdrawalRequests(block, state, spec)); + using ArrayPoolList requestsList = new(receipts.Length * 2); - ConsensusRequest[] requests = requestsList.ToArray(); - Hash256 root = new RequestsTrie(requests).RootHash; - block.Body.Requests = requests; - block.Header.RequestsRoot = root; - } + requestsList.AddRange(_depositsProcessor.ProcessDeposits(block, receipts, spec)); + requestsList.AddRange(_withdrawalRequestsProcessor.ReadRequests(block, state, spec)); + requestsList.AddRange(_consolidationRequestsProcessor.ReadRequests(block, state, spec)); + + ConsensusRequest[] requests = requestsList.ToArray(); + Hash256 root = new RequestsTrie(requests).RootHash; + block.Body.Requests = requests; + block.Header.RequestsRoot = root; } } diff --git a/src/Nethermind/Nethermind.Consensus/Requests/ConsolidationRequestProcessor.cs b/src/Nethermind/Nethermind.Consensus/Requests/ConsolidationRequestProcessor.cs new file mode 100644 index 00000000000..9d18024b080 --- /dev/null +++ b/src/Nethermind/Nethermind.Consensus/Requests/ConsolidationRequestProcessor.cs @@ -0,0 +1,44 @@ +// SPDX-FileCopyrightText: 2024 Demerzel Solutions Limited +// SPDX-License-Identifier: LGPL-3.0-only + +using System; +using System.Collections.Generic; +using Nethermind.Core; +using Nethermind.Core.ConsensusRequests; +using Nethermind.Core.Specs; +using Nethermind.Evm.TransactionProcessing; +using Nethermind.State; + +namespace Nethermind.Consensus.Requests; + +// https://eips.ethereum.org/EIPS/eip-7251#block-processing +public class ConsolidationRequestsProcessor(ITransactionProcessor transactionProcessor) : RequestProcessor(transactionProcessor), IRequestProcessor +{ + private const int SizeOfClass = 20 + 48 + 48; + + public IEnumerable ReadRequests(Block block, IWorldState state, IReleaseSpec spec) + { + return base.ReadRequests(block, state, spec, spec.Eip7251ContractAddress); + } + protected override bool IsEnabledInSpec(IReleaseSpec spec) + { + return spec.ConsolidationRequestsEnabled; + } + protected override IEnumerable ParseResult(Memory result) + { + int count = result.Length / SizeOfClass; + + for (int i = 0; i < count; ++i) + { + int offset = i * SizeOfClass; + ConsolidationRequest request = new() + { + SourceAddress = new Address(result.Slice(offset, 20).ToArray()), + SourcePubkey = result.Slice(offset + 20, 48), + TargetPubkey = result.Slice(offset + 68, 48) + }; + + yield return request; + } + } +} diff --git a/src/Nethermind/Nethermind.Consensus/Requests/IWithdrawalRequestsProcessor.cs b/src/Nethermind/Nethermind.Consensus/Requests/IRequestProcessor.cs similarity index 56% rename from src/Nethermind/Nethermind.Consensus/Requests/IWithdrawalRequestsProcessor.cs rename to src/Nethermind/Nethermind.Consensus/Requests/IRequestProcessor.cs index ba396ed1441..6f33453fdfb 100644 --- a/src/Nethermind/Nethermind.Consensus/Requests/IWithdrawalRequestsProcessor.cs +++ b/src/Nethermind/Nethermind.Consensus/Requests/IRequestProcessor.cs @@ -1,15 +1,14 @@ // SPDX-FileCopyrightText: 2024 Demerzel Solutions Limited // SPDX-License-Identifier: LGPL-3.0-only -using System.Collections.Generic; using Nethermind.Core; -using Nethermind.Core.ConsensusRequests; using Nethermind.Core.Specs; using Nethermind.State; +using System.Collections.Generic; namespace Nethermind.Consensus.Requests; -public interface IWithdrawalRequestsProcessor +public interface IRequestProcessor { - IEnumerable ReadWithdrawalRequests(Block block, IWorldState state, IReleaseSpec spec); + IEnumerable ReadRequests(Block block, IWorldState state, IReleaseSpec spec); } diff --git a/src/Nethermind/Nethermind.Consensus/Requests/RequestProcessor.cs b/src/Nethermind/Nethermind.Consensus/Requests/RequestProcessor.cs new file mode 100644 index 00000000000..c9c40c8dcd1 --- /dev/null +++ b/src/Nethermind/Nethermind.Consensus/Requests/RequestProcessor.cs @@ -0,0 +1,53 @@ +// SPDX-FileCopyrightText: 2024 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.Specs; +using Nethermind.Crypto; +using Nethermind.Evm; +using Nethermind.Evm.Tracing; +using Nethermind.Evm.TransactionProcessing; +using Nethermind.Int256; +using Nethermind.State; + +namespace Nethermind.Consensus.Requests; + +public abstract class RequestProcessor(ITransactionProcessor transactionProcessor) +{ + private const long GasLimit = 30_000_000L; + + public IEnumerable ReadRequests(Block block, IWorldState state, IReleaseSpec spec, Address contractAddress) + { + if (!IsEnabledInSpec(spec)) + return Array.Empty(); + + if (!state.AccountExists(contractAddress)) + return Array.Empty(); + + CallOutputTracer tracer = new(); + + Transaction? transaction = new() + { + Value = UInt256.Zero, + Data = Array.Empty(), + To = contractAddress, + SenderAddress = Address.SystemUser, + GasLimit = GasLimit, + GasPrice = UInt256.Zero, + }; + transaction.Hash = transaction.CalculateHash(); + + transactionProcessor.Execute(transaction, new BlockExecutionContext(block.Header), tracer); + var result = tracer.ReturnValue; + if (result == null || result.Length == 0) + return Enumerable.Empty(); + return ParseResult(tracer.ReturnValue); + } + + + protected abstract bool IsEnabledInSpec(IReleaseSpec spec); + protected abstract IEnumerable ParseResult(Memory result); +} diff --git a/src/Nethermind/Nethermind.Consensus/Requests/WithdrawalRequestsProcessor.cs b/src/Nethermind/Nethermind.Consensus/Requests/WithdrawalRequestsProcessor.cs index 564f2573f5d..86483add1a0 100644 --- a/src/Nethermind/Nethermind.Consensus/Requests/WithdrawalRequestsProcessor.cs +++ b/src/Nethermind/Nethermind.Consensus/Requests/WithdrawalRequestsProcessor.cs @@ -4,68 +4,43 @@ using System; using System.Buffers.Binary; using System.Collections.Generic; -using System.Linq; using Nethermind.Core; using Nethermind.Core.ConsensusRequests; -using Nethermind.Core.Crypto; using Nethermind.Core.Extensions; using Nethermind.Core.Specs; -using Nethermind.Crypto; -using Nethermind.Evm; -using Nethermind.Evm.Tracing; using Nethermind.Evm.TransactionProcessing; -using Nethermind.Int256; using Nethermind.State; namespace Nethermind.Consensus.Requests; // https://eips.ethereum.org/EIPS/eip-7002#block-processing -public class WithdrawalRequestsProcessor(ITransactionProcessor transactionProcessor) : IWithdrawalRequestsProcessor +public class WithdrawalRequestsProcessor(ITransactionProcessor transactionProcessor) : RequestProcessor(transactionProcessor), IRequestProcessor { - private const long GasLimit = 30_000_000L; private const int SizeOfClass = 20 + 48 + 8; - public IEnumerable ReadWithdrawalRequests(Block block, IWorldState state, IReleaseSpec spec) + public IEnumerable ReadRequests(Block block, IWorldState state, IReleaseSpec spec) { - if (!spec.IsEip7002Enabled || !state.AccountExists(spec.Eip7002ContractAddress)) - { - yield break; - } + return ReadRequests(block, state, spec, spec.Eip7002ContractAddress); + } - byte[]? result = ExecuteTransaction(block, spec); - if (result?.Length > 0) - { - int count = result.Length / SizeOfClass; - for (int i = 0; i < count; ++i) - { - Memory memory = result.AsMemory(i * SizeOfClass, SizeOfClass); - WithdrawalRequest request = new() - { - SourceAddress = new Address(memory.Slice(0, 20).AsArray()), - ValidatorPubkey = memory.Slice(20, 48), - Amount = BinaryPrimitives.ReadUInt64BigEndian(memory.Slice(68, 8).Span) - }; - yield return request; - } - } + protected override bool IsEnabledInSpec(IReleaseSpec spec) + { + return spec.WithdrawalRequestsEnabled; } - private byte[]? ExecuteTransaction(Block block, IReleaseSpec spec) + protected override IEnumerable ParseResult(Memory result) { - CallOutputTracer tracer = new(); - Transaction transaction = new() + int count = result.Length / SizeOfClass; + for (int i = 0; i < count; ++i) { - Value = UInt256.Zero, - Data = Array.Empty(), - To = spec.Eip7002ContractAddress, - SenderAddress = Address.SystemUser, - GasLimit = GasLimit, - GasPrice = UInt256.Zero, - }; - transaction.Hash = transaction.CalculateHash(); - - transactionProcessor.Execute(transaction, new BlockExecutionContext(block.Header), tracer); - - return tracer.ReturnValue; + int offset = i * SizeOfClass; + WithdrawalRequest request = new() + { + SourceAddress = new Address(result.Slice(offset, 20).AsArray()), + ValidatorPubkey = result.Slice(offset + 20, 48), + Amount = BinaryPrimitives.ReadUInt64BigEndian(result.Slice(offset + 68, 8).Span) + }; + yield return request; + } } } diff --git a/src/Nethermind/Nethermind.Consensus/Validators/BlockValidator.cs b/src/Nethermind/Nethermind.Consensus/Validators/BlockValidator.cs index d5dcd925af1..1e3aa152eed 100644 --- a/src/Nethermind/Nethermind.Consensus/Validators/BlockValidator.cs +++ b/src/Nethermind/Nethermind.Consensus/Validators/BlockValidator.cs @@ -288,7 +288,7 @@ public bool ValidateRequests(Block block, out string? error) => private bool ValidateRequests(Block block, IReleaseSpec spec, out string? error) { - if (spec.ConsensusRequestsEnabled && block.Requests is null) + if (spec.RequestsEnabled && block.Requests is null) { error = BlockErrorMessages.MissingRequests; @@ -297,7 +297,7 @@ private bool ValidateRequests(Block block, IReleaseSpec spec, out string? error) return false; } - if (!spec.ConsensusRequestsEnabled && block.Requests is not null) + if (!spec.RequestsEnabled && block.Requests is not null) { error = BlockErrorMessages.RequestsNotEnabled; diff --git a/src/Nethermind/Nethermind.Consensus/Validators/TxValidator.cs b/src/Nethermind/Nethermind.Consensus/Validators/TxValidator.cs index 5207135b109..070ab579360 100644 --- a/src/Nethermind/Nethermind.Consensus/Validators/TxValidator.cs +++ b/src/Nethermind/Nethermind.Consensus/Validators/TxValidator.cs @@ -11,6 +11,7 @@ using Nethermind.Crypto; using Nethermind.Evm; using Nethermind.Int256; +using System.Linq; namespace Nethermind.Consensus.Validators; @@ -25,6 +26,7 @@ public TxValidator(ulong chainId) new LegacySignatureTxValidator(chainId), ContractSizeTxValidator.Instance, NonBlobFieldsTxValidator.Instance, + NonSetCodeFieldsTxValidator.Instance ])); RegisterValidator(TxType.AccessList, new CompositeTxValidator([ new ReleaseSpecTxValidator(static spec => spec.IsEip2930Enabled), @@ -33,6 +35,7 @@ public TxValidator(ulong chainId) new ExpectedChainIdTxValidator(chainId), ContractSizeTxValidator.Instance, NonBlobFieldsTxValidator.Instance, + NonSetCodeFieldsTxValidator.Instance ])); RegisterValidator(TxType.EIP1559, new CompositeTxValidator([ new ReleaseSpecTxValidator(static spec => spec.IsEip1559Enabled), @@ -42,6 +45,7 @@ public TxValidator(ulong chainId) GasFieldsTxValidator.Instance, ContractSizeTxValidator.Instance, NonBlobFieldsTxValidator.Instance, + NonSetCodeFieldsTxValidator.Instance ])); RegisterValidator(TxType.Blob, new CompositeTxValidator([ new ReleaseSpecTxValidator(static spec => spec.IsEip4844Enabled), @@ -51,7 +55,19 @@ public TxValidator(ulong chainId) GasFieldsTxValidator.Instance, ContractSizeTxValidator.Instance, BlobFieldsTxValidator.Instance, - MempoolBlobTxValidator.Instance + MempoolBlobTxValidator.Instance, + NonSetCodeFieldsTxValidator.Instance + ])); + RegisterValidator(TxType.SetCode, new CompositeTxValidator([ + new ReleaseSpecTxValidator(static spec => spec.IsEip7702Enabled), + IntrinsicGasTxValidator.Instance, + SignatureTxValidator.Instance, + new ExpectedChainIdTxValidator(chainId), + GasFieldsTxValidator.Instance, + ContractSizeTxValidator.Instance, + NonBlobFieldsTxValidator.Instance, + NoContractCreationTxValidator.Instance, + AuthorizationListTxValidator.Instance, ])); } @@ -150,6 +166,18 @@ private NonBlobFieldsTxValidator() { } }; } +public sealed class NonSetCodeFieldsTxValidator : ITxValidator +{ + public static readonly NonSetCodeFieldsTxValidator Instance = new(); + private NonSetCodeFieldsTxValidator() { } + + public ValidationResult IsWellFormed(Transaction transaction, IReleaseSpec releaseSpec) => transaction switch + { + { AuthorizationList: not null } => TxErrorMessages.NotAllowedAuthorizationList, + _ => ValidationResult.Success + }; +} + public sealed class BlobFieldsTxValidator : ITxValidator { public static readonly BlobFieldsTxValidator Instance = new(); @@ -282,3 +310,33 @@ public sealed class SignatureTxValidator : BaseSignatureTxValidator public static readonly SignatureTxValidator Instance = new(); private SignatureTxValidator() { } } + +public sealed class NoContractCreationTxValidator : ITxValidator +{ + public static readonly NoContractCreationTxValidator Instance = new(); + private NoContractCreationTxValidator() { } + public ValidationResult IsWellFormed(Transaction transaction, IReleaseSpec releaseSpec) => + transaction.IsContractCreation ? TxErrorMessages.NotAllowedCreateTransaction : ValidationResult.Success; +} + +public sealed class AuthorizationListTxValidator : ITxValidator +{ + public static readonly AuthorizationListTxValidator Instance = new(); + private AuthorizationListTxValidator() { } + + public ValidationResult IsWellFormed(Transaction transaction, IReleaseSpec releaseSpec) => + transaction.AuthorizationList switch + { + null or { Length: 0 } => TxErrorMessages.MissingAuthorizationList, + var authorizationList when authorizationList.Any(a => !ValidateAuthoritySignature(a.AuthoritySignature)) => + TxErrorMessages.InvalidAuthoritySignature, + _ => ValidationResult.Success + }; + + private bool ValidateAuthoritySignature(Signature signature) + { + UInt256 sValue = new(signature.SAsSpan, isBigEndian: true); + + return sValue < Secp256K1Curve.HalfNPlusOne && signature.RecoveryId is 0 or 1; + } +} diff --git a/src/Nethermind/Nethermind.Core.Test/Blockchain/TestBlockchain.cs b/src/Nethermind/Nethermind.Core.Test/Blockchain/TestBlockchain.cs index 3558e7b8461..2af6892ee13 100644 --- a/src/Nethermind/Nethermind.Core.Test/Blockchain/TestBlockchain.cs +++ b/src/Nethermind/Nethermind.Core.Test/Blockchain/TestBlockchain.cs @@ -38,6 +38,7 @@ using Nethermind.Specs.Test; using Nethermind.State; using Nethermind.State.Repositories; +using Nethermind.Synchronization; using Nethermind.Trie; using Nethermind.Trie.Pruning; using Nethermind.TxPool; @@ -181,15 +182,16 @@ protected virtual async Task Build(ISpecProvider? specProvider = ReadOnlyState = new ChainHeadReadOnlyStateProvider(BlockTree, StateReader); TransactionComparerProvider = new TransactionComparerProvider(SpecProvider, BlockTree); - TxPool = CreateTxPool(); + CodeInfoRepository codeInfoRepository = new(); + TxPool = CreateTxPool(codeInfoRepository); IChainHeadInfoProvider chainHeadInfoProvider = - new ChainHeadInfoProvider(SpecProvider, BlockTree, StateReader); + new ChainHeadInfoProvider(SpecProvider, BlockTree, StateReader, codeInfoRepository); - NonceManager = new NonceManager(chainHeadInfoProvider.AccountStateProvider); + NonceManager = new NonceManager(chainHeadInfoProvider.ReadOnlyStateProvider); _trieStoreWatcher = new TrieStoreBoundaryWatcher(WorldStateManager, BlockTree, LogManager); - CodeInfoRepository codeInfoRepository = new(); + ReceiptStorage = new InMemoryReceiptStorage(blockTree: BlockTree); VirtualMachine virtualMachine = new(new BlockhashProvider(BlockTree, SpecProvider, State, LogManager), SpecProvider, codeInfoRepository, LogManager); TxProcessor = new TransactionProcessor(SpecProvider, State, virtualMachine, codeInfoRepository, LogManager); @@ -315,12 +317,12 @@ protected virtual IBlockProducerRunner CreateBlockProducerRunner() public virtual ILogManager LogManager { get; set; } = LimboLogs.Instance; - protected virtual TxPool.TxPool CreateTxPool() => + protected virtual TxPool.TxPool CreateTxPool(CodeInfoRepository codeInfoRepository) => new( EthereumEcdsa, new BlobTxStorage(), - new ChainHeadInfoProvider(new FixedForkActivationChainHeadSpecProvider(SpecProvider), BlockTree, ReadOnlyState), - new TxPoolConfig() { BlobsSupport = BlobsSupportMode.InMemory }, + new ChainHeadInfoProvider(new FixedForkActivationChainHeadSpecProvider(SpecProvider), BlockTree, ReadOnlyState, codeInfoRepository), + new TxPoolConfig { BlobsSupport = BlobsSupportMode.InMemory }, new TxValidator(SpecProvider.ChainId), LogManager, TransactionComparerProvider.GetDefaultComparer()); @@ -362,8 +364,11 @@ protected virtual Block GetGenesisBlock() genesisBlockBuilder.WithParentBeaconBlockRoot(Keccak.Zero); } - if (SpecProvider.GenesisSpec.ConsensusRequestsEnabled) + if (SpecProvider.GenesisSpec.RequestsEnabled) + { genesisBlockBuilder.WithConsensusRequests(0); + } + genesisBlockBuilder.WithStateRoot(State.StateRoot); return genesisBlockBuilder.TestObject; diff --git a/src/Nethermind/Nethermind.Core.Test/Builders/BlockBuilder.cs b/src/Nethermind/Nethermind.Core.Test/Builders/BlockBuilder.cs index a43039ef8af..4b5e591949b 100644 --- a/src/Nethermind/Nethermind.Core.Test/Builders/BlockBuilder.cs +++ b/src/Nethermind/Nethermind.Core.Test/Builders/BlockBuilder.cs @@ -281,7 +281,7 @@ public BlockBuilder WithConsensusRequests(int count) var consensusRequests = new ConsensusRequest[count]; for (var i = 0; i < count; i++) - consensusRequests[i] = new(); + consensusRequests[i] = new Deposit(); return WithConsensusRequests(consensusRequests); } diff --git a/src/Nethermind/Nethermind.Core.Test/Builders/ConsolidationRequestBuilder.cs b/src/Nethermind/Nethermind.Core.Test/Builders/ConsolidationRequestBuilder.cs new file mode 100644 index 00000000000..63628ef305b --- /dev/null +++ b/src/Nethermind/Nethermind.Core.Test/Builders/ConsolidationRequestBuilder.cs @@ -0,0 +1,32 @@ +// SPDX-FileCopyrightText: 2022 Demerzel Solutions Limited +// SPDX-License-Identifier: LGPL-3.0-only + +namespace Nethermind.Core.Test.Builders; +using Nethermind.Core.ConsensusRequests; + +public class ConsolidationRequestBuilder : BuilderBase +{ + public ConsolidationRequestBuilder() => TestObject = new(); + + public ConsolidationRequestBuilder WithSourceAddress(Address sourceAddress) + { + TestObject.SourceAddress = sourceAddress; + + return this; + } + + public ConsolidationRequestBuilder WithSourcePubkey(byte[] SourcePubkey) + { + TestObject.SourcePubkey = SourcePubkey; + + return this; + } + + public ConsolidationRequestBuilder WithTargetPubkey(byte[] TargetPubkey) + { + TestObject.TargetPubkey = TargetPubkey; + + return this; + } + +} diff --git a/src/Nethermind/Nethermind.Core.Test/Builders/TestItem.cs b/src/Nethermind/Nethermind.Core.Test/Builders/TestItem.cs index bae4135790c..ac8cf8e0313 100644 --- a/src/Nethermind/Nethermind.Core.Test/Builders/TestItem.cs +++ b/src/Nethermind/Nethermind.Core.Test/Builders/TestItem.cs @@ -115,6 +115,13 @@ public static Hash256 KeccakFromNumber(int i) public static WithdrawalRequest WithdrawalRequestE = new() { SourceAddress = AddressE, ValidatorPubkey = PublicKeyE.Bytes }; public static WithdrawalRequest WithdrawalRequestF = new() { SourceAddress = AddressF, ValidatorPubkey = PublicKeyF.Bytes }; + public static ConsolidationRequest ConsolidationRequestA = new() { SourceAddress = AddressA, SourcePubkey = PublicKeyA.Bytes, TargetPubkey = PublicKeyB.Bytes }; + public static ConsolidationRequest ConsolidationRequestB = new() { SourceAddress = AddressB, SourcePubkey = PublicKeyB.Bytes, TargetPubkey = PublicKeyC.Bytes }; + public static ConsolidationRequest ConsolidationRequestC = new() { SourceAddress = AddressC, SourcePubkey = PublicKeyC.Bytes, TargetPubkey = PublicKeyD.Bytes }; + public static ConsolidationRequest ConsolidationRequestD = new() { SourceAddress = AddressD, SourcePubkey = PublicKeyD.Bytes, TargetPubkey = PublicKeyE.Bytes }; + public static ConsolidationRequest ConsolidationRequestE = new() { SourceAddress = AddressE, SourcePubkey = PublicKeyE.Bytes, TargetPubkey = PublicKeyF.Bytes }; + public static ConsolidationRequest ConsolidationRequestF = new() { SourceAddress = AddressF, SourcePubkey = PublicKeyF.Bytes, TargetPubkey = PublicKeyA.Bytes }; + public static IPEndPoint IPEndPointA = IPEndPoint.Parse("10.0.0.1"); public static IPEndPoint IPEndPointB = IPEndPoint.Parse("10.0.0.2"); public static IPEndPoint IPEndPointC = IPEndPoint.Parse("10.0.0.3"); diff --git a/src/Nethermind/Nethermind.Core.Test/Builders/TransactionBuilder.cs b/src/Nethermind/Nethermind.Core.Test/Builders/TransactionBuilder.cs index cb8c9e984f3..cf81f10a348 100644 --- a/src/Nethermind/Nethermind.Core.Test/Builders/TransactionBuilder.cs +++ b/src/Nethermind/Nethermind.Core.Test/Builders/TransactionBuilder.cs @@ -7,7 +7,6 @@ using Nethermind.Core.Eip2930; using Nethermind.Crypto; using Nethermind.Int256; -using Nethermind.Logging; namespace Nethermind.Core.Test.Builders { @@ -218,6 +217,22 @@ public TransactionBuilder WithShardBlobTxTypeAndFields(int blobCount = 1, boo return this; } + public TransactionBuilder WithAuthorizationCodeIfAuthorizationListTx() + { + return TestObjectInternal.Type == TxType.SetCode ? WithAuthorizationCode(new AuthorizationTuple(0, Address.Zero, 0, new Signature(new byte[64], 0))) : this; + } + + public TransactionBuilder WithAuthorizationCode(AuthorizationTuple authTuple) + { + TestObjectInternal.AuthorizationList = TestObjectInternal.AuthorizationList is not null ? [.. TestObjectInternal.AuthorizationList, authTuple] : [authTuple]; + return this; + } + public TransactionBuilder WithAuthorizationCode(AuthorizationTuple[] authList) + { + TestObjectInternal.AuthorizationList = authList; + return this; + } + public TransactionBuilder With(Action anyChange) { anyChange(TestObjectInternal); @@ -282,5 +297,11 @@ public TransactionBuilder WithIsServiceTransaction(bool isServiceTransaction) TestObjectInternal.IsServiceTransaction = isServiceTransaction; return this; } + + public TransactionBuilder From(T item) + { + TestObjectInternal = item; + return this; + } } } diff --git a/src/Nethermind/Nethermind.Core.Test/Crypto/EthereumEcdsaTests.cs b/src/Nethermind/Nethermind.Core.Test/Crypto/EthereumEcdsaTests.cs index a11dc55dd69..64d98d18dea 100644 --- a/src/Nethermind/Nethermind.Core.Test/Crypto/EthereumEcdsaTests.cs +++ b/src/Nethermind/Nethermind.Core.Test/Crypto/EthereumEcdsaTests.cs @@ -1,10 +1,15 @@ // SPDX-FileCopyrightText: 2022 Demerzel Solutions Limited // SPDX-License-Identifier: LGPL-3.0-only +using System; using System.Collections.Generic; +using System.Net; +using Nethermind.Core.Crypto; using Nethermind.Core.Test.Builders; using Nethermind.Crypto; +using Nethermind.Int256; using Nethermind.Logging; +using Nethermind.Serialization.Rlp; using NUnit.Framework; namespace Nethermind.Core.Test.Crypto @@ -73,5 +78,23 @@ public void Sign_generic_network() Address? address = ecdsa.RecoverAddress(tx); Assert.That(address, Is.EqualTo(key.Address)); } + + [Test] + [Repeat(3)] + public void RecoverAddress_AuthorizationTupleOfDifferentSize_RecoversAddressCorrectly() + { + PrivateKey signer = Build.A.PrivateKey.TestObject; + AuthorizationTuple authorizationTuple = new EthereumEcdsa(BlockchainIds.GenericNonRealNetwork) + .Sign(signer, + TestContext.CurrentContext.Random.NextULong(), + Build.A.Address.TestObjectInternal, + TestContext.CurrentContext.Random.NextULong()); + + EthereumEcdsa ecdsa = new(BlockchainIds.GenericNonRealNetwork); + + Address? authority = ecdsa.RecoverAddress(authorizationTuple); + + Assert.That(authority, Is.EqualTo(signer.Address)); + } } } diff --git a/src/Nethermind/Nethermind.Core.Test/Encoding/AuthorizationTupleDecoderTests.cs b/src/Nethermind/Nethermind.Core.Test/Encoding/AuthorizationTupleDecoderTests.cs new file mode 100644 index 00000000000..0d243601940 --- /dev/null +++ b/src/Nethermind/Nethermind.Core.Test/Encoding/AuthorizationTupleDecoderTests.cs @@ -0,0 +1,80 @@ +// SPDX-FileCopyrightText: 2024 Demerzel Solutions Limited +// SPDX-License-Identifier: LGPL-3.0-only + +using FluentAssertions; +using Nethermind.Core.Crypto; +using Nethermind.Core.Test.Builders; +using Nethermind.Int256; +using Nethermind.Serialization.Rlp; +using NSubstitute.ExceptionExtensions; +using NUnit.Framework; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Nethermind.Core.Test.Encoding; + +[TestFixture] +public class AuthorizationTupleDecoderTests +{ + public static IEnumerable AuthorizationTupleEncodeCases() + { + yield return new AuthorizationTuple(0, Address.Zero, 0, new Signature(new byte[64], 0)); + yield return new AuthorizationTuple(0, Address.Zero, 0, new Signature(new byte[64], 0)); + yield return new AuthorizationTuple( + ulong.MaxValue, + new Address(Enumerable.Range(0, 20).Select(i => (byte)0xff).ToArray()), + ulong.MaxValue, + new Signature(Enumerable.Range(0, 64).Select(i => (byte)0xff).ToArray(), 1)); + } + + [TestCaseSource(nameof(AuthorizationTupleEncodeCases))] + public void Encode_TupleHasValues_TupleCanBeDecodedToEquivalentTuple(AuthorizationTuple item) + { + AuthorizationTupleDecoder sut = new(); + + RlpStream result = sut.Encode(item); + result.Position = 0; + + sut.Decode(result).Should().BeEquivalentTo(item); + } + + [Test] + public void DecodeValueDecoderContext_CodeAddressIsNull_ThrowsRlpException() + { + RlpStream stream = TupleRlpStreamWithNull(); + + AuthorizationTupleDecoder sut = new(); + Assert.That(() => + { + Rlp.ValueDecoderContext decoderContext = new Rlp.ValueDecoderContext(stream.Data); + sut.Decode(ref decoderContext, RlpBehaviors.None); + } + , Throws.TypeOf()); + } + + private static RlpStream TupleRlpStreamWithNull() + { + Address? codeAddress = null; + Signature sig = new(new byte[64], 0); + int length = + +Rlp.LengthOf(1) + + Rlp.LengthOf(codeAddress) + + Rlp.LengthOf(0) + + Rlp.LengthOf(sig.RecoveryId) + + Rlp.LengthOf(sig.R) + + Rlp.LengthOf(sig.S); + RlpStream stream = new RlpStream(Rlp.LengthOfSequence(length)); + stream.StartSequence(length); + stream.Encode(1); + stream.Encode(codeAddress); + stream.Encode(0); + stream.Encode(sig.RecoveryId); + stream.Encode(sig.R); + stream.Encode(sig.S); + stream.Position = 0; + return stream; + } +} diff --git a/src/Nethermind/Nethermind.Core.Test/Encoding/ConsensusRequestDecoderTests.cs b/src/Nethermind/Nethermind.Core.Test/Encoding/ConsensusRequestDecoderTests.cs index 3fc4c35588c..c311fb36a7f 100644 --- a/src/Nethermind/Nethermind.Core.Test/Encoding/ConsensusRequestDecoderTests.cs +++ b/src/Nethermind/Nethermind.Core.Test/Encoding/ConsensusRequestDecoderTests.cs @@ -50,6 +50,26 @@ public void Roundtrip_withdrawalRequest() decoded.Should().BeEquivalentTo(withdrawalRequest); } + [Test] + public void Roundtrip_consolidationRequest() + { + byte[] SourcePubkey = new byte[48]; + SourcePubkey[11] = 11; + byte[] TargetPubkey = new byte[48]; + TargetPubkey[22] = 22; + ConsensusRequest consolidationRequest = new ConsolidationRequest() + { + SourceAddress = TestItem.AddressA, + SourcePubkey = SourcePubkey, + TargetPubkey = TargetPubkey + }; + + byte[] rlp = Rlp.Encode(consolidationRequest).Bytes; + ConsensusRequest decoded = Rlp.Decode(rlp); + + decoded.Should().BeEquivalentTo(consolidationRequest); + } + [Test] public void Should_decode_deposit_with_ValueDecoderContext() { @@ -62,7 +82,7 @@ public void Should_decode_deposit_with_ValueDecoderContext() Amount = int.MaxValue }; RlpStream stream = new(1024); - ConsensusRequestDecoder codec = new(); + ConsensusRequestDecoder codec = ConsensusRequestDecoder.Instance; codec.Encode(stream, deposit); @@ -82,7 +102,7 @@ public void Should_decode_withdrawalRequest_with_ValueDecoderContext() Amount = int.MaxValue }; RlpStream stream = new(1024); - ConsensusRequestDecoder codec = new(); + ConsensusRequestDecoder codec = ConsensusRequestDecoder.Instance; codec.Encode(stream, withdrawalRequest); @@ -93,7 +113,27 @@ public void Should_decode_withdrawalRequest_with_ValueDecoderContext() } [Test] - public void Should_encode_deposit_same_for_Rlp_Encode_and_DepositDecoder_Encode() + public void Should_decode_consolidationRequest_with_ValueDecoderContext() + { + ConsensusRequest consolidationRequest = new ConsolidationRequest() + { + SourceAddress = TestItem.AddressA, + SourcePubkey = KeccakTests.KeccakOfAnEmptyString.ToBytes(), + TargetPubkey = KeccakTests.KeccakOfAnEmptyString.ToBytes() + }; + RlpStream stream = new(1024); + ConsensusRequestDecoder codec = ConsensusRequestDecoder.Instance; + + codec.Encode(stream, consolidationRequest); + + Rlp.ValueDecoderContext decoderContext = new(stream.Data.AsSpan()); + ConsolidationRequest? decoded = (ConsolidationRequest?)codec.Decode(ref decoderContext); + + decoded.Should().BeEquivalentTo(consolidationRequest); + } + + [Test] + public void Should_encode_deposit_same_for_Rlp_Encode_and_ConsensusRequestDecoder_Encode() { ConsensusRequest deposit = new Deposit() { @@ -103,14 +143,14 @@ public void Should_encode_deposit_same_for_Rlp_Encode_and_DepositDecoder_Encode( WithdrawalCredentials = KeccakTests.KeccakOfAnEmptyString.ToBytes(), Amount = int.MaxValue }; - byte[] rlp1 = new ConsensusRequestDecoder().Encode(deposit).Bytes; + byte[] rlp1 = ConsensusRequestDecoder.Instance.Encode(deposit).Bytes; byte[] rlp2 = Rlp.Encode(deposit).Bytes; rlp1.Should().BeEquivalentTo(rlp2); } [Test] - public void Should_encode_withdrawalRequest_same_for_Rlp_Encode_and_DepositDecoder_Encode() + public void Should_encode_withdrawalRequest_same_for_Rlp_Encode_and_ConsensusRequestDecoder_Encode() { ConsensusRequest withdrawalRequest = new WithdrawalRequest() { @@ -118,12 +158,27 @@ public void Should_encode_withdrawalRequest_same_for_Rlp_Encode_and_DepositDecod ValidatorPubkey = KeccakTests.KeccakOfAnEmptyString.ToBytes(), Amount = int.MaxValue }; - byte[] rlp1 = new ConsensusRequestDecoder().Encode(withdrawalRequest).Bytes; + byte[] rlp1 = ConsensusRequestDecoder.Instance.Encode(withdrawalRequest).Bytes; byte[] rlp2 = Rlp.Encode(withdrawalRequest).Bytes; rlp1.Should().BeEquivalentTo(rlp2); } + [Test] + public void Should_encode_consolidationRequest_same_for_Rlp_Encode_and_ConsensusRequestDecoder_Encode() + { + ConsensusRequest consolidationRequest = new ConsolidationRequest() + { + SourceAddress = TestItem.AddressA, + SourcePubkey = KeccakTests.KeccakOfAnEmptyString.ToBytes(), + TargetPubkey = KeccakTests.KeccakOfAnEmptyString.ToBytes() + }; + byte[] rlp1 = ConsensusRequestDecoder.Instance.Encode(consolidationRequest).Bytes; + byte[] rlp2 = Rlp.Encode(consolidationRequest).Bytes; + + rlp1.Should().BeEquivalentTo(rlp2); + } + [Test] public void Should_encode_ConsensusRequests_Array() { @@ -142,12 +197,18 @@ public void Should_encode_ConsensusRequests_Array() SourceAddress = TestItem.AddressA, ValidatorPubkey = KeccakTests.KeccakOfAnEmptyString.ToBytes(), Amount = int.MaxValue + }, + new ConsolidationRequest() + { + SourceAddress = TestItem.AddressA, + SourcePubkey = KeccakTests.KeccakOfAnEmptyString.ToBytes(), + TargetPubkey = KeccakTests.KeccakOfAnEmptyString.ToBytes() } }; byte[] rlp = Rlp.Encode(requests).Bytes; RlpStream rlpStream = new(rlp); - ConsensusRequest[] decoded = Rlp.DecodeArray(rlpStream, new ConsensusRequestDecoder()); + ConsensusRequest[] decoded = Rlp.DecodeArray(rlpStream, ConsensusRequestDecoder.Instance); decoded.Should().BeEquivalentTo(requests); } } diff --git a/src/Nethermind/Nethermind.Core.Test/Encoding/ShardBlobTxDecoderTests.cs b/src/Nethermind/Nethermind.Core.Test/Encoding/ShardBlobTxDecoderTests.cs index bf70a7b46f9..ced26306e3a 100644 --- a/src/Nethermind/Nethermind.Core.Test/Encoding/ShardBlobTxDecoderTests.cs +++ b/src/Nethermind/Nethermind.Core.Test/Encoding/ShardBlobTxDecoderTests.cs @@ -8,6 +8,7 @@ using FluentAssertions; using Nethermind.Core.Crypto; using Nethermind.Core.Extensions; +using Nethermind.Core.Test.Builders; using Nethermind.Crypto; using Nethermind.Logging; using Nethermind.Serialization.Rlp; @@ -24,7 +25,7 @@ public partial class ShardBlobTxDecoderTests public static Task SetUp() => KzgPolynomialCommitments.InitializeAsync(); public static IEnumerable<(Transaction, string)> TestCaseSource() => - TxDecoderTests.TestObjectsSource().Select(tos => (tos.Item1 + TxDecoderTests.TestCaseSource().Select(tos => (Build.A.Transaction.From(tos.Item1) .WithChainId(TestBlockchainIds.ChainId) .WithShardBlobTxTypeAndFields(2, false) .SignedAndResolved() diff --git a/src/Nethermind/Nethermind.Core/AccountStateProviderExtensions.cs b/src/Nethermind/Nethermind.Core/AccountStateProviderExtensions.cs index 45a8c09be5f..b07d3e9ebdd 100644 --- a/src/Nethermind/Nethermind.Core/AccountStateProviderExtensions.cs +++ b/src/Nethermind/Nethermind.Core/AccountStateProviderExtensions.cs @@ -2,6 +2,7 @@ // SPDX-License-Identifier: LGPL-3.0-only using Nethermind.Core.Specs; +using System; namespace Nethermind.Core { @@ -9,8 +10,5 @@ public static class AccountStateProviderExtensions { public static bool HasCode(this IAccountStateProvider stateProvider, Address address) => stateProvider.TryGetAccount(address, out AccountStruct account) && account.HasCode; - - public static bool IsInvalidContractSender(this IAccountStateProvider stateProvider, IReleaseSpec spec, Address address) => - spec.IsEip3607Enabled && stateProvider.HasCode(address); } } diff --git a/src/Nethermind/Nethermind.Core/Address.cs b/src/Nethermind/Nethermind.Core/Address.cs index f09c9e5b76d..01507842989 100644 --- a/src/Nethermind/Nethermind.Core/Address.cs +++ b/src/Nethermind/Nethermind.Core/Address.cs @@ -3,6 +3,7 @@ using System; using System.ComponentModel; +using System.Diagnostics; using System.Globalization; using System.Numerics; using System.Runtime.CompilerServices; @@ -19,6 +20,7 @@ namespace Nethermind.Core { [JsonConverter(typeof(AddressConverter))] [TypeConverter(typeof(AddressTypeConverter))] + [DebuggerDisplay("{ToString()}")] public class Address : IEquatable
, IComparable
{ public const int Size = 20; @@ -26,6 +28,8 @@ public class Address : IEquatable
, IComparable
private const int PrefixedHexCharsCount = 2 + HexCharsCount; // 0x5a4eab120fb44eb6684e5e32785702ff45ea344d public static Address Zero { get; } = new(new byte[Size]); + public static Address MaxValue { get; } = new("0xffffffffffffffffffffffffffffffffffffffff"); + public const string SystemUserHex = "0xfffffffffffffffffffffffffffffffffffffffe"; public static Address SystemUser { get; } = new(SystemUserHex); diff --git a/src/Nethermind/Nethermind.Core/AuthorizationTuple.cs b/src/Nethermind/Nethermind.Core/AuthorizationTuple.cs new file mode 100644 index 00000000000..69fd3b53768 --- /dev/null +++ b/src/Nethermind/Nethermind.Core/AuthorizationTuple.cs @@ -0,0 +1,36 @@ +// SPDX-FileCopyrightText: 2024 Demerzel Solutions Limited +// SPDX-License-Identifier: LGPL-3.0-only + +using Nethermind.Core.Crypto; +using System; + +namespace Nethermind.Core; +public class AuthorizationTuple( + ulong chainId, + Address codeAddress, + ulong nonce, + Signature sig, + Address? authority = null) +{ + public AuthorizationTuple( + ulong chainId, + Address codeAddress, + ulong nonce, + ulong yParity, + byte[] r, + byte[] s, + Address? authority = null) : this(chainId, codeAddress, nonce, new Signature(r, s, yParity + Signature.VOffset), authority) + { } + + public ulong ChainId { get; } = chainId; + public Address CodeAddress { get; protected set; } = codeAddress; + public ulong Nonce { get; } = nonce; + public Signature AuthoritySignature { get; protected set; } = sig; + + /// + /// may be recovered at a later point. + /// + public Address? Authority { get; set; } = authority; + + public override string ToString() => $"Delegation authorization from {Authority} to {CodeAddress} on chain {ChainId} with Nonce {Nonce}"; +} diff --git a/src/Nethermind/Nethermind.Core/ConsensusRequests/ConsensusRequest.cs b/src/Nethermind/Nethermind.Core/ConsensusRequests/ConsensusRequest.cs index b5cbf9f8e34..e9e5f6d38e1 100644 --- a/src/Nethermind/Nethermind.Core/ConsensusRequests/ConsensusRequest.cs +++ b/src/Nethermind/Nethermind.Core/ConsensusRequests/ConsensusRequest.cs @@ -10,39 +10,23 @@ namespace Nethermind.Core.ConsensusRequests; public enum ConsensusRequestsType : byte { Deposit = 0, - WithdrawalRequest = 1 + WithdrawalRequest = 1, + ConsolidationRequest = 2 } -public class ConsensusRequest +public abstract class ConsensusRequest { [JsonIgnore] public ConsensusRequestsType Type { get; protected set; } - - [JsonIgnore] - public ulong AmountField { get; protected set; } - - [JsonIgnore] - public Address? SourceAddressField { get; protected set; } - - [JsonIgnore] - public Memory? PubKeyField { get; set; } - - [JsonIgnore] - public byte[]? WithdrawalCredentialsField { get; protected set; } - - [JsonIgnore] - public byte[]? SignatureField { get; protected set; } - - [JsonIgnore] - public ulong? IndexField { get; protected set; } } public static class ConsensusRequestExtensions { - public static (int depositCount, int withdrawalRequestCount) GetTypeCounts(this ConsensusRequest[]? requests) + public static (int depositCount, int withdrawalRequestCount, int consolidationRequestCount) GetTypeCounts(this ConsensusRequest[]? requests) { int depositCount = 0; int withdrawalRequestCount = 0; + int consolidationRequestCount = 0; int length = requests?.Length ?? 0; for (int i = 0; i < length; i++) { @@ -50,35 +34,45 @@ public static (int depositCount, int withdrawalRequestCount) GetTypeCounts(this { depositCount++; } - else + else if (requests[i].Type == ConsensusRequestsType.WithdrawalRequest) { withdrawalRequestCount++; } + else + { + consolidationRequestCount++; + } } - return (depositCount, withdrawalRequestCount); + return (depositCount, withdrawalRequestCount, consolidationRequestCount); } - public static (Deposit[]? deposits, WithdrawalRequest[]? withdrawalRequests) SplitRequests(this ConsensusRequest[]? requests) + public static (Deposit[]? deposits, WithdrawalRequest[]? withdrawalRequests, ConsolidationRequest[]? consolidationRequests) SplitRequests(this ConsensusRequest[]? requests) { - if (requests is null) return (null, null); - (int depositCount, int withdrawalRequestCount) = requests.GetTypeCounts(); + if (requests is null) return (null, null, null); + (int depositCount, int withdrawalRequestCount, int consolidationRequestCount) = requests.GetTypeCounts(); Deposit[] deposits = new Deposit[depositCount]; WithdrawalRequest[] withdrawalRequests = new WithdrawalRequest[withdrawalRequestCount]; + ConsolidationRequest[] consolidationRequests = new ConsolidationRequest[consolidationRequestCount]; int depositIndex = 0; int withdrawalRequestIndex = 0; + int consolidationRequestIndex = 0; for (int i = 0; i < requests.Length; i++) { if (requests[i].Type == ConsensusRequestsType.Deposit) { deposits[depositIndex++] = (Deposit)requests[i]; } - else + else if (requests[i].Type == ConsensusRequestsType.WithdrawalRequest) { withdrawalRequests[withdrawalRequestIndex++] = (WithdrawalRequest)requests[i]; } + else + { + consolidationRequests[consolidationRequestIndex++] = (ConsolidationRequest)requests[i]; + } } - return (deposits, withdrawalRequests); + return (deposits, withdrawalRequests, consolidationRequests); } } diff --git a/src/Nethermind/Nethermind.Core/ConsensusRequests/ConsolidationRequest.cs b/src/Nethermind/Nethermind.Core/ConsensusRequests/ConsolidationRequest.cs new file mode 100644 index 00000000000..abf79b924a1 --- /dev/null +++ b/src/Nethermind/Nethermind.Core/ConsensusRequests/ConsolidationRequest.cs @@ -0,0 +1,31 @@ +// SPDX-FileCopyrightText: 2022 Demerzel Solutions Limited +// SPDX-License-Identifier: LGPL-3.0-only +using System; +using Nethermind.Core.Extensions; + +namespace Nethermind.Core.ConsensusRequests; + +/// +/// Represents a Deposit that has been validated at the consensus layer. +/// +public class ConsolidationRequest : ConsensusRequest +{ + public ConsolidationRequest() + { + Type = ConsensusRequestsType.ConsolidationRequest; + } + public Address? SourceAddress { get; set; } + public Memory? SourcePubkey { get; set; } + + public Memory? TargetPubkey { get; set; } + + public override string ToString() => ToString(string.Empty); + + public string ToString(string indentation) => @$"{indentation}{nameof(ConsolidationRequest)} + {{ {nameof(SourceAddress)}: {SourceAddress}, + {nameof(SourcePubkey)}: {SourcePubkey?.Span.ToHexString()}, + {nameof(TargetPubkey)}: {TargetPubkey?.Span.ToHexString()}, + }}"; + + +} diff --git a/src/Nethermind/Nethermind.Core/ConsensusRequests/Deposit.cs b/src/Nethermind/Nethermind.Core/ConsensusRequests/Deposit.cs index 09da1d1f8a8..50b46fc27c2 100644 --- a/src/Nethermind/Nethermind.Core/ConsensusRequests/Deposit.cs +++ b/src/Nethermind/Nethermind.Core/ConsensusRequests/Deposit.cs @@ -3,7 +3,6 @@ using System; using Nethermind.Core.Extensions; -using System.Text; namespace Nethermind.Core.ConsensusRequests; @@ -17,34 +16,13 @@ public Deposit() Type = ConsensusRequestsType.Deposit; Amount = 0; } - public Memory? Pubkey - { - get { return PubKeyField; } - set { PubKeyField = value; } - } + public Memory? Pubkey { get; set; } + public byte[]? WithdrawalCredentials { get; set; } - public byte[]? WithdrawalCredentials - { - get { return WithdrawalCredentialsField; } - set { WithdrawalCredentialsField = value; } - } + public ulong Amount { get; set; } - public ulong Amount - { - get { return AmountField; } - set { AmountField = value; } - } - - public byte[]? Signature - { - get { return SignatureField; } - set { SignatureField = value; } - } - public ulong? Index - { - get { return IndexField; } - set { IndexField = value; } - } + public byte[]? Signature { get; set; } + public ulong? Index { get; set; } public override string ToString() => ToString(string.Empty); public string ToString(string indentation) => @$"{indentation}{nameof(Deposit)} diff --git a/src/Nethermind/Nethermind.Core/ConsensusRequests/WithdrawalRequest.cs b/src/Nethermind/Nethermind.Core/ConsensusRequests/WithdrawalRequest.cs index 355205d5174..f9d79f127c7 100644 --- a/src/Nethermind/Nethermind.Core/ConsensusRequests/WithdrawalRequest.cs +++ b/src/Nethermind/Nethermind.Core/ConsensusRequests/WithdrawalRequest.cs @@ -17,23 +17,11 @@ public WithdrawalRequest() Type = ConsensusRequestsType.WithdrawalRequest; Amount = 0; } - public Address? SourceAddress - { - get { return SourceAddressField; } - set { SourceAddressField = value; } - } + public Address? SourceAddress { get; set; } - public Memory? ValidatorPubkey - { - get { return PubKeyField; } - set { PubKeyField = value; } - } + public Memory? ValidatorPubkey { get; set; } - public ulong Amount - { - get { return AmountField; } - set { AmountField = value; } - } + public ulong Amount { get; set; } public override string ToString() => ToString(string.Empty); public string ToString(string indentation) => @$"{indentation}{nameof(WithdrawalRequest)} diff --git a/src/Nethermind/Nethermind.Core/Eip2930/AccessList.cs b/src/Nethermind/Nethermind.Core/Eip2930/AccessList.cs index 86bc94d0809..9372639ce52 100644 --- a/src/Nethermind/Nethermind.Core/Eip2930/AccessList.cs +++ b/src/Nethermind/Nethermind.Core/Eip2930/AccessList.cs @@ -26,6 +26,7 @@ private AccessList(List<(Address address, int count)> addresses, List k public static AccessList Empty { get; } = new(new List<(Address, int)>(), new List()); public bool IsEmpty => _addresses.Count == 0; + public (int AddressesCount, int StorageKeysCount) Count => (_addresses.Count, _keys.Count); public class Builder { @@ -72,26 +73,21 @@ public AccessList Build() IEnumerator<(Address Address, StorageKeysEnumerable StorageKeys)> IEnumerable<(Address Address, StorageKeysEnumerable StorageKeys)>.GetEnumerator() => GetEnumerator(); IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); - public struct Enumerator : IEnumerator<(Address Address, StorageKeysEnumerable StorageKeys)>, IEnumerator<(Address Address, IEnumerable StorageKeys)> + public struct Enumerator(AccessList accessList) : IEnumerator<(Address Address, StorageKeysEnumerable StorageKeys)>, + IEnumerator<(Address Address, IEnumerable StorageKeys)> { - private readonly AccessList _accessList; private int _index = -1; private int _keysIndex = 0; - public Enumerator(AccessList accessList) - { - _accessList = accessList; - } - public bool MoveNext() { _index++; if (_index > 0) { - _keysIndex += CollectionsMarshal.AsSpan(_accessList._addresses)[_index - 1].count; + _keysIndex += CollectionsMarshal.AsSpan(accessList._addresses)[_index - 1].count; } - return _index < _accessList._addresses.Count; + return _index < accessList._addresses.Count; } public void Reset() @@ -104,8 +100,8 @@ public readonly (Address Address, StorageKeysEnumerable StorageKeys) Current { get { - ref readonly var addressCount = ref CollectionsMarshal.AsSpan(_accessList._addresses)[_index]; - return (addressCount.address, new StorageKeysEnumerable(_accessList, _keysIndex, addressCount.count)); + ref readonly var addressCount = ref CollectionsMarshal.AsSpan(accessList._addresses)[_index]; + return (addressCount.address, new StorageKeysEnumerable(accessList, _keysIndex, addressCount.count)); } } diff --git a/src/Nethermind/Nethermind.Core/Eip7251Constants.cs b/src/Nethermind/Nethermind.Core/Eip7251Constants.cs new file mode 100644 index 00000000000..4fc30bd2601 --- /dev/null +++ b/src/Nethermind/Nethermind.Core/Eip7251Constants.cs @@ -0,0 +1,9 @@ +// SPDX-FileCopyrightText: 2024 Demerzel Solutions Limited +// SPDX-License-Identifier: LGPL-3.0-only + +namespace Nethermind.Core; + +public static class Eip7251Constants +{ + public static readonly Address ConsolidationRequestPredeployAddress = new("0x00b42dbF2194e931E80326D950320f7d9Dbeac02"); +} diff --git a/src/Nethermind/Nethermind.Core/Eip7702Constants.cs b/src/Nethermind/Nethermind.Core/Eip7702Constants.cs new file mode 100644 index 00000000000..d54ff84f4b4 --- /dev/null +++ b/src/Nethermind/Nethermind.Core/Eip7702Constants.cs @@ -0,0 +1,15 @@ +// SPDX-FileCopyrightText: 2024 Demerzel Solutions Limited +// SPDX-License-Identifier: LGPL-3.0-only + +using System; + +namespace Nethermind.Core; +public static class Eip7702Constants +{ + public const byte Magic = 0x05; + public static ReadOnlySpan DelegationHeader => [0xef, 0x01, 0x00]; + private static readonly int HeaderLength = DelegationHeader.Length; + public static bool IsDelegatedCode(ReadOnlySpan code) => + code.Length == HeaderLength + Address.Size + && DelegationHeader.SequenceEqual(code.Slice(0, DelegationHeader.Length)); +} diff --git a/src/Nethermind/Nethermind.Core/Specs/IReleaseSpec.cs b/src/Nethermind/Nethermind.Core/Specs/IReleaseSpec.cs index 0516fc9f958..0189f27f4fa 100644 --- a/src/Nethermind/Nethermind.Core/Specs/IReleaseSpec.cs +++ b/src/Nethermind/Nethermind.Core/Specs/IReleaseSpec.cs @@ -283,6 +283,15 @@ public interface IReleaseSpec : IEip1559Spec, IReceiptSpec bool WithdrawalRequestsEnabled => IsEip7002Enabled; Address Eip7002ContractAddress { get; } + + /// + /// EIP-7251: triggered consolidations + /// + bool IsEip7251Enabled { get; } + bool ConsolidationRequestsEnabled => IsEip7251Enabled; + Address Eip7251ContractAddress { get; } + + /// /// Save historical block hashes in state /// @@ -299,6 +308,11 @@ public interface IReleaseSpec : IEip1559Spec, IReceiptSpec /// bool IsEip6780Enabled { get; } + /// + /// Transactions that allows code delegation for EOA + /// + bool IsEip7702Enabled { get; } + /// /// Blob base fee collection for Gnosis /// @@ -396,6 +410,8 @@ public interface IReleaseSpec : IEip1559Spec, IReceiptSpec public bool BlobBaseFeeEnabled => IsEip4844Enabled; - public bool ConsensusRequestsEnabled => WithdrawalRequestsEnabled || DepositsEnabled; + bool IsAuthorizationListEnabled => IsEip7702Enabled; + + public bool RequestsEnabled => ConsolidationRequestsEnabled || WithdrawalRequestsEnabled || DepositsEnabled; } } diff --git a/src/Nethermind/Nethermind.Core/Specs/ReleaseSpecDecorator.cs b/src/Nethermind/Nethermind.Core/Specs/ReleaseSpecDecorator.cs index 8cd33749e43..01c65e977f9 100644 --- a/src/Nethermind/Nethermind.Core/Specs/ReleaseSpecDecorator.cs +++ b/src/Nethermind/Nethermind.Core/Specs/ReleaseSpecDecorator.cs @@ -71,10 +71,13 @@ public class ReleaseSpecDecorator(IReleaseSpec spec) : IReleaseSpec public Address DepositContractAddress => spec.DepositContractAddress; public bool IsEip7002Enabled => spec.IsEip7002Enabled; public Address Eip7002ContractAddress => spec.Eip7002ContractAddress; + public bool IsEip7251Enabled => spec.IsEip7251Enabled; + public Address Eip7251ContractAddress => spec.Eip7251ContractAddress; public virtual bool IsEip2935Enabled => spec.IsEip2935Enabled; public virtual bool IsEip7709Enabled => spec.IsEip7709Enabled; public virtual Address Eip2935ContractAddress => spec.Eip2935ContractAddress; public virtual bool IsEip6780Enabled => spec.IsEip6780Enabled; + public bool IsEip7702Enabled => spec.IsEip7702Enabled; public virtual bool IsRip7212Enabled => spec.IsRip7212Enabled; public virtual bool IsOpGraniteEnabled => spec.IsOpGraniteEnabled; public virtual ulong WithdrawalTimestamp => spec.WithdrawalTimestamp; diff --git a/src/Nethermind/Nethermind.Core/Transaction.cs b/src/Nethermind/Nethermind.Core/Transaction.cs index ec162f85c88..987af864482 100644 --- a/src/Nethermind/Nethermind.Core/Transaction.cs +++ b/src/Nethermind/Nethermind.Core/Transaction.cs @@ -4,6 +4,8 @@ using System; using System.Buffers; using System.Diagnostics; +using System.Diagnostics.CodeAnalysis; +using System.Linq; using System.Runtime.CompilerServices; using System.Text; using System.Text.Json.Serialization; @@ -46,6 +48,7 @@ public class Transaction public bool SupportsAccessList => Type >= TxType.AccessList && Type != TxType.DepositTx; public bool Supports1559 => Type >= TxType.EIP1559 && Type != TxType.DepositTx; public bool SupportsBlobs => Type == TxType.Blob && Type != TxType.DepositTx; + public bool SupportsAuthorizationList => Type == TxType.SetCode && Type != TxType.DepositTx; public long GasLimit { get; set; } public Address? To { get; set; } public UInt256 Value { get; set; } @@ -56,6 +59,12 @@ public class Transaction public bool IsContractCreation => To is null; public bool IsMessageCall => To is not null; + [MemberNotNullWhen(true, nameof(AuthorizationList))] + public bool HasAuthorizationList => + Type == TxType.SetCode && + AuthorizationList is not null && + AuthorizationList.Length > 0; + private Hash256? _hash; [JsonIgnore] @@ -161,6 +170,12 @@ private void ClearPreHashInternal() public object? NetworkWrapper { get; set; } + /// + /// List of EOA code authorizations. + /// https://eips.ethereum.org/EIPS/eip-7702 + /// + public AuthorizationTuple[]? AuthorizationList { get; set; } + /// /// Service transactions are free. The field added to handle baseFee validation after 1559 /// @@ -263,6 +278,7 @@ public bool Return(Transaction obj) obj.IsServiceTransaction = default; obj.PoolIndex = default; obj._size = default; + obj.AuthorizationList = default; return true; } @@ -293,6 +309,7 @@ public void CopyTo(Transaction tx) tx.IsServiceTransaction = IsServiceTransaction; tx.PoolIndex = PoolIndex; tx._size = _size; + tx.AuthorizationList = AuthorizationList; } } @@ -304,7 +321,10 @@ public class GeneratedTransaction : Transaction { } /// /// System transaction that is to be executed by the node without including in the block. /// - public class SystemTransaction : Transaction { } + public class SystemTransaction : Transaction + { + private new const long GasLimit = 30_000_000L; + } /// /// Used inside Transaction::GetSize to calculate encoded transaction size diff --git a/src/Nethermind/Nethermind.Core/TxType.cs b/src/Nethermind/Nethermind.Core/TxType.cs index b642ca78121..8b9d2f65b92 100644 --- a/src/Nethermind/Nethermind.Core/TxType.cs +++ b/src/Nethermind/Nethermind.Core/TxType.cs @@ -9,6 +9,8 @@ public enum TxType : byte AccessList = 1, EIP1559 = 2, Blob = 3, - DepositTx = 0x7E + SetCode = 4, + + DepositTx = 0x7E, } } diff --git a/src/Nethermind/Nethermind.Crypto/EthereumEcdsa.cs b/src/Nethermind/Nethermind.Crypto/EthereumEcdsa.cs index 7b27a145d43..deecd2c9d11 100644 --- a/src/Nethermind/Nethermind.Crypto/EthereumEcdsa.cs +++ b/src/Nethermind/Nethermind.Crypto/EthereumEcdsa.cs @@ -5,8 +5,10 @@ using System.Globalization; using System.IO; using System.Numerics; +using System.Runtime.CompilerServices; using Nethermind.Core; using Nethermind.Core.Crypto; +using Nethermind.Int256; using Nethermind.Logging; using Nethermind.Serialization.Rlp; @@ -26,81 +28,15 @@ public class EthereumEcdsa : Ecdsa, IEthereumEcdsa BigInteger.Parse("00FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEBAAEDCE6AF48A03BBFD25E8CD0364141", NumberStyles.HexNumber); + private readonly AuthorizationTupleDecoder _tupleDecoder = AuthorizationTupleDecoder.Instance; private readonly ulong _chainIdValue; + public ulong ChainId => _chainIdValue; public EthereumEcdsa(ulong chainId) { _chainIdValue = chainId; } - public void Sign(PrivateKey privateKey, Transaction tx, bool isEip155Enabled) - { - if (tx.Type != TxType.Legacy) - { - tx.ChainId = _chainIdValue; - } - - Hash256 hash = Keccak.Compute(Rlp.Encode(tx, true, isEip155Enabled, _chainIdValue).Bytes); - tx.Signature = Sign(privateKey, hash); - - if (tx.Type == TxType.Legacy && isEip155Enabled) - { - tx.Signature.V = tx.Signature.V + 8 + 2 * _chainIdValue; - } - } - - /// - /// - /// - /// - /// - /// - public bool Verify(Address sender, Transaction tx) - { - Address? recovered = RecoverAddress(tx); - return recovered?.Equals(sender) ?? false; - } - - /// - /// - /// - /// - /// - /// - public Address? RecoverAddress(Transaction tx, bool useSignatureChainId = false) - { - if (tx.Signature is null) - { - throw new InvalidDataException("Cannot recover sender address from a transaction without a signature."); - } - - useSignatureChainId &= tx.Signature.ChainId.HasValue; - - // feels like it is the same check twice - bool applyEip155 = useSignatureChainId - || tx.Signature.V == CalculateV(_chainIdValue, false) - || tx.Signature.V == CalculateV(_chainIdValue, true); - - ulong chainId; - switch (tx.Type) - { - case TxType.Legacy when useSignatureChainId: - chainId = tx.Signature.ChainId.Value; - break; - case TxType.Legacy: - chainId = _chainIdValue; - break; - default: - chainId = tx.ChainId!.Value; - break; - } - Hash256 hash = Keccak.Compute(Rlp.Encode(tx, true, applyEip155, chainId).Bytes); - - return RecoverAddress(tx.Signature, hash); - } - - public static ulong CalculateV(ulong chainId, bool addParity = true) => chainId * 2 + 35ul + (addParity ? 1u : 0u); - public Address? RecoverAddress(Signature signature, Hash256 message) { return RecoverAddress(signature.BytesWithRecovery, message); diff --git a/src/Nethermind/Nethermind.Crypto/EthereumEcdsaExtensions.cs b/src/Nethermind/Nethermind.Crypto/EthereumEcdsaExtensions.cs new file mode 100644 index 00000000000..3b7d939c936 --- /dev/null +++ b/src/Nethermind/Nethermind.Crypto/EthereumEcdsaExtensions.cs @@ -0,0 +1,103 @@ +// SPDX-FileCopyrightText: 2022 Demerzel Solutions Limited +// SPDX-License-Identifier: LGPL-3.0-only + +using System; +using System.IO; +using System.Runtime.CompilerServices; +using Nethermind.Core; +using Nethermind.Core.Crypto; +using Nethermind.Serialization.Rlp; + +namespace Nethermind.Crypto +{ + public static class EthereumEcdsaExtensions + { + public static AuthorizationTuple Sign(this IEthereumEcdsa ecdsa, PrivateKey signer, ulong chainId, Address codeAddress, ulong nonce) + { + using NettyRlpStream rlp = AuthorizationTupleDecoder.Instance.EncodeWithoutSignature(chainId, codeAddress, nonce); + Span preImage = stackalloc byte[rlp.Length + 1]; + preImage[0] = Eip7702Constants.Magic; + rlp.AsSpan().CopyTo(preImage.Slice(1)); + Signature sig = ecdsa.Sign(signer, Keccak.Compute(preImage)); + return new AuthorizationTuple(chainId, codeAddress, nonce, sig); + } + + public static void Sign(this IEthereumEcdsa ecdsa, PrivateKey privateKey, Transaction tx, bool isEip155Enabled = true) + { + if (tx.Type != TxType.Legacy) + { + tx.ChainId = ecdsa.ChainId; + } + + Hash256 hash = Keccak.Compute(Rlp.Encode(tx, true, isEip155Enabled, ecdsa.ChainId).Bytes); + tx.Signature = ecdsa.Sign(privateKey, hash); + + if (tx.Type == TxType.Legacy && isEip155Enabled) + { + tx.Signature.V = tx.Signature.V + 8 + 2 * ecdsa.ChainId; + } + } + + /// + /// + /// + /// + /// + /// + public static bool Verify(this IEthereumEcdsa ecdsa, Address sender, Transaction tx) + { + Address? recovered = ecdsa.RecoverAddress(tx); + return recovered?.Equals(sender) ?? false; + } + + /// + /// + /// + /// + /// + /// + public static Address? RecoverAddress(this IEthereumEcdsa ecdsa, Transaction tx, bool useSignatureChainId = false) + { + if (tx.Signature is null) + { + throw new InvalidDataException("Cannot recover sender address from a transaction without a signature."); + } + + useSignatureChainId &= tx.Signature.ChainId.HasValue; + + // feels like it is the same check twice + bool applyEip155 = useSignatureChainId + || tx.Signature.V == CalculateV(ecdsa.ChainId, false) + || tx.Signature.V == CalculateV(ecdsa.ChainId, true); + + ulong chainId; + switch (tx.Type) + { + case TxType.Legacy when useSignatureChainId: + chainId = tx.Signature.ChainId.Value; + break; + case TxType.Legacy: + chainId = ecdsa.ChainId; + break; + default: + chainId = tx.ChainId!.Value; + break; + } + Hash256 hash = Keccak.Compute(Rlp.Encode(tx, true, applyEip155, chainId).Bytes); + + return ecdsa.RecoverAddress(tx.Signature, hash); + } + + public static ulong CalculateV(ulong chainId, bool addParity = true) => chainId * 2 + 35ul + (addParity ? 1u : 0u); + + [SkipLocalsInit] + public static Address? RecoverAddress(this IEthereumEcdsa ecdsa, AuthorizationTuple tuple) + { + Span buffer = stackalloc byte[128]; + buffer[0] = Eip7702Constants.Magic; + using NettyRlpStream stream = AuthorizationTupleDecoder.Instance.EncodeWithoutSignature(tuple.ChainId, tuple.CodeAddress, tuple.Nonce); + stream.AsSpan().CopyTo(buffer.Slice(1)); + return ecdsa.RecoverAddress(tuple.AuthoritySignature, Keccak.Compute(buffer.Slice(0, stream.Length + 1))); + } + } +} diff --git a/src/Nethermind/Nethermind.Crypto/IEthereumEcdsa.cs b/src/Nethermind/Nethermind.Crypto/IEthereumEcdsa.cs index 2c72540a705..67f17f74d5b 100644 --- a/src/Nethermind/Nethermind.Crypto/IEthereumEcdsa.cs +++ b/src/Nethermind/Nethermind.Crypto/IEthereumEcdsa.cs @@ -4,15 +4,14 @@ using System; using Nethermind.Core; using Nethermind.Core.Crypto; +using Nethermind.Int256; namespace Nethermind.Crypto { public interface IEthereumEcdsa : IEcdsa { - void Sign(PrivateKey privateKey, Transaction tx, bool isEip155Enabled = true); - Address? RecoverAddress(Transaction tx, bool useSignatureChainId = false); + ulong ChainId { get; } Address? RecoverAddress(Signature signature, Hash256 message); Address? RecoverAddress(Span signatureBytes, Hash256 message); - bool Verify(Address sender, Transaction tx); } } diff --git a/src/Nethermind/Nethermind.Crypto/NullEthereumEcdsa.cs b/src/Nethermind/Nethermind.Crypto/NullEthereumEcdsa.cs index fade56407c5..78ff585916f 100644 --- a/src/Nethermind/Nethermind.Crypto/NullEthereumEcdsa.cs +++ b/src/Nethermind/Nethermind.Crypto/NullEthereumEcdsa.cs @@ -4,6 +4,7 @@ using System; using Nethermind.Core; using Nethermind.Core.Crypto; +using Nethermind.Int256; namespace Nethermind.Crypto { @@ -11,6 +12,8 @@ public class NullEthereumEcdsa : IEthereumEcdsa { public static NullEthereumEcdsa Instance { get; } = new(); + public ulong ChainId => 0; + private NullEthereumEcdsa() { } @@ -30,11 +33,6 @@ public CompressedPublicKey RecoverCompressedPublicKey(Signature signature, Hash2 throw new InvalidOperationException($"{nameof(NullEthereumEcdsa)} does not expect any calls"); } - public void Sign(PrivateKey privateKey, Transaction tx, bool _) - { - throw new InvalidOperationException($"{nameof(NullEthereumEcdsa)} does not expect any calls"); - } - public Address RecoverAddress(Transaction tx, bool useSignatureChainId = false) { throw new InvalidOperationException($"{nameof(NullEthereumEcdsa)} does not expect any calls"); diff --git a/src/Nethermind/Nethermind.Evm.Benchmark/EvmBenchmarks.cs b/src/Nethermind/Nethermind.Evm.Benchmark/EvmBenchmarks.cs index fd31df6fa0c..be98ea39aab 100644 --- a/src/Nethermind/Nethermind.Evm.Benchmark/EvmBenchmarks.cs +++ b/src/Nethermind/Nethermind.Evm.Benchmark/EvmBenchmarks.cs @@ -55,7 +55,7 @@ public void GlobalSetup() codeInfo: new CodeInfo(ByteCode), value: 0, transferValue: 0, - txExecutionContext: new TxExecutionContext(_header, Address.Zero, 0, null), + txExecutionContext: new TxExecutionContext(_header, Address.Zero, 0, null, codeInfoRepository), inputData: default ); diff --git a/src/Nethermind/Nethermind.Evm.Benchmark/MultipleUnsignedOperations.cs b/src/Nethermind/Nethermind.Evm.Benchmark/MultipleUnsignedOperations.cs index 3a45a79026b..3b3853d5fc5 100644 --- a/src/Nethermind/Nethermind.Evm.Benchmark/MultipleUnsignedOperations.cs +++ b/src/Nethermind/Nethermind.Evm.Benchmark/MultipleUnsignedOperations.cs @@ -87,7 +87,7 @@ public void GlobalSetup() codeInfo: new CodeInfo(_bytecode.Concat(_bytecode).Concat(_bytecode).Concat(_bytecode).ToArray()), value: 0, transferValue: 0, - txExecutionContext: new TxExecutionContext(_header, Address.Zero, 0, null), + txExecutionContext: new TxExecutionContext(_header, Address.Zero, 0, null, codeInfoRepository), inputData: default ); diff --git a/src/Nethermind/Nethermind.Evm.Benchmark/StaticCallBenchmarks.cs b/src/Nethermind/Nethermind.Evm.Benchmark/StaticCallBenchmarks.cs index c0f3e229c4f..12c12742ed7 100644 --- a/src/Nethermind/Nethermind.Evm.Benchmark/StaticCallBenchmarks.cs +++ b/src/Nethermind/Nethermind.Evm.Benchmark/StaticCallBenchmarks.cs @@ -98,7 +98,7 @@ public void GlobalSetup() codeInfo: new CodeInfo(Bytecode), value: 0, transferValue: 0, - txExecutionContext: new TxExecutionContext(_header, Address.Zero, 0, null), + txExecutionContext: new TxExecutionContext(_header, Address.Zero, 0, null, codeInfoRepository), inputData: default ); diff --git a/src/Nethermind/Nethermind.Evm.Test/CodeInfoRepositoryTests.cs b/src/Nethermind/Nethermind.Evm.Test/CodeInfoRepositoryTests.cs new file mode 100644 index 00000000000..f83b9b9846d --- /dev/null +++ b/src/Nethermind/Nethermind.Evm.Test/CodeInfoRepositoryTests.cs @@ -0,0 +1,205 @@ +// SPDX-FileCopyrightText: 2024 Demerzel Solutions Limited +// SPDX-License-Identifier: LGPL-3.0-only + +using Nethermind.Core.Crypto; +using Nethermind.Core; +using Nethermind.Crypto; +using Nethermind.Int256; +using Nethermind.Logging; +using Nethermind.Serialization.Rlp; +using NSubstitute; +using NUnit.Framework; +using System; +using System.Collections.Generic; +using Nethermind.Core.Test.Builders; +using FluentAssertions; +using Nethermind.State; +using Nethermind.Core.Specs; +using Nethermind.Evm.CodeAnalysis; +using Nethermind.Core.Extensions; +using Nethermind.Db; +using Nethermind.Trie.Pruning; + +namespace Nethermind.Evm.Test; + +[TestFixture, Parallelizable] +public class CodeInfoRepositoryTests +{ + public static IEnumerable NotDelegationCodeCases() + { + byte[] rndAddress = new byte[20]; + TestContext.CurrentContext.Random.NextBytes(rndAddress); + //Change first byte of the delegation header + byte[] code = [.. Eip7702Constants.DelegationHeader, .. rndAddress]; + code[0] = TestContext.CurrentContext.Random.NextByte(0xee); + yield return new object[] + { + code + }; + //Change second byte of the delegation header + code = [.. Eip7702Constants.DelegationHeader, .. rndAddress]; + code[1] = TestContext.CurrentContext.Random.NextByte(0x2, 0xff); + yield return new object[] + { + code + }; + //Change third byte of the delegation header + code = [.. Eip7702Constants.DelegationHeader, .. rndAddress]; + code[2] = TestContext.CurrentContext.Random.NextByte(0x1, 0xff); + yield return new object[] + { + code + }; + code = [.. Eip7702Constants.DelegationHeader, .. new byte[21]]; + yield return new object[] + { + code + }; + code = [.. Eip7702Constants.DelegationHeader, .. new byte[19]]; + yield return new object[] + { + code + }; + } + [TestCaseSource(nameof(NotDelegationCodeCases))] + public void TryGetDelegation_CodeIsNotDelegation_ReturnsFalse(byte[] code) + { + IDb stateDb = new MemDb(); + IDb codeDb = new MemDb(); + TrieStore trieStore = new(stateDb, LimboLogs.Instance); + IWorldState stateProvider = new WorldState(trieStore, codeDb, LimboLogs.Instance); + stateProvider.CreateAccount(TestItem.AddressA, 0); + stateProvider.InsertCode(TestItem.AddressA, code, Substitute.For()); + CodeInfoRepository sut = new(); + + sut.TryGetDelegation(stateProvider, TestItem.AddressA, out _).Should().Be(false); + } + + + public static IEnumerable DelegationCodeCases() + { + byte[] address = new byte[20]; + byte[] code = [.. Eip7702Constants.DelegationHeader, .. address]; + yield return new object[] + { + code + }; + TestContext.CurrentContext.Random.NextBytes(address); + code = [.. Eip7702Constants.DelegationHeader, .. address]; + yield return new object[] + { + code + }; + } + [TestCaseSource(nameof(DelegationCodeCases))] + public void TryGetDelegation_CodeTryGetDelegation_ReturnsTrue(byte[] code) + { + IDb stateDb = new MemDb(); + IDb codeDb = new MemDb(); + TrieStore trieStore = new(stateDb, LimboLogs.Instance); + IWorldState stateProvider = new WorldState(trieStore, codeDb, LimboLogs.Instance); + stateProvider.CreateAccount(TestItem.AddressA, 0); + stateProvider.InsertCode(TestItem.AddressA, code, Substitute.For()); + CodeInfoRepository sut = new(); + + sut.TryGetDelegation(stateProvider, TestItem.AddressA, out _).Should().Be(true); + } + + [TestCaseSource(nameof(DelegationCodeCases))] + public void TryGetDelegation_CodeTryGetDelegation_CorrectDelegationAddressIsSet(byte[] code) + { + IDb stateDb = new MemDb(); + IDb codeDb = new MemDb(); + TrieStore trieStore = new(stateDb, LimboLogs.Instance); + IWorldState stateProvider = new WorldState(trieStore, codeDb, LimboLogs.Instance); + stateProvider.CreateAccount(TestItem.AddressA, 0); + stateProvider.InsertCode(TestItem.AddressA, code, Substitute.For()); + CodeInfoRepository sut = new(); + + Address result; + sut.TryGetDelegation(stateProvider, TestItem.AddressA, out result); + + result.Should().Be(new Address(code.Slice(3, Address.Size))); + } + + [TestCaseSource(nameof(DelegationCodeCases))] + public void GetExecutableCodeHash_CodeTryGetDelegation_ReturnsHashOfDelegated(byte[] code) + { + IDb stateDb = new MemDb(); + IDb codeDb = new MemDb(); + TrieStore trieStore = new(stateDb, LimboLogs.Instance); + IWorldState stateProvider = new WorldState(trieStore, codeDb, LimboLogs.Instance); + stateProvider.CreateAccount(TestItem.AddressA, 0); + stateProvider.InsertCode(TestItem.AddressA, code, Substitute.For()); + Address delegationAddress = new Address(code.Slice(3, Address.Size)); + byte[] delegationCode = new byte[32]; + stateProvider.CreateAccount(delegationAddress, 0); + stateProvider.InsertCode(delegationAddress, delegationCode, Substitute.For()); + + CodeInfoRepository sut = new(); + + sut.GetExecutableCodeHash(stateProvider, TestItem.AddressA).Should().Be(Keccak.Compute(delegationCode).ValueHash256); + } + + [TestCaseSource(nameof(NotDelegationCodeCases))] + public void GetExecutableCodeHash_CodeIsNotDelegation_ReturnsCodeHashOfAddress(byte[] code) + { + IDb stateDb = new MemDb(); + IDb codeDb = new MemDb(); + TrieStore trieStore = new(stateDb, LimboLogs.Instance); + IWorldState stateProvider = new WorldState(trieStore, codeDb, LimboLogs.Instance); + stateProvider.CreateAccount(TestItem.AddressA, 0); + stateProvider.InsertCode(TestItem.AddressA, code, Substitute.For()); + + CodeInfoRepository sut = new(); + + sut.GetExecutableCodeHash(stateProvider, TestItem.AddressA).Should().Be(Keccak.Compute(code).ValueHash256); + } + + [TestCaseSource(nameof(DelegationCodeCases))] + public void GetCachedCodeInfo_CodeTryGetDelegation_ReturnsCodeOfDelegation(byte[] code) + { + IDb stateDb = new MemDb(); + IDb codeDb = new MemDb(); + TrieStore trieStore = new(stateDb, LimboLogs.Instance); + IWorldState stateProvider = new WorldState(trieStore, codeDb, LimboLogs.Instance); + stateProvider.CreateAccount(TestItem.AddressA, 0); + stateProvider.InsertCode(TestItem.AddressA, code, Substitute.For()); + Address delegationAddress = new Address(code.Slice(3, Address.Size)); + stateProvider.CreateAccount(delegationAddress, 0); + byte[] delegationCode = new byte[32]; + stateProvider.InsertCode(delegationAddress, delegationCode, Substitute.For()); + CodeInfoRepository sut = new(); + + CodeInfo result = sut.GetCachedCodeInfo(stateProvider, TestItem.AddressA, Substitute.For()); + result.MachineCode.ToArray().Should().BeEquivalentTo(delegationCode); + } + + [TestCaseSource(nameof(NotDelegationCodeCases))] + public void GetCachedCodeInfo_CodeIsNotDelegation_ReturnsCodeOfAddress(byte[] code) + { + IDb stateDb = new MemDb(); + IDb codeDb = new MemDb(); + TrieStore trieStore = new(stateDb, LimboLogs.Instance); + IWorldState stateProvider = new WorldState(trieStore, codeDb, LimboLogs.Instance); + stateProvider.CreateAccount(TestItem.AddressA, 0); + stateProvider.InsertCode(TestItem.AddressA, code, Substitute.For()); + + CodeInfoRepository sut = new(); + + sut.GetCachedCodeInfo(stateProvider, TestItem.AddressA, Substitute.For()).Should().BeEquivalentTo(new CodeInfo(code)); + } + + private static AuthorizationTuple CreateAuthorizationTuple(PrivateKey signer, ulong chainId, Address codeAddress, ulong nonce) + { + AuthorizationTupleDecoder decoder = new(); + using NettyRlpStream rlp = decoder.EncodeWithoutSignature(chainId, codeAddress, nonce); + Span code = stackalloc byte[rlp.Length + 1]; + code[0] = Eip7702Constants.Magic; + rlp.AsSpan().CopyTo(code.Slice(1)); + EthereumEcdsa ecdsa = new(1); + Signature sig = ecdsa.Sign(signer, Keccak.Compute(code)); + + return new AuthorizationTuple(chainId, codeAddress, nonce, sig, signer.Address); + } +} diff --git a/src/Nethermind/Nethermind.Evm.Test/ConsolidationRequestProcessorTest.cs b/src/Nethermind/Nethermind.Evm.Test/ConsolidationRequestProcessorTest.cs new file mode 100644 index 00000000000..99973b8bc23 --- /dev/null +++ b/src/Nethermind/Nethermind.Evm.Test/ConsolidationRequestProcessorTest.cs @@ -0,0 +1,96 @@ +// SPDX-FileCopyrightText: 2024 Demerzel Solutions Limited +// SPDX-License-Identifier: LGPL-3.0-only + +using System; +using System.Linq; +using FluentAssertions; +using Nethermind.Consensus.Requests; +using Nethermind.Core; +using Nethermind.Core.ConsensusRequests; +using Nethermind.Core.Extensions; +using Nethermind.Core.Specs; +using Nethermind.Core.Test.Builders; +using Nethermind.Crypto; +using Nethermind.Db; +using Nethermind.Evm.Tracing; +using Nethermind.Evm.TransactionProcessing; +using Nethermind.Int256; +using Nethermind.Logging; +using Nethermind.Specs; +using Nethermind.State; +using Nethermind.Trie.Pruning; +using NSubstitute; +using NUnit.Framework; + + +namespace Nethermind.Evm.Test; + +public class ConsolidationRequestProcessorTests +{ + + private ISpecProvider _specProvider; + private ITransactionProcessor _transactionProcessor; + private IWorldState _stateProvider; + + private ICodeInfoRepository _codeInfoRepository; + + private static readonly UInt256 AccountBalance = 1.Ether(); + + private readonly Address eip7251Account = Eip7251Constants.ConsolidationRequestPredeployAddress; + + [SetUp] + public void Setup() + { + _specProvider = MainnetSpecProvider.Instance; + MemDb stateDb = new(); + TrieStore trieStore = new(stateDb, LimboLogs.Instance); + _stateProvider = new WorldState(trieStore, new MemDb(), LimboLogs.Instance); + _stateProvider.CreateAccount(eip7251Account, AccountBalance); + _stateProvider.Commit(_specProvider.GenesisSpec); + _stateProvider.CommitTree(0); + + _codeInfoRepository = new CodeInfoRepository(); + + VirtualMachine virtualMachine = new(new TestBlockhashProvider(_specProvider), _specProvider, _codeInfoRepository, LimboLogs.Instance); + + _transactionProcessor = Substitute.For(); + + _transactionProcessor.Execute(Arg.Any(), Arg.Any(), Arg.Any()) + .Returns(ci => + { + CallOutputTracer tracer = ci.Arg(); + tracer.ReturnValue = Bytes.FromHexString("a94f5374fce5edbc8e2a8697c15331677e6ebf0b0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000010000000000000000a94f5374fce5edbc8e2a8697c15331677e6ebf0b000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002ffffffffffffffffa94f5374fce5edbc8e2a8697c15331677e6ebf0b0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000030000000000000000a94f5374fce5edbc8e2a8697c15331677e6ebf0b000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000004ffffffffffffffffa94f5374fce5edbc8e2a8697c15331677e6ebf0b0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000050000000000000000a94f5374fce5edbc8e2a8697c15331677e6ebf0b000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000006ffffffffffffffffa94f5374fce5edbc8e2a8697c15331677e6ebf0b0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000070000000000000000a94f5374fce5edbc8e2a8697c15331677e6ebf0b000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000008ffffffffffffffffa94f5374fce5edbc8e2a8697c15331677e6ebf0b0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000090000000000000000a94f5374fce5edbc8e2a8697c15331677e6ebf0b00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000affffffffffffffffa94f5374fce5edbc8e2a8697c15331677e6ebf0b00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000b0000000000000000a94f5374fce5edbc8e2a8697c15331677e6ebf0b00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000cffffffffffffffffa94f5374fce5edbc8e2a8697c15331677e6ebf0b00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000d0000000000000000a94f5374fce5edbc8e2a8697c15331677e6ebf0b00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000effffffffffffffffa94f5374fce5edbc8e2a8697c15331677e6ebf0b00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000f0000000000000000a94f5374fce5edbc8e2a8697c15331677e6ebf0b000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000010ffffffffffffffff"); + return new TransactionResult(); + }); + } + + + [Test] + public void ShouldProcessConsolidationRequest() + { + + IReleaseSpec spec = Substitute.For(); + spec.ConsolidationRequestsEnabled.Returns(true); + spec.Eip7251ContractAddress.Returns(eip7251Account); + + Block block = Build.A.Block.TestObject; + + ConsolidationRequestsProcessor ConsolidationRequestsProcessor = new(transactionProcessor: _transactionProcessor); + + var ConsolidationRequest = new ConsolidationRequest() + { + SourceAddress = new Address(Bytes.FromHexString("0xa94f5374fce5edbc8e2a8697c15331677e6ebf0b")), + SourcePubkey = Bytes.FromHexString("000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001"), + TargetPubkey = Bytes.FromHexString("0000000000000000a94f5374fce5edbc8e2a8697c15331677e6ebf0b0000000000000000000000000000000000000000") + }; + + var ConsolidationRequests = ConsolidationRequestsProcessor.ReadRequests(block, _stateProvider, spec).ToList(); + + Assert.That(ConsolidationRequests, Has.Count.EqualTo(10)); + ConsolidationRequest ConsolidationRequestResult = ConsolidationRequests[0]; + + ConsolidationRequestResult.Should().BeEquivalentTo(ConsolidationRequest, options => options + .Using>(ctx => ctx.Subject.Span.SequenceEqual(ctx.Expectation.Span).Should().BeTrue()) + .WhenTypeIs>()); + } +} diff --git a/src/Nethermind/Nethermind.Evm.Test/IntrinsicGasCalculatorTests.cs b/src/Nethermind/Nethermind.Evm.Test/IntrinsicGasCalculatorTests.cs index 126efa7438b..d1c068694da 100644 --- a/src/Nethermind/Nethermind.Evm.Test/IntrinsicGasCalculatorTests.cs +++ b/src/Nethermind/Nethermind.Evm.Test/IntrinsicGasCalculatorTests.cs @@ -4,6 +4,7 @@ using System.Collections.Generic; using System.IO; using FluentAssertions; +using MathNet.Numerics.Random; using Nethermind.Core; using Nethermind.Core.Eip2930; using Nethermind.Core.Extensions; @@ -115,5 +116,89 @@ void Test(IReleaseSpec spec, bool isAfterRepricing) Test(Shanghai.Instance, true); Test(Cancun.Instance, true); } + public static IEnumerable<(AuthorizationTuple[] contractCode, long expectedCost)> AuthorizationListTestCaseSource() + { + yield return ( + [], 0); + yield return ( + [new AuthorizationTuple( + TestContext.CurrentContext.Random.NextULong(), + new Address(TestContext.CurrentContext.Random.NextBytes(20)), + TestContext.CurrentContext.Random.NextULong(), + TestContext.CurrentContext.Random.NextULong(), + TestContext.CurrentContext.Random.NextBytes(10), + TestContext.CurrentContext.Random.NextBytes(10)) + ], + GasCostOf.NewAccount); + yield return ( + [new AuthorizationTuple( + TestContext.CurrentContext.Random.NextULong(), + new Address(TestContext.CurrentContext.Random.NextBytes(20)), + TestContext.CurrentContext.Random.NextULong(), + TestContext.CurrentContext.Random.NextULong(), + TestContext.CurrentContext.Random.NextBytes(10), + TestContext.CurrentContext.Random.NextBytes(10)), + new AuthorizationTuple( + TestContext.CurrentContext.Random.NextULong(), + new Address(TestContext.CurrentContext.Random.NextBytes(20)), + TestContext.CurrentContext.Random.NextULong(), + TestContext.CurrentContext.Random.NextULong(), + TestContext.CurrentContext.Random.NextBytes(10), + TestContext.CurrentContext.Random.NextBytes(10)) + ], + GasCostOf.NewAccount * 2); + yield return ( + [new AuthorizationTuple( + TestContext.CurrentContext.Random.NextULong(), + new Address(TestContext.CurrentContext.Random.NextBytes(20)), + TestContext.CurrentContext.Random.NextULong(), + TestContext.CurrentContext.Random.NextULong(), + TestContext.CurrentContext.Random.NextBytes(10), + TestContext.CurrentContext.Random.NextBytes(10)), + new AuthorizationTuple( + TestContext.CurrentContext.Random.NextULong(), + new Address(TestContext.CurrentContext.Random.NextBytes(20)), + TestContext.CurrentContext.Random.NextULong(), + TestContext.CurrentContext.Random.NextULong(), + TestContext.CurrentContext.Random.NextBytes(10), + TestContext.CurrentContext.Random.NextBytes(10)), + new AuthorizationTuple( + TestContext.CurrentContext.Random.NextULong(), + new Address(TestContext.CurrentContext.Random.NextBytes(20)), + TestContext.CurrentContext.Random.NextULong(), + TestContext.CurrentContext.Random.NextULong(), + TestContext.CurrentContext.Random.NextBytes(10), + TestContext.CurrentContext.Random.NextBytes(10)) + ], + GasCostOf.NewAccount * 3); + } + [TestCaseSource(nameof(AuthorizationListTestCaseSource))] + public void Calculate_TxHasAuthorizationList_ReturnsExpectedCostOfTx((AuthorizationTuple[] AuthorizationList, long ExpectedCost) testCase) + { + Transaction tx = Build.A.Transaction.SignedAndResolved() + .WithAuthorizationCode(testCase.AuthorizationList) + .TestObject; + + IntrinsicGasCalculator.Calculate(tx, Prague.Instance) + .Should().Be(GasCostOf.Transaction + (testCase.ExpectedCost)); + } + + [Test] + public void Calculate_TxHasAuthorizationListBeforePrague_ThrowsInvalidDataException() + { + Transaction tx = Build.A.Transaction.SignedAndResolved() + .WithAuthorizationCode( + new AuthorizationTuple( + 0, + TestItem.AddressF, + 0, + TestContext.CurrentContext.Random.NextULong(), + TestContext.CurrentContext.Random.NextBytes(10), + TestContext.CurrentContext.Random.NextBytes(10)) + ) + .TestObject; + + Assert.That(() => IntrinsicGasCalculator.Calculate(tx, Cancun.Instance), Throws.InstanceOf()); + } } } diff --git a/src/Nethermind/Nethermind.Evm.Test/TransactionProcessorEip7702Tests.cs b/src/Nethermind/Nethermind.Evm.Test/TransactionProcessorEip7702Tests.cs new file mode 100644 index 00000000000..4fe6ab74f20 --- /dev/null +++ b/src/Nethermind/Nethermind.Evm.Test/TransactionProcessorEip7702Tests.cs @@ -0,0 +1,613 @@ +// SPDX-FileCopyrightText: 2023 Demerzel Solutions Limited +// SPDX-License-Identifier: LGPL-3.0-only + +using Nethermind.Core; +using Nethermind.Core.Extensions; +using Nethermind.Core.Specs; +using Nethermind.Specs; +using Nethermind.Core.Test.Builders; +using Nethermind.Crypto; +using Nethermind.Db; +using Nethermind.Evm.Tracing; +using Nethermind.Evm.TransactionProcessing; +using Nethermind.Logging; +using Nethermind.Specs.Forks; +using Nethermind.State; +using Nethermind.Trie.Pruning; +using NUnit.Framework; +using System.Collections.Generic; +using Nethermind.Core.Crypto; +using System; +using System.Linq; +using Nethermind.Int256; + +namespace Nethermind.Evm.Test; + +[TestFixture] +internal class TransactionProcessorEip7702Tests +{ + private ISpecProvider _specProvider; + private IEthereumEcdsa _ethereumEcdsa; + private TransactionProcessor _transactionProcessor; + private IWorldState _stateProvider; + + [SetUp] + public void Setup() + { + MemDb stateDb = new(); + _specProvider = new TestSpecProvider(Prague.Instance); + TrieStore trieStore = new(stateDb, LimboLogs.Instance); + _stateProvider = new WorldState(trieStore, new MemDb(), LimboLogs.Instance); + CodeInfoRepository codeInfoRepository = new(); + VirtualMachine virtualMachine = new(new TestBlockhashProvider(_specProvider), _specProvider, codeInfoRepository, LimboLogs.Instance); + _transactionProcessor = new TransactionProcessor(_specProvider, _stateProvider, virtualMachine, codeInfoRepository, LimboLogs.Instance); + _ethereumEcdsa = new EthereumEcdsa(_specProvider.ChainId); + } + + [Test] + public void Execute_TxHasAuthorizationWithCodeThatSavesCallerAddress_ExpectedAddressIsSaved() + { + PrivateKey sender = TestItem.PrivateKeyA; + PrivateKey signer = TestItem.PrivateKeyB; + Address codeSource = TestItem.AddressC; + _stateProvider.CreateAccount(sender.Address, 1.Ether()); + //Save caller in storage slot 0 + byte[] code = Prepare.EvmCode + .Op(Instruction.CALLER) + .Op(Instruction.PUSH0) + .Op(Instruction.SSTORE) + .Done; + DeployCode(codeSource, code); + + Transaction tx = Build.A.Transaction + .WithType(TxType.SetCode) + .WithTo(signer.Address) + .WithGasLimit(100_000) + .WithAuthorizationCode(_ethereumEcdsa.Sign(signer, _specProvider.ChainId, codeSource, 0)) + .SignedAndResolved(_ethereumEcdsa, sender, true) + .TestObject; + Block block = Build.A.Block.WithNumber(long.MaxValue) + .WithTimestamp(MainnetSpecProvider.PragueBlockTimestamp) + .WithTransactions(tx) + .WithGasLimit(10000000).TestObject; + + _transactionProcessor.Execute(tx, block.Header, NullTxTracer.Instance); + + ReadOnlySpan cell = _stateProvider.Get(new StorageCell(signer.Address, 0)); + + Assert.That(new Address(cell.ToArray()), Is.EqualTo(sender.Address)); + } + + public static IEnumerable DelegatedAndNotDelegatedCodeCases() + { + byte[] delegatedCode = new byte[23]; + Eip7702Constants.DelegationHeader.CopyTo(delegatedCode); + yield return new object[] { delegatedCode, true }; + yield return new object[] { Prepare.EvmCode.Op(Instruction.GAS).Done, false }; + } + [TestCaseSource(nameof(DelegatedAndNotDelegatedCodeCases))] + public void Execute_TxHasAuthorizationCodeButAuthorityHasCode_OnlyInsertIfExistingCodeIsDelegated(byte[] authorityCode, bool shouldInsert) + { + PrivateKey sender = TestItem.PrivateKeyA; + PrivateKey signer = TestItem.PrivateKeyB; + Address codeSource = TestItem.AddressC; + _stateProvider.CreateAccount(sender.Address, 1.Ether()); + //Save caller in storage slot 0 + byte[] code = Prepare.EvmCode + .Op(Instruction.CALLER) + .Op(Instruction.PUSH0) + .Op(Instruction.SSTORE) + .Done; + DeployCode(codeSource, code); + DeployCode(signer.Address, authorityCode); + + Transaction tx = Build.A.Transaction + .WithType(TxType.SetCode) + .WithTo(signer.Address) + .WithGasLimit(60_000) + .WithAuthorizationCode(_ethereumEcdsa.Sign(signer, _specProvider.ChainId, codeSource, 0)) + .SignedAndResolved(_ethereumEcdsa, sender, true) + .TestObject; + Block block = Build.A.Block.WithNumber(long.MaxValue) + .WithTimestamp(MainnetSpecProvider.PragueBlockTimestamp) + .WithTransactions(tx) + .WithGasLimit(10000000).TestObject; + + _transactionProcessor.Execute(tx, block.Header, NullTxTracer.Instance); + + ReadOnlySpan signerCode = _stateProvider.GetCode(signer.Address); + + byte[] expectedCode = shouldInsert ? [.. Eip7702Constants.DelegationHeader, .. codeSource.Bytes] : authorityCode; + + Assert.That(signerCode.ToArray(), Is.EquivalentTo(expectedCode)); + } + + public static IEnumerable SenderSignerCases() + { + yield return new object[] { TestItem.PrivateKeyA, TestItem.PrivateKeyB, 0ul }; + yield return new object[] { TestItem.PrivateKeyA, TestItem.PrivateKeyA, 1ul }; + } + [TestCaseSource(nameof(SenderSignerCases))] + public void Execute_SenderAndSignerIsTheSameOrNotWithCodeThatSavesCallerAddress_SenderAddressIsSaved(PrivateKey sender, PrivateKey signer, ulong nonce) + { + Address codeSource = TestItem.AddressC; + _stateProvider.CreateAccount(sender.Address, 1.Ether()); + //Save caller in storage slot 0 + byte[] code = Prepare.EvmCode + .Op(Instruction.CALLER) + .Op(Instruction.PUSH0) + .Op(Instruction.SSTORE) + .Done; + DeployCode(codeSource, code); + + Transaction tx = Build.A.Transaction + .WithType(TxType.SetCode) + .WithTo(signer.Address) + .WithGasLimit(600_000) + .WithAuthorizationCode(_ethereumEcdsa.Sign(signer, _specProvider.ChainId, codeSource, nonce)) + .SignedAndResolved(_ethereumEcdsa, sender, true) + .TestObject; + Block block = Build.A.Block.WithNumber(long.MaxValue) + .WithTimestamp(MainnetSpecProvider.PragueBlockTimestamp) + .WithTransactions(tx) + .WithGasLimit(10000000).TestObject; + + _transactionProcessor.Execute(tx, block.Header, NullTxTracer.Instance); + + ReadOnlySpan cellValue = _stateProvider.Get(new StorageCell(signer.Address, 0)); + + Assert.That(cellValue.ToArray(), Is.EqualTo(sender.Address.Bytes)); + } + public static IEnumerable DifferentCommitValues() + { + //Base case + yield return new object[] { 1ul, 0ul, TestItem.AddressA.Bytes }; + //Wrong nonce + yield return new object[] { 1ul, 1ul, new[] { (byte)0x0 } }; + //Wrong chain id + yield return new object[] { 2ul, 0ul, new[] { (byte)0x0 } }; + } + + [TestCaseSource(nameof(DifferentCommitValues))] + public void Execute_CommitMessageHasDifferentData_ExpectedAddressIsSavedInStorageSlot(ulong chainId, ulong nonce, byte[] expectedStorageValue) + { + PrivateKey sender = TestItem.PrivateKeyA; + PrivateKey signer = TestItem.PrivateKeyB; + Address codeSource = TestItem.AddressC; + _stateProvider.CreateAccount(sender.Address, 1.Ether()); + //Save caller in storage slot 0 + byte[] code = Prepare.EvmCode + .Op(Instruction.CALLER) + .Op(Instruction.PUSH0) + .Op(Instruction.SSTORE) + .Done; + DeployCode(codeSource, code); + + Transaction tx = Build.A.Transaction + .WithType(TxType.SetCode) + .WithTo(signer.Address) + .WithGasLimit(100_000) + .WithAuthorizationCode(_ethereumEcdsa.Sign(signer, chainId, codeSource, nonce)) + .SignedAndResolved(_ethereumEcdsa, sender, true) + .TestObject; + Block block = Build.A.Block.WithNumber(long.MaxValue) + .WithTimestamp(MainnetSpecProvider.PragueBlockTimestamp) + .WithTransactions(tx) + .WithGasLimit(10000000).TestObject; + + _transactionProcessor.Execute(tx, block.Header, NullTxTracer.Instance); + + var actual = _stateProvider.Get(new StorageCell(signer.Address, 0)).ToArray(); + Assert.That(actual, Is.EqualTo(expectedStorageValue)); + } + + [TestCase(0)] + [TestCase(1)] + [TestCase(10)] + [TestCase(99)] + public void Execute_TxHasDifferentAmountOfAuthorizedCode_UsedGasIsExpected(int count) + { + PrivateKey sender = TestItem.PrivateKeyA; + PrivateKey signer = TestItem.PrivateKeyB; + _stateProvider.CreateAccount(sender.Address, 1.Ether()); + + Transaction tx = Build.A.Transaction + .WithType(TxType.SetCode) + .WithTo(signer.Address) + .WithGasLimit(GasCostOf.Transaction + GasCostOf.NewAccount * count) + .WithAuthorizationCode(Enumerable.Range(0, count) + .Select(i => _ethereumEcdsa.Sign( + signer, + _specProvider.ChainId, + TestItem.AddressC, + 0)).ToArray()) + .SignedAndResolved(_ethereumEcdsa, sender, true) + .TestObject; + Block block = Build.A.Block.WithNumber(long.MaxValue) + .WithTimestamp(MainnetSpecProvider.PragueBlockTimestamp) + .WithTransactions(tx) + .WithGasLimit(100000000).TestObject; + + CallOutputTracer tracer = new(); + + _transactionProcessor.Execute(tx, block.Header, tracer); + + Assert.That(tracer.GasSpent, Is.EqualTo(GasCostOf.Transaction + GasCostOf.NewAccount * count)); + } + + private static IEnumerable EvmExecutionErrorCases() + { + byte[] runOutOfGasCode = Prepare.EvmCode + .Op(Instruction.CALLER) + .Op(Instruction.BALANCE) + .Op(Instruction.PUSH0) + .Op(Instruction.JUMP) + .Done; + yield return new object[] { runOutOfGasCode }; + byte[] revertExecution = Prepare.EvmCode + .Op(Instruction.REVERT) + .Done; + yield return new object[] { revertExecution }; + } + [TestCaseSource(nameof(EvmExecutionErrorCases))] + public void Execute_TxWithDelegationRunsOutOfGas_DelegationRefundIsStillApplied(byte[] executionErrorCode) + { + PrivateKey sender = TestItem.PrivateKeyA; + Address codeSource = TestItem.AddressB; + + _stateProvider.CreateAccount(codeSource, 0); + _stateProvider.InsertCode(codeSource, executionErrorCode, Prague.Instance); + _stateProvider.CreateAccount(sender.Address, 1.Ether()); + + const long gasLimit = 10_000_000; + Transaction tx = Build.A.Transaction + .WithType(TxType.SetCode) + .WithTo(codeSource) + .WithGasLimit(gasLimit) + .WithAuthorizationCode( + _ethereumEcdsa.Sign( + sender, + _specProvider.ChainId, + Address.Zero, + 1) + ) + .SignedAndResolved(_ethereumEcdsa, sender, true) + .TestObject; + Block block = Build.A.Block.WithNumber(long.MaxValue) + .WithTimestamp(MainnetSpecProvider.PragueBlockTimestamp) + .WithTransactions(tx) + .WithGasLimit(long.MaxValue).TestObject; + + CallOutputTracer tracer = new(); + + _transactionProcessor.Execute(tx, block.Header, tracer); + + Assert.That(tracer.GasSpent, Is.EqualTo(gasLimit - GasCostOf.NewAccount + GasCostOf.PerAuthBaseCost)); + } + + [Test] + public void Execute_TxAuthorizationListWithBALANCE_WarmAccountReadGasIsCharged() + { + PrivateKey sender = TestItem.PrivateKeyA; + PrivateKey signer = TestItem.PrivateKeyB; + Address codeSource = TestItem.AddressC; + _stateProvider.CreateAccount(sender.Address, 1.Ether()); + + byte[] code = Prepare.EvmCode + .PushData(signer.Address) + .Op(Instruction.BALANCE) + .Done; + DeployCode(codeSource, code); + + Transaction tx = Build.A.Transaction + .WithType(TxType.SetCode) + .WithTo(signer.Address) + .WithGasLimit(60_000) + .WithAuthorizationCode( + _ethereumEcdsa.Sign( + signer, + _specProvider.ChainId, + codeSource, + 0)) + .SignedAndResolved(_ethereumEcdsa, sender, true) + .TestObject; + Block block = Build.A.Block.WithNumber(long.MaxValue) + .WithTimestamp(MainnetSpecProvider.PragueBlockTimestamp) + .WithTransactions(tx) + .WithGasLimit(100000000).TestObject; + + CallOutputTracer tracer = new(); + + _transactionProcessor.Execute(tx, block.Header, tracer); + //Tx should only be charged for warm state read + Assert.That(tracer.GasSpent, Is.EqualTo(GasCostOf.Transaction + + GasCostOf.NewAccount + + Prague.Instance.GetBalanceCost() + + GasCostOf.WarmStateRead + + GasCostOf.VeryLow)); + } + + [TestCase(2)] + [TestCase(1)] + public void Execute_AuthorizationListHasSameAuthorityButDifferentCode_OnlyLastInstanceIsUsed(int expectedStoredValue) + { + PrivateKey sender = TestItem.PrivateKeyA; + PrivateKey signer = TestItem.PrivateKeyB; + Address firstCodeSource = TestItem.AddressC; + Address secondCodeSource = TestItem.AddressD; + _stateProvider.CreateAccount(sender.Address, 1.Ether()); + + byte[] firstCode = Prepare.EvmCode + .PushData(0) + .Op(Instruction.PUSH0) + .Op(Instruction.SSTORE) + .Done; + DeployCode(firstCodeSource, firstCode); + + byte[] secondCode = Prepare.EvmCode + .PushData(expectedStoredValue) + .Op(Instruction.PUSH0) + .Op(Instruction.SSTORE) + .Done; + DeployCode(secondCodeSource, secondCode); + + AuthorizationTuple[] authList = [ + _ethereumEcdsa.Sign( + signer, + _specProvider.ChainId, + firstCodeSource, + 0), + _ethereumEcdsa.Sign( + signer, + _specProvider.ChainId, + secondCodeSource, + 1), + ]; + Transaction tx = Build.A.Transaction + .WithType(TxType.SetCode) + .WithTo(signer.Address) + .WithGasLimit(100_000) + .WithAuthorizationCode(authList) + .SignedAndResolved(_ethereumEcdsa, sender, true) + .TestObject; + Block block = Build.A.Block.WithNumber(long.MaxValue) + .WithTimestamp(MainnetSpecProvider.PragueBlockTimestamp) + .WithTransactions(tx) + .WithGasLimit(10000000).TestObject; + + _transactionProcessor.Execute(tx, block.Header, NullTxTracer.Instance); + + Assert.That(_stateProvider.Get(new StorageCell(signer.Address, 0)).ToArray(), Is.EquivalentTo(new[] { expectedStoredValue })); + } + + [TestCase] + public void Execute_FirstTxHasAuthorizedCodeThatIncrementsAndSecondDoesNot_StorageSlotIsOnlyIncrementedOnce() + { + PrivateKey sender = TestItem.PrivateKeyA; + PrivateKey signer = TestItem.PrivateKeyB; + Address codeSource = TestItem.AddressC; + _stateProvider.CreateAccount(sender.Address, 1.Ether()); + //Increment 1 everytime it's called + byte[] code = Prepare.EvmCode + .Op(Instruction.PUSH0) + .Op(Instruction.SLOAD) + .PushData(1) + .Op(Instruction.ADD) + .Op(Instruction.PUSH0) + .Op(Instruction.SSTORE) + .Done; + DeployCode(codeSource, code); + + Transaction tx1 = Build.A.Transaction + .WithType(TxType.SetCode) + .WithTo(signer.Address) + .WithGasLimit(60_000) + .WithAuthorizationCode(_ethereumEcdsa.Sign( + signer, + _specProvider.ChainId, + codeSource, + 0)) + .SignedAndResolved(_ethereumEcdsa, sender, true) + .TestObject; + Transaction tx2 = Build.A.Transaction + .WithType(TxType.SetCode) + .WithNonce(1) + .WithTo(signer.Address) + .WithGasLimit(60_000) + .WithAuthorizationCode([]) + .SignedAndResolved(_ethereumEcdsa, sender, true) + .TestObject; + Block block = Build.A.Block.WithNumber(long.MaxValue) + .WithTimestamp(MainnetSpecProvider.PragueBlockTimestamp) + .WithTransactions(tx1, tx2) + .WithGasLimit(10000000).TestObject; + + _transactionProcessor.Execute(tx1, block.Header, NullTxTracer.Instance); + _transactionProcessor.Execute(tx2, block.Header, NullTxTracer.Instance); + + Assert.That(_stateProvider.Get(new StorageCell(signer.Address, 0)).ToArray(), Is.EquivalentTo(new[] { 1 })); + } + + public static IEnumerable OpcodesWithEXT() + { + //EXTCODESIZE should return the size of the delegated code + yield return new object[] { + Prepare.EvmCode + .PushData(TestItem.AddressA) + .Op(Instruction.EXTCODESIZE) + .Op(Instruction.PUSH0) + .Op(Instruction.SSTORE) + .Done, + new byte[]{ 2 + 22 } }; + //EXTCODEHASH should return the HASH of the delegated code + yield return new object[] { + Prepare.EvmCode + .PushData(TestItem.AddressA) + .Op(Instruction.EXTCODEHASH) + .Op(Instruction.PUSH0) + .Op(Instruction.SSTORE) + .Done, + Keccak.Compute( + Prepare.EvmCode + .PushData(TestItem.AddressA) + .Op(Instruction.EXTCODEHASH) + .Op(Instruction.PUSH0) + .Op(Instruction.SSTORE) + .Done).Bytes.ToArray() + }; + //EXTCOPYCODE should copy the delegated code + byte[] code = Prepare.EvmCode + .PushData(TestItem.AddressA) + .Op(Instruction.DUP1) + .Op(Instruction.EXTCODESIZE) + .Op(Instruction.PUSH0) + .Op(Instruction.PUSH0) + .Op(Instruction.DUP4) + .Op(Instruction.EXTCODECOPY) + .Op(Instruction.PUSH0) + .Op(Instruction.MLOAD) + .Op(Instruction.PUSH0) + .Op(Instruction.SSTORE) + .Op(Instruction.STOP) + .Done; + yield return new object[] + { + code, + code + }; + } + [TestCaseSource(nameof(OpcodesWithEXT))] + public void Execute_DelegatedCodeUsesEXTOPCODES_StoresExpectedValue(byte[] code, byte[] expectedValue) + { + PrivateKey signer = TestItem.PrivateKeyA; + PrivateKey sender = TestItem.PrivateKeyB; + Address codeSource = TestItem.AddressC; + _stateProvider.CreateAccount(sender.Address, 1.Ether()); + + DeployCode(codeSource, code); + + Transaction tx = Build.A.Transaction + .WithType(TxType.SetCode) + .WithTo(signer.Address) + .WithGasLimit(100_000) + .WithAuthorizationCode(_ethereumEcdsa.Sign( + signer, + _specProvider.ChainId, + codeSource, + 0)) + .SignedAndResolved(_ethereumEcdsa, sender, true) + .TestObject; + Block block = Build.A.Block.WithNumber(long.MaxValue) + .WithTimestamp(MainnetSpecProvider.PragueBlockTimestamp) + .WithTransactions(tx) + .WithGasLimit(10000000).TestObject; + + TransactionResult result = _transactionProcessor.Execute(tx, block.Header, NullTxTracer.Instance); + Assert.That(_stateProvider.Get(new StorageCell(signer.Address, 0)).ToArray(), Is.EquivalentTo(expectedValue)); + } + + public static IEnumerable CountsAsAccessedCases() + { + EthereumEcdsa ethereumEcdsa = new(BlockchainIds.GenericNonRealNetwork); + + yield return new object[] + { + new AuthorizationTuple[] + { + ethereumEcdsa.Sign(TestItem.PrivateKeyA, 1, TestItem.AddressF, 0), + ethereumEcdsa.Sign(TestItem.PrivateKeyB, 1, TestItem.AddressF, 0), + }, + new Address[] + { + TestItem.AddressA, + TestItem.AddressB + } + }; + yield return new object[] + { + new AuthorizationTuple[] + { + ethereumEcdsa.Sign(TestItem.PrivateKeyA, 1, TestItem.AddressF, 0), + ethereumEcdsa.Sign(TestItem.PrivateKeyB, 2, TestItem.AddressF, 0), + }, + new Address[] + { + TestItem.AddressA, + } + }; + yield return new object[] + { + new AuthorizationTuple[] + { + ethereumEcdsa.Sign(TestItem.PrivateKeyA, 1, TestItem.AddressF, 0), + //Bad signature + new AuthorizationTuple(1, TestItem.AddressF, 0, new Signature(new byte[65]), TestItem.AddressA) + }, + new Address[] + { + TestItem.AddressA, + } + }; + } + + [TestCaseSource(nameof(CountsAsAccessedCases))] + public void Execute_CombinationOfValidAndInvalidTuples_AddsTheCorrectAddressesToAccessedAddresses(AuthorizationTuple[] tuples, Address[] shouldCountAsAccessed) + { + PrivateKey sender = TestItem.PrivateKeyA; + _stateProvider.CreateAccount(sender.Address, 1.Ether()); + + Transaction tx = Build.A.Transaction + .WithType(TxType.SetCode) + .WithTo(TestItem.AddressB) + .WithGasLimit(100_000) + .WithAuthorizationCode(tuples) + .SignedAndResolved(_ethereumEcdsa, sender, true) + .TestObject; + Block block = Build.A.Block.WithNumber(long.MaxValue) + .WithTimestamp(MainnetSpecProvider.PragueBlockTimestamp) + .WithTransactions(tx) + .WithGasLimit(10000000).TestObject; + + AccessTxTracer txTracer = new AccessTxTracer(); + TransactionResult result = _transactionProcessor.Execute(tx, block.Header, txTracer); + Assert.That(txTracer.AccessList.Select(a => a.Address), Is.SupersetOf(shouldCountAsAccessed)); + } + + [TestCase(true)] + [TestCase(false)] + public void Execute_AuthorityAccountExistsOrNot_NonceIsIncrementedByOne(bool accountExists) + { + PrivateKey authority = TestItem.PrivateKeyA; + PrivateKey sender = TestItem.PrivateKeyB; + + if (accountExists) + _stateProvider.CreateAccount(authority.Address, 0); + _stateProvider.CreateAccount(sender.Address, 1.Ether()); + + AuthorizationTuple[] tuples = + { + _ethereumEcdsa.Sign(authority, 1, sender.Address, 0), + }; + + Transaction tx = Build.A.Transaction + .WithType(TxType.SetCode) + .WithTo(TestItem.AddressB) + .WithGasLimit(100_000) + .WithAuthorizationCode(tuples) + .SignedAndResolved(_ethereumEcdsa, sender, true) + .TestObject; + Block block = Build.A.Block.WithNumber(long.MaxValue) + .WithTimestamp(MainnetSpecProvider.PragueBlockTimestamp) + .WithTransactions(tx) + .WithGasLimit(10000000).TestObject; + TransactionResult result = _transactionProcessor.Execute(tx, block.Header, NullTxTracer.Instance); + + Assert.That(_stateProvider.GetNonce(authority.Address), Is.EqualTo((UInt256)1)); + } + + private void DeployCode(Address codeSource, byte[] code) + { + _stateProvider.CreateAccountIfNotExists(codeSource, 0); + _stateProvider.InsertCode(codeSource, ValueKeccak.Compute(code), code, _specProvider.GetSpec(MainnetSpecProvider.PragueActivation)); + } +} diff --git a/src/Nethermind/Nethermind.Evm.Test/VirtualMachineTestsBase.cs b/src/Nethermind/Nethermind.Evm.Test/VirtualMachineTestsBase.cs index 6df49e3f481..6960cc8b18c 100644 --- a/src/Nethermind/Nethermind.Evm.Test/VirtualMachineTestsBase.cs +++ b/src/Nethermind/Nethermind.Evm.Test/VirtualMachineTestsBase.cs @@ -124,10 +124,22 @@ protected TestAllTracerWithOutput Execute(ForkActivation activation, params byte return tracer; } + protected TestAllTracerWithOutput Execute(ForkActivation activation, Transaction tx) + { + (Block block, _) = PrepareTx(activation, 100000, null); + TestAllTracerWithOutput tracer = CreateTracer(); + _processor.Execute(tx, block.Header, tracer); + return tracer; + } + protected TestAllTracerWithOutput Execute(params byte[] code) { return Execute(Activation, code); } + protected TestAllTracerWithOutput Execute(Transaction tx) + { + return Execute(Activation, tx); + } protected virtual TestAllTracerWithOutput CreateTracer() => new(); @@ -197,7 +209,8 @@ protected TestAllTracerWithOutput Execute(ForkActivation activation, long gasLim int value = 1, long blockGasLimit = DefaultBlockGasLimit, byte[][]? blobVersionedHashes = null, - ulong excessBlobGas = 0) + ulong excessBlobGas = 0, + Transaction transaction = null) { senderRecipientAndMiner ??= SenderRecipientAndMiner.Default; @@ -227,7 +240,7 @@ protected TestAllTracerWithOutput Execute(ForkActivation activation, long gasLim TestState.CommitTree(0); GetLogManager().GetClassLogger().Debug("Committed initial tree"); - Transaction transaction = Build.A.Transaction + transaction ??= Build.A.Transaction .WithGasLimit(gasLimit) .WithGasPrice(1) .WithValue(value) diff --git a/src/Nethermind/Nethermind.Evm.Test/WithdrawalRequestsProcessorTests.cs b/src/Nethermind/Nethermind.Evm.Test/WithdrawalRequestsProcessorTests.cs index 820809bf95f..279b668e424 100644 --- a/src/Nethermind/Nethermind.Evm.Test/WithdrawalRequestsProcessorTests.cs +++ b/src/Nethermind/Nethermind.Evm.Test/WithdrawalRequestsProcessorTests.cs @@ -32,11 +32,8 @@ public class WithdrawalRequestProcessorTests private IEthereumEcdsa _ethereumEcdsa; private ITransactionProcessor _transactionProcessor; private IWorldState _stateProvider; - private ICodeInfoRepository _codeInfoRepository; - private static readonly UInt256 AccountBalance = 1.Ether(); - private readonly Address eip7002Account = Eip7002Constants.WithdrawalRequestPredeployAddress; [SetUp] @@ -72,7 +69,7 @@ public void Setup() public void ShouldProcessWithdrawalRequest() { IReleaseSpec spec = Substitute.For(); - spec.IsEip7002Enabled.Returns(true); + spec.WithdrawalRequestsEnabled.Returns(true); spec.Eip7002ContractAddress.Returns(eip7002Account); Block block = Build.A.Block.TestObject; @@ -86,7 +83,7 @@ public void ShouldProcessWithdrawalRequest() Amount = 0 }; - var withdrawalRequests = withdrawalRequestsProcessor.ReadWithdrawalRequests(block, _stateProvider, spec).ToList(); + var withdrawalRequests = withdrawalRequestsProcessor.ReadRequests(block, _stateProvider, spec).ToList(); Assert.That(withdrawalRequests, Has.Count.EqualTo(16)); diff --git a/src/Nethermind/Nethermind.Evm/CodeInfoRepository.cs b/src/Nethermind/Nethermind.Evm/CodeInfoRepository.cs index 1dd64dabe4d..99e8d3720d5 100644 --- a/src/Nethermind/Nethermind.Evm/CodeInfoRepository.cs +++ b/src/Nethermind/Nethermind.Evm/CodeInfoRepository.cs @@ -16,49 +16,12 @@ using Nethermind.Evm.Precompiles.Bls; using Nethermind.Evm.Precompiles.Snarks; using Nethermind.State; +using Nethermind.Crypto; namespace Nethermind.Evm; public class CodeInfoRepository : ICodeInfoRepository { - internal sealed class CodeLruCache - { - private const int CacheCount = 16; - private const int CacheMax = CacheCount - 1; - private readonly ClockCache[] _caches; - - public CodeLruCache() - { - _caches = new ClockCache[CacheCount]; - for (int i = 0; i < _caches.Length; i++) - { - // Cache per nibble to reduce contention as TxPool is very parallel - _caches[i] = new ClockCache(MemoryAllowance.CodeCacheSize / CacheCount); - } - } - - public CodeInfo? Get(in ValueHash256 codeHash) - { - ClockCache cache = _caches[GetCacheIndex(codeHash)]; - return cache.Get(codeHash); - } - - public bool Set(in ValueHash256 codeHash, CodeInfo codeInfo) - { - ClockCache cache = _caches[GetCacheIndex(codeHash)]; - return cache.Set(codeHash, codeInfo); - } - - private static int GetCacheIndex(in ValueHash256 codeHash) => codeHash.Bytes[^1] & CacheMax; - - public bool TryGet(in ValueHash256 codeHash, [NotNullWhen(true)] out CodeInfo? codeInfo) - { - codeInfo = Get(codeHash); - return codeInfo is not null; - } - } - - private static readonly FrozenDictionary _precompiles = InitializePrecompiledContracts(); private static readonly CodeLruCache _codeCache = new(); private readonly FrozenDictionary _localPrecompiles; @@ -102,13 +65,26 @@ public CodeInfoRepository(ConcurrentDictionary kvp.Key, kvp => CreateCachedPrecompile(kvp, precompileCache)); } - public CodeInfo GetCachedCodeInfo(IWorldState worldState, Address codeSource, IReleaseSpec vmSpec) + public CodeInfo GetCachedCodeInfo(IWorldState worldState, Address codeSource, IReleaseSpec vmSpec, out Address? delegationAddress) { + delegationAddress = null; if (codeSource.IsPrecompile(vmSpec)) { return _localPrecompiles[codeSource]; } + CodeInfo cachedCodeInfo = InternalGetCachedCode(worldState, codeSource); + + if (TryGetDelegatedAddress(cachedCodeInfo.MachineCode.Span, out delegationAddress)) + { + cachedCodeInfo = InternalGetCachedCode(worldState, delegationAddress); + } + + return cachedCodeInfo; + } + + private static CodeInfo InternalGetCachedCode(IReadOnlyStateProvider worldState, Address codeSource) + { CodeInfo? cachedCodeInfo = null; ValueHash256 codeHash = worldState.GetCodeHash(codeSource); if (codeHash == Keccak.OfAnEmptyString.ValueHash256) @@ -145,28 +121,61 @@ static void MissingCode(Address codeSource, in ValueHash256 codeHash) } } - public CodeInfo GetOrAdd(ValueHash256 codeHash, ReadOnlySpan initCode) + public void InsertCode(IWorldState state, ReadOnlyMemory code, Address codeOwner, IReleaseSpec spec) { - if (!_codeCache.TryGet(codeHash, out CodeInfo? codeInfo)) - { - codeInfo = new(initCode.ToArray()); + CodeInfo codeInfo = new(code); + codeInfo.AnalyseInBackgroundIfRequired(); - // Prime the code cache as likely to be used by more txs - _codeCache.Set(codeHash, codeInfo); - } + ValueHash256 codeHash = code.Length == 0 ? ValueKeccak.OfAnEmptyString : ValueKeccak.Compute(code.Span); + state.InsertCode(codeOwner, codeHash, code, spec); + _codeCache.Set(codeHash, codeInfo); + } - return codeInfo; + public void SetDelegation(IWorldState state, Address codeSource, Address authority, IReleaseSpec spec) + { + byte[] authorizedBuffer = new byte[Eip7702Constants.DelegationHeader.Length + Address.Size]; + Eip7702Constants.DelegationHeader.CopyTo(authorizedBuffer); + codeSource.Bytes.CopyTo(authorizedBuffer, Eip7702Constants.DelegationHeader.Length); + ValueHash256 codeHash = ValueKeccak.Compute(authorizedBuffer); + state.InsertCode(authority, codeHash, authorizedBuffer.AsMemory(), spec); + _codeCache.Set(codeHash, new CodeInfo(authorizedBuffer)); } + /// + /// Retrieves code hash of delegation if delegated. Otherwise code hash of . + /// + /// + /// + public ValueHash256 GetExecutableCodeHash(IWorldState worldState, Address address) + { + ValueHash256 codeHash = worldState.GetCodeHash(address); + if (codeHash == Keccak.OfAnEmptyString.ValueHash256) + { + return Keccak.OfAnEmptyString.ValueHash256; + } - public void InsertCode(IWorldState state, ReadOnlyMemory code, Address codeOwner, IReleaseSpec spec) + CodeInfo codeInfo = InternalGetCachedCode(worldState, address); + return codeInfo.IsEmpty + ? Keccak.OfAnEmptyString.ValueHash256 + : TryGetDelegatedAddress(codeInfo.MachineCode.Span, out Address? delegationAddress) + ? worldState.GetCodeHash(delegationAddress) + : codeHash; + } + + /// + /// Parses delegation code to extract the contained address. + /// Assumes is delegation code! + /// + private static bool TryGetDelegatedAddress(ReadOnlySpan code, [NotNullWhen(true)] out Address? address) { - CodeInfo codeInfo = new(code); - codeInfo.AnalyseInBackgroundIfRequired(); + if (Eip7702Constants.IsDelegatedCode(code)) + { + address = new Address(code.Slice(Eip7702Constants.DelegationHeader.Length).ToArray()); + return true; + } - Hash256 codeHash = code.Length == 0 ? Keccak.OfAnEmptyString : Keccak.Compute(code.Span); - state.InsertCode(codeOwner, codeHash, code, spec); - _codeCache.Set(codeHash, codeInfo); + address = null; + return false; } private CodeInfo CreateCachedPrecompile( @@ -174,6 +183,9 @@ private CodeInfo CreateCachedPrecompile( ConcurrentDictionary, bool)> cache) => new(new CachedPrecompile(originalPrecompile.Key.Value, originalPrecompile.Value.Precompile!, cache)); + public bool TryGetDelegation(IReadOnlyStateProvider worldState, Address address, [NotNullWhen(true)] out Address? delegatedAddress) => + TryGetDelegatedAddress(InternalGetCachedCode(worldState, address).MachineCode.Span, out delegatedAddress); + private class CachedPrecompile( Address address, IPrecompile precompile, @@ -199,4 +211,42 @@ private class CachedPrecompile( return result; } } + + private sealed class CodeLruCache + { + private const int CacheCount = 16; + private const int CacheMax = CacheCount - 1; + private readonly ClockCache[] _caches; + + public CodeLruCache() + { + _caches = new ClockCache[CacheCount]; + for (int i = 0; i < _caches.Length; i++) + { + // Cache per nibble to reduce contention as TxPool is very parallel + _caches[i] = new ClockCache(MemoryAllowance.CodeCacheSize / CacheCount); + } + } + + public CodeInfo? Get(in ValueHash256 codeHash) + { + ClockCache cache = _caches[GetCacheIndex(codeHash)]; + return cache.Get(codeHash); + } + + public bool Set(in ValueHash256 codeHash, CodeInfo codeInfo) + { + ClockCache cache = _caches[GetCacheIndex(codeHash)]; + return cache.Set(codeHash, codeInfo); + } + + private static int GetCacheIndex(in ValueHash256 codeHash) => codeHash.Bytes[^1] & CacheMax; + + public bool TryGet(in ValueHash256 codeHash, [NotNullWhen(true)] out CodeInfo? codeInfo) + { + codeInfo = Get(codeHash); + return codeInfo is not null; + } + } } + diff --git a/src/Nethermind/Nethermind.Evm/GasCostOf.cs b/src/Nethermind/Nethermind.Evm/GasCostOf.cs index 68dc8cae654..e67770b53e8 100644 --- a/src/Nethermind/Nethermind.Evm/GasCostOf.cs +++ b/src/Nethermind/Nethermind.Evm/GasCostOf.cs @@ -62,5 +62,6 @@ public static class GasCostOf public const long AccessStorageListEntry = 1900; // eip-2930 public const long TLoad = WarmStateRead; // eip-1153 public const long TStore = WarmStateRead; // eip-1153 + public const long PerAuthBaseCost = 2500; // eip-7702 } } diff --git a/src/Nethermind/Nethermind.Evm/ICodeInfoRepository.cs b/src/Nethermind/Nethermind.Evm/ICodeInfoRepository.cs index 6fde3cbfe7f..b2f0a4a176d 100644 --- a/src/Nethermind/Nethermind.Evm/ICodeInfoRepository.cs +++ b/src/Nethermind/Nethermind.Evm/ICodeInfoRepository.cs @@ -2,6 +2,8 @@ // SPDX-License-Identifier: LGPL-3.0-only using System; +using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; using Nethermind.Core; using Nethermind.Core.Crypto; using Nethermind.Core.Specs; @@ -12,7 +14,15 @@ namespace Nethermind.Evm; public interface ICodeInfoRepository { - CodeInfo GetCachedCodeInfo(IWorldState worldState, Address codeSource, IReleaseSpec vmSpec); - CodeInfo GetOrAdd(ValueHash256 codeHash, ReadOnlySpan initCode); + CodeInfo GetCachedCodeInfo(IWorldState worldState, Address codeSource, IReleaseSpec vmSpec, out Address? delegationAddress); + ValueHash256 GetExecutableCodeHash(IWorldState worldState, Address address); void InsertCode(IWorldState state, ReadOnlyMemory code, Address codeOwner, IReleaseSpec spec); + void SetDelegation(IWorldState state, Address codeSource, Address authority, IReleaseSpec spec); + bool TryGetDelegation(IReadOnlyStateProvider worldState, Address address, [NotNullWhen(true)] out Address? delegatedAddress); +} + +public static class CodeInfoRepositoryExtensions +{ + public static CodeInfo GetCachedCodeInfo(this ICodeInfoRepository codeInfoRepository, IWorldState worldState, Address codeSource, IReleaseSpec vmSpec) + => codeInfoRepository.GetCachedCodeInfo(worldState, codeSource, vmSpec, out _); } diff --git a/src/Nethermind/Nethermind.Evm/IVirtualMachine.cs b/src/Nethermind/Nethermind.Evm/IVirtualMachine.cs index 8099d6d7b6c..a5ca480a980 100644 --- a/src/Nethermind/Nethermind.Evm/IVirtualMachine.cs +++ b/src/Nethermind/Nethermind.Evm/IVirtualMachine.cs @@ -1,11 +1,6 @@ // SPDX-FileCopyrightText: 2022 Demerzel Solutions Limited // SPDX-License-Identifier: LGPL-3.0-only -using System; -using Nethermind.Core; -using Nethermind.Core.Crypto; -using Nethermind.Core.Specs; -using Nethermind.Evm.CodeAnalysis; using Nethermind.Evm.Tracing; using Nethermind.State; diff --git a/src/Nethermind/Nethermind.Evm/IntrinsicGasCalculator.cs b/src/Nethermind/Nethermind.Evm/IntrinsicGasCalculator.cs index 7742bcfc5ba..a8e5dc5c86f 100644 --- a/src/Nethermind/Nethermind.Evm/IntrinsicGasCalculator.cs +++ b/src/Nethermind/Nethermind.Evm/IntrinsicGasCalculator.cs @@ -2,52 +2,39 @@ // SPDX-License-Identifier: LGPL-3.0-only using System; +using System.Diagnostics; +using System.Diagnostics.CodeAnalysis; using System.IO; -using System.Numerics; using Nethermind.Core; using Nethermind.Core.Eip2930; using Nethermind.Core.Specs; using Nethermind.Int256; -using System.Runtime.Intrinsics; -using System.Runtime.InteropServices; -using System.Runtime.CompilerServices; using Nethermind.Core.Extensions; namespace Nethermind.Evm; public static class IntrinsicGasCalculator { - public static long Calculate(Transaction transaction, IReleaseSpec releaseSpec) - { - long result = GasCostOf.Transaction; - result += DataCost(transaction, releaseSpec); - result += CreateCost(transaction, releaseSpec); - result += AccessListCost(transaction, releaseSpec); - return result; - } - - private static long CreateCost(Transaction transaction, IReleaseSpec releaseSpec) - { - long createCost = 0; - if (transaction.IsContractCreation && releaseSpec.IsEip2Enabled) - { - createCost += GasCostOf.TxCreate; - } + public static long Calculate(Transaction transaction, IReleaseSpec releaseSpec) => + GasCostOf.Transaction + + DataCost(transaction, releaseSpec) + + CreateCost(transaction, releaseSpec) + + AccessListCost(transaction, releaseSpec) + + AuthorizationListCost(transaction, releaseSpec); - return createCost; - } + private static long CreateCost(Transaction transaction, IReleaseSpec releaseSpec) => + transaction.IsContractCreation && releaseSpec.IsEip2Enabled ? GasCostOf.TxCreate : 0; private static long DataCost(Transaction transaction, IReleaseSpec releaseSpec) { - long txDataNonZeroGasCost = - releaseSpec.IsEip2028Enabled ? GasCostOf.TxDataNonZeroEip2028 : GasCostOf.TxDataNonZero; + long txDataNonZeroGasCost = releaseSpec.IsEip2028Enabled ? GasCostOf.TxDataNonZeroEip2028 : GasCostOf.TxDataNonZero; Span data = transaction.Data.GetValueOrDefault().Span; int totalZeros = data.CountZeros(); - var baseDataCost = (transaction.IsContractCreation && releaseSpec.IsEip3860Enabled + long baseDataCost = transaction.IsContractCreation && releaseSpec.IsEip3860Enabled ? EvmPooledMemory.Div32Ceiling((UInt256)data.Length) * GasCostOf.InitCodeWord - : 0); + : 0; return baseDataCost + totalZeros * GasCostOf.TxDataZero + @@ -57,27 +44,48 @@ private static long DataCost(Transaction transaction, IReleaseSpec releaseSpec) private static long AccessListCost(Transaction transaction, IReleaseSpec releaseSpec) { AccessList? accessList = transaction.AccessList; - long accessListCost = 0; if (accessList is not null) { if (!releaseSpec.UseTxAccessLists) { - throw new InvalidDataException( - $"Transaction with an access list received within the context of {releaseSpec.Name}. Eip-2930 is not enabled."); + ThrowInvalidDataException(releaseSpec); } - if (accessList.IsEmpty) return accessListCost; + (int addressesCount, int storageKeysCount) = accessList.Count; + return addressesCount * GasCostOf.AccessAccountListEntry + storageKeysCount * GasCostOf.AccessStorageListEntry; + } + + return 0; + + [DoesNotReturn] + [StackTraceHidden] + static void ThrowInvalidDataException(IReleaseSpec releaseSpec) + { + throw new InvalidDataException($"Transaction with an authorization list received within the context of {releaseSpec.Name}. Eip-7702 is not enabled."); + } + } + + private static long AuthorizationListCost(Transaction transaction, IReleaseSpec releaseSpec) + { + AuthorizationTuple[]? transactionAuthorizationList = transaction.AuthorizationList; - foreach ((Address address, AccessList.StorageKeysEnumerable storageKeys) entry in accessList) + if (transactionAuthorizationList is not null) + { + if (!releaseSpec.IsAuthorizationListEnabled) { - accessListCost += GasCostOf.AccessAccountListEntry; - foreach (UInt256 _ in entry.storageKeys) - { - accessListCost += GasCostOf.AccessStorageListEntry; - } + ThrowInvalidDataException(releaseSpec); } + + return transactionAuthorizationList.Length * GasCostOf.NewAccount; } - return accessListCost; + return 0; + + [DoesNotReturn] + [StackTraceHidden] + static void ThrowInvalidDataException(IReleaseSpec releaseSpec) + { + throw new InvalidDataException($"Transaction with an authorization list received within the context of {releaseSpec.Name}. Eip-7702 is not enabled."); + } } } diff --git a/src/Nethermind/Nethermind.Evm/Tracing/TxTracer.cs b/src/Nethermind/Nethermind.Evm/Tracing/TxTracer.cs index 439427fb1c2..dee699912cc 100644 --- a/src/Nethermind/Nethermind.Evm/Tracing/TxTracer.cs +++ b/src/Nethermind/Nethermind.Evm/Tracing/TxTracer.cs @@ -10,7 +10,7 @@ namespace Nethermind.Evm.Tracing; -public class TxTracer : ITxTracer +public abstract class TxTracer : ITxTracer { [SuppressMessage("ReSharper", "VirtualMemberCallInConstructor")] protected TxTracer() diff --git a/src/Nethermind/Nethermind.Evm/TransactionProcessing/TransactionProcessor.cs b/src/Nethermind/Nethermind.Evm/TransactionProcessing/TransactionProcessor.cs index b21e56d310d..3ebf373ab25 100644 --- a/src/Nethermind/Nethermind.Evm/TransactionProcessing/TransactionProcessor.cs +++ b/src/Nethermind/Nethermind.Evm/TransactionProcessing/TransactionProcessor.cs @@ -2,6 +2,7 @@ // SPDX-License-Identifier: LGPL-3.0-only using System; +using System.Collections.Generic; using System.Diagnostics; using System.Diagnostics.CodeAnalysis; using System.IO; @@ -17,7 +18,6 @@ using Nethermind.Logging; using Nethermind.State; using Nethermind.State.Tracing; -using static Nethermind.Core.Extensions.MemoryExtensions; using static Nethermind.Evm.VirtualMachine; @@ -41,6 +41,7 @@ public abstract class TransactionProcessorBase : ITransactionProcessor private readonly ICodeInfoRepository _codeInfoRepository; private SystemTransactionProcessor? _systemTransactionProcessor; private readonly ILogManager _logManager; + private readonly HashSet
_accessedAddresses = []; [Flags] protected enum ExecutionOptions @@ -149,10 +150,13 @@ protected virtual TransactionResult Execute(Transaction tx, in BlockExecutionCon if (commit) WorldState.Commit(spec, tracer.IsTracingState ? tracer : NullTxTracer.Instance, commitStorageRoots: false); - ExecutionEnvironment env = BuildExecutionEnvironment(tx, in blCtx, spec, effectiveGasPrice); + _accessedAddresses.Clear(); + int delegationRefunds = ProcessDelegations(tx, spec, _accessedAddresses); + + ExecutionEnvironment env = BuildExecutionEnvironment(tx, in blCtx, spec, effectiveGasPrice, _codeInfoRepository, _accessedAddresses); long gasAvailable = tx.GasLimit - intrinsicGas; - ExecuteEvmCall(tx, header, spec, tracer, opts, gasAvailable, env, out TransactionSubstate? substate, out long spentGas, out byte statusCode); + ExecuteEvmCall(tx, header, spec, tracer, opts, delegationRefunds, _accessedAddresses, gasAvailable, env, out TransactionSubstate? substate, out long spentGas, out byte statusCode); PayFees(tx, header, spec, tracer, substate, spentGas, premiumPerGas, blobBaseFee, statusCode); // Finalize @@ -168,6 +172,7 @@ protected virtual TransactionResult Execute(Transaction tx, in BlockExecutionCon if (!opts.HasFlag(ExecutionOptions.NoValidation)) WorldState.AddToBalance(tx.SenderAddress!, senderReservedGasPayment, spec); DecrementNonce(tx); + WorldState.Commit(spec); } } @@ -200,6 +205,74 @@ protected virtual TransactionResult Execute(Transaction tx, in BlockExecutionCon return TransactionResult.Ok; } + private int ProcessDelegations(Transaction tx, IReleaseSpec spec, HashSet
accessedAddresses) + { + int refunds = 0; + if (spec.IsEip7702Enabled && tx.HasAuthorizationList) + { + foreach (AuthorizationTuple authTuple in tx.AuthorizationList) + { + authTuple.Authority ??= Ecdsa.RecoverAddress(authTuple); + + if (!IsValidForExecution(authTuple, accessedAddresses, out _)) + { + if (Logger.IsDebug) Logger.Debug($"Delegation {authTuple} is invalid"); + } + else + { + if (!WorldState.AccountExists(authTuple.Authority!)) + { + WorldState.CreateAccount(authTuple.Authority, 0, 1); + } + else + { + refunds++; + WorldState.IncrementNonce(authTuple.Authority); + } + + _codeInfoRepository.SetDelegation(WorldState, authTuple.CodeAddress, authTuple.Authority, spec); + } + } + + } + + return refunds; + + bool IsValidForExecution( + AuthorizationTuple authorizationTuple, + ISet
accessedAddresses, + [NotNullWhen(false)] out string? error) + { + if (authorizationTuple.Authority is null) + { + error = "Bad signature."; + return false; + } + if (authorizationTuple.ChainId != 0 && SpecProvider.ChainId != authorizationTuple.ChainId) + { + error = $"Chain id ({authorizationTuple.ChainId}) does not match."; + return false; + } + + accessedAddresses.Add(authorizationTuple.Authority); + + if (WorldState.HasCode(authorizationTuple.Authority) && !_codeInfoRepository.TryGetDelegation(WorldState, authorizationTuple.Authority, out _)) + { + error = $"Authority ({authorizationTuple.Authority}) has code deployed."; + return false; + } + 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; + } + + error = null; + return true; + } + } + protected virtual IReleaseSpec GetSpec(Transaction tx, BlockHeader header) => SpecProvider.GetSpec(header); private static void UpdateMetrics(ExecutionOptions opts, UInt256 effectiveGasPrice) @@ -377,6 +450,7 @@ protected virtual TransactionResult BuyGas(Transaction tx, BlockHeader header, I TraceLogInvalidTx(tx, $"INSUFFICIENT_MAX_FEE_PER_GAS_FOR_SENDER_BALANCE: ({tx.SenderAddress})_BALANCE = {senderBalance}, MAX_FEE_PER_GAS: {tx.MaxFeePerGas}"); return "insufficient MaxFeePerGas for sender balance"; } + if (tx.SupportsBlobs) { overflows = UInt256.MultiplyOverflow(BlobGasCalculator.CalculateBlobGas(tx), (UInt256)tx.MaxFeePerBlobGas!, out UInt256 maxBlobGasFee); @@ -431,16 +505,24 @@ private ExecutionEnvironment BuildExecutionEnvironment( Transaction tx, in BlockExecutionContext blCtx, IReleaseSpec spec, - in UInt256 effectiveGasPrice) + in UInt256 effectiveGasPrice, + ICodeInfoRepository codeInfoRepository, + HashSet
accessedAddresses) { Address recipient = tx.GetRecipient(tx.IsContractCreation ? WorldState.GetNonce(tx.SenderAddress!) : 0); if (recipient is null) ThrowInvalidDataException("Recipient has not been resolved properly before tx execution"); - TxExecutionContext executionContext = new(in blCtx, tx.SenderAddress!, effectiveGasPrice, tx.BlobVersionedHashes!); + accessedAddresses.Add(recipient); + accessedAddresses.Add(tx.SenderAddress!); + TxExecutionContext executionContext = new(in blCtx, tx.SenderAddress, effectiveGasPrice, tx.BlobVersionedHashes, codeInfoRepository); + Address? delegationAddress = null; CodeInfo codeInfo = tx.IsContractCreation ? new(tx.Data ?? Memory.Empty) - : _codeInfoRepository.GetCachedCodeInfo(WorldState, recipient, spec); + : codeInfoRepository.GetCachedCodeInfo(WorldState, recipient, spec, out delegationAddress); + + if (delegationAddress is not null) + accessedAddresses.Add(delegationAddress); codeInfo.AnalyseInBackgroundIfRequired(); @@ -467,6 +549,8 @@ protected virtual void ExecuteEvmCall( IReleaseSpec spec, ITxTracer tracer, ExecutionOptions opts, + int delegationRefunds, + IEnumerable
accessedAddresses, in long gasAvailable, in ExecutionEnvironment env, out TransactionSubstate? substate, @@ -490,7 +574,7 @@ protected virtual void ExecuteEvmCall( if (tx.IsContractCreation) { // if transaction is a contract creation then recipient address is the contract deployment address - if (!PrepareAccountForContractDeployment(env.ExecutingAccount, spec)) + if (!PrepareAccountForContractDeployment(env.ExecutingAccount, _codeInfoRepository, spec)) { goto Fail; } @@ -500,21 +584,7 @@ protected virtual void ExecuteEvmCall( using (EvmState state = new(unspentGas, env, executionType, true, snapshot, false)) { - if (spec.UseTxAccessLists) - { - state.WarmUp(tx.AccessList); // eip-2930 - } - - if (spec.UseHotAndColdStorage) - { - state.WarmUp(tx.SenderAddress!); // eip-2929 - state.WarmUp(env.ExecutingAccount); // eip-2929 - } - - if (spec.AddCoinbaseToTxAccessList) - { - state.WarmUp(header.GasBeneficiary!); - } + WarmUp(tx, header, spec, state, accessedAddresses); substate = !tracer.IsTracingActions ? VirtualMachine.Run(state, WorldState, tracer) @@ -574,7 +644,7 @@ protected virtual void ExecuteEvmCall( statusCode = StatusCode.Success; } - spentGas = Refund(tx, header, spec, opts, substate, unspentGas, env.TxExecutionContext.GasPrice); + spentGas = Refund(tx, header, spec, opts, substate, unspentGas, env.TxExecutionContext.GasPrice, delegationRefunds); goto Complete; } catch (Exception ex) when (ex is EvmException or OverflowException) // TODO: OverflowException? still needed? hope not @@ -595,6 +665,27 @@ protected virtual void PayValue(Transaction tx, IReleaseSpec spec, ExecutionOpti WorldState.SubtractFromBalance(tx.SenderAddress!, tx.Value, spec); } + private void WarmUp(Transaction tx, BlockHeader header, IReleaseSpec spec, EvmState state, IEnumerable
accessedAddresses) + { + if (spec.UseTxAccessLists) + { + state.WarmUp(tx.AccessList); // eip-2930 + } + + if (spec.UseHotAndColdStorage) // eip-2929 + { + foreach (Address accessed in accessedAddresses) + { + state.WarmUp(accessed); + } + } + + if (spec.AddCoinbaseToTxAccessList) + { + state.WarmUp(header.GasBeneficiary!); + } + } + protected virtual void PayFees(Transaction tx, BlockHeader header, IReleaseSpec spec, ITxTracer tracer, in TransactionSubstate substate, in long spentGas, in UInt256 premiumPerGas, in UInt256 blobBaseFee, in byte statusCode) { bool gasBeneficiaryNotDestroyed = substate?.DestroyList.Contains(header.GasBeneficiary) != true; @@ -619,9 +710,9 @@ protected virtual void PayFees(Transaction tx, BlockHeader header, IReleaseSpec } } - protected bool PrepareAccountForContractDeployment(Address contractAddress, IReleaseSpec spec) + protected bool PrepareAccountForContractDeployment(Address contractAddress, ICodeInfoRepository codeInfoRepository, IReleaseSpec spec) { - if (WorldState.AccountExists(contractAddress) && contractAddress.IsNonZeroAccount(spec, _codeInfoRepository, WorldState)) + if (WorldState.AccountExists(contractAddress) && contractAddress.IsNonZeroAccount(spec, codeInfoRepository, WorldState)) { if (Logger.IsTrace) Logger.Trace($"Contract collision at {contractAddress}"); @@ -638,22 +729,36 @@ protected void TraceLogInvalidTx(Transaction transaction, string reason) } protected virtual long Refund(Transaction tx, BlockHeader header, IReleaseSpec spec, ExecutionOptions opts, - in TransactionSubstate substate, in long unspentGas, in UInt256 gasPrice) + in TransactionSubstate substate, in long unspentGas, in UInt256 gasPrice, int codeInsertRefunds) { long spentGas = tx.GasLimit; + var codeInsertRefund = (GasCostOf.NewAccount - GasCostOf.PerAuthBaseCost) * codeInsertRefunds; + if (!substate.IsError) { spentGas -= unspentGas; - long refund = substate.ShouldRevert - ? 0 - : RefundHelper.CalculateClaimableRefund(spentGas, - substate.Refund + substate.DestroyList.Count * RefundOf.Destroy(spec.IsEip3529Enabled), spec); + + long totalToRefund = codeInsertRefund; + if (!substate.ShouldRevert) + totalToRefund += substate.Refund + substate.DestroyList.Count * RefundOf.Destroy(spec.IsEip3529Enabled); + long actualRefund = RefundHelper.CalculateClaimableRefund(spentGas, totalToRefund, spec); + + if (Logger.IsTrace) + Logger.Trace("Refunding unused gas of " + unspentGas + " and refund of " + actualRefund); + // If noValidation we didn't charge for gas, so do not refund + if (!opts.HasFlag(ExecutionOptions.NoValidation)) + WorldState.AddToBalance(tx.SenderAddress!, (ulong)(unspentGas + actualRefund) * gasPrice, spec); + spentGas -= actualRefund; + } + else if (codeInsertRefund > 0) + { + long refund = RefundHelper.CalculateClaimableRefund(spentGas, codeInsertRefund, spec); if (Logger.IsTrace) - Logger.Trace("Refunding unused gas of " + unspentGas + " and refund of " + refund); + Logger.Trace("Refunding delegations only: " + refund); // If noValidation we didn't charge for gas, so do not refund if (!opts.HasFlag(ExecutionOptions.NoValidation)) - WorldState.AddToBalance(tx.SenderAddress!, (ulong)(unspentGas + refund) * gasPrice, spec); + WorldState.AddToBalance(tx.SenderAddress!, (ulong)refund * gasPrice, spec); spentGas -= refund; } diff --git a/src/Nethermind/Nethermind.Evm/TxExecutionContext.cs b/src/Nethermind/Nethermind.Evm/TxExecutionContext.cs index 637c6fc0258..e1aa68c2b4e 100644 --- a/src/Nethermind/Nethermind.Evm/TxExecutionContext.cs +++ b/src/Nethermind/Nethermind.Evm/TxExecutionContext.cs @@ -6,19 +6,17 @@ namespace Nethermind.Evm { - public readonly struct TxExecutionContext + public readonly struct TxExecutionContext( + in BlockExecutionContext blockExecutionContext, + Address origin, + in UInt256 gasPrice, + byte[][] blobVersionedHashes, + ICodeInfoRepository codeInfoRepository) { - public readonly BlockExecutionContext BlockExecutionContext; - public Address Origin { get; } - public UInt256 GasPrice { get; } - public byte[][]? BlobVersionedHashes { get; } - - public TxExecutionContext(in BlockExecutionContext blockExecutionContext, Address origin, in UInt256 gasPrice, byte[][] blobVersionedHashes) - { - BlockExecutionContext = blockExecutionContext; - Origin = origin; - GasPrice = gasPrice; - BlobVersionedHashes = blobVersionedHashes; - } + public readonly BlockExecutionContext BlockExecutionContext = blockExecutionContext; + public Address Origin { get; } = origin; + public UInt256 GasPrice { get; } = gasPrice; + public byte[][]? BlobVersionedHashes { get; } = blobVersionedHashes; + public ICodeInfoRepository CodeInfoRepository { get; } = codeInfoRepository; } } diff --git a/src/Nethermind/Nethermind.Evm/VirtualMachine.cs b/src/Nethermind/Nethermind.Evm/VirtualMachine.cs index c1b9ab1af67..b5ca0aa4a7d 100644 --- a/src/Nethermind/Nethermind.Evm/VirtualMachine.cs +++ b/src/Nethermind/Nethermind.Evm/VirtualMachine.cs @@ -4,7 +4,6 @@ using System; using System.Collections.Generic; -using System.Linq; using System.Runtime.CompilerServices; using Nethermind.Core; using Nethermind.Core.Crypto; @@ -27,9 +26,9 @@ [assembly: InternalsVisibleTo("Nethermind.Evm.Test")] namespace Nethermind.Evm; - using Int256; + public class VirtualMachine : IVirtualMachine { public const int MaxCallDepth = 1024; @@ -69,8 +68,8 @@ public VirtualMachine( { ILogger logger = logManager?.GetClassLogger() ?? throw new ArgumentNullException(nameof(logManager)); _evm = logger.IsTrace - ? new VirtualMachine(blockhashProvider, specProvider, codeInfoRepository, logger) - : new VirtualMachine(blockhashProvider, specProvider, codeInfoRepository, logger); + ? new VirtualMachine(blockhashProvider, specProvider, logger) + : new VirtualMachine(blockhashProvider, specProvider, logger); } public TransactionSubstate Run(EvmState state, IWorldState worldState, ITxTracer txTracer) @@ -140,23 +139,20 @@ internal sealed class VirtualMachine : IVirtualMachine where TLogger : private readonly IBlockhashProvider _blockhashProvider; private readonly ISpecProvider _specProvider; private readonly ILogger _logger; - private IWorldState _state; + private IWorldState _state = null!; private readonly Stack _stateStack = new(); private (Address Address, bool ShouldDelete) _parityTouchBugAccount = (Address.FromNumber(3), false); private ReadOnlyMemory _returnDataBuffer = Array.Empty(); private ITxTracer _txTracer = NullTxTracer.Instance; - private readonly ICodeInfoRepository _codeInfoRepository; public VirtualMachine( IBlockhashProvider? blockhashProvider, ISpecProvider? specProvider, - ICodeInfoRepository codeInfoRepository, ILogger? logger) { _logger = logger ?? throw new ArgumentNullException(nameof(logger)); _blockhashProvider = blockhashProvider ?? throw new ArgumentNullException(nameof(blockhashProvider)); _specProvider = specProvider ?? throw new ArgumentNullException(nameof(specProvider)); - _codeInfoRepository = codeInfoRepository ?? throw new ArgumentNullException(nameof(codeInfoRepository)); _chainId = ((UInt256)specProvider.ChainId).ToBigEndian(); } @@ -166,7 +162,9 @@ public TransactionSubstate Run(EvmState state, IWorldState worl _txTracer = txTracer; _state = worldState; - IReleaseSpec spec = _specProvider.GetSpec(state.Env.TxExecutionContext.BlockExecutionContext.Header.Number, state.Env.TxExecutionContext.BlockExecutionContext.Header.Timestamp); + ref readonly TxExecutionContext txExecutionContext = ref state.Env.TxExecutionContext; + ICodeInfoRepository codeInfoRepository = txExecutionContext.CodeInfoRepository; + IReleaseSpec spec = _specProvider.GetSpec(txExecutionContext.BlockExecutionContext.Header.Number, txExecutionContext.BlockExecutionContext.Header.Timestamp); EvmState currentState = state; ReadOnlyMemory? previousCallResult = null; ZeroPaddedSpan previousCallOutput = ZeroPaddedSpan.Empty; @@ -347,7 +345,7 @@ public TransactionSubstate Run(EvmState state, IWorldState worl if (gasAvailableForCodeDeposit >= codeDepositGasCost && !invalidCode) { ReadOnlyMemory code = callResult.Output; - _codeInfoRepository.InsertCode(_state, code, callCodeOwner, spec); + codeInfoRepository.InsertCode(_state, code, callCodeOwner, spec); currentState.GasAvailable -= codeDepositGasCost; @@ -494,13 +492,22 @@ private static void UpdateGasUp(long refund, ref long gasAvailable) gasAvailable += refund; } - private bool ChargeAccountAccessGas(ref long gasAvailable, EvmState vmState, Address address, IReleaseSpec spec, bool chargeForWarm = true) + private bool ChargeAccountAccessGas(ref long gasAvailable, EvmState vmState, Address address, bool chargeForDelegation, IReleaseSpec spec, bool chargeForWarm = true) { - // Console.WriteLine($"Accessing {address}"); + if (!spec.UseHotAndColdStorage) + { + return true; + } + bool notOutOfGas = ChargeAccountGas(ref gasAvailable, vmState, address, spec); + return notOutOfGas + && chargeForDelegation + && vmState.Env.TxExecutionContext.CodeInfoRepository.TryGetDelegation(_state, address, out Address delegated) + ? ChargeAccountGas(ref gasAvailable, vmState, delegated, spec) + : notOutOfGas; - bool result = true; - if (spec.UseHotAndColdStorage) + bool ChargeAccountGas(ref long gasAvailable, EvmState vmState, Address address, IReleaseSpec spec) { + bool result = true; if (_txTracer.IsTracingAccess) // when tracing access we want cost as if it was warmed up from access list { vmState.WarmUp(address); @@ -515,9 +522,8 @@ private bool ChargeAccountAccessGas(ref long gasAvailable, EvmState vmState, Add { result = UpdateGas(GasCostOf.WarmStateRead, ref gasAvailable); } + return result; } - - return result; } private enum StorageAccessType @@ -1170,7 +1176,7 @@ private CallResult ExecuteCode externalCode = _codeInfoRepository.GetCachedCodeInfo(_state, address, spec).MachineCode; + ReadOnlyMemory externalCode = txCtx.CodeInfoRepository.GetCachedCodeInfo(_state, address, spec).MachineCode; slice = externalCode.SliceWithZeroPadding(b, (int)result); vmState.Memory.Save(in a, in slice); if (typeof(TTracingInstructions) == typeof(IsTracing)) @@ -1914,15 +1920,17 @@ private CallResult ExecuteCode [SkipLocalsInit] [MethodImpl(MethodImplOptions.NoInlining)] - private void InstructionExtCodeSize(Address address, ref EvmStack stack, IReleaseSpec spec) where TTracingInstructions : struct, IIsTracing + private void InstructionExtCodeSize(Address address, ref EvmStack stack, ICodeInfoRepository codeInfoRepository, IReleaseSpec spec) where TTracingInstructions : struct, IIsTracing { - ReadOnlyMemory accountCode = _codeInfoRepository.GetCachedCodeInfo(_state, address, spec).MachineCode; + ReadOnlyMemory accountCode = codeInfoRepository.GetCachedCodeInfo(_state, address, spec).MachineCode; UInt256 result = (UInt256)accountCode.Span.Length; stack.PushUInt256(in result); } @@ -2092,7 +2100,7 @@ private EvmExceptionType InstructionCall( Address codeSource = stack.PopAddress(); if (codeSource is null) return EvmExceptionType.StackUnderflow; - if (!ChargeAccountAccessGas(ref gasAvailable, vmState, codeSource, spec)) return EvmExceptionType.OutOfGas; + if (!ChargeAccountAccessGas(ref gasAvailable, vmState, codeSource, true, spec)) return EvmExceptionType.OutOfGas; UInt256 callValue; switch (instruction) @@ -2117,6 +2125,7 @@ private EvmExceptionType InstructionCall( if (vmState.IsStatic && !transferValue.IsZero && instruction != Instruction.CALLCODE) return EvmExceptionType.StaticCallViolation; Address caller = instruction == Instruction.DELEGATECALL ? env.Caller : env.ExecutingAccount; + Address target = instruction == Instruction.CALL || instruction == Instruction.STATICCALL ? codeSource : env.ExecutingAccount; @@ -2147,7 +2156,7 @@ private EvmExceptionType InstructionCall( !UpdateMemoryCost(vmState, ref gasAvailable, in outputOffset, outputLength) || !UpdateGas(gasExtra, ref gasAvailable)) return EvmExceptionType.OutOfGas; - CodeInfo codeInfo = _codeInfoRepository.GetCachedCodeInfo(_state, codeSource, spec); + CodeInfo codeInfo = vmState.Env.TxExecutionContext.CodeInfoRepository.GetCachedCodeInfo(_state, codeSource, spec); codeInfo.AnalyseInBackgroundIfRequired(); if (spec.Use63Over64Rule) @@ -2166,8 +2175,7 @@ private EvmExceptionType InstructionCall( gasLimitUl += GasCostOf.CallStipend; } - if (env.CallDepth >= MaxCallDepth || - !transferValue.IsZero && _state.GetBalance(env.ExecutingAccount) < transferValue) + if (env.CallDepth >= MaxCallDepth || !transferValue.IsZero && _state.GetBalance(env.ExecutingAccount) < transferValue) { _returnDataBuffer = Array.Empty(); stack.PushZero(); @@ -2311,7 +2319,7 @@ private EvmExceptionType InstructionSelfDestruct(EvmState vmState, ref Address inheritor = stack.PopAddress(); if (inheritor is null) return EvmExceptionType.StackUnderflow; - if (!ChargeAccountAccessGas(ref gasAvailable, vmState, inheritor, spec, false)) return EvmExceptionType.OutOfGas; + if (!ChargeAccountAccessGas(ref gasAvailable, vmState, inheritor, false, spec, false)) return EvmExceptionType.OutOfGas; Address executingAccount = vmState.Env.ExecutingAccount; bool createInSameTx = vmState.CreateList.Contains(executingAccount); @@ -2436,7 +2444,7 @@ private EvmExceptionType InstructionSelfDestruct(EvmState vmState, ref bool accountExists = _state.AccountExists(contractAddress); - if (accountExists && contractAddress.IsNonZeroAccount(spec, _codeInfoRepository, _state)) + if (accountExists && contractAddress.IsNonZeroAccount(spec, env.TxExecutionContext.CodeInfoRepository, _state)) { /* we get the snapshot before this as there is a possibility with that we will touch an empty account and remove it even if the REVERT operation follows */ if (typeof(TLogger) == typeof(IsTracing)) _logger.Trace($"Contract collision at {contractAddress}"); @@ -2777,30 +2785,13 @@ private void EndInstructionTraceError(long gasAvailable, EvmExceptionType evmExc _txTracer.ReportOperationError(evmExceptionType); } - private static ExecutionType GetCallExecutionType(Instruction instruction, bool isPostMerge = false) - { - ExecutionType executionType; - if (instruction == Instruction.CALL) + private static ExecutionType GetCallExecutionType(Instruction instruction, bool isPostMerge = false) => + instruction switch { - executionType = ExecutionType.CALL; - } - else if (instruction == Instruction.DELEGATECALL) - { - executionType = ExecutionType.DELEGATECALL; - } - else if (instruction == Instruction.STATICCALL) - { - executionType = ExecutionType.STATICCALL; - } - else if (instruction == Instruction.CALLCODE) - { - executionType = ExecutionType.CALLCODE; - } - else - { - throw new NotSupportedException($"Execution type is undefined for {instruction.GetName(isPostMerge)}"); - } - - return executionType; - } + Instruction.CALL => ExecutionType.CALL, + Instruction.DELEGATECALL => ExecutionType.DELEGATECALL, + Instruction.STATICCALL => ExecutionType.STATICCALL, + Instruction.CALLCODE => ExecutionType.CALLCODE, + _ => throw new NotSupportedException($"Execution type is undefined for {instruction.GetName(isPostMerge)}") + }; } diff --git a/src/Nethermind/Nethermind.Facade/Eth/AuthorizationTupleForRpc.cs b/src/Nethermind/Nethermind.Facade/Eth/AuthorizationTupleForRpc.cs new file mode 100644 index 00000000000..c35c97b1d56 --- /dev/null +++ b/src/Nethermind/Nethermind.Facade/Eth/AuthorizationTupleForRpc.cs @@ -0,0 +1,43 @@ +// SPDX-FileCopyrightText: 2022 Demerzel Solutions Limited +// SPDX-License-Identifier: LGPL-3.0-only + +using Nethermind.Core; +using Nethermind.Int256; +using System.Collections.Generic; +using System.Linq; +using System.Text.Json.Serialization; + +namespace Nethermind.Facade.Eth +{ + public struct AuthorizationTupleForRpc + { + [JsonConstructor] + public AuthorizationTupleForRpc() + { + } + public AuthorizationTupleForRpc(UInt256 chainId, ulong nonce, Address address, UInt256? yParity, UInt256? s, UInt256? r) + { + ChainId = chainId; + Nonce = nonce; + Address = address; + YParity = yParity; + S = s; + R = r; + } + + public UInt256 ChainId { get; set; } + public ulong Nonce { get; set; } + public Address Address { get; set; } + public UInt256? YParity { get; set; } + public UInt256? S { get; set; } + public UInt256? R { get; set; } + + public static IEnumerable FromAuthorizationList(AuthorizationTuple[] authorizationList) => + authorizationList.Select(tuple => new AuthorizationTupleForRpc(tuple.ChainId, + tuple.Nonce, + tuple.CodeAddress, + tuple.AuthoritySignature.RecoveryId, + new UInt256(tuple.AuthoritySignature.S), + new UInt256(tuple.AuthoritySignature.R))); + } +} diff --git a/src/Nethermind/Nethermind.Facade/Eth/TransactionForRpc.cs b/src/Nethermind/Nethermind.Facade/Eth/TransactionForRpc.cs index d51ed363156..5b054f48106 100644 --- a/src/Nethermind/Nethermind.Facade/Eth/TransactionForRpc.cs +++ b/src/Nethermind/Nethermind.Facade/Eth/TransactionForRpc.cs @@ -61,6 +61,11 @@ public TransactionForRpc(Hash256? blockHash, long? blockNumber, int? txIndex, Tr { AccessList = null; } + AuthorizationList = transaction.SupportsAuthorizationList + ? transaction.AuthorizationList is null + ? Array.Empty() + : AuthorizationTupleForRpc.FromAuthorizationList(transaction.AuthorizationList) + : null; MaxFeePerBlobGas = transaction.MaxFeePerBlobGas; BlobVersionedHashes = transaction.BlobVersionedHashes; @@ -126,6 +131,8 @@ public TransactionForRpc() { } public TxType Type { get; set; } public IEnumerable? AccessList { get; set; } + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + public IEnumerable? AuthorizationList { get; set; } [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] public UInt256? MaxFeePerBlobGas { get; set; } // eip4844 diff --git a/src/Nethermind/Nethermind.Facade/OverridableCodeInfoRepository.cs b/src/Nethermind/Nethermind.Facade/OverridableCodeInfoRepository.cs index dbe43ddddc7..082d5ad57fe 100644 --- a/src/Nethermind/Nethermind.Facade/OverridableCodeInfoRepository.cs +++ b/src/Nethermind/Nethermind.Facade/OverridableCodeInfoRepository.cs @@ -3,6 +3,7 @@ using System; using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; using Nethermind.Core; using Nethermind.Core.Crypto; using Nethermind.Core.Specs; @@ -16,17 +17,17 @@ public class OverridableCodeInfoRepository(ICodeInfoRepository codeInfoRepositor { private readonly Dictionary _codeOverwrites = new(); - public CodeInfo GetCachedCodeInfo(IWorldState worldState, Address codeSource, IReleaseSpec vmSpec) => - _codeOverwrites.TryGetValue(codeSource, out CodeInfo result) + public CodeInfo GetCachedCodeInfo(IWorldState worldState, Address codeSource, IReleaseSpec vmSpec, out Address? delegationAddress) + { + delegationAddress = null; + return _codeOverwrites.TryGetValue(codeSource, out CodeInfo result) ? result : codeInfoRepository.GetCachedCodeInfo(worldState, codeSource, vmSpec); - - public CodeInfo GetOrAdd(ValueHash256 codeHash, ReadOnlySpan initCode) => codeInfoRepository.GetOrAdd(codeHash, initCode); + } public void InsertCode(IWorldState state, ReadOnlyMemory code, Address codeOwner, IReleaseSpec spec) => codeInfoRepository.InsertCode(state, code, codeOwner, spec); - public void SetCodeOverwrite( IWorldState worldState, IReleaseSpec vmSpec, @@ -36,9 +37,18 @@ public void SetCodeOverwrite( { if (redirectAddress is not null) { - _codeOverwrites[redirectAddress] = GetCachedCodeInfo(worldState, key, vmSpec); + _codeOverwrites[redirectAddress] = this.GetCachedCodeInfo(worldState, key, vmSpec); } _codeOverwrites[key] = value; } + + public void SetDelegation(IWorldState state, Address codeSource, Address authority, IReleaseSpec spec) => + codeInfoRepository.SetDelegation(state, codeSource, authority, spec); + + public bool TryGetDelegation(IReadOnlyStateProvider worldState, Address address, [NotNullWhen(true)] out Address? delegatedAddress) => + codeInfoRepository.TryGetDelegation(worldState, address, out delegatedAddress); + + public ValueHash256 GetExecutableCodeHash(IWorldState worldState, Address address) => + codeInfoRepository.GetExecutableCodeHash(worldState, address); } diff --git a/src/Nethermind/Nethermind.Init/Steps/InitializeBlockchain.cs b/src/Nethermind/Nethermind.Init/Steps/InitializeBlockchain.cs index 1c7d9f22a29..62754cb2fb2 100644 --- a/src/Nethermind/Nethermind.Init/Steps/InitializeBlockchain.cs +++ b/src/Nethermind/Nethermind.Init/Steps/InitializeBlockchain.cs @@ -57,7 +57,9 @@ protected virtual Task InitBlockchain() IReceiptConfig receiptConfig = getApi.Config(); IStateReader stateReader = setApi.StateReader!; - ITxPool txPool = _api.TxPool = CreateTxPool(); + PreBlockCaches? preBlockCaches = (_api.WorldState as IPreBlockCaches)?.Caches; + CodeInfoRepository codeInfoRepository = new(preBlockCaches?.PrecompileCache); + ITxPool txPool = _api.TxPool = CreateTxPool(codeInfoRepository); ReceiptCanonicalityMonitor receiptCanonicalityMonitor = new(getApi.ReceiptStorage, _api.LogManager); getApi.DisposeStack.Push(receiptCanonicalityMonitor); @@ -66,8 +68,7 @@ protected virtual Task InitBlockchain() _api.BlockPreprocessor.AddFirst( new RecoverSignatures(getApi.EthereumEcdsa, txPool, getApi.SpecProvider, getApi.LogManager)); - PreBlockCaches? preBlockCaches = (_api.WorldState as IPreBlockCaches)?.Caches; - CodeInfoRepository codeInfoRepository = new(preBlockCaches?.PrecompileCache); + VirtualMachine virtualMachine = CreateVirtualMachine(codeInfoRepository); _api.TransactionProcessor = CreateTransactionProcessor(codeInfoRepository, virtualMachine); @@ -79,17 +80,17 @@ protected virtual Task InitBlockchain() setApi.BlockValidator = CreateBlockValidator(); IChainHeadInfoProvider chainHeadInfoProvider = - new ChainHeadInfoProvider(getApi.SpecProvider!, getApi.BlockTree!, stateReader); + new ChainHeadInfoProvider(getApi.SpecProvider!, getApi.BlockTree!, stateReader, codeInfoRepository); // TODO: can take the tx sender from plugin here maybe ITxSigner txSigner = new WalletTxSigner(getApi.Wallet, getApi.SpecProvider!.ChainId); TxSealer nonceReservingTxSealer = new(txSigner, getApi.Timestamper); - INonceManager nonceManager = new NonceManager(chainHeadInfoProvider.AccountStateProvider); + INonceManager nonceManager = new NonceManager(chainHeadInfoProvider.ReadOnlyStateProvider); setApi.NonceManager = nonceManager; setApi.TxSender = new TxPoolSender(txPool, nonceReservingTxSealer, nonceManager, getApi.EthereumEcdsa!); - setApi.TxPoolInfoProvider = new TxPoolInfoProvider(chainHeadInfoProvider.AccountStateProvider, txPool); + setApi.TxPoolInfoProvider = new TxPoolInfoProvider(chainHeadInfoProvider.ReadOnlyStateProvider, txPool); setApi.GasPriceOracle = new GasPriceOracle(getApi.BlockTree!, getApi.SpecProvider, _api.LogManager, blocksConfig.MinGasPrice); BlockCachePreWarmer? preWarmer = blocksConfig.PreWarmStateOnBlockProcessing ? new(new(_api.WorldStateManager!, _api.BlockTree!, _api.SpecProvider, _api.LogManager, _api.WorldState), _api.SpecProvider!, _api.LogManager, preBlockCaches) @@ -199,10 +200,10 @@ protected virtual IHealthHintService CreateHealthHintService() => protected virtual IBlockProductionPolicy CreateBlockProductionPolicy() => new BlockProductionPolicy(_api.Config()); - protected virtual TxPool.TxPool CreateTxPool() => + protected virtual TxPool.TxPool CreateTxPool(CodeInfoRepository codeInfoRepository) => new(_api.EthereumEcdsa!, _api.BlobTxStorage ?? NullBlobTxStorage.Instance, - new ChainHeadInfoProvider(_api.SpecProvider!, _api.BlockTree!, _api.StateReader!), + new ChainHeadInfoProvider(_api.SpecProvider!, _api.BlockTree!, _api.StateReader!, codeInfoRepository), _api.Config(), _api.TxValidator!, _api.LogManager, diff --git a/src/Nethermind/Nethermind.JsonRpc.Test/Modules/Eth/EthRpcModuleTests.cs b/src/Nethermind/Nethermind.JsonRpc.Test/Modules/Eth/EthRpcModuleTests.cs index 914c3ebf8d8..843406aad94 100644 --- a/src/Nethermind/Nethermind.JsonRpc.Test/Modules/Eth/EthRpcModuleTests.cs +++ b/src/Nethermind/Nethermind.JsonRpc.Test/Modules/Eth/EthRpcModuleTests.cs @@ -1201,6 +1201,101 @@ public async Task eth_getBlockByNumber_should_return_withdrawals_correctly() })), Is.EqualTo(result)); } + + [Test] + public async Task eth_sendRawTransaction_sender_with_non_delegated_code_is_rejected() + { + var specProvider = new TestSpecProvider(Prague.Instance); + specProvider.AllowTestChainOverride = false; + + TestRpcBlockchain Test = await TestRpcBlockchain.ForTest(SealEngineType.NethDev).Build(specProvider); + + Transaction testTx = Build.A.Transaction + .WithType(TxType.SetCode) + .WithNonce(Test.State.GetNonce(TestItem.AddressA)) + .WithMaxFeePerGas(9.GWei()) + .WithMaxPriorityFeePerGas(9.GWei()) + .WithGasLimit(GasCostOf.Transaction + GasCostOf.NewAccount) + .WithAuthorizationCodeIfAuthorizationListTx() + .WithTo(TestItem.AddressA) + .SignedAndResolved(TestItem.PrivateKeyA).TestObject; + + string result = await Test.TestEthRpc("eth_sendRawTransaction", Bytes.ToHexString(Rlp.Encode(testTx).Bytes)); + + JsonRpcErrorResponse actual = new EthereumJsonSerializer().Deserialize(result); + Assert.That(actual.Error!.Message, Is.EqualTo(nameof(AcceptTxResult.SenderIsContract))); + } + + + [Test] + public async Task eth_sendRawTransaction_sender_with_delegated_code_is_accepted() + { + var specProvider = new TestSpecProvider(Prague.Instance); + specProvider.AllowTestChainOverride = false; + + TestRpcBlockchain test = await TestRpcBlockchain.ForTest(SealEngineType.NethDev).Build(specProvider); + Transaction setCodeTx = Build.A.Transaction + .WithType(TxType.SetCode) + .WithNonce(test.State.GetNonce(TestItem.AddressB)) + .WithMaxFeePerGas(9.GWei()) + .WithMaxPriorityFeePerGas(9.GWei()) + .WithGasLimit(GasCostOf.Transaction + GasCostOf.NewAccount) + .WithAuthorizationCode(test.EthereumEcdsa.Sign(TestItem.PrivateKeyB, 0, Address.Zero, (ulong)test.State.GetNonce(TestItem.AddressB) + 1)) + .WithTo(TestItem.AddressA) + .SignedAndResolved(TestItem.PrivateKeyB).TestObject; + + await test.AddBlock(setCodeTx); + + var code = test.State.GetCode(TestItem.AddressB); + + Assert.That(code!.Slice(0, 3), Is.EquivalentTo(Eip7702Constants.DelegationHeader.ToArray())); + + Transaction normalTx = Build.A.Transaction + .WithNonce(test.State.GetNonce(TestItem.AddressB)) + .WithMaxFeePerGas(9.GWei()) + .WithMaxPriorityFeePerGas(9.GWei()) + .WithGasLimit(GasCostOf.Transaction) + .WithTo(TestItem.AddressA) + .SignedAndResolved(TestItem.PrivateKeyB).TestObject; + + string result = await test.TestEthRpc("eth_sendRawTransaction", Bytes.ToHexString(Rlp.Encode(normalTx).Bytes)); + + JsonRpcSuccessResponse actual = new EthereumJsonSerializer().Deserialize(result); + Assert.That(actual.Result, Is.Not.Null); + } + + [Test] + public async Task eth_sendRawTransaction_returns_correct_error_if_AuthorityTuple_has_null_value() + { + var specProvider = new TestSpecProvider(Prague.Instance); + specProvider.AllowTestChainOverride = false; + + TestRpcBlockchain test = await TestRpcBlockchain.ForTest(SealEngineType.NethDev).Build(specProvider); + Transaction invalidSetCodeTx = Build.A.Transaction + .WithType(TxType.SetCode) + .WithNonce(test.State.GetNonce(TestItem.AddressB)) + .WithMaxFeePerGas(9.GWei()) + .WithMaxPriorityFeePerGas(9.GWei()) + .WithGasLimit(GasCostOf.Transaction + GasCostOf.NewAccount) + .WithAuthorizationCode(new AllowNullAuthorizationTuple(0, null, 0, new Signature(new byte[65]))) + .WithTo(TestItem.AddressA) + .SignedAndResolved(TestItem.PrivateKeyB).TestObject; + + string result = await test.TestEthRpc("eth_sendRawTransaction", Bytes.ToHexString(Rlp.Encode(invalidSetCodeTx).Bytes)); + + JsonRpcErrorResponse actual = new EthereumJsonSerializer().Deserialize(result); + Assert.That(actual.Error!.Code, Is.EqualTo(ErrorCodes.TransactionRejected)); + } + public class AllowNullAuthorizationTuple : AuthorizationTuple + { + public AllowNullAuthorizationTuple(ulong chainId, Address? codeAddress, ulong nonce, Signature? sig) + : base(chainId, Address.Zero, nonce, new Signature(new byte[65])) + { + CodeAddress = codeAddress!; + AuthoritySignature = sig!; + } + } + private static (byte[] ByteCode, AccessListItemForRpc[] AccessList) GetTestAccessList(long loads = 2, bool allowSystemUser = true) { AccessListItemForRpc[] accessList = allowSystemUser diff --git a/src/Nethermind/Nethermind.JsonRpc.Test/Modules/ParityRpcModuleTests.cs b/src/Nethermind/Nethermind.JsonRpc.Test/Modules/ParityRpcModuleTests.cs index 3fd4d6dbac0..708dd5575c1 100644 --- a/src/Nethermind/Nethermind.JsonRpc.Test/Modules/ParityRpcModuleTests.cs +++ b/src/Nethermind/Nethermind.JsonRpc.Test/Modules/ParityRpcModuleTests.cs @@ -35,6 +35,7 @@ using NSubstitute; using NUnit.Framework; using System; +using Nethermind.Evm; namespace Nethermind.JsonRpc.Test.Modules { @@ -75,7 +76,7 @@ public void Initialize() _txPool = new TxPool.TxPool(_ethereumEcdsa, new BlobTxStorage(), - new ChainHeadInfoProvider(new FixedForkActivationChainHeadSpecProvider(specProvider), _blockTree, stateProvider), + new ChainHeadInfoProvider(new FixedForkActivationChainHeadSpecProvider(specProvider), _blockTree, stateProvider, new CodeInfoRepository()), new TxPoolConfig(), new TxValidator(specProvider.ChainId), LimboLogs.Instance, diff --git a/src/Nethermind/Nethermind.Merge.Plugin.Test/ConsensusRequestsProcessorMock.cs b/src/Nethermind/Nethermind.Merge.Plugin.Test/ConsensusRequestsProcessorMock.cs index 386989099f8..14b1b8881c1 100644 --- a/src/Nethermind/Nethermind.Merge.Plugin.Test/ConsensusRequestsProcessorMock.cs +++ b/src/Nethermind/Nethermind.Merge.Plugin.Test/ConsensusRequestsProcessorMock.cs @@ -20,7 +20,9 @@ public class ConsensusRequestsProcessorMock : IConsensusRequestsProcessor TestItem.DepositA_1Eth, TestItem.DepositB_2Eth, TestItem.WithdrawalRequestA, - TestItem.WithdrawalRequestB + TestItem.WithdrawalRequestB, + TestItem.ConsolidationRequestA, + TestItem.ConsolidationRequestB ]; public void ProcessRequests(Block block, IWorldState state, TxReceipt[] receipts, IReleaseSpec spec) diff --git a/src/Nethermind/Nethermind.Merge.Plugin.Test/EngineModuleTests.HelperFunctions.cs b/src/Nethermind/Nethermind.Merge.Plugin.Test/EngineModuleTests.HelperFunctions.cs index af91c25f367..2de5457185a 100644 --- a/src/Nethermind/Nethermind.Merge.Plugin.Test/EngineModuleTests.HelperFunctions.cs +++ b/src/Nethermind/Nethermind.Merge.Plugin.Test/EngineModuleTests.HelperFunctions.cs @@ -119,8 +119,15 @@ private static ExecutionPayload CreateBlockRequest(MergeTestBlockchain chain, Ex return blockRequest; } - private static ExecutionPayloadV3 CreateBlockRequestV3(MergeTestBlockchain chain, ExecutionPayload parent, Address miner, Withdrawal[]? withdrawals = null, - ulong? blobGasUsed = null, ulong? excessBlobGas = null, Transaction[]? transactions = null, Hash256? parentBeaconBlockRoot = null) + private static ExecutionPayloadV3 CreateBlockRequestV3( + MergeTestBlockchain chain, + ExecutionPayload parent, + Address miner, + Withdrawal[]? withdrawals = null, + ulong? blobGasUsed = null, + ulong? excessBlobGas = null, + Transaction[]? transactions = null, + Hash256? parentBeaconBlockRoot = null) { ExecutionPayloadV3 blockRequestV3 = CreateBlockRequestInternal(parent, miner, withdrawals, blobGasUsed, excessBlobGas, transactions: transactions, parentBeaconBlockRoot: parentBeaconBlockRoot); blockRequestV3.TryGetBlock(out Block? block); @@ -170,9 +177,11 @@ private static T CreateBlockRequestInternal(ExecutionPayload parent, Address { Deposit[]? deposits = null; WithdrawalRequest[]? withdrawalRequests = null; + ConsolidationRequest[]? consolidationRequests = null; + if (requests is not null) { - (deposits, withdrawalRequests) = requests.SplitRequests(); + (deposits, withdrawalRequests, consolidationRequests) = requests.SplitRequests(); } T blockRequest = new() @@ -191,7 +200,8 @@ private static T CreateBlockRequestInternal(ExecutionPayload parent, Address ExcessBlobGas = excessBlobGas, ParentBeaconBlockRoot = parentBeaconBlockRoot, DepositRequests = deposits, - WithdrawalRequests = withdrawalRequests + WithdrawalRequests = withdrawalRequests, + ConsolidationRequests = consolidationRequests, }; blockRequest.SetTransactions(transactions ?? Array.Empty()); diff --git a/src/Nethermind/Nethermind.Merge.Plugin.Test/EngineModuleTests.V4.cs b/src/Nethermind/Nethermind.Merge.Plugin.Test/EngineModuleTests.V4.cs index 3c3874b2f8f..7918db73e3c 100644 --- a/src/Nethermind/Nethermind.Merge.Plugin.Test/EngineModuleTests.V4.cs +++ b/src/Nethermind/Nethermind.Merge.Plugin.Test/EngineModuleTests.V4.cs @@ -167,6 +167,42 @@ public virtual async Task Should_process_block_as_expected_V4(string latestValid })); } + + [Test] + public async Task NewPayloadV4_reject_payload_with_bad_authorization_list_rlp() + { + ConsensusRequestsProcessorMock consensusRequestsProcessorMock = new(); + using MergeTestBlockchain chain = await CreateBlockchain(Prague.Instance, null, null, null, consensusRequestsProcessorMock); + IEngineRpcModule rpc = CreateEngineModule(chain); + Hash256 lastHash = (await ProduceBranchV4(rpc, chain, 10, CreateParentBlockRequestOnHead(chain.BlockTree), true)) + .LastOrDefault()?.BlockHash ?? Keccak.Zero; + + Transaction invalidSetCodeTx = Build.A.Transaction + .WithType(TxType.SetCode) + .WithNonce(0) + .WithMaxFeePerGas(9.GWei()) + .WithMaxPriorityFeePerGas(9.GWei()) + .WithGasLimit(100_000) + .WithAuthorizationCode(new JsonRpc.Test.Modules.Eth.EthRpcModuleTests.AllowNullAuthorizationTuple(0, null, 0, new Signature(new byte[65]))) + .WithTo(TestItem.AddressA) + .SignedAndResolved(TestItem.PrivateKeyB).TestObject; + + Block invalidBlock = Build.A.Block + .WithNumber(chain.BlockTree.Head!.Number + 1) + .WithTimestamp(chain.BlockTree.Head!.Timestamp + 12) + .WithTransactions([invalidSetCodeTx]) + .WithParentBeaconBlockRoot(chain.BlockTree.Head!.ParentBeaconBlockRoot) + .WithBlobGasUsed(0) + .WithExcessBlobGas(0) + .TestObject; + + ExecutionPayloadV4 executionPayload = ExecutionPayloadV4.Create(invalidBlock); + + var response = await rpc.engine_newPayloadV4(executionPayload, [], invalidBlock.ParentBeaconBlockRoot); + + Assert.That(response.Data.Status, Is.EqualTo("INVALID")); + } + [TestCase(30)] public async Task can_progress_chain_one_by_one_v4(int count) { @@ -319,9 +355,11 @@ public virtual async Task { Deposit[]? deposits = null; WithdrawalRequest[]? withdrawalRequests = null; + ConsolidationRequest[]? consolidationRequests = null; + if (requests is not null) { - (deposits, withdrawalRequests) = requests.SplitRequests(); + (deposits, withdrawalRequests, consolidationRequests) = requests.SplitRequests(); } ConsensusRequestsProcessorMock consensusRequestsProcessorMock = new(); using MergeTestBlockchain chain = await CreateBlockchain(Prague.Instance, null, null, null, consensusRequestsProcessorMock); @@ -332,9 +370,9 @@ public virtual async Task IEnumerable payloadBodies = rpc.engine_getPayloadBodiesByHashV2(blockHashes).Data; ExecutionPayloadBodyV2Result?[] expected = { - new (Array.Empty(), Array.Empty() , deposits, withdrawalRequests), + new (Array.Empty(), Array.Empty() , deposits, withdrawalRequests, consolidationRequests), null, - new (Array.Empty(), Array.Empty(), deposits, withdrawalRequests), + new (Array.Empty(), Array.Empty(), deposits, withdrawalRequests,consolidationRequests), }; payloadBodies.Should().BeEquivalentTo(expected, o => o.WithStrictOrdering()); } @@ -346,9 +384,11 @@ public virtual async Task { Deposit[]? deposits = null; WithdrawalRequest[]? withdrawalRequests = null; + ConsolidationRequest[]? consolidationRequests = null; + if (requests is not null) { - (deposits, withdrawalRequests) = requests.SplitRequests(); + (deposits, withdrawalRequests, consolidationRequests) = requests.SplitRequests(); } ConsensusRequestsProcessorMock consensusRequestsProcessorMock = new(); @@ -363,7 +403,7 @@ await rpc.engine_forkchoiceUpdatedV3(new ForkchoiceStateV1(executionPayload2.Blo rpc.engine_getPayloadBodiesByRangeV2(1, 3).Result.Data; ExecutionPayloadBodyV2Result?[] expected = { - new (Array.Empty(), Array.Empty() , deposits, withdrawalRequests), + new (Array.Empty(), Array.Empty() , deposits, withdrawalRequests, consolidationRequests), }; payloadBodies.Should().BeEquivalentTo(expected, o => o.WithStrictOrdering()); diff --git a/src/Nethermind/Nethermind.Merge.Plugin/Data/ExecutionPayload.cs b/src/Nethermind/Nethermind.Merge.Plugin/Data/ExecutionPayload.cs index aeb2bda72fd..61201fbaabc 100644 --- a/src/Nethermind/Nethermind.Merge.Plugin/Data/ExecutionPayload.cs +++ b/src/Nethermind/Nethermind.Merge.Plugin/Data/ExecutionPayload.cs @@ -89,6 +89,12 @@ public byte[][] Transactions /// public virtual WithdrawalRequest[]? WithdrawalRequests { get; set; } + /// + /// Gets or sets a collection of as defined in + /// EIP-7251. + /// + public virtual ConsolidationRequest[]? ConsolidationRequests { get; set; } + /// /// Gets or sets as defined in @@ -226,7 +232,7 @@ public ValidationResult ValidateParams(IReleaseSpec spec, int version, out strin return ValidationResult.Fail; } - if (spec.ConsensusRequestsEnabled) + if (spec.RequestsEnabled) { error = "ExecutionPayloadV4 expected"; return ValidationResult.Fail; @@ -246,7 +252,7 @@ public ValidationResult ValidateParams(IReleaseSpec spec, int version, out strin private int GetExecutionPayloadVersion() => this switch { - { DepositRequests: not null, WithdrawalRequests: not null } => 4, + { DepositRequests: not null, WithdrawalRequests: not null, ConsolidationRequests: not null } => 4, { BlobGasUsed: not null } or { ExcessBlobGas: not null } or { ParentBeaconBlockRoot: not null } => 3, { Withdrawals: not null } => 2, _ => 1 diff --git a/src/Nethermind/Nethermind.Merge.Plugin/Data/ExecutionPayloadBodyV1Result.cs b/src/Nethermind/Nethermind.Merge.Plugin/Data/ExecutionPayloadBodyV1Result.cs index 2b20e438649..f6733147ad1 100644 --- a/src/Nethermind/Nethermind.Merge.Plugin/Data/ExecutionPayloadBodyV1Result.cs +++ b/src/Nethermind/Nethermind.Merge.Plugin/Data/ExecutionPayloadBodyV1Result.cs @@ -13,7 +13,7 @@ namespace Nethermind.Merge.Plugin.Data; public class ExecutionPayloadBodyV1Result { - public ExecutionPayloadBodyV1Result(IList transactions, IList? withdrawals) + public ExecutionPayloadBodyV1Result(IReadOnlyList transactions, IReadOnlyList? withdrawals) { ArgumentNullException.ThrowIfNull(transactions); @@ -28,8 +28,8 @@ public ExecutionPayloadBodyV1Result(IList transactions, IList Transactions { get; set; } + public IReadOnlyList Transactions { get; set; } [JsonIgnore(Condition = JsonIgnoreCondition.Never)] - public IList? Withdrawals { get; set; } + public IReadOnlyList? Withdrawals { get; set; } } diff --git a/src/Nethermind/Nethermind.Merge.Plugin/Data/ExecutionPayloadBodyV2Result.cs b/src/Nethermind/Nethermind.Merge.Plugin/Data/ExecutionPayloadBodyV2Result.cs index 508f900fc55..d929fcf6fd8 100644 --- a/src/Nethermind/Nethermind.Merge.Plugin/Data/ExecutionPayloadBodyV2Result.cs +++ b/src/Nethermind/Nethermind.Merge.Plugin/Data/ExecutionPayloadBodyV2Result.cs @@ -11,26 +11,34 @@ namespace Nethermind.Merge.Plugin.Data; public class ExecutionPayloadBodyV2Result : ExecutionPayloadBodyV1Result { public ExecutionPayloadBodyV2Result( - IList transactions, - IList? withdrawals, - IList? deposits, - IList? withdrawalsRequests + IReadOnlyList transactions, + IReadOnlyList? withdrawals, + IReadOnlyList? deposits, + IReadOnlyList? withdrawalsRequests, + IReadOnlyList? consolidationRequests ) : base(transactions, withdrawals) { DepositRequests = deposits; WithdrawalRequests = withdrawalsRequests; + ConsolidationRequests = consolidationRequests; } /// /// Deposit requests . /// [JsonIgnore(Condition = JsonIgnoreCondition.Never)] - public IList? DepositRequests { get; set; } + public IReadOnlyList? DepositRequests { get; set; } /// /// Withdrawal requests . /// [JsonIgnore(Condition = JsonIgnoreCondition.Never)] - public IList? WithdrawalRequests { get; set; } + public IReadOnlyList? WithdrawalRequests { get; set; } + + /// + /// Consolidation requests . + /// + [JsonIgnore(Condition = JsonIgnoreCondition.Never)] + public IReadOnlyList? ConsolidationRequests { get; set; } } diff --git a/src/Nethermind/Nethermind.Merge.Plugin/Data/ExecutionPayloadV4.cs b/src/Nethermind/Nethermind.Merge.Plugin/Data/ExecutionPayloadV4.cs index cef9b04c3fb..e0d65da1215 100644 --- a/src/Nethermind/Nethermind.Merge.Plugin/Data/ExecutionPayloadV4.cs +++ b/src/Nethermind/Nethermind.Merge.Plugin/Data/ExecutionPayloadV4.cs @@ -25,7 +25,7 @@ public class ExecutionPayloadV4 : ExecutionPayloadV3, IExecutionPayloadFactory(block); ConsensusRequest[]? blockRequests = block.Requests; - (executionPayload.DepositRequests, executionPayload.WithdrawalRequests) = blockRequests?.SplitRequests() ?? ([], []); + (executionPayload.DepositRequests, executionPayload.WithdrawalRequests, executionPayload.ConsolidationRequests) = blockRequests?.SplitRequests() ?? ([], [], []); return executionPayload; } @@ -40,7 +40,8 @@ public override bool TryGetBlock([NotNullWhen(true)] out Block? block, UInt256? var depositsLength = DepositRequests?.Length ?? 0; var withdrawalRequestsLength = WithdrawalRequests?.Length ?? 0; - var requestsCount = depositsLength + withdrawalRequestsLength; + var consolidationRequestsLength = ConsolidationRequests?.Length ?? 0; + var requestsCount = depositsLength + withdrawalRequestsLength + consolidationRequestsLength; if (requestsCount > 0) { var requests = new ConsensusRequest[requestsCount]; @@ -50,11 +51,16 @@ public override bool TryGetBlock([NotNullWhen(true)] out Block? block, UInt256? requests[i] = DepositRequests![i]; } - for (; i < requestsCount; ++i) + for (; i < depositsLength + withdrawalRequestsLength; ++i) { requests[i] = WithdrawalRequests![i - depositsLength]; } + for (; i < requestsCount; ++i) + { + requests[i] = ConsolidationRequests![i - depositsLength - withdrawalRequestsLength]; + } + block.Body.Requests = requests; block.Header.RequestsRoot = new RequestsTrie(requests).RootHash; } @@ -69,7 +75,8 @@ public override bool TryGetBlock([NotNullWhen(true)] out Block? block, UInt256? public override bool ValidateFork(ISpecProvider specProvider) => specProvider.GetSpec(BlockNumber, Timestamp).DepositsEnabled - && specProvider.GetSpec(BlockNumber, Timestamp).WithdrawalRequestsEnabled; + && specProvider.GetSpec(BlockNumber, Timestamp).WithdrawalRequestsEnabled + && specProvider.GetSpec(BlockNumber, Timestamp).ConsolidationRequestsEnabled; /// /// Gets or sets as defined in @@ -84,4 +91,11 @@ public override bool ValidateFork(ISpecProvider specProvider) => /// [JsonRequired] public sealed override WithdrawalRequest[]? WithdrawalRequests { get; set; } + + /// + /// Gets or sets as defined in + /// EIP-7251. + /// + [JsonRequired] + public sealed override ConsolidationRequest[]? ConsolidationRequests { get; set; } } diff --git a/src/Nethermind/Nethermind.Merge.Plugin/Handlers/EngineRpcCapabilitiesProvider.cs b/src/Nethermind/Nethermind.Merge.Plugin/Handlers/EngineRpcCapabilitiesProvider.cs index e69773391e4..57d1ab5f5bd 100644 --- a/src/Nethermind/Nethermind.Merge.Plugin/Handlers/EngineRpcCapabilitiesProvider.cs +++ b/src/Nethermind/Nethermind.Merge.Plugin/Handlers/EngineRpcCapabilitiesProvider.cs @@ -49,10 +49,17 @@ public EngineRpcCapabilitiesProvider(ISpecProvider specProvider) #endregion #region Prague - _capabilities[nameof(IEngineRpcModule.engine_getPayloadV4)] = (spec.ConsensusRequestsEnabled, spec.ConsensusRequestsEnabled); - _capabilities[nameof(IEngineRpcModule.engine_newPayloadV4)] = (spec.ConsensusRequestsEnabled, spec.ConsensusRequestsEnabled); - _capabilities[nameof(IEngineRpcModule.engine_getPayloadBodiesByHashV2)] = (spec.ConsensusRequestsEnabled, false); - _capabilities[nameof(IEngineRpcModule.engine_getPayloadBodiesByRangeV2)] = (spec.ConsensusRequestsEnabled, false); + _capabilities[nameof(IEngineRpcModule.engine_getPayloadV4)] = (spec.RequestsEnabled, spec.RequestsEnabled); + _capabilities[nameof(IEngineRpcModule.engine_newPayloadV4)] = (spec.RequestsEnabled, spec.RequestsEnabled); + _capabilities[nameof(IEngineRpcModule.engine_getPayloadBodiesByHashV2)] = (spec.RequestsEnabled, false); + _capabilities[nameof(IEngineRpcModule.engine_getPayloadBodiesByRangeV2)] = (spec.RequestsEnabled, false); + #endregion + + #region Prague + _capabilities[nameof(IEngineRpcModule.engine_getPayloadV4)] = (spec.RequestsEnabled, spec.RequestsEnabled); + _capabilities[nameof(IEngineRpcModule.engine_newPayloadV4)] = (spec.RequestsEnabled, spec.RequestsEnabled); + _capabilities[nameof(IEngineRpcModule.engine_getPayloadBodiesByHashV2)] = (spec.RequestsEnabled, false); + _capabilities[nameof(IEngineRpcModule.engine_getPayloadBodiesByRangeV2)] = (spec.RequestsEnabled, false); #endregion } diff --git a/src/Nethermind/Nethermind.Merge.Plugin/Handlers/GetPayloadBodiesByHashV2Handler.cs b/src/Nethermind/Nethermind.Merge.Plugin/Handlers/GetPayloadBodiesByHashV2Handler.cs index ff64739b948..0c95bbd88b0 100644 --- a/src/Nethermind/Nethermind.Merge.Plugin/Handlers/GetPayloadBodiesByHashV2Handler.cs +++ b/src/Nethermind/Nethermind.Merge.Plugin/Handlers/GetPayloadBodiesByHashV2Handler.cs @@ -31,8 +31,8 @@ public class GetPayloadBodiesByHashV2Handler(IBlockTree blockTree, ILogManager l for (int i = 0; i < blockHashes.Count; i++) { Block? block = _blockTree.FindBlock(blockHashes[i]); - (Deposit[]? deposits, WithdrawalRequest[]? withdrawalRequests) = block?.Requests?.SplitRequests() ?? (null, null); - yield return block is null ? null : new ExecutionPayloadBodyV2Result(block.Transactions, block.Withdrawals, deposits, withdrawalRequests); + (Deposit[]? deposits, WithdrawalRequest[]? withdrawalRequests, ConsolidationRequest[]? consolidationRequests) = block?.Requests?.SplitRequests() ?? (null, null, null); + yield return block is null ? null : new ExecutionPayloadBodyV2Result(block.Transactions, block.Withdrawals, deposits, withdrawalRequests, consolidationRequests); } } } diff --git a/src/Nethermind/Nethermind.Merge.Plugin/Handlers/GetPayloadBodiesByRangeV2Handler.cs b/src/Nethermind/Nethermind.Merge.Plugin/Handlers/GetPayloadBodiesByRangeV2Handler.cs index 4f97a0b800a..f1a3107457d 100644 --- a/src/Nethermind/Nethermind.Merge.Plugin/Handlers/GetPayloadBodiesByRangeV2Handler.cs +++ b/src/Nethermind/Nethermind.Merge.Plugin/Handlers/GetPayloadBodiesByRangeV2Handler.cs @@ -42,8 +42,8 @@ public class GetPayloadBodiesByRangeV2Handler(IBlockTree blockTree, ILogManager continue; } - (Deposit[]? deposits, WithdrawalRequest[]? withdrawalRequests) = block!.Requests?.SplitRequests() ?? (null, null); - yield return new ExecutionPayloadBodyV2Result(block.Transactions, block.Withdrawals, deposits, withdrawalRequests); + (Deposit[]? deposits, WithdrawalRequest[]? withdrawalRequests, ConsolidationRequest[]? consolidationRequests) = block!.Requests?.SplitRequests() ?? (null, null, null); + yield return new ExecutionPayloadBodyV2Result(block.Transactions, block.Withdrawals, deposits, withdrawalRequests, consolidationRequests); } yield break; diff --git a/src/Nethermind/Nethermind.Mev.Test/MevMegabundleTests.cs b/src/Nethermind/Nethermind.Mev.Test/MevMegabundleTests.cs new file mode 100644 index 00000000000..e69de29bb2d diff --git a/src/Nethermind/Nethermind.Mev/MevPlugin.cs b/src/Nethermind/Nethermind.Mev/MevPlugin.cs new file mode 100644 index 00000000000..e69de29bb2d diff --git a/src/Nethermind/Nethermind.Mev/Source/BundlePool.cs b/src/Nethermind/Nethermind.Mev/Source/BundlePool.cs new file mode 100644 index 00000000000..e69de29bb2d diff --git a/src/Nethermind/Nethermind.Network.Benchmark/Eth62ProtocolHandlerBenchmarks.cs b/src/Nethermind/Nethermind.Network.Benchmark/Eth62ProtocolHandlerBenchmarks.cs index 5700d30de35..538ae8f1ff6 100644 --- a/src/Nethermind/Nethermind.Network.Benchmark/Eth62ProtocolHandlerBenchmarks.cs +++ b/src/Nethermind/Nethermind.Network.Benchmark/Eth62ProtocolHandlerBenchmarks.cs @@ -16,6 +16,7 @@ using Nethermind.Core.Timers; using Nethermind.Crypto; using Nethermind.Db; +using Nethermind.Evm; using Nethermind.Logging; using Nethermind.Network.P2P; using Nethermind.Network.P2P.Analyzers; @@ -58,7 +59,7 @@ public void SetUp() TxPool.TxPool txPool = new TxPool.TxPool( ecdsa, new BlobTxStorage(), - new ChainHeadInfoProvider(new FixedForkActivationChainHeadSpecProvider(MainnetSpecProvider.Instance), tree, stateProvider), + new ChainHeadInfoProvider(new FixedForkActivationChainHeadSpecProvider(MainnetSpecProvider.Instance), tree, stateProvider, new CodeInfoRepository()), new TxPoolConfig(), new TxValidator(TestBlockchainIds.ChainId), LimboLogs.Instance, diff --git a/src/Nethermind/Nethermind.Network/P2P/Subprotocols/Eth/V62/Messages/BlockBodiesMessageSerializer.cs b/src/Nethermind/Nethermind.Network/P2P/Subprotocols/Eth/V62/Messages/BlockBodiesMessageSerializer.cs index 65fd4af43d8..c037fe6b16e 100644 --- a/src/Nethermind/Nethermind.Network/P2P/Subprotocols/Eth/V62/Messages/BlockBodiesMessageSerializer.cs +++ b/src/Nethermind/Nethermind.Network/P2P/Subprotocols/Eth/V62/Messages/BlockBodiesMessageSerializer.cs @@ -59,7 +59,7 @@ private class BlockBodyDecoder : IRlpValueDecoder private readonly TxDecoder _txDecoder = TxDecoder.Instance; private readonly HeaderDecoder _headerDecoder = new(); private readonly WithdrawalDecoder _withdrawalDecoderDecoder = new(); - private readonly ConsensusRequestDecoder _requestsDecoder = new(); + private readonly ConsensusRequestDecoder _requestsDecoder = ConsensusRequestDecoder.Instance; public int GetLength(BlockBody item, RlpBehaviors rlpBehaviors) { diff --git a/src/Nethermind/Nethermind.Optimism/OptimismEthereumEcdsa.cs b/src/Nethermind/Nethermind.Optimism/OptimismEthereumEcdsa.cs index f70db353bf7..16c2e6b8cf8 100644 --- a/src/Nethermind/Nethermind.Optimism/OptimismEthereumEcdsa.cs +++ b/src/Nethermind/Nethermind.Optimism/OptimismEthereumEcdsa.cs @@ -12,25 +12,13 @@ public class OptimismEthereumEcdsa : Ecdsa, IEthereumEcdsa { private readonly IEthereumEcdsa _ethereumEcdsa; + public ulong ChainId => _ethereumEcdsa.ChainId; + public OptimismEthereumEcdsa(IEthereumEcdsa ethereumEcdsa) { _ethereumEcdsa = ethereumEcdsa; } - - public void Sign(PrivateKey privateKey, Transaction tx, bool isEip155Enabled = true) => _ethereumEcdsa.Sign(privateKey, tx, isEip155Enabled); - - public Address? RecoverAddress(Transaction tx, bool useSignatureChainId = false) - { - if (tx.Signature is null && tx.IsOPSystemTransaction) - { - return Address.Zero; - } - return _ethereumEcdsa.RecoverAddress(tx, useSignatureChainId); - } - public Address? RecoverAddress(Signature signature, Hash256 message) => _ethereumEcdsa.RecoverAddress(signature, message); public Address? RecoverAddress(Span signatureBytes, Hash256 message) => _ethereumEcdsa.RecoverAddress(signatureBytes, message); - - public bool Verify(Address sender, Transaction tx) => _ethereumEcdsa.Verify(sender, tx); } diff --git a/src/Nethermind/Nethermind.Optimism/OptimismTransactionProcessor.cs b/src/Nethermind/Nethermind.Optimism/OptimismTransactionProcessor.cs index 481c655d757..58dab4bab85 100644 --- a/src/Nethermind/Nethermind.Optimism/OptimismTransactionProcessor.cs +++ b/src/Nethermind/Nethermind.Optimism/OptimismTransactionProcessor.cs @@ -153,7 +153,7 @@ protected override void PayFees(Transaction tx, BlockHeader header, IReleaseSpec } protected override long Refund(Transaction tx, BlockHeader header, IReleaseSpec spec, ExecutionOptions opts, - in TransactionSubstate substate, in long unspentGas, in UInt256 gasPrice) + in TransactionSubstate substate, in long unspentGas, in UInt256 gasPrice, int refunds) { // if deposit: skip refunds, skip tipping coinbase // Regolith changes this behaviour to report the actual gasUsed instead of always reporting all gas used. @@ -164,6 +164,6 @@ protected override long Refund(Transaction tx, BlockHeader header, IReleaseSpec return tx.IsOPSystemTransaction ? 0 : tx.GasLimit; } - return base.Refund(tx, header, spec, opts, substate, unspentGas, gasPrice); + return base.Refund(tx, header, spec, opts, substate, unspentGas, gasPrice, refunds); } } diff --git a/src/Nethermind/Nethermind.Runner/configs/hive.cfg b/src/Nethermind/Nethermind.Runner/configs/hive.cfg index 4bbfac20a03..d0d51c733ce 100644 --- a/src/Nethermind/Nethermind.Runner/configs/hive.cfg +++ b/src/Nethermind/Nethermind.Runner/configs/hive.cfg @@ -9,7 +9,12 @@ }, "JsonRpc": { "Enabled": true, - "Host": "0.0.0.0" + "Timeout": 20000, + "Host": "127.0.0.1", + "Port": 8545, + "EngineHost": "127.0.0.1", + "EnginePort": 8551, + "EngineEnabledModules": "net,eth,subscribe,engine,web3,client" }, "Network": { "ExternalIp": "127.0.0.1" diff --git a/src/Nethermind/Nethermind.Serialization.Rlp/BlockDecoder.cs b/src/Nethermind/Nethermind.Serialization.Rlp/BlockDecoder.cs index 3c6eba08339..0530f141fd8 100644 --- a/src/Nethermind/Nethermind.Serialization.Rlp/BlockDecoder.cs +++ b/src/Nethermind/Nethermind.Serialization.Rlp/BlockDecoder.cs @@ -14,7 +14,7 @@ public class BlockDecoder : IRlpValueDecoder, IRlpStreamDecoder private readonly HeaderDecoder _headerDecoder = new(); private readonly TxDecoder _txDecoder = TxDecoder.Instance; private readonly WithdrawalDecoder _withdrawalDecoder = new(); - private readonly ConsensusRequestDecoder _consensusRequestsDecoder = new(); + private readonly ConsensusRequestDecoder _consensusRequestsDecoder = ConsensusRequestDecoder.Instance; public Block? Decode(RlpStream rlpStream, RlpBehaviors rlpBehaviors = RlpBehaviors.None) { diff --git a/src/Nethermind/Nethermind.Serialization.Rlp/ConsensusRequestDecoder.cs b/src/Nethermind/Nethermind.Serialization.Rlp/ConsensusRequestDecoder.cs index e06c135bd9b..91ac5675f87 100644 --- a/src/Nethermind/Nethermind.Serialization.Rlp/ConsensusRequestDecoder.cs +++ b/src/Nethermind/Nethermind.Serialization.Rlp/ConsensusRequestDecoder.cs @@ -8,12 +8,16 @@ namespace Nethermind.Serialization.Rlp; public class ConsensusRequestDecoder : IRlpStreamDecoder, IRlpValueDecoder, IRlpObjectDecoder { - private readonly WithdrawalRequestDecoder _withdrawalRequestDecoder = new(); + public static ConsensusRequestDecoder Instance { get; } = new(); + private readonly DepositDecoder _depositDecoder = DepositDecoder.Instance; + private readonly WithdrawalRequestDecoder _withdrawalRequestDecoder = WithdrawalRequestDecoder.Instance; + private readonly ConsolidationRequestDecoder _consolidationRequestDecoder = ConsolidationRequestDecoder.Instance; public int GetContentLength(ConsensusRequest item, RlpBehaviors rlpBehaviors) { int length = item.Type switch { + ConsensusRequestsType.ConsolidationRequest => _consolidationRequestDecoder.GetContentLength((ConsolidationRequest)item, rlpBehaviors), ConsensusRequestsType.WithdrawalRequest => _withdrawalRequestDecoder.GetContentLength((WithdrawalRequest)item, rlpBehaviors), ConsensusRequestsType.Deposit => _depositDecoder.GetContentLength((Deposit)item, rlpBehaviors), _ => throw new RlpException($"Unsupported consensus request type {item.Type}") @@ -25,6 +29,7 @@ public int GetLength(ConsensusRequest item, RlpBehaviors rlpBehaviors) { int length = item.Type switch { + ConsensusRequestsType.ConsolidationRequest => _consolidationRequestDecoder.GetLength((ConsolidationRequest)item, rlpBehaviors), ConsensusRequestsType.WithdrawalRequest => _withdrawalRequestDecoder.GetLength((WithdrawalRequest)item, rlpBehaviors), ConsensusRequestsType.Deposit => _depositDecoder.GetLength((Deposit)item, rlpBehaviors), _ => throw new RlpException($"Unsupported consensus request type {item.Type}") @@ -55,6 +60,7 @@ public int GetLength(ConsensusRequest item, RlpBehaviors rlpBehaviors) ConsensusRequest result = consensusRequestsType switch { + ConsensusRequestsType.ConsolidationRequest => _consolidationRequestDecoder.Decode(rlpStream, rlpBehaviors), ConsensusRequestsType.WithdrawalRequest => _withdrawalRequestDecoder.Decode(rlpStream, rlpBehaviors), ConsensusRequestsType.Deposit => _depositDecoder.Decode(rlpStream, rlpBehaviors), @@ -81,6 +87,7 @@ public ConsensusRequest Decode(ref Rlp.ValueDecoderContext decoderContext, RlpBe ConsensusRequest result = consensusRequestsType switch { + ConsensusRequestsType.ConsolidationRequest => _consolidationRequestDecoder.Decode(ref decoderContext, rlpBehaviors), ConsensusRequestsType.WithdrawalRequest => _withdrawalRequestDecoder.Decode(ref decoderContext, rlpBehaviors), ConsensusRequestsType.Deposit => _depositDecoder.Decode(ref decoderContext, rlpBehaviors), _ => throw new RlpException($"Unsupported consensus request type {consensusRequestsType}") @@ -102,6 +109,9 @@ public void Encode(RlpStream stream, ConsensusRequest item, RlpBehaviors rlpBeha stream.WriteByte((byte)item.Type); switch (item.Type) { + case ConsensusRequestsType.ConsolidationRequest: + _consolidationRequestDecoder.Encode(stream, (ConsolidationRequest)item, rlpBehaviors); + break; case ConsensusRequestsType.WithdrawalRequest: _withdrawalRequestDecoder.Encode(stream, (WithdrawalRequest)item, rlpBehaviors); break; diff --git a/src/Nethermind/Nethermind.Serialization.Rlp/ConsolidationRequestDecoder.cs b/src/Nethermind/Nethermind.Serialization.Rlp/ConsolidationRequestDecoder.cs new file mode 100644 index 00000000000..99b2f3e49e8 --- /dev/null +++ b/src/Nethermind/Nethermind.Serialization.Rlp/ConsolidationRequestDecoder.cs @@ -0,0 +1,58 @@ +// SPDX-FileCopyrightText: 2024 Demerzel Solutions Limited +// SPDX-License-Identifier: LGPL-3.0-only + +using System; +using Nethermind.Core; +using Nethermind.Core.ConsensusRequests; + +namespace Nethermind.Serialization.Rlp; + +public class ConsolidationRequestDecoder : IRlpStreamDecoder, IRlpValueDecoder +{ + public static ConsolidationRequestDecoder Instance { get; } = new(); + public int GetLength(ConsolidationRequest item, RlpBehaviors rlpBehaviors) => + Rlp.LengthOfSequence(GetContentLength(item, rlpBehaviors)); + + public int GetContentLength(ConsolidationRequest item, RlpBehaviors rlpBehaviors) => + Rlp.LengthOf(item.SourceAddress) + Rlp.LengthOf(item.SourcePubkey) + + Rlp.LengthOf(item.TargetPubkey); + + public ConsolidationRequest Decode(RlpStream rlpStream, RlpBehaviors rlpBehaviors = RlpBehaviors.None) + { + int _ = rlpStream.ReadSequenceLength(); + Address sourceAddress = rlpStream.DecodeAddress(); + ArgumentNullException.ThrowIfNull(sourceAddress); + byte[] SourcePubkey = rlpStream.DecodeByteArray(); + byte[] TargetPubkey = rlpStream.DecodeByteArray(); + return new ConsolidationRequest() + { + SourceAddress = sourceAddress, + SourcePubkey = SourcePubkey, + TargetPubkey = TargetPubkey + }; + } + + public ConsolidationRequest Decode(ref Rlp.ValueDecoderContext decoderContext, RlpBehaviors rlpBehaviors = RlpBehaviors.None) + { + int _ = decoderContext.ReadSequenceLength(); + Address sourceAddress = decoderContext.DecodeAddress(); + ArgumentNullException.ThrowIfNull(sourceAddress); + byte[] SourcePubkey = decoderContext.DecodeByteArray(); + byte[] TargetPubkey = decoderContext.DecodeByteArray(); + return new ConsolidationRequest() + { + SourceAddress = sourceAddress, + SourcePubkey = SourcePubkey, + TargetPubkey = TargetPubkey + }; + } + + public void Encode(RlpStream stream, ConsolidationRequest item, RlpBehaviors rlpBehaviors = RlpBehaviors.None) + { + int contentLength = GetContentLength(item, rlpBehaviors); + stream.StartSequence(contentLength); + stream.Encode(item.SourceAddress); + stream.Encode(item.SourcePubkey); + stream.Encode(item.TargetPubkey); + } +} diff --git a/src/Nethermind/Nethermind.Serialization.Rlp/Eip7702/AuthorizationTupleDecoder.cs b/src/Nethermind/Nethermind.Serialization.Rlp/Eip7702/AuthorizationTupleDecoder.cs new file mode 100644 index 00000000000..b3e7dfcc80d --- /dev/null +++ b/src/Nethermind/Nethermind.Serialization.Rlp/Eip7702/AuthorizationTupleDecoder.cs @@ -0,0 +1,120 @@ +// SPDX-FileCopyrightText: 2024 Demerzel Solutions Limited +// SPDX-License-Identifier: LGPL-3.0-only + +using DotNetty.Buffers; +using Nethermind.Core; +using Nethermind.Int256; +using Nethermind.Serialization.Rlp.Eip2930; +using System; +using System.Diagnostics; +using System.Diagnostics.CodeAnalysis; + +namespace Nethermind.Serialization.Rlp; + +public class AuthorizationTupleDecoder : IRlpStreamDecoder, IRlpValueDecoder +{ + public static readonly AuthorizationTupleDecoder Instance = new(); + + public AuthorizationTuple Decode(RlpStream stream, RlpBehaviors rlpBehaviors = RlpBehaviors.None) + { + int length = stream.ReadSequenceLength(); + int check = length + stream.Position; + ulong chainId = stream.DecodeULong(); + Address? codeAddress = stream.DecodeAddress(); + ulong nonce = stream.DecodeULong(); + ulong yParity = stream.DecodeULong(); + byte[] r = stream.DecodeByteArray(); + byte[] s = stream.DecodeByteArray(); + + if (!rlpBehaviors.HasFlag(RlpBehaviors.AllowExtraBytes)) + { + stream.Check(check); + } + + if (codeAddress is null) + { + ThrowMissingCodeAddressException(); + } + + return new AuthorizationTuple(chainId, codeAddress, nonce, yParity, r, s); + } + + public AuthorizationTuple Decode(ref Rlp.ValueDecoderContext decoderContext, RlpBehaviors rlpBehaviors = RlpBehaviors.None) + { + int length = decoderContext.ReadSequenceLength(); + int check = length + decoderContext.Position; + ulong chainId = decoderContext.DecodeULong(); + Address? codeAddress = decoderContext.DecodeAddress(); + ulong nonce = decoderContext.DecodeULong(); + ulong yParity = decoderContext.DecodeULong(); + byte[] r = decoderContext.DecodeByteArray(); + byte[] s = decoderContext.DecodeByteArray(); + + if (!rlpBehaviors.HasFlag(RlpBehaviors.AllowExtraBytes)) + { + decoderContext.Check(check); + } + + if (codeAddress is null) + { + ThrowMissingCodeAddressException(); + } + + return new AuthorizationTuple(chainId, codeAddress, nonce, yParity, r, s); + } + + public RlpStream Encode(AuthorizationTuple item, RlpBehaviors rlpBehaviors = RlpBehaviors.None) + { + RlpStream stream = new(GetLength(item, rlpBehaviors)); + Encode(stream, item, rlpBehaviors); + return stream; + } + + public void Encode(RlpStream stream, AuthorizationTuple item, RlpBehaviors rlpBehaviors = RlpBehaviors.None) + { + int contentLength = GetContentLength(item); + stream.StartSequence(contentLength); + stream.Encode(item.ChainId); + stream.Encode(item.CodeAddress); + stream.Encode(item.Nonce); + stream.Encode(item.AuthoritySignature.RecoveryId); + stream.Encode(new UInt256(item.AuthoritySignature.R, true)); + stream.Encode(new UInt256(item.AuthoritySignature.S, true)); + } + + public NettyRlpStream EncodeWithoutSignature(ulong chainId, Address codeAddress, ulong nonce) + { + int contentLength = GetContentLengthWithoutSig(chainId, codeAddress, nonce); + var totalLength = Rlp.LengthOfSequence(contentLength); + IByteBuffer byteBuffer = PooledByteBufferAllocator.Default.Buffer(totalLength); + NettyRlpStream stream = new(byteBuffer); + EncodeWithoutSignature(stream, chainId, codeAddress, nonce); + return stream; + } + + public void EncodeWithoutSignature(RlpStream stream, ulong chainId, Address codeAddress, ulong nonce) + { + int contentLength = GetContentLengthWithoutSig(chainId, codeAddress, nonce); + stream.StartSequence(contentLength); + stream.Encode(chainId); + stream.Encode(codeAddress ?? throw new RlpException($"Invalid tx {nameof(AuthorizationTuple)} format - address is null")); + stream.Encode(nonce); + } + + public int GetLength(AuthorizationTuple item, RlpBehaviors rlpBehaviors) => Rlp.LengthOfSequence(GetContentLength(item)); + + private static int GetContentLength(AuthorizationTuple tuple) => + GetContentLengthWithoutSig(tuple.ChainId, tuple.CodeAddress, tuple.Nonce) + + Rlp.LengthOf(tuple.AuthoritySignature.RecoveryId) + + Rlp.LengthOf(new UInt256(tuple.AuthoritySignature.R.AsSpan(), true)) + + Rlp.LengthOf(new UInt256(tuple.AuthoritySignature.S.AsSpan(), true)); + + private static int GetContentLengthWithoutSig(ulong chainId, Address codeAddress, ulong nonce) => + Rlp.LengthOf(chainId) + + Rlp.LengthOf(codeAddress) + + Rlp.LengthOf(nonce); + + [DoesNotReturn] + [StackTraceHidden] + private static void ThrowMissingCodeAddressException() => throw new RlpException("Missing code address for Authorization"); +} diff --git a/src/Nethermind/Nethermind.Serialization.Rlp/RlpStream.cs b/src/Nethermind/Nethermind.Serialization.Rlp/RlpStream.cs index 6672a485297..5c1919959ef 100644 --- a/src/Nethermind/Nethermind.Serialization.Rlp/RlpStream.cs +++ b/src/Nethermind/Nethermind.Serialization.Rlp/RlpStream.cs @@ -27,7 +27,7 @@ public class RlpStream private static readonly BlockInfoDecoder _blockInfoDecoder = new(); private static readonly TxDecoder _txDecoder = TxDecoder.Instance; private static readonly WithdrawalDecoder _withdrawalDecoder = new(); - private static readonly ConsensusRequestDecoder _requestsDecoder = new(); + private static readonly ConsensusRequestDecoder _requestsDecoder = ConsensusRequestDecoder.Instance; private static readonly LogEntryDecoder _logEntryDecoder = LogEntryDecoder.Instance; private readonly CappedArray _data; @@ -56,6 +56,23 @@ public RlpStream(in CappedArray data) _data = data; } + public void EncodeArray(T?[]? items, RlpBehaviors rlpBehaviors = RlpBehaviors.None) + { + if (items is null) + { + WriteByte(Rlp.NullObjectByte); + return; + } + IRlpStreamDecoder decoder = Rlp.GetStreamDecoder(); + int contentLength = decoder.GetContentLength(items); + + StartSequence(contentLength); + + foreach (var item in items) + { + decoder.Encode(this, item, rlpBehaviors); + } + } public void Encode(Block value) { _blockDecoder.Encode(this, value); diff --git a/src/Nethermind/Nethermind.Serialization.Rlp/TxDecoder.cs b/src/Nethermind/Nethermind.Serialization.Rlp/TxDecoder.cs index 6a720d6e5e5..0c16876e02f 100644 --- a/src/Nethermind/Nethermind.Serialization.Rlp/TxDecoder.cs +++ b/src/Nethermind/Nethermind.Serialization.Rlp/TxDecoder.cs @@ -40,6 +40,7 @@ protected TxDecoder(Func? transactionFactory = null) RegisterDecoder(new AccessListTxDecoder(factory)); RegisterDecoder(new EIP1559TxDecoder(factory)); RegisterDecoder(new BlobTxDecoder(factory)); + RegisterDecoder(new SetCodeTxDecoder(factory)); } public void RegisterDecoder(ITxDecoder decoder) => _decoders[(int)decoder.Type] = decoder; diff --git a/src/Nethermind/Nethermind.Serialization.Rlp/TxDecoders/BaseTxDecoder.cs b/src/Nethermind/Nethermind.Serialization.Rlp/TxDecoders/BaseTxDecoder.cs index ab72eef75e7..a8918382c1d 100644 --- a/src/Nethermind/Nethermind.Serialization.Rlp/TxDecoders/BaseTxDecoder.cs +++ b/src/Nethermind/Nethermind.Serialization.Rlp/TxDecoders/BaseTxDecoder.cs @@ -155,7 +155,7 @@ protected virtual void DecodeGasPrice(Transaction transaction, ref Rlp.ValueDeco transaction.GasPrice = decoderContext.DecodeUInt256(); } - private Signature? DecodeSignature(Transaction transaction, RlpStream rlpStream, RlpBehaviors rlpBehaviors = RlpBehaviors.None) + protected Signature? DecodeSignature(Transaction transaction, RlpStream rlpStream, RlpBehaviors rlpBehaviors = RlpBehaviors.None) { ulong v = rlpStream.DecodeULong(); ReadOnlySpan rBytes = rlpStream.DecodeByteArraySpan(); @@ -163,7 +163,7 @@ protected virtual void DecodeGasPrice(Transaction transaction, ref Rlp.ValueDeco return DecodeSignature(v, rBytes, sBytes, transaction.Signature, rlpBehaviors); } - private Signature? DecodeSignature(Transaction transaction, ref Rlp.ValueDecoderContext decoderContext, RlpBehaviors rlpBehaviors = RlpBehaviors.None) + protected Signature? DecodeSignature(Transaction transaction, ref Rlp.ValueDecoderContext decoderContext, RlpBehaviors rlpBehaviors = RlpBehaviors.None) { ulong v = decoderContext.DecodeULong(); ReadOnlySpan rBytes = decoderContext.DecodeByteArraySpan(); diff --git a/src/Nethermind/Nethermind.Serialization.Rlp/TxDecoders/SetCodeTxDecoder.cs b/src/Nethermind/Nethermind.Serialization.Rlp/TxDecoders/SetCodeTxDecoder.cs new file mode 100644 index 00000000000..ffc110f998b --- /dev/null +++ b/src/Nethermind/Nethermind.Serialization.Rlp/TxDecoders/SetCodeTxDecoder.cs @@ -0,0 +1,38 @@ +// SPDX-FileCopyrightText: 2022 Demerzel Solutions Limited +// SPDX-License-Identifier: LGPL-3.0-only + +using System; +using Nethermind.Core; + +namespace Nethermind.Serialization.Rlp.TxDecoders; + +public sealed class SetCodeTxDecoder(Func? transactionFactory = null) + : BaseEIP1559TxDecoder(TxType.SetCode, transactionFactory) where T : Transaction, new() +{ + private AuthorizationTupleDecoder _authTupleDecoder = new(); + + protected override void DecodePayload(Transaction transaction, RlpStream rlpStream, RlpBehaviors rlpBehaviors = RlpBehaviors.None) + { + base.DecodePayload(transaction, rlpStream, rlpBehaviors); + transaction.AuthorizationList = rlpStream.DecodeArray((s) => _authTupleDecoder.Decode(s, rlpBehaviors)); + } + + protected override void DecodePayload(Transaction transaction, ref Rlp.ValueDecoderContext decoderContext, + RlpBehaviors rlpBehaviors = RlpBehaviors.None) + { + base.DecodePayload(transaction, ref decoderContext, rlpBehaviors); + transaction.AuthorizationList = decoderContext.DecodeArray(_authTupleDecoder); + } + + protected override void EncodePayload(Transaction transaction, RlpStream stream, RlpBehaviors rlpBehaviors = RlpBehaviors.None) + { + base.EncodePayload(transaction, stream, rlpBehaviors); + stream.EncodeArray(transaction.AuthorizationList, rlpBehaviors); + } + + protected override int GetPayloadLength(Transaction transaction) + { + return base.GetPayloadLength(transaction) + + (transaction.AuthorizationList is null ? 1 : Rlp.LengthOfSequence(_authTupleDecoder.GetContentLength(transaction.AuthorizationList, RlpBehaviors.None))); + } +} diff --git a/src/Nethermind/Nethermind.Serialization.Rlp/WithdrawalRequestDecoder.cs b/src/Nethermind/Nethermind.Serialization.Rlp/WithdrawalRequestDecoder.cs index 4a0de9f3288..450a842cc9c 100644 --- a/src/Nethermind/Nethermind.Serialization.Rlp/WithdrawalRequestDecoder.cs +++ b/src/Nethermind/Nethermind.Serialization.Rlp/WithdrawalRequestDecoder.cs @@ -9,6 +9,7 @@ namespace Nethermind.Serialization.Rlp; public class WithdrawalRequestDecoder : IRlpStreamDecoder, IRlpValueDecoder, IRlpObjectDecoder { + public static WithdrawalRequestDecoder Instance { get; } = new(); public int GetLength(WithdrawalRequest item, RlpBehaviors rlpBehaviors) => Rlp.LengthOfSequence(GetContentLength(item, rlpBehaviors)); diff --git a/src/Nethermind/Nethermind.Specs.Test/ChainSpecStyle/ChainSpecBasedSpecProviderTests.cs b/src/Nethermind/Nethermind.Specs.Test/ChainSpecStyle/ChainSpecBasedSpecProviderTests.cs index 72f9b114458..ba794b320b1 100644 --- a/src/Nethermind/Nethermind.Specs.Test/ChainSpecStyle/ChainSpecBasedSpecProviderTests.cs +++ b/src/Nethermind/Nethermind.Specs.Test/ChainSpecStyle/ChainSpecBasedSpecProviderTests.cs @@ -708,7 +708,9 @@ public void Eip_transitions_loaded_correctly() Eip3855TransitionTimestamp = 1000000012, Eip3860TransitionTimestamp = 1000000012, Eip1153TransitionTimestamp = 1000000024, - Eip2537TransitionTimestamp = 1000000024 + Eip2537TransitionTimestamp = 1000000024, + + Eip7702TransitionTimestamp = 1000000032, } }; @@ -787,6 +789,7 @@ void TestTransitions(ForkActivation activation, Action changes) r.IsEip3860Enabled = true; }); TestTransitions((40001L, 1000000024), r => { r.IsEip1153Enabled = r.IsEip2537Enabled = true; }); + TestTransitions((40001L, 1000000032), r => { r.IsEip7702Enabled = true; }); } [TestCaseSource(nameof(BlockNumbersAndTimestampsNearForkActivations))] diff --git a/src/Nethermind/Nethermind.Specs.Test/OverridableReleaseSpec.cs b/src/Nethermind/Nethermind.Specs.Test/OverridableReleaseSpec.cs index 0c329498934..5394d1c475d 100644 --- a/src/Nethermind/Nethermind.Specs.Test/OverridableReleaseSpec.cs +++ b/src/Nethermind/Nethermind.Specs.Test/OverridableReleaseSpec.cs @@ -157,9 +157,12 @@ public ulong Eip4844TransitionTimestamp public Address? Eip4788ContractAddress => _spec.Eip4788ContractAddress; public bool IsEip7002Enabled => _spec.IsEip7002Enabled; public Address Eip7002ContractAddress => _spec.Eip7002ContractAddress; + public bool IsEip7251Enabled => _spec.IsEip7251Enabled; + public Address Eip7251ContractAddress => _spec.Eip7251ContractAddress; public bool IsEip2935Enabled => _spec.IsEip2935Enabled; public bool IsEip7709Enabled => _spec.IsEip7709Enabled; public Address Eip2935ContractAddress => _spec.Eip2935ContractAddress; + public bool IsEip7702Enabled => _spec.IsEip7702Enabled; public UInt256 ForkBaseFee => _spec.ForkBaseFee; public UInt256 BaseFeeMaxChangeDenominator => _spec.BaseFeeMaxChangeDenominator; public long ElasticityMultiplier => _spec.ElasticityMultiplier; diff --git a/src/Nethermind/Nethermind.Specs/ChainSpecStyle/ChainParameters.cs b/src/Nethermind/Nethermind.Specs/ChainSpecStyle/ChainParameters.cs index 7c69667757c..4252264aab1 100644 --- a/src/Nethermind/Nethermind.Specs/ChainSpecStyle/ChainParameters.cs +++ b/src/Nethermind/Nethermind.Specs/ChainSpecStyle/ChainParameters.cs @@ -122,9 +122,12 @@ public class ChainParameters public Address DepositContractAddress { get; set; } public ulong? Eip7002TransitionTimestamp { get; set; } public Address Eip7002ContractAddress { get; set; } + public ulong? Eip7251TransitionTimestamp { get; set; } + public Address Eip7251ContractAddress { get; set; } public ulong? Eip2935TransitionTimestamp { get; set; } public Address Eip2935ContractAddress { get; set; } public ulong? Rip7212TransitionTimestamp { get; set; } + public ulong? Eip7702TransitionTimestamp { get; set; } public ulong? OpGraniteTransitionTimestamp { get; set; } #region EIP-4844 parameters diff --git a/src/Nethermind/Nethermind.Specs/ChainSpecStyle/ChainSpecBasedSpecProvider.cs b/src/Nethermind/Nethermind.Specs/ChainSpecStyle/ChainSpecBasedSpecProvider.cs index 5ff3ce60863..bb7c9a7011e 100644 --- a/src/Nethermind/Nethermind.Specs/ChainSpecStyle/ChainSpecBasedSpecProvider.cs +++ b/src/Nethermind/Nethermind.Specs/ChainSpecStyle/ChainSpecBasedSpecProvider.cs @@ -259,12 +259,17 @@ private static ReleaseSpec CreateReleaseSpec(ChainSpec chainSpec, long releaseSt releaseSpec.IsEip2935Enabled = (chainSpec.Parameters.Eip2935TransitionTimestamp ?? ulong.MaxValue) <= releaseStartTimestamp; releaseSpec.Eip2935ContractAddress = chainSpec.Parameters.Eip2935ContractAddress; + releaseSpec.IsEip7702Enabled = (chainSpec.Parameters.Eip7702TransitionTimestamp ?? ulong.MaxValue) <= releaseStartTimestamp; + releaseSpec.IsEip6110Enabled = (chainSpec.Parameters.Eip6110TransitionTimestamp ?? ulong.MaxValue) <= releaseStartTimestamp; releaseSpec.DepositContractAddress = chainSpec.Parameters.DepositContractAddress; releaseSpec.IsEip7002Enabled = (chainSpec.Parameters.Eip7002TransitionTimestamp ?? ulong.MaxValue) <= releaseStartTimestamp; releaseSpec.Eip7002ContractAddress = chainSpec.Parameters.Eip7002ContractAddress; + releaseSpec.IsEip7251Enabled = (chainSpec.Parameters.Eip7251TransitionTimestamp ?? ulong.MaxValue) <= releaseStartTimestamp; + releaseSpec.Eip7251ContractAddress = chainSpec.Parameters.Eip7251ContractAddress; + return releaseSpec; } diff --git a/src/Nethermind/Nethermind.Specs/ChainSpecStyle/ChainSpecLoader.cs b/src/Nethermind/Nethermind.Specs/ChainSpecStyle/ChainSpecLoader.cs index cd71dc2bbcd..1f45f5c83c3 100644 --- a/src/Nethermind/Nethermind.Specs/ChainSpecStyle/ChainSpecLoader.cs +++ b/src/Nethermind/Nethermind.Specs/ChainSpecStyle/ChainSpecLoader.cs @@ -146,6 +146,7 @@ bool GetForInnerPathExistence(KeyValuePair o) => Rip7212TransitionTimestamp = chainSpecJson.Params.Rip7212TransitionTimestamp, OpGraniteTransitionTimestamp = chainSpecJson.Params.OpGraniteTransitionTimestamp, Eip4788TransitionTimestamp = chainSpecJson.Params.Eip4788TransitionTimestamp, + Eip7702TransitionTimestamp = chainSpecJson.Params.Eip7702TransitionTimestamp, Eip4788ContractAddress = chainSpecJson.Params.Eip4788ContractAddress ?? Eip4788Constants.BeaconRootsAddress, Eip2935TransitionTimestamp = chainSpecJson.Params.Eip2935TransitionTimestamp, Eip2935ContractAddress = chainSpecJson.Params.Eip2935ContractAddress ?? Eip2935Constants.BlockHashHistoryAddress, @@ -162,6 +163,8 @@ bool GetForInnerPathExistence(KeyValuePair o) => DepositContractAddress = chainSpecJson.Params.DepositContractAddress ?? Eip6110Constants.MainnetDepositContractAddress, Eip7002TransitionTimestamp = chainSpecJson.Params.Eip7002TransitionTimestamp, Eip7002ContractAddress = chainSpecJson.Params.Eip7002ContractAddress ?? Eip7002Constants.WithdrawalRequestPredeployAddress, + Eip7251TransitionTimestamp = chainSpecJson.Params.Eip7251TransitionTimestamp, + Eip7251ContractAddress = chainSpecJson.Params.Eip7251ContractAddress ?? Eip7251Constants.ConsolidationRequestPredeployAddress, FeeCollector = chainSpecJson.Params.FeeCollector, Eip1559FeeCollectorTransition = chainSpecJson.Params.Eip1559FeeCollectorTransition, Eip1559BaseFeeMinValueTransition = chainSpecJson.Params.Eip1559BaseFeeMinValueTransition, @@ -425,10 +428,11 @@ private static void LoadGenesis(ChainSpecJson chainSpecJson, ChainSpec chainSpec 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; if (withdrawalsEnabled) genesisHeader.WithdrawalsRoot = Keccak.EmptyTreeHash; - bool requestsEnabled = depositsEnabled || withdrawalRequestsEnabled; + var requestsEnabled = depositsEnabled || withdrawalRequestsEnabled || consolidationRequestsEnabled; if (requestsEnabled) genesisHeader.RequestsRoot = Keccak.EmptyTreeHash; ; diff --git a/src/Nethermind/Nethermind.Specs/ChainSpecStyle/Json/ChainSpecParamsJson.cs b/src/Nethermind/Nethermind.Specs/ChainSpecStyle/Json/ChainSpecParamsJson.cs index 45ebbf77e38..f905249c4e8 100644 --- a/src/Nethermind/Nethermind.Specs/ChainSpecStyle/Json/ChainSpecParamsJson.cs +++ b/src/Nethermind/Nethermind.Specs/ChainSpecStyle/Json/ChainSpecParamsJson.cs @@ -153,6 +153,9 @@ internal class ChainSpecParamsJson public Address DepositContractAddress { get; set; } public ulong? Eip7002TransitionTimestamp { get; set; } public Address Eip7002ContractAddress { get; set; } + public ulong? Eip7251TransitionTimestamp { get; set; } + public Address Eip7251ContractAddress { get; set; } public ulong? Rip7212TransitionTimestamp { get; set; } + public ulong? Eip7702TransitionTimestamp { get; set; } public ulong? OpGraniteTransitionTimestamp { get; set; } } diff --git a/src/Nethermind/Nethermind.Specs/Forks/00_Olympic.cs b/src/Nethermind/Nethermind.Specs/Forks/00_Olympic.cs index 2aaffc45998..d456bde53be 100644 --- a/src/Nethermind/Nethermind.Specs/Forks/00_Olympic.cs +++ b/src/Nethermind/Nethermind.Specs/Forks/00_Olympic.cs @@ -28,6 +28,7 @@ protected Olympic() ValidateReceipts = true; // The below addresses are added for all forks, but the given EIPs can be enabled at a specific timestamp or block. + Eip7251ContractAddress = Eip7251Constants.ConsolidationRequestPredeployAddress; Eip7002ContractAddress = Eip7002Constants.WithdrawalRequestPredeployAddress; DepositContractAddress = Eip6110Constants.MainnetDepositContractAddress; } diff --git a/src/Nethermind/Nethermind.Specs/Forks/18_Prague.cs b/src/Nethermind/Nethermind.Specs/Forks/18_Prague.cs index d88d482ea47..7fd7e8c348d 100644 --- a/src/Nethermind/Nethermind.Specs/Forks/18_Prague.cs +++ b/src/Nethermind/Nethermind.Specs/Forks/18_Prague.cs @@ -16,8 +16,10 @@ protected Prague() Name = "Prague"; IsEip2537Enabled = true; IsEip2935Enabled = true; + IsEip7702Enabled = true; IsEip6110Enabled = true; IsEip7002Enabled = true; + IsEip7251Enabled = true; IsRip7212Enabled = true; Eip2935ContractAddress = Eip2935Constants.BlockHashHistoryAddress; } diff --git a/src/Nethermind/Nethermind.Specs/ReleaseSpec.cs b/src/Nethermind/Nethermind.Specs/ReleaseSpec.cs index 3bda8e6eada..f138b747d3b 100644 --- a/src/Nethermind/Nethermind.Specs/ReleaseSpec.cs +++ b/src/Nethermind/Nethermind.Specs/ReleaseSpec.cs @@ -86,9 +86,17 @@ public bool IsEip1559Enabled public bool IsEip5656Enabled { get; set; } public bool IsEip6780Enabled { get; set; } public bool IsEip4788Enabled { get; set; } + public bool IsEip7702Enabled { get; set; } public bool IsEip4844FeeCollectorEnabled { get; set; } public bool IsEip7002Enabled { get; set; } + public bool IsEip7251Enabled { get; set; } + private Address _eip7251ContractAddress; + public Address Eip7251ContractAddress + { + get => IsEip7251Enabled ? _eip7251ContractAddress : null; + set => _eip7251ContractAddress = value; + } private Address _eip7002ContractAddress; public Address Eip7002ContractAddress { @@ -122,7 +130,5 @@ public Address Eip2935ContractAddress get => IsEip2935Enabled ? _eip2935ContractAddress : null; set => _eip2935ContractAddress = value; } - - } } diff --git a/src/Nethermind/Nethermind.State.Test/StateProviderTests.cs b/src/Nethermind/Nethermind.State.Test/StateProviderTests.cs index f1e5d5eaad1..e47aab57922 100644 --- a/src/Nethermind/Nethermind.State.Test/StateProviderTests.cs +++ b/src/Nethermind/Nethermind.State.Test/StateProviderTests.cs @@ -182,7 +182,7 @@ public void Restore_in_the_middle() provider.CreateAccount(_address1, 1); provider.AddToBalance(_address1, 1, Frontier.Instance); provider.IncrementNonce(_address1); - provider.InsertCode(_address1, new byte[] { 1 }, Frontier.Instance); + provider.InsertCode(_address1, new byte[] { 1 }, Frontier.Instance, false); provider.UpdateStorageRoot(_address1, Hash2); Assert.That(provider.GetNonce(_address1), Is.EqualTo(UInt256.One)); diff --git a/src/Nethermind/Nethermind.State.Test/StateReaderTests.cs b/src/Nethermind/Nethermind.State.Test/StateReaderTests.cs index 5e56dd22f44..bedccef85ce 100644 --- a/src/Nethermind/Nethermind.State.Test/StateReaderTests.cs +++ b/src/Nethermind/Nethermind.State.Test/StateReaderTests.cs @@ -248,5 +248,114 @@ public void Can_collect_stats() var stats = stateReader.CollectStats(provider.StateRoot, new MemDb(), Logger); stats.AccountCount.Should().Be(1); } + + [Test] + public void IsInvalidContractSender_AccountHasCode_ReturnsTrue() + { + IReleaseSpec releaseSpec = Substitute.For(); + releaseSpec.IsEip3607Enabled.Returns(true); + releaseSpec.IsEip7702Enabled.Returns(true); + TrieStore trieStore = new TrieStore(new MemDb(), Logger); + WorldState sut = new(trieStore, new MemDb(), Logger); + sut.CreateAccount(TestItem.AddressA, 0); + sut.InsertCode(TestItem.AddressA, ValueKeccak.Compute(new byte[1]), new byte[1], releaseSpec, false); + sut.Commit(MuirGlacier.Instance); + sut.CommitTree(0); + + bool result = sut.IsInvalidContractSender(releaseSpec, TestItem.AddressA); + + Assert.That(result, Is.True); + } + + [Test] + public void IsInvalidContractSender_AccountHasNoCode_ReturnsFalse() + { + IReleaseSpec releaseSpec = Substitute.For(); + releaseSpec.IsEip3607Enabled.Returns(true); + releaseSpec.IsEip7702Enabled.Returns(true); + TrieStore trieStore = new TrieStore(new MemDb(), Logger); + WorldState sut = new(trieStore, new MemDb(), Logger); + sut.CreateAccount(TestItem.AddressA, 0); + sut.Commit(MuirGlacier.Instance); + sut.CommitTree(0); + + bool result = sut.IsInvalidContractSender(releaseSpec, TestItem.AddressA); + + Assert.That(result, Is.False); + } + + [Test] + public void IsInvalidContractSender_AccountHasDelegatedCode_ReturnsFalse() + { + IReleaseSpec releaseSpec = Substitute.For(); + releaseSpec.IsEip3607Enabled.Returns(true); + releaseSpec.IsEip7702Enabled.Returns(true); + TrieStore trieStore = new TrieStore(new MemDb(), Logger); + WorldState sut = new(trieStore, new MemDb(), Logger); + sut.CreateAccount(TestItem.AddressA, 0); + byte[] code = [.. Eip7702Constants.DelegationHeader, .. new byte[20]]; + sut.InsertCode(TestItem.AddressA, ValueKeccak.Compute(code), code, releaseSpec, false); + sut.Commit(MuirGlacier.Instance); + sut.CommitTree(0); + + bool result = sut.IsInvalidContractSender(releaseSpec, TestItem.AddressA); + + Assert.That(result, Is.False); + } + + [Test] + public void IsInvalidContractSender_AccountHasCodeButDelegateReturnsTrue_ReturnsFalse() + { + IReleaseSpec releaseSpec = Substitute.For(); + releaseSpec.IsEip3607Enabled.Returns(true); + releaseSpec.IsEip7702Enabled.Returns(true); + TrieStore trieStore = new TrieStore(new MemDb(), Logger); + WorldState sut = new(trieStore, new MemDb(), Logger); + sut.CreateAccount(TestItem.AddressA, 0); + byte[] code = new byte[20]; + sut.InsertCode(TestItem.AddressA, ValueKeccak.Compute(code), code, releaseSpec, false); + sut.Commit(MuirGlacier.Instance); + sut.CommitTree(0); + + bool result = sut.IsInvalidContractSender(releaseSpec, TestItem.AddressA, () => true); + + Assert.That(result, Is.False); + } + + [Test] + public void IsInvalidContractSender_AccountHasDelegatedCodeBut7702IsNotEnabled_ReturnsTrue() + { + IReleaseSpec releaseSpec = Substitute.For(); + releaseSpec.IsEip3607Enabled.Returns(true); + TrieStore trieStore = new TrieStore(new MemDb(), Logger); + WorldState sut = new(trieStore, new MemDb(), Logger); + sut.CreateAccount(TestItem.AddressA, 0); + byte[] code = [.. Eip7702Constants.DelegationHeader, .. new byte[20]]; + sut.InsertCode(TestItem.AddressA, ValueKeccak.Compute(code), code, releaseSpec, false); + sut.Commit(MuirGlacier.Instance); + sut.CommitTree(0); + + bool result = sut.IsInvalidContractSender(releaseSpec, TestItem.AddressA); + + Assert.That(result, Is.True); + } + + [Test] + public void IsInvalidContractSender_AccountHasDelegatedCodeBut3807IsNotEnabled_ReturnsFalse() + { + IReleaseSpec releaseSpec = Substitute.For(); + releaseSpec.IsEip7702Enabled.Returns(true); + TrieStore trieStore = new TrieStore(new MemDb(), Logger); + WorldState sut = new(trieStore, new MemDb(), Logger); + sut.CreateAccount(TestItem.AddressA, 0); + byte[] code = [.. Eip7702Constants.DelegationHeader, .. new byte[20]]; + sut.InsertCode(TestItem.AddressA, ValueKeccak.Compute(code), code, releaseSpec, false); + sut.Commit(MuirGlacier.Instance); + sut.CommitTree(0); + + bool result = sut.IsInvalidContractSender(releaseSpec, TestItem.AddressA); + + Assert.That(result, Is.False); + } } } diff --git a/src/Nethermind/Nethermind.State/IReadOnlyStateProviderExtensions.cs b/src/Nethermind/Nethermind.State/IReadOnlyStateProviderExtensions.cs new file mode 100644 index 00000000000..2fdf091a047 --- /dev/null +++ b/src/Nethermind/Nethermind.State/IReadOnlyStateProviderExtensions.cs @@ -0,0 +1,37 @@ +// SPDX-FileCopyrightText: 2022 Demerzel Solutions Limited +// SPDX-License-Identifier: LGPL-3.0-only + +using Nethermind.Core; +using Nethermind.Core.Specs; +using System; + +namespace Nethermind.State +{ + public static class IReadOnlyStateProviderExtensions + { + public static byte[] GetCode(this IReadOnlyStateProvider stateProvider, Address address) + { + stateProvider.TryGetAccount(address, out AccountStruct account); + return !account.HasCode ? Array.Empty() : stateProvider.GetCode(account.CodeHash) ?? Array.Empty(); + } + /// + /// Checks if has code that is not a delegation, according to the rules of eip-3607 and eip-7702. + /// Where possible a cache for code lookup should be used, since the fallback will read from . + /// + /// + /// + /// + /// + /// + public static bool IsInvalidContractSender( + this IReadOnlyStateProvider stateProvider, + IReleaseSpec spec, + Address sender, + Func? isDelegatedCode = null) => + spec.IsEip3607Enabled + && stateProvider.HasCode(sender) + && (!spec.IsEip7702Enabled + || (!isDelegatedCode?.Invoke() ?? !Eip7702Constants.IsDelegatedCode(GetCode(stateProvider, sender)))); + } + +} diff --git a/src/Nethermind/Nethermind.State/IWorldState.cs b/src/Nethermind/Nethermind.State/IWorldState.cs index 21ac37a2a64..ac5bf705ab6 100644 --- a/src/Nethermind/Nethermind.State/IWorldState.cs +++ b/src/Nethermind/Nethermind.State/IWorldState.cs @@ -87,7 +87,7 @@ public interface IWorldState : IJournal, IReadOnlyStateProvider void CreateAccount(Address address, in UInt256 balance, in UInt256 nonce = default); void CreateAccountIfNotExists(Address address, in UInt256 balance, in UInt256 nonce = default); - void InsertCode(Address address, Hash256 codeHash, ReadOnlyMemory code, IReleaseSpec spec, bool isGenesis = false); + void InsertCode(Address address, in ValueHash256 codeHash, ReadOnlyMemory code, IReleaseSpec spec, bool isGenesis = false); void AddToBalance(Address address, in UInt256 balanceChange, IReleaseSpec spec); diff --git a/src/Nethermind/Nethermind.State/IWorldStateExtensions.cs b/src/Nethermind/Nethermind.State/IWorldStateExtensions.cs index f6ffa4250d1..2cdeadddb5e 100644 --- a/src/Nethermind/Nethermind.State/IWorldStateExtensions.cs +++ b/src/Nethermind/Nethermind.State/IWorldStateExtensions.cs @@ -13,15 +13,9 @@ namespace Nethermind.State { public static class WorldStateExtensions { - public static byte[] GetCode(this IWorldState stateProvider, Address address) - { - stateProvider.TryGetAccount(address, out AccountStruct account); - return !account.HasCode ? Array.Empty() : stateProvider.GetCode(account.CodeHash) ?? Array.Empty(); - } - public static void InsertCode(this IWorldState worldState, Address address, ReadOnlyMemory code, IReleaseSpec spec, bool isGenesis = false) { - Hash256 codeHash = code.Length == 0 ? Keccak.OfAnEmptyString : Keccak.Compute(code.Span); + ValueHash256 codeHash = code.Length == 0 ? ValueKeccak.OfAnEmptyString : ValueKeccak.Compute(code.Span); worldState.InsertCode(address, codeHash, code, spec, isGenesis); } diff --git a/src/Nethermind/Nethermind.State/Proofs/RequestsTrie.cs b/src/Nethermind/Nethermind.State/Proofs/RequestsTrie.cs index f33d484ef48..d69e1d0c8d7 100644 --- a/src/Nethermind/Nethermind.State/Proofs/RequestsTrie.cs +++ b/src/Nethermind/Nethermind.State/Proofs/RequestsTrie.cs @@ -14,7 +14,7 @@ namespace Nethermind.State.Proofs; public class RequestsTrie(ConsensusRequest[]? requests, bool canBuildProof = false, ICappedArrayPool? bufferPool = null) : PatriciaTrie(requests, canBuildProof, bufferPool) { - private static readonly ConsensusRequestDecoder _codec = new(); + private static readonly ConsensusRequestDecoder _codec = ConsensusRequestDecoder.Instance; protected override void Initialize(ConsensusRequest[] requests) { diff --git a/src/Nethermind/Nethermind.State/StateProvider.cs b/src/Nethermind/Nethermind.State/StateProvider.cs index 0afd1fdef0c..c4689da5d04 100644 --- a/src/Nethermind/Nethermind.State/StateProvider.cs +++ b/src/Nethermind/Nethermind.State/StateProvider.cs @@ -29,7 +29,7 @@ internal class StateProvider // Note: // False negatives are fine as they will just result in a overwrite set // False positives would be problematic as the code _must_ be persisted - private readonly ClockKeyCacheNonConcurrent _codeInsertFilter = new(1_024); + private readonly ClockKeyCacheNonConcurrent _codeInsertFilter = new(1_024); private readonly Dictionary _blockCache = new(4_096); private readonly ConcurrentDictionary? _preBlockCache; @@ -37,7 +37,7 @@ internal class StateProvider private readonly ILogger _logger; private readonly IKeyValueStore _codeDb; - private List _changes = new(Resettable.StartCapacity); + private readonly List _changes = new(Resettable.StartCapacity); internal readonly StateTree _tree; private readonly Func _getStateFromTrie; @@ -116,7 +116,7 @@ public UInt256 GetBalance(Address address) return account?.Balance ?? UInt256.Zero; } - public void InsertCode(Address address, Hash256 codeHash, ReadOnlyMemory code, IReleaseSpec spec, bool isGenesis = false) + public void InsertCode(Address address, in ValueHash256 codeHash, ReadOnlyMemory code, IReleaseSpec spec, bool isGenesis = false) { _needsStateRootUpdate = true; @@ -149,7 +149,7 @@ public void InsertCode(Address address, Hash256 codeHash, ReadOnlyMemory c throw new InvalidOperationException($"Account {address} is null when updating code hash"); } - if (account.CodeHash != codeHash) + if (account.CodeHash.ValueHash256 != codeHash) { if (_logger.IsDebug) _logger.Debug($" Update {address} C {account.CodeHash} -> {codeHash}"); Account changedAccount = account.WithChangedCodeHash(codeHash); diff --git a/src/Nethermind/Nethermind.State/WorldState.cs b/src/Nethermind/Nethermind.State/WorldState.cs index fa3753df79c..c18fb219381 100644 --- a/src/Nethermind/Nethermind.State/WorldState.cs +++ b/src/Nethermind/Nethermind.State/WorldState.cs @@ -143,7 +143,7 @@ public void CreateAccount(Address address, in UInt256 balance, in UInt256 nonce { _stateProvider.CreateAccount(address, balance, nonce); } - public void InsertCode(Address address, Hash256 codeHash, ReadOnlyMemory code, IReleaseSpec spec, bool isGenesis = false) + public void InsertCode(Address address, in ValueHash256 codeHash, ReadOnlyMemory code, IReleaseSpec spec, bool isGenesis = false) { _stateProvider.InsertCode(address, codeHash, code, spec, isGenesis); } diff --git a/src/Nethermind/Nethermind.Synchronization.Test/SyncThreadTests.cs b/src/Nethermind/Nethermind.Synchronization.Test/SyncThreadTests.cs index fb3f5927289..c86dfbb240b 100644 --- a/src/Nethermind/Nethermind.Synchronization.Test/SyncThreadTests.cs +++ b/src/Nethermind/Nethermind.Synchronization.Test/SyncThreadTests.cs @@ -269,15 +269,15 @@ private SyncTestContext CreateSyncManager(int index) ITransactionComparerProvider transactionComparerProvider = new TransactionComparerProvider(specProvider, tree); + CodeInfoRepository codeInfoRepository = new(); TxPool.TxPool txPool = new(ecdsa, new BlobTxStorage(), - new ChainHeadInfoProvider(specProvider, tree, stateReader), + new ChainHeadInfoProvider(specProvider, tree, stateReader, codeInfoRepository), new TxPoolConfig(), new TxValidator(specProvider.ChainId), logManager, transactionComparerProvider.GetDefaultComparer()); BlockhashProvider blockhashProvider = new(tree, specProvider, stateProvider, LimboLogs.Instance); - CodeInfoRepository codeInfoRepository = new(); VirtualMachine virtualMachine = new(blockhashProvider, specProvider, codeInfoRepository, logManager); Always sealValidator = Always.Valid; diff --git a/src/Nethermind/Nethermind.TxPool.Test/NonceManagerTests.cs b/src/Nethermind/Nethermind.TxPool.Test/NonceManagerTests.cs index 574d7b7e0c0..a78fcb377b2 100644 --- a/src/Nethermind/Nethermind.TxPool.Test/NonceManagerTests.cs +++ b/src/Nethermind/Nethermind.TxPool.Test/NonceManagerTests.cs @@ -11,6 +11,7 @@ using Nethermind.Core.Specs; using Nethermind.Core.Test.Builders; using Nethermind.Db; +using Nethermind.Evm; using Nethermind.Int256; using Nethermind.Logging; using Nethermind.Specs; @@ -42,8 +43,8 @@ public void Setup() _blockTree.Head.Returns(block); _blockTree.FindBestSuggestedHeader().Returns(Build.A.BlockHeader.WithNumber(10000000).TestObject); - _headInfo = new ChainHeadInfoProvider(_specProvider, _blockTree, _stateProvider); - _nonceManager = new NonceManager(_headInfo.AccountStateProvider); + _headInfo = new ChainHeadInfoProvider(_specProvider, _blockTree, _stateProvider, new CodeInfoRepository()); + _nonceManager = new NonceManager(_headInfo.ReadOnlyStateProvider); } [Test] diff --git a/src/Nethermind/Nethermind.TxPool.Test/TxPoolTests.cs b/src/Nethermind/Nethermind.TxPool.Test/TxPoolTests.cs index bdb1a26a5a6..4e7f10b33b2 100644 --- a/src/Nethermind/Nethermind.TxPool.Test/TxPoolTests.cs +++ b/src/Nethermind/Nethermind.TxPool.Test/TxPoolTests.cs @@ -1660,7 +1660,7 @@ public void Should_correctly_add_tx_to_local_pool_when_underpaid([Values] TxType // No need to check for deposit tx if (txType == TxType.DepositTx) return; - ISpecProvider specProvider = GetCancunSpecProvider(); + ISpecProvider specProvider = GetPragueSpecProvider(); TxPoolConfig txPoolConfig = new TxPoolConfig { Size = 30, PersistentBlobStorageSize = 0 }; _txPool = CreatePool(txPoolConfig, specProvider); @@ -1684,8 +1684,10 @@ public void Should_correctly_add_tx_to_local_pool_when_underpaid([Values] TxType .WithNonce(0) .WithType(txType) .WithShardBlobTxTypeAndFieldsIfBlobTx() + .WithAuthorizationCodeIfAuthorizationListTx() .WithMaxFeePerGas(9.GWei()) .WithMaxPriorityFeePerGas(9.GWei()) + .WithGasLimit(txType != TxType.SetCode ? GasCostOf.Transaction : GasCostOf.Transaction + GasCostOf.NewAccount) .WithTo(TestItem.AddressB) .SignedAndResolved(_ethereumEcdsa, TestItem.PrivateKeyA).TestObject; @@ -1698,6 +1700,35 @@ public void Should_correctly_add_tx_to_local_pool_when_underpaid([Values] TxType _txPool.GetPendingTransactions().Should().NotContain(testTx); } + static IEnumerable<(byte[], AcceptTxResult)> CodeCases() + { + yield return (new byte[16], AcceptTxResult.SenderIsContract); + //Delegation code + yield return ([.. Eip7702Constants.DelegationHeader, .. new byte[20]], AcceptTxResult.Accepted); + } + [TestCaseSource(nameof(CodeCases))] + public void SubmitTx_CodeIsNotDelegationAndDelegation_DelegationIsAccepted((byte[] code, AcceptTxResult expected) testCase) + { + ISpecProvider specProvider = GetPragueSpecProvider(); + TxPoolConfig txPoolConfig = new TxPoolConfig { Size = 30, PersistentBlobStorageSize = 0 }; + _txPool = CreatePool(txPoolConfig, specProvider); + + Transaction testTx = Build.A.Transaction + .WithNonce(0) + .WithMaxFeePerGas(9.GWei()) + .WithMaxPriorityFeePerGas(9.GWei()) + .WithGasLimit(100_000) + .WithTo(TestItem.AddressB) + .SignedAndResolved(_ethereumEcdsa, TestItem.PrivateKeyA).TestObject; + + EnsureSenderBalance(TestItem.PrivateKeyA.Address, UInt256.MaxValue); + + _stateProvider.InsertCode(TestItem.PrivateKeyA.Address, testCase.code, Prague.Instance); + + AcceptTxResult result = _txPool.SubmitTx(testTx, TxHandlingOptions.PersistentBroadcast); + result.Should().Be(testCase.expected); + } + private IDictionary GetPeers(int limit = 100) { var peers = new Dictionary(); @@ -1726,7 +1757,7 @@ private TxPool CreatePool( txStorage ??= new BlobTxStorage(); _headInfo = chainHeadInfoProvider; - _headInfo ??= new ChainHeadInfoProvider(specProvider, _blockTree, _stateProvider); + _headInfo ??= new ChainHeadInfoProvider(specProvider, _blockTree, _stateProvider, new CodeInfoRepository()); return new TxPool( _ethereumEcdsa, @@ -1763,6 +1794,13 @@ private static ISpecProvider GetCancunSpecProvider() return specProvider; } + private static ISpecProvider GetPragueSpecProvider() + { + var specProvider = Substitute.For(); + specProvider.GetSpec(Arg.Any()).Returns(Prague.Instance); + return specProvider; + } + private Transaction[] AddTransactionsToPool(bool sameTransactionSenderPerPeer = true, bool sameNoncePerPeer = false, int transactionsPerPeer = 10) { var transactions = GetTransactions(GetPeers(transactionsPerPeer), sameTransactionSenderPerPeer, sameNoncePerPeer); diff --git a/src/Nethermind/Nethermind.TxPool/Filters/DeployedCodeFilter.cs b/src/Nethermind/Nethermind.TxPool/Filters/DeployedCodeFilter.cs index 12d6e5a7035..1eb06f8dcc8 100644 --- a/src/Nethermind/Nethermind.TxPool/Filters/DeployedCodeFilter.cs +++ b/src/Nethermind/Nethermind.TxPool/Filters/DeployedCodeFilter.cs @@ -3,23 +3,21 @@ using Nethermind.Core; using Nethermind.Core.Specs; +using Nethermind.Evm; +using Nethermind.State; namespace Nethermind.TxPool.Filters { /// /// Filters out transactions that sender has any code deployed. If is enabled. /// - internal sealed class DeployedCodeFilter : IIncomingTxFilter + internal sealed class DeployedCodeFilter(IReadOnlyStateProvider worldState, ICodeInfoRepository codeInfoRepository, IChainHeadSpecProvider specProvider) : IIncomingTxFilter { - private readonly IChainHeadSpecProvider _specProvider; - - public DeployedCodeFilter(IChainHeadSpecProvider specProvider) - { - _specProvider = specProvider; - } public AcceptTxResult Accept(Transaction tx, ref TxFilteringState state, TxHandlingOptions txHandlingOptions) { - return _specProvider.GetCurrentHeadSpec().IsEip3607Enabled && state.SenderAccount.HasCode + return worldState.IsInvalidContractSender(specProvider.GetCurrentHeadSpec(), + tx.SenderAddress!, + () => codeInfoRepository.TryGetDelegation(worldState, tx.SenderAddress!, out _)) ? AcceptTxResult.SenderIsContract : AcceptTxResult.Accepted; } diff --git a/src/Nethermind/Nethermind.TxPool/Filters/IsValidTxSender.cs b/src/Nethermind/Nethermind.TxPool/Filters/IsValidTxSender.cs new file mode 100644 index 00000000000..f9077d6c966 --- /dev/null +++ b/src/Nethermind/Nethermind.TxPool/Filters/IsValidTxSender.cs @@ -0,0 +1,18 @@ +// SPDX-FileCopyrightText: 2024 Demerzel Solutions Limited +// SPDX-License-Identifier: LGPL-3.0-only + +using Nethermind.Core; +using Nethermind.Core.Specs; +using Nethermind.Evm; +using Nethermind.State; + +namespace Nethermind.TxPool.Filters; +public class IsValidTxSender(IWorldState worldState, ICodeInfoRepository codeInfoRepository, IChainHeadSpecProvider specProvider) +{ + public bool IsValid(Address sender) + { + return specProvider.GetCurrentHeadSpec().IsEip3607Enabled + && worldState.HasCode(sender) + && (!specProvider.GetCurrentHeadSpec().IsEip7702Enabled || !codeInfoRepository.TryGetDelegation(worldState, sender, out _)); + } +} diff --git a/src/Nethermind/Nethermind.TxPool/Filters/RecoverAuthorityFilter.cs b/src/Nethermind/Nethermind.TxPool/Filters/RecoverAuthorityFilter.cs new file mode 100644 index 00000000000..8075d9d14dd --- /dev/null +++ b/src/Nethermind/Nethermind.TxPool/Filters/RecoverAuthorityFilter.cs @@ -0,0 +1,28 @@ +// SPDX-FileCopyrightText: 2022 Demerzel Solutions Limited +// SPDX-License-Identifier: LGPL-3.0-only + +using System.Threading.Tasks; +using Nethermind.Core; +using Nethermind.Crypto; + +namespace Nethermind.TxPool.Filters +{ + /// + /// Will recover authority from transactions with authority_list + /// /// + internal sealed class RecoverAuthorityFilter(IEthereumEcdsa ecdsa) : IIncomingTxFilter + { + public AcceptTxResult Accept(Transaction tx, ref TxFilteringState state, TxHandlingOptions handlingOptions) + { + if (tx.HasAuthorizationList) + { + foreach (AuthorizationTuple tuple in tx.AuthorizationList) + { + tuple.Authority ??= ecdsa.RecoverAddress(tuple); + } + } + + return AcceptTxResult.Accepted; + } + } +} diff --git a/src/Nethermind/Nethermind.TxPool/IChainHeadInfoProvider.cs b/src/Nethermind/Nethermind.TxPool/IChainHeadInfoProvider.cs index 2387ac91427..ca89bd82aec 100644 --- a/src/Nethermind/Nethermind.TxPool/IChainHeadInfoProvider.cs +++ b/src/Nethermind/Nethermind.TxPool/IChainHeadInfoProvider.cs @@ -4,7 +4,9 @@ using System; using Nethermind.Core; using Nethermind.Core.Specs; +using Nethermind.Evm; using Nethermind.Int256; +using Nethermind.State; namespace Nethermind.TxPool { @@ -12,13 +14,15 @@ public interface IChainHeadInfoProvider { IChainHeadSpecProvider SpecProvider { get; } - IAccountStateProvider AccountStateProvider { get; } + IReadOnlyStateProvider ReadOnlyStateProvider { get; } - public long HeadNumber { get; } + ICodeInfoRepository CodeInfoRepository { get; } - public long? BlockGasLimit { get; } + long HeadNumber { get; } - public UInt256 CurrentBaseFee { get; } + long? BlockGasLimit { get; } + + UInt256 CurrentBaseFee { get; } public UInt256 CurrentFeePerBlobGas { get; } diff --git a/src/Nethermind/Nethermind.TxPool/Metrics.cs b/src/Nethermind/Nethermind.TxPool/Metrics.cs index 53721d957fe..d825ad8060b 100644 --- a/src/Nethermind/Nethermind.TxPool/Metrics.cs +++ b/src/Nethermind/Nethermind.TxPool/Metrics.cs @@ -122,6 +122,10 @@ public static class Metrics [Description("Ratio of 1559-type transactions in the block.")] public static float Eip1559TransactionsRatio { get; set; } + [GaugeMetric] + [Description("Ratio of 7702-type transactions in the block.")] + public static long Eip7702TransactionsInBlock { get; set; } + [GaugeMetric] [Description("Number of blob transactions in the block.")] public static long BlobTransactionsInBlock { get; set; } diff --git a/src/Nethermind/Nethermind.TxPool/Nethermind.TxPool.csproj b/src/Nethermind/Nethermind.TxPool/Nethermind.TxPool.csproj index 2f6f825141f..7c667ed34e8 100644 --- a/src/Nethermind/Nethermind.TxPool/Nethermind.TxPool.csproj +++ b/src/Nethermind/Nethermind.TxPool/Nethermind.TxPool.csproj @@ -13,6 +13,8 @@ + + diff --git a/src/Nethermind/Nethermind.TxPool/TxPool.cs b/src/Nethermind/Nethermind.TxPool/TxPool.cs index 956e7a55397..aea6460a8ff 100644 --- a/src/Nethermind/Nethermind.TxPool/TxPool.cs +++ b/src/Nethermind/Nethermind.TxPool/TxPool.cs @@ -103,7 +103,7 @@ public TxPool(IEthereumEcdsa ecdsa, _headInfo = chainHeadInfoProvider ?? throw new ArgumentNullException(nameof(chainHeadInfoProvider)); _txPoolConfig = txPoolConfig; _blobReorgsSupportEnabled = txPoolConfig.BlobsSupport.SupportsReorgs(); - _accounts = _accountCache = new AccountCache(_headInfo.AccountStateProvider); + _accounts = _accountCache = new AccountCache(_headInfo.ReadOnlyStateProvider); _specProvider = _headInfo.SpecProvider; MemoryAllowance.MemPoolSize = txPoolConfig.Size; @@ -124,34 +124,36 @@ public TxPool(IEthereumEcdsa ecdsa, _headInfo.HeadChanged += OnHeadChange; - _preHashFilters = new IIncomingTxFilter[] - { + _preHashFilters = + [ new NotSupportedTxFilter(txPoolConfig, _logger), new GasLimitTxFilter(_headInfo, txPoolConfig, _logger), new PriorityFeeTooLowFilter(_logger), new FeeTooLowFilter(_headInfo, _transactions, _blobTransactions, thereIsPriorityContract, _logger), new MalformedTxFilter(_specProvider, validator, _logger) - }; + ]; - List postHashFilters = new() - { + List postHashFilters = + [ new NullHashTxFilter(), // needs to be first as it assigns the hash new AlreadyKnownTxFilter(_hashCache, _logger), new UnknownSenderFilter(ecdsa, _logger), - new TxTypeTxFilter(_transactions, _blobTransactions), // has to be after UnknownSenderFilter as it uses sender + new TxTypeTxFilter(_transactions, + _blobTransactions), // has to be after UnknownSenderFilter as it uses sender new BalanceZeroFilter(thereIsPriorityContract, _logger), new BalanceTooLowFilter(_transactions, _blobTransactions, _logger), new LowNonceFilter(_logger), // has to be after UnknownSenderFilter as it uses sender new FutureNonceFilter(txPoolConfig), new GapNonceFilter(_transactions, _blobTransactions, _logger), - }; + new RecoverAuthorityFilter(ecdsa) + ]; if (incomingTxFilter is not null) { postHashFilters.Add(incomingTxFilter); } - postHashFilters.Add(new DeployedCodeFilter(_specProvider)); + postHashFilters.Add(new DeployedCodeFilter(chainHeadInfoProvider.ReadOnlyStateProvider, chainHeadInfoProvider.CodeInfoRepository, _specProvider)); _postHashFilters = postHashFilters.ToArray(); @@ -304,6 +306,7 @@ private void RemoveProcessedTransactions(Block block) long discoveredForPendingTxs = 0; long discoveredForHashCache = 0; long eip1559Txs = 0; + long eip7702Txs = 0; long blobTxs = 0; long blobs = 0; @@ -333,6 +336,11 @@ private void RemoveProcessedTransactions(Block block) } } + if (blockTx.Type == TxType.SetCode) + { + eip7702Txs++; + } + if (!IsKnown(txHash)) { discoveredForHashCache++; @@ -355,6 +363,7 @@ private void RemoveProcessedTransactions(Block block) Metrics.DarkPoolRatioLevel1 = (float)discoveredForHashCache / transactionsInBlock; Metrics.DarkPoolRatioLevel2 = (float)discoveredForPendingTxs / transactionsInBlock; Metrics.Eip1559TransactionsRatio = (float)eip1559Txs / transactionsInBlock; + Metrics.Eip7702TransactionsInBlock = eip7702Txs; Metrics.BlobTransactionsInBlock = blobTxs; Metrics.BlobsInBlock = blobs; } @@ -905,6 +914,7 @@ private static void WriteTxPoolReport(in ILogger logger) ------------------------------------------------ Ratios in last block: * Eip1559 Transactions: {Metrics.Eip1559TransactionsRatio,24:P5} +* Eip7702 Transactions: {Metrics.Eip7702TransactionsInBlock,24:P5} * DarkPool Level1: {Metrics.DarkPoolRatioLevel1,24:P5} * DarkPool Level2: {Metrics.DarkPoolRatioLevel2,24:P5} Amounts: