diff --git a/src/Nethermind/Nethermind.Init/Steps/RegisterRpcModules.cs b/src/Nethermind/Nethermind.Init/Steps/RegisterRpcModules.cs index bc77defb9d5..2dbf397a00a 100644 --- a/src/Nethermind/Nethermind.Init/Steps/RegisterRpcModules.cs +++ b/src/Nethermind/Nethermind.Init/Steps/RegisterRpcModules.cs @@ -113,7 +113,8 @@ public virtual async Task Execute(CancellationToken cancellationToken) _api.SyncModeSelector, _api.BadBlocksStore, _api.FileSystem, - _api.LogManager); + _api.LogManager, + _api.StateReader); rpcModuleProvider.RegisterBoundedByCpuCount(debugModuleFactory, _jsonRpcConfig.Timeout); RegisterTraceRpcModule(rpcModuleProvider); diff --git a/src/Nethermind/Nethermind.JsonRpc.Test/Modules/DebugModuleTests.cs b/src/Nethermind/Nethermind.JsonRpc.Test/Modules/DebugModuleTests.cs index fed0e3ccfba..61eb40b8c93 100644 --- a/src/Nethermind/Nethermind.JsonRpc.Test/Modules/DebugModuleTests.cs +++ b/src/Nethermind/Nethermind.JsonRpc.Test/Modules/DebugModuleTests.cs @@ -393,6 +393,109 @@ public void Debug_traceCall_test() debugTraceCall.Should().BeEquivalentTo(expected); } + [Test] + public void Debug_traceCallMany_test() + { + GethTxTraceEntry entry = new(); + + entry.Storage = new Dictionary + { + {"1".PadLeft(64, '0'), "2".PadLeft(64, '0')}, + {"3".PadLeft(64, '0'), "4".PadLeft(64, '0')}, + }; + + entry.Memory = new string[] + { + "5".PadLeft(64, '0'), + "6".PadLeft(64, '0') + }; + entry.Stack = new string[] { }; + entry.Opcode = "STOP"; + entry.Gas = 22000; + entry.GasCost = 1; + entry.Depth = 1; + + var traces = new List(); + traces.Add(new GethLikeTxTrace() + { + ReturnValue = Bytes.FromHexString("a2"), + Entries = new List(), + }); + traces[0].Entries.Add(entry); + + + GethTraceOptions gtOptions = new(); + + Transaction transaction = Build.A.Transaction.WithTo(TestItem.AddressA).WithHash(TestItem.KeccakA).TestObject; + TransactionForRpc txForRpc = new(transaction); + TransactionForRpcWithTraceTypes transactionForRpcWithTraceTypes = new(); + transactionForRpcWithTraceTypes.Transaction = txForRpc; + transactionForRpcWithTraceTypes.TraceTypes = new string[1]; + + BlockHeader blockHeader = new BlockHeader( + new Hash256("0x0000000000000000000000000000000000000000000000000000000000000000"), + new Hash256("0x0000000000000000000000000000000000000000000000000000000000000000"), + new Address("0xfffffffffffffffffffffffffffffffffffffffe"), + 1000000, + 123456, + 8000000, + 1694890200, + new byte[] { 0x54, 0x65, 0x73, 0x74, 0x45, 0x78, 0x74, 0x72, 0x61, 0x44, 0x61, 0x74, 0x61 }, + 500000, + 100000, + new Hash256("0x0000000000000000000000000000000000000000000000000000000000000000") + ); + + debugBridge.GetBlockTrace(Arg.Any(), Arg.Any(), Arg.Any()).Returns(traces); + debugBridge.HasStateForBlock(Arg.Any()).Returns(true); + debugBridge.SearchBlockHeaderForTraceCall(Arg.Any()).Returns(new JsonRpc.Modules.SearchResult(blockHeader)); + + DebugRpcModule rpcModule = new(LimboLogs.Instance, debugBridge, jsonRpcConfig, specProvider); + ResultWrapper> debug_traceCallMany_output = rpcModule.debug_traceCallMany(new TransactionForRpcWithTraceTypes[] { transactionForRpcWithTraceTypes }, null); + + + var expected = ResultWrapper>.Success( + new List() + { + new GethLikeTxTrace() + { + Failed = false, + Entries = new List() + { + new GethTxTraceEntry() + { + Gas = 22000, + GasCost = 1, + Depth = 1, + Memory = new string[] + { + "0000000000000000000000000000000000000000000000000000000000000005", + "0000000000000000000000000000000000000000000000000000000000000006" + }, + Opcode = "STOP", + ProgramCounter = 0, + Stack = Array.Empty(), + Storage = new Dictionary() + { + { + "0000000000000000000000000000000000000000000000000000000000000001", + "0000000000000000000000000000000000000000000000000000000000000002" + }, + { + "0000000000000000000000000000000000000000000000000000000000000003", + "0000000000000000000000000000000000000000000000000000000000000004" + }, + } + } + }, + Gas = 0, + ReturnValue = new byte[] { 162 } + } + }); + + debug_traceCallMany_output.Should().BeEquivalentTo(expected); + } + [Test] public async Task Migrate_receipts() { diff --git a/src/Nethermind/Nethermind.JsonRpc/Modules/DebugModule/DebugBridge.cs b/src/Nethermind/Nethermind.JsonRpc/Modules/DebugModule/DebugBridge.cs index dd7d34ebc82..8876153aadd 100644 --- a/src/Nethermind/Nethermind.JsonRpc/Modules/DebugModule/DebugBridge.cs +++ b/src/Nethermind/Nethermind.JsonRpc/Modules/DebugModule/DebugBridge.cs @@ -17,7 +17,9 @@ using Nethermind.Core.Specs; using Nethermind.Db; using Nethermind.Evm.Tracing.GethStyle; +using Nethermind.Int256; using Nethermind.Serialization.Rlp; +using Nethermind.State; using Nethermind.State.Proofs; using Nethermind.Synchronization.ParallelSync; using Nethermind.Synchronization.Reporting; @@ -36,6 +38,7 @@ public class DebugBridge : IDebugBridge private readonly IBlockStore _badBlockStore; private readonly IBlockStore _blockStore; private readonly Dictionary _dbMappings; + private readonly IStateReader _stateReader; public DebugBridge( IConfigProvider configProvider, @@ -46,7 +49,8 @@ public DebugBridge( IReceiptsMigration receiptsMigration, ISpecProvider specProvider, ISyncModeSelector syncModeSelector, - IBlockStore badBlockStore) + IBlockStore badBlockStore, + IStateReader stateReader) { _configProvider = configProvider ?? throw new ArgumentNullException(nameof(configProvider)); _tracer = tracer ?? throw new ArgumentNullException(nameof(tracer)); @@ -56,12 +60,14 @@ public DebugBridge( _specProvider = specProvider ?? throw new ArgumentNullException(nameof(specProvider)); _syncModeSelector = syncModeSelector ?? throw new ArgumentNullException(nameof(syncModeSelector)); _badBlockStore = badBlockStore; + _stateReader = stateReader ?? throw new ArgumentNullException(nameof(stateReader)); dbProvider = dbProvider ?? throw new ArgumentNullException(nameof(dbProvider)); IDb blockInfosDb = dbProvider.BlockInfosDb ?? throw new ArgumentNullException(nameof(dbProvider.BlockInfosDb)); IDb blocksDb = dbProvider.BlocksDb ?? throw new ArgumentNullException(nameof(dbProvider.BlocksDb)); IDb headersDb = dbProvider.HeadersDb ?? throw new ArgumentNullException(nameof(dbProvider.HeadersDb)); IDb codeDb = dbProvider.CodeDb ?? throw new ArgumentNullException(nameof(dbProvider.CodeDb)); IDb metadataDb = dbProvider.MetadataDb ?? throw new ArgumentNullException(nameof(dbProvider.MetadataDb)); + _dbMappings = new Dictionary(StringComparer.InvariantCultureIgnoreCase) { @@ -212,4 +218,38 @@ public IEnumerable TraceBadBlockToFile( CancellationToken cancellationToken, GethTraceOptions? gethTraceOptions = null) => _tracer.TraceBadBlockToFile(blockHash, gethTraceOptions ?? GethTraceOptions.Default, cancellationToken); + + public SearchResult SearchBlockHeaderForTraceCall(BlockParameter blockParameter) + { + SearchResult headerSearch = _blockTree.SearchForHeader(blockParameter); + if (headerSearch.IsError) + { + return headerSearch; + } + + BlockHeader header = headerSearch.Object; + if (header!.IsGenesis) + { + UInt256 baseFee = header.BaseFeePerGas; + header = new BlockHeader( + header.Hash!, + Keccak.OfAnEmptySequenceRlp, + Address.Zero, + header.Difficulty, + header.Number + 1, + header.GasLimit, + header.Timestamp + 1, + header.ExtraData, + header.BlobGasUsed, + header.ExcessBlobGas); + + header.TotalDifficulty = 2 * header.Difficulty; + header.BaseFeePerGas = baseFee; + } + + return new SearchResult(header); + } + + public bool HasStateForBlock(BlockHeader header) => _stateReader.HasStateForBlock(header); + } diff --git a/src/Nethermind/Nethermind.JsonRpc/Modules/DebugModule/DebugModuleFactory.cs b/src/Nethermind/Nethermind.JsonRpc/Modules/DebugModule/DebugModuleFactory.cs index a394e5a36ea..833568c0066 100644 --- a/src/Nethermind/Nethermind.JsonRpc/Modules/DebugModule/DebugModuleFactory.cs +++ b/src/Nethermind/Nethermind.JsonRpc/Modules/DebugModule/DebugModuleFactory.cs @@ -6,6 +6,7 @@ using System.IO.Abstractions; using Nethermind.Blockchain; using Nethermind.Blockchain.Blocks; +using Nethermind.Blockchain.Find; using Nethermind.Blockchain.Receipts; using Nethermind.Config; using Nethermind.Consensus.Processing; @@ -41,6 +42,7 @@ public class DebugModuleFactory : ModuleFactoryBase private readonly IBlockStore _badBlockStore; private readonly IFileSystem _fileSystem; private readonly ILogger _logger; + private readonly IStateReader _stateReader; public DebugModuleFactory( IWorldStateManager worldStateManager, @@ -57,7 +59,8 @@ public DebugModuleFactory( ISyncModeSelector syncModeSelector, IBlockStore badBlockStore, IFileSystem fileSystem, - ILogManager logManager) + ILogManager logManager, + IStateReader stateReader) { _worldStateManager = worldStateManager; _dbProvider = dbProvider.AsReadOnly(false); @@ -75,6 +78,7 @@ public DebugModuleFactory( _badBlockStore = badBlockStore; _fileSystem = fileSystem ?? throw new ArgumentNullException(nameof(fileSystem)); _logger = logManager.GetClassLogger(); + _stateReader = stateReader ?? throw new ArgumentNullException(nameof(stateReader)); } public override IDebugRpcModule Create() @@ -120,7 +124,8 @@ public override IDebugRpcModule Create() _receiptsMigration, _specProvider, _syncModeSelector, - _badBlockStore); + _badBlockStore, + _stateReader); return new DebugRpcModule(_logManager, debugBridge, _jsonRpcConfig, _specProvider); } diff --git a/src/Nethermind/Nethermind.JsonRpc/Modules/DebugModule/DebugRpcModule.cs b/src/Nethermind/Nethermind.JsonRpc/Modules/DebugModule/DebugRpcModule.cs index aa0318197c6..352f59b7248 100644 --- a/src/Nethermind/Nethermind.JsonRpc/Modules/DebugModule/DebugRpcModule.cs +++ b/src/Nethermind/Nethermind.JsonRpc/Modules/DebugModule/DebugRpcModule.cs @@ -18,8 +18,16 @@ using System.Collections.Generic; using Nethermind.JsonRpc.Modules.Eth; using Nethermind.Core.Specs; + +using Nethermind.Facade.Eth; +using Nethermind.Evm.Tracing.ParityStyle; +using Nethermind.Int256; +using Newtonsoft.Json; +using Nethermind.State; + using Nethermind.Facade.Eth.RpcTransaction; + namespace Nethermind.JsonRpc.Modules.DebugModule; public class DebugRpcModule : IDebugRpcModule @@ -85,6 +93,45 @@ public ResultWrapper debug_traceCall(TransactionForRpc call, Bl if (_logger.IsTrace) _logger.Trace($"{nameof(debug_traceTransaction)} request {tx.Hash}, result: trace"); return ResultWrapper.Success(transactionTrace); } + public ResultWrapper> debug_traceCallMany(TransactionForRpcWithTraceTypes[] calls, BlockParameter? blockParameter = null) + { + blockParameter ??= BlockParameter.Latest; + using CancellationTokenSource cancellationTokenSource = new(_traceTimeout); + CancellationToken cancellationToken = cancellationTokenSource.Token; + + SearchResult headerSearch = _debugBridge.SearchBlockHeaderForTraceCall(blockParameter); + if (headerSearch.IsError) + { + return ResultWrapper>.Fail(headerSearch); + } + + if (!_debugBridge.HasStateForBlock(headerSearch.Object)) + { + return ResultWrapper>.Fail($"No state available for block {headerSearch.Object.ToString(BlockHeader.Format.FullHashAndNumber)}", ErrorCodes.ResourceUnavailable); + } + + Dictionary traceTypeByTransaction = new(calls.Length); + Transaction[] txs = new Transaction[calls.Length]; + for (int i = 0; i < calls.Length; i++) + { + calls[i].Transaction.EnsureDefaults(_jsonRpcConfig.GasCap); + Transaction tx = calls[i].Transaction.ToTransaction(); + tx.Hash = new Hash256(new UInt256((ulong)i).ToBigEndian()); + txs[i] = tx; + } + + Block block = new(headerSearch.Object!, txs, Enumerable.Empty()); + + IReadOnlyCollection traces = _debugBridge.GetBlockTrace(blockParameter, cancellationToken); + + if (traces is null) + { + return ResultWrapper>.Fail($"Failed to trace block transactions for input txns: {JsonConvert.SerializeObject(calls)}", ErrorCodes.ResourceNotFound); + } + + if (_logger.IsTrace) _logger.Trace($"{nameof(debug_traceCallMany)} with input transactions: {calls} returned the result: {traces}"); + return ResultWrapper>.Success(traces); + } public ResultWrapper debug_traceTransactionByBlockhashAndIndex(Hash256 blockhash, int index, GethTraceOptions options = null) { diff --git a/src/Nethermind/Nethermind.JsonRpc/Modules/DebugModule/IDebugBridge.cs b/src/Nethermind/Nethermind.JsonRpc/Modules/DebugModule/IDebugBridge.cs index 9b8db38d2cf..1d3299625e9 100644 --- a/src/Nethermind/Nethermind.JsonRpc/Modules/DebugModule/IDebugBridge.cs +++ b/src/Nethermind/Nethermind.JsonRpc/Modules/DebugModule/IDebugBridge.cs @@ -39,4 +39,6 @@ public interface IDebugBridge public IEnumerable GetBadBlocks(); TxReceipt[]? GetReceiptsForBlock(BlockParameter param); Transaction? GetTransactionFromHash(Hash256 hash); + public SearchResult SearchBlockHeaderForTraceCall(BlockParameter blockParameter); + public bool HasStateForBlock(BlockHeader header); } diff --git a/src/Nethermind/Nethermind.JsonRpc/Modules/DebugModule/IDebugRpcModule.cs b/src/Nethermind/Nethermind.JsonRpc/Modules/DebugModule/IDebugRpcModule.cs index f8c1129cddc..784bbc45539 100644 --- a/src/Nethermind/Nethermind.JsonRpc/Modules/DebugModule/IDebugRpcModule.cs +++ b/src/Nethermind/Nethermind.JsonRpc/Modules/DebugModule/IDebugRpcModule.cs @@ -33,6 +33,9 @@ public interface IDebugRpcModule : IRpcModule [JsonRpcMethod(Description = "This method lets you run an eth_call within the context of the given block execution using the final state of parent block as the base. The block can be specified either by hash or by number. It takes the same input object as a eth_call. It returns the same output as debug_traceTransaction.", IsImplemented = true, IsSharable = true)] ResultWrapper debug_traceCall(TransactionForRpc call, BlockParameter? blockParameter = null, GethTraceOptions? options = null); + [JsonRpcMethod(Description = "This method lets you run trace_callMany for a list of transactions.It doesn't charge fees. It returns a list of Trace objects.", IsImplemented = true, IsSharable = true)] + ResultWrapper> debug_traceCallMany(TransactionForRpcWithTraceTypes[] calls, BlockParameter? blockParameter = null); + [JsonRpcMethod(Description = "", IsSharable = true)] ResultWrapper debug_traceTransactionByBlockAndIndex(BlockParameter blockParameter, int txIndex, GethTraceOptions options = null);