diff --git a/src/Nethermind/Ethereum.Test.Base/BlockchainTestBase.cs b/src/Nethermind/Ethereum.Test.Base/BlockchainTestBase.cs index 2ada2bad74b..e6141c97996 100644 --- a/src/Nethermind/Ethereum.Test.Base/BlockchainTestBase.cs +++ b/src/Nethermind/Ethereum.Test.Base/BlockchainTestBase.cs @@ -148,9 +148,11 @@ protected async Task RunTest(BlockchainTest test, Stopwatch? IHeaderValidator headerValidator = new HeaderValidator(blockTree, Sealer, specProvider, _logManager); IUnclesValidator unclesValidator = new UnclesValidator(blockTree, headerValidator, _logManager); IBlockValidator blockValidator = new BlockValidator(txValidator, headerValidator, unclesValidator, specProvider, _logManager); + CodeInfoRepository codeInfoRepository = new(); IVirtualMachine virtualMachine = new VirtualMachine( blockhashProvider, specProvider, + codeInfoRepository, _logManager); IBlockProcessor blockProcessor = new BlockProcessor( @@ -162,6 +164,7 @@ protected async Task RunTest(BlockchainTest test, Stopwatch? specProvider, stateProvider, virtualMachine, + codeInfoRepository, _logManager), stateProvider), stateProvider, diff --git a/src/Nethermind/Ethereum.Test.Base/GeneralTestBase.cs b/src/Nethermind/Ethereum.Test.Base/GeneralTestBase.cs index 651361e07fa..f3fabfa27da 100644 --- a/src/Nethermind/Ethereum.Test.Base/GeneralTestBase.cs +++ b/src/Nethermind/Ethereum.Test.Base/GeneralTestBase.cs @@ -71,15 +71,18 @@ protected EthereumTestResult RunTest(GeneralStateTest test, ITxTracer txTracer) TrieStore trieStore = new(stateDb, _logManager); WorldState stateProvider = new(trieStore, codeDb, _logManager); IBlockhashProvider blockhashProvider = new TestBlockhashProvider(); + CodeInfoRepository codeInfoRepository = new(); IVirtualMachine virtualMachine = new VirtualMachine( blockhashProvider, specProvider, + codeInfoRepository, _logManager); TransactionProcessor transactionProcessor = new( specProvider, stateProvider, virtualMachine, + codeInfoRepository, _logManager); InitializeTestState(test, stateProvider, specProvider); diff --git a/src/Nethermind/Nethermind.AccountAbstraction.Test/AccountAbstractionRpcModuleTests.cs b/src/Nethermind/Nethermind.AccountAbstraction.Test/AccountAbstractionRpcModuleTests.cs index a08ba710e10..6862acc8a0b 100644 --- a/src/Nethermind/Nethermind.AccountAbstraction.Test/AccountAbstractionRpcModuleTests.cs +++ b/src/Nethermind/Nethermind.AccountAbstraction.Test/AccountAbstractionRpcModuleTests.cs @@ -18,6 +18,7 @@ using Nethermind.Core.Extensions; using Nethermind.Core.Test.Builders; using Nethermind.Crypto; +using Nethermind.Facade.Eth; using Nethermind.Int256; using Nethermind.JsonRpc.Data; using Nethermind.JsonRpc.Test.Modules; diff --git a/src/Nethermind/Nethermind.AccountAbstraction/AccountAbstractionPlugin.cs b/src/Nethermind/Nethermind.AccountAbstraction/AccountAbstractionPlugin.cs index 972f654dd15..327b7715335 100644 --- a/src/Nethermind/Nethermind.AccountAbstraction/AccountAbstractionPlugin.cs +++ b/src/Nethermind/Nethermind.AccountAbstraction/AccountAbstractionPlugin.cs @@ -123,7 +123,7 @@ private UserOperationSimulator UserOperationSimulator(Address entryPoint) ReadOnlyTxProcessingEnvFactory readOnlyTxProcessingEnvFactory = new( getFromApi.WorldStateManager!, - getFromApi.BlockTree, + getFromApi.BlockTree!, getFromApi.SpecProvider, getFromApi.LogManager); diff --git a/src/Nethermind/Nethermind.AccountAbstraction/Executor/IUserOperationSimulator.cs b/src/Nethermind/Nethermind.AccountAbstraction/Executor/IUserOperationSimulator.cs index 6781e276de8..d4af7269707 100644 --- a/src/Nethermind/Nethermind.AccountAbstraction/Executor/IUserOperationSimulator.cs +++ b/src/Nethermind/Nethermind.AccountAbstraction/Executor/IUserOperationSimulator.cs @@ -18,7 +18,7 @@ ResultWrapper Simulate(UserOperation userOperation, UInt256? timestamp = null, CancellationToken cancellationToken = default); - BlockchainBridge.CallOutput EstimateGas(BlockHeader header, Transaction tx, + CallOutput EstimateGas(BlockHeader header, Transaction tx, CancellationToken cancellationToken); } } diff --git a/src/Nethermind/Nethermind.AccountAbstraction/Executor/UserOperationSimulator.cs b/src/Nethermind/Nethermind.AccountAbstraction/Executor/UserOperationSimulator.cs index e2bd4f8a0b4..adc912ddf30 100644 --- a/src/Nethermind/Nethermind.AccountAbstraction/Executor/UserOperationSimulator.cs +++ b/src/Nethermind/Nethermind.AccountAbstraction/Executor/UserOperationSimulator.cs @@ -185,7 +185,7 @@ private Transaction BuildSimulateValidationTransaction( } [Todo("Refactor once BlockchainBridge is separated")] - public BlockchainBridge.CallOutput EstimateGas(BlockHeader header, Transaction tx, CancellationToken cancellationToken) + public CallOutput EstimateGas(BlockHeader header, Transaction tx, CancellationToken cancellationToken) { ReadOnlyTxProcessingEnv txProcessingEnv = _readOnlyTxProcessingEnvFactory.Create(); using IReadOnlyTransactionProcessor transactionProcessor = txProcessingEnv.Build(header.StateRoot!); @@ -202,7 +202,7 @@ public BlockchainBridge.CallOutput EstimateGas(BlockHeader header, Transaction t GasEstimator gasEstimator = new(transactionProcessor, _stateProvider, _specProvider, _blocksConfig); long estimate = gasEstimator.Estimate(tx, header, estimateGasTracer, GasEstimator.DefaultErrorMargin, cancellationToken); - return new BlockchainBridge.CallOutput + return new CallOutput { Error = tryCallResult.Success ? estimateGasTracer.Error : tryCallResult.Error, GasSpent = estimate, diff --git a/src/Nethermind/Nethermind.AccountAbstraction/Source/UserOperationTxSource.cs b/src/Nethermind/Nethermind.AccountAbstraction/Source/UserOperationTxSource.cs index ec6def2cd1e..4dac4b3fe47 100644 --- a/src/Nethermind/Nethermind.AccountAbstraction/Source/UserOperationTxSource.cs +++ b/src/Nethermind/Nethermind.AccountAbstraction/Source/UserOperationTxSource.cs @@ -172,7 +172,7 @@ public IEnumerable GetTransactions(BlockHeader parent, long gasLimi // TODO: Remove logging, just for testing _logger.Info($"Constructed tx from {userOperationsToInclude!.Count} userOperations: {userOperationTransaction.Hash}"); - BlockchainBridge.CallOutput callOutput = _userOperationSimulators[entryPoint].EstimateGas(parent, userOperationTransaction, CancellationToken.None); + CallOutput callOutput = _userOperationSimulators[entryPoint].EstimateGas(parent, userOperationTransaction, CancellationToken.None); FailedOp? failedOp = txBuilder.DecodeEntryPointOutputError(callOutput.OutputData); if (failedOp is not null) { diff --git a/src/Nethermind/Nethermind.Api/NethermindApi.cs b/src/Nethermind/Nethermind.Api/NethermindApi.cs index 7d79bc29a31..e07aa13ba9e 100644 --- a/src/Nethermind/Nethermind.Api/NethermindApi.cs +++ b/src/Nethermind/Nethermind.Api/NethermindApi.cs @@ -31,6 +31,7 @@ using Nethermind.Evm.TransactionProcessing; using Nethermind.Facade; using Nethermind.Facade.Eth; +using Nethermind.Facade.Simulate; using Nethermind.Grpc; using Nethermind.JsonRpc; using Nethermind.JsonRpc.Modules; @@ -81,11 +82,20 @@ public IBlockchainBridge CreateBlockchainBridge() SpecProvider, LogManager); + SimulateReadOnlyBlocksProcessingEnvFactory simulateReadOnlyBlocksProcessingEnvFactory = + new SimulateReadOnlyBlocksProcessingEnvFactory( + WorldStateManager!, + readOnlyTree, + DbProvider!, + SpecProvider!, + LogManager); + IMiningConfig miningConfig = ConfigProvider.GetConfig(); IBlocksConfig blocksConfig = ConfigProvider.GetConfig(); return new BlockchainBridge( readOnlyTxProcessingEnv, + simulateReadOnlyBlocksProcessingEnvFactory, TxPool, ReceiptFinder, FilterStore, diff --git a/src/Nethermind/Nethermind.Blockchain.Test/Producers/DevBlockproducerTests.cs b/src/Nethermind/Nethermind.Blockchain.Test/Producers/DevBlockproducerTests.cs index 32773975400..ebe237921e7 100644 --- a/src/Nethermind/Nethermind.Blockchain.Test/Producers/DevBlockproducerTests.cs +++ b/src/Nethermind/Nethermind.Blockchain.Test/Producers/DevBlockproducerTests.cs @@ -58,14 +58,17 @@ public void Test() LimboLogs.Instance); StateReader stateReader = new(trieStore, dbProvider.GetDb(DbNames.State), LimboLogs.Instance); BlockhashProvider blockhashProvider = new(blockTree, specProvider, stateProvider, LimboLogs.Instance); + CodeInfoRepository codeInfoRepository = new(); VirtualMachine virtualMachine = new( blockhashProvider, specProvider, + codeInfoRepository, LimboLogs.Instance); TransactionProcessor txProcessor = new( specProvider, stateProvider, virtualMachine, + codeInfoRepository, LimboLogs.Instance); BlockProcessor blockProcessor = new( specProvider, diff --git a/src/Nethermind/Nethermind.Blockchain.Test/ReorgTests.cs b/src/Nethermind/Nethermind.Blockchain.Test/ReorgTests.cs index c5ede7d957b..4303d621c45 100644 --- a/src/Nethermind/Nethermind.Blockchain.Test/ReorgTests.cs +++ b/src/Nethermind/Nethermind.Blockchain.Test/ReorgTests.cs @@ -57,14 +57,17 @@ public void Setup() LimboLogs.Instance, transactionComparerProvider.GetDefaultComparer()); BlockhashProvider blockhashProvider = new(_blockTree, specProvider, stateProvider, LimboLogs.Instance); + CodeInfoRepository codeInfoRepository = new(); VirtualMachine virtualMachine = new( blockhashProvider, specProvider, + codeInfoRepository, LimboLogs.Instance); TransactionProcessor transactionProcessor = new( specProvider, stateProvider, virtualMachine, + codeInfoRepository, LimboLogs.Instance); BlockProcessor blockProcessor = new( diff --git a/src/Nethermind/Nethermind.Blockchain.Test/TransactionSelectorTests.cs b/src/Nethermind/Nethermind.Blockchain.Test/TransactionSelectorTests.cs index bb730fa4b20..6b833e60048 100644 --- a/src/Nethermind/Nethermind.Blockchain.Test/TransactionSelectorTests.cs +++ b/src/Nethermind/Nethermind.Blockchain.Test/TransactionSelectorTests.cs @@ -195,7 +195,7 @@ public void Proper_transactions_selected(ProperTransactionsSelectedTestCase test MemDb stateDb = new(); MemDb codeDb = new(); TrieStore trieStore = new(stateDb, LimboLogs.Instance); - WorldState stateProvider = new(trieStore, codeDb, LimboLogs.Instance); + IWorldState stateProvider = new WorldState(trieStore, codeDb, LimboLogs.Instance); StateReader _ = new(new TrieStore(stateDb, LimboLogs.Instance), codeDb, LimboLogs.Instance); ISpecProvider specProvider = Substitute.For(); diff --git a/src/Nethermind/Nethermind.Blockchain.Test/TransactionsExecutorTests.cs b/src/Nethermind/Nethermind.Blockchain.Test/TransactionsExecutorTests.cs index f43d92ead95..8799490191a 100644 --- a/src/Nethermind/Nethermind.Blockchain.Test/TransactionsExecutorTests.cs +++ b/src/Nethermind/Nethermind.Blockchain.Test/TransactionsExecutorTests.cs @@ -262,7 +262,7 @@ public void Proper_transactions_selected(TransactionSelectorTests.ProperTransact MemDb stateDb = new(); MemDb codeDb = new(); TrieStore trieStore = new(stateDb, LimboLogs.Instance); - WorldState stateProvider = new(trieStore, codeDb, LimboLogs.Instance); + IWorldState stateProvider = new WorldState(trieStore, codeDb, LimboLogs.Instance); ISpecProvider specProvider = Substitute.For(); IReleaseSpec spec = testCase.ReleaseSpec; diff --git a/src/Nethermind/Nethermind.Blockchain/BlockTree.cs b/src/Nethermind/Nethermind.Blockchain/BlockTree.cs index 470a8d62d4b..c7338fa2aec 100644 --- a/src/Nethermind/Nethermind.Blockchain/BlockTree.cs +++ b/src/Nethermind/Nethermind.Blockchain/BlockTree.cs @@ -801,22 +801,17 @@ private void DeleteBlocks(Hash256 deletePointer) return childHash; } - public bool IsMainChain(BlockHeader blockHeader) - { - ChainLevelInfo? chainLevelInfo = LoadLevel(blockHeader.Number); - bool isMain = chainLevelInfo is not null && chainLevelInfo.MainChainBlock?.BlockHash.Equals(blockHeader.Hash) == true; - return isMain; - } + public bool IsMainChain(BlockHeader blockHeader) => + LoadLevel(blockHeader.Number)?.MainChainBlock?.BlockHash.Equals(blockHeader.Hash) == true; - public bool IsMainChain(Hash256 blockHash) + public bool IsMainChain(Hash256 blockHash, bool throwOnMissingHash = true) { BlockHeader? header = FindHeader(blockHash, BlockTreeLookupOptions.TotalDifficultyNotNeeded); - if (header is null) - { - throw new InvalidOperationException($"Not able to retrieve block number for an unknown block {blockHash}"); - } - - return IsMainChain(header); + return header is not null + ? IsMainChain(header) + : throwOnMissingHash + ? throw new InvalidOperationException($"Not able to retrieve block number for an unknown block {blockHash}") + : false; } public BlockHeader? FindBestSuggestedHeader() => BestSuggestedHeader; diff --git a/src/Nethermind/Nethermind.Blockchain/BlockTreeOverlay.cs b/src/Nethermind/Nethermind.Blockchain/BlockTreeOverlay.cs new file mode 100644 index 00000000000..c16834f63a8 --- /dev/null +++ b/src/Nethermind/Nethermind.Blockchain/BlockTreeOverlay.cs @@ -0,0 +1,251 @@ +// SPDX-FileCopyrightText: 2024 Demerzel Solutions Limited +// SPDX-License-Identifier: LGPL-3.0-only + +using System; +using System.Collections.Generic; +using System.Threading; +using System.Threading.Tasks; +using Nethermind.Blockchain.Visitors; +using Nethermind.Core; +using Nethermind.Core.Collections; +using Nethermind.Core.Crypto; + +namespace Nethermind.Blockchain; + +public class BlockTreeOverlay : IBlockTree +{ + private readonly IBlockTree _baseTree; + private readonly IBlockTree _overlayTree; + + public BlockTreeOverlay(IReadOnlyBlockTree baseTree, IBlockTree overlayTree) + { + _baseTree = baseTree ?? throw new ArgumentNullException(nameof(baseTree)); + _overlayTree = overlayTree ?? throw new ArgumentNullException(nameof(overlayTree)); + _overlayTree.UpdateMainChain(new[] { _baseTree.Head }, true, true); + } + + public ulong NetworkId => _baseTree.NetworkId; + public ulong ChainId => _baseTree.ChainId; + public BlockHeader? Genesis => _baseTree.Genesis; + public BlockHeader? BestSuggestedHeader => _overlayTree.BestSuggestedHeader ?? _baseTree.BestSuggestedHeader; + public Block? BestSuggestedBody => _overlayTree.BestSuggestedBody ?? _baseTree.BestSuggestedBody; + public BlockHeader? BestSuggestedBeaconHeader => _overlayTree.BestSuggestedBeaconHeader ?? _baseTree.BestSuggestedBeaconHeader; + public BlockHeader? LowestInsertedHeader => _overlayTree.LowestInsertedHeader ?? _baseTree.LowestInsertedHeader; + + public long? LowestInsertedBodyNumber + { + get => _overlayTree.LowestInsertedBodyNumber ?? _baseTree.LowestInsertedBodyNumber; + set => _overlayTree.LowestInsertedBodyNumber = value; + } + + public BlockHeader? LowestInsertedBeaconHeader + { + get => _overlayTree.LowestInsertedBeaconHeader ?? _baseTree.LowestInsertedBeaconHeader; + set => _overlayTree.LowestInsertedBeaconHeader = value; + } + + public long BestKnownNumber => Math.Max(_overlayTree.BestKnownNumber, _baseTree.BestKnownNumber); + public long BestKnownBeaconNumber => Math.Max(_overlayTree.BestKnownBeaconNumber, _baseTree.BestKnownBeaconNumber); + public Hash256 HeadHash => _overlayTree.HeadHash ?? _baseTree.HeadHash; + public Hash256 GenesisHash => _baseTree.GenesisHash; + public Hash256? PendingHash => _overlayTree.PendingHash ?? _baseTree.PendingHash; + public Hash256? FinalizedHash => _overlayTree.FinalizedHash ?? _baseTree.FinalizedHash; + public Hash256? SafeHash => _overlayTree.SafeHash ?? _baseTree.SafeHash; + public Block? Head => _overlayTree.Head ?? _baseTree.Head; + public long? BestPersistedState { get => _overlayTree.BestPersistedState; set => _overlayTree.BestPersistedState = value; } + + public AddBlockResult Insert(BlockHeader header, BlockTreeInsertHeaderOptions headerOptions = BlockTreeInsertHeaderOptions.None) => + _overlayTree.Insert(header, headerOptions); + + public AddBlockResult Insert(Block block, + BlockTreeInsertBlockOptions insertBlockOptions = BlockTreeInsertBlockOptions.None, + BlockTreeInsertHeaderOptions insertHeaderOptions = BlockTreeInsertHeaderOptions.None, + WriteFlags bodiesWriteFlags = WriteFlags.None) => + _overlayTree.Insert(block, insertBlockOptions, insertHeaderOptions, bodiesWriteFlags); + + public void UpdateHeadBlock(Hash256 blockHash) => + _overlayTree.UpdateHeadBlock(blockHash); + + public AddBlockResult SuggestBlock(Block block, + BlockTreeSuggestOptions options = BlockTreeSuggestOptions.ShouldProcess) => + _overlayTree.SuggestBlock(block, options); + + public ValueTask SuggestBlockAsync(Block block, + BlockTreeSuggestOptions options = BlockTreeSuggestOptions.ShouldProcess) => + _overlayTree.SuggestBlockAsync(block, options); + + public AddBlockResult SuggestHeader(BlockHeader header) => _overlayTree.SuggestHeader(header); + + public bool IsKnownBlock(long number, Hash256 blockHash) => _overlayTree.IsKnownBlock(number, blockHash) || _baseTree.IsKnownBlock(number, blockHash); + + public bool IsKnownBeaconBlock(long number, Hash256 blockHash) => _overlayTree.IsKnownBeaconBlock(number, blockHash) || _baseTree.IsKnownBeaconBlock(number, blockHash); + + public bool WasProcessed(long number, Hash256 blockHash) => _overlayTree.WasProcessed(number, blockHash) || _baseTree.WasProcessed(number, blockHash); + + public void UpdateMainChain(IReadOnlyList blocks, bool wereProcessed, bool forceHeadBlock = false) => + _overlayTree.UpdateMainChain(blocks, wereProcessed, forceHeadBlock); + + public void MarkChainAsProcessed(IReadOnlyList blocks) => _overlayTree.MarkChainAsProcessed(blocks); + + public bool CanAcceptNewBlocks => _overlayTree.CanAcceptNewBlocks; + + public Task Accept(IBlockTreeVisitor blockTreeVisitor, CancellationToken cancellationToken) => _overlayTree.Accept(blockTreeVisitor, cancellationToken); + + public (BlockInfo? Info, ChainLevelInfo? Level) GetInfo(long number, Hash256 blockHash) + { + (BlockInfo Info, ChainLevelInfo Level) overlayInfo = _overlayTree.GetInfo(number, blockHash); + return overlayInfo.Info is not null || overlayInfo.Level is not null ? overlayInfo : _baseTree.GetInfo(number, blockHash); + } + + public ChainLevelInfo? FindLevel(long number) => _overlayTree.FindLevel(number) ?? _baseTree.FindLevel(number); + + public BlockInfo FindCanonicalBlockInfo(long blockNumber) => _overlayTree.FindCanonicalBlockInfo(blockNumber) ?? _baseTree.FindCanonicalBlockInfo(blockNumber); + + public Hash256 FindHash(long blockNumber) => _overlayTree.FindHash(blockNumber) ?? _baseTree.FindHash(blockNumber); + + public IOwnedReadOnlyList FindHeaders(Hash256 hash, int numberOfBlocks, int skip, bool reverse) + { + IOwnedReadOnlyList overlayHeaders = _overlayTree.FindHeaders(hash, numberOfBlocks, skip, reverse); + return overlayHeaders.Count > 0 ? overlayHeaders : _baseTree.FindHeaders(hash, numberOfBlocks, skip, reverse); + } + + public void DeleteInvalidBlock(Block invalidBlock) => + _overlayTree.DeleteInvalidBlock(invalidBlock); + + public void ForkChoiceUpdated(Hash256? finalizedBlockHash, Hash256? safeBlockBlockHash) => + _overlayTree.ForkChoiceUpdated(finalizedBlockHash, safeBlockBlockHash); + + public event EventHandler? NewBestSuggestedBlock + { + add + { + if (value is not null) + { + _baseTree.NewBestSuggestedBlock += value; + _overlayTree.NewBestSuggestedBlock += value; + } + } + remove + { + if (value is not null) + { + _baseTree.NewBestSuggestedBlock -= value; + _overlayTree.NewBestSuggestedBlock -= value; + } + } + } + + public event EventHandler? NewSuggestedBlock + { + add + { + if (value is not null) + { + _baseTree.NewSuggestedBlock += value; + _overlayTree.NewSuggestedBlock += value; + } + } + remove + { + if (value is not null) + { + _baseTree.NewSuggestedBlock -= value; + _overlayTree.NewSuggestedBlock -= value; + } + } + } + + public event EventHandler? BlockAddedToMain + { + add + { + if (value is not null) + { + _baseTree.BlockAddedToMain += value; + _overlayTree.BlockAddedToMain += value; + } + } + remove + { + if (value is not null) + { + _baseTree.BlockAddedToMain -= value; + _overlayTree.BlockAddedToMain -= value; + } + } + } + + public event EventHandler? NewHeadBlock + { + add + { + if (value is not null) + { + _baseTree.NewHeadBlock += value; + _overlayTree.NewHeadBlock += value; + } + } + remove + { + if (value is not null) + { + _baseTree.NewHeadBlock -= value; + _overlayTree.NewHeadBlock -= value; + } + } + } + + public event EventHandler? OnUpdateMainChain + { + add + { + if (value is not null) + { + _baseTree.OnUpdateMainChain += value; + _overlayTree.OnUpdateMainChain += value; + } + } + remove + { + if (value is not null) + { + _baseTree.OnUpdateMainChain -= value; + _overlayTree.OnUpdateMainChain -= value; + } + } + } + + public int DeleteChainSlice(in long startNumber, long? endNumber = null, bool force = false) => + _overlayTree.DeleteChainSlice(startNumber, endNumber, force); + + public bool IsBetterThanHead(BlockHeader? header) => _overlayTree.IsBetterThanHead(header) || _baseTree.IsBetterThanHead(header); + + public void UpdateBeaconMainChain(BlockInfo[]? blockInfos, long clearBeaconMainChainStartPoint) => + _overlayTree.UpdateBeaconMainChain(blockInfos, clearBeaconMainChainStartPoint); + + public void RecalculateTreeLevels() => _overlayTree.RecalculateTreeLevels(); + + public Block? FindBlock(Hash256 blockHash, BlockTreeLookupOptions options, long? blockNumber = null) => + _overlayTree.FindBlock(blockHash, options, blockNumber) ?? _baseTree.FindBlock(blockHash, options, blockNumber); + + public Block? FindBlock(long blockNumber, BlockTreeLookupOptions options) => + _overlayTree.FindBlock(blockNumber, options) ?? _baseTree.FindBlock(blockNumber, options); + + public BlockHeader? FindHeader(Hash256 blockHash, BlockTreeLookupOptions options, long? blockNumber = null) => + _overlayTree.FindHeader(blockHash, options, blockNumber) ?? _baseTree.FindHeader(blockHash, options, blockNumber); + + public BlockHeader? FindHeader(long blockNumber, BlockTreeLookupOptions options) => + _overlayTree.FindHeader(blockNumber, options) ?? _baseTree.FindHeader(blockNumber, options); + + public Hash256? FindBlockHash(long blockNumber) => + _overlayTree.FindBlockHash(blockNumber) ?? _baseTree.FindBlockHash(blockNumber); + + public bool IsMainChain(BlockHeader blockHeader) => + _baseTree.IsMainChain(blockHeader) || _overlayTree.IsMainChain(blockHeader); + + public bool IsMainChain(Hash256 blockHash, bool throwOnMissingHash = true) => + _baseTree.IsMainChain(blockHash, false) || _overlayTree.IsMainChain(blockHash, throwOnMissingHash); + + public BlockHeader FindBestSuggestedHeader() => + _overlayTree.FindBestSuggestedHeader() ?? _baseTree.FindBestSuggestedHeader(); +} diff --git a/src/Nethermind/Nethermind.Blockchain/Find/IBlockFinder.cs b/src/Nethermind/Nethermind.Blockchain/Find/IBlockFinder.cs index 69299ab7dda..e65b57f8c49 100644 --- a/src/Nethermind/Nethermind.Blockchain/Find/IBlockFinder.cs +++ b/src/Nethermind/Nethermind.Blockchain/Find/IBlockFinder.cs @@ -43,8 +43,9 @@ public interface IBlockFinder /// Checks if the block is currently in the canonical chain /// /// Hash of the block to check + /// If should throw when hash is not found /// True if part of the canonical chain, otherwise False - bool IsMainChain(Hash256 blockHash); + bool IsMainChain(Hash256 blockHash, bool throwOnMissingHash = true); public Block? FindBlock(Hash256 blockHash, long? blockNumber = null) => FindBlock(blockHash, BlockTreeLookupOptions.None, blockNumber); diff --git a/src/Nethermind/Nethermind.Blockchain/ReadOnlyBlockTree.cs b/src/Nethermind/Nethermind.Blockchain/ReadOnlyBlockTree.cs index 7b5f21f81b8..4fc9e1077e8 100644 --- a/src/Nethermind/Nethermind.Blockchain/ReadOnlyBlockTree.cs +++ b/src/Nethermind/Nethermind.Blockchain/ReadOnlyBlockTree.cs @@ -110,7 +110,7 @@ public void UpdateHeadBlock(Hash256 blockHash) public void DeleteInvalidBlock(Block invalidBlock) => throw new InvalidOperationException($"{nameof(ReadOnlyBlockTree)} does not expect {nameof(DeleteInvalidBlock)} calls"); - public bool IsMainChain(Hash256 blockHash) => _wrapped.IsMainChain(blockHash); + public bool IsMainChain(Hash256 blockHash, bool throwOnMissingHash = true) => _wrapped.IsMainChain(blockHash, throwOnMissingHash); public BlockHeader FindBestSuggestedHeader() => _wrapped.FindBestSuggestedHeader(); diff --git a/src/Nethermind/Nethermind.Cli.Test/ProofCliModuleTests.cs b/src/Nethermind/Nethermind.Cli.Test/ProofCliModuleTests.cs index dac449f4a53..3eac4d9207c 100644 --- a/src/Nethermind/Nethermind.Cli.Test/ProofCliModuleTests.cs +++ b/src/Nethermind/Nethermind.Cli.Test/ProofCliModuleTests.cs @@ -7,6 +7,7 @@ using Nethermind.Cli.Modules; using Nethermind.Core.Crypto; using Nethermind.Core.Test.Builders; +using Nethermind.Facade.Eth; using Nethermind.JsonRpc; using Nethermind.JsonRpc.Client; using Nethermind.JsonRpc.Data; diff --git a/src/Nethermind/Nethermind.Cli/Modules/EthCliModule.cs b/src/Nethermind/Nethermind.Cli/Modules/EthCliModule.cs index adba54d9821..961ce37bfe9 100644 --- a/src/Nethermind/Nethermind.Cli/Modules/EthCliModule.cs +++ b/src/Nethermind/Nethermind.Cli/Modules/EthCliModule.cs @@ -7,6 +7,7 @@ using Nethermind.Core; using Nethermind.Core.Crypto; using Nethermind.Core.Extensions; +using Nethermind.Facade.Eth; using Nethermind.Int256; using Nethermind.JsonRpc.Data; @@ -49,6 +50,10 @@ public JsValue GetProof(string address, string[] storageKeys, string? blockParam return NodeManager.Post("eth_call", tx, blockParameter ?? "latest").Result; } + [CliFunction("eth", "simulateV1")] + public JsValue SimulateV1(ulong version, object[] blockCalls, string? blockParameter = null, bool traceTransfers = true) => + NodeManager.PostJint("eth_simulateV1", 1, blockCalls, blockParameter ?? "latest", traceTransfers).Result; + [CliFunction("eth", "getBlockByHash")] public JsValue GetBlockByHash(string hash, bool returnFullTransactionObjects) { diff --git a/src/Nethermind/Nethermind.Clique.Test/CliqueBlockProducerTests.cs b/src/Nethermind/Nethermind.Clique.Test/CliqueBlockProducerTests.cs index 306732f99f9..ce11d9a3b6a 100644 --- a/src/Nethermind/Nethermind.Clique.Test/CliqueBlockProducerTests.cs +++ b/src/Nethermind/Nethermind.Clique.Test/CliqueBlockProducerTests.cs @@ -128,8 +128,10 @@ 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, nodeLogManager), + new VirtualMachine(blockhashProvider, specProvider, codeInfoRepository, nodeLogManager), + codeInfoRepository, nodeLogManager); BlockProcessor blockProcessor = new( goerliSpecProvider, @@ -147,8 +149,8 @@ public On CreateNode(PrivateKey privateKey, bool withGenesisAlreadyProcessed = f IReadOnlyTrieStore minerTrieStore = trieStore.AsReadOnly(); WorldState minerStateProvider = new(minerTrieStore, codeDb, nodeLogManager); - VirtualMachine minerVirtualMachine = new(blockhashProvider, specProvider, nodeLogManager); - TransactionProcessor minerTransactionProcessor = new(goerliSpecProvider, minerStateProvider, minerVirtualMachine, nodeLogManager); + VirtualMachine minerVirtualMachine = new(blockhashProvider, specProvider, codeInfoRepository, nodeLogManager); + TransactionProcessor minerTransactionProcessor = new(goerliSpecProvider, minerStateProvider, minerVirtualMachine, codeInfoRepository, nodeLogManager); BlockProcessor minerBlockProcessor = new( goerliSpecProvider, diff --git a/src/Nethermind/Nethermind.Consensus/Processing/BlockProcessor.BlockProductionTransactionPicker.cs b/src/Nethermind/Nethermind.Consensus/Processing/BlockProcessor.BlockProductionTransactionPicker.cs index 10aa4a3a9e2..e5c1c8d1543 100644 --- a/src/Nethermind/Nethermind.Consensus/Processing/BlockProcessor.BlockProductionTransactionPicker.cs +++ b/src/Nethermind/Nethermind.Consensus/Processing/BlockProcessor.BlockProductionTransactionPicker.cs @@ -16,10 +16,12 @@ public partial class BlockProcessor public class BlockProductionTransactionPicker : IBlockProductionTransactionPicker { protected readonly ISpecProvider _specProvider; + private readonly bool _ignoreEip3607; - public BlockProductionTransactionPicker(ISpecProvider specProvider) + public BlockProductionTransactionPicker(ISpecProvider specProvider, bool ignoreEip3607 = false) { _specProvider = specProvider; + _ignoreEip3607 = ignoreEip3607; } public event EventHandler? AddingTransaction; @@ -63,7 +65,7 @@ public virtual AddingTxEventArgs CanAddTransaction(Block block, Transaction curr return args.Set(TxAction.Skip, $"EIP-3860 - transaction size over max init code size"); } - if (stateProvider.IsInvalidContractSender(spec, currentTx.SenderAddress)) + if (!_ignoreEip3607 && stateProvider.IsInvalidContractSender(spec, currentTx.SenderAddress)) { return args.Set(TxAction.Skip, $"Sender is contract"); } diff --git a/src/Nethermind/Nethermind.Consensus/Processing/BlockProcessor.BlockValidationTransactionsExecutor.cs b/src/Nethermind/Nethermind.Consensus/Processing/BlockProcessor.BlockValidationTransactionsExecutor.cs index 47aab02ee72..d487cda83fa 100644 --- a/src/Nethermind/Nethermind.Consensus/Processing/BlockProcessor.BlockValidationTransactionsExecutor.cs +++ b/src/Nethermind/Nethermind.Consensus/Processing/BlockProcessor.BlockValidationTransactionsExecutor.cs @@ -49,7 +49,7 @@ public TxReceipt[] ProcessTransactions(Block block, ProcessingOptions processing return receiptsTracer.TxReceipts.ToArray(); } - private void ProcessTransaction(in BlockExecutionContext blkCtx, Transaction currentTx, int index, BlockReceiptsTracer receiptsTracer, ProcessingOptions processingOptions) + protected virtual void ProcessTransaction(in BlockExecutionContext blkCtx, Transaction currentTx, int index, BlockReceiptsTracer receiptsTracer, ProcessingOptions processingOptions) { TransactionResult result = _transactionProcessor.ProcessTransaction(in blkCtx, currentTx, receiptsTracer, processingOptions, _stateProvider); if (!result) ThrowInvalidBlockException(result, blkCtx.Header, currentTx, index); diff --git a/src/Nethermind/Nethermind.Consensus/Processing/BlockProcessor.cs b/src/Nethermind/Nethermind.Consensus/Processing/BlockProcessor.cs index d52edd62af7..a4f6afd26c7 100644 --- a/src/Nethermind/Nethermind.Consensus/Processing/BlockProcessor.cs +++ b/src/Nethermind/Nethermind.Consensus/Processing/BlockProcessor.cs @@ -2,6 +2,7 @@ // SPDX-License-Identifier: LGPL-3.0-only using System; +using System.Collections.Concurrent; using System.Collections.Generic; using System.Numerics; using System.Runtime.InteropServices; diff --git a/src/Nethermind/Nethermind.Consensus/Processing/IBlockProcessor.cs b/src/Nethermind/Nethermind.Consensus/Processing/IBlockProcessor.cs index ceb8da16d7c..88aba737926 100644 --- a/src/Nethermind/Nethermind.Consensus/Processing/IBlockProcessor.cs +++ b/src/Nethermind/Nethermind.Consensus/Processing/IBlockProcessor.cs @@ -18,9 +18,7 @@ public interface IBlockProcessor /// Initial state for the processed branch. /// List of blocks to be processed. /// Options to use for processor and transaction processor. - /// - /// Block tracer to use. By default either or - /// + /// Block tracer to use. By default either or /// List of processed blocks. Block[] Process( Hash256 newBranchStateRoot, diff --git a/src/Nethermind/Nethermind.Consensus/Processing/ReadOnlyTxProcessingEnv.cs b/src/Nethermind/Nethermind.Consensus/Processing/ReadOnlyTxProcessingEnv.cs index 3fdb0138fba..3290d6d1c0b 100644 --- a/src/Nethermind/Nethermind.Consensus/Processing/ReadOnlyTxProcessingEnv.cs +++ b/src/Nethermind/Nethermind.Consensus/Processing/ReadOnlyTxProcessingEnv.cs @@ -12,48 +12,42 @@ // ReSharper disable MemberCanBePrivate.Global // ReSharper disable UnusedAutoPropertyAccessor.Global -namespace Nethermind.Consensus.Processing; - -public class ReadOnlyTxProcessingEnv : IReadOnlyTxProcessorSource +namespace Nethermind.Consensus.Processing { - protected readonly ISpecProvider _specProvider; - protected readonly ILogManager? _logManager; - - public IStateReader StateReader { get; } - public IWorldState StateProvider { get; } - public ITransactionProcessor TransactionProcessor { get; set; } - public IBlockTree BlockTree { get; } - - public ReadOnlyTxProcessingEnv( - IWorldStateManager? worldStateManager, - IReadOnlyBlockTree? readOnlyBlockTree, - ISpecProvider? specProvider, - ILogManager? logManager, - IWorldState? worldStateToWarmUp = null) - { - ArgumentNullException.ThrowIfNull(worldStateManager); - ArgumentNullException.ThrowIfNull(readOnlyBlockTree); - ArgumentNullException.ThrowIfNull(specProvider); - - StateReader = worldStateManager.GlobalStateReader; - StateProvider = worldStateManager.CreateResettableWorldState(worldStateToWarmUp); - BlockTree = readOnlyBlockTree; - _specProvider = specProvider; - _logManager = logManager; - TransactionProcessor = CreateTransactionProcessor(); - } - - protected virtual TransactionProcessor CreateTransactionProcessor() - { - BlockhashProvider blockhashProvider = new(BlockTree, _specProvider, StateProvider, _logManager); - VirtualMachine virtualMachine = new(blockhashProvider, _specProvider, _logManager); - return new TransactionProcessor(_specProvider, StateProvider, virtualMachine, _logManager); - } - - public IReadOnlyTransactionProcessor Build(Hash256 stateRoot) => new ReadOnlyTransactionProcessor(TransactionProcessor, StateProvider, stateRoot); - - public void Reset() + public class ReadOnlyTxProcessingEnv : ReadOnlyTxProcessingEnvBase, IReadOnlyTxProcessorSource { - StateProvider.Reset(); + public ITransactionProcessor TransactionProcessor { get; set; } + public IVirtualMachine Machine { get; } + + public ICodeInfoRepository CodeInfoRepository { get; } + public ReadOnlyTxProcessingEnv( + IWorldStateManager worldStateManager, + IBlockTree blockTree, + ISpecProvider? specProvider, + ILogManager? logManager, + IWorldState? worldStateToWarmUp = null) + : this(worldStateManager, blockTree.AsReadOnly(), specProvider, logManager, worldStateToWarmUp) + { + } + + public ReadOnlyTxProcessingEnv( + IWorldStateManager worldStateManager, + IReadOnlyBlockTree readOnlyBlockTree, + ISpecProvider? specProvider, + ILogManager? logManager, + IWorldState? worldStateToWarmUp = null + ) : base(worldStateManager, readOnlyBlockTree, specProvider, logManager, worldStateToWarmUp) + { + CodeInfoRepository = new CodeInfoRepository(); + Machine = new VirtualMachine(BlockhashProvider, specProvider, CodeInfoRepository, logManager); + TransactionProcessor = CreateTransactionProcessor(); + } + + protected virtual TransactionProcessor CreateTransactionProcessor() + { + return new TransactionProcessor(SpecProvider, StateProvider, Machine, CodeInfoRepository, LogManager); + } + + public IReadOnlyTransactionProcessor Build(Hash256 stateRoot) => new ReadOnlyTransactionProcessor(TransactionProcessor, StateProvider, stateRoot); } } diff --git a/src/Nethermind/Nethermind.Consensus/Processing/ReadOnlyTxProcessingEnvBase.cs b/src/Nethermind/Nethermind.Consensus/Processing/ReadOnlyTxProcessingEnvBase.cs new file mode 100644 index 00000000000..7b990da55e6 --- /dev/null +++ b/src/Nethermind/Nethermind.Consensus/Processing/ReadOnlyTxProcessingEnvBase.cs @@ -0,0 +1,46 @@ +// SPDX-FileCopyrightText: 2023 Demerzel Solutions Limited +// SPDX-License-Identifier: LGPL-3.0-only + +using System; +using Nethermind.Blockchain; +using Nethermind.Core.Specs; +using Nethermind.Evm; +using Nethermind.Logging; +using Nethermind.State; + +namespace Nethermind.Consensus.Processing; + +public class ReadOnlyTxProcessingEnvBase +{ + public IStateReader StateReader { get; protected set; } + public IWorldState StateProvider { get; protected set; } + public IBlockTree BlockTree { get; protected set; } + public IBlockhashProvider BlockhashProvider { get; protected set; } + + public ISpecProvider SpecProvider { get; } + public ILogManager? LogManager { get; set; } + + protected ReadOnlyTxProcessingEnvBase( + IWorldStateManager worldStateManager, + IBlockTree readOnlyBlockTree, + ISpecProvider? specProvider, + ILogManager? logManager, + IWorldState? worldStateToWarmUp = null + ) + { + ArgumentNullException.ThrowIfNull(specProvider); + ArgumentNullException.ThrowIfNull(worldStateManager); + SpecProvider = specProvider; + StateReader = worldStateManager.GlobalStateReader; + StateProvider = worldStateManager.CreateResettableWorldState(worldStateToWarmUp); + BlockTree = readOnlyBlockTree ?? throw new ArgumentNullException(nameof(readOnlyBlockTree)); + BlockhashProvider = new BlockhashProvider(BlockTree, specProvider, StateProvider, logManager); + LogManager = logManager; + } + + + public void Reset() + { + StateProvider.Reset(); + } +} diff --git a/src/Nethermind/Nethermind.Consensus/Processing/ReadOnlyTxProcessingEnvFactory.cs b/src/Nethermind/Nethermind.Consensus/Processing/ReadOnlyTxProcessingEnvFactory.cs index 246eadbbdda..93c214be0cd 100644 --- a/src/Nethermind/Nethermind.Consensus/Processing/ReadOnlyTxProcessingEnvFactory.cs +++ b/src/Nethermind/Nethermind.Consensus/Processing/ReadOnlyTxProcessingEnvFactory.cs @@ -10,18 +10,18 @@ namespace Nethermind.Consensus.Processing; public class ReadOnlyTxProcessingEnvFactory( IWorldStateManager worldStateManager, - IReadOnlyBlockTree? readOnlyBlockTree, + IReadOnlyBlockTree readOnlyBlockTree, ISpecProvider? specProvider, ILogManager? logManager, IWorldState? worldStateToWarmUp = null) { public ReadOnlyTxProcessingEnvFactory( IWorldStateManager worldStateManager, - IBlockTree? blockTree, + IBlockTree blockTree, ISpecProvider? specProvider, ILogManager? logManager, IWorldState? worldStateToWarmUp = null) - : this(worldStateManager, blockTree?.AsReadOnly(), specProvider, logManager, worldStateToWarmUp) + : this(worldStateManager, blockTree.AsReadOnly(), specProvider, logManager, worldStateToWarmUp) { } diff --git a/src/Nethermind/Nethermind.Consensus/Validators/SimulateBlockValidatorProxy.cs b/src/Nethermind/Nethermind.Consensus/Validators/SimulateBlockValidatorProxy.cs new file mode 100644 index 00000000000..83381ed2271 --- /dev/null +++ b/src/Nethermind/Nethermind.Consensus/Validators/SimulateBlockValidatorProxy.cs @@ -0,0 +1,30 @@ +// SPDX-FileCopyrightText: 2023 Demerzel Solutions Limited +// SPDX-License-Identifier: LGPL-3.0-only + +using Nethermind.Core; + +namespace Nethermind.Consensus.Validators; + +public class SimulateBlockValidatorProxy(IBlockValidator baseBlockValidator) : IBlockValidator +{ + public bool ValidateWithdrawals(Block block, out string? error) => + baseBlockValidator.ValidateWithdrawals(block, out error); + + public bool ValidateOrphanedBlock(Block block, out string? error) => + baseBlockValidator.ValidateOrphanedBlock(block, out error); + + public bool ValidateSuggestedBlock(Block block, out string? error, bool validateHashes = true) => + baseBlockValidator.ValidateSuggestedBlock(block, out error, validateHashes); + + public bool ValidateProcessedBlock(Block processedBlock, TxReceipt[] receipts, Block suggestedBlock, out string? error) + { + error = ""; + return true; + } + + public bool Validate(BlockHeader header, BlockHeader? parent, bool isUncle, out string? error) => + baseBlockValidator.Validate(header, parent, isUncle, out error); + + public bool Validate(BlockHeader header, bool isUncle, out string? error) => + baseBlockValidator.Validate(header, isUncle, out error); +} diff --git a/src/Nethermind/Nethermind.Core.Test/Blockchain/TestBlockchain.cs b/src/Nethermind/Nethermind.Core.Test/Blockchain/TestBlockchain.cs index f52bed39e13..77129d23082 100644 --- a/src/Nethermind/Nethermind.Core.Test/Blockchain/TestBlockchain.cs +++ b/src/Nethermind/Nethermind.Core.Test/Blockchain/TestBlockchain.cs @@ -8,7 +8,10 @@ using Nethermind.Blockchain; using Nethermind.Blockchain.Blocks; using Nethermind.Blockchain.Find; +using Nethermind.Blockchain.FullPruning; +using Nethermind.Blockchain.Headers; using Nethermind.Blockchain.Receipts; +using Nethermind.Blockchain.Synchronization; using Nethermind.Config; using Nethermind.Consensus; using Nethermind.Consensus.BeaconBlockRoot; @@ -33,6 +36,7 @@ using Nethermind.Specs; using Nethermind.Specs.Test; using Nethermind.State; +using Nethermind.State.Repositories; using Nethermind.Trie; using Nethermind.Trie.Pruning; using Nethermind.TxPool; @@ -156,10 +160,16 @@ protected virtual async Task Build(ISpecProvider? specProvider = WorldStateManager = new WorldStateManager(State, TrieStore, DbProvider, LimboLogs.Instance); StateReader = new StateReader(ReadOnlyTrieStore, CodeDb, LogManager); - BlockTree = Builders.Build.A.BlockTree() - .WithSpecProvider(SpecProvider) - .WithoutSettingHead - .TestObject; + BlockTree = new BlockTree(new BlockStore(DbProvider.BlocksDb), + new HeaderStore(DbProvider.HeadersDb, DbProvider.BlockNumbersDb), + DbProvider.BlockInfosDb, + DbProvider.MetadataDb, + new BlockStore(new TestMemDb(), 100), + new ChainLevelInfoRepository(DbProvider.BlockInfosDb), + SpecProvider, + NullBloomStorage.Instance, + new SyncConfig(), + LimboLogs.Instance); ReadOnlyState = new ChainHeadReadOnlyStateProvider(BlockTree, StateReader); TransactionComparerProvider = new TransactionComparerProvider(SpecProvider, BlockTree); @@ -171,10 +181,11 @@ protected virtual async Task Build(ISpecProvider? specProvider = NonceManager = new NonceManager(chainHeadInfoProvider.AccountStateProvider); _trieStoreWatcher = new TrieStoreBoundaryWatcher(WorldStateManager, BlockTree, LogManager); - + CodeInfoRepository codeInfoRepository = new(); ReceiptStorage = new InMemoryReceiptStorage(blockTree: BlockTree); - VirtualMachine virtualMachine = new(new BlockhashProvider(BlockTree, SpecProvider, State, LogManager), SpecProvider, LogManager); - TxProcessor = new TransactionProcessor(SpecProvider, State, virtualMachine, LogManager); + VirtualMachine virtualMachine = new(new BlockhashProvider(BlockTree, SpecProvider, State, LogManager), SpecProvider, codeInfoRepository, LogManager); + TxProcessor = new TransactionProcessor(SpecProvider, State, virtualMachine, codeInfoRepository, LogManager); + BlockPreprocessorStep = new RecoverSignatures(EthereumEcdsa, TxPool, SpecProvider, LogManager); HeaderValidator = new HeaderValidator(BlockTree, Always.Valid, SpecProvider, LogManager); diff --git a/src/Nethermind/Nethermind.Core.Test/Json/UInt256DictionaryKeyConverterTests.cs b/src/Nethermind/Nethermind.Core.Test/Json/UInt256DictionaryKeyConverterTests.cs index 533e777f8af..2f37bd6b572 100644 --- a/src/Nethermind/Nethermind.Core.Test/Json/UInt256DictionaryKeyConverterTests.cs +++ b/src/Nethermind/Nethermind.Core.Test/Json/UInt256DictionaryKeyConverterTests.cs @@ -115,7 +115,7 @@ public void WriteJson_Dictionary_SerializedCorrectly() { new UInt256(1), new UInt256(12345) } }; string serialised = JsonSerializer.Serialize(dictionary, Options); - + serialised = serialised.Replace("\r\n", "\n"); string expected = """ { "0x1": "0x3039" diff --git a/src/Nethermind/Nethermind.Core/Address.cs b/src/Nethermind/Nethermind.Core/Address.cs index d56cda23269..79acbe7d5ff 100644 --- a/src/Nethermind/Nethermind.Core/Address.cs +++ b/src/Nethermind/Nethermind.Core/Address.cs @@ -35,9 +35,9 @@ public class Address : IEquatable
, IComparable
public byte[] Bytes { get; } - public Address(Hash256 keccak) : this(keccak.Bytes.Slice(12, Size).ToArray()) { } + public Address(Hash256 hash) : this(hash.Bytes.Slice(12, Size).ToArray()) { } - public Address(in ValueHash256 keccak) : this(keccak.BytesAsSpan.Slice(12, Size).ToArray()) { } + public Address(in ValueHash256 hash) : this(hash.BytesAsSpan.Slice(12, Size).ToArray()) { } public byte this[int index] => Bytes[index]; @@ -235,6 +235,14 @@ public override bool CanConvertTo(ITypeDescriptorContext? context, Type? destina } public Hash256 ToAccountPath => Keccak.Compute(Bytes); + + [SkipLocalsInit] + public ValueHash256 ToHash() + { + Span addressBytes = stackalloc byte[Hash256.Size]; + Bytes.CopyTo(addressBytes.Slice(Hash256.Size - Address.Size)); + return new ValueHash256(addressBytes); + } } public readonly struct AddressAsKey(Address key) : IEquatable diff --git a/src/Nethermind/Nethermind.Core/Buffers/CappedArray.cs b/src/Nethermind/Nethermind.Core/Buffers/CappedArray.cs index 0d909d05077..9dfa54cf9a0 100644 --- a/src/Nethermind/Nethermind.Core/Buffers/CappedArray.cs +++ b/src/Nethermind/Nethermind.Core/Buffers/CappedArray.cs @@ -110,4 +110,14 @@ public readonly Span AsSpan(int start, int length) if (_length == array.Length) return array; return AsSpan().ToArray(); } + + public readonly ArraySegment AsArraySegment() + { + return AsArraySegment(0, _length); + } + + public readonly ArraySegment AsArraySegment(int start, int length) + { + return new ArraySegment(_array!, start, length); + } } diff --git a/src/Nethermind/Nethermind.Core/CappedArrayMemoryManager.cs b/src/Nethermind/Nethermind.Core/CappedArrayMemoryManager.cs new file mode 100644 index 00000000000..65d52241f0b --- /dev/null +++ b/src/Nethermind/Nethermind.Core/CappedArrayMemoryManager.cs @@ -0,0 +1,63 @@ +// SPDX-FileCopyrightText: 2024 Demerzel Solutions Limited +// SPDX-License-Identifier: LGPL-3.0-only + +using System; +using System.Buffers; + +namespace Nethermind.Core.Buffers; + +public class CappedArrayMemoryManager(CappedArray? data) : MemoryManager +{ + private readonly CappedArray _data = data ?? throw new ArgumentNullException(nameof(data)); + private bool _isDisposed; + + public override Span GetSpan() + { + if (_isDisposed) + { + throw new ObjectDisposedException(nameof(CappedArrayMemoryManager)); + } + + return _data.AsSpan(); + } + + public override MemoryHandle Pin(int elementIndex = 0) + { + if (_isDisposed) + { + throw new ObjectDisposedException(nameof(CappedArrayMemoryManager)); + } + + if (elementIndex < 0 || elementIndex >= _data.Length) + { + throw new ArgumentOutOfRangeException(nameof(elementIndex)); + } + // Pinning is a no-op in this managed implementation + return new MemoryHandle(); + } + + public override void Unpin() + { + if (_isDisposed) + { + throw new ObjectDisposedException(nameof(CappedArrayMemoryManager)); + } + // Unpinning is a no-op in this managed implementation + } + + protected override void Dispose(bool disposing) + { + _isDisposed = true; + } + + protected override bool TryGetArray(out ArraySegment segment) + { + if (_isDisposed) + { + throw new ObjectDisposedException(nameof(CappedArrayMemoryManager)); + } + + segment = _data.AsArraySegment(); + return true; + } +} diff --git a/src/Nethermind/Nethermind.Core/Collections/LinkedHashSet.cs b/src/Nethermind/Nethermind.Core/Collections/LinkedHashSet.cs index 7ebcb0ad518..553448f9c60 100644 --- a/src/Nethermind/Nethermind.Core/Collections/LinkedHashSet.cs +++ b/src/Nethermind/Nethermind.Core/Collections/LinkedHashSet.cs @@ -196,7 +196,6 @@ public void UnionWith(IEnumerable? other) #endregion - #region ICollection public int Count => _dict.Count; diff --git a/src/Nethermind/Nethermind.Core/Crypto/Hash256.cs b/src/Nethermind/Nethermind.Core/Crypto/Hash256.cs index b2002f8936f..6b123e45158 100644 --- a/src/Nethermind/Nethermind.Core/Crypto/Hash256.cs +++ b/src/Nethermind/Nethermind.Core/Crypto/Hash256.cs @@ -117,12 +117,12 @@ public Hash256 ToCommitment() } public static bool operator ==(in ValueHash256 left, in ValueHash256 right) => left.Equals(in right); - public static bool operator !=(in ValueHash256 left, in ValueHash256 right) => !(left == right); public static bool operator >(in ValueHash256 left, in ValueHash256 right) => left.CompareTo(in right) > 0; public static bool operator <(in ValueHash256 left, in ValueHash256 right) => left.CompareTo(in right) < 0; public static bool operator >=(in ValueHash256 left, in ValueHash256 right) => left.CompareTo(in right) >= 0; public static bool operator <=(in ValueHash256 left, in ValueHash256 right) => left.CompareTo(in right) <= 0; + public static implicit operator Hash256(in ValueHash256 keccak) => new(keccak); } public readonly struct Hash256AsKey(Hash256 key) : IEquatable diff --git a/src/Nethermind/Nethermind.Db.Test/DbOnTheRocksTests.cs b/src/Nethermind/Nethermind.Db.Test/DbOnTheRocksTests.cs index 5aa658b3424..7b7ff34f41c 100644 --- a/src/Nethermind/Nethermind.Db.Test/DbOnTheRocksTests.cs +++ b/src/Nethermind/Nethermind.Db.Test/DbOnTheRocksTests.cs @@ -48,7 +48,7 @@ public void TearDown() public void WriteOptions_is_correct() { IDbConfig config = new DbConfig(); - DbOnTheRocks db = new(DbPath, GetRocksDbSettings(DbPath, "Blocks"), config, LimboLogs.Instance); + using DbOnTheRocks db = new(DbPath, GetRocksDbSettings(DbPath, "Blocks"), config, LimboLogs.Instance); WriteOptions? options = db.WriteFlagsToWriteOptions(WriteFlags.LowPriority); Native.Instance.rocksdb_writeoptions_get_low_pri(options.Handle).Should().BeTrue(); diff --git a/src/Nethermind/Nethermind.Db/ReadOnlyDb.cs b/src/Nethermind/Nethermind.Db/ReadOnlyDb.cs index 6feee30504c..495a51fbe5e 100644 --- a/src/Nethermind/Nethermind.Db/ReadOnlyDb.cs +++ b/src/Nethermind/Nethermind.Db/ReadOnlyDb.cs @@ -3,38 +3,30 @@ using System; using System.Collections.Generic; +using System.Linq; using Nethermind.Core; namespace Nethermind.Db { - public class ReadOnlyDb : IReadOnlyDb + public class ReadOnlyDb(IDb wrappedDb, bool createInMemWriteStore) : IReadOnlyDb { private readonly MemDb _memDb = new(); - private readonly IDb _wrappedDb; - private readonly bool _createInMemWriteStore; - - public ReadOnlyDb(IDb wrappedDb, bool createInMemWriteStore) - { - _wrappedDb = wrappedDb; - _createInMemWriteStore = createInMemWriteStore; - } - public void Dispose() { _memDb.Dispose(); } - public string Name { get; } = "ReadOnlyDb"; + public string Name { get => wrappedDb.Name; } public byte[]? Get(ReadOnlySpan key, ReadFlags flags = ReadFlags.None) { - return _memDb.Get(key, flags) ?? _wrappedDb.Get(key, flags); + return _memDb.Get(key, flags) ?? wrappedDb.Get(key, flags); } public void Set(ReadOnlySpan key, byte[]? value, WriteFlags flags = WriteFlags.None) { - if (!_createInMemWriteStore) + if (!createInMemWriteStore) { throw new InvalidOperationException($"This {nameof(ReadOnlyDb)} did not expect any writes."); } @@ -46,7 +38,7 @@ public KeyValuePair[] this[byte[][] keys] { get { - var result = _wrappedDb[keys]; + var result = wrappedDb[keys]; var memResult = _memDb[keys]; for (int i = 0; i < memResult.Length; i++) { @@ -61,43 +53,34 @@ public KeyValuePair[] this[byte[][] keys] } } - public IEnumerable> GetAll(bool ordered = false) => _memDb.GetAll(); + public IEnumerable> GetAll(bool ordered = false) => _memDb.GetAll().Union(wrappedDb.GetAll()); - public IEnumerable GetAllKeys(bool ordered = false) => _memDb.GetAllKeys(); + public IEnumerable GetAllKeys(bool ordered = false) => _memDb.GetAllKeys().Union(wrappedDb.GetAllKeys()); - public IEnumerable GetAllValues(bool ordered = false) => _memDb.GetAllValues(); + public IEnumerable GetAllValues(bool ordered = false) => _memDb.GetAllValues().Union(wrappedDb.GetAllValues()); - public IWriteBatch StartWriteBatch() - { - return this.LikeABatch(); - } + public IWriteBatch StartWriteBatch() => this.LikeABatch(); - public IDbMeta.DbMetric GatherMetric(bool includeSharedCache = false) => _wrappedDb.GatherMetric(includeSharedCache); + public IDbMeta.DbMetric GatherMetric(bool includeSharedCache = false) => wrappedDb.GatherMetric(includeSharedCache); public void Remove(ReadOnlySpan key) { } - public bool KeyExists(ReadOnlySpan key) - { - return _memDb.KeyExists(key) || _wrappedDb.KeyExists(key); - } + public bool KeyExists(ReadOnlySpan key) => _memDb.KeyExists(key) || wrappedDb.KeyExists(key); public void Flush() { - _wrappedDb.Flush(); + wrappedDb.Flush(); _memDb.Flush(); } - public void Clear() { throw new InvalidOperationException(); } + public void Clear() => throw new InvalidOperationException(); - public virtual void ClearTempChanges() - { - _memDb.Clear(); - } + public virtual void ClearTempChanges() => _memDb.Clear(); - public Span GetSpan(ReadOnlySpan key) => _memDb.Get(key).AsSpan(); + public Span GetSpan(ReadOnlySpan key) => Get(key).AsSpan(); public void PutSpan(ReadOnlySpan keyBytes, ReadOnlySpan value, WriteFlags writeFlags = WriteFlags.None) { - if (!_createInMemWriteStore) + if (!createInMemWriteStore) { throw new InvalidOperationException($"This {nameof(ReadOnlyDb)} did not expect any writes."); } diff --git a/src/Nethermind/Nethermind.Evm.Benchmark/EvmBenchmarks.cs b/src/Nethermind/Nethermind.Evm.Benchmark/EvmBenchmarks.cs index 8d01214bfaa..fd31df6fa0c 100644 --- a/src/Nethermind/Nethermind.Evm.Benchmark/EvmBenchmarks.cs +++ b/src/Nethermind/Nethermind.Evm.Benchmark/EvmBenchmarks.cs @@ -44,8 +44,8 @@ public void GlobalSetup() _stateProvider = new WorldState(trieStore, codeDb, new OneLoggerLogManager(NullLogger.Instance)); _stateProvider.CreateAccount(Address.Zero, 1000.Ether()); _stateProvider.Commit(_spec); - - _virtualMachine = new VirtualMachine(_blockhashProvider, MainnetSpecProvider.Instance, LimboLogs.Instance); + CodeInfoRepository codeInfoRepository = new(); + _virtualMachine = new VirtualMachine(_blockhashProvider, MainnetSpecProvider.Instance, codeInfoRepository, LimboLogs.Instance); _environment = new ExecutionEnvironment ( diff --git a/src/Nethermind/Nethermind.Evm.Benchmark/MultipleUnsignedOperations.cs b/src/Nethermind/Nethermind.Evm.Benchmark/MultipleUnsignedOperations.cs index 749ae715bd8..3a45a79026b 100644 --- a/src/Nethermind/Nethermind.Evm.Benchmark/MultipleUnsignedOperations.cs +++ b/src/Nethermind/Nethermind.Evm.Benchmark/MultipleUnsignedOperations.cs @@ -76,7 +76,8 @@ public void GlobalSetup() _stateProvider.Commit(_spec); Console.WriteLine(MuirGlacier.Instance); - _virtualMachine = new VirtualMachine(_blockhashProvider, MainnetSpecProvider.Instance, new OneLoggerLogManager(NullLogger.Instance)); + CodeInfoRepository codeInfoRepository = new(); + _virtualMachine = new VirtualMachine(_blockhashProvider, MainnetSpecProvider.Instance, codeInfoRepository, new OneLoggerLogManager(NullLogger.Instance)); _environment = new ExecutionEnvironment ( diff --git a/src/Nethermind/Nethermind.Evm.Benchmark/StaticCallBenchmarks.cs b/src/Nethermind/Nethermind.Evm.Benchmark/StaticCallBenchmarks.cs index ebedead21a1..c0f3e229c4f 100644 --- a/src/Nethermind/Nethermind.Evm.Benchmark/StaticCallBenchmarks.cs +++ b/src/Nethermind/Nethermind.Evm.Benchmark/StaticCallBenchmarks.cs @@ -87,7 +87,8 @@ public void GlobalSetup() _stateProvider.Commit(_spec); Console.WriteLine(MuirGlacier.Instance); - _virtualMachine = new VirtualMachine(_blockhashProvider, MainnetSpecProvider.Instance, new OneLoggerLogManager(NullLogger.Instance)); + CodeInfoRepository codeInfoRepository = new(); + _virtualMachine = new VirtualMachine(_blockhashProvider, MainnetSpecProvider.Instance, codeInfoRepository, new OneLoggerLogManager(NullLogger.Instance)); _environment = new ExecutionEnvironment ( diff --git a/src/Nethermind/Nethermind.Evm.Test/Eip3198BaseFeeTests.cs b/src/Nethermind/Nethermind.Evm.Test/Eip3198BaseFeeTests.cs index 6b20654a11f..75430c258c1 100644 --- a/src/Nethermind/Nethermind.Evm.Test/Eip3198BaseFeeTests.cs +++ b/src/Nethermind/Nethermind.Evm.Test/Eip3198BaseFeeTests.cs @@ -26,7 +26,7 @@ public class Eip3198BaseFeeTests : VirtualMachineTestsBase [TestCase(false, 0, false)] public void Base_fee_opcode_should_return_expected_results(bool eip3198Enabled, int baseFee, bool send1559Tx) { - _processor = new TransactionProcessor(SpecProvider, TestState, Machine, LimboLogs.Instance); + _processor = new TransactionProcessor(SpecProvider, TestState, Machine, CodeInfoRepository, LimboLogs.Instance); byte[] code = Prepare.EvmCode .Op(Instruction.BASEFEE) .PushData(0) diff --git a/src/Nethermind/Nethermind.Evm.Test/Eip3529RefundsTests.cs b/src/Nethermind/Nethermind.Evm.Test/Eip3529RefundsTests.cs index fe3e7a58e32..81c276ef05d 100644 --- a/src/Nethermind/Nethermind.Evm.Test/Eip3529RefundsTests.cs +++ b/src/Nethermind/Nethermind.Evm.Test/Eip3529RefundsTests.cs @@ -73,7 +73,7 @@ private void Test(string codeHex, long gasUsed, long refund, byte originalValue, TestState.CreateAccount(Recipient, 1.Ether()); TestState.Set(new StorageCell(Recipient, 0), new[] { originalValue }); TestState.Commit(eip3529Enabled ? London.Instance : Berlin.Instance); - _processor = new TransactionProcessor(SpecProvider, TestState, Machine, LimboLogs.Instance); + _processor = new TransactionProcessor(SpecProvider, TestState, Machine, CodeInfoRepository, LimboLogs.Instance); long blockNumber = eip3529Enabled ? MainnetSpecProvider.LondonBlockNumber : MainnetSpecProvider.LondonBlockNumber - 1; (Block block, Transaction transaction) = PrepareTx(blockNumber, 100000, Bytes.FromHexString(codeHex)); diff --git a/src/Nethermind/Nethermind.Evm.Test/Eip3541Tests.cs b/src/Nethermind/Nethermind.Evm.Test/Eip3541Tests.cs index 4154e94f095..0c5f2219dd3 100644 --- a/src/Nethermind/Nethermind.Evm.Test/Eip3541Tests.cs +++ b/src/Nethermind/Nethermind.Evm.Test/Eip3541Tests.cs @@ -96,7 +96,7 @@ void DeployCodeAndAssertTx(string code, bool eip3541Enabled, ContractDeployment break; } - _processor = new TransactionProcessor(SpecProvider, TestState, Machine, LimboLogs.Instance); + _processor = new TransactionProcessor(SpecProvider, TestState, Machine, CodeInfoRepository, LimboLogs.Instance); long blockNumber = eip3541Enabled ? MainnetSpecProvider.LondonBlockNumber : MainnetSpecProvider.LondonBlockNumber - 1; (Block block, Transaction transaction) = PrepareTx(blockNumber, 100000, createContract); diff --git a/src/Nethermind/Nethermind.Evm.Test/Eip7516BlobBaseFeeTests.cs b/src/Nethermind/Nethermind.Evm.Test/Eip7516BlobBaseFeeTests.cs index 1a1036de336..f62d8c595f6 100644 --- a/src/Nethermind/Nethermind.Evm.Test/Eip7516BlobBaseFeeTests.cs +++ b/src/Nethermind/Nethermind.Evm.Test/Eip7516BlobBaseFeeTests.cs @@ -27,7 +27,7 @@ public class Eip7516BlobBaseFeeTests : VirtualMachineTestsBase [TestCase(false, 0ul)] public void Blob_Base_fee_opcode_should_return_expected_results(bool eip7516Enabled, ulong excessBlobGas) { - _processor = new TransactionProcessor(SpecProvider, TestState, Machine, LimboLogs.Instance); + _processor = new TransactionProcessor(SpecProvider, TestState, Machine, CodeInfoRepository, LimboLogs.Instance); byte[] code = Prepare.EvmCode .Op(Instruction.BLOBBASEFEE) .PushData(0) diff --git a/src/Nethermind/Nethermind.Evm.Test/EvmPooledMemoryTests.cs b/src/Nethermind/Nethermind.Evm.Test/EvmPooledMemoryTests.cs index 18529a85794..ab88c978cae 100644 --- a/src/Nethermind/Nethermind.Evm.Test/EvmPooledMemoryTests.cs +++ b/src/Nethermind/Nethermind.Evm.Test/EvmPooledMemoryTests.cs @@ -158,14 +158,17 @@ private static string run(byte[] input) new MemDb(), LimboLogs.Instance); ISpecProvider specProvider = new TestSpecProvider(London.Instance); + CodeInfoRepository codeInfoRepository = new(); VirtualMachine virtualMachine = new( new TestBlockhashProvider(specProvider), specProvider, + codeInfoRepository, LimboLogs.Instance); TransactionProcessor transactionProcessor = new TransactionProcessor( specProvider, stateProvider, virtualMachine, + codeInfoRepository, LimboLogs.Instance); stateProvider.CreateAccount(to, 123); diff --git a/src/Nethermind/Nethermind.Evm.Test/Tracing/GasEstimationTests.cs b/src/Nethermind/Nethermind.Evm.Test/Tracing/GasEstimationTests.cs index 2e41c2443ed..def68b3ca1d 100644 --- a/src/Nethermind/Nethermind.Evm.Test/Tracing/GasEstimationTests.cs +++ b/src/Nethermind/Nethermind.Evm.Test/Tracing/GasEstimationTests.cs @@ -362,9 +362,9 @@ public TestEnvironment() _stateProvider.Commit(_specProvider.GenesisSpec); _stateProvider.CommitTree(0); - VirtualMachine virtualMachine = new(new TestBlockhashProvider(_specProvider), _specProvider, LimboLogs.Instance); - _transactionProcessor = new TransactionProcessor(_specProvider, _stateProvider, - virtualMachine, 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, LimboLogs.Instance); tracer = new(); diff --git a/src/Nethermind/Nethermind.Evm.Test/TransactionProcessorEip4844Tests.cs b/src/Nethermind/Nethermind.Evm.Test/TransactionProcessorEip4844Tests.cs index 7312cf923f7..562b27e937e 100644 --- a/src/Nethermind/Nethermind.Evm.Test/TransactionProcessorEip4844Tests.cs +++ b/src/Nethermind/Nethermind.Evm.Test/TransactionProcessorEip4844Tests.cs @@ -23,10 +23,10 @@ namespace Nethermind.Evm.Test; [TestFixture] internal class TransactionProcessorEip4844Tests { - private ISpecProvider _specProvider; - private IEthereumEcdsa _ethereumEcdsa; - private TransactionProcessor _transactionProcessor; - private IWorldState _stateProvider; + private ISpecProvider _specProvider = null!; + private IEthereumEcdsa _ethereumEcdsa = null!; + private TransactionProcessor _transactionProcessor = null!; + private IWorldState _stateProvider = null!; [SetUp] public void Setup() @@ -35,8 +35,9 @@ public void Setup() _specProvider = new TestSpecProvider(Cancun.Instance); TrieStore trieStore = new(stateDb, LimboLogs.Instance); _stateProvider = new WorldState(trieStore, new MemDb(), LimboLogs.Instance); - VirtualMachine virtualMachine = new(new TestBlockhashProvider(_specProvider), _specProvider, LimboLogs.Instance); - _transactionProcessor = new TransactionProcessor(_specProvider, _stateProvider, virtualMachine, 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, LimboLogs.Instance); } diff --git a/src/Nethermind/Nethermind.Evm.Test/TransactionProcessorFeeTests.cs b/src/Nethermind/Nethermind.Evm.Test/TransactionProcessorFeeTests.cs index 09d61e31b55..e0185255a0b 100644 --- a/src/Nethermind/Nethermind.Evm.Test/TransactionProcessorFeeTests.cs +++ b/src/Nethermind/Nethermind.Evm.Test/TransactionProcessorFeeTests.cs @@ -42,8 +42,9 @@ public void Setup() _stateProvider.Commit(_specProvider.GenesisSpec); _stateProvider.CommitTree(0); - VirtualMachine virtualMachine = new(new TestBlockhashProvider(_specProvider), _specProvider, LimboLogs.Instance); - _transactionProcessor = new TransactionProcessor(_specProvider, _stateProvider, virtualMachine, 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, LimboLogs.Instance); } diff --git a/src/Nethermind/Nethermind.Evm.Test/TransactionProcessorTests.cs b/src/Nethermind/Nethermind.Evm.Test/TransactionProcessorTests.cs index 5d30cc00eee..3a7139dc4b6 100644 --- a/src/Nethermind/Nethermind.Evm.Test/TransactionProcessorTests.cs +++ b/src/Nethermind/Nethermind.Evm.Test/TransactionProcessorTests.cs @@ -58,8 +58,9 @@ public void Setup() _stateProvider.Commit(_specProvider.GenesisSpec); _stateProvider.CommitTree(0); - VirtualMachine virtualMachine = new(new TestBlockhashProvider(_specProvider), _specProvider, LimboLogs.Instance); - _transactionProcessor = new TransactionProcessor(_specProvider, _stateProvider, virtualMachine, 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, LimboLogs.Instance); } diff --git a/src/Nethermind/Nethermind.Evm.Test/VirtualMachineTestsBase.cs b/src/Nethermind/Nethermind.Evm.Test/VirtualMachineTestsBase.cs index 5985f35d3d5..dcddc35cfec 100644 --- a/src/Nethermind/Nethermind.Evm.Test/VirtualMachineTestsBase.cs +++ b/src/Nethermind/Nethermind.Evm.Test/VirtualMachineTestsBase.cs @@ -35,6 +35,7 @@ public class VirtualMachineTestsBase private IDb _stateDb; protected VirtualMachine Machine { get; private set; } + protected CodeInfoRepository CodeInfoRepository { get; private set; } protected IWorldState TestState { get; private set; } protected static Address Contract { get; } = new("0xd75a3a95360e44a3874e691fb48d77855f127069"); protected static Address Sender { get; } = TestItem.AddressA; @@ -67,8 +68,9 @@ public virtual void Setup() TestState = new WorldState(trieStore, codeDb, logManager); _ethereumEcdsa = new EthereumEcdsa(SpecProvider.ChainId, logManager); IBlockhashProvider blockhashProvider = new TestBlockhashProvider(SpecProvider); - Machine = new VirtualMachine(blockhashProvider, SpecProvider, logManager); - _processor = new TransactionProcessor(SpecProvider, TestState, Machine, logManager); + CodeInfoRepository = new CodeInfoRepository(); + Machine = new VirtualMachine(blockhashProvider, SpecProvider, CodeInfoRepository, logManager); + _processor = new TransactionProcessor(SpecProvider, TestState, Machine, CodeInfoRepository, logManager); } [TearDown] diff --git a/src/Nethermind/Nethermind.Evm/ByteCodeBuilder.cs b/src/Nethermind/Nethermind.Evm/ByteCodeBuilder.cs index 3f1407b117c..a44ab9dae90 100644 --- a/src/Nethermind/Nethermind.Evm/ByteCodeBuilder.cs +++ b/src/Nethermind/Nethermind.Evm/ByteCodeBuilder.cs @@ -274,10 +274,10 @@ public Prepare PushData(string data) return this; } - public Prepare PushData(byte[] data) + public Prepare PushData(ReadOnlyMemory data) { _byteCode.Add((byte)(Instruction.PUSH1 + (byte)data.Length - 1)); - _byteCode.AddRange(data); + _byteCode.AddRange(data.Span); return this; } diff --git a/src/Nethermind/Nethermind.Evm/CodeInfoRepository.cs b/src/Nethermind/Nethermind.Evm/CodeInfoRepository.cs new file mode 100644 index 00000000000..48b67ddd52d --- /dev/null +++ b/src/Nethermind/Nethermind.Evm/CodeInfoRepository.cs @@ -0,0 +1,161 @@ +// SPDX-FileCopyrightText: 2023 Demerzel Solutions Limited +// SPDX-License-Identifier: LGPL-3.0-only + +using System; +using System.Collections.Frozen; +using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; +using System.Diagnostics; +using System.Threading; +using Nethermind.Core; +using Nethermind.Core.Caching; +using Nethermind.Core.Crypto; +using Nethermind.Core.Specs; +using Nethermind.Evm.CodeAnalysis; +using Nethermind.Evm.Precompiles; +using Nethermind.Evm.Precompiles.Bls; +using Nethermind.Evm.Precompiles.Snarks; +using Nethermind.State; + +namespace Nethermind.Evm; + +public class CodeInfoRepository : ICodeInfoRepository +{ + internal sealed class CodeLruCache + { + private const int CacheCount = 16; + private const int CacheMax = CacheCount - 1; + private readonly LruCacheLowObject[] _caches; + + public CodeLruCache() + { + _caches = new LruCacheLowObject[CacheCount]; + for (int i = 0; i < _caches.Length; i++) + { + // Cache per nibble to reduce contention as TxPool is very parallel + _caches[i] = new LruCacheLowObject(MemoryAllowance.CodeCacheSize / CacheCount, $"VM bytecodes {i}"); + } + } + + public CodeInfo? Get(in ValueHash256 codeHash) + { + LruCacheLowObject cache = _caches[GetCacheIndex(codeHash)]; + return cache.Get(codeHash); + } + + public bool Set(in ValueHash256 codeHash, CodeInfo codeInfo) + { + LruCacheLowObject 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 static FrozenDictionary InitializePrecompiledContracts() + { + return new Dictionary + { + [EcRecoverPrecompile.Address] = new(EcRecoverPrecompile.Instance), + [Sha256Precompile.Address] = new(Sha256Precompile.Instance), + [Ripemd160Precompile.Address] = new(Ripemd160Precompile.Instance), + [IdentityPrecompile.Address] = new(IdentityPrecompile.Instance), + + [Bn254AddPrecompile.Address] = new(Bn254AddPrecompile.Instance), + [Bn254MulPrecompile.Address] = new(Bn254MulPrecompile.Instance), + [Bn254PairingPrecompile.Address] = new(Bn254PairingPrecompile.Instance), + [ModExpPrecompile.Address] = new(ModExpPrecompile.Instance), + + [Blake2FPrecompile.Address] = new(Blake2FPrecompile.Instance), + + [G1AddPrecompile.Address] = new(G1AddPrecompile.Instance), + [G1MulPrecompile.Address] = new(G1MulPrecompile.Instance), + [G1MultiExpPrecompile.Address] = new(G1MultiExpPrecompile.Instance), + [G2AddPrecompile.Address] = new(G2AddPrecompile.Instance), + [G2MulPrecompile.Address] = new(G2MulPrecompile.Instance), + [G2MultiExpPrecompile.Address] = new(G2MultiExpPrecompile.Instance), + [PairingPrecompile.Address] = new(PairingPrecompile.Instance), + [MapToG1Precompile.Address] = new(MapToG1Precompile.Instance), + [MapToG2Precompile.Address] = new(MapToG2Precompile.Instance), + + [PointEvaluationPrecompile.Address] = new(PointEvaluationPrecompile.Instance), + }.ToFrozenDictionary(); + } + + public CodeInfo GetCachedCodeInfo(IWorldState worldState, Address codeSource, IReleaseSpec vmSpec) + { + if (codeSource.IsPrecompile(vmSpec)) + { + return _precompiles[codeSource]; + } + + CodeInfo? cachedCodeInfo = null; + ValueHash256 codeHash = worldState.GetCodeHash(codeSource); + if (codeHash == Keccak.OfAnEmptyString.ValueHash256) + { + cachedCodeInfo = CodeInfo.Empty; + } + + cachedCodeInfo ??= _codeCache.Get(codeHash); + if (cachedCodeInfo is null) + { + byte[]? code = worldState.GetCode(codeHash); + + if (code is null) + { + MissingCode(codeSource, codeHash); + } + + cachedCodeInfo = new CodeInfo(code); + cachedCodeInfo.AnalyseInBackgroundIfRequired(); + _codeCache.Set(codeHash, cachedCodeInfo); + } + else + { + Db.Metrics.IncrementCodeDbCache(); + } + + return cachedCodeInfo; + + [DoesNotReturn] + [StackTraceHidden] + static void MissingCode(Address codeSource, in ValueHash256 codeHash) + { + throw new NullReferenceException($"Code {codeHash} missing in the state for address {codeSource}"); + } + } + + public CodeInfo GetOrAdd(ValueHash256 codeHash, ReadOnlySpan initCode) + { + if (!_codeCache.TryGet(codeHash, out CodeInfo? codeInfo)) + { + codeInfo = new(initCode.ToArray()); + + // Prime the code cache as likely to be used by more txs + _codeCache.Set(codeHash, codeInfo); + } + + return codeInfo; + } + + + public void InsertCode(IWorldState state, ReadOnlyMemory code, Address codeOwner, IReleaseSpec spec) + { + CodeInfo codeInfo = new(code); + codeInfo.AnalyseInBackgroundIfRequired(); + + Hash256 codeHash = code.Length == 0 ? Keccak.OfAnEmptyString : Keccak.Compute(code.Span); + state.InsertCode(codeOwner, codeHash, code, spec); + _codeCache.Set(codeHash, codeInfo); + } +} diff --git a/src/Nethermind/Nethermind.Evm/ICodeInfoRepository.cs b/src/Nethermind/Nethermind.Evm/ICodeInfoRepository.cs new file mode 100644 index 00000000000..6fde3cbfe7f --- /dev/null +++ b/src/Nethermind/Nethermind.Evm/ICodeInfoRepository.cs @@ -0,0 +1,18 @@ +// SPDX-FileCopyrightText: 2023 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.State; + +namespace Nethermind.Evm; + +public interface ICodeInfoRepository +{ + CodeInfo GetCachedCodeInfo(IWorldState worldState, Address codeSource, IReleaseSpec vmSpec); + CodeInfo GetOrAdd(ValueHash256 codeHash, ReadOnlySpan initCode); + void InsertCode(IWorldState state, ReadOnlyMemory code, Address codeOwner, IReleaseSpec spec); +} diff --git a/src/Nethermind/Nethermind.Evm/IVirtualMachine.cs b/src/Nethermind/Nethermind.Evm/IVirtualMachine.cs index 8fc865a0d82..8099d6d7b6c 100644 --- a/src/Nethermind/Nethermind.Evm/IVirtualMachine.cs +++ b/src/Nethermind/Nethermind.Evm/IVirtualMachine.cs @@ -17,8 +17,5 @@ public interface IVirtualMachine { TransactionSubstate Run(EvmState state, IWorldState worldState, ITxTracer txTracer) where TTracingActions : struct, IIsTracing; - - CodeInfo GetCachedCodeInfo(IWorldState worldState, Address codeSource, IReleaseSpec spec); - void InsertCode(ReadOnlyMemory code, Address codeOwner, IReleaseSpec spec); } } diff --git a/src/Nethermind/Nethermind.Evm/Tracing/BlockReceiptsTracer.cs b/src/Nethermind/Nethermind.Evm/Tracing/BlockReceiptsTracer.cs index f319531a9f0..4a8587a7690 100644 --- a/src/Nethermind/Nethermind.Evm/Tracing/BlockReceiptsTracer.cs +++ b/src/Nethermind/Nethermind.Evm/Tracing/BlockReceiptsTracer.cs @@ -12,6 +12,7 @@ namespace Nethermind.Evm.Tracing; public class BlockReceiptsTracer : IBlockTracer, ITxTracer, IJournal, ITxTracerWrapper { + private IBlockTracer _otherTracer = NullBlockTracer.Instance; protected Block Block = null!; public bool IsTracingReceipt => true; public bool IsTracingActions => _currentTxTracer.IsTracingActions; @@ -29,8 +30,6 @@ public class BlockReceiptsTracer : IBlockTracer, ITxTracer, IJournal, ITxTr public bool IsTracingFees => _currentTxTracer.IsTracingFees; public bool IsTracingLogs => _currentTxTracer.IsTracingLogs; - private IBlockTracer _otherTracer = NullBlockTracer.Instance; - public void MarkAsSuccess(Address recipient, long gasSpent, byte[] output, LogEntry[] logs, Hash256? stateRoot = null) { _txReceipts.Add(BuildReceipt(recipient, gasSpent, StatusCode.Success, logs, stateRoot)); diff --git a/src/Nethermind/Nethermind.Evm/Tracing/CancellationBlockTracer.cs b/src/Nethermind/Nethermind.Evm/Tracing/CancellationBlockTracer.cs index 2fabb220ca2..311a2fedd26 100644 --- a/src/Nethermind/Nethermind.Evm/Tracing/CancellationBlockTracer.cs +++ b/src/Nethermind/Nethermind.Evm/Tracing/CancellationBlockTracer.cs @@ -7,54 +7,46 @@ namespace Nethermind.Evm.Tracing { - public class CancellationBlockTracer : IBlockTracer + public class CancellationBlockTracer(IBlockTracer innerTracer, CancellationToken token = default) : IBlockTracer { - private readonly IBlockTracer _innerTracer; - private readonly CancellationToken _token; private bool _isTracingRewards; - public CancellationBlockTracer(IBlockTracer innerTracer, CancellationToken token = default) - { - _innerTracer = innerTracer; - _token = token; - } - public bool IsTracingRewards { - get => _isTracingRewards || _innerTracer.IsTracingRewards; + get => _isTracingRewards || innerTracer.IsTracingRewards; set => _isTracingRewards = value; } public void ReportReward(Address author, string rewardType, UInt256 rewardValue) { - _token.ThrowIfCancellationRequested(); - if (_innerTracer.IsTracingRewards) + token.ThrowIfCancellationRequested(); + if (innerTracer.IsTracingRewards) { - _innerTracer.ReportReward(author, rewardType, rewardValue); + innerTracer.ReportReward(author, rewardType, rewardValue); } } public void StartNewBlockTrace(Block block) { - _token.ThrowIfCancellationRequested(); - _innerTracer.StartNewBlockTrace(block); + token.ThrowIfCancellationRequested(); + innerTracer.StartNewBlockTrace(block); } public ITxTracer StartNewTxTrace(Transaction? tx) { - _token.ThrowIfCancellationRequested(); - return _innerTracer.StartNewTxTrace(tx).WithCancellation(_token); + token.ThrowIfCancellationRequested(); + return innerTracer.StartNewTxTrace(tx).WithCancellation(token); } public void EndTxTrace() { - _token.ThrowIfCancellationRequested(); - _innerTracer.EndTxTrace(); + token.ThrowIfCancellationRequested(); + innerTracer.EndTxTrace(); } public void EndBlockTrace() { - _innerTracer.EndBlockTrace(); + innerTracer.EndBlockTrace(); } } } diff --git a/src/Nethermind/Nethermind.Evm/Tracing/CancellationTxTracer.cs b/src/Nethermind/Nethermind.Evm/Tracing/CancellationTxTracer.cs index bc6badef0df..5bfd05b9d4a 100644 --- a/src/Nethermind/Nethermind.Evm/Tracing/CancellationTxTracer.cs +++ b/src/Nethermind/Nethermind.Evm/Tracing/CancellationTxTracer.cs @@ -4,16 +4,15 @@ using System; using System.Collections.Generic; using System.Threading; +using System.Linq; using Nethermind.Core; using Nethermind.Core.Crypto; using Nethermind.Int256; namespace Nethermind.Evm.Tracing; -public class CancellationTxTracer : ITxTracer, ITxTracerWrapper +public class CancellationTxTracer(ITxTracer innerTracer, CancellationToken token = default) : ITxTracer, ITxTracerWrapper { - private readonly ITxTracer _innerTracer; - private readonly CancellationToken _token; private readonly bool _isTracingReceipt; private readonly bool _isTracingActions; private readonly bool _isTracingOpLevelStorage; @@ -29,435 +28,431 @@ public class CancellationTxTracer : ITxTracer, ITxTracerWrapper private readonly bool _isTracingFees; private readonly bool _isTracingOpLevelLogs; - public ITxTracer InnerTracer => _innerTracer; - - public CancellationTxTracer(ITxTracer innerTracer, CancellationToken token = default) - { - _innerTracer = innerTracer; - _token = token; - } + public ITxTracer InnerTracer => innerTracer; public bool IsCancelable => true; - public bool IsCancelled => _token.IsCancellationRequested; + public bool IsCancelled => token.IsCancellationRequested; public bool IsTracingReceipt { - get => _isTracingReceipt || _innerTracer.IsTracingReceipt; + get => _isTracingReceipt || innerTracer.IsTracingReceipt; init => _isTracingReceipt = value; } public bool IsTracingActions { - get => _isTracingActions || _innerTracer.IsTracingActions; + get => _isTracingActions || innerTracer.IsTracingActions; init => _isTracingActions = value; } public bool IsTracingOpLevelStorage { - get => _isTracingOpLevelStorage || _innerTracer.IsTracingOpLevelStorage; + get => _isTracingOpLevelStorage || innerTracer.IsTracingOpLevelStorage; init => _isTracingOpLevelStorage = value; } public bool IsTracingMemory { - get => _isTracingMemory || _innerTracer.IsTracingMemory; + get => _isTracingMemory || innerTracer.IsTracingMemory; init => _isTracingMemory = value; } public bool IsTracingInstructions { - get => _isTracingInstructions || _innerTracer.IsTracingInstructions; + get => _isTracingInstructions || innerTracer.IsTracingInstructions; init => _isTracingInstructions = value; } public bool IsTracingRefunds { - get => _isTracingRefunds || _innerTracer.IsTracingRefunds; + get => _isTracingRefunds || innerTracer.IsTracingRefunds; init => _isTracingRefunds = value; } public bool IsTracingCode { - get => _isTracingCode || _innerTracer.IsTracingCode; + get => _isTracingCode || innerTracer.IsTracingCode; init => _isTracingCode = value; } public bool IsTracingStack { - get => _isTracingStack || _innerTracer.IsTracingStack; + get => _isTracingStack || innerTracer.IsTracingStack; init => _isTracingStack = value; } public bool IsTracingState { - get => _isTracingState || _innerTracer.IsTracingState; + get => _isTracingState || innerTracer.IsTracingState; init => _isTracingState = value; } public bool IsTracingStorage { - get => _isTracingStorage || _innerTracer.IsTracingStorage; + get => _isTracingStorage || innerTracer.IsTracingStorage; init => _isTracingStorage = value; } public bool IsTracingBlockHash { - get => _isTracingBlockHash || _innerTracer.IsTracingBlockHash; + get => _isTracingBlockHash || innerTracer.IsTracingBlockHash; init => _isTracingBlockHash = value; } public bool IsTracingAccess { - get => _isTracingBlockAccess || _innerTracer.IsTracingAccess; + get => _isTracingBlockAccess || innerTracer.IsTracingAccess; init => _isTracingBlockAccess = value; } public bool IsTracingFees { - get => _isTracingFees || _innerTracer.IsTracingFees; + get => _isTracingFees || innerTracer.IsTracingFees; init => _isTracingFees = value; } + public bool IsTracingLogs { - get => _isTracingOpLevelLogs || _innerTracer.IsTracingLogs; + get => _isTracingOpLevelLogs || innerTracer.IsTracingLogs; init => _isTracingOpLevelLogs = value; } + public void ReportBalanceChange(Address address, UInt256? before, UInt256? after) { - _token.ThrowIfCancellationRequested(); - if (_innerTracer.IsTracingState) + token.ThrowIfCancellationRequested(); + if (innerTracer.IsTracingState) { - _innerTracer.ReportBalanceChange(address, before, after); + innerTracer.ReportBalanceChange(address, before, after); } } public void ReportCodeChange(Address address, byte[] before, byte[] after) { - _token.ThrowIfCancellationRequested(); - if (_innerTracer.IsTracingState) + token.ThrowIfCancellationRequested(); + if (innerTracer.IsTracingState) { - _innerTracer.ReportCodeChange(address, before, after); + innerTracer.ReportCodeChange(address, before, after); } } public void ReportNonceChange(Address address, UInt256? before, UInt256? after) { - _token.ThrowIfCancellationRequested(); - if (_innerTracer.IsTracingState) + token.ThrowIfCancellationRequested(); + if (innerTracer.IsTracingState) { - _innerTracer.ReportNonceChange(address, before, after); + innerTracer.ReportNonceChange(address, before, after); } } public void ReportAccountRead(Address address) { - _token.ThrowIfCancellationRequested(); - if (_innerTracer.IsTracingState) + token.ThrowIfCancellationRequested(); + if (innerTracer.IsTracingState) { - _innerTracer.ReportAccountRead(address); + innerTracer.ReportAccountRead(address); } } public void ReportStorageChange(in StorageCell storageCell, byte[] before, byte[] after) { - _token.ThrowIfCancellationRequested(); - if (_innerTracer.IsTracingStorage) + token.ThrowIfCancellationRequested(); + if (innerTracer.IsTracingStorage) { - _innerTracer.ReportStorageChange(storageCell, before, after); + innerTracer.ReportStorageChange(storageCell, before, after); } } public void ReportStorageRead(in StorageCell storageCell) { - _token.ThrowIfCancellationRequested(); - if (_innerTracer.IsTracingStorage) + token.ThrowIfCancellationRequested(); + if (innerTracer.IsTracingStorage) { - _innerTracer.ReportStorageRead(storageCell); + innerTracer.ReportStorageRead(storageCell); } } public void MarkAsSuccess(Address recipient, long gasSpent, byte[] output, LogEntry[] logs, Hash256? stateRoot = null) { - _token.ThrowIfCancellationRequested(); - if (_innerTracer.IsTracingReceipt) + token.ThrowIfCancellationRequested(); + if (innerTracer.IsTracingReceipt) { - _innerTracer.MarkAsSuccess(recipient, gasSpent, output, logs, stateRoot); + innerTracer.MarkAsSuccess(recipient, gasSpent, output, logs, stateRoot); } } public void MarkAsFailed(Address recipient, long gasSpent, byte[] output, string error, Hash256? stateRoot = null) { - _token.ThrowIfCancellationRequested(); - if (_innerTracer.IsTracingReceipt) + token.ThrowIfCancellationRequested(); + if (innerTracer.IsTracingReceipt) { - _innerTracer.MarkAsFailed(recipient, gasSpent, output, error, stateRoot); + innerTracer.MarkAsFailed(recipient, gasSpent, output, error, stateRoot); } } public void StartOperation(int pc, Instruction opcode, long gas, in ExecutionEnvironment env) { - _token.ThrowIfCancellationRequested(); - if (_innerTracer.IsTracingInstructions) + token.ThrowIfCancellationRequested(); + if (innerTracer.IsTracingInstructions) { - _innerTracer.StartOperation(pc, opcode, gas, env); + innerTracer.StartOperation(pc, opcode, gas, env); } } public void ReportOperationError(EvmExceptionType error) { - _token.ThrowIfCancellationRequested(); - if (_innerTracer.IsTracingInstructions) + token.ThrowIfCancellationRequested(); + if (innerTracer.IsTracingInstructions) { - _innerTracer.ReportOperationError(error); + innerTracer.ReportOperationError(error); } } public void ReportOperationRemainingGas(long gas) { - _token.ThrowIfCancellationRequested(); - if (_innerTracer.IsTracingInstructions) + token.ThrowIfCancellationRequested(); + if (innerTracer.IsTracingInstructions) { - _innerTracer.ReportOperationRemainingGas(gas); + innerTracer.ReportOperationRemainingGas(gas); } } public void ReportLog(LogEntry log) { - _token.ThrowIfCancellationRequested(); - if (_innerTracer.IsTracingLogs) + token.ThrowIfCancellationRequested(); + if (innerTracer.IsTracingLogs) { - _innerTracer.ReportLog(log); + innerTracer.ReportLog(log); } } public void SetOperationStack(TraceStack stack) { - _token.ThrowIfCancellationRequested(); - if (_innerTracer.IsTracingStack) + token.ThrowIfCancellationRequested(); + if (innerTracer.IsTracingStack) { - _innerTracer.SetOperationStack(stack); + innerTracer.SetOperationStack(stack); } } public void ReportStackPush(in ReadOnlySpan stackItem) { - _token.ThrowIfCancellationRequested(); - if (_innerTracer.IsTracingInstructions) + token.ThrowIfCancellationRequested(); + if (innerTracer.IsTracingInstructions) { - _innerTracer.ReportStackPush(stackItem); + innerTracer.ReportStackPush(stackItem); } } public void ReportStackPush(in ZeroPaddedSpan stackItem) { - _token.ThrowIfCancellationRequested(); - if (_innerTracer.IsTracingInstructions) + token.ThrowIfCancellationRequested(); + if (innerTracer.IsTracingInstructions) { - _innerTracer.ReportStackPush(stackItem); + innerTracer.ReportStackPush(stackItem); } } public void ReportStackPush(byte stackItem) { - _token.ThrowIfCancellationRequested(); - if (_innerTracer.IsTracingInstructions) + token.ThrowIfCancellationRequested(); + if (innerTracer.IsTracingInstructions) { - _innerTracer.ReportStackPush(stackItem); + innerTracer.ReportStackPush(stackItem); } } public void SetOperationMemory(TraceMemory memoryTrace) { - _token.ThrowIfCancellationRequested(); - if (_innerTracer.IsTracingMemory) + token.ThrowIfCancellationRequested(); + if (innerTracer.IsTracingMemory) { - _innerTracer.SetOperationMemory(memoryTrace); + innerTracer.SetOperationMemory(memoryTrace); } } public void SetOperationMemorySize(ulong newSize) { - _token.ThrowIfCancellationRequested(); - if (_innerTracer.IsTracingMemory) + token.ThrowIfCancellationRequested(); + if (innerTracer.IsTracingMemory) { - _innerTracer.SetOperationMemorySize(newSize); + innerTracer.SetOperationMemorySize(newSize); } } public void ReportMemoryChange(long offset, in ReadOnlySpan data) { - _token.ThrowIfCancellationRequested(); - if (_innerTracer.IsTracingInstructions) + token.ThrowIfCancellationRequested(); + if (innerTracer.IsTracingInstructions) { - _innerTracer.ReportMemoryChange(offset, data); + innerTracer.ReportMemoryChange(offset, data); } } public void ReportMemoryChange(long offset, in ZeroPaddedSpan data) { - _token.ThrowIfCancellationRequested(); - if (_innerTracer.IsTracingInstructions) + token.ThrowIfCancellationRequested(); + if (innerTracer.IsTracingInstructions) { - _innerTracer.ReportMemoryChange(offset, data); + innerTracer.ReportMemoryChange(offset, data); } } public void ReportMemoryChange(long offset, byte data) { - _token.ThrowIfCancellationRequested(); - if (_innerTracer.IsTracingInstructions) + token.ThrowIfCancellationRequested(); + if (innerTracer.IsTracingInstructions) { - _innerTracer.ReportMemoryChange(offset, data); + innerTracer.ReportMemoryChange(offset, data); } } public void ReportStorageChange(in ReadOnlySpan key, in ReadOnlySpan value) { - _token.ThrowIfCancellationRequested(); - if (_innerTracer.IsTracingInstructions) + token.ThrowIfCancellationRequested(); + if (innerTracer.IsTracingInstructions) { - _innerTracer.ReportStorageChange(key, value); + innerTracer.ReportStorageChange(key, value); } } public void SetOperationStorage(Address address, UInt256 storageIndex, ReadOnlySpan newValue, ReadOnlySpan currentValue) { - _token.ThrowIfCancellationRequested(); - if (_innerTracer.IsTracingOpLevelStorage) + token.ThrowIfCancellationRequested(); + if (innerTracer.IsTracingOpLevelStorage) { - _innerTracer.SetOperationStorage(address, storageIndex, newValue, currentValue); + innerTracer.SetOperationStorage(address, storageIndex, newValue, currentValue); } } public void LoadOperationStorage(Address address, UInt256 storageIndex, ReadOnlySpan value) { - _token.ThrowIfCancellationRequested(); - if (_innerTracer.IsTracingOpLevelStorage) + token.ThrowIfCancellationRequested(); + if (innerTracer.IsTracingOpLevelStorage) { - _innerTracer.LoadOperationStorage(address, storageIndex, value); + innerTracer.LoadOperationStorage(address, storageIndex, value); } } public void ReportSelfDestruct(Address address, UInt256 balance, Address refundAddress) { - _token.ThrowIfCancellationRequested(); - if (_innerTracer.IsTracingActions) + token.ThrowIfCancellationRequested(); + if (innerTracer.IsTracingActions) { - _innerTracer.ReportSelfDestruct(address, balance, refundAddress); + innerTracer.ReportSelfDestruct(address, balance, refundAddress); } } public void ReportAction(long gas, UInt256 value, Address from, Address to, ReadOnlyMemory input, ExecutionType callType, bool isPrecompileCall = false) { - _token.ThrowIfCancellationRequested(); - if (_innerTracer.IsTracingActions) + token.ThrowIfCancellationRequested(); + if (innerTracer.IsTracingActions) { - _innerTracer.ReportAction(gas, value, from, to, input, callType, isPrecompileCall); + innerTracer.ReportAction(gas, value, from, to, input, callType, isPrecompileCall); } } public void ReportActionEnd(long gas, ReadOnlyMemory output) { - _token.ThrowIfCancellationRequested(); - if (_innerTracer.IsTracingActions) + token.ThrowIfCancellationRequested(); + if (innerTracer.IsTracingActions) { - _innerTracer.ReportActionEnd(gas, output); + innerTracer.ReportActionEnd(gas, output); } } public void ReportActionError(EvmExceptionType evmExceptionType) { - _token.ThrowIfCancellationRequested(); - if (_innerTracer.IsTracingActions) + token.ThrowIfCancellationRequested(); + if (innerTracer.IsTracingActions) { - _innerTracer.ReportActionError(evmExceptionType); + innerTracer.ReportActionError(evmExceptionType); } } public void ReportActionRevert(long gasLeft, ReadOnlyMemory output) { - _token.ThrowIfCancellationRequested(); - if (_innerTracer.IsTracingActions) + token.ThrowIfCancellationRequested(); + if (innerTracer.IsTracingActions) { - _innerTracer.ReportActionRevert(gasLeft, output); + innerTracer.ReportActionRevert(gasLeft, output); } } public void ReportActionEnd(long gas, Address deploymentAddress, ReadOnlyMemory deployedCode) { - _token.ThrowIfCancellationRequested(); - if (_innerTracer.IsTracingActions) + token.ThrowIfCancellationRequested(); + if (innerTracer.IsTracingActions) { - _innerTracer.ReportActionEnd(gas, deploymentAddress, deployedCode); + innerTracer.ReportActionEnd(gas, deploymentAddress, deployedCode); } } public void ReportBlockHash(Hash256 blockHash) { - _token.ThrowIfCancellationRequested(); - if (_innerTracer.IsTracingBlockHash) + token.ThrowIfCancellationRequested(); + if (innerTracer.IsTracingBlockHash) { - _innerTracer.ReportBlockHash(blockHash); + innerTracer.ReportBlockHash(blockHash); } } public void ReportByteCode(ReadOnlyMemory byteCode) { - _token.ThrowIfCancellationRequested(); - if (_innerTracer.IsTracingCode) + token.ThrowIfCancellationRequested(); + if (innerTracer.IsTracingCode) { - _innerTracer.ReportByteCode(byteCode); + innerTracer.ReportByteCode(byteCode); } } public void ReportGasUpdateForVmTrace(long refund, long gasAvailable) { - _token.ThrowIfCancellationRequested(); - if (_innerTracer.IsTracingInstructions) + token.ThrowIfCancellationRequested(); + if (innerTracer.IsTracingInstructions) { - _innerTracer.ReportGasUpdateForVmTrace(refund, gasAvailable); + innerTracer.ReportGasUpdateForVmTrace(refund, gasAvailable); } } public void ReportRefund(long refund) { - _token.ThrowIfCancellationRequested(); - if (_innerTracer.IsTracingRefunds) + token.ThrowIfCancellationRequested(); + if (innerTracer.IsTracingRefunds) { - _innerTracer.ReportRefund(refund); + innerTracer.ReportRefund(refund); } } public void ReportExtraGasPressure(long extraGasPressure) { - _token.ThrowIfCancellationRequested(); - if (_innerTracer.IsTracingRefunds) + token.ThrowIfCancellationRequested(); + if (innerTracer.IsTracingRefunds) { - _innerTracer.ReportExtraGasPressure(extraGasPressure); + innerTracer.ReportExtraGasPressure(extraGasPressure); } } public void ReportAccess(IReadOnlySet
accessedAddresses, IReadOnlySet accessedStorageCells) { - _token.ThrowIfCancellationRequested(); - if (_innerTracer.IsTracingAccess) + token.ThrowIfCancellationRequested(); + if (innerTracer.IsTracingAccess) { - _innerTracer.ReportAccess(accessedAddresses, accessedStorageCells); + innerTracer.ReportAccess(accessedAddresses, accessedStorageCells); } } public void ReportFees(UInt256 fees, UInt256 burntFees) { - _token.ThrowIfCancellationRequested(); - if (_innerTracer.IsTracingFees) + token.ThrowIfCancellationRequested(); + if (innerTracer.IsTracingFees) { - _innerTracer.ReportFees(fees, burntFees); + innerTracer.ReportFees(fees, burntFees); } } public void Dispose() { - _innerTracer.Dispose(); + innerTracer.Dispose(); } } diff --git a/src/Nethermind/Nethermind.Evm/Tracing/ITxLogsMutator.cs b/src/Nethermind/Nethermind.Evm/Tracing/ITxLogsMutator.cs new file mode 100644 index 00000000000..fad85dc3b1f --- /dev/null +++ b/src/Nethermind/Nethermind.Evm/Tracing/ITxLogsMutator.cs @@ -0,0 +1,14 @@ +// SPDX-FileCopyrightText: 2024 Demerzel Solutions Limited +// SPDX-License-Identifier: LGPL-3.0-only + +using System.Collections.Generic; +using Nethermind.Core; + +namespace Nethermind.Evm.Tracing; + +public interface ITxLogsMutator +{ + bool IsMutatingLogs { get; } + + void SetLogsToMutate(ICollection logsToMutate); +} diff --git a/src/Nethermind/Nethermind.Evm/TransactionProcessing/BuildUpTransactionProcessorAdapter.cs b/src/Nethermind/Nethermind.Evm/TransactionProcessing/BuildUpTransactionProcessorAdapter.cs index d0215a41aa3..2ac0a3b8fcb 100644 --- a/src/Nethermind/Nethermind.Evm/TransactionProcessing/BuildUpTransactionProcessorAdapter.cs +++ b/src/Nethermind/Nethermind.Evm/TransactionProcessing/BuildUpTransactionProcessorAdapter.cs @@ -6,16 +6,9 @@ namespace Nethermind.Evm.TransactionProcessing { - public class BuildUpTransactionProcessorAdapter : ITransactionProcessorAdapter + public class BuildUpTransactionProcessorAdapter(ITransactionProcessor transactionProcessor) : ITransactionProcessorAdapter { - private readonly ITransactionProcessor _transactionProcessor; - - public BuildUpTransactionProcessorAdapter(ITransactionProcessor transactionProcessor) - { - _transactionProcessor = transactionProcessor; - } - public TransactionResult Execute(Transaction transaction, in BlockExecutionContext blkCtx, ITxTracer txTracer) => - _transactionProcessor.BuildUp(transaction, in blkCtx, txTracer); + transactionProcessor.BuildUp(transaction, in blkCtx, txTracer); } } diff --git a/src/Nethermind/Nethermind.Evm/TransactionProcessing/TransactionProcessor.cs b/src/Nethermind/Nethermind.Evm/TransactionProcessing/TransactionProcessor.cs index 5194bb46269..95744638d44 100644 --- a/src/Nethermind/Nethermind.Evm/TransactionProcessing/TransactionProcessor.cs +++ b/src/Nethermind/Nethermind.Evm/TransactionProcessing/TransactionProcessor.cs @@ -7,8 +7,6 @@ using System.IO; using System.Linq; using System.Runtime.CompilerServices; -using System.Threading; - using Nethermind.Core; using Nethermind.Core.Crypto; using Nethermind.Core.Specs; @@ -33,6 +31,7 @@ public class TransactionProcessor : ITransactionProcessor protected ISpecProvider SpecProvider { get; private init; } protected IWorldState WorldState { get; private init; } protected IVirtualMachine VirtualMachine { get; private init; } + private readonly ICodeInfoRepository _codeInfoRepository; [Flags] protected enum ExecutionOptions @@ -67,17 +66,21 @@ public TransactionProcessor( ISpecProvider? specProvider, IWorldState? worldState, IVirtualMachine? virtualMachine, + ICodeInfoRepository? codeInfoRepository, ILogManager? logManager) { ArgumentNullException.ThrowIfNull(logManager, nameof(logManager)); ArgumentNullException.ThrowIfNull(specProvider, nameof(specProvider)); ArgumentNullException.ThrowIfNull(worldState, nameof(worldState)); ArgumentNullException.ThrowIfNull(virtualMachine, nameof(virtualMachine)); + ArgumentNullException.ThrowIfNull(codeInfoRepository, nameof(codeInfoRepository)); Logger = logManager.GetClassLogger(); SpecProvider = specProvider; WorldState = worldState; VirtualMachine = virtualMachine; + _codeInfoRepository = codeInfoRepository; + Ecdsa = new EthereumEcdsa(specProvider.ChainId, logManager); } @@ -409,7 +412,7 @@ protected ExecutionEnvironment BuildExecutionEnvironment( CodeInfo codeInfo = tx.IsContractCreation ? new(tx.Data ?? Memory.Empty) - : VirtualMachine.GetCachedCodeInfo(WorldState, recipient, spec); + : _codeInfoRepository.GetCachedCodeInfo(WorldState, recipient, spec); codeInfo.AnalyseInBackgroundIfRequired(); @@ -521,7 +524,7 @@ protected void ExecuteEvmCall( if (unspentGas >= codeDepositGasCost) { var code = substate.Output.ToArray(); - VirtualMachine.InsertCode(code, env.ExecutingAccount, spec); + _codeInfoRepository.InsertCode(WorldState, code, env.ExecutingAccount, spec); unspentGas -= codeDepositGasCost; } @@ -579,7 +582,7 @@ protected void PrepareAccountForContractDeployment(Address contractAddress, IRel { if (WorldState.AccountExists(contractAddress)) { - CodeInfo codeInfo = VirtualMachine.GetCachedCodeInfo(WorldState, contractAddress, spec); + CodeInfo codeInfo = _codeInfoRepository.GetCachedCodeInfo(WorldState, contractAddress, spec); bool codeIsNotEmpty = codeInfo.MachineCode.Length != 0; bool accountNonceIsNotZero = WorldState.GetNonce(contractAddress) != 0; diff --git a/src/Nethermind/Nethermind.Evm/VirtualMachine.cs b/src/Nethermind/Nethermind.Evm/VirtualMachine.cs index 7f681fdb560..12ced688ccb 100644 --- a/src/Nethermind/Nethermind.Evm/VirtualMachine.cs +++ b/src/Nethermind/Nethermind.Evm/VirtualMachine.cs @@ -6,23 +6,18 @@ using System.Collections.Generic; using System.Runtime.CompilerServices; using Nethermind.Core; -using Nethermind.Core.Caching; using Nethermind.Core.Crypto; using Nethermind.Core.Extensions; using Nethermind.Core.Specs; using Nethermind.Evm.CodeAnalysis; using Nethermind.Evm.Precompiles; -using Nethermind.Evm.Precompiles.Bls; -using Nethermind.Evm.Precompiles.Snarks; using Nethermind.Evm.Tracing; using Nethermind.Logging; using Nethermind.State; using System.Diagnostics.CodeAnalysis; -using System.Diagnostics; using System.Runtime.Intrinsics; using static Nethermind.Evm.VirtualMachine; using static System.Runtime.CompilerServices.Unsafe; -using ValueHash256 = Nethermind.Core.Crypto.ValueHash256; #if DEBUG using Nethermind.Evm.Tracing.Debugger; @@ -36,7 +31,7 @@ namespace Nethermind.Evm; using System.Linq; using System.Runtime.InteropServices; using System.Threading; -using System.Threading.Tasks; + using Int256; using Nethermind.Core.Collections; @@ -44,15 +39,12 @@ namespace Nethermind.Evm; public class VirtualMachine : IVirtualMachine { public const int MaxCallDepth = 1024; - internal static FrozenDictionary PrecompileCode { get; } = InitializePrecompiledContracts(); - internal static CodeLruCache CodeCache { get; } = new(); - - private readonly static UInt256 P255Int = (UInt256)System.Numerics.BigInteger.Pow(2, 255); + private static readonly UInt256 P255Int = (UInt256)System.Numerics.BigInteger.Pow(2, 255); internal static ref readonly UInt256 P255 => ref P255Int; internal static readonly UInt256 BigInt256 = 256; internal static readonly UInt256 BigInt32 = 32; - internal static readonly byte[] BytesZero = { 0 }; + internal static readonly byte[] BytesZero = [0]; internal static readonly byte[] BytesZero32 = { @@ -75,61 +67,19 @@ public class VirtualMachine : IVirtualMachine public VirtualMachine( IBlockhashProvider? blockhashProvider, ISpecProvider? specProvider, + ICodeInfoRepository codeInfoRepository, ILogManager? logManager) { ILogger logger = logManager?.GetClassLogger() ?? throw new ArgumentNullException(nameof(logManager)); - if (!logger.IsTrace) - { - _evm = new VirtualMachine(blockhashProvider, specProvider, logger); - } - else - { - _evm = new VirtualMachine(blockhashProvider, specProvider, logger); - } - } - - public CodeInfo GetCachedCodeInfo(IWorldState worldState, Address codeSource, IReleaseSpec spec) - => _evm.GetCachedCodeInfo(worldState, codeSource, spec); - - public void InsertCode(ReadOnlyMemory code, Address codeOwner, IReleaseSpec spec) - { - _evm.InsertCode(code, codeOwner, spec); + _evm = logger.IsTrace + ? new VirtualMachine(blockhashProvider, specProvider, codeInfoRepository, logger) + : new VirtualMachine(blockhashProvider, specProvider, codeInfoRepository, logger); } public TransactionSubstate Run(EvmState state, IWorldState worldState, ITxTracer txTracer) where TTracingActions : struct, VirtualMachine.IIsTracing => _evm.Run(state, worldState, txTracer); - private static FrozenDictionary InitializePrecompiledContracts() - { - return new Dictionary - { - [EcRecoverPrecompile.Address] = new(EcRecoverPrecompile.Instance), - [Sha256Precompile.Address] = new(Sha256Precompile.Instance), - [Ripemd160Precompile.Address] = new(Ripemd160Precompile.Instance), - [IdentityPrecompile.Address] = new(IdentityPrecompile.Instance), - - [Bn254AddPrecompile.Address] = new(Bn254AddPrecompile.Instance), - [Bn254MulPrecompile.Address] = new(Bn254MulPrecompile.Instance), - [Bn254PairingPrecompile.Address] = new(Bn254PairingPrecompile.Instance), - [ModExpPrecompile.Address] = new(ModExpPrecompile.Instance), - - [Blake2FPrecompile.Address] = new(Blake2FPrecompile.Instance), - - [G1AddPrecompile.Address] = new(G1AddPrecompile.Instance), - [G1MulPrecompile.Address] = new(G1MulPrecompile.Instance), - [G1MultiExpPrecompile.Address] = new(G1MultiExpPrecompile.Instance), - [G2AddPrecompile.Address] = new(G2AddPrecompile.Instance), - [G2MulPrecompile.Address] = new(G2MulPrecompile.Instance), - [G2MultiExpPrecompile.Address] = new(G2MultiExpPrecompile.Instance), - [PairingPrecompile.Address] = new(PairingPrecompile.Instance), - [MapToG1Precompile.Address] = new(MapToG1Precompile.Instance), - [MapToG2Precompile.Address] = new(MapToG2Precompile.Instance), - - [PointEvaluationPrecompile.Address] = new(PointEvaluationPrecompile.Instance), - }.ToFrozenDictionary(); - } - internal readonly ref struct CallResult { public static CallResult InvalidSubroutineEntry => new(EvmExceptionType.InvalidSubroutineEntry); @@ -184,37 +134,6 @@ public CallResult(ReadOnlyMemory output, bool? precompileSuccess, bool sho public interface IIsTracing { } public readonly struct NotTracing : IIsTracing { } public readonly struct IsTracing : IIsTracing { } - - internal sealed class CodeLruCache - { - private const int CacheCount = 16; - private const int CacheMax = CacheCount - 1; - private readonly LruCacheLowObject[] _caches; - - public CodeLruCache() - { - _caches = new LruCacheLowObject[CacheCount]; - for (int i = 0; i < _caches.Length; i++) - { - // Cache per nibble to reduce contention as TxPool is very parallel - _caches[i] = new LruCacheLowObject(MemoryAllowance.CodeCacheSize / CacheCount, $"VM bytecodes {i}"); - } - } - - public CodeInfo Get(in ValueHash256 codeHash) - { - var cache = _caches[GetCacheIndex(codeHash)]; - return cache.Get(codeHash); - } - - public bool Set(in ValueHash256 codeHash, CodeInfo codeInfo) - { - var cache = _caches[GetCacheIndex(codeHash)]; - return cache.Set(codeHash, codeInfo); - } - - private static int GetCacheIndex(in ValueHash256 codeHash) => codeHash.Bytes[^1] & CacheMax; - } } internal sealed class VirtualMachine : IVirtualMachine where TLogger : struct, IIsTracing @@ -225,20 +144,23 @@ internal sealed class VirtualMachine : IVirtualMachine where TLogger : private readonly ISpecProvider _specProvider; private readonly ILogger _logger; private IWorldState _worldState; - 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, - ILogger logger) + ICodeInfoRepository codeInfoRepository, + ILogger? logger) { - _logger = 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(); } @@ -292,18 +214,21 @@ public TransactionSubstate Run(EvmState state, IWorldState worl { if (typeof(TTracingActions) == typeof(IsTracing) && !currentState.IsContinuation) { - _txTracer.ReportAction(currentState.GasAvailable, currentState.Env.Value, currentState.From, currentState.To, currentState.ExecutionType.IsAnyCreate() ? currentState.Env.CodeInfo.MachineCode : currentState.Env.InputData, currentState.ExecutionType); + _txTracer.ReportAction(currentState.GasAvailable, + currentState.Env.Value, + currentState.From, + currentState.To, + currentState.ExecutionType.IsAnyCreate() + ? currentState.Env.CodeInfo.MachineCode + : currentState.Env.InputData, + currentState.ExecutionType); + if (_txTracer.IsTracingCode) _txTracer.ReportByteCode(currentState.Env.CodeInfo.MachineCode); } - if (!_txTracer.IsTracingInstructions) - { - callResult = ExecuteCall(currentState, previousCallResult, previousCallOutput, previousCallOutputDestination, spec); - } - else - { - callResult = ExecuteCall(currentState, previousCallResult, previousCallOutput, previousCallOutputDestination, spec); - } + callResult = !_txTracer.IsTracingInstructions + ? ExecuteCall(currentState, previousCallResult, previousCallOutput, previousCallOutputDestination, spec) + : ExecuteCall(currentState, previousCallResult, previousCallOutput, previousCallOutputDestination, spec); if (!callResult.IsReturn) { @@ -420,7 +345,7 @@ public TransactionSubstate Run(EvmState state, IWorldState worl if (gasAvailableForCodeDeposit >= codeDepositGasCost && !invalidCode) { ReadOnlyMemory code = callResult.Output; - InsertCode(code, callCodeOwner, spec); + _codeInfoRepository.InsertCode(_state, code, callCodeOwner, spec); currentState.GasAvailable -= codeDepositGasCost; @@ -529,16 +454,6 @@ public TransactionSubstate Run(EvmState state, IWorldState worl } } - public void InsertCode(ReadOnlyMemory code, Address callCodeOwner, IReleaseSpec spec) - { - var codeInfo = new CodeInfo(code); - codeInfo.AnalyseInBackgroundIfRequired(); - - Hash256 codeHash = code.Length == 0 ? Keccak.OfAnEmptyString : Keccak.Compute(code.Span); - _state.InsertCode(callCodeOwner, codeHash, code, spec); - CodeCache.Set(codeHash, codeInfo); - } - private void RevertParityTouchBugAccount(IReleaseSpec spec) { if (_parityTouchBugAccount.ShouldDelete) @@ -552,50 +467,6 @@ private void RevertParityTouchBugAccount(IReleaseSpec spec) } } - public CodeInfo GetCachedCodeInfo(IWorldState worldState, Address codeSource, IReleaseSpec vmSpec) - { - if (codeSource.IsPrecompile(vmSpec)) - { - return PrecompileCode[codeSource]; - } - - CodeInfo cachedCodeInfo = null; - ValueHash256 codeHash = worldState.GetCodeHash(codeSource); - if (codeHash == Keccak.OfAnEmptyString.ValueHash256) - { - cachedCodeInfo = CodeInfo.Empty; - } - - cachedCodeInfo ??= CodeCache.Get(codeHash); - if (cachedCodeInfo is null) - { - byte[]? code = worldState.GetCode(codeHash); - - if (code is null) - { - MissingCode(codeSource, codeHash); - } - - cachedCodeInfo = new CodeInfo(code); - cachedCodeInfo.AnalyseInBackgroundIfRequired(); - - CodeCache.Set(codeHash, cachedCodeInfo); - } - else - { - Db.Metrics.IncrementCodeDbCache(); - } - - return cachedCodeInfo; - - [DoesNotReturn] - [StackTraceHidden] - static void MissingCode(Address codeSource, in ValueHash256 codeHash) - { - throw new NullReferenceException($"Code {codeHash} missing in the state for address {codeSource}"); - } - } - private static bool UpdateGas(long gasCost, ref long gasAvailable) { if (gasAvailable < gasCost) @@ -1462,7 +1333,7 @@ private CallResult ExecuteCode externalCode = GetCachedCodeInfo(_worldState, address, spec).MachineCode; + ReadOnlyMemory externalCode = _codeInfoRepository.GetCachedCodeInfo(_worldState, address, spec).MachineCode; slice = externalCode.SliceWithZeroPadding(b, (int)result); vmState.Memory.Save(in a, in slice); if (typeof(TTracingInstructions) == typeof(IsTracing)) @@ -2172,8 +2043,8 @@ static void ThrowOperationCanceledException() => [MethodImpl(MethodImplOptions.NoInlining)] private void InstructionExtCodeSize(Address address, ref EvmStack stack, IReleaseSpec spec) where TTracingInstructions : struct, IIsTracing { - int codeLength = GetCachedCodeInfo(_worldState, address, spec).MachineCode.Span.Length; - UInt256 result = (UInt256)codeLength; + ReadOnlyMemory accountCode = _codeInfoRepository.GetCachedCodeInfo(_worldState, address, spec).MachineCode; + UInt256 result = (UInt256)accountCode.Span.Length; stack.PushUInt256(in result); } @@ -2250,7 +2121,7 @@ private EvmExceptionType InstructionCall( !UpdateMemoryCost(vmState, ref gasAvailable, in outputOffset, outputLength) || !UpdateGas(gasExtra, ref gasAvailable)) return EvmExceptionType.OutOfGas; - CodeInfo codeInfo = GetCachedCodeInfo(_worldState, codeSource, spec); + CodeInfo codeInfo = _codeInfoRepository.GetCachedCodeInfo(_worldState, codeSource, spec); codeInfo.AnalyseInBackgroundIfRequired(); if (spec.Use63Over64Rule) @@ -2538,7 +2409,7 @@ private EvmExceptionType InstructionSelfDestruct(EvmState vmState, ref Snapshot snapshot = _worldState.TakeSnapshot(); bool accountExists = _state.AccountExists(contractAddress); - if (accountExists && (GetCachedCodeInfo(_worldState, contractAddress, spec).MachineCode.Length != 0 || + if (accountExists && (_codeInfoRepository.GetCachedCodeInfo(_worldState, contractAddress, spec).MachineCode.Length != 0 || _state.GetNonce(contractAddress) != 0)) { /* 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 */ diff --git a/src/Nethermind/Nethermind.Facade.Test/BlockchainBridgeTests.cs b/src/Nethermind/Nethermind.Facade.Test/BlockchainBridgeTests.cs index 69d8ef8077c..f3e3a2d8022 100644 --- a/src/Nethermind/Nethermind.Facade.Test/BlockchainBridgeTests.cs +++ b/src/Nethermind/Nethermind.Facade.Test/BlockchainBridgeTests.cs @@ -29,6 +29,7 @@ using NUnit.Framework; using Nethermind.Config; using Nethermind.Evm; +using Nethermind.Facade.Simulate; using Nethermind.State; namespace Nethermind.Facade.Test @@ -67,9 +68,17 @@ public async Task SetUp() IWorldStateManager readOnlyWorldStateManager = new ReadOnlyWorldStateManager(dbProvider, trieStore, LimboLogs.Instance); + IReadOnlyBlockTree readOnlyBlockTree = _blockTree.AsReadOnly(); ReadOnlyTxProcessingEnv processingEnv = new( readOnlyWorldStateManager, - new ReadOnlyBlockTree(_blockTree), + readOnlyBlockTree, + _specProvider, + LimboLogs.Instance); + + SimulateReadOnlyBlocksProcessingEnvFactory simulateProcessingEnvFactory = new SimulateReadOnlyBlocksProcessingEnvFactory( + readOnlyWorldStateManager, + readOnlyBlockTree, + new ReadOnlyDbProvider(_dbProvider, true), _specProvider, LimboLogs.Instance); @@ -77,6 +86,7 @@ public async Task SetUp() _blockchainBridge = new BlockchainBridge( processingEnv, + simulateProcessingEnvFactory, _txPool, _receiptStorage, _filterStore, @@ -195,10 +205,17 @@ public void Bridge_head_is_correct(long headNumber) IWorldStateManager readOnlyWorldStateManager = new ReadOnlyWorldStateManager(dbProvider, trieStore, LimboLogs.Instance); - + IReadOnlyBlockTree roBlockTree = _blockTree.AsReadOnly(); ReadOnlyTxProcessingEnv processingEnv = new( readOnlyWorldStateManager, - new ReadOnlyBlockTree(_blockTree), + roBlockTree, + _specProvider, + LimboLogs.Instance); + + SimulateReadOnlyBlocksProcessingEnvFactory simulateProcessingEnv = new SimulateReadOnlyBlocksProcessingEnvFactory( + readOnlyWorldStateManager, + roBlockTree, + new ReadOnlyDbProvider(_dbProvider, true), _specProvider, LimboLogs.Instance); @@ -210,6 +227,7 @@ public void Bridge_head_is_correct(long headNumber) _blockchainBridge = new BlockchainBridge( processingEnv, + simulateProcessingEnv, _txPool, _receiptStorage, _filterStore, diff --git a/src/Nethermind/Nethermind.Facade/BlockchainBridge.cs b/src/Nethermind/Nethermind.Facade/BlockchainBridge.cs index 5c82dede5a9..0a0ca34345a 100644 --- a/src/Nethermind/Nethermind.Facade/BlockchainBridge.cs +++ b/src/Nethermind/Nethermind.Facade/BlockchainBridge.cs @@ -19,13 +19,15 @@ using Block = Nethermind.Core.Block; using System.Threading; using Nethermind.Consensus.Processing; -using Nethermind.Core.Eip2930; using Nethermind.Core.Specs; using Nethermind.Evm.TransactionProcessing; using Nethermind.Facade.Filters; using Nethermind.State; using Nethermind.Core.Extensions; using Nethermind.Config; +using Nethermind.Facade.Proxy.Models.Simulate; +using Nethermind.Facade.Simulate; +using Transaction = Nethermind.Core.Transaction; namespace Nethermind.Facade { @@ -47,8 +49,10 @@ public class BlockchainBridge : IBlockchainBridge private readonly ILogFinder _logFinder; private readonly ISpecProvider _specProvider; private readonly IBlocksConfig _blocksConfig; + private readonly SimulateBridgeHelper _simulateBridgeHelper; public BlockchainBridge(ReadOnlyTxProcessingEnv processingEnv, + SimulateReadOnlyBlocksProcessingEnvFactory simulateProcessingEnvFactory, ITxPool? txPool, IReceiptFinder? receiptStorage, IFilterStore? filterStore, @@ -61,7 +65,7 @@ public BlockchainBridge(ReadOnlyTxProcessingEnv processingEnv, bool isMining) { _processingEnv = processingEnv ?? throw new ArgumentNullException(nameof(processingEnv)); - _txPool = txPool ?? throw new ArgumentNullException(nameof(_txPool)); + _txPool = txPool ?? throw new ArgumentNullException(nameof(txPool)); _receiptFinder = receiptStorage ?? throw new ArgumentNullException(nameof(receiptStorage)); _filterStore = filterStore ?? throw new ArgumentNullException(nameof(filterStore)); _filterManager = filterManager ?? throw new ArgumentNullException(nameof(filterManager)); @@ -71,6 +75,9 @@ public BlockchainBridge(ReadOnlyTxProcessingEnv processingEnv, _specProvider = specProvider ?? throw new ArgumentNullException(nameof(specProvider)); _blocksConfig = blocksConfig; IsMining = isMining; + _simulateBridgeHelper = new SimulateBridgeHelper( + simulateProcessingEnvFactory ?? throw new ArgumentNullException(nameof(simulateProcessingEnvFactory)), + _blocksConfig); } public Block? HeadBlock @@ -127,31 +134,6 @@ public Block? HeadBlock return blockHash is not null ? _receiptFinder.Get(blockHash).ForTransaction(txHash) : null; } - public class CallOutput - { - public CallOutput() - { - } - - public CallOutput(byte[] outputData, long gasSpent, string error, bool inputError = false) - { - Error = error; - OutputData = outputData; - GasSpent = gasSpent; - InputError = inputError; - } - - public string? Error { get; set; } - - public byte[] OutputData { get; set; } - - public long GasSpent { get; set; } - - public bool InputError { get; set; } - - public AccessList? AccessList { get; set; } - } - public CallOutput Call(BlockHeader header, Transaction tx, CancellationToken cancellationToken) { CallOutputTracer callOutputTracer = new(); @@ -166,6 +148,28 @@ public CallOutput Call(BlockHeader header, Transaction tx, CancellationToken can }; } + public SimulateOutput Simulate(BlockHeader header, SimulatePayload payload, CancellationToken cancellationToken) + { + SimulateBlockTracer simulateOutputTracer = new(payload.TraceTransfers, payload.ReturnFullTransactionObjects, _specProvider); + BlockReceiptsTracer tracer = new(); + tracer.SetOtherTracer(simulateOutputTracer); + SimulateOutput result = new(); + try + { + if (!_simulateBridgeHelper.TrySimulate(header, payload, new CancellationBlockTracer(tracer, cancellationToken), out string error)) + { + result.Error = error; + } + } + catch (Exception ex) + { + result.Error = ex.ToString(); + } + + result.Items = simulateOutputTracer.Results; + return result; + } + public CallOutput EstimateGas(BlockHeader header, Transaction tx, int errorMargin, CancellationToken cancellationToken) { using IReadOnlyTransactionProcessor? readOnlyTransactionProcessor = _processingEnv.Build(header.StateRoot!); diff --git a/src/Nethermind/Nethermind.Facade/BlockchainBridgeExtensions.cs b/src/Nethermind/Nethermind.Facade/BlockchainBridgeExtensions.cs new file mode 100644 index 00000000000..a5af9f8b625 --- /dev/null +++ b/src/Nethermind/Nethermind.Facade/BlockchainBridgeExtensions.cs @@ -0,0 +1,12 @@ +// SPDX-FileCopyrightText: 2023 Demerzel Solutions Limited +// SPDX-License-Identifier: LGPL-3.0-only + +using Nethermind.Core; + +namespace Nethermind.Facade; + +public static class BlockchainBridgeExtensions +{ + public static bool HasStateForBlock(this IBlockchainBridge blockchainBridge, BlockHeader header) => + blockchainBridge.HasStateForRoot(header.StateRoot!); +} diff --git a/src/Nethermind/Nethermind.Facade/CallOutput.cs b/src/Nethermind/Nethermind.Facade/CallOutput.cs new file mode 100644 index 00000000000..42034a699a0 --- /dev/null +++ b/src/Nethermind/Nethermind.Facade/CallOutput.cs @@ -0,0 +1,20 @@ +// SPDX-FileCopyrightText: 2023 Demerzel Solutions Limited +// SPDX-License-Identifier: LGPL-3.0-only + +using System; +using Nethermind.Core.Eip2930; + +namespace Nethermind.Facade; + +public class CallOutput +{ + public string? Error { get; set; } + + public byte[] OutputData { get; set; } = Array.Empty(); + + public long GasSpent { get; set; } + + public bool InputError { get; set; } + + public AccessList? AccessList { get; set; } +} diff --git a/src/Nethermind/Nethermind.JsonRpc/Data/AccessListItemForRpc.cs b/src/Nethermind/Nethermind.Facade/Eth/AccessListItemForRpc.cs similarity index 98% rename from src/Nethermind/Nethermind.JsonRpc/Data/AccessListItemForRpc.cs rename to src/Nethermind/Nethermind.Facade/Eth/AccessListItemForRpc.cs index 17533dd0f25..0269aeeb0e6 100644 --- a/src/Nethermind/Nethermind.JsonRpc/Data/AccessListItemForRpc.cs +++ b/src/Nethermind/Nethermind.Facade/Eth/AccessListItemForRpc.cs @@ -12,7 +12,7 @@ using System.Text.Json; using System.Text.Json.Serialization; -namespace Nethermind.JsonRpc.Data +namespace Nethermind.Facade.Eth { public struct AccessListItemForRpc : IEquatable { diff --git a/src/Nethermind/Nethermind.JsonRpc/Modules/Eth/BlockForRpc.cs b/src/Nethermind/Nethermind.Facade/Eth/BlockForRpc.cs similarity index 99% rename from src/Nethermind/Nethermind.JsonRpc/Modules/Eth/BlockForRpc.cs rename to src/Nethermind/Nethermind.Facade/Eth/BlockForRpc.cs index 4d0d491ce83..dde7bdd6e21 100644 --- a/src/Nethermind/Nethermind.JsonRpc/Modules/Eth/BlockForRpc.cs +++ b/src/Nethermind/Nethermind.Facade/Eth/BlockForRpc.cs @@ -14,7 +14,7 @@ using System.Text.Json.Serialization; using System.Runtime.CompilerServices; -namespace Nethermind.JsonRpc.Modules.Eth; +namespace Nethermind.Facade.Eth; public class BlockForRpc { diff --git a/src/Nethermind/Nethermind.JsonRpc/Data/TransactionForRpc.cs b/src/Nethermind/Nethermind.Facade/Eth/TransactionForRpc.cs similarity index 99% rename from src/Nethermind/Nethermind.JsonRpc/Data/TransactionForRpc.cs rename to src/Nethermind/Nethermind.Facade/Eth/TransactionForRpc.cs index 9806860fad7..ab7a7bbc8ba 100644 --- a/src/Nethermind/Nethermind.JsonRpc/Data/TransactionForRpc.cs +++ b/src/Nethermind/Nethermind.Facade/Eth/TransactionForRpc.cs @@ -10,8 +10,9 @@ using Nethermind.Core.Eip2930; using Nethermind.Core.Extensions; using Nethermind.Int256; +using Nethermind.JsonRpc.Data; -namespace Nethermind.JsonRpc.Data; +namespace Nethermind.Facade.Eth; public class TransactionForRpc { diff --git a/src/Nethermind/Nethermind.Facade/IBlockchainBridge.cs b/src/Nethermind/Nethermind.Facade/IBlockchainBridge.cs index f998ae99fd0..ab0ffa8d309 100644 --- a/src/Nethermind/Nethermind.Facade/IBlockchainBridge.cs +++ b/src/Nethermind/Nethermind.Facade/IBlockchainBridge.cs @@ -9,8 +9,11 @@ using Nethermind.Core.Crypto; using Nethermind.Evm; using Nethermind.Facade.Filters; +using Nethermind.Facade.Simulate; +using Nethermind.Facade.Proxy.Models.Simulate; using Nethermind.Int256; using Nethermind.Trie; +using static Nethermind.Facade.BlockchainBridge; using Block = Nethermind.Core.Block; namespace Nethermind.Facade @@ -24,9 +27,11 @@ public interface IBlockchainBridge : ILogFinder TxReceipt GetReceipt(Hash256 txHash); (TxReceipt? Receipt, TxGasInfo? GasInfo, int LogIndexStart) GetReceiptAndGasInfo(Hash256 txHash); (TxReceipt? Receipt, Transaction Transaction, UInt256? baseFee) GetTransaction(Hash256 txHash, bool checkTxnPool = true); - BlockchainBridge.CallOutput Call(BlockHeader header, Transaction tx, CancellationToken cancellationToken); - BlockchainBridge.CallOutput EstimateGas(BlockHeader header, Transaction tx, int errorMarginBasisPoints, CancellationToken cancellationToken); - BlockchainBridge.CallOutput CreateAccessList(BlockHeader header, Transaction tx, CancellationToken cancellationToken, bool optimize); + CallOutput Call(BlockHeader header, Transaction tx, CancellationToken cancellationToken); + SimulateOutput Simulate(BlockHeader header, SimulatePayload payload, CancellationToken cancellationToken); + CallOutput EstimateGas(BlockHeader header, Transaction tx, int errorMarginBasisPoints, CancellationToken cancellationToken); + + CallOutput CreateAccessList(BlockHeader header, Transaction tx, CancellationToken cancellationToken, bool optimize); ulong GetChainId(); int NewBlockFilter(); diff --git a/src/Nethermind/Nethermind.Facade/Nethermind.Facade.csproj b/src/Nethermind/Nethermind.Facade/Nethermind.Facade.csproj index 200aa01482e..7a90f4758a8 100644 --- a/src/Nethermind/Nethermind.Facade/Nethermind.Facade.csproj +++ b/src/Nethermind/Nethermind.Facade/Nethermind.Facade.csproj @@ -2,6 +2,7 @@ annotations + True diff --git a/src/Nethermind/Nethermind.Facade/OverridableCodeInfoRepository.cs b/src/Nethermind/Nethermind.Facade/OverridableCodeInfoRepository.cs new file mode 100644 index 00000000000..dbe43ddddc7 --- /dev/null +++ b/src/Nethermind/Nethermind.Facade/OverridableCodeInfoRepository.cs @@ -0,0 +1,44 @@ +// SPDX-FileCopyrightText: 2023 Demerzel Solutions Limited +// SPDX-License-Identifier: LGPL-3.0-only + +using System; +using System.Collections.Generic; +using Nethermind.Core; +using Nethermind.Core.Crypto; +using Nethermind.Core.Specs; +using Nethermind.Evm; +using Nethermind.Evm.CodeAnalysis; +using Nethermind.State; + +namespace Nethermind.Facade; + +public class OverridableCodeInfoRepository(ICodeInfoRepository codeInfoRepository) : ICodeInfoRepository +{ + private readonly Dictionary _codeOverwrites = new(); + + public CodeInfo GetCachedCodeInfo(IWorldState worldState, Address codeSource, IReleaseSpec vmSpec) => + _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, + Address key, + CodeInfo value, + Address? redirectAddress = null) + { + if (redirectAddress is not null) + { + _codeOverwrites[redirectAddress] = GetCachedCodeInfo(worldState, key, vmSpec); + } + + _codeOverwrites[key] = value; + } +} diff --git a/src/Nethermind/Nethermind.Facade/Proxy/Models/AccountOverride.cs b/src/Nethermind/Nethermind.Facade/Proxy/Models/AccountOverride.cs new file mode 100644 index 00000000000..5b8a26ab431 --- /dev/null +++ b/src/Nethermind/Nethermind.Facade/Proxy/Models/AccountOverride.cs @@ -0,0 +1,27 @@ +// SPDX-FileCopyrightText: 2023 Demerzel Solutions Limited +// SPDX-License-Identifier: LGPL-3.0-only + +using System.Collections.Generic; +using Nethermind.Core; +using Nethermind.Core.Crypto; +using Nethermind.Int256; + +namespace Nethermind.Facade.Proxy.Models; + +public class AccountOverride +{ + public UInt256? Nonce { get; set; } + public UInt256? Balance { get; set; } + public byte[]? Code { get; set; } + public Address? MovePrecompileToAddress { get; set; } + + /// + /// Storage for AccountOverrideState + /// + public Dictionary? State { get; set; } + + /// + /// Storage difference for AccountOverrideStateDiff + /// + public Dictionary? StateDiff { get; set; } +} diff --git a/src/Nethermind/Nethermind.Facade/Proxy/Models/CallTransactionModel.cs b/src/Nethermind/Nethermind.Facade/Proxy/Models/CallTransactionModel.cs deleted file mode 100644 index 697b11eb3d7..00000000000 --- a/src/Nethermind/Nethermind.Facade/Proxy/Models/CallTransactionModel.cs +++ /dev/null @@ -1,30 +0,0 @@ -// SPDX-FileCopyrightText: 2022 Demerzel Solutions Limited -// SPDX-License-Identifier: LGPL-3.0-only - -using Nethermind.Core; -using Nethermind.Int256; -using static Nethermind.Core.Extensions.MemoryExtensions; - -namespace Nethermind.Facade.Proxy.Models -{ - public class CallTransactionModel - { - public Address From { get; set; } - public Address To { get; set; } - public UInt256 Gas { get; set; } - public UInt256 GasPrice { get; set; } - public UInt256 Value { get; set; } - public byte[] Data { get; set; } - - public static CallTransactionModel FromTransaction(Transaction transaction) - => new() - { - From = transaction.SenderAddress, - To = transaction.To, - Data = transaction.Data.AsArray(), - Value = transaction.Value, - Gas = (UInt256)transaction.GasLimit, - GasPrice = transaction.GasPrice - }; - } -} diff --git a/src/Nethermind/Nethermind.Facade/Proxy/Models/Simulate/BlockOverride.cs b/src/Nethermind/Nethermind.Facade/Proxy/Models/Simulate/BlockOverride.cs new file mode 100644 index 00000000000..ad017bec29e --- /dev/null +++ b/src/Nethermind/Nethermind.Facade/Proxy/Models/Simulate/BlockOverride.cs @@ -0,0 +1,61 @@ +// SPDX-FileCopyrightText: 2023 Demerzel Solutions Limited +// SPDX-License-Identifier: LGPL-3.0-only + +using System; +using Nethermind.Config; +using Nethermind.Core; +using Nethermind.Core.Crypto; +using Nethermind.Core.Specs; +using Nethermind.Int256; + +namespace Nethermind.Facade.Proxy.Models.Simulate; + +public class BlockOverride +{ + public ulong? Number { get; set; } + public Hash256 PrevRandao { get; set; } = Keccak.Zero; + public ulong? Time { get; set; } + public ulong? GasLimit { get; set; } + public Address? FeeRecipient { get; set; } + public UInt256? BaseFeePerGas { get; set; } + + public BlockHeader GetBlockHeader(BlockHeader parent, IBlocksConfig cfg, IReleaseSpec spec) + { + ulong newTime = Time ?? checked(parent.Timestamp + cfg.SecondsPerSlot); + + long newGasLimit = GasLimit switch + { + null => parent.GasLimit, + <= long.MaxValue => (long)GasLimit, + _ => throw new OverflowException($"GasLimit value is too large, max value {ulong.MaxValue}") + }; + + long newBlockNumber = Number switch + { + null => checked(parent.Number + 1), + <= long.MaxValue => (long)Number, + _ => throw new OverflowException($"Block Number value is too large, max value {ulong.MaxValue}") + }; + + Address newFeeRecipientAddress = FeeRecipient ?? parent.Beneficiary!; + UInt256 newDifficulty = parent.Difficulty == 0 ? 0 : parent.Difficulty + 1; + BlockHeader result = new( + parent.Hash!, + Keccak.OfAnEmptySequenceRlp, + newFeeRecipientAddress, + newDifficulty, + newBlockNumber, + newGasLimit, + newTime, + Array.Empty()) + { + BaseFeePerGas = BaseFeePerGas ?? BaseFeeCalculator.Calculate(parent, spec), + MixHash = PrevRandao, + IsPostMerge = parent.Difficulty == 0, + TotalDifficulty = parent.TotalDifficulty + newDifficulty, + SealEngineType = parent.SealEngineType + }; + + return result; + } +} diff --git a/src/Nethermind/Nethermind.Facade/Proxy/Models/Simulate/BlockStateCall.cs b/src/Nethermind/Nethermind.Facade/Proxy/Models/Simulate/BlockStateCall.cs new file mode 100644 index 00000000000..db4efb549ba --- /dev/null +++ b/src/Nethermind/Nethermind.Facade/Proxy/Models/Simulate/BlockStateCall.cs @@ -0,0 +1,15 @@ +// SPDX-FileCopyrightText: 2023 Demerzel Solutions Limited +// SPDX-License-Identifier: LGPL-3.0-only + +using System; +using System.Collections.Generic; +using Nethermind.Core; + +namespace Nethermind.Facade.Proxy.Models.Simulate; + +public class BlockStateCall +{ + public BlockOverride? BlockOverrides { get; set; } + public Dictionary? StateOverrides { get; set; } + public T[]? Calls { get; set; } = Array.Empty(); +} diff --git a/src/Nethermind/Nethermind.Facade/Proxy/Models/Simulate/Error.cs b/src/Nethermind/Nethermind.Facade/Proxy/Models/Simulate/Error.cs new file mode 100644 index 00000000000..a53bdd9a9e5 --- /dev/null +++ b/src/Nethermind/Nethermind.Facade/Proxy/Models/Simulate/Error.cs @@ -0,0 +1,11 @@ +// SPDX-FileCopyrightText: 2023 Demerzel Solutions Limited +// SPDX-License-Identifier: LGPL-3.0-only + +namespace Nethermind.Facade.Proxy.Models.Simulate; + +public class Error +{ + public int Code { get; set; } + public string Message { get; set; } + public string Data { get; set; } +} diff --git a/src/Nethermind/Nethermind.Facade/Proxy/Models/Simulate/Log.cs b/src/Nethermind/Nethermind.Facade/Proxy/Models/Simulate/Log.cs new file mode 100644 index 00000000000..1e0f05d12c3 --- /dev/null +++ b/src/Nethermind/Nethermind.Facade/Proxy/Models/Simulate/Log.cs @@ -0,0 +1,21 @@ +// SPDX-FileCopyrightText: 2023 Demerzel Solutions Limited +// SPDX-License-Identifier: LGPL-3.0-only + +using Nethermind.Core; +using Nethermind.Core.Crypto; + +namespace Nethermind.Facade.Proxy.Models.Simulate; + +public class Log +{ + public Address Address { get; set; } + public Hash256[] Topics { get; set; } + public byte[] Data { get; set; } + public ulong BlockNumber { get; set; } + public Hash256 TransactionHash { get; set; } + public ulong TransactionIndex { get; set; } + public Hash256 BlockHash { get; set; } + + public ulong LogIndex { get; set; } + public bool Removed { get; set; } = false; +} diff --git a/src/Nethermind/Nethermind.Facade/Proxy/Models/Simulate/ResultType.cs b/src/Nethermind/Nethermind.Facade/Proxy/Models/Simulate/ResultType.cs new file mode 100644 index 00000000000..78e8707e914 --- /dev/null +++ b/src/Nethermind/Nethermind.Facade/Proxy/Models/Simulate/ResultType.cs @@ -0,0 +1,11 @@ +// SPDX-FileCopyrightText: 2023 Demerzel Solutions Limited +// SPDX-License-Identifier: LGPL-3.0-only + +namespace Nethermind.Facade.Proxy.Models.Simulate; + +public enum ResultType +{ + Failure, + Success, + Invalid +} diff --git a/src/Nethermind/Nethermind.Facade/Proxy/Models/Simulate/SimulateBlockResult.cs b/src/Nethermind/Nethermind.Facade/Proxy/Models/Simulate/SimulateBlockResult.cs new file mode 100644 index 00000000000..4b4e300fe10 --- /dev/null +++ b/src/Nethermind/Nethermind.Facade/Proxy/Models/Simulate/SimulateBlockResult.cs @@ -0,0 +1,19 @@ +// SPDX-FileCopyrightText: 2023 Demerzel Solutions Limited +// SPDX-License-Identifier: LGPL-3.0-only + +using System; +using System.Collections.Generic; +using System.Reflection; +using Nethermind.Core; +using Nethermind.Core.Specs; +using Nethermind.Facade.Eth; +using Nethermind.Int256; + +namespace Nethermind.Facade.Proxy.Models.Simulate; + +public class SimulateBlockResult(Block source, bool includeFullTransactionData, ISpecProvider specProvider) + : BlockForRpc(source, includeFullTransactionData, specProvider) +{ + public List Calls { get; set; } = new(); + public UInt256 BlobBaseFee { get; set; } +} diff --git a/src/Nethermind/Nethermind.Facade/Proxy/Models/Simulate/SimulateCallResult.cs b/src/Nethermind/Nethermind.Facade/Proxy/Models/Simulate/SimulateCallResult.cs new file mode 100644 index 00000000000..e07ec128b4d --- /dev/null +++ b/src/Nethermind/Nethermind.Facade/Proxy/Models/Simulate/SimulateCallResult.cs @@ -0,0 +1,16 @@ +// SPDX-FileCopyrightText: 2023 Demerzel Solutions Limited +// SPDX-License-Identifier: LGPL-3.0-only + +using System.Collections.Generic; +using System.Linq; + +namespace Nethermind.Facade.Proxy.Models.Simulate; + +public class SimulateCallResult +{ + public ulong Status { get; set; } + public byte[]? ReturnData { get; set; } + public ulong? GasUsed { get; set; } + public Error? Error { get; set; } + public IEnumerable Logs { get; set; } = Enumerable.Empty(); +} diff --git a/src/Nethermind/Nethermind.Facade/Proxy/Models/Simulate/SimulatePayload.cs b/src/Nethermind/Nethermind.Facade/Proxy/Models/Simulate/SimulatePayload.cs new file mode 100644 index 00000000000..ad81f77158e --- /dev/null +++ b/src/Nethermind/Nethermind.Facade/Proxy/Models/Simulate/SimulatePayload.cs @@ -0,0 +1,30 @@ +// SPDX-FileCopyrightText: 2023 Demerzel Solutions Limited +// SPDX-License-Identifier: LGPL-3.0-only + +using System.Collections.Generic; + +namespace Nethermind.Facade.Proxy.Models.Simulate; + +public class SimulatePayload +{ + /// + /// Definition of blocks that can contain calls and overrides + /// + public List>? BlockStateCalls { get; set; } + + /// + /// Should trace ETH Transfers + /// + public bool TraceTransfers { get; set; } = false; + + /// + /// When true, the simulate does all validations that a normal EVM would do, except contract sender and signature + /// checks. When false, multicall behaves like eth_call. + /// + public bool Validation { get; set; } = false; + + /// + /// When true, the simulate returns Full Tx Objects + /// + public bool ReturnFullTransactionObjects { get; set; } = false; +} diff --git a/src/Nethermind/Nethermind.Facade/Proxy/Models/Simulate/TransactionWithSourceDetails.cs b/src/Nethermind/Nethermind.Facade/Proxy/Models/Simulate/TransactionWithSourceDetails.cs new file mode 100644 index 00000000000..b3fed70ba7c --- /dev/null +++ b/src/Nethermind/Nethermind.Facade/Proxy/Models/Simulate/TransactionWithSourceDetails.cs @@ -0,0 +1,13 @@ +// SPDX-FileCopyrightText: 2023 Demerzel Solutions Limited +// SPDX-License-Identifier: LGPL-3.0-only + +using Nethermind.Core; + +namespace Nethermind.Facade.Proxy.Models.Simulate; + +public class TransactionWithSourceDetails +{ + public bool HadGasLimitInRequest; + public bool HadNonceInRequest; + public Transaction Transaction { get; set; } +} diff --git a/src/Nethermind/Nethermind.Facade/Simulate/SimulateBlockTracer.cs b/src/Nethermind/Nethermind.Facade/Simulate/SimulateBlockTracer.cs new file mode 100644 index 00000000000..c6ab396eedc --- /dev/null +++ b/src/Nethermind/Nethermind.Facade/Simulate/SimulateBlockTracer.cs @@ -0,0 +1,60 @@ +// SPDX-FileCopyrightText: 2023 Demerzel Solutions Limited +// SPDX-License-Identifier: LGPL-3.0-only + +using System.Collections.Generic; +using System.Linq; +using Nethermind.Core; +using Nethermind.Core.Specs; +using Nethermind.Evm; +using Nethermind.Evm.Tracing; +using Nethermind.Facade.Proxy.Models.Simulate; +using Nethermind.Int256; + +namespace Nethermind.Facade.Simulate; + +public class SimulateBlockTracer(bool isTracingLogs, bool includeFullTxData, ISpecProvider spec) : BlockTracer +{ + private readonly List _txTracers = new(); + + private Block _currentBlock = null!; + public List Results { get; } = new(); + + public override void StartNewBlockTrace(Block block) + { + _txTracers.Clear(); + _currentBlock = block; + } + + public override ITxTracer StartNewTxTrace(Transaction? tx) + { + + if (tx?.Hash is not null) + { + ulong txIndex = (ulong)_txTracers.Count; + SimulateTxMutatorTracer result = new(isTracingLogs, tx.Hash, (ulong)_currentBlock.Number, + _currentBlock.Hash, txIndex); + _txTracers.Add(result); + return result; + } + + return NullTxTracer.Instance; + } + + public override void EndBlockTrace() + { + SimulateBlockResult? result = new(_currentBlock, includeFullTxData, spec) + { + Calls = _txTracers.Select(t => t.TraceResult).ToList(), + }; + if (_currentBlock.Header.ExcessBlobGas is not null) + { + if (!BlobGasCalculator.TryCalculateBlobGasPricePerUnit(_currentBlock.Header.ExcessBlobGas.Value, + out UInt256 blobGasPricePerUnit)) + { + result.BlobBaseFee = blobGasPricePerUnit; + } + } + + Results.Add(result); + } +} diff --git a/src/Nethermind/Nethermind.Facade/Simulate/SimulateBlockhashProvider.cs b/src/Nethermind/Nethermind.Facade/Simulate/SimulateBlockhashProvider.cs new file mode 100644 index 00000000000..8f054a809ca --- /dev/null +++ b/src/Nethermind/Nethermind.Facade/Simulate/SimulateBlockhashProvider.cs @@ -0,0 +1,21 @@ +// SPDX-FileCopyrightText: 2024 Demerzel Solutions Limited +// SPDX-License-Identifier: LGPL-3.0-only + +using Nethermind.Blockchain; +using Nethermind.Core; +using Nethermind.Core.Crypto; +using Nethermind.Evm; + +namespace Nethermind.Facade.Simulate; + +public sealed class SimulateBlockhashProvider(IBlockhashProvider blockhashProvider, IBlockTree blockTree) + : IBlockhashProvider +{ + public Hash256? GetBlockhash(BlockHeader currentBlock, in long number) + { + long bestKnown = blockTree.BestKnownNumber; + return bestKnown < number && blockTree.BestSuggestedHeader is not null + ? blockhashProvider.GetBlockhash(blockTree.BestSuggestedHeader!, in bestKnown) + : blockhashProvider.GetBlockhash(currentBlock, in number); + } +} diff --git a/src/Nethermind/Nethermind.Facade/Simulate/SimulateBridgeHelper.cs b/src/Nethermind/Nethermind.Facade/Simulate/SimulateBridgeHelper.cs new file mode 100644 index 00000000000..b93f5594e41 --- /dev/null +++ b/src/Nethermind/Nethermind.Facade/Simulate/SimulateBridgeHelper.cs @@ -0,0 +1,284 @@ +// SPDX-FileCopyrightText: 2023 Demerzel Solutions Limited +// SPDX-License-Identifier: LGPL-3.0-only + +using System; +using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; +using System.Linq; +using System.Runtime.InteropServices; +using Nethermind.Blockchain; +using Nethermind.Config; +using Nethermind.Consensus.Processing; +using Nethermind.Core; +using Nethermind.Core.Collections; +using Nethermind.Core.Crypto; +using Nethermind.Core.Specs; +using Nethermind.Crypto; +using Nethermind.Evm.Tracing; +using Nethermind.Facade.Proxy.Models.Simulate; +using Nethermind.Int256; +using Nethermind.State; +using Transaction = Nethermind.Core.Transaction; + +namespace Nethermind.Facade.Simulate; + +public class SimulateBridgeHelper(SimulateReadOnlyBlocksProcessingEnvFactory simulateProcessingEnvFactory, IBlocksConfig blocksConfig) +{ + private const ProcessingOptions SimulateProcessingOptions = + ProcessingOptions.ForceProcessing + | ProcessingOptions.IgnoreParentNotOnMainChain + | ProcessingOptions.MarkAsProcessed + | ProcessingOptions.StoreReceipts; + + private void PrepareState(BlockHeader blockHeader, + BlockHeader parent, + BlockStateCall blockStateCall, + IWorldState stateProvider, + OverridableCodeInfoRepository codeInfoRepository, + IReleaseSpec releaseSpec) + { + stateProvider.StateRoot = parent.StateRoot!; + stateProvider.ApplyStateOverrides(codeInfoRepository, blockStateCall.StateOverrides, releaseSpec, blockHeader.Number); + + IEnumerable
senders = blockStateCall.Calls?.Select(details => details.Transaction.SenderAddress) ?? Enumerable.Empty(); + IEnumerable
targets = blockStateCall.Calls?.Select(details => details.Transaction.To!) ?? Enumerable.Empty(); + foreach (Address address in senders.Union(targets).Where(t => t is not null)) + { + stateProvider.CreateAccountIfNotExists(address, 0, 1); + } + + stateProvider.Commit(releaseSpec); + stateProvider.CommitTree(blockHeader.Number - 1); + stateProvider.RecalculateStateRoot(); + + blockHeader.StateRoot = stateProvider.StateRoot; + } + + public bool TrySimulate( + BlockHeader parent, + SimulatePayload payload, + IBlockTracer tracer, + [NotNullWhen(false)] out string? error) => + TrySimulate(parent, payload, tracer, simulateProcessingEnvFactory.Create(payload.Validation), out error); + + + private bool TrySimulate( + BlockHeader parent, + SimulatePayload payload, + IBlockTracer tracer, + SimulateReadOnlyBlocksProcessingEnv env, + [NotNullWhen(false)] out string? error) + { + IBlockTree blockTree = env.BlockTree; + IWorldState stateProvider = env.StateProvider; + parent = GetParent(parent, payload, blockTree); + IReleaseSpec spec = env.SpecProvider.GetSpec(parent); + + if (payload.BlockStateCalls is not null) + { + Dictionary nonceCache = new(); + List suggestedBlocks = [null]; + + foreach (BlockStateCall blockCall in payload.BlockStateCalls) + { + nonceCache.Clear(); + BlockHeader callHeader = GetCallHeader(blockCall, parent, payload.Validation, spec); //currentSpec is still parent spec + spec = env.SpecProvider.GetSpec(callHeader); + PrepareState(callHeader, parent, blockCall, env.StateProvider, env.CodeInfoRepository, spec); + + if (blockCall.BlockOverrides is { BaseFeePerGas: not null }) + { + callHeader.BaseFeePerGas = blockCall.BlockOverrides.BaseFeePerGas.Value; + } + else if (!payload.Validation) + { + callHeader.BaseFeePerGas = 0; + } + callHeader.Hash = callHeader.CalculateHash(); + + Transaction[] transactions = CreateTransactions(payload, blockCall, callHeader, stateProvider, nonceCache); + if (!TryGetBlock(payload, env, callHeader, transactions, out Block currentBlock, out error)) + { + return false; + } + + ProcessingOptions processingFlags = SimulateProcessingOptions; + + if (!payload.Validation) + { + processingFlags |= ProcessingOptions.NoValidation; + } + + suggestedBlocks[0] = currentBlock; + + IBlockProcessor processor = env.GetProcessor(payload.Validation); + Block processedBlock = + processor.Process(stateProvider.StateRoot, suggestedBlocks, processingFlags, tracer)[0]; + + FinalizeStateAndBlock(stateProvider, processedBlock, spec, currentBlock, blockTree); + parent = processedBlock.Header; + } + } + + error = null; + return true; + } + + private static void FinalizeStateAndBlock(IWorldState stateProvider, Block processedBlock, IReleaseSpec currentSpec, Block currentBlock, IBlockTree blockTree) + { + stateProvider.StateRoot = processedBlock.StateRoot!; + stateProvider.Commit(currentSpec); + stateProvider.CommitTree(currentBlock.Number); + blockTree.SuggestBlock(processedBlock, BlockTreeSuggestOptions.ForceSetAsMain); + blockTree.UpdateHeadBlock(processedBlock.Hash!); + } + + private static BlockHeader GetParent(BlockHeader parent, SimulatePayload payload, IBlockTree blockTree) + { + Block? latestBlock = blockTree.FindLatestBlock(); + long latestBlockNumber = latestBlock?.Number ?? 0; + + if (latestBlockNumber < parent.Number) + { + parent = latestBlock?.Header ?? blockTree.Head!.Header; + } + + BlockStateCall? firstBlock = payload.BlockStateCalls?.FirstOrDefault(); + + ulong lastKnown = (ulong)latestBlockNumber; + if (firstBlock?.BlockOverrides?.Number > 0 && firstBlock.BlockOverrides?.Number < lastKnown) + { + Block? searchResult = blockTree.FindBlock((long)firstBlock.BlockOverrides.Number); + if (searchResult is not null) + { + parent = searchResult.Header; + } + } + + return parent; + } + + private static bool TryGetBlock( + SimulatePayload payload, + SimulateReadOnlyBlocksProcessingEnv env, + BlockHeader callHeader, + Transaction[] transactions, + out Block currentBlock, + [NotNullWhen(false)] out string? error) + { + IWorldState stateProvider = env.StateProvider; + Snapshot shoot = stateProvider.TakeSnapshot(); + currentBlock = new Block(callHeader); + LinkedHashSet testedTxs = new(); + for (int index = 0; index < transactions.Length; index++) + { + Transaction transaction = transactions[index]; + BlockProcessor.AddingTxEventArgs? args = env.BlockTransactionPicker.CanAddTransaction(currentBlock, transaction, testedTxs, stateProvider); + + if (args.Action is BlockProcessor.TxAction.Stop or BlockProcessor.TxAction.Skip && payload.Validation) + { + error = $"invalid transaction index: {index} at block number: {callHeader.Number}, Reason: {args.Reason}"; + return false; + } + + stateProvider.IncrementNonce(transaction.SenderAddress!); + testedTxs.Add(transaction); + } + + stateProvider.Restore(shoot); + stateProvider.RecalculateStateRoot(); + + currentBlock = currentBlock.WithReplacedBody(currentBlock.Body.WithChangedTransactions(testedTxs.ToArray())); + error = null; + return true; + } + + private Transaction[] CreateTransactions(SimulatePayload payload, + BlockStateCall callInputBlock, + BlockHeader callHeader, + IWorldState stateProvider, + Dictionary nonceCache) + { + int notSpecifiedGasTxsCount = callInputBlock.Calls?.Count(details => !details.HadGasLimitInRequest) ?? 0; + long gasSpecified = callInputBlock.Calls?.Where(details => details.HadGasLimitInRequest).Sum(details => details.Transaction.GasLimit) ?? 0; + if (notSpecifiedGasTxsCount > 0) + { + long gasPerTx = callHeader.GasLimit - gasSpecified / notSpecifiedGasTxsCount; + IEnumerable notSpecifiedGasTxs = callInputBlock.Calls?.Where(details => !details.HadGasLimitInRequest) ?? Enumerable.Empty(); + foreach (TransactionWithSourceDetails call in notSpecifiedGasTxs) + { + call.Transaction.GasLimit = gasPerTx; + } + } + + return callInputBlock.Calls?.Select(t => CreateTransaction(t, callHeader, stateProvider, nonceCache, payload.Validation)).ToArray() ?? Array.Empty(); + } + + private Transaction CreateTransaction(TransactionWithSourceDetails transactionDetails, + BlockHeader callHeader, + IWorldState stateProvider, + Dictionary nonceCache, + bool validate) + { + Transaction? transaction = transactionDetails.Transaction; + transaction.SenderAddress ??= Address.Zero; + transaction.To ??= Address.Zero; + transaction.Data ??= Memory.Empty; + + if (!transactionDetails.HadNonceInRequest) + { + ref UInt256 cachedNonce = ref CollectionsMarshal.GetValueRefOrAddDefault(nonceCache, transaction.SenderAddress, out bool exist); + if (!exist) + { + if (stateProvider.TryGetAccount(transaction.SenderAddress, out AccountStruct test)) + { + cachedNonce = test.Nonce; + } + // else // Todo think if we shall create account here + } + else + { + cachedNonce++; + } + + transaction.Nonce = cachedNonce; + } + + if (validate) + { + if (transaction.GasPrice == 0) + { + transaction.GasPrice = callHeader.BaseFeePerGas; + } + + if (transaction.Type == TxType.EIP1559 && transaction.DecodedMaxFeePerGas == 0) + { + transaction.DecodedMaxFeePerGas = transaction.GasPrice == 0 + ? callHeader.BaseFeePerGas + 1 + : transaction.GasPrice; + } + } + + transaction.Hash ??= transaction.CalculateHash(); + + return transaction; + } + + private BlockHeader GetCallHeader(BlockStateCall block, BlockHeader parent, bool payloadValidation, IReleaseSpec parentSpec) => + block.BlockOverrides is not null + ? block.BlockOverrides.GetBlockHeader(parent, blocksConfig, parentSpec) + : new BlockHeader( + parent.Hash!, + Keccak.OfAnEmptySequenceRlp, + Address.Zero, + UInt256.Zero, + parent.Number + 1, + parent.GasLimit, + parent.Timestamp + 1, + Array.Empty()) + { + BaseFeePerGas = !payloadValidation ? 0 : BaseFeeCalculator.Calculate(parent, parentSpec), + MixHash = parent.MixHash, + IsPostMerge = parent.Difficulty == 0 + }; +} diff --git a/src/Nethermind/Nethermind.Facade/Simulate/SimulateDictionaryBlockStore.cs b/src/Nethermind/Nethermind.Facade/Simulate/SimulateDictionaryBlockStore.cs new file mode 100644 index 00000000000..4b76d9bcdb5 --- /dev/null +++ b/src/Nethermind/Nethermind.Facade/Simulate/SimulateDictionaryBlockStore.cs @@ -0,0 +1,94 @@ +// SPDX-FileCopyrightText: 2024 Demerzel Solutions Limited +// SPDX-License-Identifier: LGPL-3.0-only + +using System.Collections.Generic; +using Nethermind.Blockchain.Blocks; +using Nethermind.Core; +using Nethermind.Core.Buffers; +using Nethermind.Core.Crypto; +using Nethermind.Core.Extensions; +using Nethermind.Serialization.Rlp; + +namespace Nethermind.Facade.Simulate; + +public class SimulateDictionaryBlockStore(IBlockStore readonlyBaseBlockStore) : IBlockStore +{ + private readonly Dictionary _blockDict = new(); + private readonly Dictionary _blockNumDict = new(); + private readonly Dictionary _metadataDict = new(Bytes.EqualityComparer); + private readonly BlockDecoder _blockDecoder = new(); + + public void Insert(Block block, WriteFlags writeFlags = WriteFlags.None) + { + _blockDict[block.Hash] = block; + _blockNumDict[block.Number] = block; + } + + public void Delete(long blockNumber, Hash256 blockHash) + { + _blockDict.Remove(blockHash); + _blockNumDict.Remove(blockNumber); + } + + public Block? Get(long blockNumber, Hash256 blockHash, RlpBehaviors rlpBehaviors = RlpBehaviors.None, bool shouldCache = true) + { + if (_blockNumDict.TryGetValue(blockNumber, out Block block)) + { + return block; + } + + block = readonlyBaseBlockStore.Get(blockNumber, blockHash, rlpBehaviors, false); + if (block is not null && shouldCache) + { + Cache(block); + } + return block; + } + + public byte[]? GetRaw(long blockNumber, Hash256 blockHash) + { + if (_blockNumDict.TryGetValue(blockNumber, out Block block)) + { + using NettyRlpStream newRlp = _blockDecoder.EncodeToNewNettyStream(block); + return newRlp.AsSpan().ToArray(); + } + return readonlyBaseBlockStore.GetRaw(blockNumber, blockHash); + } + + public IEnumerable GetAll() + { + var allBlocks = new HashSet(readonlyBaseBlockStore.GetAll()); + foreach (Block block in _blockDict.Values) + { + allBlocks.Add(block); + } + return allBlocks; + } + + public ReceiptRecoveryBlock? GetReceiptRecoveryBlock(long blockNumber, Hash256 blockHash) + { + if (_blockNumDict.TryGetValue(blockNumber, out Block block)) + { + using NettyRlpStream newRlp = _blockDecoder.EncodeToNewNettyStream(block); + using var memoryManager = new CappedArrayMemoryManager(newRlp.Data); + return BlockDecoder.DecodeToReceiptRecoveryBlock(memoryManager, memoryManager.Memory, RlpBehaviors.None); + } + return readonlyBaseBlockStore.GetReceiptRecoveryBlock(blockNumber, blockHash); + } + + public void Cache(Block block) + { + Insert(block); + } + + public void SetMetadata(byte[] key, byte[] value) + { + _metadataDict[key] = value; + readonlyBaseBlockStore.SetMetadata(key, value); + } + + public byte[]? GetMetadata(byte[] key) + { + return _metadataDict.TryGetValue(key, out var value) ? value : readonlyBaseBlockStore.GetMetadata(key); + } +} diff --git a/src/Nethermind/Nethermind.Facade/Simulate/SimulateDictionaryHeaderStore.cs b/src/Nethermind/Nethermind.Facade/Simulate/SimulateDictionaryHeaderStore.cs new file mode 100644 index 00000000000..715df65eabb --- /dev/null +++ b/src/Nethermind/Nethermind.Facade/Simulate/SimulateDictionaryHeaderStore.cs @@ -0,0 +1,72 @@ +// SPDX-FileCopyrightText: 2024 Demerzel Solutions Limited +// SPDX-License-Identifier: LGPL-3.0-only + +using System.Collections.Generic; +using Nethermind.Blockchain.Headers; +using Nethermind.Core; +using Nethermind.Core.Crypto; + +namespace Nethermind.Facade.Simulate; + +/// +/// This type is needed for two things: +/// - Bypass issue of networking compatibility and RLPs not supporting BaseFeePerGas of 0 +/// - Improve performance to get faster local caching without re-encoding of data in Simulate blocks +/// +/// +public class SimulateDictionaryHeaderStore(IHeaderStore readonlyBaseHeaderStore) : IHeaderStore +{ + private readonly Dictionary _headerDict = new(); + private readonly Dictionary _blockNumberDict = new(); + + public void Insert(BlockHeader header) + { + _headerDict[header.Hash] = header; + InsertBlockNumber(header.Hash, header.Number); + } + + public BlockHeader? Get(Hash256 blockHash, bool shouldCache = false, long? blockNumber = null) + { + if (blockNumber is null) + { + blockNumber = GetBlockNumber(blockHash); + } + + if (blockNumber.HasValue && _headerDict.TryGetValue(blockHash, out BlockHeader? header)) + { + if (shouldCache) + { + Cache(header); + } + return header; + } + + header = readonlyBaseHeaderStore.Get(blockHash, false, blockNumber); + if (header is not null && shouldCache) + { + Cache(header); + } + return header; + } + + public void Cache(BlockHeader header) + { + Insert(header); + } + + public void Delete(Hash256 blockHash) + { + _headerDict.Remove(blockHash); + _blockNumberDict.Remove(blockHash); + } + + public void InsertBlockNumber(Hash256 blockHash, long blockNumber) + { + _blockNumberDict[blockHash] = blockNumber; + } + + public long? GetBlockNumber(Hash256 blockHash) + { + return _blockNumberDict.TryGetValue(blockHash, out var blockNumber) ? blockNumber : readonlyBaseHeaderStore.GetBlockNumber(blockHash); + } +} diff --git a/src/Nethermind/Nethermind.Facade/Simulate/SimulateOutput.cs b/src/Nethermind/Nethermind.Facade/Simulate/SimulateOutput.cs new file mode 100644 index 00000000000..939de8f3021 --- /dev/null +++ b/src/Nethermind/Nethermind.Facade/Simulate/SimulateOutput.cs @@ -0,0 +1,16 @@ +// SPDX-FileCopyrightText: 2023 Demerzel Solutions Limited +// SPDX-License-Identifier: LGPL-3.0-only + +using System.Collections.Generic; +using Nethermind.Facade.Proxy.Models.Simulate; + +namespace Nethermind.Facade.Simulate; + +public class SimulateOutput +{ + + public string? Error { get; set; } + public int? ErrorCode { get; set; } + + public IReadOnlyList Items { get; set; } +} diff --git a/src/Nethermind/Nethermind.Facade/Simulate/SimulateReadOnlyBlocksProcessingEnv.cs b/src/Nethermind/Nethermind.Facade/Simulate/SimulateReadOnlyBlocksProcessingEnv.cs new file mode 100644 index 00000000000..b11ce18308f --- /dev/null +++ b/src/Nethermind/Nethermind.Facade/Simulate/SimulateReadOnlyBlocksProcessingEnv.cs @@ -0,0 +1,118 @@ +// SPDX-FileCopyrightText: 2023 Demerzel Solutions Limited +// SPDX-License-Identifier: LGPL-3.0-only + +using System; +using Nethermind.Blockchain; +using Nethermind.Blockchain.Blocks; +using Nethermind.Blockchain.Receipts; +using Nethermind.Consensus.Processing; +using Nethermind.Consensus.Rewards; +using Nethermind.Consensus.Validators; +using Nethermind.Core; +using Nethermind.Core.Crypto; +using Nethermind.Core.Specs; +using Nethermind.Db; +using Nethermind.Evm; +using Nethermind.Evm.Tracing; +using Nethermind.Evm.TransactionProcessing; +using Nethermind.Logging; +using Nethermind.State; +using static Nethermind.Consensus.Processing.BlockProcessor; + +namespace Nethermind.Facade.Simulate; + +public class SimulateBlockValidationTransactionsExecutor : BlockValidationTransactionsExecutor +{ + public SimulateBlockValidationTransactionsExecutor(ITransactionProcessor transactionProcessor, IWorldState stateProvider) : base(transactionProcessor, stateProvider) + { + } + + public SimulateBlockValidationTransactionsExecutor(ITransactionProcessorAdapter transactionProcessor, IWorldState stateProvider) : base(transactionProcessor, stateProvider) + { + } + + protected override void ProcessTransaction(in BlockExecutionContext blkCtx, Transaction currentTx, int index, + BlockReceiptsTracer receiptsTracer, ProcessingOptions processingOptions) + { + processingOptions |= ProcessingOptions.ForceProcessing | ProcessingOptions.DoNotVerifyNonce | ProcessingOptions.NoValidation; + base.ProcessTransaction(in blkCtx, currentTx, index, receiptsTracer, processingOptions); + } +} + +public class SimulateReadOnlyBlocksProcessingEnv : ReadOnlyTxProcessingEnvBase, IDisposable +{ + private readonly IBlockValidator _blockValidator; + private readonly ILogManager? _logManager; + private readonly TransactionProcessor _transactionProcessor; + + public SimulateReadOnlyBlocksProcessingEnv( + IWorldStateManager worldStateManager, + IReadOnlyBlockTree baseBlockTree, + IReadOnlyDbProvider readOnlyDbProvider, + IBlockTree blockTree, + ISpecProvider specProvider, + ILogManager? logManager = null, + bool validate = false) + : base(worldStateManager, blockTree, specProvider, logManager) + { + ReadOnlyBlockTree = baseBlockTree; + DbProvider = readOnlyDbProvider; + WorldStateManager = worldStateManager; + _logManager = logManager; + + BlockTree = new BlockTreeOverlay(ReadOnlyBlockTree, blockTree); + BlockhashProvider = new SimulateBlockhashProvider(new BlockhashProvider(BlockTree, specProvider, StateProvider, logManager), BlockTree); + StateProvider = WorldStateManager.GlobalWorldState; + StateReader = WorldStateManager.GlobalStateReader; + CodeInfoRepository = new OverridableCodeInfoRepository(new CodeInfoRepository()); + VirtualMachine = new SimulateVirtualMachine(new VirtualMachine(BlockhashProvider, specProvider, CodeInfoRepository, logManager)); + _transactionProcessor = new SimulateTransactionProcessor(SpecProvider, StateProvider, VirtualMachine, CodeInfoRepository, _logManager, validate); + _blockValidator = CreateValidator(); + BlockTransactionPicker = new BlockProductionTransactionPicker(specProvider, true); + } + + public IWorldStateManager WorldStateManager { get; } + public IVirtualMachine VirtualMachine { get; } + public IReadOnlyDbProvider DbProvider { get; } + public IReadOnlyBlockTree ReadOnlyBlockTree { get; set; } + public OverridableCodeInfoRepository CodeInfoRepository { get; } + public BlockProductionTransactionPicker BlockTransactionPicker { get; } + + public void Dispose() + { + DbProvider.Dispose(); + } + + private SimulateBlockValidatorProxy CreateValidator() + { + HeaderValidator headerValidator = new( + BlockTree, + Always.Valid, + SpecProvider, + _logManager); + + BlockValidator blockValidator = new( + new TxValidator(SpecProvider!.ChainId), + headerValidator, + Always.Valid, + SpecProvider, + _logManager); + + return new SimulateBlockValidatorProxy(blockValidator); + } + + public IBlockProcessor GetProcessor(bool validate) => + new BlockProcessor(SpecProvider, + _blockValidator, + NoBlockRewards.Instance, + validate + ? new BlockValidationTransactionsExecutor(_transactionProcessor, StateProvider) + : new SimulateBlockValidationTransactionsExecutor(_transactionProcessor, StateProvider), + StateProvider, + NullReceiptStorage.Instance, + new BlockhashStore(BlockTree, SpecProvider, StateProvider), + _logManager); + + public IReadOnlyTransactionProcessor Build(Hash256 stateRoot) => + new ReadOnlyTransactionProcessor(_transactionProcessor, StateProvider, stateRoot); +} diff --git a/src/Nethermind/Nethermind.Facade/Simulate/SimulateReadOnlyBlocksProcessingEnvFactory.cs b/src/Nethermind/Nethermind.Facade/Simulate/SimulateReadOnlyBlocksProcessingEnvFactory.cs new file mode 100644 index 00000000000..99078525673 --- /dev/null +++ b/src/Nethermind/Nethermind.Facade/Simulate/SimulateReadOnlyBlocksProcessingEnvFactory.cs @@ -0,0 +1,63 @@ +// SPDX-FileCopyrightText: 2024 Demerzel Solutions Limited +// SPDX-License-Identifier: LGPL-3.0-only + +using Nethermind.Blockchain; +using Nethermind.Blockchain.Blocks; +using Nethermind.Blockchain.Headers; +using Nethermind.Blockchain.Synchronization; +using Nethermind.Core.Specs; +using Nethermind.Db; +using Nethermind.Db.Blooms; +using Nethermind.Logging; +using Nethermind.State; +using Nethermind.State.Repositories; +using Nethermind.Trie.Pruning; + +namespace Nethermind.Facade.Simulate; + +public class SimulateReadOnlyBlocksProcessingEnvFactory( + IWorldStateManager worldStateManager, + IReadOnlyBlockTree baseBlockTree, + IDbProvider dbProvider, + ISpecProvider specProvider, + ILogManager? logManager = null) +{ + public SimulateReadOnlyBlocksProcessingEnv Create(bool validate) + { + IReadOnlyDbProvider editableDbProvider = new ReadOnlyDbProvider(dbProvider, true); + OverlayTrieStore overlayTrieStore = new(editableDbProvider.StateDb, worldStateManager.TrieStore, logManager); + OverlayWorldStateManager overlayWorldStateManager = new(editableDbProvider, overlayTrieStore, logManager); + BlockTree tempBlockTree = CreateTempBlockTree(editableDbProvider, specProvider, logManager, editableDbProvider); + + return new SimulateReadOnlyBlocksProcessingEnv( + overlayWorldStateManager, + baseBlockTree, + editableDbProvider, + tempBlockTree, + specProvider, + logManager, + validate); + } + + private static BlockTree CreateTempBlockTree(IReadOnlyDbProvider readOnlyDbProvider, ISpecProvider? specProvider, ILogManager? logManager, IReadOnlyDbProvider editableDbProvider) + { + IBlockStore mainblockStore = new BlockStore(editableDbProvider.BlocksDb); + IHeaderStore mainHeaderStore = new HeaderStore(editableDbProvider.HeadersDb, editableDbProvider.BlockNumbersDb); + SimulateDictionaryHeaderStore tmpHeaderStore = new(mainHeaderStore); + const int badBlocksStored = 1; + + SimulateDictionaryBlockStore tmpBlockStore = new(mainblockStore); + IBlockStore badBlockStore = new BlockStore(editableDbProvider.BadBlocksDb, badBlocksStored); + + return new(tmpBlockStore, + tmpHeaderStore, + editableDbProvider.BlockInfosDb, + editableDbProvider.MetadataDb, + badBlockStore, + new ChainLevelInfoRepository(readOnlyDbProvider.BlockInfosDb), + specProvider, + NullBloomStorage.Instance, + new SyncConfig(), + logManager); + } +} diff --git a/src/Nethermind/Nethermind.Facade/Simulate/SimulateTransactionProcessor.cs b/src/Nethermind/Nethermind.Facade/Simulate/SimulateTransactionProcessor.cs new file mode 100644 index 00000000000..79499458247 --- /dev/null +++ b/src/Nethermind/Nethermind.Facade/Simulate/SimulateTransactionProcessor.cs @@ -0,0 +1,32 @@ +// 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.Evm.Tracing; +using Nethermind.Evm.TransactionProcessing; +using Nethermind.Logging; +using Nethermind.State; + +namespace Nethermind.Facade.Simulate; + +public class SimulateTransactionProcessor( + ISpecProvider? specProvider, + IWorldState? worldState, + IVirtualMachine? virtualMachine, + ICodeInfoRepository? codeInfoRepository, + ILogManager? logManager, + bool validate) + : TransactionProcessor(specProvider, worldState, virtualMachine, codeInfoRepository, logManager), ITransactionProcessor +{ + protected override TransactionResult Execute(Transaction tx, in BlockExecutionContext blCtx, ITxTracer tracer, ExecutionOptions opts) + { + if (!validate) + { + opts |= ExecutionOptions.NoValidation; + } + + return base.Execute(tx, in blCtx, tracer, opts); + } +} diff --git a/src/Nethermind/Nethermind.Facade/Simulate/SimulateTxMutatorTracer.cs b/src/Nethermind/Nethermind.Facade/Simulate/SimulateTxMutatorTracer.cs new file mode 100644 index 00000000000..4234b78edf9 --- /dev/null +++ b/src/Nethermind/Nethermind.Facade/Simulate/SimulateTxMutatorTracer.cs @@ -0,0 +1,97 @@ +// SPDX-FileCopyrightText: 2023 Demerzel Solutions Limited +// SPDX-License-Identifier: LGPL-3.0-only + +using System; +using System.Collections.Generic; +using System.Linq; +using Nethermind.Abi; +using Nethermind.Core; +using Nethermind.Core.Crypto; +using Nethermind.Evm; +using Nethermind.Evm.Tracing; +using Nethermind.Facade.Proxy.Models.Simulate; +using Nethermind.Int256; +using Log = Nethermind.Facade.Proxy.Models.Simulate.Log; + +namespace Nethermind.Facade.Simulate; + +internal sealed class SimulateTxMutatorTracer : TxTracer, ITxLogsMutator +{ + public const int ExecutionError = -32015; + + private static readonly Hash256 transferSignature = + new AbiSignature("Transfer", AbiType.Address, AbiType.Address, AbiType.UInt256).Hash; + + private static readonly Address Erc20Sender = new("0xeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee"); + private readonly Hash256 _currentBlockHash; + private readonly ulong _currentBlockNumber; + private readonly Hash256 _txHash; + private readonly ulong _txIndex; + private ICollection? _logsToMutate; + + public SimulateTxMutatorTracer(bool isTracingTransfers, Hash256 txHash, ulong currentBlockNumber, Hash256 currentBlockHash, + ulong txIndex) + { + _txHash = txHash; + _currentBlockNumber = currentBlockNumber; + _currentBlockHash = currentBlockHash; + _txIndex = txIndex; + IsTracingReceipt = true; + IsTracingActions = IsMutatingLogs = isTracingTransfers; + } + + public SimulateCallResult? TraceResult { get; set; } + + public bool IsMutatingLogs { get; } + + public void SetLogsToMutate(ICollection logsToMutate) => _logsToMutate = logsToMutate; + + public override void ReportAction(long gas, UInt256 value, Address from, Address to, ReadOnlyMemory input, ExecutionType callType, bool isPrecompileCall = false) + { + base.ReportAction(gas, value, from, to, input, callType, isPrecompileCall); + if (value > UInt256.Zero) + { + var data = AbiEncoder.Instance.Encode(AbiEncodingStyle.Packed, new AbiSignature("", AbiType.UInt256), + value); + _logsToMutate?.Add(new LogEntry(Erc20Sender, data, [transferSignature, from.ToHash(), to.ToHash()])); + } + } + + public override void MarkAsSuccess(Address recipient, long gasSpent, byte[] output, LogEntry[] logs, + Hash256? stateRoot = null) + { + TraceResult = new SimulateCallResult + { + GasUsed = (ulong)gasSpent, + ReturnData = output, + Status = StatusCode.Success, + Logs = logs.Select((entry, i) => new Log + { + Address = entry.LoggersAddress, + Topics = entry.Topics, + Data = entry.Data, + LogIndex = (ulong)i, + TransactionHash = _txHash, + TransactionIndex = _txIndex, + BlockHash = _currentBlockHash, + BlockNumber = _currentBlockNumber + }).ToList() + }; + } + + public override void MarkAsFailed(Address recipient, long gasSpent, byte[] output, string error, + Hash256? stateRoot = null) + { + TraceResult = new SimulateCallResult + { + GasUsed = (ulong)gasSpent, + Error = new Error + { + Code = ExecutionError, // revert error code stub + Message = error + }, + ReturnData = null, + Status = StatusCode.Failure + }; + } +} diff --git a/src/Nethermind/Nethermind.Facade/Simulate/SimulateVirtualMachine.cs b/src/Nethermind/Nethermind.Facade/Simulate/SimulateVirtualMachine.cs new file mode 100644 index 00000000000..5ea81093e80 --- /dev/null +++ b/src/Nethermind/Nethermind.Facade/Simulate/SimulateVirtualMachine.cs @@ -0,0 +1,37 @@ +// SPDX-FileCopyrightText: 2024 Demerzel Solutions Limited +// SPDX-License-Identifier: LGPL-3.0-only + +using System.Diagnostics.CodeAnalysis; +using Nethermind.Evm; +using Nethermind.Evm.Tracing; +using Nethermind.State; + +namespace Nethermind.Facade.Simulate; + +public class SimulateVirtualMachine(IVirtualMachine virtualMachine) : IVirtualMachine +{ + public TransactionSubstate Run(EvmState state, IWorldState worldState, ITxTracer txTracer) where TTracingActions : struct, VirtualMachine.IIsTracing + { + if (typeof(TTracingActions) == typeof(VirtualMachine.IsTracing) && TryGetLogsMutator(txTracer, out ITxLogsMutator logsMutator)) + { + logsMutator.SetLogsToMutate(state.Logs); + } + + return virtualMachine.Run(state, worldState, txTracer); + } + + private static bool TryGetLogsMutator(ITxTracer txTracer, [NotNullWhen(true)] out ITxLogsMutator? txLogsMutator) + { + switch (txTracer) + { + case ITxLogsMutator { IsMutatingLogs: true } logsMutator: + txLogsMutator = logsMutator; + return true; + case ITxTracerWrapper txTracerWrapper: + return TryGetLogsMutator(txTracerWrapper.InnerTracer, out txLogsMutator); + default: + txLogsMutator = null; + return false; + } + } +} diff --git a/src/Nethermind/Nethermind.Facade/StateOverridesExtensions.cs b/src/Nethermind/Nethermind.Facade/StateOverridesExtensions.cs new file mode 100644 index 00000000000..080e6c2a81b --- /dev/null +++ b/src/Nethermind/Nethermind.Facade/StateOverridesExtensions.cs @@ -0,0 +1,132 @@ +// SPDX-FileCopyrightText: 2023 Demerzel Solutions Limited +// SPDX-License-Identifier: LGPL-3.0-only + +using System; +using System.Collections.Generic; +using Nethermind.Core; +using Nethermind.Core.Crypto; +using Nethermind.Core.Extensions; +using Nethermind.Core.Specs; +using Nethermind.Evm.CodeAnalysis; +using Nethermind.Facade.Proxy.Models; +using Nethermind.Int256; +using Nethermind.State; +using Nethermind.Trie; + +namespace Nethermind.Facade; + +public static class StateOverridesExtensions +{ + public static void ApplyStateOverrides( + this IWorldState state, + OverridableCodeInfoRepository overridableCodeInfoRepository, + Dictionary? overrides, + IReleaseSpec spec, + long blockNumber) + { + if (overrides is not null) + { + foreach ((Address address, AccountOverride accountOverride) in overrides) + { + if (!state.TryGetAccount(address, out AccountStruct account)) + { + state.CreateAccount(address, accountOverride.Balance ?? UInt256.Zero, accountOverride.Nonce ?? UInt256.Zero); + } + else + { + state.UpdateBalance(spec, account, accountOverride, address); + state.UpdateNonce(account, accountOverride, address); + } + + state.UpdateCode(overridableCodeInfoRepository, spec, accountOverride, address); + state.UpdateState(accountOverride, address); + } + } + + state.Commit(spec); + state.CommitTree(blockNumber); + state.RecalculateStateRoot(); + } + + private static void UpdateState(this IWorldState stateProvider, AccountOverride accountOverride, Address address) + { + void ApplyState(Dictionary diff) + { + foreach ((UInt256 index, Hash256 value) in diff) + { + stateProvider.Set(new StorageCell(address, index), value.Bytes.WithoutLeadingZeros().ToArray()); + } + } + + if (accountOverride.State is not null) + { + stateProvider.ClearStorage(address); + ApplyState(accountOverride.State); + } + else if (accountOverride.StateDiff is not null) + { + ApplyState(accountOverride.StateDiff); + } + } + + private static void UpdateCode( + this IWorldState stateProvider, + OverridableCodeInfoRepository overridableCodeInfoRepository, + IReleaseSpec currentSpec, + AccountOverride accountOverride, + Address address) + { + if (accountOverride.Code is not null) + { + overridableCodeInfoRepository.SetCodeOverwrite( + stateProvider, + currentSpec, + address, + new CodeInfo(accountOverride.Code), + accountOverride.MovePrecompileToAddress); + } + } + + private static void UpdateNonce( + this IWorldState stateProvider, + in AccountStruct account, + AccountOverride accountOverride, + Address address) + { + if (accountOverride.Nonce is not null) + { + UInt256 nonce = account.Nonce; + UInt256 newNonce = accountOverride.Nonce.Value; + if (nonce > newNonce) + { + stateProvider.DecrementNonce(address, nonce - newNonce); + } + else if (nonce < accountOverride.Nonce) + { + stateProvider.IncrementNonce(address, newNonce - nonce); + } + } + } + + private static void UpdateBalance( + this IWorldState stateProvider, + IReleaseSpec spec, + in AccountStruct account, + AccountOverride accountOverride, + Address address) + { + if (accountOverride.Balance is not null) + { + UInt256 balance = account.Balance; + UInt256 newBalance = accountOverride.Balance.Value; + if (balance > newBalance) + { + stateProvider.SubtractFromBalance(address, balance - newBalance, spec); + } + else if (balance < newBalance) + { + stateProvider.AddToBalance(address, newBalance - balance, spec); + } + } + } +} diff --git a/src/Nethermind/Nethermind.Init/Steps/InitializeBlockchain.cs b/src/Nethermind/Nethermind.Init/Steps/InitializeBlockchain.cs index 49ff7033eb0..116b08dfae9 100644 --- a/src/Nethermind/Nethermind.Init/Steps/InitializeBlockchain.cs +++ b/src/Nethermind/Nethermind.Init/Steps/InitializeBlockchain.cs @@ -161,16 +161,20 @@ protected virtual ITransactionProcessor CreateTransactionProcessor() { if (_api.SpecProvider is null) throw new StepDependencyException(nameof(_api.SpecProvider)); - VirtualMachine virtualMachine = CreateVirtualMachine(); + CodeInfoRepository codeInfoRepository = new(); + VirtualMachine virtualMachine = CreateVirtualMachine(codeInfoRepository); - return new TransactionProcessor( + TransactionProcessor transactionProcessor = new( _api.SpecProvider, _api.WorldState, virtualMachine, + codeInfoRepository, _api.LogManager); + + return transactionProcessor; } - protected virtual VirtualMachine CreateVirtualMachine() + protected VirtualMachine CreateVirtualMachine(CodeInfoRepository codeInfoRepository) { if (_api.BlockTree is null) throw new StepDependencyException(nameof(_api.BlockTree)); if (_api.SpecProvider is null) throw new StepDependencyException(nameof(_api.SpecProvider)); @@ -180,10 +184,13 @@ protected virtual VirtualMachine CreateVirtualMachine() BlockhashProvider blockhashProvider = new( _api.BlockTree, _api.SpecProvider, _api.WorldState, _api.LogManager); - return new VirtualMachine( + VirtualMachine virtualMachine = new( blockhashProvider, _api.SpecProvider, + codeInfoRepository, _api.LogManager); + + return virtualMachine; } protected virtual IHealthHintService CreateHealthHintService() => diff --git a/src/Nethermind/Nethermind.Init/Steps/RegisterRpcModules.cs b/src/Nethermind/Nethermind.Init/Steps/RegisterRpcModules.cs index 4c2939de9c4..db5341d33b4 100644 --- a/src/Nethermind/Nethermind.Init/Steps/RegisterRpcModules.cs +++ b/src/Nethermind/Nethermind.Init/Steps/RegisterRpcModules.cs @@ -7,6 +7,7 @@ using System.Threading.Tasks; using Nethermind.Api; using Nethermind.Blockchain.FullPruning; +using Nethermind.Config; using Nethermind.Core; using Nethermind.Init.Steps.Migrations; using Nethermind.JsonRpc; @@ -73,6 +74,7 @@ public virtual async Task Execute(CancellationToken cancellationToken) IInitConfig initConfig = _api.Config(); INetworkConfig networkConfig = _api.Config(); + // lets add threads to support parallel eth_getLogs ThreadPool.GetMinThreads(out int workerThreads, out int completionPortThreads); ThreadPool.SetMinThreads(workerThreads + Environment.ProcessorCount, completionPortThreads + Environment.ProcessorCount); @@ -212,9 +214,14 @@ protected ModuleFactoryBase CreateEthModuleFactory() StepDependencyException.ThrowIfNull(_api.StateReader); StepDependencyException.ThrowIfNull(_api.GasPriceOracle); StepDependencyException.ThrowIfNull(_api.EthSyncingInfo); + StepDependencyException.ThrowIfNull(_api.EthSyncingInfo); var feeHistoryOracle = new FeeHistoryOracle(_api.BlockTree, _api.ReceiptStorage, _api.SpecProvider); _api.DisposeStack.Push(feeHistoryOracle); + + IBlocksConfig blockConfig = _api.Config(); + ulong secondsPerSlot = blockConfig.SecondsPerSlot; + return new EthModuleFactory( _api.TxPool, _api.TxSender, @@ -228,7 +235,8 @@ protected ModuleFactoryBase CreateEthModuleFactory() _api.ReceiptStorage, _api.GasPriceOracle, _api.EthSyncingInfo, - feeHistoryOracle); + feeHistoryOracle, + secondsPerSlot); } protected virtual void RegisterEthRpcModule(IRpcModuleProvider rpcModuleProvider) diff --git a/src/Nethermind/Nethermind.JsonRpc.Benchmark/EthModuleBenchmarks.cs b/src/Nethermind/Nethermind.JsonRpc.Benchmark/EthModuleBenchmarks.cs index 9a92edf772b..246c6052c22 100644 --- a/src/Nethermind/Nethermind.JsonRpc.Benchmark/EthModuleBenchmarks.cs +++ b/src/Nethermind/Nethermind.JsonRpc.Benchmark/EthModuleBenchmarks.cs @@ -36,6 +36,7 @@ using BlockTree = Nethermind.Blockchain.BlockTree; using Nethermind.Blockchain.Synchronization; using Nethermind.Config; +using Nethermind.Facade.Simulate; using Nethermind.Synchronization.ParallelSync; namespace Nethermind.JsonRpc.Benchmark @@ -80,7 +81,8 @@ public void GlobalSetup() new SyncConfig(), LimboLogs.Instance); _blockhashProvider = new BlockhashProvider(blockTree, specProvider, stateProvider, LimboLogs.Instance); - _virtualMachine = new VirtualMachine(_blockhashProvider, specProvider, LimboLogs.Instance); + CodeInfoRepository codeInfoRepository = new(); + _virtualMachine = new VirtualMachine(_blockhashProvider, specProvider, codeInfoRepository, LimboLogs.Instance); Block genesisBlock = Build.A.Block.Genesis.TestObject; blockTree.SuggestBlock(genesisBlock); @@ -89,7 +91,7 @@ public void GlobalSetup() blockTree.SuggestBlock(block1); TransactionProcessor transactionProcessor - = new(MainnetSpecProvider.Instance, stateProvider, _virtualMachine, LimboLogs.Instance); + = new(MainnetSpecProvider.Instance, stateProvider, _virtualMachine, codeInfoRepository, LimboLogs.Instance); IBlockProcessor.IBlockTransactionsExecutor transactionsExecutor = new BlockProcessor.BlockValidationTransactionsExecutor(transactionProcessor, stateProvider); BlockProcessor blockProcessor = new(specProvider, Always.Valid, new RewardCalculator(specProvider), transactionsExecutor, @@ -127,6 +129,12 @@ TransactionProcessor transactionProcessor new ReadOnlyBlockTree(blockTree), specProvider, LimboLogs.Instance), + new SimulateReadOnlyBlocksProcessingEnvFactory( + stateManager, + new ReadOnlyBlockTree(blockTree), + new ReadOnlyDbProvider(dbProvider, true), + specProvider, + LimboLogs.Instance), NullTxPool.Instance, NullReceiptStorage.Instance, NullFilterStore.Instance, @@ -158,7 +166,8 @@ TransactionProcessor transactionProcessor specProvider, gasPriceOracle, ethSyncingInfo, - feeHistoryOracle); + feeHistoryOracle, + new BlocksConfig().SecondsPerSlot); } [Benchmark] diff --git a/src/Nethermind/Nethermind.JsonRpc.Test/ConsensusHelperTests.ReceiptsJsonRpcDataSource.cs b/src/Nethermind/Nethermind.JsonRpc.Test/ConsensusHelperTests.ReceiptsJsonRpcDataSource.cs index 081ebeddd34..fe635d2b871 100644 --- a/src/Nethermind/Nethermind.JsonRpc.Test/ConsensusHelperTests.ReceiptsJsonRpcDataSource.cs +++ b/src/Nethermind/Nethermind.JsonRpc.Test/ConsensusHelperTests.ReceiptsJsonRpcDataSource.cs @@ -6,6 +6,7 @@ using System.Linq; using System.Threading.Tasks; using Nethermind.Core.Crypto; +using Nethermind.Facade.Eth; using Nethermind.JsonRpc.Data; using Nethermind.JsonRpc.Modules.Eth; using Nethermind.Serialization.Json; diff --git a/src/Nethermind/Nethermind.JsonRpc.Test/Data/AccessListItemForRpcTests.cs b/src/Nethermind/Nethermind.JsonRpc.Test/Data/AccessListItemForRpcTests.cs index 31345ac4e63..ae948e1d693 100644 --- a/src/Nethermind/Nethermind.JsonRpc.Test/Data/AccessListItemForRpcTests.cs +++ b/src/Nethermind/Nethermind.JsonRpc.Test/Data/AccessListItemForRpcTests.cs @@ -6,6 +6,7 @@ using Nethermind.Core; using Nethermind.Core.Eip2930; using Nethermind.Core.Test.Builders; +using Nethermind.Facade.Eth; using Nethermind.Int256; using Nethermind.JsonRpc.Data; using NUnit.Framework; diff --git a/src/Nethermind/Nethermind.JsonRpc.Test/Data/Eip2930Tests.cs b/src/Nethermind/Nethermind.JsonRpc.Test/Data/Eip2930Tests.cs index a9176491bdf..f34daa6963f 100644 --- a/src/Nethermind/Nethermind.JsonRpc.Test/Data/Eip2930Tests.cs +++ b/src/Nethermind/Nethermind.JsonRpc.Test/Data/Eip2930Tests.cs @@ -8,6 +8,7 @@ using Nethermind.Core.Eip2930; using Nethermind.Core.Extensions; using Nethermind.Core.Test.Builders; +using Nethermind.Facade.Eth; using Nethermind.Int256; using Nethermind.JsonRpc.Data; using Nethermind.Serialization.Json; diff --git a/src/Nethermind/Nethermind.JsonRpc.Test/JsonRpcServiceTests.cs b/src/Nethermind/Nethermind.JsonRpc.Test/JsonRpcServiceTests.cs index 0485a2e4528..00b2e77d735 100644 --- a/src/Nethermind/Nethermind.JsonRpc.Test/JsonRpcServiceTests.cs +++ b/src/Nethermind/Nethermind.JsonRpc.Test/JsonRpcServiceTests.cs @@ -16,6 +16,8 @@ using Nethermind.Core.Extensions; using Nethermind.Core.Specs; using Nethermind.Core.Test.Builders; +using Nethermind.Facade.Eth; +using Nethermind.Facade.Proxy.Models.Simulate; using Nethermind.Int256; using Nethermind.JsonRpc.Data; using Nethermind.JsonRpc.Modules; @@ -92,13 +94,27 @@ public void Eth_module_populates_size_when_returning_block_data() Assert.That((response?.Result as BlockForRpc)?.Size, Is.EqualTo(513L)); } + + [Test] + public void CanRunEthSimulateV1Empty() + { + SimulatePayload payload = new() { BlockStateCalls = new List>() }; + string serializedCall = new EthereumJsonSerializer().Serialize(payload); + IEthRpcModule ethRpcModule = Substitute.For(); + ethRpcModule.eth_simulateV1(payload).ReturnsForAnyArgs(_ => + ResultWrapper>.Success(Array.Empty())); + JsonRpcSuccessResponse? response = TestRequest(ethRpcModule, "eth_simulateV1", serializedCall) as JsonRpcSuccessResponse; + Assert.That(response?.Result, Is.EqualTo(Array.Empty())); + } + + [Test] public void CanHandleOptionalArguments() { EthereumJsonSerializer serializer = new(); string serialized = serializer.Serialize(new TransactionForRpc()); IEthRpcModule ethRpcModule = Substitute.For(); - ethRpcModule.eth_call(Arg.Any()).ReturnsForAnyArgs(x => ResultWrapper.Success("0x1")); + ethRpcModule.eth_call(Arg.Any()).ReturnsForAnyArgs(_ => ResultWrapper.Success("0x1")); JsonRpcSuccessResponse? response = TestRequest(ethRpcModule, "eth_call", serialized) as JsonRpcSuccessResponse; Assert.That(response?.Result, Is.EqualTo("0x1")); } diff --git a/src/Nethermind/Nethermind.JsonRpc.Test/Modules/BoundedModulePoolTests.cs b/src/Nethermind/Nethermind.JsonRpc.Test/Modules/BoundedModulePoolTests.cs index 341c574c4dd..5a09f99127e 100644 --- a/src/Nethermind/Nethermind.JsonRpc.Test/Modules/BoundedModulePoolTests.cs +++ b/src/Nethermind/Nethermind.JsonRpc.Test/Modules/BoundedModulePoolTests.cs @@ -4,6 +4,7 @@ using System.Threading.Tasks; using Nethermind.Blockchain.Receipts; using Nethermind.Blockchain.Synchronization; +using Nethermind.Config; using Nethermind.Core.Specs; using Nethermind.Core.Test.Builders; using Nethermind.Db; @@ -56,7 +57,8 @@ public Task Initialize() Substitute.For(), Substitute.For(), Substitute.For(), - Substitute.For()), + Substitute.For(), + new BlocksConfig().SecondsPerSlot), 1, 1000); return Task.CompletedTask; diff --git a/src/Nethermind/Nethermind.JsonRpc.Test/Modules/DebugModuleTests.cs b/src/Nethermind/Nethermind.JsonRpc.Test/Modules/DebugModuleTests.cs index 064fff4e886..e66d93d334f 100644 --- a/src/Nethermind/Nethermind.JsonRpc.Test/Modules/DebugModuleTests.cs +++ b/src/Nethermind/Nethermind.JsonRpc.Test/Modules/DebugModuleTests.cs @@ -19,6 +19,7 @@ using Nethermind.Db; using Nethermind.Evm.Tracing.GethStyle; using Nethermind.Evm.Tracing.GethStyle.Custom; +using Nethermind.Facade.Eth; using Nethermind.Int256; using Nethermind.JsonRpc.Data; using Nethermind.JsonRpc.Modules.DebugModule; diff --git a/src/Nethermind/Nethermind.JsonRpc.Test/Modules/Eth/EthRpcModuleTests.EstimateGas.cs b/src/Nethermind/Nethermind.JsonRpc.Test/Modules/Eth/EthRpcModuleTests.EstimateGas.cs index e40b064b796..0bc1a37fb09 100644 --- a/src/Nethermind/Nethermind.JsonRpc.Test/Modules/Eth/EthRpcModuleTests.EstimateGas.cs +++ b/src/Nethermind/Nethermind.JsonRpc.Test/Modules/Eth/EthRpcModuleTests.EstimateGas.cs @@ -10,6 +10,7 @@ using Nethermind.Core.Test.Builders; using Nethermind.Evm; using Nethermind.Evm.Tracing; +using Nethermind.Facade.Eth; using Nethermind.JsonRpc.Data; using Nethermind.Specs; using Nethermind.Specs.Forks; diff --git a/src/Nethermind/Nethermind.JsonRpc.Test/Modules/Eth/EthRpcModuleTests.EthCall.cs b/src/Nethermind/Nethermind.JsonRpc.Test/Modules/Eth/EthRpcModuleTests.EthCall.cs index cd7fb615ec8..ed73767fcf1 100644 --- a/src/Nethermind/Nethermind.JsonRpc.Test/Modules/Eth/EthRpcModuleTests.EthCall.cs +++ b/src/Nethermind/Nethermind.JsonRpc.Test/Modules/Eth/EthRpcModuleTests.EthCall.cs @@ -8,6 +8,7 @@ using Nethermind.Core.Extensions; using Nethermind.Core.Test.Builders; using Nethermind.Evm; +using Nethermind.Facade.Eth; using Nethermind.JsonRpc.Data; using Nethermind.Specs; using Nethermind.Specs.Forks; diff --git a/src/Nethermind/Nethermind.JsonRpc.Test/Modules/Eth/EthRpcModuleTests.cs b/src/Nethermind/Nethermind.JsonRpc.Test/Modules/Eth/EthRpcModuleTests.cs index 107e473b58b..b613c75e75c 100644 --- a/src/Nethermind/Nethermind.JsonRpc.Test/Modules/Eth/EthRpcModuleTests.cs +++ b/src/Nethermind/Nethermind.JsonRpc.Test/Modules/Eth/EthRpcModuleTests.cs @@ -22,6 +22,7 @@ using Nethermind.Evm; using Nethermind.Evm.Tracing; using Nethermind.Facade; +using Nethermind.Facade.Eth; using Nethermind.Facade.Filters; using Nethermind.Int256; using Nethermind.JsonRpc.Data; @@ -64,6 +65,7 @@ public async Task Eth_get_balance_default_block() } [Test] + [SetCulture("en-US")] public async Task Eth_get_eth_feeHistory() { using Context ctx = await Context.Create(); diff --git a/src/Nethermind/Nethermind.JsonRpc.Test/Modules/Eth/EthRpcSimulateTestsBase.cs b/src/Nethermind/Nethermind.JsonRpc.Test/Modules/Eth/EthRpcSimulateTestsBase.cs new file mode 100644 index 00000000000..b3d887961c1 --- /dev/null +++ b/src/Nethermind/Nethermind.JsonRpc.Test/Modules/Eth/EthRpcSimulateTestsBase.cs @@ -0,0 +1,149 @@ +// SPDX-FileCopyrightText: 2023 Demerzel Solutions Limited +// SPDX-License-Identifier: LGPL-3.0-only + +using System.Linq; +using System.Threading.Tasks; +using FluentAssertions; +using Nethermind.Abi; +using Nethermind.Blockchain.Contracts.Json; +using Nethermind.Blockchain.Find; +using Nethermind.Consensus; +using Nethermind.Core; +using Nethermind.Core.Crypto; +using Nethermind.Core.Extensions; +using Nethermind.Core.Specs; +using Nethermind.Crypto; +using Nethermind.Facade.Eth; +using Nethermind.Int256; +using Nethermind.JsonRpc.Data; +using Nethermind.Logging; +using Nethermind.Specs; +using Nethermind.Specs.Forks; +using Nethermind.TxPool; + +namespace Nethermind.JsonRpc.Test.Modules.Eth; + +public class EthRpcSimulateTestsBase +{ + public static Task CreateChain(IReleaseSpec? releaseSpec = null) + { + TestRpcBlockchain testMevRpcBlockchain = new(); + TestSpecProvider testSpecProvider = releaseSpec is not null + ? new TestSpecProvider(releaseSpec) + : new TestSpecProvider(London.Instance); + return TestRpcBlockchain.ForTest(testMevRpcBlockchain).Build(testSpecProvider); + } + + private static string GetEcRecoverContractJsonAbi(string name = "recover") + { + return $@" +[ + {{ + ""payable"": false, + ""inputs"": [ + {{ + ""internalType"": ""bytes32"", + ""name"": ""hash"", + ""type"": ""bytes32"" + }}, + {{ + ""internalType"": ""uint8"", + ""name"": ""v"", + ""type"": ""uint8"" + + }}, + {{ + ""internalType"": ""bytes32"", + ""name"": ""r"", + ""type"": ""bytes32"" + }}, + {{ + ""internalType"": ""bytes32"", + ""name"": ""s"", + ""type"": ""bytes32"" + }} + ], + ""name"": ""{name}"", + ""outputs"": [ + {{ + ""internalType"": ""address"", + ""name"": """", + ""type"": ""address"" + }} + ], + ""stateMutability"": ""pure"", + ""type"": ""function"" + }} +]"; + } + + public static byte[] GetTxData(TestRpcBlockchain chain, PrivateKey account, string name = "recover") + { + // Step 1: Hash the message + Hash256 messageHash = Keccak.Compute("Hello, world!"); + // Step 2: Sign the hash + Signature signature = chain.EthereumEcdsa.Sign(account, messageHash); + + //Check real address + return GenerateTransactionDataForEcRecover(messageHash, signature, name); + } + + public static async Task
DeployEcRecoverContract(TestRpcBlockchain chain, PrivateKey privateKey, string contractBytecode) + { + byte[] bytecode = Bytes.FromHexString(contractBytecode); + Transaction tx = new() + { + Value = UInt256.Zero, + Nonce = 0, + Data = bytecode, + GasLimit = 3_000_000, + SenderAddress = privateKey.Address, + To = null, + GasPrice = 20.GWei() + }; + + TxPoolSender txSender = new(chain.TxPool, + new TxSealer(new Signer(chain.SpecProvider.ChainId, privateKey, LimboLogs.Instance), chain.Timestamper), + chain.NonceManager, + chain.EthereumEcdsa); + + (Hash256 hash, AcceptTxResult? code) = await txSender.SendTransaction(tx, TxHandlingOptions.ManagedNonce | TxHandlingOptions.PersistentBroadcast); + + code?.Should().Be(AcceptTxResult.Accepted); + Transaction[] txs = chain.TxPool.GetPendingTransactions(); + await chain.AddBlock(true, txs); + + TxReceipt? createContractTxReceipt = null; + while (createContractTxReceipt is null) + { + await Task.Delay(100); + createContractTxReceipt = chain.Bridge.GetReceipt(hash); + } + + createContractTxReceipt.ContractAddress.Should().NotBeNull($"Contract transaction {tx.Hash!} was not deployed."); + return createContractTxReceipt.ContractAddress!; + } + + protected static byte[] GenerateTransactionDataForEcRecover(Hash256 keccak, Signature signature, string name = "recover") + { + AbiDefinition call = new AbiDefinitionParser().Parse(GetEcRecoverContractJsonAbi(name)); + AbiEncodingInfo functionInfo = call.GetFunction(name).GetCallInfo(); + return AbiEncoder.Instance.Encode(functionInfo.EncodingStyle, functionInfo.Signature, keccak, signature.V, signature.R, signature.S); + } + + private static Address? ParseEcRecoverAddress(byte[] data, string name = "recover") + { + AbiDefinition call = new AbiDefinitionParser().Parse(GetEcRecoverContractJsonAbi(name)); + AbiEncodingInfo functionInfo = call.GetFunction(name).GetReturnInfo(); + return AbiEncoder.Instance.Decode(functionInfo.EncodingStyle, functionInfo.Signature, data).FirstOrDefault() as Address; + } + + public static Address? EcRecoverCall(TestRpcBlockchain testRpcBlockchain, Address senderAddress, byte[] bytes, Address? toAddress = null) + { + SystemTransaction transaction = new() { Data = bytes, To = toAddress, SenderAddress = senderAddress }; + transaction.Hash = transaction.CalculateHash(); + TransactionForRpc transactionForRpc = new(transaction); + ResultWrapper mainChainResult = testRpcBlockchain.EthRpcModule.eth_call(transactionForRpc, BlockParameter.Pending); + return ParseEcRecoverAddress(Bytes.FromHexString(mainChainResult.Data)); + } +} diff --git a/src/Nethermind/Nethermind.JsonRpc.Test/Modules/Eth/EthSimulateTestsPrecompilesWithRedirection.cs b/src/Nethermind/Nethermind.JsonRpc.Test/Modules/Eth/EthSimulateTestsPrecompilesWithRedirection.cs new file mode 100644 index 00000000000..33d80a28d30 --- /dev/null +++ b/src/Nethermind/Nethermind.JsonRpc.Test/Modules/Eth/EthSimulateTestsPrecompilesWithRedirection.cs @@ -0,0 +1,194 @@ +// SPDX-FileCopyrightText: 2023 Demerzel Solutions Limited +// SPDX-License-Identifier: LGPL-3.0-only + +using System.Collections.Generic; +using System.Diagnostics; +using System.Linq; +using System.Threading.Tasks; +using Nethermind.Blockchain.Find; +using Nethermind.Config; +using Nethermind.Core; +using Nethermind.Core.Crypto; +using Nethermind.Core.Extensions; +using Nethermind.Core.Test.Builders; +using Nethermind.Evm; +using Nethermind.Evm.Precompiles; +using Nethermind.Facade.Eth; +using Nethermind.Facade.Proxy.Models; +using Nethermind.Facade.Proxy.Models.Simulate; +using Nethermind.JsonRpc.Data; +using Nethermind.JsonRpc.Modules.Eth; +using NUnit.Framework; + +namespace Nethermind.JsonRpc.Test.Modules.Eth; + +public class EthSimulateTestsPrecompilesWithRedirection +{ + [Test] + public async Task Test_eth_simulate_create() + { + TestRpcBlockchain chain = await EthRpcSimulateTestsBase.CreateChain(); + + Transaction systemTransactionForModifiedVm = new() + { + SenderAddress = TestItem.AddressB, + Data = Bytes.FromHexString("0xee82ac5e0000000000000000000000000000000000000000000000000000000000000001"), + To = TestItem.AddressA, + GasLimit = 3_500_000, + GasPrice = 20.GWei() + }; + + TransactionForRpc transactionForRpc = new(systemTransactionForModifiedVm) { Nonce = null }; + + SimulatePayload payload = new() + { + BlockStateCalls = + [ + new() + { + StateOverrides = new Dictionary + { + { + TestItem.AddressA, + new AccountOverride + { + Code = Bytes.FromHexString("0x6080604052348015600f57600080fd5b506004361060285760003560e01c8063ee82ac5e14602d575b600080fd5b60436004803603810190603f91906098565b6057565b604051604e919060d7565b60405180910390f35b600081409050919050565b600080fd5b6000819050919050565b6078816067565b8114608257600080fd5b50565b6000813590506092816071565b92915050565b60006020828403121560ab5760aa6062565b5b600060b7848285016085565b91505092915050565b6000819050919050565b60d18160c0565b82525050565b600060208201905060ea600083018460ca565b9291505056fea2646970667358221220a4d7face162688805e99e86526524ac3dadfb01cc29366d0d68b70dadcf01afe64736f6c63430008120033") + } + }, + }, + Calls = [transactionForRpc] + } + ] + }; + + //will mock our GetCachedCodeInfo function - it shall be called 3 times if redirect is working, 2 times if not + SimulateTxExecutor executor = new(chain.Bridge, chain.BlockFinder, new JsonRpcConfig(), new BlocksConfig().SecondsPerSlot); + + ResultWrapper> result = executor.Execute(payload, BlockParameter.Latest); + + //Check results + byte[]? returnData = result.Data[0].Calls.First().ReturnData; + Assert.IsNotNull(returnData); + } + + + /// + /// This test verifies that a temporary forked blockchain can redirect precompiles + /// + [Test] + public async Task Test_eth_simulate_ecr_moved() + { + TestRpcBlockchain chain = await EthRpcSimulateTestsBase.CreateChain(); + //The following opcodes code is based on the following contract compiled: + /* + function ecrecover(bytes32 hash, uint8 v, bytes32 r, bytes32 s) public returns(address) + { + + address redirectedToAddress = 0x0000000000000000000000000000000000000666; + + assembly { + // Copy msg.data. We take full control of memory in this inline assembly + // block because it will not return to Solidity code. We overwrite the + // Solidity scratch pad at memory position 0. + calldatacopy(0, 0, calldatasize()) + + // Call the implementation. + // out and outsize are 0 because we don't know the size yet. + let result := delegatecall(gas(), redirectedToAddress, 0, calldatasize(), 0, 0) + + // Copy the returned data. + returndatacopy(0, 0, returndatasize()) + + return (0, returndatasize()) + } + } + */ + + byte[] code = Prepare.EvmCode + .JUMPDEST() + .PushData(Bytes.ZeroByte) + .Op(Instruction.DUP1) + .PushData(TestItem.AddressB.Bytes) + .Op(Instruction.SWAP1) + .Op(Instruction.POP) + .Op(Instruction.CALLDATASIZE) + .PushData(Bytes.ZeroByte) + .Op(Instruction.DUP1) + .Op(Instruction.CALLDATACOPY) + .PushData(Bytes.ZeroByte) + .Op(Instruction.DUP1) + .Op(Instruction.CALLDATASIZE) + .PushData(Bytes.ZeroByte) + .Op(Instruction.DUP5) + .Op(Instruction.GAS) + .Op(Instruction.DELEGATECALL) + .Op(Instruction.RETURNDATASIZE) + .PushData(Bytes.ZeroByte) + .Op(Instruction.DUP1) + .Op(Instruction.RETURNDATACOPY) + .Op(Instruction.RETURNDATASIZE) + .PushData(Bytes.ZeroByte) + .Op(Instruction.RETURN) + .Done; + + byte[] transactionData = EthRpcSimulateTestsBase.GetTxData(chain, TestItem.PrivateKeyA); + + Hash256 headHash = chain.BlockFinder.Head!.Hash!; + Address contractAddress = await EthRpcSimulateTestsBase.DeployEcRecoverContract(chain, TestItem.PrivateKeyB, EthSimulateTestsSimplePrecompiles.EcRecoverCallerContractBytecode); + + EthRpcSimulateTestsBase.EcRecoverCall(chain, TestItem.AddressB, transactionData, contractAddress); + + chain.BlockTree.UpdateMainChain(new List { chain.BlockFinder.Head! }, true, true); + chain.BlockTree.UpdateHeadBlock(chain.BlockFinder.Head!.Hash!); + + Assert.That(headHash != chain.BlockFinder.Head!.Hash!); + chain.State.StateRoot = chain.BlockFinder.Head!.StateRoot!; + + TransactionForRpc transactionForRpc = new(new Transaction + { + Data = transactionData, + To = contractAddress, + SenderAddress = TestItem.AddressA, + GasLimit = 3_500_000, + GasPrice = 20.GWei() + }) + { + Nonce = null + }; + + SimulatePayload payload = new() + { + BlockStateCalls = + [ + new() + { + StateOverrides = new Dictionary + { + { + EcRecoverPrecompile.Address, + new AccountOverride + { + Code = code, + MovePrecompileToAddress = TestItem.AddressB + } + } + }, + Calls = [transactionForRpc] + } + ] + }; + + //will mock our GetCachedCodeInfo function - it shall be called 3 times if redirect is working, 2 times if not + SimulateTxExecutor executor = new(chain.Bridge, chain.BlockFinder, new JsonRpcConfig(), new BlocksConfig().SecondsPerSlot); + + Debug.Assert(contractAddress is not null, nameof(contractAddress) + " is not null"); + Assert.IsTrue(chain.State.AccountExists(contractAddress)); + + ResultWrapper> result = executor.Execute(payload, BlockParameter.Latest); + + //Check results + byte[] addressBytes = result.Data[0].Calls[0].ReturnData!.SliceWithZeroPaddingEmptyOnError(12, 20); + Address resultingAddress = new(addressBytes); + Assert.That(resultingAddress, Is.EqualTo(TestItem.AddressA)); + } +} diff --git a/src/Nethermind/Nethermind.JsonRpc.Test/Modules/Eth/Simulate/EthSimulateTestsBlocksAndTransactions.cs b/src/Nethermind/Nethermind.JsonRpc.Test/Modules/Eth/Simulate/EthSimulateTestsBlocksAndTransactions.cs new file mode 100644 index 00000000000..233f9bd8d49 --- /dev/null +++ b/src/Nethermind/Nethermind.JsonRpc.Test/Modules/Eth/Simulate/EthSimulateTestsBlocksAndTransactions.cs @@ -0,0 +1,281 @@ +// SPDX-FileCopyrightText: 2023 Demerzel Solutions Limited +// SPDX-License-Identifier: LGPL-3.0-only + +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using FluentAssertions; +using Nethermind.Blockchain.Find; +using Nethermind.Config; +using Nethermind.Core; +using Nethermind.Core.Extensions; +using Nethermind.Core.Test.Builders; +using Nethermind.Crypto; +using Nethermind.Facade.Eth; +using Nethermind.Facade.Proxy.Models; +using Nethermind.Facade.Proxy.Models.Simulate; +using Nethermind.Int256; +using Nethermind.JsonRpc.Data; +using Nethermind.JsonRpc.Modules.Eth; +using Nethermind.Serialization.Json; +using NUnit.Framework; +using ResultType = Nethermind.Facade.Proxy.Models.Simulate.ResultType; + +namespace Nethermind.JsonRpc.Test.Modules.Eth; + +public class EthSimulateTestsBlocksAndTransactions +{ + private static Transaction GetTransferTxData(UInt256 nonce, IEthereumEcdsa ethereumEcdsa, PrivateKey from, Address to, UInt256 ammount) + { + Transaction tx = new() + { + Value = ammount, + Nonce = nonce, + GasLimit = 50_000, + SenderAddress = from.Address, + To = to, + GasPrice = 20.GWei() + }; + + ethereumEcdsa.Sign(from, tx); + tx.Hash = tx.CalculateHash(); + return tx; + } + + [Test] + public async Task Test_eth_simulate_serialisation() + { + TestRpcBlockchain chain = await EthRpcSimulateTestsBase.CreateChain(); + + UInt256 nonceA = chain.State.GetNonce(TestItem.AddressA); + Transaction txToFail = GetTransferTxData(nonceA, chain.EthereumEcdsa, TestItem.PrivateKeyA, TestItem.AddressB, 10_000_000); + UInt256 nextNonceA = ++nonceA; + Transaction tx = GetTransferTxData(nextNonceA, chain.EthereumEcdsa, TestItem.PrivateKeyA, TestItem.AddressB, 4_000_000); + + SimulatePayload payload = new() + { + BlockStateCalls = + [ + new() + { + BlockOverrides = new BlockOverride { Number = 10 }, + Calls = [new TransactionForRpc(txToFail), new TransactionForRpc(tx)], + StateOverrides = new Dictionary + { + { TestItem.AddressA, new AccountOverride { Balance = Math.Max(420_000_004_000_001UL, 1_000_000_004_000_001UL) } } + } + } + ], + TraceTransfers = true, + Validation = true + }; + + //Force persistence of head block in main chain + chain.BlockTree.UpdateMainChain(new List { chain.BlockFinder.Head! }, true, true); + chain.BlockTree.UpdateHeadBlock(chain.BlockFinder.Head!.Hash!); + + //will mock our GetCachedCodeInfo function - it shall be called 3 times if redirect is working, 2 times if not + SimulateTxExecutor executor = new(chain.Bridge, chain.BlockFinder, new JsonRpcConfig(), new BlocksConfig().SecondsPerSlot); + ResultWrapper> result = executor.Execute(payload, BlockParameter.Latest); + IReadOnlyList data = result.Data; + Assert.That(data.Count, Is.EqualTo(7)); + + SimulateBlockResult blockResult = data.Last(); + blockResult.Calls.Select(c => c.Status).Should().BeEquivalentTo(new[] { (ulong)ResultType.Success, (ulong)ResultType.Success }); + + } + + + /// + /// This test verifies that a temporary forked blockchain can make transactions, blocks and report on them + /// We test on blocks before current head and after it, + /// Note that if we get blocks before head we set simulation start state to one of that first block + /// + [Test] + public async Task Test_eth_simulate_eth_moved() + { + TestRpcBlockchain chain = await EthRpcSimulateTestsBase.CreateChain(); + + UInt256 nonceA = chain.State.GetNonce(TestItem.AddressA); + Transaction txMainnetAtoB = GetTransferTxData(nonceA, chain.EthereumEcdsa, TestItem.PrivateKeyA, TestItem.AddressB, 1); + Transaction txAtoB1 = GetTransferTxData(nonceA + 1, chain.EthereumEcdsa, TestItem.PrivateKeyA, TestItem.AddressB, 1); + Transaction txAtoB2 = GetTransferTxData(nonceA + 2, chain.EthereumEcdsa, TestItem.PrivateKeyA, TestItem.AddressB, 1); + Transaction txAtoB3 = GetTransferTxData(nonceA + 3, chain.EthereumEcdsa, TestItem.PrivateKeyA, TestItem.AddressB, 1); + Transaction txAtoB4 = GetTransferTxData(nonceA + 4, chain.EthereumEcdsa, TestItem.PrivateKeyA, TestItem.AddressB, 1); + + SimulatePayload payload = new() + { + BlockStateCalls = new List> + { + new() + { + BlockOverrides = + new BlockOverride + { + Number = (ulong)chain.BlockFinder.Head!.Number+2, + GasLimit = 5_000_000, + FeeRecipient = TestItem.AddressC, + BaseFeePerGas = 0 + }, + Calls = new[] { new TransactionForRpc(txAtoB1), new TransactionForRpc(txAtoB2) } + }, + new() + { + BlockOverrides = + new BlockOverride + { + Number = (ulong)checked(chain.Bridge.HeadBlock.Number + 10), + GasLimit = 5_000_000, + FeeRecipient = TestItem.AddressC, + BaseFeePerGas = 0 + }, + Calls = new[] { new TransactionForRpc(txAtoB3), new TransactionForRpc(txAtoB4) } + } + }, + TraceTransfers = true + }; + + //Test that transfer tx works on mainchain + UInt256 before = chain.State.GetBalance(TestItem.AddressA); + await chain.AddBlock(true, txMainnetAtoB); + UInt256 after = chain.State.GetBalance(TestItem.AddressA); + Assert.Less(after, before); + + chain.Bridge.GetReceipt(txMainnetAtoB.Hash!); + + //Force persistancy of head block in main chain + chain.BlockTree.UpdateMainChain(new List { chain.BlockFinder.Head! }, true, true); + chain.BlockTree.UpdateHeadBlock(chain.BlockFinder.Head!.Hash!); + + //will mock our GetCachedCodeInfo function - it shall be called 3 times if redirect is working, 2 times if not + SimulateTxExecutor executor = new(chain.Bridge, chain.BlockFinder, new JsonRpcConfig(), new BlocksConfig().SecondsPerSlot); + ResultWrapper> result = + executor.Execute(payload, BlockParameter.Latest); + IReadOnlyList data = result.Data; + + Assert.That(data.Count, Is.EqualTo(9)); + + SimulateBlockResult blockResult = data[0]; + Assert.That(blockResult.Calls.Count(), Is.EqualTo(2)); + blockResult = data.Last(); + Assert.That(blockResult.Calls.Count(), Is.EqualTo(2)); + } + + /// + /// This test verifies that a temporary forked blockchain can make transactions, blocks and report on them + /// + [Test] + public async Task Test_eth_simulate_transactions_forced_fail() + { + TestRpcBlockchain chain = await EthRpcSimulateTestsBase.CreateChain(); + + UInt256 nonceA = chain.State.GetNonce(TestItem.AddressA); + + Transaction txMainnetAtoB = + GetTransferTxData(nonceA, chain.EthereumEcdsa, TestItem.PrivateKeyA, TestItem.AddressB, 1); + //shall be Ok + Transaction txAtoB1 = + GetTransferTxData(nonceA + 1, chain.EthereumEcdsa, TestItem.PrivateKeyA, TestItem.AddressB, 1); + + //shall fail + Transaction txAtoB2 = + GetTransferTxData(nonceA + 2, chain.EthereumEcdsa, TestItem.PrivateKeyA, TestItem.AddressB, UInt256.MaxValue); + TransactionForRpc transactionForRpc = new(txAtoB2) { Nonce = null }; + TransactionForRpc transactionForRpc2 = new(txAtoB1) { Nonce = null }; + SimulatePayload payload = new() + { + BlockStateCalls = new List> + { + new() + { + BlockOverrides = + new BlockOverride + { + Number = (ulong)checked(chain.Bridge.HeadBlock.Number + 10), + GasLimit = 5_000_000, + FeeRecipient = TestItem.AddressC, + BaseFeePerGas = 0 + }, + Calls = new[] { transactionForRpc2 } + }, + new() + { + BlockOverrides = + new BlockOverride + { + Number = 123, + GasLimit = 5_000_000, + FeeRecipient = TestItem.AddressC, + BaseFeePerGas = 0 + }, + Calls = new[] { transactionForRpc } + } + }, + TraceTransfers = true, + Validation = true + }; + + //Test that transfer tx works on mainchain + UInt256 before = chain.State.GetBalance(TestItem.AddressA); + await chain.AddBlock(true, txMainnetAtoB); + UInt256 after = chain.State.GetBalance(TestItem.AddressA); + Assert.Less(after, before); + + chain.Bridge.GetReceipt(txMainnetAtoB.Hash!); + + //Force persistancy of head block in main chain + chain.BlockTree.UpdateMainChain(new List { chain.BlockFinder.Head! }, true, true); + chain.BlockTree.UpdateHeadBlock(chain.BlockFinder.Head!.Hash!); + + //will mock our GetCachedCodeInfo function - it shall be called 3 times if redirect is working, 2 times if not + SimulateTxExecutor executor = new(chain.Bridge, chain.BlockFinder, new JsonRpcConfig(), new BlocksConfig().SecondsPerSlot); + + ResultWrapper> result = + executor.Execute(payload, BlockParameter.Latest); + Assert.IsTrue(result.Result!.Error!.Contains("higher than sender balance")); + } + + + [Test] + public async Task TestTransferLogsAddress() + { + EthereumJsonSerializer serializer = new(); + string input = """ + { + "traceTransfers": true, + "blockStateCalls": [ + { + "blockOverrides": { + "baseFeePerGas": "0xa" + }, + "stateOverrides": { + "0xc000000000000000000000000000000000000000": { + "balance": "0x35a4ece8" + } + }, + "calls": [ + { + "from": "0xc000000000000000000000000000000000000000", + "to": "0xc100000000000000000000000000000000000000", + "gas": "0x5208", + "maxFeePerGas": "0x14", + "maxPriorityFeePerGas": "0x1", + "maxFeePerBlobGas": "0x0", + "value": "0x65", + "nonce": "0x0", + "input": "0x" + } + ] + } + ] + } + """; + var payload = serializer.Deserialize>(input); + TestRpcBlockchain chain = await EthRpcSimulateTestsBase.CreateChain(); + Console.WriteLine("current test: simulateTransferOverBlockStateCalls"); + var result = chain.EthRpcModule.eth_simulateV1(payload!, BlockParameter.Latest); + var logs = result.Data.First().Calls.First().Logs.ToArray(); + Assert.That(logs.First().Address == new Address("0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE")); + } +} diff --git a/src/Nethermind/Nethermind.JsonRpc.Test/Modules/Eth/Simulate/EthSimulateTestsHiveBase.cs b/src/Nethermind/Nethermind.JsonRpc.Test/Modules/Eth/Simulate/EthSimulateTestsHiveBase.cs new file mode 100644 index 00000000000..7c1b85e4325 --- /dev/null +++ b/src/Nethermind/Nethermind.JsonRpc.Test/Modules/Eth/Simulate/EthSimulateTestsHiveBase.cs @@ -0,0 +1,82 @@ +// SPDX-FileCopyrightText: 2023 Demerzel Solutions Limited +// SPDX-License-Identifier: LGPL-3.0-only + +using System; +using System.Collections.Generic; +using System.Threading.Tasks; +using Nethermind.Blockchain.Find; +using Nethermind.Facade.Eth; +using Nethermind.Facade.Proxy.Models.Simulate; +using Nethermind.JsonRpc.Data; +using Nethermind.Serialization.Json; +using NUnit.Framework; +using ResultType = Nethermind.Core.ResultType; + +namespace Nethermind.JsonRpc.Test.Modules.Eth; + +public class EthSimulateTestsHiveBase +{ + private static readonly object[] HiveTestCases = + { +new object[] {"multicall-add-more-non-defined-BlockStateCalls-than-fit-but-now-with-fit", "{\"blockStateCalls\": [{\"blockOverrides\": {\"number\": \"0xa\"}, \"stateOverrides\": {\"0xc100000000000000000000000000000000000000\": {\"code\": \"0x608060405234801561001057600080fd5b506000366060484641444543425a3a60014361002c919061009b565b406040516020016100469a99989796959493929190610138565b6040516020818303038152906040529050915050805190602001f35b6000819050919050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052601160045260246000fd5b60006100a682610062565b91506100b183610062565b92508282039050818111156100c9576100c861006c565b5b92915050565b6100d881610062565b82525050565b600073ffffffffffffffffffffffffffffffffffffffff82169050919050565b6000610109826100de565b9050919050565b610119816100fe565b82525050565b6000819050919050565b6101328161011f565b82525050565b60006101408201905061014e600083018d6100cf565b61015b602083018c6100cf565b610168604083018b610110565b610175606083018a6100cf565b61018260808301896100cf565b61018f60a08301886100cf565b61019c60c08301876100cf565b6101a960e08301866100cf565b6101b76101008301856100cf565b6101c5610120830184610129565b9b9a505050505050505050505056fea26469706673582212205139ae3ba8d46d11c29815d001b725f9840c90e330884ed070958d5af4813d8764736f6c63430008120033\"}}, \"calls\": [{\"from\": \"0xc000000000000000000000000000000000000000\", \"to\": \"0xc100000000000000000000000000000000000000\", \"input\": \"0x\"}]}, {\"calls\": [{\"from\": \"0xc000000000000000000000000000000000000000\", \"to\": \"0xc100000000000000000000000000000000000000\", \"input\": \"0x\"}]}, {\"blockOverrides\": {\"number\": \"0x14\"}, \"calls\": [{\"from\": \"0xc000000000000000000000000000000000000000\", \"to\": \"0xc100000000000000000000000000000000000000\", \"input\": \"0x\"}]}, {\"calls\": [{\"from\": \"0xc000000000000000000000000000000000000000\", \"to\": \"0xc100000000000000000000000000000000000000\", \"input\": \"0x\"}]}]}"}, +new object[] {"multicall-basefee-too-low-without-validation-38012", "{\"blockStateCalls\": [{\"blockOverrides\": {\"baseFeePerGas\": \"0xa\"}, \"stateOverrides\": {\"0xc000000000000000000000000000000000000000\": {\"balance\": \"0x7d0\"}}, \"calls\": [{\"from\": \"0xc100000000000000000000000000000000000000\", \"to\": \"0xc100000000000000000000000000000000000000\", \"maxFeePerGas\": \"0x0\", \"maxPriorityFeePerGas\": \"0x0\"}]}]}"}, +//new object[] {"multicall-block-override-reflected-in-contract-simple", "{\"blockStateCalls\": [{\"blockOverrides\": {\"number\": \"0x12a\", \"time\": \"0x64\"}}, {\"blockOverrides\": {\"number\": \"0x14\", \"time\": \"0x65\"}}, {\"blockOverrides\": {\"number\": \"0x15\", \"time\": \"0xc8\"}}]}"}, +//new object[] {"multicall-block-timestamps-incrementing", "{\"blockStateCalls\": [{\"blockOverrides\": {\"time\": \"0x12b\"}}, {\"blockOverrides\": {\"time\": \"0x12c\"}}]}"}, +new object[] {"multicall-blockhash-complex", "{\"blockStateCalls\": [{\"blockOverrides\": {\"number\": \"0xa\"}, \"stateOverrides\": {\"0xc000000000000000000000000000000000000000\": {\"balance\": \"0x1e8480\"}, \"0xc200000000000000000000000000000000000000\": {\"code\": \"0x6080604052348015600f57600080fd5b506004361060285760003560e01c8063ee82ac5e14602d575b600080fd5b60436004803603810190603f91906098565b6057565b604051604e919060d7565b60405180910390f35b600081409050919050565b600080fd5b6000819050919050565b6078816067565b8114608257600080fd5b50565b6000813590506092816071565b92915050565b60006020828403121560ab5760aa6062565b5b600060b7848285016085565b91505092915050565b6000819050919050565b60d18160c0565b82525050565b600060208201905060ea600083018460ca565b9291505056fea2646970667358221220a4d7face162688805e99e86526524ac3dadfb01cc29366d0d68b70dadcf01afe64736f6c63430008120033\"}}, \"calls\": [{\"from\": \"0xc000000000000000000000000000000000000000\", \"to\": \"0xc200000000000000000000000000000000000000\", \"input\": \"0xee82ac5e0000000000000000000000000000000000000000000000000000000000000001\"}]}, {\"blockOverrides\": {\"number\": \"0x14\"}, \"calls\": [{\"from\": \"0xc000000000000000000000000000000000000000\", \"to\": \"0xc200000000000000000000000000000000000000\", \"input\": \"0xee82ac5e000000000000000000000000000000000000000000000000000000000000000a\"}]}, {\"blockOverrides\": {\"number\": \"0x1e\"}, \"calls\": [{\"from\": \"0xc000000000000000000000000000000000000000\", \"to\": \"0xc200000000000000000000000000000000000000\", \"input\": \"0xee82ac5e000000000000000000000000000000000000000000000000000000000000001d\"}]}]}"}, +new object[] {"multicall-blockhash-simple", "{\"blockStateCalls\": [{\"stateOverrides\": {\"0xc200000000000000000000000000000000000000\": {\"code\": \"0x6080604052348015600f57600080fd5b506004361060285760003560e01c8063ee82ac5e14602d575b600080fd5b60436004803603810190603f91906098565b6057565b604051604e919060d7565b60405180910390f35b600081409050919050565b600080fd5b6000819050919050565b6078816067565b8114608257600080fd5b50565b6000813590506092816071565b92915050565b60006020828403121560ab5760aa6062565b5b600060b7848285016085565b91505092915050565b6000819050919050565b60d18160c0565b82525050565b600060208201905060ea600083018460ca565b9291505056fea2646970667358221220a4d7face162688805e99e86526524ac3dadfb01cc29366d0d68b70dadcf01afe64736f6c63430008120033\"}}, \"calls\": [{\"from\": \"0xc000000000000000000000000000000000000000\", \"to\": \"0xc200000000000000000000000000000000000000\", \"input\": \"0xee82ac5e0000000000000000000000000000000000000000000000000000000000000001\"}]}]}"}, +new object[] {"multicall-blockhash-start-before-head", "{\"blockStateCalls\": [{\"blockOverrides\": {\"number\": \"0xa\"}, \"stateOverrides\": {\"0xc000000000000000000000000000000000000000\": {\"balance\": \"0x1e8480\"}, \"0xc200000000000000000000000000000000000000\": {\"code\": \"0x6080604052348015600f57600080fd5b506004361060285760003560e01c8063ee82ac5e14602d575b600080fd5b60436004803603810190603f91906098565b6057565b604051604e919060d7565b60405180910390f35b600081409050919050565b600080fd5b6000819050919050565b6078816067565b8114608257600080fd5b50565b6000813590506092816071565b92915050565b60006020828403121560ab5760aa6062565b5b600060b7848285016085565b91505092915050565b6000819050919050565b60d18160c0565b82525050565b600060208201905060ea600083018460ca565b9291505056fea2646970667358221220a4d7face162688805e99e86526524ac3dadfb01cc29366d0d68b70dadcf01afe64736f6c63430008120033\"}}, \"calls\": [{\"from\": \"0xc000000000000000000000000000000000000000\", \"to\": \"0xc200000000000000000000000000000000000000\", \"input\": \"0xee82ac5e0000000000000000000000000000000000000000000000000000000000000001\"}, {\"from\": \"0xc000000000000000000000000000000000000000\", \"to\": \"0xc200000000000000000000000000000000000000\", \"input\": \"0xee82ac5e0000000000000000000000000000000000000000000000000000000000000002\"}]}, {\"blockOverrides\": {\"number\": \"0x14\"}, \"calls\": [{\"from\": \"0xc000000000000000000000000000000000000000\", \"to\": \"0xc200000000000000000000000000000000000000\", \"input\": \"0xee82ac5e0000000000000000000000000000000000000000000000000000000000000013\"}]}]}"}, +new object[] {"multicall-check-that-balance-is-there-after-new-block", "{\"blockStateCalls\": [{\"stateOverrides\": {\"0xc000000000000000000000000000000000000000\": {\"balance\": \"0x2710\"}, \"0xc200000000000000000000000000000000000000\": {\"code\": \"0x608060405234801561001057600080fd5b506004361061002b5760003560e01c8063f8b2cb4f14610030575b600080fd5b61004a600480360381019061004591906100e4565b610060565b604051610057919061012a565b60405180910390f35b60008173ffffffffffffffffffffffffffffffffffffffff16319050919050565b600080fd5b600073ffffffffffffffffffffffffffffffffffffffff82169050919050565b60006100b182610086565b9050919050565b6100c1816100a6565b81146100cc57600080fd5b50565b6000813590506100de816100b8565b92915050565b6000602082840312156100fa576100f9610081565b5b6000610108848285016100cf565b91505092915050565b6000819050919050565b61012481610111565b82525050565b600060208201905061013f600083018461011b565b9291505056fea2646970667358221220172c443a163d8a43e018c339d1b749c312c94b6de22835953d960985daf228c764736f6c63430008120033\"}}, \"calls\": [{\"from\": \"0xc000000000000000000000000000000000000000\", \"to\": \"0xc200000000000000000000000000000000000000\", \"input\": \"0xf8b2cb4f000000000000000000000000c000000000000000000000000000000000000000\"}, {\"from\": \"0xc000000000000000000000000000000000000000\", \"to\": \"0xc200000000000000000000000000000000000000\", \"input\": \"0xf8b2cb4f000000000000000000000000c100000000000000000000000000000000000000\"}, {\"from\": \"0xc000000000000000000000000000000000000000\", \"to\": \"0xc100000000000000000000000000000000000000\", \"value\": \"0x3e8\"}]}, {\"calls\": [{\"from\": \"0xc000000000000000000000000000000000000000\", \"to\": \"0xc200000000000000000000000000000000000000\", \"input\": \"0xf8b2cb4f000000000000000000000000c000000000000000000000000000000000000000\"}, {\"from\": \"0xc000000000000000000000000000000000000000\", \"to\": \"0xc200000000000000000000000000000000000000\", \"input\": \"0xf8b2cb4f000000000000000000000000c100000000000000000000000000000000000000\"}, {\"from\": \"0xc000000000000000000000000000000000000000\", \"to\": \"0xc100000000000000000000000000000000000000\", \"value\": \"0x3e8\"}]}]}"}, +new object[] {"multicall-contract-calls-itself", "{\"blockStateCalls\": [{\"stateOverrides\": {\"0xc000000000000000000000000000000000000000\": {\"code\": \"0x608060405234801561001057600080fd5b506000366060484641444543425a3a60014361002c919061009b565b406040516020016100469a99989796959493929190610138565b6040516020818303038152906040529050915050805190602001f35b6000819050919050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052601160045260246000fd5b60006100a682610062565b91506100b183610062565b92508282039050818111156100c9576100c861006c565b5b92915050565b6100d881610062565b82525050565b600073ffffffffffffffffffffffffffffffffffffffff82169050919050565b6000610109826100de565b9050919050565b610119816100fe565b82525050565b6000819050919050565b6101328161011f565b82525050565b60006101408201905061014e600083018d6100cf565b61015b602083018c6100cf565b610168604083018b610110565b610175606083018a6100cf565b61018260808301896100cf565b61018f60a08301886100cf565b61019c60c08301876100cf565b6101a960e08301866100cf565b6101b76101008301856100cf565b6101c5610120830184610129565b9b9a505050505050505050505056fea26469706673582212205139ae3ba8d46d11c29815d001b725f9840c90e330884ed070958d5af4813d8764736f6c63430008120033\"}}, \"calls\": [{\"from\": \"0xc000000000000000000000000000000000000000\", \"to\": \"0xc000000000000000000000000000000000000000\"}]}], \"traceTransfers\": true}"}, +new object[] {"multicall-empty-calls-and-overrides-multicall", "{\"blockStateCalls\": [{\"stateOverrides\": {}, \"calls\": [{}]}, {\"stateOverrides\": {}, \"calls\": [{}]}], \"traceTransfers\": true}"}, +new object[] {"multicall-eth-send-should-not-produce-logs-by-default", "{\"blockStateCalls\": [{\"stateOverrides\": {\"0xc000000000000000000000000000000000000000\": {\"balance\": \"0x7d0\"}}, \"calls\": [{\"from\": \"0xc000000000000000000000000000000000000000\", \"to\": \"0xc100000000000000000000000000000000000000\", \"value\": \"0x3e8\"}]}]}"}, +new object[] {"multicall-eth-send-should-not-produce-logs-on-revert", "{\"blockStateCalls\": [{\"stateOverrides\": {\"0xc000000000000000000000000000000000000000\": {\"balance\": \"0x7d0\"}, \"0xc100000000000000000000000000000000000000\": {\"code\": \"0x608060405260006042576040517f08c379a0000000000000000000000000000000000000000000000000000000008152600401603990609d565b60405180910390fd5b005b600082825260208201905092915050565b7f416c7761797320726576657274696e6720636f6e747261637400000000000000600082015250565b600060896019836044565b91506092826055565b602082019050919050565b6000602082019050818103600083015260b481607e565b905091905056fea264697066735822122005cbbbc709291f66fadc17416c1b0ed4d72941840db11468a21b8e1a0362024c64736f6c63430008120033\"}}, \"calls\": [{\"from\": \"0xc000000000000000000000000000000000000000\", \"to\": \"0xc100000000000000000000000000000000000000\", \"value\": \"0x3e8\"}]}], \"traceTransfers\": true}"}, +new object[] {"multicall-eth-send-should-produce-logs", "{\"blockStateCalls\": [{\"stateOverrides\": {\"0xc000000000000000000000000000000000000000\": {\"balance\": \"0x7d0\"}}, \"calls\": [{\"from\": \"0xc000000000000000000000000000000000000000\", \"to\": \"0xc100000000000000000000000000000000000000\", \"value\": \"0x3e8\"}]}], \"traceTransfers\": true}"}, +new object[] {"multicall-eth-send-should-produce-more-logs-on-forward", "{\"blockStateCalls\": [{\"stateOverrides\": {\"0xc000000000000000000000000000000000000000\": {\"balance\": \"0x7d0\"}, \"0xc100000000000000000000000000000000000000\": {\"code\": \"0x60806040526004361061001e5760003560e01c80634b64e49214610023575b600080fd5b61003d6004803603810190610038919061011f565b61003f565b005b60008173ffffffffffffffffffffffffffffffffffffffff166108fc349081150290604051600060405180830381858888f193505050509050806100b8576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004016100af906101a9565b60405180910390fd5b5050565b600080fd5b600073ffffffffffffffffffffffffffffffffffffffff82169050919050565b60006100ec826100c1565b9050919050565b6100fc816100e1565b811461010757600080fd5b50565b600081359050610119816100f3565b92915050565b600060208284031215610135576101346100bc565b5b60006101438482850161010a565b91505092915050565b600082825260208201905092915050565b7f4661696c656420746f2073656e64204574686572000000000000000000000000600082015250565b600061019360148361014c565b915061019e8261015d565b602082019050919050565b600060208201905081810360008301526101c281610186565b905091905056fea2646970667358221220563acd6f5b8ad06a3faf5c27fddd0ecbc198408b99290ce50d15c2cf7043694964736f6c63430008120033\"}}, \"calls\": [{\"from\": \"0xc000000000000000000000000000000000000000\", \"to\": \"0xc100000000000000000000000000000000000000\", \"value\": \"0x3e8\", \"input\": \"0x4b64e4920000000000000000000000000000000000000000000000000000000000000100\"}]}], \"traceTransfers\": true}"}, +new object[] {"multicall-eth-send-should-produce-no-logs-on-forward-revert", "{\"blockStateCalls\": [{\"stateOverrides\": {\"0xc000000000000000000000000000000000000000\": {\"balance\": \"0x7d0\"}, \"0xc100000000000000000000000000000000000000\": {\"code\": \"0x60806040526004361061001e5760003560e01c80634b64e49214610023575b600080fd5b61003d6004803603810190610038919061011f565b61003f565b005b60008173ffffffffffffffffffffffffffffffffffffffff166108fc349081150290604051600060405180830381858888f193505050509050806100b8576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004016100af906101a9565b60405180910390fd5b5050565b600080fd5b600073ffffffffffffffffffffffffffffffffffffffff82169050919050565b60006100ec826100c1565b9050919050565b6100fc816100e1565b811461010757600080fd5b50565b600081359050610119816100f3565b92915050565b600060208284031215610135576101346100bc565b5b60006101438482850161010a565b91505092915050565b600082825260208201905092915050565b7f4661696c656420746f2073656e64204574686572000000000000000000000000600082015250565b600061019360148361014c565b915061019e8261015d565b602082019050919050565b600060208201905081810360008301526101c281610186565b905091905056fea2646970667358221220563acd6f5b8ad06a3faf5c27fddd0ecbc198408b99290ce50d15c2cf7043694964736f6c63430008120033\"}, \"0xc200000000000000000000000000000000000000\": {\"code\": \"0x608060405260006042576040517f08c379a0000000000000000000000000000000000000000000000000000000008152600401603990609d565b60405180910390fd5b005b600082825260208201905092915050565b7f416c7761797320726576657274696e6720636f6e747261637400000000000000600082015250565b600060896019836044565b91506092826055565b602082019050919050565b6000602082019050818103600083015260b481607e565b905091905056fea264697066735822122005cbbbc709291f66fadc17416c1b0ed4d72941840db11468a21b8e1a0362024c64736f6c63430008120033\"}}, \"calls\": [{\"from\": \"0xc000000000000000000000000000000000000000\", \"to\": \"0xc100000000000000000000000000000000000000\", \"value\": \"0x3e8\", \"input\": \"0x4b64e492c200000000000000000000000000000000000000000000000000000000000000\"}]}], \"traceTransfers\": true}"}, +new object[] {"multicall-get-block-properties", "{\"blockStateCalls\": [{\"stateOverrides\": {\"0xc100000000000000000000000000000000000000\": {\"code\": \"0x608060405234801561001057600080fd5b506000366060484641444543425a3a60014361002c919061009b565b406040516020016100469a99989796959493929190610138565b6040516020818303038152906040529050915050805190602001f35b6000819050919050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052601160045260246000fd5b60006100a682610062565b91506100b183610062565b92508282039050818111156100c9576100c861006c565b5b92915050565b6100d881610062565b82525050565b600073ffffffffffffffffffffffffffffffffffffffff82169050919050565b6000610109826100de565b9050919050565b610119816100fe565b82525050565b6000819050919050565b6101328161011f565b82525050565b60006101408201905061014e600083018d6100cf565b61015b602083018c6100cf565b610168604083018b610110565b610175606083018a6100cf565b61018260808301896100cf565b61018f60a08301886100cf565b61019c60c08301876100cf565b6101a960e08301866100cf565b6101b76101008301856100cf565b6101c5610120830184610129565b9b9a505050505050505050505056fea26469706673582212205139ae3ba8d46d11c29815d001b725f9840c90e330884ed070958d5af4813d8764736f6c63430008120033\"}}, \"calls\": [{\"from\": \"0xc000000000000000000000000000000000000000\", \"to\": \"0xc100000000000000000000000000000000000000\", \"input\": \"0x\"}]}]}"}, +new object[] {"multicall-instrict-gas-38013", "{\"blockStateCalls\": [{\"calls\": [{\"from\": \"0xc100000000000000000000000000000000000000\", \"to\": \"0xc100000000000000000000000000000000000000\", \"gas\": \"0x0\"}]}]}"}, +new object[] {"multicall-logs", "{\"blockStateCalls\": [{\"stateOverrides\": {\"0xc200000000000000000000000000000000000000\": {\"code\": \"0x7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff80600080a1600080f3\"}}, \"calls\": [{\"from\": \"0xc000000000000000000000000000000000000000\", \"to\": \"0xc200000000000000000000000000000000000000\", \"input\": \"0x6057361d0000000000000000000000000000000000000000000000000000000000000005\"}]}]}"}, +new object[] {"multicall-move-account-twice", "{\"blockStateCalls\": [{\"stateOverrides\": {\"0xc000000000000000000000000000000000000000\": {\"balance\": \"0x3e8\"}, \"0xc200000000000000000000000000000000000000\": {\"balance\": \"0x7d0\"}, \"0xc300000000000000000000000000000000000000\": {\"balance\": \"0xbb8\"}, \"0xc400000000000000000000000000000000000000\": {\"code\": \"0x608060405234801561001057600080fd5b506004361061002b5760003560e01c8063f8b2cb4f14610030575b600080fd5b61004a600480360381019061004591906100e4565b610060565b604051610057919061012a565b60405180910390f35b60008173ffffffffffffffffffffffffffffffffffffffff16319050919050565b600080fd5b600073ffffffffffffffffffffffffffffffffffffffff82169050919050565b60006100b182610086565b9050919050565b6100c1816100a6565b81146100cc57600080fd5b50565b6000813590506100de816100b8565b92915050565b6000602082840312156100fa576100f9610081565b5b6000610108848285016100cf565b91505092915050565b6000819050919050565b61012481610111565b82525050565b600060208201905061013f600083018461011b565b9291505056fea2646970667358221220172c443a163d8a43e018c339d1b749c312c94b6de22835953d960985daf228c764736f6c63430008120033\"}}}, {\"stateOverrides\": {\"0xc000000000000000000000000000000000000000\": {\"balance\": \"0xbb8\", \"MovePrecompileToAddress\": \"0xc200000000000000000000000000000000000000\"}, \"0xc200000000000000000000000000000000000000\": {\"balance\": \"0xfa0\", \"MovePrecompileToAddress\": \"0xc300000000000000000000000000000000000000\"}}, \"calls\": [{\"from\": \"0xc000000000000000000000000000000000000000\", \"to\": \"0xc400000000000000000000000000000000000000\", \"input\": \"0xf8b2cb4f000000000000000000000000c000000000000000000000000000000000000000\"}, {\"from\": \"0xc000000000000000000000000000000000000000\", \"to\": \"0xc400000000000000000000000000000000000000\", \"input\": \"0xf8b2cb4f000000000000000000000000c200000000000000000000000000000000000000\"}, {\"from\": \"0xc000000000000000000000000000000000000000\", \"to\": \"0xc400000000000000000000000000000000000000\", \"input\": \"0xf8b2cb4f000000000000000000000000c300000000000000000000000000000000000000\"}]}]}"}, +new object[] {"multicall-move-ecrecover-and-call", "{\"blockStateCalls\": [{\"calls\": [{\"from\": \"0xc100000000000000000000000000000000000000\", \"to\": \"0x0000000000000000000000000000000000000001\", \"input\": \"0x4554480000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000007b45544800000000000000000000000000000000000000000000000000000000004554480000000000000000000000000000000000000000000000000000000000\"}, {\"from\": \"0xc100000000000000000000000000000000000000\", \"to\": \"0x0000000000000000000000000000000000000001\", \"input\": \"0x1c8aff950685c2ed4bc3174f3472287b56d9517b9c948127319a09a7a36deac8000000000000000000000000000000000000000000000000000000000000001cb7cf302145348387b9e69fde82d8e634a0f8761e78da3bfa059efced97cbed0d2a66b69167cafe0ccfc726aec6ee393fea3cf0e4f3f9c394705e0f56d9bfe1c9\"}]}, {\"stateOverrides\": {\"0x0000000000000000000000000000000000000001\": {\"MovePrecompileToAddress\": \"0x0000000000000000000000000000000000123456\"}}, \"calls\": [{\"from\": \"0xc100000000000000000000000000000000000000\", \"to\": \"0x0000000000000000000000000000000000123456\", \"input\": \"0x4554480000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000007b45544800000000000000000000000000000000000000000000000000000000004554480000000000000000000000000000000000000000000000000000000000\"}, {\"from\": \"0xc100000000000000000000000000000000000000\", \"to\": \"0x0000000000000000000000000000000000123456\", \"input\": \"0x1c8aff950685c2ed4bc3174f3472287b56d9517b9c948127319a09a7a36deac8000000000000000000000000000000000000000000000000000000000000001cb7cf302145348387b9e69fde82d8e634a0f8761e78da3bfa059efced97cbed0d2a66b69167cafe0ccfc726aec6ee393fea3cf0e4f3f9c394705e0f56d9bfe1c9\"}]}]}"}, +new object[] {"multicall-move-to-address-itself-reference-38022", "{\"blockStateCalls\": [{\"stateOverrides\": {\"0xc000000000000000000000000000000000000000\": {\"balance\": \"0x30d40\"}, \"0xc100000000000000000000000000000000000000\": {\"MovePrecompileToAddress\": \"0xc100000000000000000000000000000000000000\"}}, \"calls\": [{\"from\": \"0xc000000000000000000000000000000000000000\", \"to\": \"0xc100000000000000000000000000000000000000\", \"value\": \"0x1\"}]}]}"}, +new object[] {"multicall-move-two-accounts-to-same-38023", "{\"blockStateCalls\": [{\"stateOverrides\": {\"0x0000000000000000000000000000000000000001\": {\"MovePrecompileToAddress\": \"0xc200000000000000000000000000000000000000\"}, \"0x0000000000000000000000000000000000000002\": {\"MovePrecompileToAddress\": \"0xc200000000000000000000000000000000000000\"}}}]}"}, +new object[] {"multicall-move-two-non-precompiles-accounts-to-same", "{\"blockStateCalls\": [{\"stateOverrides\": {\"0x0100000000000000000000000000000000000000\": {\"MovePrecompileToAddress\": \"0xc200000000000000000000000000000000000000\"}, \"0x0200000000000000000000000000000000000000\": {\"MovePrecompileToAddress\": \"0xc200000000000000000000000000000000000000\"}}}]}"}, +new object[] {"multicall-no-fields-call", "{\"blockStateCalls\": [{\"calls\": [{}]}], \"traceTransfers\": true}"}, +new object[] {"multicall-only-from-to-transaction", "{\"blockStateCalls\": [{\"calls\": [{\"from\": \"0xc000000000000000000000000000000000000000\", \"to\": \"0xc100000000000000000000000000000000000000\"}]}], \"traceTransfers\": true}"}, +new object[] {"multicall-only-from-transaction", "{\"blockStateCalls\": [{\"calls\": [{\"from\": \"0xc000000000000000000000000000000000000000\"}]}], \"traceTransfers\": true}"}, +new object[] {"multicall-override-address-twice-in-separate-BlockStateCalls", "{\"blockStateCalls\": [{\"stateOverrides\": {\"0xc000000000000000000000000000000000000000\": {\"balance\": \"0x7d0\"}}, \"calls\": [{\"from\": \"0xc000000000000000000000000000000000000000\", \"to\": \"0xc100000000000000000000000000000000000000\", \"value\": \"0x3e8\"}]}, {\"stateOverrides\": {\"0xc000000000000000000000000000000000000000\": {\"balance\": \"0x7d0\"}}, \"calls\": [{\"from\": \"0xc000000000000000000000000000000000000000\", \"to\": \"0xc100000000000000000000000000000000000000\", \"value\": \"0x3e8\"}]}], \"traceTransfers\": true}"}, +//new object[] {"multicall-override-all-in-BlockStateCalls", "{\"blockStateCalls\": [{\"blockOverrides\": {\"number\": \"0x3e9\", \"time\": \"0x3eb\", \"gasLimit\": \"0x3ec\", \"feeRecipient\": \"0xc200000000000000000000000000000000000000\", \"prevRandao\": \"0xc300000000000000000000000000000000000000000000000000000000000000\", \"baseFeePerGas\": \"0x3ef\"}}]}"}, +new object[] {"multicall-override-block-num", "{\"blockStateCalls\": [{\"blockOverrides\": {\"number\": \"0xb\"}, \"calls\": [{\"from\": \"0xc000000000000000000000000000000000000000\", \"input\": \"0x4360005260206000f3\"}]}, {\"blockOverrides\": {\"number\": \"0xc\"}, \"calls\": [{\"from\": \"0xc100000000000000000000000000000000000000\", \"input\": \"0x4360005260206000f3\"}]}]}"}, +new object[] {"multicall-override-ecrecover", "{\"blockStateCalls\": [{\"stateOverrides\": {\"0x0000000000000000000000000000000000000001\": {\"code\": \"0x608060405234801561001057600080fd5b506004361061003a5760003560e01c806305fdbc81146101ee578063c00692601461020a5761003b565b5b600036606060008060008086868101906100559190610462565b93509350935093506000806000868686866040516020016100799493929190610520565b60405160208183030381529060405280519060200120815260200190815260200160002060009054906101000a900473ffffffffffffffffffffffffffffffffffffffff169050600073ffffffffffffffffffffffffffffffffffffffff168173ffffffffffffffffffffffffffffffffffffffff16036101bb576000806212345673ffffffffffffffffffffffffffffffffffffffff166127108b8b6040516101249291906105ad565b60006040518083038160008787f1925050503d8060008114610162576040519150601f19603f3d011682016040523d82523d6000602084013e610167565b606091505b5091509150816101ac576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004016101a39061066f565b60405180910390fd5b809750505050505050506101e3565b806040516020016101cc9190610709565b604051602081830303815290604052955050505050505b915050805190602001f35b6102086004803603810190610203919061093a565b610226565b005b610224600480360381019061021f9190610983565b6102ec565b005b60005b81518110156102e8576102d5828281518110610248576102476109fe565b5b602002602001015160000151838381518110610267576102666109fe565b5b602002602001015160200151848481518110610286576102856109fe565b5b6020026020010151604001518585815181106102a5576102a46109fe565b5b6020026020010151606001518686815181106102c4576102c36109fe565b5b6020026020010151608001516102ec565b80806102e090610a66565b915050610229565b5050565b600073ffffffffffffffffffffffffffffffffffffffff168173ffffffffffffffffffffffffffffffffffffffff160361035b576040517f08c379a000000000000000000000000000000000000000000000000000000000815260040161035290610afa565b60405180910390fd5b80600080878787876040516020016103769493929190610520565b60405160208183030381529060405280519060200120815260200190815260200160002060006101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff1602179055505050505050565b6000604051905090565b600080fd5b600080fd5b6000819050919050565b610406816103f3565b811461041157600080fd5b50565b600081359050610423816103fd565b92915050565b600060ff82169050919050565b61043f81610429565b811461044a57600080fd5b50565b60008135905061045c81610436565b92915050565b6000806000806080858703121561047c5761047b6103e9565b5b600061048a87828801610414565b945050602061049b8782880161044d565b93505060406104ac87828801610414565b92505060606104bd87828801610414565b91505092959194509250565b6000819050919050565b6104e46104df826103f3565b6104c9565b82525050565b60008160f81b9050919050565b6000610502826104ea565b9050919050565b61051a61051582610429565b6104f7565b82525050565b600061052c82876104d3565b60208201915061053c8286610509565b60018201915061054c82856104d3565b60208201915061055c82846104d3565b60208201915081905095945050505050565b600081905092915050565b82818337600083830152505050565b6000610594838561056e565b93506105a1838584610579565b82840190509392505050565b60006105ba828486610588565b91508190509392505050565b600082825260208201905092915050565b7f6661696c656420746f2063616c6c206d6f7665642065637265636f766572206160008201527f742061646472657373203078303030303030303030303030303030303030303060208201527f3030303030303030303030303030313233343536000000000000000000000000604082015250565b60006106596054836105c6565b9150610664826105d7565b606082019050919050565b600060208201905081810360008301526106888161064c565b9050919050565b600073ffffffffffffffffffffffffffffffffffffffff82169050919050565b60006106ba8261068f565b9050919050565b60008160601b9050919050565b60006106d9826106c1565b9050919050565b60006106eb826106ce565b9050919050565b6107036106fe826106af565b6106e0565b82525050565b600061071582846106f2565b60148201915081905092915050565b600080fd5b6000601f19601f8301169050919050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052604160045260246000fd5b61077282610729565b810181811067ffffffffffffffff821117156107915761079061073a565b5b80604052505050565b60006107a46103df565b90506107b08282610769565b919050565b600067ffffffffffffffff8211156107d0576107cf61073a565b5b602082029050602081019050919050565b600080fd5b600080fd5b6107f4816106af565b81146107ff57600080fd5b50565b600081359050610811816107eb565b92915050565b600060a0828403121561082d5761082c6107e6565b5b61083760a061079a565b9050600061084784828501610414565b600083015250602061085b8482850161044d565b602083015250604061086f84828501610414565b604083015250606061088384828501610414565b606083015250608061089784828501610802565b60808301525092915050565b60006108b66108b1846107b5565b61079a565b90508083825260208201905060a084028301858111156108d9576108d86107e1565b5b835b8181101561090257806108ee8882610817565b84526020840193505060a0810190506108db565b5050509392505050565b600082601f83011261092157610920610724565b5b81356109318482602086016108a3565b91505092915050565b6000602082840312156109505761094f6103e9565b5b600082013567ffffffffffffffff81111561096e5761096d6103ee565b5b61097a8482850161090c565b91505092915050565b600080600080600060a0868803121561099f5761099e6103e9565b5b60006109ad88828901610414565b95505060206109be8882890161044d565b94505060406109cf88828901610414565b93505060606109e088828901610414565b92505060806109f188828901610802565b9150509295509295909350565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052603260045260246000fd5b7f4e487b7100000000000000000000000000000000000000000000000000000000600052601160045260246000fd5b6000819050919050565b6000610a7182610a5c565b91507fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff8203610aa357610aa2610a2d565b5b600182019050919050565b7f72657475726e20616464726573732063616e6e6f742062652030783000000000600082015250565b6000610ae4601c836105c6565b9150610aef82610aae565b602082019050919050565b60006020820190508181036000830152610b1381610ad7565b905091905056fea2646970667358221220154f5b68ccfa5be744e7245765a3530dac4035052284a68b5dded1945b45075e64736f6c63430008120033\", \"MovePrecompileToAddress\": \"0x0000000000000000000000000000000000123456\"}, \"0xc100000000000000000000000000000000000000\": {\"balance\": \"0x30d40\"}}, \"calls\": [{\"from\": \"0xc100000000000000000000000000000000000000\", \"to\": \"0x0000000000000000000000000000000000123456\", \"input\": \"0x4554480000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000007b45544800000000000000000000000000000000000000000000000000000000004554480000000000000000000000000000000000000000000000000000000000\"}, {\"from\": \"0xc100000000000000000000000000000000000000\", \"to\": \"0x0000000000000000000000000000000000123456\", \"input\": \"0x1c8aff950685c2ed4bc3174f3472287b56d9517b9c948127319a09a7a36deac8000000000000000000000000000000000000000000000000000000000000001cb7cf302145348387b9e69fde82d8e634a0f8761e78da3bfa059efced97cbed0d2a66b69167cafe0ccfc726aec6ee393fea3cf0e4f3f9c394705e0f56d9bfe1c9\"}, {\"from\": \"0xc100000000000000000000000000000000000000\", \"to\": \"0x0000000000000000000000000000000000000001\", \"input\": \"0xc00692604554480000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000007b45544800000000000000000000000000000000000000000000000000000000004554480000000000000000000000000000000000000000000000000000000000000000000000000000000000d8da6bf26964af9d7eed9e03e53415d37aa96045\"}, {\"from\": \"0xc100000000000000000000000000000000000000\", \"to\": \"0x0000000000000000000000000000000000000001\", \"input\": \"0x4554480000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000007b45544800000000000000000000000000000000000000000000000000000000004554480000000000000000000000000000000000000000000000000000000000\"}, {\"from\": \"0xc100000000000000000000000000000000000000\", \"to\": \"0x0000000000000000000000000000000000000001\", \"input\": \"0x1c8aff950685c2ed4bc3174f3472287b56d9517b9c948127319a09a7a36deac8000000000000000000000000000000000000000000000000000000000000001cb7cf302145348387b9e69fde82d8e634a0f8761e78da3bfa059efced97cbed0d2a66b69167cafe0ccfc726aec6ee393fea3cf0e4f3f9c394705e0f56d9bfe1c9\"}, {\"from\": \"0xc100000000000000000000000000000000000000\", \"to\": \"0x0000000000000000000000000000000000000001\", \"input\": \"0x4554480000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000007b45544800000000000000000000000000000000000000000000000000000000004554490000000000000000000000000000000000000000000000000000000000\"}]}]}"}, +new object[] {"multicall-override-identity", "{\"blockStateCalls\": [{\"stateOverrides\": {\"0x0000000000000000000000000000000000000004\": {\"code\": \"0x\", \"MovePrecompileToAddress\": \"0x0000000000000000000000000000000000123456\"}}, \"calls\": [{\"from\": \"0xc000000000000000000000000000000000000000\", \"to\": \"0x0000000000000000000000000000000000123456\", \"input\": \"0x1234\"}, {\"from\": \"0xc000000000000000000000000000000000000000\", \"to\": \"0x0000000000000000000000000000000000000004\", \"input\": \"0x1234\"}]}]}"}, +new object[] {"multicall-override-sha256", "{\"blockStateCalls\": [{\"stateOverrides\": {\"0x0000000000000000000000000000000000000002\": {\"code\": \"0x\", \"MovePrecompileToAddress\": \"0x0000000000000000000000000000000000123456\"}}, \"calls\": [{\"from\": \"0xc000000000000000000000000000000000000000\", \"to\": \"0x0000000000000000000000000000000000123456\", \"input\": \"0x1234\"}, {\"from\": \"0xc000000000000000000000000000000000000000\", \"to\": \"0x0000000000000000000000000000000000000002\", \"input\": \"0x1234\"}]}]}"}, +new object[] {"multicall-override-storage-slots", "{\"blockStateCalls\": [{\"stateOverrides\": {\"0xc000000000000000000000000000000000000000\": {\"balance\": \"0x7d0\"}, \"0xc100000000000000000000000000000000000000\": {\"code\": \"0x608060405234801561001057600080fd5b506004361061004c5760003560e01c80630ff4c916146100515780633033413b1461008157806344e12f871461009f5780637b8d56e3146100bd575b600080fd5b61006b600480360381019061006691906101f6565b6100d9565b6040516100789190610232565b60405180910390f35b61008961013f565b6040516100969190610232565b60405180910390f35b6100a7610145565b6040516100b49190610232565b60405180910390f35b6100d760048036038101906100d2919061024d565b61014b565b005b60006002821061011e576040517f08c379a0000000000000000000000000000000000000000000000000000000008152600401610115906102ea565b60405180910390fd5b6000820361012c5760005490505b6001820361013a5760015490505b919050565b60015481565b60005481565b6002821061018e576040517f08c379a0000000000000000000000000000000000000000000000000000000008152600401610185906102ea565b60405180910390fd5b600082036101a257806000819055506101b7565b600182036101b657806001819055506101b7565b5b5050565b600080fd5b6000819050919050565b6101d3816101c0565b81146101de57600080fd5b50565b6000813590506101f0816101ca565b92915050565b60006020828403121561020c5761020b6101bb565b5b600061021a848285016101e1565b91505092915050565b61022c816101c0565b82525050565b60006020820190506102476000830184610223565b92915050565b60008060408385031215610264576102636101bb565b5b6000610272858286016101e1565b9250506020610283858286016101e1565b9150509250929050565b600082825260208201905092915050565b7f746f6f2062696720736c6f740000000000000000000000000000000000000000600082015250565b60006102d4600c8361028d565b91506102df8261029e565b602082019050919050565b60006020820190508181036000830152610303816102c7565b905091905056fea2646970667358221220ceea194bb66b5b9f52c83e5bf5a1989255de8cb7157838eff98f970c3a04cb3064736f6c63430008120033\"}}, \"calls\": [{\"from\": \"0xc000000000000000000000000000000000000000\", \"to\": \"0xc100000000000000000000000000000000000000\", \"input\": \"0x7b8d56e300000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001\"}, {\"from\": \"0xc000000000000000000000000000000000000000\", \"to\": \"0xc100000000000000000000000000000000000000\", \"input\": \"0x7b8d56e300000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000002\"}, {\"from\": \"0xc000000000000000000000000000000000000000\", \"to\": \"0xc100000000000000000000000000000000000000\", \"input\": \"0x0ff4c9160000000000000000000000000000000000000000000000000000000000000000\"}, {\"from\": \"0xc000000000000000000000000000000000000000\", \"to\": \"0xc100000000000000000000000000000000000000\", \"input\": \"0x0ff4c9160000000000000000000000000000000000000000000000000000000000000001\"}]}, {\"stateOverrides\": {\"0xc100000000000000000000000000000000000000\": {\"stateDiff\": {\"0x0000000000000000000000000000000000000000000000000000000000000000\": \"0x1200000000000000000000000000000000000000000000000000000000000000\"}}}, \"calls\": [{\"from\": \"0xc000000000000000000000000000000000000000\", \"to\": \"0xc100000000000000000000000000000000000000\", \"input\": \"0x0ff4c9160000000000000000000000000000000000000000000000000000000000000000\"}, {\"from\": \"0xc000000000000000000000000000000000000000\", \"to\": \"0xc100000000000000000000000000000000000000\", \"input\": \"0x0ff4c9160000000000000000000000000000000000000000000000000000000000000001\"}]}, {\"stateOverrides\": {\"0xc100000000000000000000000000000000000000\": {\"state\": {\"0x0000000000000000000000000000000000000000000000000000000000000000\": \"0x1200000000000000000000000000000000000000000000000000000000000000\"}}}, \"calls\": [{\"from\": \"0xc000000000000000000000000000000000000000\", \"to\": \"0xc100000000000000000000000000000000000000\", \"input\": \"0x0ff4c9160000000000000000000000000000000000000000000000000000000000000000\"}, {\"from\": \"0xc000000000000000000000000000000000000000\", \"to\": \"0xc100000000000000000000000000000000000000\", \"input\": \"0x0ff4c9160000000000000000000000000000000000000000000000000000000000000001\"}]}], \"traceTransfers\": true}"}, +new object[] {"multicall-precompile-is-sending-transaction", "{\"blockStateCalls\": [{\"calls\": [{\"from\": \"0x0000000000000000000000000000000000000004\", \"to\": \"0x0000000000000000000000000000000000000002\", \"input\": \"0x1234\"}]}]}"}, +new object[] {"multicall-run-gas-spending", "{\"blockStateCalls\": [{\"blockOverrides\": {\"gasLimit\": \"0x16e360\"}, \"stateOverrides\": {\"0xc000000000000000000000000000000000000000\": {\"balance\": \"0x1e8480\"}, \"0xc200000000000000000000000000000000000000\": {\"code\": \"0x608060405234801561001057600080fd5b506004361061002b5760003560e01c8063815b8ab414610030575b600080fd5b61004a600480360381019061004591906100b6565b61004c565b005b60005a90505b60011561007657815a826100669190610112565b106100715750610078565b610052565b505b50565b600080fd5b6000819050919050565b61009381610080565b811461009e57600080fd5b50565b6000813590506100b08161008a565b92915050565b6000602082840312156100cc576100cb61007b565b5b60006100da848285016100a1565b91505092915050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052601160045260246000fd5b600061011d82610080565b915061012883610080565b92508282039050818111156101405761013f6100e3565b5b9291505056fea2646970667358221220a659ba4db729a6ee4db02fcc5c1118db53246b0e5e686534fc9add6f2e93faec64736f6c63430008120033\"}}, \"calls\": [{\"from\": \"0xc000000000000000000000000000000000000000\", \"to\": \"0xc200000000000000000000000000000000000000\", \"input\": \"0x815b8ab40000000000000000000000000000000000000000000000000000000000000000\"}]}, {\"calls\": [{\"from\": \"0xc000000000000000000000000000000000000000\", \"to\": \"0xc200000000000000000000000000000000000000\", \"input\": \"0x815b8ab40000000000000000000000000000000000000000000000000000000000000000\"}, {\"from\": \"0xc000000000000000000000000000000000000000\", \"to\": \"0xc200000000000000000000000000000000000000\", \"input\": \"0x815b8ab400000000000000000000000000000000000000000000000000000000000f4240\"}]}, {\"calls\": [{\"from\": \"0xc000000000000000000000000000000000000000\", \"to\": \"0xc200000000000000000000000000000000000000\", \"input\": \"0x815b8ab40000000000000000000000000000000000000000000000000000000000000000\"}, {\"from\": \"0xc000000000000000000000000000000000000000\", \"to\": \"0xc200000000000000000000000000000000000000\", \"input\": \"0x815b8ab400000000000000000000000000000000000000000000000000000000000f4240\"}]}]}"}, +new object[] {"multicall-run-out-of-gas-in-block-38015", "{\"blockStateCalls\": [{\"blockOverrides\": {\"gasLimit\": \"0x16e360\"}, \"stateOverrides\": {\"0xc000000000000000000000000000000000000000\": {\"balance\": \"0x1e8480\"}, \"0xc200000000000000000000000000000000000000\": {\"code\": \"0x608060405234801561001057600080fd5b506004361061002b5760003560e01c8063815b8ab414610030575b600080fd5b61004a600480360381019061004591906100b6565b61004c565b005b60005a90505b60011561007657815a826100669190610112565b106100715750610078565b610052565b505b50565b600080fd5b6000819050919050565b61009381610080565b811461009e57600080fd5b50565b6000813590506100b08161008a565b92915050565b6000602082840312156100cc576100cb61007b565b5b60006100da848285016100a1565b91505092915050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052601160045260246000fd5b600061011d82610080565b915061012883610080565b92508282039050818111156101405761013f6100e3565b5b9291505056fea2646970667358221220a659ba4db729a6ee4db02fcc5c1118db53246b0e5e686534fc9add6f2e93faec64736f6c63430008120033\"}}}, {\"calls\": [{\"from\": \"0xc000000000000000000000000000000000000000\", \"to\": \"0xc200000000000000000000000000000000000000\", \"input\": \"0x815b8ab400000000000000000000000000000000000000000000000000000000000f4240\"}, {\"from\": \"0xc000000000000000000000000000000000000000\", \"to\": \"0xc200000000000000000000000000000000000000\", \"input\": \"0x815b8ab400000000000000000000000000000000000000000000000000000000000f4240\"}]}]}"}, +new object[] {"multicall-self-destructing-state-override", "{\"blockStateCalls\": [{\"stateOverrides\": {\"0xc200000000000000000000000000000000000000\": {\"code\": \"0x6080604052348015600f57600080fd5b506004361060285760003560e01c806383197ef014602d575b600080fd5b60336035565b005b600073ffffffffffffffffffffffffffffffffffffffff16fffea26469706673582212208e566fde20a17fff9658b9b1db37e27876fd8934ccf9b2aa308cabd37698681f64736f6c63430008120033\"}, \"0xc300000000000000000000000000000000000000\": {\"code\": \"0x73000000000000000000000000000000000000000030146080604052600436106100355760003560e01c8063dce4a4471461003a575b600080fd5b610054600480360381019061004f91906100f8565b61006a565b60405161006191906101b5565b60405180910390f35b6060813b6040519150601f19601f602083010116820160405280825280600060208401853c50919050565b600080fd5b600073ffffffffffffffffffffffffffffffffffffffff82169050919050565b60006100c58261009a565b9050919050565b6100d5816100ba565b81146100e057600080fd5b50565b6000813590506100f2816100cc565b92915050565b60006020828403121561010e5761010d610095565b5b600061011c848285016100e3565b91505092915050565b600081519050919050565b600082825260208201905092915050565b60005b8381101561015f578082015181840152602081019050610144565b60008484015250505050565b6000601f19601f8301169050919050565b600061018782610125565b6101918185610130565b93506101a1818560208601610141565b6101aa8161016b565b840191505092915050565b600060208201905081810360008301526101cf818461017c565b90509291505056fea26469706673582212206a5f0cd9f230619fa520fc4b9d4b518643258cad412f2fa33945ce528b4b895164736f6c63430008120033\"}}}, {\"calls\": [{\"from\": \"0xc000000000000000000000000000000000000000\", \"to\": \"0xc300000000000000000000000000000000000000\", \"input\": \"0xdce4a447000000000000000000000000c200000000000000000000000000000000000000\"}]}, {\"calls\": [{\"from\": \"0xc000000000000000000000000000000000000000\", \"to\": \"0xc200000000000000000000000000000000000000\", \"input\": \"0x83197ef0\"}]}, {\"calls\": [{\"from\": \"0xc000000000000000000000000000000000000000\", \"to\": \"0xc300000000000000000000000000000000000000\", \"input\": \"0xdce4a447000000000000000000000000c200000000000000000000000000000000000000\"}]}, {\"stateOverrides\": {\"0xc200000000000000000000000000000000000000\": {\"code\": \"0x6080604052348015600f57600080fd5b506004361060285760003560e01c806383197ef014602d575b600080fd5b60336035565b005b600073ffffffffffffffffffffffffffffffffffffffff16fffea26469706673582212208e566fde20a17fff9658b9b1db37e27876fd8934ccf9b2aa308cabd37698681f64736f6c63430008120033\"}}}, {\"calls\": [{\"from\": \"0xc000000000000000000000000000000000000000\", \"to\": \"0xc300000000000000000000000000000000000000\", \"input\": \"0xdce4a447000000000000000000000000c200000000000000000000000000000000000000\"}]}]}"}, +new object[] {"multicall-self-destructive-contract-produces-logs", "{\"blockStateCalls\": [{\"stateOverrides\": {\"0xc200000000000000000000000000000000000000\": {\"code\": \"0x6080604052348015600f57600080fd5b506004361060285760003560e01c806383197ef014602d575b600080fd5b60336035565b005b600073ffffffffffffffffffffffffffffffffffffffff16fffea26469706673582212208e566fde20a17fff9658b9b1db37e27876fd8934ccf9b2aa308cabd37698681f64736f6c63430008120033\", \"balance\": \"0x1e8480\"}}, \"calls\": [{\"from\": \"0xc000000000000000000000000000000000000000\", \"to\": \"0xc200000000000000000000000000000000000000\", \"input\": \"0x83197ef0\"}]}], \"traceTransfers\": true}"}, +new object[] {"multicall-set-read-storage", "{\"blockStateCalls\": [{\"stateOverrides\": {\"0xc200000000000000000000000000000000000000\": {\"code\": \"0x608060405234801561001057600080fd5b50600436106100365760003560e01c80632e64cec11461003b5780636057361d14610059575b600080fd5b610043610075565b60405161005091906100d9565b60405180910390f35b610073600480360381019061006e919061009d565b61007e565b005b60008054905090565b8060008190555050565b60008135905061009781610103565b92915050565b6000602082840312156100b3576100b26100fe565b5b60006100c184828501610088565b91505092915050565b6100d3816100f4565b82525050565b60006020820190506100ee60008301846100ca565b92915050565b6000819050919050565b600080fd5b61010c816100f4565b811461011757600080fd5b5056fea2646970667358221220404e37f487a89a932dca5e77faaf6ca2de3b991f93d230604b1b8daaef64766264736f6c63430008070033\"}}, \"calls\": [{\"from\": \"0xc000000000000000000000000000000000000000\", \"to\": \"0xc200000000000000000000000000000000000000\", \"input\": \"0x6057361d0000000000000000000000000000000000000000000000000000000000000005\"}, {\"from\": \"0xc000000000000000000000000000000000000000\", \"to\": \"0xc200000000000000000000000000000000000000\", \"input\": \"0x2e64cec1\"}]}]}"}, +new object[] {"multicall-simple", "{\"blockStateCalls\": [{\"stateOverrides\": {\"0xc000000000000000000000000000000000000000\": {\"balance\": \"0x3e8\"}}, \"calls\": [{\"from\": \"0xc000000000000000000000000000000000000000\", \"to\": \"0xc100000000000000000000000000000000000000\", \"value\": \"0x3e8\"}, {\"from\": \"0xc100000000000000000000000000000000000000\", \"to\": \"0xc200000000000000000000000000000000000000\", \"value\": \"0x3e8\"}]}]}"}, +new object[] {"multicall-simple-send-from-contract", "{\"blockStateCalls\": [{\"stateOverrides\": {\"0xc000000000000000000000000000000000000000\": {\"code\": \"0x60806040526004361061001e5760003560e01c80634b64e49214610023575b600080fd5b61003d6004803603810190610038919061011f565b61003f565b005b60008173ffffffffffffffffffffffffffffffffffffffff166108fc349081150290604051600060405180830381858888f193505050509050806100b8576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004016100af906101a9565b60405180910390fd5b5050565b600080fd5b600073ffffffffffffffffffffffffffffffffffffffff82169050919050565b60006100ec826100c1565b9050919050565b6100fc816100e1565b811461010757600080fd5b50565b600081359050610119816100f3565b92915050565b600060208284031215610135576101346100bc565b5b60006101438482850161010a565b91505092915050565b600082825260208201905092915050565b7f4661696c656420746f2073656e64204574686572000000000000000000000000600082015250565b600061019360148361014c565b915061019e8261015d565b602082019050919050565b600060208201905081810360008301526101c281610186565b905091905056fea2646970667358221220563acd6f5b8ad06a3faf5c27fddd0ecbc198408b99290ce50d15c2cf7043694964736f6c63430008120033\", \"balance\": \"0x3e8\"}}, \"calls\": [{\"from\": \"0xc000000000000000000000000000000000000000\", \"to\": \"0xc100000000000000000000000000000000000000\", \"value\": \"0x3e8\"}]}], \"traceTransfers\": true}"}, +new object[] {"multicall-simple-state-diff", "{\"blockStateCalls\": [{\"stateOverrides\": {\"0xc000000000000000000000000000000000000000\": {\"balance\": \"0x7d0\"}, \"0xc100000000000000000000000000000000000000\": {\"code\": \"0x608060405234801561001057600080fd5b506004361061004c5760003560e01c80630ff4c916146100515780633033413b1461008157806344e12f871461009f5780637b8d56e3146100bd575b600080fd5b61006b600480360381019061006691906101f6565b6100d9565b6040516100789190610232565b60405180910390f35b61008961013f565b6040516100969190610232565b60405180910390f35b6100a7610145565b6040516100b49190610232565b60405180910390f35b6100d760048036038101906100d2919061024d565b61014b565b005b60006002821061011e576040517f08c379a0000000000000000000000000000000000000000000000000000000008152600401610115906102ea565b60405180910390fd5b6000820361012c5760005490505b6001820361013a5760015490505b919050565b60015481565b60005481565b6002821061018e576040517f08c379a0000000000000000000000000000000000000000000000000000000008152600401610185906102ea565b60405180910390fd5b600082036101a257806000819055506101b7565b600182036101b657806001819055506101b7565b5b5050565b600080fd5b6000819050919050565b6101d3816101c0565b81146101de57600080fd5b50565b6000813590506101f0816101ca565b92915050565b60006020828403121561020c5761020b6101bb565b5b600061021a848285016101e1565b91505092915050565b61022c816101c0565b82525050565b60006020820190506102476000830184610223565b92915050565b60008060408385031215610264576102636101bb565b5b6000610272858286016101e1565b9250506020610283858286016101e1565b9150509250929050565b600082825260208201905092915050565b7f746f6f2062696720736c6f740000000000000000000000000000000000000000600082015250565b60006102d4600c8361028d565b91506102df8261029e565b602082019050919050565b60006020820190508181036000830152610303816102c7565b905091905056fea2646970667358221220ceea194bb66b5b9f52c83e5bf5a1989255de8cb7157838eff98f970c3a04cb3064736f6c63430008120033\"}}, \"calls\": [{\"from\": \"0xc000000000000000000000000000000000000000\", \"to\": \"0xc100000000000000000000000000000000000000\", \"input\": \"0x7b8d56e300000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001\"}, {\"from\": \"0xc000000000000000000000000000000000000000\", \"to\": \"0xc100000000000000000000000000000000000000\", \"input\": \"0x7b8d56e300000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000002\"}]}, {\"stateOverrides\": {\"0xc100000000000000000000000000000000000000\": {\"state\": {\"0x0000000000000000000000000000000000000000000000000000000000000000\": \"0x1200000000000000000000000000000000000000000000000000000000000000\"}}}, \"calls\": [{\"from\": \"0xc000000000000000000000000000000000000000\", \"to\": \"0xc100000000000000000000000000000000000000\", \"input\": \"0x0ff4c9160000000000000000000000000000000000000000000000000000000000000000\"}, {\"from\": \"0xc000000000000000000000000000000000000000\", \"to\": \"0xc100000000000000000000000000000000000000\", \"input\": \"0x0ff4c9160000000000000000000000000000000000000000000000000000000000000001\"}]}], \"traceTransfers\": true}"}, +new object[] {"multicall-simple-with-validation-no-funds", "{\"blockStateCalls\": [{\"stateOverrides\": {\"0xc000000000000000000000000000000000000000\": {\"balance\": \"0x3e8\"}}, \"calls\": [{\"from\": \"0xc000000000000000000000000000000000000000\", \"to\": \"0xc100000000000000000000000000000000000000\", \"value\": \"0x3e8\"}, {\"from\": \"0xc100000000000000000000000000000000000000\", \"to\": \"0xc200000000000000000000000000000000000000\", \"value\": \"0x3e8\"}]}]}"}, +new object[] {"multicall-transaction-too-high-nonce", "{\"blockStateCalls\": [{\"calls\": [{\"from\": \"0xc100000000000000000000000000000000000000\", \"to\": \"0xc100000000000000000000000000000000000000\", \"nonce\": \"0x64\"}]}]}"}, +new object[] {"multicall-transaction-too-low-nonce-38010", "{\"blockStateCalls\": [{\"stateOverrides\": {\"0xc000000000000000000000000000000000000000\": {\"nonce\": \"0xa\"}}, \"calls\": [{\"from\": \"0xc100000000000000000000000000000000000000\", \"to\": \"0xc100000000000000000000000000000000000000\", \"nonce\": \"0x0\"}]}]}"}, +}; + + [Test, TestCaseSource(nameof(HiveTestCases)), Parallelizable(ParallelScope.All)] + public async Task TestsimulateHive(string name, string data) + { + EthereumJsonSerializer serializer = new(); + SimulatePayload? payload = serializer.Deserialize>(data); + TestRpcBlockchain chain = await EthRpcSimulateTestsBase.CreateChain(); + Console.WriteLine($"current test: {name}"); + ResultWrapper> result = + chain.EthRpcModule.eth_simulateV1(payload!, BlockParameter.Latest); + + Console.WriteLine(); + Assert.That(result.Result.ResultType, Is.EqualTo(ResultType.Success)); + Assert.IsNotNull(result.Data); + } +} diff --git a/src/Nethermind/Nethermind.JsonRpc.Test/Modules/Eth/Simulate/EthSimulateTestsSimplePrecompiles.cs b/src/Nethermind/Nethermind.JsonRpc.Test/Modules/Eth/Simulate/EthSimulateTestsSimplePrecompiles.cs new file mode 100644 index 00000000000..b362a5439fd --- /dev/null +++ b/src/Nethermind/Nethermind.JsonRpc.Test/Modules/Eth/Simulate/EthSimulateTestsSimplePrecompiles.cs @@ -0,0 +1,108 @@ +// SPDX-FileCopyrightText: 2023 Demerzel Solutions Limited +// SPDX-License-Identifier: LGPL-3.0-only + +using System.Collections.Generic; +using System.Threading; +using System.Threading.Tasks; +using Nethermind.Core; +using Nethermind.Core.Crypto; +using Nethermind.Core.Extensions; +using Nethermind.Core.Test.Builders; +using Nethermind.Crypto; +using Nethermind.Evm; +using Nethermind.Evm.Precompiles; +using Nethermind.Facade.Proxy.Models; +using Nethermind.Facade.Proxy.Models.Simulate; +using Nethermind.Facade.Simulate; +using NUnit.Framework; + +namespace Nethermind.JsonRpc.Test.Modules.Eth; + +public class EthSimulateTestsSimplePrecompiles : EthRpcSimulateTestsBase +{ + /* Compiled contract + * Call example for TestItem.AddressA + * recover 0xb6e16d27ac5ab427a7f68900ac5559ce272dc6c37c82b3e052246c82244c50e4 28 0x7b8b1991eb44757bc688016d27940df8fb971d7c87f77a6bc4e938e3202c4403 0x7e9267b0aeaa82fa765361918f2d8abd9cdd86e64aa6f2b81d3c4e0b69a7b055 + * returns address: 0xb7705aE4c6F81B66cdB323C65f4E8133690fC099 + + pragma solidity ^0.8.7; + + contract EcrecoverProxy { + function recover(bytes32 hash, uint8 v, bytes32 r, bytes32 s) public pure returns (address) { + return ecrecover(hash, v, r, s); + } + } + */ + + //Taken from contract compiler output metadata + public const string EcRecoverCallerContractBytecode = + "608060405234801561001057600080fd5b5061028b806100206000396000f3fe608060405234801561001057600080fd5b506004361061002b5760003560e01c8063c2bf17b014610030575b600080fd5b61004a6004803603810190610045919061012f565b610060565b60405161005791906101d7565b60405180910390f35b6000600185858585604051600081526020016040526040516100859493929190610210565b6020604051602081039080840390855afa1580156100a7573d6000803e3d6000fd5b505050602060405103519050949350505050565b600080fd5b6000819050919050565b6100d3816100c0565b81146100de57600080fd5b50565b6000813590506100f0816100ca565b92915050565b600060ff82169050919050565b61010c816100f6565b811461011757600080fd5b50565b60008135905061012981610103565b92915050565b60008060008060808587031215610149576101486100bb565b5b6000610157878288016100e1565b94505060206101688782880161011a565b9350506040610179878288016100e1565b925050606061018a878288016100e1565b91505092959194509250565b600073ffffffffffffffffffffffffffffffffffffffff82169050919050565b60006101c182610196565b9050919050565b6101d1816101b6565b82525050565b60006020820190506101ec60008301846101c8565b92915050565b6101fb816100c0565b82525050565b61020a816100f6565b82525050565b600060808201905061022560008301876101f2565b6102326020830186610201565b61023f60408301856101f2565b61024c60608301846101f2565b9594505050505056fea26469706673582212204855668ab62273dde1249722b61c57ad057ef3d17384f21233e1b7bb309db7e464736f6c63430008120033"; + + + /// + /// This test verifies that a temporary forked blockchain can updates precompiles + /// + [Test] + public async Task Test_eth_simulate_erc() + { + TestRpcBlockchain chain = await CreateChain(); + + //Impose Opcode instead of EcRecoverPrecompile, it returns const TestItem.AddressE address + byte[] code = Prepare.EvmCode + .StoreDataInMemory(0, TestItem.AddressE + .ToString(false, false) + .PadLeft(64, '0')) + .PushData(Bytes.FromHexString("0x20")) + .PushData(Bytes.FromHexString("0x0")) + .Op(Instruction.RETURN).Done; + + // Step 1: Hash the message + Hash256 messageHash = Keccak.Compute("Hello, world!"); + // Step 2: Sign the hash + Signature signature = chain.EthereumEcdsa.Sign(TestItem.PrivateKeyA, messageHash); + + Address contractAddress = await DeployEcRecoverContract(chain, TestItem.PrivateKeyB, EcRecoverCallerContractBytecode); + byte[] transactionData = GenerateTransactionDataForEcRecover(messageHash, signature); + + SystemTransaction tx = new() + { + Data = transactionData, + To = contractAddress, + SenderAddress = TestItem.PublicKeyB.Address + }; + tx.Hash = tx.CalculateHash(); + + TransactionWithSourceDetails txDetails = new() + { + Transaction = tx, + HadGasLimitInRequest = false, + HadNonceInRequest = false + }; + + SimulatePayload payload = new() + { + BlockStateCalls = + [ + new BlockStateCall + { + StateOverrides = new Dictionary + { + { EcRecoverPrecompile.Address, new AccountOverride { Code = code } } + }, + Calls = [txDetails] + } + ], + TraceTransfers = true + }; + + SimulateOutput result = chain.Bridge.Simulate(chain.BlockFinder.Head?.Header!, payload, CancellationToken.None); + + byte[] addressBytes = result.Items[0].Calls[0].ReturnData!.SliceWithZeroPaddingEmptyOnError(12, 20); + Address resultingAddress = new(addressBytes); + Assert.That(resultingAddress, Is.EqualTo(TestItem.AddressE)); + + //Check that initial VM is intact + Address? mainChainRpcAddress = EcRecoverCall(chain, TestItem.AddressB, transactionData, contractAddress); + Assert.That(mainChainRpcAddress, Is.EqualTo(TestItem.AddressA)); + } +} diff --git a/src/Nethermind/Nethermind.JsonRpc.Test/Modules/Proof/ProofRpcModuleTests.cs b/src/Nethermind/Nethermind.JsonRpc.Test/Modules/Proof/ProofRpcModuleTests.cs index 31a16936028..22d8e66c775 100644 --- a/src/Nethermind/Nethermind.JsonRpc.Test/Modules/Proof/ProofRpcModuleTests.cs +++ b/src/Nethermind/Nethermind.JsonRpc.Test/Modules/Proof/ProofRpcModuleTests.cs @@ -29,6 +29,7 @@ using FluentAssertions; using Nethermind.Consensus.Processing; using Nethermind.Core.Buffers; +using Nethermind.Facade.Eth; using Nethermind.State.Tracing; using NSubstitute; diff --git a/src/Nethermind/Nethermind.JsonRpc.Test/Modules/SingletonModulePoolTests.cs b/src/Nethermind/Nethermind.JsonRpc.Test/Modules/SingletonModulePoolTests.cs index bc06fd5cb57..1ffb4b32cdb 100644 --- a/src/Nethermind/Nethermind.JsonRpc.Test/Modules/SingletonModulePoolTests.cs +++ b/src/Nethermind/Nethermind.JsonRpc.Test/Modules/SingletonModulePoolTests.cs @@ -24,6 +24,7 @@ using Nethermind.Facade.Eth; using Nethermind.JsonRpc.Modules.Eth.FeeHistory; using Nethermind.JsonRpc.Modules.Eth.GasPrice; +using Nethermind.Config; namespace Nethermind.JsonRpc.Test.Modules { @@ -55,7 +56,8 @@ public Task Initialize() Substitute.For(), Substitute.For(), Substitute.For(), - Substitute.For()); + Substitute.For(), + new BlocksConfig().SecondsPerSlot); return Task.CompletedTask; } diff --git a/src/Nethermind/Nethermind.JsonRpc.Test/Modules/TestRpcBlockchain.cs b/src/Nethermind/Nethermind.JsonRpc.Test/Modules/TestRpcBlockchain.cs index c112755087c..6fba1d025d9 100644 --- a/src/Nethermind/Nethermind.JsonRpc.Test/Modules/TestRpcBlockchain.cs +++ b/src/Nethermind/Nethermind.JsonRpc.Test/Modules/TestRpcBlockchain.cs @@ -29,6 +29,8 @@ using Nethermind.Wallet; using Nethermind.Config; +using Nethermind.Db; +using Nethermind.Facade.Simulate; using Nethermind.Synchronization.ParallelSync; using NSubstitute; @@ -58,14 +60,9 @@ public class TestRpcBlockchain : TestBlockchain public static Builder ForTest(T blockchain) where T : TestRpcBlockchain => new(blockchain); - public class Builder where T : TestRpcBlockchain + public class Builder(T blockchain) where T : TestRpcBlockchain { - private readonly TestRpcBlockchain _blockchain; - - public Builder(T blockchain) - { - _blockchain = blockchain; - } + private readonly TestRpcBlockchain _blockchain = blockchain; public Builder WithBlockchainBridge(IBlockchainBridge blockchainBridge) { @@ -108,31 +105,41 @@ public Builder WithConfig(IJsonRpcConfig config) return this; } - public async Task Build(ISpecProvider? specProvider = null, UInt256? initialValues = null) - { - return (T)(await _blockchain.Build(specProvider, initialValues)); - } + public async Task Build( + ISpecProvider? specProvider = null, + UInt256? initialValues = null) => + (T)(await _blockchain.Build(specProvider, initialValues, true)); } - protected override async Task Build(ISpecProvider? specProvider = null, UInt256? initialValues = null, bool addBlockOnStart = true) + protected override async Task Build( + ISpecProvider? specProvider = null, + UInt256? initialValues = null, + bool addBlockOnStart = true) { specProvider ??= new TestSpecProvider(Berlin.Instance); - await base.Build(specProvider, initialValues); + await base.Build(specProvider, initialValues, addBlockOnStart); IFilterStore filterStore = new FilterStore(); IFilterManager filterManager = new FilterManager(filterStore, BlockProcessor, TxPool, LimboLogs.Instance); - + var dbProvider = new ReadOnlyDbProvider(DbProvider, false); + IReadOnlyBlockTree? roBlockTree = BlockTree!.AsReadOnly(); ReadOnlyTxProcessingEnv processingEnv = new( WorldStateManager, - new ReadOnlyBlockTree(BlockTree), + roBlockTree, + SpecProvider, + LimboLogs.Instance); + SimulateReadOnlyBlocksProcessingEnvFactory simulateProcessingEnvFactory = new SimulateReadOnlyBlocksProcessingEnvFactory( + WorldStateManager, + roBlockTree, + new ReadOnlyDbProvider(dbProvider, true), SpecProvider, LimboLogs.Instance); + BlocksConfig blocksConfig = new BlocksConfig(); ReceiptFinder ??= ReceiptStorage; - Bridge ??= new BlockchainBridge(processingEnv, TxPool, ReceiptFinder, filterStore, filterManager, EthereumEcdsa, Timestamper, LogFinder, SpecProvider, new BlocksConfig(), false); + Bridge ??= new BlockchainBridge(processingEnv, simulateProcessingEnvFactory, TxPool, ReceiptFinder, filterStore, filterManager, EthereumEcdsa, Timestamper, LogFinder, SpecProvider, blocksConfig, false); BlockFinder ??= BlockTree; GasPriceOracle ??= new GasPriceOracle(BlockFinder, SpecProvider, LogManager); - ITxSigner txSigner = new WalletTxSigner(TestWallet, specProvider.ChainId); TxSealer = new TxSealer(txSigner, Timestamper); TxSender ??= new TxPoolSender(TxPool, TxSealer, NonceManager, EthereumEcdsa ?? new EthereumEcdsa(specProvider.ChainId, LogManager)); @@ -153,7 +160,8 @@ protected override async Task Build(ISpecProvider? specProvider GasPriceOracle, new EthSyncingInfo(BlockTree, ReceiptStorage, syncConfig, new StaticSelector(SyncMode.All), Substitute.For(), LogManager), - FeeHistoryOracle); + FeeHistoryOracle, + blocksConfig.SecondsPerSlot); return this; } diff --git a/src/Nethermind/Nethermind.JsonRpc.Test/Modules/Trace/ParityStyleTracerTests.cs b/src/Nethermind/Nethermind.JsonRpc.Test/Modules/Trace/ParityStyleTracerTests.cs index 54e8d292a82..a6aef576922 100644 --- a/src/Nethermind/Nethermind.JsonRpc.Test/Modules/Trace/ParityStyleTracerTests.cs +++ b/src/Nethermind/Nethermind.JsonRpc.Test/Modules/Trace/ParityStyleTracerTests.cs @@ -63,8 +63,9 @@ public void Setup() _stateReader = new StateReader(trieStore, codeDb, LimboLogs.Instance); BlockhashProvider blockhashProvider = new(_blockTree, specProvider, stateProvider, LimboLogs.Instance); - VirtualMachine virtualMachine = new(blockhashProvider, specProvider, LimboLogs.Instance); - TransactionProcessor transactionProcessor = new(specProvider, stateProvider, virtualMachine, LimboLogs.Instance); + CodeInfoRepository codeInfoRepository = new(); + VirtualMachine virtualMachine = new(blockhashProvider, specProvider, codeInfoRepository, LimboLogs.Instance); + TransactionProcessor transactionProcessor = new(specProvider, stateProvider, virtualMachine, codeInfoRepository, LimboLogs.Instance); _poSSwitcher = Substitute.For(); BlockProcessor blockProcessor = new( diff --git a/src/Nethermind/Nethermind.JsonRpc.Test/Modules/TraceRpcModuleTests.cs b/src/Nethermind/Nethermind.JsonRpc.Test/Modules/TraceRpcModuleTests.cs index 115afc9123f..e86796589e1 100644 --- a/src/Nethermind/Nethermind.JsonRpc.Test/Modules/TraceRpcModuleTests.cs +++ b/src/Nethermind/Nethermind.JsonRpc.Test/Modules/TraceRpcModuleTests.cs @@ -25,6 +25,7 @@ using Nethermind.Db; using Nethermind.Evm; using Nethermind.Evm.Tracing.ParityStyle; +using Nethermind.Facade.Eth; using Nethermind.Serialization.Json; using Nethermind.Specs.Forks; using Nethermind.Specs.Test; diff --git a/src/Nethermind/Nethermind.JsonRpc.Test/Modules/TransactionForRpcConverterTests.cs b/src/Nethermind/Nethermind.JsonRpc.Test/Modules/TransactionForRpcConverterTests.cs index 9e1822b3e8e..6b6add97578 100644 --- a/src/Nethermind/Nethermind.JsonRpc.Test/Modules/TransactionForRpcConverterTests.cs +++ b/src/Nethermind/Nethermind.JsonRpc.Test/Modules/TransactionForRpcConverterTests.cs @@ -5,6 +5,7 @@ using FluentAssertions; using Nethermind.Core; using Nethermind.Core.Crypto; +using Nethermind.Facade.Eth; using Nethermind.JsonRpc.Data; using Nethermind.JsonRpc.Test.Data; using Nethermind.Serialization.Json; diff --git a/src/Nethermind/Nethermind.JsonRpc.TraceStore.Test/TraceStoreRpcModuleTests.cs b/src/Nethermind/Nethermind.JsonRpc.TraceStore.Test/TraceStoreRpcModuleTests.cs index dbe61f725b1..8c82d299e41 100644 --- a/src/Nethermind/Nethermind.JsonRpc.TraceStore.Test/TraceStoreRpcModuleTests.cs +++ b/src/Nethermind/Nethermind.JsonRpc.TraceStore.Test/TraceStoreRpcModuleTests.cs @@ -12,6 +12,7 @@ using Nethermind.Core.Test.Builders; using Nethermind.Db; using Nethermind.Evm.Tracing.ParityStyle; +using Nethermind.Facade.Eth; using Nethermind.JsonRpc.Data; using Nethermind.JsonRpc.Modules.Trace; using Nethermind.Logging; diff --git a/src/Nethermind/Nethermind.JsonRpc.TraceStore/TraceStoreRpcModule.cs b/src/Nethermind/Nethermind.JsonRpc.TraceStore/TraceStoreRpcModule.cs index b01b95b265d..d9267e28112 100644 --- a/src/Nethermind/Nethermind.JsonRpc.TraceStore/TraceStoreRpcModule.cs +++ b/src/Nethermind/Nethermind.JsonRpc.TraceStore/TraceStoreRpcModule.cs @@ -7,6 +7,7 @@ using Nethermind.Core.Crypto; using Nethermind.Db; using Nethermind.Evm.Tracing.ParityStyle; +using Nethermind.Facade.Eth; using Nethermind.JsonRpc.Data; using Nethermind.JsonRpc.Modules; using Nethermind.JsonRpc.Modules.Trace; diff --git a/src/Nethermind/Nethermind.JsonRpc/Data/AccessListForRpc.cs b/src/Nethermind/Nethermind.JsonRpc/Data/AccessListForRpc.cs index 84e569a4965..7c21797de61 100644 --- a/src/Nethermind/Nethermind.JsonRpc/Data/AccessListForRpc.cs +++ b/src/Nethermind/Nethermind.JsonRpc/Data/AccessListForRpc.cs @@ -2,6 +2,7 @@ // SPDX-License-Identifier: LGPL-3.0-only using System.Collections.Generic; +using Nethermind.Facade.Eth; using Nethermind.Int256; namespace Nethermind.JsonRpc.Data diff --git a/src/Nethermind/Nethermind.JsonRpc/Data/TransactionForRpcWithTraceTypes.cs b/src/Nethermind/Nethermind.JsonRpc/Data/TransactionForRpcWithTraceTypes.cs index 061f0b02f11..5f5c98acaf1 100644 --- a/src/Nethermind/Nethermind.JsonRpc/Data/TransactionForRpcWithTraceTypes.cs +++ b/src/Nethermind/Nethermind.JsonRpc/Data/TransactionForRpcWithTraceTypes.cs @@ -4,6 +4,7 @@ using System; using System.Text.Json; using System.Text.Json.Serialization; +using Nethermind.Facade.Eth; namespace Nethermind.JsonRpc.Data { diff --git a/src/Nethermind/Nethermind.JsonRpc/ErrorType.cs b/src/Nethermind/Nethermind.JsonRpc/ErrorCodes.cs similarity index 72% rename from src/Nethermind/Nethermind.JsonRpc/ErrorType.cs rename to src/Nethermind/Nethermind.JsonRpc/ErrorCodes.cs index 5af200cf91b..3990b33b775 100644 --- a/src/Nethermind/Nethermind.JsonRpc/ErrorType.cs +++ b/src/Nethermind/Nethermind.JsonRpc/ErrorCodes.cs @@ -86,5 +86,30 @@ public static class ErrorCodes /// Unknown block error /// public const int UnknownBlockError = -39001; + + /// + /// Invalid RPC simulate call block number out of order + /// + public const int InvalidInputBlocksOutOfOrder = -38020; + + /// + /// Invalid RPC simulate call Block timestamp in sequence did not increase + /// + public const int BlockTimestampNotIncreased = -38021; + + /// + /// Invalid RPC simulate call containing too many blocks + /// + public const int InvalidInputTooManyBlocks = -38026; + + /// + /// Invalid RPC simulate call Not enough gas provided to pay for intrinsic gas for a transaction + /// + public const int InsufficientIntrinsicGas = -38013; + + /// + /// Invalid RPC simulate call transaction + /// + public const int InvalidTransaction = -38014; } } diff --git a/src/Nethermind/Nethermind.JsonRpc/IJsonRpcConfig.cs b/src/Nethermind/Nethermind.JsonRpc/IJsonRpcConfig.cs index c8818c84aad..e52d9d1e265 100644 --- a/src/Nethermind/Nethermind.JsonRpc/IJsonRpcConfig.cs +++ b/src/Nethermind/Nethermind.JsonRpc/IJsonRpcConfig.cs @@ -161,6 +161,9 @@ public interface IJsonRpcConfig : IConfig [ConfigItem(Description = "The max batch size limit for batched JSON-RPC calls.", DefaultValue = "33554432")] long? MaxBatchResponseBodySize { get; set; } + [ConfigItem(Description = "The max blocks count limit for eth_simulate JSON-RPC calls.", DefaultValue = "256")] + long? MaxSimulateBlocksCap { get; set; } + [ConfigItem(Description = "The error margin used in eth_estimateGas expressed in basis points.", DefaultValue = "150")] int EstimateErrorMargin { get; set; } } diff --git a/src/Nethermind/Nethermind.JsonRpc/JsonRpcConfig.cs b/src/Nethermind/Nethermind.JsonRpc/JsonRpcConfig.cs index 953ad379bc1..26d3d32760e 100644 --- a/src/Nethermind/Nethermind.JsonRpc/JsonRpcConfig.cs +++ b/src/Nethermind/Nethermind.JsonRpc/JsonRpcConfig.cs @@ -55,6 +55,7 @@ public int WebSocketsPort public string[] EngineEnabledModules { get; set; } = ModuleType.DefaultEngineModules.ToArray(); public int MaxBatchSize { get; set; } = 1024; public long? MaxBatchResponseBodySize { get; set; } = 32.MiB(); + public long? MaxSimulateBlocksCap { get; set; } = 256; public int EstimateErrorMargin { get; set; } = 150; }; }; diff --git a/src/Nethermind/Nethermind.JsonRpc/Modules/DebugModule/DebugRpcModule.cs b/src/Nethermind/Nethermind.JsonRpc/Modules/DebugModule/DebugRpcModule.cs index 2c8813c7dbd..2de6e774830 100644 --- a/src/Nethermind/Nethermind.JsonRpc/Modules/DebugModule/DebugRpcModule.cs +++ b/src/Nethermind/Nethermind.JsonRpc/Modules/DebugModule/DebugRpcModule.cs @@ -18,6 +18,7 @@ using System.Collections.Generic; using Nethermind.JsonRpc.Modules.Eth; using Nethermind.Core.Specs; +using Nethermind.Facade.Eth; namespace Nethermind.JsonRpc.Modules.DebugModule; diff --git a/src/Nethermind/Nethermind.JsonRpc/Modules/DebugModule/IDebugRpcModule.cs b/src/Nethermind/Nethermind.JsonRpc/Modules/DebugModule/IDebugRpcModule.cs index 4a057892b70..6615e53f408 100644 --- a/src/Nethermind/Nethermind.JsonRpc/Modules/DebugModule/IDebugRpcModule.cs +++ b/src/Nethermind/Nethermind.JsonRpc/Modules/DebugModule/IDebugRpcModule.cs @@ -6,6 +6,7 @@ using Nethermind.Blockchain.Find; using Nethermind.Core.Crypto; using Nethermind.Evm.Tracing.GethStyle; +using Nethermind.Facade.Eth; using Nethermind.JsonRpc.Data; using Nethermind.JsonRpc.Modules.Eth; using Nethermind.Synchronization.Reporting; diff --git a/src/Nethermind/Nethermind.JsonRpc/Modules/Eth/BadBlock.cs b/src/Nethermind/Nethermind.JsonRpc/Modules/Eth/BadBlock.cs index 3844a4a20ea..de2af695562 100644 --- a/src/Nethermind/Nethermind.JsonRpc/Modules/Eth/BadBlock.cs +++ b/src/Nethermind/Nethermind.JsonRpc/Modules/Eth/BadBlock.cs @@ -5,6 +5,7 @@ using Nethermind.Core; using Nethermind.Serialization.Rlp; using Nethermind.Core.Crypto; +using Nethermind.Facade.Eth; namespace Nethermind.JsonRpc.Modules.Eth; diff --git a/src/Nethermind/Nethermind.JsonRpc/Modules/Eth/EthModuleFactory.cs b/src/Nethermind/Nethermind.JsonRpc/Modules/Eth/EthModuleFactory.cs index 25e42fc74f2..6a8bc34d9b7 100644 --- a/src/Nethermind/Nethermind.JsonRpc/Modules/Eth/EthModuleFactory.cs +++ b/src/Nethermind/Nethermind.JsonRpc/Modules/Eth/EthModuleFactory.cs @@ -14,6 +14,9 @@ using Nethermind.State; using Nethermind.TxPool; using Nethermind.Wallet; +using System.Text.Json; +using System.Text.Json.Serialization; +using Nethermind.Config; namespace Nethermind.JsonRpc.Modules.Eth { @@ -30,7 +33,8 @@ public class EthModuleFactory( IReceiptStorage receiptStorage, IGasPriceOracle gasPriceOracle, IEthSyncingInfo ethSyncingInfo, - IFeeHistoryOracle feeHistoryOracle) + IFeeHistoryOracle feeHistoryOracle, + ulong secondsPerSlot) : ModuleFactoryBase { private readonly IReadOnlyBlockTree _blockTree = blockTree.AsReadOnly(); @@ -62,7 +66,8 @@ public override IEthRpcModule Create() _specProvider, _gasPriceOracle, _ethSyncingInfo, - _feeHistoryOracle); + _feeHistoryOracle, + secondsPerSlot); } } } diff --git a/src/Nethermind/Nethermind.JsonRpc/Modules/Eth/EthRpcModule.TransactionExecutor.cs b/src/Nethermind/Nethermind.JsonRpc/Modules/Eth/EthRpcModule.TransactionExecutor.cs index afc281f7f6d..6da17831925 100644 --- a/src/Nethermind/Nethermind.JsonRpc/Modules/Eth/EthRpcModule.TransactionExecutor.cs +++ b/src/Nethermind/Nethermind.JsonRpc/Modules/Eth/EthRpcModule.TransactionExecutor.cs @@ -11,110 +11,81 @@ using Nethermind.Core.Extensions; using Nethermind.Evm; using Nethermind.Facade; +using Nethermind.Facade.Eth; using Nethermind.Int256; using Nethermind.JsonRpc.Data; using Nethermind.Specs.Forks; namespace Nethermind.JsonRpc.Modules.Eth { + //General executor public partial class EthRpcModule { - private abstract class TxExecutor + // Single call executor + private abstract class TxExecutor(IBlockchainBridge blockchainBridge, IBlockFinder blockFinder, IJsonRpcConfig rpcConfig) + : ExecutorBase(blockchainBridge, blockFinder, rpcConfig) { - protected readonly IBlockchainBridge _blockchainBridge; - private readonly IBlockFinder _blockFinder; - private readonly IJsonRpcConfig _rpcConfig; + private bool NoBaseFee { get; set; } - protected TxExecutor(IBlockchainBridge blockchainBridge, IBlockFinder blockFinder, IJsonRpcConfig rpcConfig) - { - _blockchainBridge = blockchainBridge; - _blockFinder = blockFinder; - _rpcConfig = rpcConfig; - } + protected override Transaction Prepare(TransactionForRpc call) => call.ToTransaction(_blockchainBridge.GetChainId()); - public ResultWrapper ExecuteTx( - TransactionForRpc transactionCall, - BlockParameter? blockParameter) + protected override ResultWrapper Execute(BlockHeader header, Transaction tx, CancellationToken token) { - SearchResult searchResult = _blockFinder.SearchForHeader(blockParameter); - if (searchResult.IsError) - { - return ResultWrapper.Fail(searchResult); - } - - BlockHeader header = searchResult.Object; - if (!HasStateForBlock(_blockchainBridge, header)) - { - return ResultWrapper.Fail($"No state available for block {header.Hash}", - ErrorCodes.ResourceUnavailable); - } - BlockHeader clonedHeader = header.Clone(); - var noBaseFee = !ShouldSetBaseFee(transactionCall); - if (noBaseFee) + if (NoBaseFee) { clonedHeader.BaseFeePerGas = 0; } - - transactionCall.EnsureDefaults(_rpcConfig.GasCap); - - using CancellationTokenSource cancellationTokenSource = new(_rpcConfig.Timeout); - Transaction tx = transactionCall.ToTransaction(_blockchainBridge.GetChainId()); if (tx.IsContractCreation && tx.DataLength == 0) { - return ResultWrapper.Fail("Contract creation without any data provided.", - ErrorCodes.InvalidInput); + return ResultWrapper.Fail("Contract creation without any data provided.", ErrorCodes.InvalidInput); } - - return ExecuteTx(clonedHeader, tx, cancellationTokenSource.Token); + return ExecuteTx(clonedHeader, tx, token); } - private static bool ShouldSetBaseFee(TransactionForRpc t) + private static bool ShouldSetBaseFee(TransactionForRpc t) => + // x?.IsZero == false <=> x > 0 + t.GasPrice?.IsZero == false || t.MaxFeePerGas?.IsZero == false || t.MaxPriorityFeePerGas?.IsZero == false; + + public override ResultWrapper Execute( + TransactionForRpc transactionCall, + BlockParameter? blockParameter) { - return - t.GasPrice > 0 || t.MaxFeePerGas > 0 || t.MaxPriorityFeePerGas > 0; + NoBaseFee = !ShouldSetBaseFee(transactionCall); + transactionCall.EnsureDefaults(_rpcConfig.GasCap); + return base.Execute(transactionCall, blockParameter); } + public ResultWrapper ExecuteTx(TransactionForRpc transactionCall, BlockParameter? blockParameter) => Execute(transactionCall, blockParameter); + protected abstract ResultWrapper ExecuteTx(BlockHeader header, Transaction tx, CancellationToken token); - protected static ResultWrapper GetInputError(BlockchainBridge.CallOutput result) => + protected ResultWrapper GetInputError(CallOutput result) => ResultWrapper.Fail(result.Error, ErrorCodes.InvalidInput); } - private class CallTxExecutor : TxExecutor + private class CallTxExecutor(IBlockchainBridge blockchainBridge, IBlockFinder blockFinder, IJsonRpcConfig rpcConfig) + : TxExecutor(blockchainBridge, blockFinder, rpcConfig) { - public CallTxExecutor(IBlockchainBridge blockchainBridge, IBlockFinder blockFinder, IJsonRpcConfig rpcConfig) - : base(blockchainBridge, blockFinder, rpcConfig) - { - } - protected override ResultWrapper ExecuteTx(BlockHeader header, Transaction tx, CancellationToken token) { - BlockchainBridge.CallOutput result = _blockchainBridge.Call(header, tx, token); + CallOutput result = _blockchainBridge.Call(header, tx, token); - if (result.Error is null) - { - return ResultWrapper.Success(result.OutputData.ToHexString(true)); - } - - return result.InputError - ? GetInputError(result) - : ResultWrapper.Fail("VM execution error.", ErrorCodes.ExecutionError, result.Error); + return result.Error is null + ? ResultWrapper.Success(result.OutputData.ToHexString(true)) + : TryGetInputError(result) ?? ResultWrapper.Fail("VM execution error.", ErrorCodes.ExecutionError, result.Error); } + } - private class EstimateGasTxExecutor : TxExecutor + private class EstimateGasTxExecutor(IBlockchainBridge blockchainBridge, IBlockFinder blockFinder, IJsonRpcConfig rpcConfig) + : TxExecutor(blockchainBridge, blockFinder, rpcConfig) { - private readonly int _errorMargin; - public EstimateGasTxExecutor(IBlockchainBridge blockchainBridge, IBlockFinder blockFinder, IJsonRpcConfig rpcConfig) - : base(blockchainBridge, blockFinder, rpcConfig) - { - _errorMargin = rpcConfig.EstimateErrorMargin; - } + private readonly int _errorMargin = rpcConfig.EstimateErrorMargin; protected override ResultWrapper ExecuteTx(BlockHeader header, Transaction tx, CancellationToken token) { - BlockchainBridge.CallOutput result = _blockchainBridge.EstimateGas(header, tx, _errorMargin, token); + CallOutput result = _blockchainBridge.EstimateGas(header, tx, _errorMargin, token); if (result.Error is null) { @@ -127,19 +98,12 @@ public EstimateGasTxExecutor(IBlockchainBridge blockchainBridge, IBlockFinder bl } } - private class CreateAccessListTxExecutor : TxExecutor + private class CreateAccessListTxExecutor(IBlockchainBridge blockchainBridge, IBlockFinder blockFinder, IJsonRpcConfig rpcConfig, bool optimize) + : TxExecutor(blockchainBridge, blockFinder, rpcConfig) { - private readonly bool _optimize; - - public CreateAccessListTxExecutor(IBlockchainBridge blockchainBridge, IBlockFinder blockFinder, IJsonRpcConfig rpcConfig, bool optimize) - : base(blockchainBridge, blockFinder, rpcConfig) - { - _optimize = optimize; - } - protected override ResultWrapper ExecuteTx(BlockHeader header, Transaction tx, CancellationToken token) { - BlockchainBridge.CallOutput result = _blockchainBridge.CreateAccessList(header, tx, token, _optimize); + CallOutput result = _blockchainBridge.CreateAccessList(header, tx, token, optimize); if (result.Error is null) { @@ -151,13 +115,13 @@ public CreateAccessListTxExecutor(IBlockchainBridge blockchainBridge, IBlockFind : ResultWrapper.Fail(result.Error, ErrorCodes.ExecutionError, new AccessListForRpc(GetResultAccessList(tx, result), GetResultGas(tx, result))); } - private static IEnumerable GetResultAccessList(Transaction tx, BlockchainBridge.CallOutput result) + private static IEnumerable GetResultAccessList(Transaction tx, CallOutput result) { AccessList? accessList = result.AccessList ?? tx.AccessList; return accessList is null ? Enumerable.Empty() : AccessListItemForRpc.FromAccessList(accessList); } - private static UInt256 GetResultGas(Transaction transaction, BlockchainBridge.CallOutput result) + private static UInt256 GetResultGas(Transaction transaction, CallOutput result) { long gas = result.GasSpent; if (result.AccessList is not null) diff --git a/src/Nethermind/Nethermind.JsonRpc/Modules/Eth/EthRpcModule.cs b/src/Nethermind/Nethermind.JsonRpc/Modules/Eth/EthRpcModule.cs index 5c6b7b31e89..6abec26637d 100644 --- a/src/Nethermind/Nethermind.JsonRpc/Modules/Eth/EthRpcModule.cs +++ b/src/Nethermind/Nethermind.JsonRpc/Modules/Eth/EthRpcModule.cs @@ -20,6 +20,7 @@ using Nethermind.Facade; using Nethermind.Facade.Eth; using Nethermind.Facade.Filters; +using Nethermind.Facade.Proxy.Models.Simulate; using Nethermind.Int256; using Nethermind.JsonRpc.Data; using Nethermind.JsonRpc.Modules.Eth.FeeHistory; @@ -52,7 +53,8 @@ public partial class EthRpcModule( ISpecProvider specProvider, IGasPriceOracle gasPriceOracle, IEthSyncingInfo ethSyncingInfo, - IFeeHistoryOracle feeHistoryOracle) : IEthRpcModule + IFeeHistoryOracle feeHistoryOracle, + ulong? secondsPerSlot) : IEthRpcModule { protected readonly Encoding _messageEncoding = Encoding.UTF8; protected readonly IJsonRpcConfig _rpcConfig = rpcConfig ?? throw new ArgumentNullException(nameof(rpcConfig)); @@ -68,6 +70,7 @@ public partial class EthRpcModule( protected readonly IGasPriceOracle _gasPriceOracle = gasPriceOracle ?? throw new ArgumentNullException(nameof(gasPriceOracle)); protected readonly IEthSyncingInfo _ethSyncingInfo = ethSyncingInfo ?? throw new ArgumentNullException(nameof(ethSyncingInfo)); protected readonly IFeeHistoryOracle _feeHistoryOracle = feeHistoryOracle ?? throw new ArgumentNullException(nameof(feeHistoryOracle)); + protected readonly ulong _secondsPerSlot = secondsPerSlot ?? throw new ArgumentNullException(nameof(secondsPerSlot)); private static bool HasStateForBlock(IBlockchainBridge blockchainBridge, BlockHeader header) { @@ -322,6 +325,10 @@ public ResultWrapper eth_call(TransactionForRpc transactionCall, BlockPa new CallTxExecutor(_blockchainBridge, _blockFinder, _rpcConfig) .ExecuteTx(transactionCall, blockParameter); + public ResultWrapper> eth_simulateV1(SimulatePayload payload, BlockParameter? blockParameter = null) => + new SimulateTxExecutor(_blockchainBridge, _blockFinder, _rpcConfig, _secondsPerSlot) + .Execute(payload, blockParameter); + public ResultWrapper eth_estimateGas(TransactionForRpc transactionCall, BlockParameter? blockParameter) => new EstimateGasTxExecutor(_blockchainBridge, _blockFinder, _rpcConfig) .ExecuteTx(transactionCall, blockParameter); diff --git a/src/Nethermind/Nethermind.JsonRpc/Modules/Eth/ExecutorBase.cs b/src/Nethermind/Nethermind.JsonRpc/Modules/Eth/ExecutorBase.cs new file mode 100644 index 00000000000..2e04d10b455 --- /dev/null +++ b/src/Nethermind/Nethermind.JsonRpc/Modules/Eth/ExecutorBase.cs @@ -0,0 +1,49 @@ +// SPDX-FileCopyrightText: 2023 Demerzel Solutions Limited +// SPDX-License-Identifier: LGPL-3.0-only + +using System.Threading; +using Nethermind.Blockchain.Find; +using Nethermind.Core; +using Nethermind.Facade; + +namespace Nethermind.JsonRpc.Modules.Eth; + +public abstract class ExecutorBase +{ + protected readonly IBlockchainBridge _blockchainBridge; + protected readonly IBlockFinder _blockFinder; + protected readonly IJsonRpcConfig _rpcConfig; + + protected ExecutorBase(IBlockchainBridge blockchainBridge, IBlockFinder blockFinder, IJsonRpcConfig rpcConfig) + { + _blockchainBridge = blockchainBridge; + _blockFinder = blockFinder; + _rpcConfig = rpcConfig; + } + + public virtual ResultWrapper Execute( + TRequest call, + BlockParameter? blockParameter) + { + SearchResult searchResult = _blockFinder.SearchForHeader(blockParameter); + if (searchResult.IsError) return ResultWrapper.Fail(searchResult); + + BlockHeader header = searchResult.Object; + if (!_blockchainBridge.HasStateForBlock(header!)) + return ResultWrapper.Fail($"No state available for block {header.Hash}", + ErrorCodes.ResourceUnavailable); + + using CancellationTokenSource cancellationTokenSource = new(_rpcConfig.Timeout); + TProcessing? toProcess = Prepare(call); + return Execute(header.Clone(), toProcess, cancellationTokenSource.Token); + } + + protected abstract TProcessing Prepare(TRequest call); + + protected abstract ResultWrapper Execute(BlockHeader header, TProcessing tx, CancellationToken token); + + protected ResultWrapper? TryGetInputError(CallOutput result) + { + return result.InputError ? ResultWrapper.Fail(result.Error!, ErrorCodes.InvalidInput) : null; + } +} diff --git a/src/Nethermind/Nethermind.JsonRpc/Modules/Eth/IEthRpcModule.cs b/src/Nethermind/Nethermind.JsonRpc/Modules/Eth/IEthRpcModule.cs index 7b42b8ddf38..89c52debbda 100644 --- a/src/Nethermind/Nethermind.JsonRpc/Modules/Eth/IEthRpcModule.cs +++ b/src/Nethermind/Nethermind.JsonRpc/Modules/Eth/IEthRpcModule.cs @@ -8,6 +8,9 @@ using Nethermind.Core.Crypto; using Nethermind.Facade.Eth; using Nethermind.Facade.Filters; +using Nethermind.Facade.Proxy.Models.Simulate; +using Nethermind.Facade.Proxy.Models; +using Nethermind.Facade.Proxy; using Nethermind.Int256; using Nethermind.JsonRpc.Data; using Nethermind.State.Proofs; @@ -150,6 +153,13 @@ public interface IEthRpcModule : IRpcModule ExampleResponse = "0x")] ResultWrapper eth_call([JsonRpcParameter(ExampleValue = "[{\"from\":\"0x0001020304050607080910111213141516171819\",\"gasPrice\":\"0x100000\", \"data\": \"0x70a082310000000000000000000000006c1f09f6271fbe133db38db9c9280307f5d22160\", \"to\": \"0x0d8775f648430679a709e98d2b0cb6250d2887ef\"}]")] TransactionForRpc transactionCall, BlockParameter? blockParameter = null); + [JsonRpcMethod(IsImplemented = true, + Description = "Executes a simulation across multiple blocks (does not create a transaction or block)", + IsSharable = false, + ExampleResponse = "0x")] + ResultWrapper> eth_simulateV1([JsonRpcParameter(ExampleValue = "{\"blockStateCalls\":[{\"stateOverrides\":{\"0x0000000000000000000000000000000000000001\":{\"code\":\"0x608060405234801561001057600080fd5b506004361061003a5760003560e01c806305fdbc81146101ee578063c00692601461020a5761003b565b5b600036606060008060008086868101906100559190610462565b93509350935093506000806000868686866040516020016100799493929190610520565b60405160208183030381529060405280519060200120815260200190815260200160002060009054906101000a900473ffffffffffffffffffffffffffffffffffffffff169050600073ffffffffffffffffffffffffffffffffffffffff168173ffffffffffffffffffffffffffffffffffffffff16036101bb576000806212345673ffffffffffffffffffffffffffffffffffffffff166127108b8b6040516101249291906105ad565b60006040518083038160008787f1925050503d8060008114610162576040519150601f19603f3d011682016040523d82523d6000602084013e610167565b606091505b5091509150816101ac576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004016101a39061066f565b60405180910390fd5b809750505050505050506101e3565b806040516020016101cc9190610709565b604051602081830303815290604052955050505050505b915050805190602001f35b6102086004803603810190610203919061093a565b610226565b005b610224600480360381019061021f9190610983565b6102ec565b005b60005b81518110156102e8576102d5828281518110610248576102476109fe565b5b602002602001015160000151838381518110610267576102666109fe565b5b602002602001015160200151848481518110610286576102856109fe565b5b6020026020010151604001518585815181106102a5576102a46109fe565b5b6020026020010151606001518686815181106102c4576102c36109fe565b5b6020026020010151608001516102ec565b80806102e090610a66565b915050610229565b5050565b600073ffffffffffffffffffffffffffffffffffffffff168173ffffffffffffffffffffffffffffffffffffffff160361035b576040517f08c379a000000000000000000000000000000000000000000000000000000000815260040161035290610afa565b60405180910390fd5b80600080878787876040516020016103769493929190610520565b60405160208183030381529060405280519060200120815260200190815260200160002060006101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff1602179055505050505050565b6000604051905090565b600080fd5b600080fd5b6000819050919050565b610406816103f3565b811461041157600080fd5b50565b600081359050610423816103fd565b92915050565b600060ff82169050919050565b61043f81610429565b811461044a57600080fd5b50565b60008135905061045c81610436565b92915050565b6000806000806080858703121561047c5761047b6103e9565b5b600061048a87828801610414565b945050602061049b8782880161044d565b93505060406104ac87828801610414565b92505060606104bd87828801610414565b91505092959194509250565b6000819050919050565b6104e46104df826103f3565b6104c9565b82525050565b60008160f81b9050919050565b6000610502826104ea565b9050919050565b61051a61051582610429565b6104f7565b82525050565b600061052c82876104d3565b60208201915061053c8286610509565b60018201915061054c82856104d3565b60208201915061055c82846104d3565b60208201915081905095945050505050565b600081905092915050565b82818337600083830152505050565b6000610594838561056e565b93506105a1838584610579565b82840190509392505050565b60006105ba828486610588565b91508190509392505050565b600082825260208201905092915050565b7f6661696c656420746f2063616c6c206d6f7665642065637265636f766572206160008201527f742061646472657373203078303030303030303030303030303030303030303060208201527f3030303030303030303030303030313233343536000000000000000000000000604082015250565b60006106596054836105c6565b9150610664826105d7565b606082019050919050565b600060208201905081810360008301526106888161064c565b9050919050565b600073ffffffffffffffffffffffffffffffffffffffff82169050919050565b60006106ba8261068f565b9050919050565b60008160601b9050919050565b60006106d9826106c1565b9050919050565b60006106eb826106ce565b9050919050565b6107036106fe826106af565b6106e0565b82525050565b600061071582846106f2565b60148201915081905092915050565b600080fd5b6000601f19601f8301169050919050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052604160045260246000fd5b61077282610729565b810181811067ffffffffffffffff821117156107915761079061073a565b5b80604052505050565b60006107a46103df565b90506107b08282610769565b919050565b600067ffffffffffffffff8211156107d0576107cf61073a565b5b602082029050602081019050919050565b600080fd5b600080fd5b6107f4816106af565b81146107ff57600080fd5b50565b600081359050610811816107eb565b92915050565b600060a0828403121561082d5761082c6107e6565b5b61083760a061079a565b9050600061084784828501610414565b600083015250602061085b8482850161044d565b602083015250604061086f84828501610414565b604083015250606061088384828501610414565b606083015250608061089784828501610802565b60808301525092915050565b60006108b66108b1846107b5565b61079a565b90508083825260208201905060a084028301858111156108d9576108d86107e1565b5b835b8181101561090257806108ee8882610817565b84526020840193505060a0810190506108db565b5050509392505050565b600082601f83011261092157610920610724565b5b81356109318482602086016108a3565b91505092915050565b6000602082840312156109505761094f6103e9565b5b600082013567ffffffffffffffff81111561096e5761096d6103ee565b5b61097a8482850161090c565b91505092915050565b600080600080600060a0868803121561099f5761099e6103e9565b5b60006109ad88828901610414565b95505060206109be8882890161044d565b94505060406109cf88828901610414565b93505060606109e088828901610414565b92505060806109f188828901610802565b9150509295509295909350565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052603260045260246000fd5b7f4e487b7100000000000000000000000000000000000000000000000000000000600052601160045260246000fd5b6000819050919050565b6000610a7182610a5c565b91507fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff8203610aa357610aa2610a2d565b5b600182019050919050565b7f72657475726e20616464726573732063616e6e6f742062652030783000000000600082015250565b6000610ae4601c836105c6565b9150610aef82610aae565b602082019050919050565b60006020820190508181036000830152610b1381610ad7565b905091905056fea2646970667358221220154f5b68ccfa5be744e7245765a3530dac4035052284a68b5dded1945b45075e64736f6c63430008120033\",\"MovePrecompileToAddress\":\"0x0000000000000000000000000000000000123456\"},\"0xc100000000000000000000000000000000000000\":{\"balance\":\"0x30d40\"}},\"calls\":[{\"from\":\"0xc100000000000000000000000000000000000000\",\"to\":\"0x0000000000000000000000000000000000123456\",\"input\":\"0x4554480000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000007b45544800000000000000000000000000000000000000000000000000000000004554480000000000000000000000000000000000000000000000000000000000\"},{\"from\":\"0xc100000000000000000000000000000000000000\",\"to\":\"0x0000000000000000000000000000000000123456\",\"input\":\"0x1c8aff950685c2ed4bc3174f3472287b56d9517b9c948127319a09a7a36deac8000000000000000000000000000000000000000000000000000000000000001cb7cf302145348387b9e69fde82d8e634a0f8761e78da3bfa059efced97cbed0d2a66b69167cafe0ccfc726aec6ee393fea3cf0e4f3f9c394705e0f56d9bfe1c9\"},{\"from\":\"0xc100000000000000000000000000000000000000\",\"to\":\"0x0000000000000000000000000000000000000001\",\"input\":\"0xc00692604554480000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000007b45544800000000000000000000000000000000000000000000000000000000004554480000000000000000000000000000000000000000000000000000000000000000000000000000000000d8da6bf26964af9d7eed9e03e53415d37aa96045\"},{\"from\":\"0xc100000000000000000000000000000000000000\",\"to\":\"0x0000000000000000000000000000000000000001\",\"input\":\"0x4554480000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000007b45544800000000000000000000000000000000000000000000000000000000004554480000000000000000000000000000000000000000000000000000000000\"},{\"from\":\"0xc100000000000000000000000000000000000000\",\"to\":\"0x0000000000000000000000000000000000000001\",\"input\":\"0x1c8aff950685c2ed4bc3174f3472287b56d9517b9c948127319a09a7a36deac8000000000000000000000000000000000000000000000000000000000000001cb7cf302145348387b9e69fde82d8e634a0f8761e78da3bfa059efced97cbed0d2a66b69167cafe0ccfc726aec6ee393fea3cf0e4f3f9c394705e0f56d9bfe1c9\"},{\"from\":\"0xc100000000000000000000000000000000000000\",\"to\":\"0x0000000000000000000000000000000000000001\",\"input\":\"0x4554480000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000007b45544800000000000000000000000000000000000000000000000000000000004554490000000000000000000000000000000000000000000000000000000000\"}]}]}")] SimulatePayload payload, + BlockParameter? blockParameter = null); + [JsonRpcMethod(IsImplemented = true, Description = "Executes a tx call and returns gas used (does not create a transaction)", IsSharable = false, diff --git a/src/Nethermind/Nethermind.JsonRpc/Modules/Eth/SimulateTxExecutor.cs b/src/Nethermind/Nethermind.JsonRpc/Modules/Eth/SimulateTxExecutor.cs new file mode 100644 index 00000000000..898f13ae6b2 --- /dev/null +++ b/src/Nethermind/Nethermind.JsonRpc/Modules/Eth/SimulateTxExecutor.cs @@ -0,0 +1,217 @@ +// SPDX-FileCopyrightText: 2023 Demerzel Solutions Limited +// SPDX-License-Identifier: LGPL-3.0-only + +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading; +using Nethermind.Blockchain.Find; +using Nethermind.Config; +using Nethermind.Core; +using Nethermind.Facade; +using Nethermind.Facade.Eth; +using Nethermind.Facade.Proxy.Models.Simulate; +using Nethermind.Facade.Simulate; +using Nethermind.JsonRpc.Data; + +namespace Nethermind.JsonRpc.Modules.Eth; + +public class SimulateTxExecutor(IBlockchainBridge blockchainBridge, IBlockFinder blockFinder, IJsonRpcConfig rpcConfig, ulong? secondsPerSlot = null) + : ExecutorBase, SimulatePayload, + SimulatePayload>(blockchainBridge, blockFinder, rpcConfig) +{ + private readonly long _blocksLimit = rpcConfig.MaxSimulateBlocksCap ?? 256; + private long _gasCapBudget = rpcConfig.GasCap ?? long.MaxValue; + + protected override SimulatePayload Prepare(SimulatePayload call) + { + SimulatePayload result = new() + { + TraceTransfers = call.TraceTransfers, + Validation = call.Validation, + BlockStateCalls = call.BlockStateCalls?.Select(blockStateCall => + { + if (blockStateCall.BlockOverrides?.GasLimit is not null) + { + blockStateCall.BlockOverrides.GasLimit = (ulong)Math.Min((long)blockStateCall.BlockOverrides.GasLimit!.Value, _gasCapBudget); + } + + return new BlockStateCall + { + BlockOverrides = blockStateCall.BlockOverrides, + StateOverrides = blockStateCall.StateOverrides, + Calls = blockStateCall.Calls?.Select(callTransactionModel => + { + if (callTransactionModel.Type == TxType.Legacy) + { + callTransactionModel.Type = TxType.EIP1559; + } + + bool hadGasLimitInRequest = callTransactionModel.Gas.HasValue; + bool hadNonceInRequest = callTransactionModel.Nonce.HasValue; + callTransactionModel.EnsureDefaults(_gasCapBudget); + _gasCapBudget -= callTransactionModel.Gas!.Value; + + Transaction tx = callTransactionModel.ToTransaction(_blockchainBridge.GetChainId()); + + TransactionWithSourceDetails? result = new() + { + HadGasLimitInRequest = hadGasLimitInRequest, + HadNonceInRequest = hadNonceInRequest, + Transaction = tx + }; + + return result; + }).ToArray() + }; + }).ToList() + }; + + return result; + } + + public override ResultWrapper> Execute( + SimulatePayload call, + BlockParameter? blockParameter) + { + if (call.BlockStateCalls is null) + return ResultWrapper>.Fail("Must contain BlockStateCalls", ErrorCodes.InvalidParams); + + if (call.BlockStateCalls!.Count > _rpcConfig.MaxSimulateBlocksCap) + return ResultWrapper>.Fail( + $"This node is configured to support only {_rpcConfig.MaxSimulateBlocksCap} blocks", ErrorCodes.InvalidInputTooManyBlocks); + + SearchResult searchResult = _blockFinder.SearchForBlock(blockParameter); + + if (searchResult.IsError || searchResult.Object is null) + return ResultWrapper>.Fail(searchResult); + + BlockHeader header = searchResult.Object.Header; + + if (!_blockchainBridge.HasStateForBlock(header!)) + return ResultWrapper>.Fail($"No state available for block {header.Hash}", + ErrorCodes.ResourceUnavailable); + + if (call.BlockStateCalls?.Count > _blocksLimit) + return ResultWrapper>.Fail( + $"Too many blocks provided, node is configured to simulate up to {_blocksLimit} while {call.BlockStateCalls?.Count} were given", + ErrorCodes.InvalidParams); + + secondsPerSlot ??= new BlocksConfig().SecondsPerSlot; + + if (call.BlockStateCalls is not null) + { + long lastBlockNumber = -1; + ulong lastBlockTime = 0; + + foreach (BlockStateCall? blockToSimulate in call.BlockStateCalls) + { + ulong givenNumber = blockToSimulate.BlockOverrides?.Number ?? + (lastBlockNumber == -1 ? (ulong)header.Number + 1 : (ulong)lastBlockNumber + 1); + + if (givenNumber > long.MaxValue) + return ResultWrapper>.Fail( + $"Block number too big {givenNumber}!", ErrorCodes.InvalidParams); + + if (givenNumber < (ulong)header.Number) + return ResultWrapper>.Fail( + $"Block number out of order {givenNumber} is < than given base number of {header.Number}!", ErrorCodes.InvalidInputBlocksOutOfOrder); + + long given = (long)givenNumber; + if (given > lastBlockNumber) + { + lastBlockNumber = given; + } + else + { + return ResultWrapper>.Fail( + $"Block number out of order {givenNumber}!", ErrorCodes.InvalidInputBlocksOutOfOrder); + } + + blockToSimulate.BlockOverrides ??= new BlockOverride(); + blockToSimulate.BlockOverrides.Number = givenNumber; + + ulong givenTime = blockToSimulate.BlockOverrides.Time ?? + (lastBlockTime == 0 + ? header.Timestamp + secondsPerSlot.Value + : lastBlockTime + secondsPerSlot.Value); + + if (givenTime < header.Timestamp) + return ResultWrapper>.Fail( + $"Block timestamp out of order {givenTime} is < than given base timestamp of {header.Timestamp}!", ErrorCodes.BlockTimestampNotIncreased); + + if (givenTime > lastBlockTime) + { + lastBlockTime = givenTime; + } + else + { + return ResultWrapper>.Fail( + $"Block timestamp out of order {givenTime}!", ErrorCodes.BlockTimestampNotIncreased); + } + + blockToSimulate.BlockOverrides.Time = givenTime; + } + + long minBlockNumber = Math.Min( + call.BlockStateCalls.Min(b => (long)(b.BlockOverrides?.Number ?? ulong.MaxValue)), + header.Number + 1); + + long maxBlockNumber = Math.Max( + call.BlockStateCalls.Max(b => (long)(b.BlockOverrides?.Number ?? ulong.MinValue)), + minBlockNumber); + + HashSet existingBlockNumbers = + [ + .. call.BlockStateCalls.Select(b => (long)(b.BlockOverrides?.Number ?? ulong.MinValue)) + ]; + + List> completeBlockStateCalls = call.BlockStateCalls; + + for (long blockNumber = minBlockNumber; blockNumber <= maxBlockNumber; blockNumber++) + { + if (!existingBlockNumbers.Contains(blockNumber)) + { + completeBlockStateCalls.Add(new BlockStateCall + { + BlockOverrides = new BlockOverride { Number = (ulong)blockNumber }, + StateOverrides = null, + Calls = Array.Empty() + }); + } + } + + call.BlockStateCalls.Sort((b1, b2) => b1.BlockOverrides!.Number!.Value.CompareTo(b2.BlockOverrides!.Number!.Value)); + } + + using CancellationTokenSource cancellationTokenSource = new(_rpcConfig.Timeout); //TODO remove! + SimulatePayload toProcess = Prepare(call); + return Execute(header.Clone(), toProcess, cancellationTokenSource.Token); + } + + protected override ResultWrapper> Execute(BlockHeader header, + SimulatePayload tx, CancellationToken token) + { + SimulateOutput results = _blockchainBridge.Simulate(header, tx, token); + + if (results.Error is not null && (results.Error.Contains("invalid transaction") + || results.Error.Contains("InsufficientBalanceException") + )) + results.ErrorCode = ErrorCodes.InvalidTransaction; + + if (results.Error is not null && results.Error.Contains("InvalidBlockException")) + results.ErrorCode = ErrorCodes.InvalidParams; + + + if (results.Error is not null && results.Error.Contains("below intrinsic gas")) + results.ErrorCode = ErrorCodes.InsufficientIntrinsicGas; + + + + return results.Error is null + ? ResultWrapper>.Success(results.Items) + : results.ErrorCode is not null + ? ResultWrapper>.Fail(results.Error!, results.ErrorCode!.Value, results.Items) + : ResultWrapper>.Fail(results.Error, results.Items); + } +} diff --git a/src/Nethermind/Nethermind.JsonRpc/Modules/Personal/IPersonalRpcModule.cs b/src/Nethermind/Nethermind.JsonRpc/Modules/Personal/IPersonalRpcModule.cs index f255d904da1..3ca1db260a6 100644 --- a/src/Nethermind/Nethermind.JsonRpc/Modules/Personal/IPersonalRpcModule.cs +++ b/src/Nethermind/Nethermind.JsonRpc/Modules/Personal/IPersonalRpcModule.cs @@ -3,6 +3,7 @@ using Nethermind.Core; using Nethermind.Core.Crypto; +using Nethermind.Facade.Eth; using Nethermind.JsonRpc.Data; namespace Nethermind.JsonRpc.Modules.Personal diff --git a/src/Nethermind/Nethermind.JsonRpc/Modules/Personal/PersonalRpcModule.cs b/src/Nethermind/Nethermind.JsonRpc/Modules/Personal/PersonalRpcModule.cs index 1070270a9d5..1116810731d 100644 --- a/src/Nethermind/Nethermind.JsonRpc/Modules/Personal/PersonalRpcModule.cs +++ b/src/Nethermind/Nethermind.JsonRpc/Modules/Personal/PersonalRpcModule.cs @@ -7,6 +7,7 @@ using Nethermind.Core.Attributes; using Nethermind.Core.Crypto; using Nethermind.Crypto; +using Nethermind.Facade.Eth; using Nethermind.JsonRpc.Data; using Nethermind.KeyStore; using Nethermind.Wallet; diff --git a/src/Nethermind/Nethermind.JsonRpc/Modules/Proof/IProofRpcModule.cs b/src/Nethermind/Nethermind.JsonRpc/Modules/Proof/IProofRpcModule.cs index a7b8e9a450d..8e2123e5533 100644 --- a/src/Nethermind/Nethermind.JsonRpc/Modules/Proof/IProofRpcModule.cs +++ b/src/Nethermind/Nethermind.JsonRpc/Modules/Proof/IProofRpcModule.cs @@ -3,6 +3,7 @@ using Nethermind.Blockchain.Find; using Nethermind.Core.Crypto; +using Nethermind.Facade.Eth; using Nethermind.JsonRpc.Data; namespace Nethermind.JsonRpc.Modules.Proof diff --git a/src/Nethermind/Nethermind.JsonRpc/Modules/Proof/ProofRpcModule.cs b/src/Nethermind/Nethermind.JsonRpc/Modules/Proof/ProofRpcModule.cs index 37780b8dcc0..e79f6fdd106 100644 --- a/src/Nethermind/Nethermind.JsonRpc/Modules/Proof/ProofRpcModule.cs +++ b/src/Nethermind/Nethermind.JsonRpc/Modules/Proof/ProofRpcModule.cs @@ -14,6 +14,7 @@ using Nethermind.Evm; using Nethermind.Evm.Tracing; using Nethermind.Evm.Tracing.Proofs; +using Nethermind.Facade.Eth; using Nethermind.JsonRpc.Data; using Nethermind.Logging; using Nethermind.Serialization.Rlp; diff --git a/src/Nethermind/Nethermind.JsonRpc/Modules/Proof/TransactionWithProof.cs b/src/Nethermind/Nethermind.JsonRpc/Modules/Proof/TransactionWithProof.cs index 4294cfb4ee6..3069c957849 100644 --- a/src/Nethermind/Nethermind.JsonRpc/Modules/Proof/TransactionWithProof.cs +++ b/src/Nethermind/Nethermind.JsonRpc/Modules/Proof/TransactionWithProof.cs @@ -1,6 +1,7 @@ // SPDX-FileCopyrightText: 2022 Demerzel Solutions Limited // SPDX-License-Identifier: LGPL-3.0-only +using Nethermind.Facade.Eth; using Nethermind.JsonRpc.Data; namespace Nethermind.JsonRpc.Modules.Proof diff --git a/src/Nethermind/Nethermind.JsonRpc/Modules/Subscribe/NewHeadSubscription.cs b/src/Nethermind/Nethermind.JsonRpc/Modules/Subscribe/NewHeadSubscription.cs index 11a414c1bf3..ed9fccd2511 100644 --- a/src/Nethermind/Nethermind.JsonRpc/Modules/Subscribe/NewHeadSubscription.cs +++ b/src/Nethermind/Nethermind.JsonRpc/Modules/Subscribe/NewHeadSubscription.cs @@ -5,6 +5,7 @@ using Nethermind.Blockchain; using Nethermind.Core; using Nethermind.Core.Specs; +using Nethermind.Facade.Eth; using Nethermind.JsonRpc.Modules.Eth; using Nethermind.Logging; diff --git a/src/Nethermind/Nethermind.JsonRpc/Modules/Subscribe/NewPendingTransactionsSubscription.cs b/src/Nethermind/Nethermind.JsonRpc/Modules/Subscribe/NewPendingTransactionsSubscription.cs index fe900bc6181..7ec4b75d77d 100644 --- a/src/Nethermind/Nethermind.JsonRpc/Modules/Subscribe/NewPendingTransactionsSubscription.cs +++ b/src/Nethermind/Nethermind.JsonRpc/Modules/Subscribe/NewPendingTransactionsSubscription.cs @@ -3,6 +3,7 @@ using System; using System.Threading.Tasks; +using Nethermind.Facade.Eth; using Nethermind.JsonRpc.Data; using Nethermind.JsonRpc.Modules.Eth; using Nethermind.Logging; diff --git a/src/Nethermind/Nethermind.JsonRpc/Modules/Trace/ITraceRpcModule.cs b/src/Nethermind/Nethermind.JsonRpc/Modules/Trace/ITraceRpcModule.cs index d3b06c6675e..dae0f773487 100644 --- a/src/Nethermind/Nethermind.JsonRpc/Modules/Trace/ITraceRpcModule.cs +++ b/src/Nethermind/Nethermind.JsonRpc/Modules/Trace/ITraceRpcModule.cs @@ -4,6 +4,7 @@ using System.Collections.Generic; using Nethermind.Blockchain.Find; using Nethermind.Core.Crypto; +using Nethermind.Facade.Eth; using Nethermind.JsonRpc.Data; namespace Nethermind.JsonRpc.Modules.Trace diff --git a/src/Nethermind/Nethermind.JsonRpc/Modules/Trace/TraceRpcModule.cs b/src/Nethermind/Nethermind.JsonRpc/Modules/Trace/TraceRpcModule.cs index dce79316b4d..3ee10c8bcb3 100644 --- a/src/Nethermind/Nethermind.JsonRpc/Modules/Trace/TraceRpcModule.cs +++ b/src/Nethermind/Nethermind.JsonRpc/Modules/Trace/TraceRpcModule.cs @@ -15,6 +15,7 @@ using Nethermind.Evm.Tracing; using Nethermind.Evm.Tracing.ParityStyle; using Nethermind.Facade; +using Nethermind.Facade.Eth; using Nethermind.Int256; using Nethermind.JsonRpc.Data; using Nethermind.Logging; diff --git a/src/Nethermind/Nethermind.JsonRpc/Modules/TxPool/TransactionPoolContent.cs b/src/Nethermind/Nethermind.JsonRpc/Modules/TxPool/TransactionPoolContent.cs index 5d0b6c0f3ba..2040cc403e8 100644 --- a/src/Nethermind/Nethermind.JsonRpc/Modules/TxPool/TransactionPoolContent.cs +++ b/src/Nethermind/Nethermind.JsonRpc/Modules/TxPool/TransactionPoolContent.cs @@ -4,6 +4,7 @@ using System.Collections.Generic; using System.Linq; using Nethermind.Core; +using Nethermind.Facade.Eth; using Nethermind.JsonRpc.Data; using Nethermind.TxPool; diff --git a/src/Nethermind/Nethermind.Merge.Plugin.Test/EngineModuleTests.V1.cs b/src/Nethermind/Nethermind.Merge.Plugin.Test/EngineModuleTests.V1.cs index bfd1368d636..e742e263aa0 100644 --- a/src/Nethermind/Nethermind.Merge.Plugin.Test/EngineModuleTests.V1.cs +++ b/src/Nethermind/Nethermind.Merge.Plugin.Test/EngineModuleTests.V1.cs @@ -20,6 +20,7 @@ using Nethermind.Core.Test.Builders; using Nethermind.Crypto; using Nethermind.Evm; +using Nethermind.Facade.Eth; using Nethermind.HealthChecks; using Nethermind.Int256; using Nethermind.JsonRpc; diff --git a/src/Nethermind/Nethermind.Merge.Plugin.Test/ExternalRpcIntegrationTests.cs b/src/Nethermind/Nethermind.Merge.Plugin.Test/ExternalRpcIntegrationTests.cs index 194e413c785..23b20746b5b 100644 --- a/src/Nethermind/Nethermind.Merge.Plugin.Test/ExternalRpcIntegrationTests.cs +++ b/src/Nethermind/Nethermind.Merge.Plugin.Test/ExternalRpcIntegrationTests.cs @@ -4,6 +4,7 @@ using System.Threading.Tasks; using Nethermind.Core.Crypto; using Nethermind.Core.Extensions; +using Nethermind.Facade.Eth; using Nethermind.Int256; using Nethermind.JsonRpc.Modules.Eth; using Nethermind.Overseer.Test.JsonRpc; diff --git a/src/Nethermind/Nethermind.Mev/Execution/TxBundleExecutor.cs b/src/Nethermind/Nethermind.Mev/Execution/TxBundleExecutor.cs index 17688769cdc..9791875b94c 100644 --- a/src/Nethermind/Nethermind.Mev/Execution/TxBundleExecutor.cs +++ b/src/Nethermind/Nethermind.Mev/Execution/TxBundleExecutor.cs @@ -68,7 +68,7 @@ private Block BuildBlock(MevBundle bundle, BlockHeader parent, ulong? timestamp) protected abstract TBlockTracer CreateBlockTracer(MevBundle mevBundle); - protected ResultWrapper GetInputError(BlockchainBridge.CallOutput result) => + protected ResultWrapper GetInputError(CallOutput result) => ResultWrapper.Fail(result.Error ?? string.Empty, ErrorCodes.InvalidInput); diff --git a/src/Nethermind/Nethermind.Optimism/InitializeBlockchainOptimism.cs b/src/Nethermind/Nethermind.Optimism/InitializeBlockchainOptimism.cs index edf205a9767..0fb0a302ede 100644 --- a/src/Nethermind/Nethermind.Optimism/InitializeBlockchainOptimism.cs +++ b/src/Nethermind/Nethermind.Optimism/InitializeBlockchainOptimism.cs @@ -44,7 +44,8 @@ protected override ITransactionProcessor CreateTransactionProcessor() if (_api.L1CostHelper is null) throw new StepDependencyException(nameof(_api.L1CostHelper)); if (_api.WorldState is null) throw new StepDependencyException(nameof(_api.WorldState)); - VirtualMachine virtualMachine = CreateVirtualMachine(); + CodeInfoRepository codeInfoRepository = new(); + VirtualMachine virtualMachine = CreateVirtualMachine(codeInfoRepository); return new OptimismTransactionProcessor( _api.SpecProvider, @@ -52,7 +53,8 @@ protected override ITransactionProcessor CreateTransactionProcessor() virtualMachine, _api.LogManager, _api.L1CostHelper, - _api.SpecHelper + _api.SpecHelper, + codeInfoRepository ); } diff --git a/src/Nethermind/Nethermind.Optimism/OptimismReadOnlyTxProcessingEnv.cs b/src/Nethermind/Nethermind.Optimism/OptimismReadOnlyTxProcessingEnv.cs index 85016b1b2dd..499a0c9883f 100644 --- a/src/Nethermind/Nethermind.Optimism/OptimismReadOnlyTxProcessingEnv.cs +++ b/src/Nethermind/Nethermind.Optimism/OptimismReadOnlyTxProcessingEnv.cs @@ -29,10 +29,10 @@ public class OptimismReadOnlyTxProcessingEnv( { protected override TransactionProcessor CreateTransactionProcessor() { - ArgumentNullException.ThrowIfNull(_logManager); + ArgumentNullException.ThrowIfNull(LogManager); - BlockhashProvider blockhashProvider = new(BlockTree, _specProvider, StateProvider, _logManager); - VirtualMachine virtualMachine = new(blockhashProvider, _specProvider, _logManager); - return new OptimismTransactionProcessor(_specProvider, StateProvider, virtualMachine, _logManager, l1CostHelper, opSpecHelper); + BlockhashProvider blockhashProvider = new(BlockTree, SpecProvider, StateProvider, LogManager); + VirtualMachine virtualMachine = new(blockhashProvider, SpecProvider, CodeInfoRepository, LogManager); + return new OptimismTransactionProcessor(SpecProvider, StateProvider, virtualMachine, LogManager, l1CostHelper, opSpecHelper, CodeInfoRepository); } } diff --git a/src/Nethermind/Nethermind.Optimism/OptimismTransactionProcessor.cs b/src/Nethermind/Nethermind.Optimism/OptimismTransactionProcessor.cs index ccf02547435..aa71d1fae45 100644 --- a/src/Nethermind/Nethermind.Optimism/OptimismTransactionProcessor.cs +++ b/src/Nethermind/Nethermind.Optimism/OptimismTransactionProcessor.cs @@ -19,7 +19,9 @@ public class OptimismTransactionProcessor( IVirtualMachine virtualMachine, ILogManager logManager, IL1CostHelper l1CostHelper, - IOptimismSpecHelper opSpecHelper) : TransactionProcessor(specProvider, worldState, virtualMachine, logManager) + IOptimismSpecHelper opSpecHelper, + ICodeInfoRepository? codeInfoRepository + ) : TransactionProcessor(specProvider, worldState, virtualMachine, codeInfoRepository, logManager) { private UInt256? _currentTxL1Cost; diff --git a/src/Nethermind/Nethermind.Optimism/Rpc/OptimismEthModuleFactory.cs b/src/Nethermind/Nethermind.Optimism/Rpc/OptimismEthModuleFactory.cs index d3e6bbd605e..9604bbdc78b 100644 --- a/src/Nethermind/Nethermind.Optimism/Rpc/OptimismEthModuleFactory.cs +++ b/src/Nethermind/Nethermind.Optimism/Rpc/OptimismEthModuleFactory.cs @@ -35,6 +35,7 @@ public class OptimismEthModuleFactory( IGasPriceOracle gasPriceOracle, IEthSyncingInfo ethSyncingInfo, IFeeHistoryOracle feeHistoryOracle, + ulong? secondsPerSlot, IJsonRpcClient? sequencerRpcClient, IAccountStateProvider accountStateProvider, @@ -79,6 +80,7 @@ public override IOptimismEthRpcModule Create() _gasPriceOracle, _ethSyncingInfo, _feeHistoryOracle, + secondsPerSlot, _sequencerRpcClient, _accountStateProvider, diff --git a/src/Nethermind/Nethermind.Optimism/Rpc/OptimismEthRpcModule.cs b/src/Nethermind/Nethermind.Optimism/Rpc/OptimismEthRpcModule.cs index 8e78818a6ac..27154fdf0e1 100644 --- a/src/Nethermind/Nethermind.Optimism/Rpc/OptimismEthRpcModule.cs +++ b/src/Nethermind/Nethermind.Optimism/Rpc/OptimismEthRpcModule.cs @@ -49,6 +49,7 @@ public OptimismEthRpcModule( IGasPriceOracle gasPriceOracle, IEthSyncingInfo ethSyncingInfo, IFeeHistoryOracle feeHistoryOracle, + ulong? secondsPerSlot, IJsonRpcClient? sequencerRpcClient, IAccountStateProvider accountStateProvider, @@ -67,7 +68,8 @@ public OptimismEthRpcModule( specProvider, gasPriceOracle, ethSyncingInfo, - feeHistoryOracle) + feeHistoryOracle, + secondsPerSlot) { _sequencerRpcClient = sequencerRpcClient; _accountStateProvider = accountStateProvider; diff --git a/src/Nethermind/Nethermind.Optimism/Rpc/RegisterOptimismRpcModules.cs b/src/Nethermind/Nethermind.Optimism/Rpc/RegisterOptimismRpcModules.cs index c72ad28a6c6..2b509d129c0 100644 --- a/src/Nethermind/Nethermind.Optimism/Rpc/RegisterOptimismRpcModules.cs +++ b/src/Nethermind/Nethermind.Optimism/Rpc/RegisterOptimismRpcModules.cs @@ -4,6 +4,7 @@ using System; using Nethermind.Api; using Nethermind.Blockchain; +using Nethermind.Config; using Nethermind.Consensus.AuRa.Withdrawals; using Nethermind.Consensus.Withdrawals; using Nethermind.Init.Steps; @@ -79,8 +80,9 @@ protected override void RegisterEthRpcModule(IRpcModuleProvider rpcModuleProvide _api.GasPriceOracle, _api.EthSyncingInfo, feeHistoryOracle, + _api.ConfigProvider.GetConfig().SecondsPerSlot, - sequencerJsonRpcClient, + sequencerJsonRpcClient, _api.WorldState, _api.EthereumEcdsa, sealer, diff --git a/src/Nethermind/Nethermind.Overseer.Test/CliqueTests.cs b/src/Nethermind/Nethermind.Overseer.Test/CliqueTests.cs index 5088be5b61f..bec343e4e35 100644 --- a/src/Nethermind/Nethermind.Overseer.Test/CliqueTests.cs +++ b/src/Nethermind/Nethermind.Overseer.Test/CliqueTests.cs @@ -4,6 +4,7 @@ using System.Threading.Tasks; using Nethermind.Core.Extensions; using Nethermind.Crypto; +using Nethermind.Facade.Eth; using Nethermind.JsonRpc.Data; using Nethermind.Overseer.Test.Framework; using NUnit.Framework; diff --git a/src/Nethermind/Nethermind.Overseer.Test/Framework/CliqueContext.cs b/src/Nethermind/Nethermind.Overseer.Test/Framework/CliqueContext.cs index 8affec1b268..9d22da0ee84 100644 --- a/src/Nethermind/Nethermind.Overseer.Test/Framework/CliqueContext.cs +++ b/src/Nethermind/Nethermind.Overseer.Test/Framework/CliqueContext.cs @@ -2,6 +2,7 @@ // SPDX-License-Identifier: LGPL-3.0-only using Nethermind.Core; +using Nethermind.Facade.Eth; using Nethermind.JsonRpc.Data; using Nethermind.Overseer.Test.JsonRpc; diff --git a/src/Nethermind/Nethermind.Runner/Ethereum/Api/ApiBuilder.cs b/src/Nethermind/Nethermind.Runner/Ethereum/Api/ApiBuilder.cs index 217cb5fbd6c..2d097c4a769 100644 --- a/src/Nethermind/Nethermind.Runner/Ethereum/Api/ApiBuilder.cs +++ b/src/Nethermind/Nethermind.Runner/Ethereum/Api/ApiBuilder.cs @@ -11,6 +11,7 @@ using Nethermind.Config; using Nethermind.Consensus; using Nethermind.Core; +using Nethermind.Facade.Eth; using Nethermind.JsonRpc.Data; using Nethermind.Logging; using Nethermind.Serialization.Json; diff --git a/src/Nethermind/Nethermind.State.Test/StateProviderTests.cs b/src/Nethermind/Nethermind.State.Test/StateProviderTests.cs index cc0f1e4496c..ee924de5b4e 100644 --- a/src/Nethermind/Nethermind.State.Test/StateProviderTests.cs +++ b/src/Nethermind/Nethermind.State.Test/StateProviderTests.cs @@ -176,9 +176,9 @@ public void Keep_in_cache() [Test] public void Restore_in_the_middle() { - byte[] code = new byte[] { 1 }; + byte[] code = [1]; - WorldState provider = new(new TrieStore(new MemDb(), Logger), _codeDb, Logger); + IWorldState provider = new WorldState(new TrieStore(new MemDb(), Logger), _codeDb, Logger); provider.CreateAccount(_address1, 1); provider.AddToBalance(_address1, 1, Frontier.Instance); provider.IncrementNonce(_address1); diff --git a/src/Nethermind/Nethermind.State/IWorldState.cs b/src/Nethermind/Nethermind.State/IWorldState.cs index b09a63ae39a..9c78813ce93 100644 --- a/src/Nethermind/Nethermind.State/IWorldState.cs +++ b/src/Nethermind/Nethermind.State/IWorldState.cs @@ -96,9 +96,13 @@ public interface IWorldState : IJournal, IReadOnlyStateProvider void UpdateStorageRoot(Address address, Hash256 storageRoot); - void IncrementNonce(Address address); + void IncrementNonce(Address address, UInt256 delta); - void DecrementNonce(Address address); + void DecrementNonce(Address address, UInt256 delta); + + void IncrementNonce(Address address) => IncrementNonce(address, UInt256.One); + + void DecrementNonce(Address address) => DecrementNonce(address, UInt256.One); /* snapshots */ diff --git a/src/Nethermind/Nethermind.State/IWorldStateManager.cs b/src/Nethermind/Nethermind.State/IWorldStateManager.cs index b71b55cfdb6..ef853ddbf51 100644 --- a/src/Nethermind/Nethermind.State/IWorldStateManager.cs +++ b/src/Nethermind/Nethermind.State/IWorldStateManager.cs @@ -10,6 +10,7 @@ public interface IWorldStateManager { IWorldState GlobalWorldState { get; } IStateReader GlobalStateReader { get; } + IReadOnlyTrieStore TrieStore { get; } /// /// Used by read only tasks that need to execute blocks. diff --git a/src/Nethermind/Nethermind.State/OverlayWorldStateManager.cs b/src/Nethermind/Nethermind.State/OverlayWorldStateManager.cs new file mode 100644 index 00000000000..a4dda8fdd65 --- /dev/null +++ b/src/Nethermind/Nethermind.State/OverlayWorldStateManager.cs @@ -0,0 +1,50 @@ +// SPDX-FileCopyrightText: 2024 Demerzel Solutions Limited +// SPDX-License-Identifier: LGPL-3.0-only + +using System; +using Nethermind.Db; +using Nethermind.Logging; +using Nethermind.Trie; +using Nethermind.Trie.Pruning; + +namespace Nethermind.State; + +public class OverlayWorldStateManager( + IReadOnlyDbProvider dbProvider, + OverlayTrieStore overlayTrieStore, + ILogManager? logManager) + : IWorldStateManager +{ + private readonly IDb _codeDb = dbProvider.GetDb(DbNames.Code); + + private readonly StateReader _reader = new(overlayTrieStore, dbProvider.GetDb(DbNames.Code), logManager); + + private readonly WorldState _state = new(overlayTrieStore, dbProvider.GetDb(DbNames.Code), logManager); + + public IWorldState GlobalWorldState => _state; + + public IStateReader GlobalStateReader => _reader; + + public IReadOnlyTrieStore TrieStore { get; } = overlayTrieStore.AsReadOnly(); + + public IWorldState CreateResettableWorldState(IWorldState? forWarmup = null) + { + PreBlockCaches? preBlockCaches = (forWarmup as IPreBlockCaches)?.Caches; + return preBlockCaches is not null + ? new WorldState( + new PreCachedTrieStore(overlayTrieStore, preBlockCaches.RlpCache), + _codeDb, + logManager, + preBlockCaches) + : new WorldState( + overlayTrieStore, + _codeDb, + logManager); + } + + public event EventHandler? ReorgBoundaryReached + { + add => overlayTrieStore.ReorgBoundaryReached += value; + remove => overlayTrieStore.ReorgBoundaryReached -= value; + } +} diff --git a/src/Nethermind/Nethermind.State/ReadOnlyWorldStateManager.cs b/src/Nethermind/Nethermind.State/ReadOnlyWorldStateManager.cs index ae24ac39a89..ed09e9574b6 100644 --- a/src/Nethermind/Nethermind.State/ReadOnlyWorldStateManager.cs +++ b/src/Nethermind/Nethermind.State/ReadOnlyWorldStateManager.cs @@ -36,6 +36,8 @@ ILogManager logManager public IStateReader GlobalStateReader { get; } + public IReadOnlyTrieStore TrieStore => _readOnlyTrieStore; + public IWorldState CreateResettableWorldState(IWorldState? forWarmup = null) { PreBlockCaches? preBlockCaches = (forWarmup as IPreBlockCaches)?.Caches; diff --git a/src/Nethermind/Nethermind.State/StateProvider.cs b/src/Nethermind/Nethermind.State/StateProvider.cs index 0a910d07b89..5fdff40bd43 100644 --- a/src/Nethermind/Nethermind.State/StateProvider.cs +++ b/src/Nethermind/Nethermind.State/StateProvider.cs @@ -250,7 +250,7 @@ public void UpdateStorageRoot(Address address, Hash256 storageRoot) } } - public void IncrementNonce(Address address) + public void IncrementNonce(Address address, UInt256 delta) { _needsStateRootUpdate = true; Account? account = GetThroughCache(address); @@ -259,12 +259,12 @@ public void IncrementNonce(Address address) throw new InvalidOperationException($"Account {address} is null when incrementing nonce"); } - Account changedAccount = account.WithChangedNonce(account.Nonce + 1); + Account changedAccount = account.WithChangedNonce(account.Nonce + delta); if (_logger.IsTrace) _logger.Trace($" Update {address} N {account.Nonce} -> {changedAccount.Nonce}"); PushUpdate(address, changedAccount); } - public void DecrementNonce(Address address) + public void DecrementNonce(Address address, UInt256 delta) { _needsStateRootUpdate = true; Account? account = GetThroughCache(address); @@ -273,7 +273,7 @@ public void DecrementNonce(Address address) throw new InvalidOperationException($"Account {address} is null when decrementing nonce."); } - Account changedAccount = account.WithChangedNonce(account.Nonce - 1); + Account changedAccount = account.WithChangedNonce(account.Nonce - delta); if (_logger.IsTrace) _logger.Trace($" Update {address} N {account.Nonce} -> {changedAccount.Nonce}"); PushUpdate(address, changedAccount); } @@ -386,8 +386,10 @@ public void CreateAccount(Address address, in UInt256 balance, in UInt256 nonce public void CreateAccountIfNotExists(Address address, in UInt256 balance, in UInt256 nonce = default) { - if (AccountExists(address)) return; - CreateAccount(address, balance, nonce); + if (!AccountExists(address)) + { + CreateAccount(address, balance, nonce); + } } public void AddToBalanceAndCreateIfNotExists(Address address, in UInt256 balance, IReleaseSpec spec) diff --git a/src/Nethermind/Nethermind.State/WorldState.cs b/src/Nethermind/Nethermind.State/WorldState.cs index 476f73de47d..04de5713578 100644 --- a/src/Nethermind/Nethermind.State/WorldState.cs +++ b/src/Nethermind/Nethermind.State/WorldState.cs @@ -165,13 +165,13 @@ public void UpdateStorageRoot(Address address, Hash256 storageRoot) { _stateProvider.UpdateStorageRoot(address, storageRoot); } - public void IncrementNonce(Address address) + public void IncrementNonce(Address address, UInt256 delta) { - _stateProvider.IncrementNonce(address); + _stateProvider.IncrementNonce(address, delta); } - public void DecrementNonce(Address address) + public void DecrementNonce(Address address, UInt256 delta) { - _stateProvider.DecrementNonce(address); + _stateProvider.DecrementNonce(address, delta); } public void CommitTree(long blockNumber) diff --git a/src/Nethermind/Nethermind.Synchronization.Test/SyncThreadTests.cs b/src/Nethermind/Nethermind.Synchronization.Test/SyncThreadTests.cs index bc0e49c4332..c41fc74bbe2 100644 --- a/src/Nethermind/Nethermind.Synchronization.Test/SyncThreadTests.cs +++ b/src/Nethermind/Nethermind.Synchronization.Test/SyncThreadTests.cs @@ -276,7 +276,8 @@ private SyncTestContext CreateSyncManager(int index) logManager, transactionComparerProvider.GetDefaultComparer()); BlockhashProvider blockhashProvider = new(tree, specProvider, stateProvider, LimboLogs.Instance); - VirtualMachine virtualMachine = new(blockhashProvider, specProvider, logManager); + CodeInfoRepository codeInfoRepository = new(); + VirtualMachine virtualMachine = new(blockhashProvider, specProvider, codeInfoRepository, logManager); Always sealValidator = Always.Valid; HeaderValidator headerValidator = new(tree, sealValidator, specProvider, logManager); @@ -291,7 +292,7 @@ private SyncTestContext CreateSyncManager(int index) RewardCalculator rewardCalculator = new(specProvider); TransactionProcessor txProcessor = - new(specProvider, stateProvider, virtualMachine, logManager); + new(specProvider, stateProvider, virtualMachine, codeInfoRepository, logManager); BlockProcessor blockProcessor = new( specProvider, @@ -312,8 +313,8 @@ private SyncTestContext CreateSyncManager(int index) SyncPeerPool syncPeerPool = new(tree, nodeStatsManager, new TotalDifficultyBetterPeerStrategy(LimboLogs.Instance), logManager, 25); WorldState devState = new(trieStore, codeDb, logManager); - VirtualMachine devEvm = new(blockhashProvider, specProvider, logManager); - TransactionProcessor devTxProcessor = new(specProvider, devState, devEvm, logManager); + VirtualMachine devEvm = new(blockhashProvider, specProvider, codeInfoRepository, logManager); + TransactionProcessor devTxProcessor = new(specProvider, devState, devEvm, codeInfoRepository, logManager); BlockProcessor devBlockProcessor = new( specProvider, diff --git a/src/Nethermind/Nethermind.Trie.Test/TrieTests.cs b/src/Nethermind/Nethermind.Trie.Test/TrieTests.cs index 47953a99e84..0d9e05c1e1f 100644 --- a/src/Nethermind/Nethermind.Trie.Test/TrieTests.cs +++ b/src/Nethermind/Nethermind.Trie.Test/TrieTests.cs @@ -1013,7 +1013,7 @@ public void Fuzz_accounts_with_storage( MemDb memDb = new(); using TrieStore trieStore = new(memDb, Prune.WhenCacheReaches(1.MB()), Persist.IfBlockOlderThan(lookupLimit), _logManager); - WorldState stateProvider = new(trieStore, new MemDb(), _logManager); + WorldState stateProvider = new WorldState(trieStore, new MemDb(), _logManager); Account[] accounts = new Account[accountsCount]; Address[] addresses = new Address[accountsCount]; @@ -1062,7 +1062,7 @@ public void Fuzz_accounts_with_storage( address, existing.Balance - account.Balance, MuirGlacier.Instance); } - stateProvider.IncrementNonce(address); + stateProvider.IncrementNonce(address, UInt256.One); } byte[] storage = new byte[1]; diff --git a/src/Nethermind/Nethermind.Trie/Pruning/OverlayTrieStore.cs b/src/Nethermind/Nethermind.Trie/Pruning/OverlayTrieStore.cs new file mode 100644 index 00000000000..50977e6ec5d --- /dev/null +++ b/src/Nethermind/Nethermind.Trie/Pruning/OverlayTrieStore.cs @@ -0,0 +1,27 @@ +// SPDX-FileCopyrightText: 2024 Demerzel Solutions Limited +// SPDX-License-Identifier: LGPL-3.0-only + +using System; +using Nethermind.Core; +using Nethermind.Core.Crypto; +using Nethermind.Logging; + +namespace Nethermind.Trie.Pruning; + +public class OverlayTrieStore(IKeyValueStoreWithBatching? keyValueStore, IReadOnlyTrieStore store, ILogManager? logManager) : TrieStore(keyValueStore, logManager) +{ + public override bool IsPersisted(Hash256? address, in TreePath path, in ValueHash256 keccak) => + base.IsPersisted(address, in path, in keccak) || store.IsPersisted(address, in path, in keccak); + + public override TrieNode FindCachedOrUnknown(Hash256? address, in TreePath path, Hash256? hash) + { + TrieNode node = base.FindCachedOrUnknown(address, in path, hash); + return node.NodeType == NodeType.Unknown ? store.FindCachedOrUnknown(address, in path, hash) : node; + } + + public override byte[]? LoadRlp(Hash256? address, in TreePath path, Hash256 hash, ReadFlags flags = ReadFlags.None) => + base.TryLoadRlp(address, in path, hash, flags) ?? store.LoadRlp(address, in path, hash, flags); + + public override byte[]? TryLoadRlp(Hash256? address, in TreePath path, Hash256 hash, ReadFlags flags = ReadFlags.None) => + base.TryLoadRlp(address, in path, hash, flags) ?? store.TryLoadRlp(address, in path, hash, flags); +} diff --git a/src/Nethermind/Nethermind.Trie/Pruning/TrieStore.cs b/src/Nethermind/Nethermind.Trie/Pruning/TrieStore.cs index 56e7b294fcd..a30a83402a9 100644 --- a/src/Nethermind/Nethermind.Trie/Pruning/TrieStore.cs +++ b/src/Nethermind/Nethermind.Trie/Pruning/TrieStore.cs @@ -594,9 +594,9 @@ static void ThrowMissingNode(Hash256 keccak) } public virtual byte[]? LoadRlp(Hash256? address, in TreePath path, Hash256 hash, ReadFlags flags = ReadFlags.None) => LoadRlp(address, path, hash, null, flags); - public byte[]? TryLoadRlp(Hash256? address, in TreePath path, Hash256 hash, ReadFlags flags = ReadFlags.None) => TryLoadRlp(address, path, hash, null, flags); + public virtual byte[]? TryLoadRlp(Hash256? address, in TreePath path, Hash256 hash, ReadFlags flags = ReadFlags.None) => TryLoadRlp(address, path, hash, null, flags); - public bool IsPersisted(Hash256? address, in TreePath path, in ValueHash256 keccak) + public virtual bool IsPersisted(Hash256? address, in TreePath path, in ValueHash256 keccak) { byte[]? rlp = _nodeStorage.Get(address, path, keccak, ReadFlags.None); @@ -614,9 +614,8 @@ public IReadOnlyTrieStore AsReadOnly(INodeStorage? store) => new ReadOnlyTrieStore(this, store); public bool IsNodeCached(Hash256? address, in TreePath path, Hash256? hash) => _dirtyNodes.IsNodeCached(new DirtyNodesCache.Key(address, path, hash)); - private bool IsNodeCached(DirtyNodesCache.Key key) => _dirtyNodes.IsNodeCached(key); - public TrieNode FindCachedOrUnknown(Hash256? address, in TreePath path, Hash256? hash) => + public virtual TrieNode FindCachedOrUnknown(Hash256? address, in TreePath path, Hash256? hash) => FindCachedOrUnknown(address, path, hash, false); internal TrieNode FindCachedOrUnknown(Hash256? address, in TreePath path, Hash256? hash, bool isReadOnly)