diff --git a/src/Nethermind/Nethermind.Api/NethermindApi.cs b/src/Nethermind/Nethermind.Api/NethermindApi.cs index bc1288d8529..b6a3574c212 100644 --- a/src/Nethermind/Nethermind.Api/NethermindApi.cs +++ b/src/Nethermind/Nethermind.Api/NethermindApi.cs @@ -77,12 +77,13 @@ public NethermindApi(IConfigProvider configProvider, IJsonSerializer jsonSeriali public IBlockchainBridge CreateBlockchainBridge() { ReadOnlyBlockTree readOnlyTree = BlockTree!.AsReadOnly(); + OverridableWorldStateManager overridableWorldStateManager = new(DbProvider!, WorldStateManager!.TrieStore, LogManager); // TODO: reuse the same trie cache here - ReadOnlyTxProcessingEnv readOnlyTxProcessingEnv = new( - WorldStateManager!, + OverridableTxProcessingEnv txProcessingEnv = new( + overridableWorldStateManager, readOnlyTree, - SpecProvider, + SpecProvider!, LogManager); SimulateReadOnlyBlocksProcessingEnvFactory simulateReadOnlyBlocksProcessingEnvFactory = @@ -97,7 +98,7 @@ public IBlockchainBridge CreateBlockchainBridge() IBlocksConfig blocksConfig = ConfigProvider.GetConfig(); return new BlockchainBridge( - readOnlyTxProcessingEnv, + txProcessingEnv, simulateReadOnlyBlocksProcessingEnvFactory, TxPool, ReceiptFinder, diff --git a/src/Nethermind/Nethermind.Consensus/Processing/BlockchainProcessor.cs b/src/Nethermind/Nethermind.Consensus/Processing/BlockchainProcessor.cs index 5e4ed8b7f2d..59c816ad211 100644 --- a/src/Nethermind/Nethermind.Consensus/Processing/BlockchainProcessor.cs +++ b/src/Nethermind/Nethermind.Consensus/Processing/BlockchainProcessor.cs @@ -630,8 +630,10 @@ private ProcessingBranch PrepareProcessingBranch(Block suggestedBlock, Processin break; } - branchingPoint = _blockTree.FindParentHeader(toBeProcessed.Header, - BlockTreeLookupOptions.TotalDifficultyNotNeeded); + branchingPoint = options.ContainsFlag(ProcessingOptions.ForceSameBlock) + ? toBeProcessed.Header + : _blockTree.FindParentHeader(toBeProcessed.Header, BlockTreeLookupOptions.TotalDifficultyNotNeeded); + if (branchingPoint is null) { // genesis block diff --git a/src/Nethermind/Nethermind.Consensus/Processing/OverridableTxProcessingEnv.cs b/src/Nethermind/Nethermind.Consensus/Processing/OverridableTxProcessingEnv.cs new file mode 100644 index 00000000000..da0f252c160 --- /dev/null +++ b/src/Nethermind/Nethermind.Consensus/Processing/OverridableTxProcessingEnv.cs @@ -0,0 +1,64 @@ +// SPDX-FileCopyrightText: 2024 Demerzel Solutions Limited +// SPDX-License-Identifier: LGPL-3.0-only + +using System; +using System.Collections.Generic; +using Nethermind.Blockchain; +using Nethermind.Core; +using Nethermind.Core.Crypto; +using Nethermind.Core.Specs; +using Nethermind.Evm; +using Nethermind.Evm.TransactionProcessing; +using Nethermind.Logging; +using Nethermind.State; + +namespace Nethermind.Consensus.Processing; + +public class OverridableTxProcessingEnv : ReadOnlyTxProcessingEnvBase, IOverridableTxProcessorSource +{ + private readonly Lazy _transactionProcessorLazy; + + protected new OverridableWorldState StateProvider { get; } + protected OverridableWorldStateManager WorldStateManager { get; } + protected OverridableCodeInfoRepository CodeInfoRepository { get; } + protected IVirtualMachine Machine { get; } + protected ITransactionProcessor TransactionProcessor => _transactionProcessorLazy.Value; + + public OverridableTxProcessingEnv( + OverridableWorldStateManager worldStateManager, + IReadOnlyBlockTree readOnlyBlockTree, + ISpecProvider specProvider, + ILogManager? logManager, + IWorldState? worldStateToWarmUp = null + ) : base(worldStateManager, readOnlyBlockTree, specProvider, logManager, worldStateToWarmUp) + { + WorldStateManager = worldStateManager; + StateProvider = (OverridableWorldState)base.StateProvider; + CodeInfoRepository = new(new CodeInfoRepository((worldStateToWarmUp as IPreBlockCaches)?.Caches.PrecompileCache)); + Machine = new VirtualMachine(BlockhashProvider, specProvider, CodeInfoRepository, logManager); + _transactionProcessorLazy = new(CreateTransactionProcessor); + } + + protected virtual ITransactionProcessor CreateTransactionProcessor() => + new TransactionProcessor(SpecProvider, StateProvider, Machine, CodeInfoRepository, LogManager); + + IOverridableTxProcessingScope IOverridableTxProcessorSource.Build(Hash256 stateRoot) => Build(stateRoot); + + public OverridableTxProcessingScope Build(Hash256 stateRoot) + { + Hash256 originalStateRoot = StateProvider.StateRoot; + StateProvider.StateRoot = stateRoot; + return new(CodeInfoRepository, TransactionProcessor, StateProvider, originalStateRoot); + } + + IOverridableTxProcessingScope IOverridableTxProcessorSource.BuildAndOverride(BlockHeader header, Dictionary? stateOverride) + { + OverridableTxProcessingScope scope = Build(header.StateRoot ?? throw new ArgumentException($"Block {header.Hash} state root is null", nameof(header))); + if (stateOverride != null) + { + scope.WorldState.ApplyStateOverrides(scope.CodeInfoRepository, stateOverride, SpecProvider.GetSpec(header), header.Number); + header.StateRoot = scope.WorldState.StateRoot; + } + return scope; + } +} diff --git a/src/Nethermind/Nethermind.Consensus/Processing/OverridableTxProcessingScope.cs b/src/Nethermind/Nethermind.Consensus/Processing/OverridableTxProcessingScope.cs new file mode 100644 index 00000000000..b168b1c1e93 --- /dev/null +++ b/src/Nethermind/Nethermind.Consensus/Processing/OverridableTxProcessingScope.cs @@ -0,0 +1,29 @@ +// SPDX-FileCopyrightText: 2024 Demerzel Solutions Limited +// SPDX-License-Identifier: LGPL-3.0-only + +using Nethermind.Core.Crypto; +using Nethermind.Evm; +using Nethermind.Evm.TransactionProcessing; +using Nethermind.State; + +namespace Nethermind.Consensus.Processing; + +public class OverridableTxProcessingScope( + IOverridableCodeInfoRepository codeInfoRepository, + ITransactionProcessor transactionProcessor, + OverridableWorldState worldState, + Hash256 originalStateRoot +) : IOverridableTxProcessingScope +{ + public IOverridableCodeInfoRepository CodeInfoRepository => codeInfoRepository; + public ITransactionProcessor TransactionProcessor => transactionProcessor; + public IWorldState WorldState => worldState; + + public void Dispose() + { + worldState.StateRoot = originalStateRoot; + worldState.Reset(); + worldState.ResetOverrides(); + codeInfoRepository.ResetOverrides(); + } +} diff --git a/src/Nethermind/Nethermind.Consensus/Processing/ProcessingOptions.cs b/src/Nethermind/Nethermind.Consensus/Processing/ProcessingOptions.cs index 555e1c9e1dd..82799e5578d 100644 --- a/src/Nethermind/Nethermind.Consensus/Processing/ProcessingOptions.cs +++ b/src/Nethermind/Nethermind.Consensus/Processing/ProcessingOptions.cs @@ -51,7 +51,10 @@ public enum ProcessingOptions /// MarkAsProcessed = 128, - All = 255, + /// + /// Forces to run on top of the specified block state, instead of reverting to the previous one. + /// + ForceSameBlock = 1 << 8, /// /// Combination of switches for block producers when they preprocess block for state root calculation. @@ -63,6 +66,12 @@ public enum ProcessingOptions /// Trace = ForceProcessing | ReadOnlyChain | DoNotVerifyNonce | NoValidation, + /// + /// EVM tracing needs to process one or more transactions on top of the specified block (instead of the previous one) + /// without storing the data on chain. + /// + TraceTransactions = Trace | ForceSameBlock, + /// /// Processing options for engine_NewPayload /// diff --git a/src/Nethermind/Nethermind.Consensus/Tracing/GethStyleTracer.cs b/src/Nethermind/Nethermind.Consensus/Tracing/GethStyleTracer.cs index 7ff20c4d203..60392ad5c81 100644 --- a/src/Nethermind/Nethermind.Consensus/Tracing/GethStyleTracer.cs +++ b/src/Nethermind/Nethermind.Consensus/Tracing/GethStyleTracer.cs @@ -16,6 +16,7 @@ using Nethermind.Core.Extensions; using Nethermind.Core.Specs; using Nethermind.Crypto; +using Nethermind.Evm; using Nethermind.Evm.Tracing; using Nethermind.Evm.Tracing.GethStyle; using Nethermind.Evm.Tracing.GethStyle.Custom.JavaScript; @@ -36,6 +37,7 @@ public class GethStyleTracer : IGethStyleTracer private readonly IWorldState _worldState; private readonly IReceiptStorage _receiptStorage; private readonly IFileSystem _fileSystem; + private readonly IOverridableTxProcessorSource _env; public GethStyleTracer(IBlockchainProcessor processor, IWorldState worldState, @@ -44,7 +46,8 @@ public GethStyleTracer(IBlockchainProcessor processor, IBadBlockStore badBlockStore, ISpecProvider specProvider, ChangeableTransactionProcessorAdapter transactionProcessorAdapter, - IFileSystem fileSystem) + IFileSystem fileSystem, + IOverridableTxProcessorSource env) { _processor = processor ?? throw new ArgumentNullException(nameof(processor)); _worldState = worldState; @@ -54,6 +57,7 @@ public GethStyleTracer(IBlockchainProcessor processor, _specProvider = specProvider; _transactionProcessorAdapter = transactionProcessorAdapter; _fileSystem = fileSystem ?? throw new ArgumentNullException(nameof(fileSystem)); + _env = env ?? throw new ArgumentNullException(nameof(env)); } public GethLikeTxTrace Trace(Hash256 blockHash, int txIndex, GethTraceOptions options, CancellationToken cancellationToken) @@ -82,7 +86,10 @@ public GethLikeTxTrace Trace(Hash256 blockHash, int txIndex, GethTraceOptions op try { - return Trace(block, tx.Hash, cancellationToken, options); + Dictionary? stateOverride = options.StateOverrides; + using IOverridableTxProcessingScope? scope = stateOverride != null ? _env.BuildAndOverride(block.Header, stateOverride) : null; + + return Trace(block, tx.Hash, cancellationToken, options, ProcessingOptions.TraceTransactions); } finally { @@ -189,7 +196,8 @@ public IEnumerable TraceBadBlockToFile(Hash256 blockHash, GethTraceOptio return tracer.FileNames; } - private GethLikeTxTrace? Trace(Block block, Hash256? txHash, CancellationToken cancellationToken, GethTraceOptions options) + private GethLikeTxTrace? Trace(Block block, Hash256? txHash, CancellationToken cancellationToken, GethTraceOptions options, + ProcessingOptions processingOptions = ProcessingOptions.Trace) { ArgumentNullException.ThrowIfNull(txHash); @@ -197,7 +205,7 @@ public IEnumerable TraceBadBlockToFile(Hash256 blockHash, GethTraceOptio try { - _processor.Process(block, ProcessingOptions.Trace, tracer.WithCancellation(cancellationToken)); + _processor.Process(block, processingOptions, tracer.WithCancellation(cancellationToken)); return tracer.BuildResult().SingleOrDefault(); } catch diff --git a/src/Nethermind/Nethermind.Consensus/Tracing/Tracer.cs b/src/Nethermind/Nethermind.Consensus/Tracing/Tracer.cs index 00c2a85147d..df6c3c721bd 100644 --- a/src/Nethermind/Nethermind.Consensus/Tracing/Tracer.cs +++ b/src/Nethermind/Nethermind.Consensus/Tracing/Tracer.cs @@ -16,18 +16,20 @@ public class Tracer : ITracer private readonly IWorldState _stateProvider; private readonly IBlockchainProcessor _traceProcessor; private readonly IBlockchainProcessor _executeProcessor; - private readonly ProcessingOptions _processingOptions; + private readonly ProcessingOptions _executeOptions; + private readonly ProcessingOptions _traceOptions; public Tracer(IWorldState stateProvider, IBlockchainProcessor traceProcessor, IBlockchainProcessor executeProcessor, - ProcessingOptions processingOptions = ProcessingOptions.Trace) + ProcessingOptions executeOptions = ProcessingOptions.Trace, ProcessingOptions traceOptions = ProcessingOptions.Trace) { _traceProcessor = traceProcessor; _executeProcessor = executeProcessor; _stateProvider = stateProvider; - _processingOptions = processingOptions; + _executeOptions = executeOptions; + _traceOptions = traceOptions; } - private void Process(Block block, IBlockTracer blockTracer, IBlockchainProcessor processor) + private void Process(Block block, IBlockTracer blockTracer, IBlockchainProcessor processor, ProcessingOptions options) { /* We force process since we want to process a block that has already been processed in the past and normally it would be ignored. We also want to make it read only so the state is not modified persistently in any way. */ @@ -36,7 +38,7 @@ We also want to make it read only so the state is not modified persistently in a try { - processor.Process(block, _processingOptions, blockTracer); + processor.Process(block, options, blockTracer); } catch (Exception) { @@ -47,9 +49,9 @@ We also want to make it read only so the state is not modified persistently in a blockTracer.EndBlockTrace(); } - public void Trace(Block block, IBlockTracer tracer) => Process(block, tracer, _traceProcessor); + public void Trace(Block block, IBlockTracer tracer) => Process(block, tracer, _traceProcessor, _traceOptions); - public void Execute(Block block, IBlockTracer tracer) => Process(block, tracer, _executeProcessor); + public void Execute(Block block, IBlockTracer tracer) => Process(block, tracer, _executeProcessor, _executeOptions); public void Accept(ITreeVisitor visitor, Hash256 stateRoot) { diff --git a/src/Nethermind/Nethermind.Core.Test/Blockchain/TestBlockchain.cs b/src/Nethermind/Nethermind.Core.Test/Blockchain/TestBlockchain.cs index d9f198e6e5b..982c9190a8c 100644 --- a/src/Nethermind/Nethermind.Core.Test/Blockchain/TestBlockchain.cs +++ b/src/Nethermind/Nethermind.Core.Test/Blockchain/TestBlockchain.cs @@ -77,6 +77,7 @@ public IBlockFinder BlockFinder public IWorldState State { get; set; } = null!; public IReadOnlyStateProvider ReadOnlyState { get; private set; } = null!; public IDb StateDb => DbProvider.StateDb; + public IDb BlocksDb => DbProvider.BlocksDb; public TrieStore TrieStore { get; set; } = null!; public IBlockProducer BlockProducer { get; private set; } = null!; public IBlockProducerRunner BlockProducerRunner { get; protected set; } = null!; diff --git a/src/Nethermind/Nethermind.Facade/Proxy/Models/AccountOverride.cs b/src/Nethermind/Nethermind.Evm/AccountOverride.cs similarity index 94% rename from src/Nethermind/Nethermind.Facade/Proxy/Models/AccountOverride.cs rename to src/Nethermind/Nethermind.Evm/AccountOverride.cs index 5b8a26ab431..91fe22d9f40 100644 --- a/src/Nethermind/Nethermind.Facade/Proxy/Models/AccountOverride.cs +++ b/src/Nethermind/Nethermind.Evm/AccountOverride.cs @@ -6,7 +6,7 @@ using Nethermind.Core.Crypto; using Nethermind.Int256; -namespace Nethermind.Facade.Proxy.Models; +namespace Nethermind.Evm; public class AccountOverride { diff --git a/src/Nethermind/Nethermind.Evm/IOverridableCodeInfoRepository.cs b/src/Nethermind/Nethermind.Evm/IOverridableCodeInfoRepository.cs new file mode 100644 index 00000000000..19e14f63b35 --- /dev/null +++ b/src/Nethermind/Nethermind.Evm/IOverridableCodeInfoRepository.cs @@ -0,0 +1,15 @@ +// SPDX-FileCopyrightText: 2024 Demerzel Solutions Limited +// SPDX-License-Identifier: LGPL-3.0-only + +using Nethermind.Core; +using Nethermind.Core.Specs; +using Nethermind.Evm.CodeAnalysis; +using Nethermind.State; + +namespace Nethermind.Evm; + +public interface IOverridableCodeInfoRepository : ICodeInfoRepository +{ + void SetCodeOverwrite(IWorldState worldState, IReleaseSpec vmSpec, Address key, CodeInfo value, Address? redirectAddress = null); + public void ResetOverrides(); +} diff --git a/src/Nethermind/Nethermind.Facade/OverridableCodeInfoRepository.cs b/src/Nethermind/Nethermind.Evm/OverridableCodeInfoRepository.cs similarity index 93% rename from src/Nethermind/Nethermind.Facade/OverridableCodeInfoRepository.cs rename to src/Nethermind/Nethermind.Evm/OverridableCodeInfoRepository.cs index 082d5ad57fe..0342586f261 100644 --- a/src/Nethermind/Nethermind.Facade/OverridableCodeInfoRepository.cs +++ b/src/Nethermind/Nethermind.Evm/OverridableCodeInfoRepository.cs @@ -7,13 +7,12 @@ using Nethermind.Core; using Nethermind.Core.Crypto; using Nethermind.Core.Specs; -using Nethermind.Evm; using Nethermind.Evm.CodeAnalysis; using Nethermind.State; -namespace Nethermind.Facade; +namespace Nethermind.Evm; -public class OverridableCodeInfoRepository(ICodeInfoRepository codeInfoRepository) : ICodeInfoRepository +public class OverridableCodeInfoRepository(ICodeInfoRepository codeInfoRepository) : IOverridableCodeInfoRepository { private readonly Dictionary _codeOverwrites = new(); @@ -51,4 +50,6 @@ public bool TryGetDelegation(IReadOnlyStateProvider worldState, Address address, public ValueHash256 GetExecutableCodeHash(IWorldState worldState, Address address) => codeInfoRepository.GetExecutableCodeHash(worldState, address); + + public void ResetOverrides() => _codeOverwrites.Clear(); } diff --git a/src/Nethermind/Nethermind.Facade/StateOverridesExtensions.cs b/src/Nethermind/Nethermind.Evm/StateOverridesExtensions.cs similarity index 94% rename from src/Nethermind/Nethermind.Facade/StateOverridesExtensions.cs rename to src/Nethermind/Nethermind.Evm/StateOverridesExtensions.cs index 32d3d947b7c..d34ef57f238 100644 --- a/src/Nethermind/Nethermind.Facade/StateOverridesExtensions.cs +++ b/src/Nethermind/Nethermind.Evm/StateOverridesExtensions.cs @@ -7,17 +7,16 @@ using Nethermind.Core.Extensions; using Nethermind.Core.Specs; using Nethermind.Evm.CodeAnalysis; -using Nethermind.Facade.Proxy.Models; using Nethermind.Int256; using Nethermind.State; -namespace Nethermind.Facade; +namespace Nethermind.Evm; public static class StateOverridesExtensions { public static void ApplyStateOverrides( this IWorldState state, - OverridableCodeInfoRepository overridableCodeInfoRepository, + IOverridableCodeInfoRepository overridableCodeInfoRepository, Dictionary? overrides, IReleaseSpec spec, long blockNumber) @@ -69,13 +68,15 @@ void ApplyState(Dictionary diff) private static void UpdateCode( this IWorldState stateProvider, - OverridableCodeInfoRepository overridableCodeInfoRepository, + IOverridableCodeInfoRepository overridableCodeInfoRepository, IReleaseSpec currentSpec, AccountOverride accountOverride, Address address) { if (accountOverride.Code is not null) { + stateProvider.InsertCode(address, accountOverride.Code, currentSpec); + overridableCodeInfoRepository.SetCodeOverwrite( stateProvider, currentSpec, diff --git a/src/Nethermind/Nethermind.Evm/Tracing/GethStyle/GethTraceOptions.cs b/src/Nethermind/Nethermind.Evm/Tracing/GethStyle/GethTraceOptions.cs index 3a9504af097..950d084634f 100644 --- a/src/Nethermind/Nethermind.Evm/Tracing/GethStyle/GethTraceOptions.cs +++ b/src/Nethermind/Nethermind.Evm/Tracing/GethStyle/GethTraceOptions.cs @@ -2,9 +2,10 @@ // SPDX-License-Identifier: LGPL-3.0-only using System; +using System.Collections.Generic; using System.Text.Json; using System.Text.Json.Serialization; - +using Nethermind.Core; using Nethermind.Core.Crypto; namespace Nethermind.Evm.Tracing.GethStyle; @@ -36,5 +37,8 @@ public record GethTraceOptions [JsonPropertyName("tracerConfig")] public JsonElement? TracerConfig { get; init; } + [JsonPropertyName("stateOverrides")] + public Dictionary? StateOverrides { get; init; } + public static GethTraceOptions Default { get; } = new(); } diff --git a/src/Nethermind/Nethermind.Evm/TransactionProcessing/IOverridableTxProcessingScope.cs b/src/Nethermind/Nethermind.Evm/TransactionProcessing/IOverridableTxProcessingScope.cs new file mode 100644 index 00000000000..9f0ed17a089 --- /dev/null +++ b/src/Nethermind/Nethermind.Evm/TransactionProcessing/IOverridableTxProcessingScope.cs @@ -0,0 +1,9 @@ +// SPDX-FileCopyrightText: 2024 Demerzel Solutions Limited +// SPDX-License-Identifier: LGPL-3.0-only + +namespace Nethermind.Evm.TransactionProcessing; + +public interface IOverridableTxProcessingScope : IReadOnlyTxProcessingScope +{ + IOverridableCodeInfoRepository CodeInfoRepository { get; } +} diff --git a/src/Nethermind/Nethermind.Evm/TransactionProcessing/IOverridableTxProcessorSource.cs b/src/Nethermind/Nethermind.Evm/TransactionProcessing/IOverridableTxProcessorSource.cs new file mode 100644 index 00000000000..324fff8d409 --- /dev/null +++ b/src/Nethermind/Nethermind.Evm/TransactionProcessing/IOverridableTxProcessorSource.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; +using Nethermind.Core.Crypto; + +namespace Nethermind.Evm.TransactionProcessing; + +public interface IOverridableTxProcessorSource +{ + IOverridableTxProcessingScope Build(Hash256 stateRoot); + IOverridableTxProcessingScope BuildAndOverride(BlockHeader header, Dictionary? stateOverride); +} diff --git a/src/Nethermind/Nethermind.Facade.Test/BlockchainBridgeTests.cs b/src/Nethermind/Nethermind.Facade.Test/BlockchainBridgeTests.cs index c698909a89d..7232165c584 100644 --- a/src/Nethermind/Nethermind.Facade.Test/BlockchainBridgeTests.cs +++ b/src/Nethermind/Nethermind.Facade.Test/BlockchainBridgeTests.cs @@ -4,12 +4,10 @@ using System; using System.Collections.Generic; using System.Linq; -using System.Threading; using System.Threading.Tasks; using FluentAssertions; using Nethermind.Blockchain; using Nethermind.Blockchain.Filters; -using Nethermind.Blockchain.Find; using Nethermind.Blockchain.Receipts; using Nethermind.Consensus.Processing; using Nethermind.Core; @@ -37,7 +35,7 @@ namespace Nethermind.Facade.Test { public class BlockchainBridgeTests { - private BlockchainBridge _blockchainBridge; + private IBlockchainBridge _blockchainBridge; private IBlockTree _blockTree; private ITxPool _txPool; private IReceiptStorage _receiptStorage; @@ -49,18 +47,15 @@ public class BlockchainBridgeTests private ISpecProvider _specProvider; private IDbProvider _dbProvider; - private class TestReadOnlyTxProcessingEnv : ReadOnlyTxProcessingEnv + private class TestReadOnlyTxProcessingEnv( + OverridableWorldStateManager worldStateManager, + IReadOnlyBlockTree blockTree, + ISpecProvider specProvider, + ILogManager logManager, + ITransactionProcessor transactionProcessor) + : OverridableTxProcessingEnv(worldStateManager, blockTree, specProvider, logManager) { - public TestReadOnlyTxProcessingEnv( - IWorldStateManager worldStateManager, - IBlockTree blockTree, - ISpecProvider specProvider, - ILogManager logManager, - ITransactionProcessor transactionProcessor) : - base(worldStateManager, blockTree, specProvider, logManager) - { - _transactionProcessor = transactionProcessor; - } + protected override ITransactionProcessor CreateTransactionProcessor() => transactionProcessor; } [SetUp] @@ -77,22 +72,20 @@ public async Task SetUp() _ethereumEcdsa = Substitute.For(); _specProvider = MainnetSpecProvider.Instance; - ReadOnlyDbProvider dbProvider = new ReadOnlyDbProvider(_dbProvider, false); IReadOnlyTrieStore trieStore = new TrieStore(_dbProvider.StateDb, LimboLogs.Instance).AsReadOnly(); - IWorldStateManager readOnlyWorldStateManager = - new ReadOnlyWorldStateManager(dbProvider, trieStore, LimboLogs.Instance); + OverridableWorldStateManager worldStateManager = new(_dbProvider, trieStore, LimboLogs.Instance); IReadOnlyBlockTree readOnlyBlockTree = _blockTree.AsReadOnly(); - ReadOnlyTxProcessingEnv processingEnv = new TestReadOnlyTxProcessingEnv( - readOnlyWorldStateManager, + TestReadOnlyTxProcessingEnv processingEnv = new( + worldStateManager, readOnlyBlockTree, _specProvider, LimboLogs.Instance, _transactionProcessor); SimulateReadOnlyBlocksProcessingEnvFactory simulateProcessingEnvFactory = new SimulateReadOnlyBlocksProcessingEnvFactory( - readOnlyWorldStateManager, + worldStateManager, readOnlyBlockTree, new ReadOnlyDbProvider(_dbProvider, true), _specProvider, @@ -157,7 +150,7 @@ public void Call_uses_valid_post_merge_and_random_value() Transaction tx = Build.A.Transaction.TestObject; - _blockchainBridge.Call(header, tx, CancellationToken.None); + _blockchainBridge.Call(header, tx); _transactionProcessor.Received().CallAndRestore( tx, Arg.Is(blkCtx => @@ -173,7 +166,7 @@ public void Call_uses_valid_block_number() BlockHeader header = Build.A.BlockHeader.WithNumber(10).TestObject; Transaction tx = new() { GasLimit = Transaction.BaseTxGasCost }; - _blockchainBridge.Call(header, tx, CancellationToken.None); + _blockchainBridge.Call(header, tx); _transactionProcessor.Received().CallAndRestore( tx, Arg.Is(blkCtx => blkCtx.Header.Number == 10), @@ -188,7 +181,7 @@ public void Call_uses_valid_mix_hash() BlockHeader header = Build.A.BlockHeader.WithMixHash(TestItem.KeccakA).TestObject; Transaction tx = new() { GasLimit = Transaction.BaseTxGasCost }; - _blockchainBridge.Call(header, tx, CancellationToken.None); + _blockchainBridge.Call(header, tx); _transactionProcessor.Received().CallAndRestore( tx, Arg.Is(blkCtx => blkCtx.Header.MixHash == TestItem.KeccakA), @@ -203,7 +196,7 @@ public void Call_uses_valid_beneficiary() BlockHeader header = Build.A.BlockHeader.WithBeneficiary(TestItem.AddressB).TestObject; Transaction tx = new() { GasLimit = Transaction.BaseTxGasCost }; - _blockchainBridge.Call(header, tx, CancellationToken.None); + _blockchainBridge.Call(header, tx); _transactionProcessor.Received().CallAndRestore( tx, Arg.Is(blkCtx => blkCtx.Header.Beneficiary == TestItem.AddressB), @@ -214,20 +207,19 @@ public void Call_uses_valid_beneficiary() [TestCase(0)] public void Bridge_head_is_correct(long headNumber) { - ReadOnlyDbProvider dbProvider = new ReadOnlyDbProvider(_dbProvider, false); IReadOnlyTrieStore trieStore = new TrieStore(_dbProvider.StateDb, LimboLogs.Instance).AsReadOnly(); - IWorldStateManager readOnlyWorldStateManager = - new ReadOnlyWorldStateManager(dbProvider, trieStore, LimboLogs.Instance); + OverridableWorldStateManager worldStateManager = + new(_dbProvider, trieStore, LimboLogs.Instance); IReadOnlyBlockTree roBlockTree = _blockTree.AsReadOnly(); - ReadOnlyTxProcessingEnv processingEnv = new( - readOnlyWorldStateManager, + OverridableTxProcessingEnv processingEnv = new( + worldStateManager, roBlockTree, _specProvider, LimboLogs.Instance); SimulateReadOnlyBlocksProcessingEnvFactory simulateProcessingEnv = new SimulateReadOnlyBlocksProcessingEnvFactory( - readOnlyWorldStateManager, + worldStateManager, roBlockTree, new ReadOnlyDbProvider(_dbProvider, true), _specProvider, diff --git a/src/Nethermind/Nethermind.Facade/BlockchainBridge.cs b/src/Nethermind/Nethermind.Facade/BlockchainBridge.cs index 50621ebb666..42e3c884e2f 100644 --- a/src/Nethermind/Nethermind.Facade/BlockchainBridge.cs +++ b/src/Nethermind/Nethermind.Facade/BlockchainBridge.cs @@ -41,7 +41,7 @@ public interface IBlockchainBridgeFactory [Todo(Improve.Refactor, "I want to remove BlockchainBridge, split it into something with logging, state and tx processing. Then we can start using independent modules.")] public class BlockchainBridge : IBlockchainBridge { - private readonly IReadOnlyTxProcessorSource _processingEnv; + private readonly IOverridableTxProcessorSource _processingEnv; private readonly IBlockTree _blockTree; private readonly IStateReader _stateReader; private readonly ITxPool _txPool; @@ -55,7 +55,7 @@ public class BlockchainBridge : IBlockchainBridge private readonly IBlocksConfig _blocksConfig; private readonly SimulateBridgeHelper _simulateBridgeHelper; - public BlockchainBridge(ReadOnlyTxProcessingEnv processingEnv, + public BlockchainBridge(OverridableTxProcessingEnv processingEnv, SimulateReadOnlyBlocksProcessingEnvFactory simulateProcessingEnvFactory, ITxPool? txPool, IReceiptFinder? receiptStorage, @@ -148,11 +148,14 @@ private bool TryGetCanonicalTransaction( return blockHash is not null ? _receiptFinder.Get(blockHash).ForTransaction(txHash) : null; } - public CallOutput Call(BlockHeader header, Transaction tx, CancellationToken cancellationToken) + public CallOutput Call(BlockHeader header, Transaction tx, Dictionary? stateOverride, CancellationToken cancellationToken) { + using IOverridableTxProcessingScope scope = _processingEnv.BuildAndOverride(header, stateOverride); + CallOutputTracer callOutputTracer = new(); - TransactionResult tryCallResult = TryCallAndRestore(header, tx, false, + TransactionResult tryCallResult = TryCallAndRestore(scope, header, tx, false, callOutputTracer.WithCancellation(cancellationToken)); + return new CallOutput { Error = tryCallResult.Success ? callOutputTracer.Error : tryCallResult.Error, @@ -175,6 +178,10 @@ public SimulateOutput Simulate(BlockHeader header, SimulatePayload? stateOverride, CancellationToken cancellationToken) { - using IReadOnlyTxProcessingScope scope = _processingEnv.Build(header.StateRoot!); + using IOverridableTxProcessingScope scope = _processingEnv.BuildAndOverride(header, stateOverride); EstimateGasTracer estimateGasTracer = new(); - TransactionResult tryCallResult = TryCallAndRestore( - header, - tx, - true, + TransactionResult tryCallResult = TryCallAndRestore(scope, header, tx, true, estimateGasTracer.WithCancellation(cancellationToken)); - GasEstimator gasEstimator = new(scope.TransactionProcessor, scope.WorldState, - _specProvider, _blocksConfig); + GasEstimator gasEstimator = new(scope.TransactionProcessor, scope.WorldState, _specProvider, _blocksConfig); long estimate = gasEstimator.Estimate(tx, header, estimateGasTracer, errorMargin, cancellationToken); return new CallOutput @@ -209,13 +212,13 @@ public CallOutput EstimateGas(BlockHeader header, Transaction tx, int errorMargi public CallOutput CreateAccessList(BlockHeader header, Transaction tx, CancellationToken cancellationToken, bool optimize) { - CallOutputTracer callOutputTracer = new(); AccessTxTracer accessTxTracer = optimize ? new(tx.SenderAddress, tx.GetRecipient(tx.IsContractCreation ? _stateReader.GetNonce(header.StateRoot, tx.SenderAddress) : 0), header.GasBeneficiary) : new(header.GasBeneficiary); - TransactionResult tryCallResult = TryCallAndRestore(header, tx, false, + CallOutputTracer callOutputTracer = new(); + TransactionResult tryCallResult = TryCallAndRestore(_processingEnv.Build(header.StateRoot!), header, tx, false, new CompositeTxTracer(callOutputTracer, accessTxTracer).WithCancellation(cancellationToken)); return new CallOutput @@ -229,6 +232,7 @@ public CallOutput CreateAccessList(BlockHeader header, Transaction tx, Cancellat } private TransactionResult TryCallAndRestore( + IOverridableTxProcessingScope scope, BlockHeader blockHeader, Transaction transaction, bool treatBlockHeaderAsParentBlock, @@ -236,7 +240,7 @@ private TransactionResult TryCallAndRestore( { try { - return CallAndRestore(blockHeader, transaction, treatBlockHeaderAsParentBlock, tracer); + return CallAndRestore(blockHeader, transaction, treatBlockHeaderAsParentBlock, tracer, scope); } catch (InsufficientBalanceException ex) { @@ -248,12 +252,11 @@ private TransactionResult CallAndRestore( BlockHeader blockHeader, Transaction transaction, bool treatBlockHeaderAsParentBlock, - ITxTracer tracer) + ITxTracer tracer, + IOverridableTxProcessingScope scope) { transaction.SenderAddress ??= Address.SystemUser; - Hash256 stateRoot = blockHeader.StateRoot!; - using IReadOnlyTxProcessingScope scope = _processingEnv.Build(stateRoot); if (transaction.Nonce == 0) { diff --git a/src/Nethermind/Nethermind.Facade/BlockchainBridgeContract.cs b/src/Nethermind/Nethermind.Facade/BlockchainBridgeContract.cs index 438bb096bd0..abe697c2540 100644 --- a/src/Nethermind/Nethermind.Facade/BlockchainBridgeContract.cs +++ b/src/Nethermind/Nethermind.Facade/BlockchainBridgeContract.cs @@ -36,7 +36,7 @@ public ConstantBridgeContract(Contract contract, IBlockchainBridge blockchainBri public override object[] Call(CallInfo callInfo) { var transaction = GenerateTransaction(callInfo); - var result = _blockchainBridge.Call(callInfo.ParentHeader, transaction, CancellationToken.None); + var result = _blockchainBridge.Call(callInfo.ParentHeader, transaction); if (!string.IsNullOrEmpty(result.Error)) { throw new AbiException(result.Error); diff --git a/src/Nethermind/Nethermind.Facade/IBlockchainBridge.cs b/src/Nethermind/Nethermind.Facade/IBlockchainBridge.cs index b3bce3accf9..459dd3f3de1 100644 --- a/src/Nethermind/Nethermind.Facade/IBlockchainBridge.cs +++ b/src/Nethermind/Nethermind.Facade/IBlockchainBridge.cs @@ -27,9 +27,9 @@ 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); - CallOutput Call(BlockHeader header, Transaction tx, CancellationToken cancellationToken); + CallOutput Call(BlockHeader header, Transaction tx, Dictionary? stateOverride = null, CancellationToken cancellationToken = default); SimulateOutput Simulate(BlockHeader header, SimulatePayload payload, CancellationToken cancellationToken); - CallOutput EstimateGas(BlockHeader header, Transaction tx, int errorMarginBasisPoints, CancellationToken cancellationToken); + CallOutput EstimateGas(BlockHeader header, Transaction tx, int errorMarginBasisPoints, Dictionary? stateOverride = null, CancellationToken cancellationToken = default); CallOutput CreateAccessList(BlockHeader header, Transaction tx, CancellationToken cancellationToken, bool optimize); ulong GetChainId(); diff --git a/src/Nethermind/Nethermind.Facade/Proxy/Models/Simulate/BlockStateCall.cs b/src/Nethermind/Nethermind.Facade/Proxy/Models/Simulate/BlockStateCall.cs index db4efb549ba..3900900bd7c 100644 --- a/src/Nethermind/Nethermind.Facade/Proxy/Models/Simulate/BlockStateCall.cs +++ b/src/Nethermind/Nethermind.Facade/Proxy/Models/Simulate/BlockStateCall.cs @@ -4,6 +4,7 @@ using System; using System.Collections.Generic; using Nethermind.Core; +using Nethermind.Evm; namespace Nethermind.Facade.Proxy.Models.Simulate; diff --git a/src/Nethermind/Nethermind.Facade/Proxy/Models/Simulate/Error.cs b/src/Nethermind/Nethermind.Facade/Proxy/Models/Simulate/Error.cs index a53bdd9a9e5..6819d2772e4 100644 --- a/src/Nethermind/Nethermind.Facade/Proxy/Models/Simulate/Error.cs +++ b/src/Nethermind/Nethermind.Facade/Proxy/Models/Simulate/Error.cs @@ -7,5 +7,5 @@ public class Error { public int Code { get; set; } public string Message { get; set; } - public string Data { get; set; } + public string? Data { get; set; } } diff --git a/src/Nethermind/Nethermind.Init/Steps/RegisterRpcModules.cs b/src/Nethermind/Nethermind.Init/Steps/RegisterRpcModules.cs index bc77defb9d5..5bc4f2ffc9a 100644 --- a/src/Nethermind/Nethermind.Init/Steps/RegisterRpcModules.cs +++ b/src/Nethermind/Nethermind.Init/Steps/RegisterRpcModules.cs @@ -99,7 +99,7 @@ public virtual async Task Execute(CancellationToken cancellationToken) rpcModuleProvider.RegisterBounded(proofModuleFactory, 2, _jsonRpcConfig.Timeout); DebugModuleFactory debugModuleFactory = new( - _api.WorldStateManager, + _api.WorldStateManager.TrieStore, _api.DbProvider, _api.BlockTree, _jsonRpcConfig, @@ -250,13 +250,15 @@ protected virtual void RegisterEthRpcModule(IRpcModuleProvider rpcModuleProvider protected ModuleFactoryBase CreateTraceModuleFactory() { StepDependencyException.ThrowIfNull(_api.WorldStateManager); + StepDependencyException.ThrowIfNull(_api.DbProvider); StepDependencyException.ThrowIfNull(_api.BlockTree); StepDependencyException.ThrowIfNull(_api.RewardCalculatorSource); StepDependencyException.ThrowIfNull(_api.ReceiptStorage); StepDependencyException.ThrowIfNull(_api.SpecProvider); return new TraceModuleFactory( - _api.WorldStateManager, + _api.WorldStateManager.TrieStore, + _api.DbProvider, _api.BlockTree, _jsonRpcConfig, _api.BlockPreprocessor, diff --git a/src/Nethermind/Nethermind.JsonRpc.Benchmark/EthModuleBenchmarks.cs b/src/Nethermind/Nethermind.JsonRpc.Benchmark/EthModuleBenchmarks.cs index 875acb33763..817b5d0be02 100644 --- a/src/Nethermind/Nethermind.JsonRpc.Benchmark/EthModuleBenchmarks.cs +++ b/src/Nethermind/Nethermind.JsonRpc.Benchmark/EthModuleBenchmarks.cs @@ -66,7 +66,7 @@ public void GlobalSetup() stateProvider.Commit(spec); stateProvider.CommitTree(0); - WorldStateManager stateManager = new WorldStateManager(stateProvider, trieStore, dbProvider, LimboLogs.Instance); + OverridableWorldStateManager stateManager = new(dbProvider, trieStore.AsReadOnly(), LimboLogs.Instance); StateReader stateReader = new(trieStore, codeDb, LimboLogs.Instance); @@ -135,7 +135,7 @@ TransactionProcessor transactionProcessor new ReceiptsRecovery(ecdsa, specProvider)); BlockchainBridge bridge = new( - new ReadOnlyTxProcessingEnv( + new OverridableTxProcessingEnv( stateManager, new ReadOnlyBlockTree(blockTree), specProvider, diff --git a/src/Nethermind/Nethermind.JsonRpc.Test/Modules/DebugModuleTests.cs b/src/Nethermind/Nethermind.JsonRpc.Test/Modules/DebugModuleTests.cs index 15038cf51b9..ef4111628be 100644 --- a/src/Nethermind/Nethermind.JsonRpc.Test/Modules/DebugModuleTests.cs +++ b/src/Nethermind/Nethermind.JsonRpc.Test/Modules/DebugModuleTests.cs @@ -31,6 +31,7 @@ namespace Nethermind.JsonRpc.Test.Modules; +// Tests with mocked IDebugBridge [Parallelizable(ParallelScope.Self)] public class DebugModuleTests { diff --git a/src/Nethermind/Nethermind.JsonRpc.Test/Modules/DebugRpcModuleTests.cs b/src/Nethermind/Nethermind.JsonRpc.Test/Modules/DebugRpcModuleTests.cs new file mode 100644 index 00000000000..2c78136b61b --- /dev/null +++ b/src/Nethermind/Nethermind.JsonRpc.Test/Modules/DebugRpcModuleTests.cs @@ -0,0 +1,213 @@ +// SPDX-FileCopyrightText: 2022 Demerzel Solutions Limited +// SPDX-License-Identifier: LGPL-3.0-only + +using System; +using System.IO.Abstractions; +using System.Text.Json; +using System.Threading.Tasks; +using FluentAssertions; +using FluentAssertions.Execution; +using FluentAssertions.Json; +using Nethermind.Blockchain.Blocks; +using Nethermind.Blockchain.Receipts; +using Nethermind.Config; +using Nethermind.Consensus.Rewards; +using Nethermind.Core; +using Nethermind.Core.Crypto; +using Nethermind.Core.Extensions; +using Nethermind.Core.Specs; +using Nethermind.Core.Test.Builders; +using Nethermind.Evm.Tracing.GethStyle; +using Nethermind.Int256; +using Nethermind.JsonRpc.Modules.DebugModule; +using Nethermind.Synchronization.ParallelSync; +using Newtonsoft.Json.Linq; +using NSubstitute; +using NUnit.Framework; + +namespace Nethermind.JsonRpc.Test.Modules; + +[Parallelizable(ParallelScope.Self)] +public class DebugRpcModuleTests +{ + private class Context : IDisposable + { + public IDebugRpcModule DebugRpcModule { get; } + public TestRpcBlockchain Blockchain { get; } + + private Context(TestRpcBlockchain blockchain, IDebugRpcModule debugRpcModule) + { + DebugRpcModule = debugRpcModule; + Blockchain = blockchain; + } + + public static async Task Create(ISpecProvider? specProvider = null, bool isAura = false) + { + TestRpcBlockchain blockchain = await TestRpcBlockchain.ForTest(isAura ? SealEngineType.AuRa : SealEngineType.NethDev).Build(specProvider); + + IConfigProvider configProvider = Substitute.For(); + IReceiptsMigration receiptsMigration = Substitute.For(); + ISyncModeSelector syncModeSelector = Substitute.For(); + var factory = new DebugModuleFactory( + blockchain.WorldStateManager.TrieStore, + blockchain.DbProvider, + blockchain.BlockTree, + blockchain.RpcConfig, + blockchain.BlockValidator, + blockchain.BlockPreprocessorStep, + NoBlockRewards.Instance, + blockchain.ReceiptStorage, + receiptsMigration, + configProvider, + blockchain.SpecProvider, + syncModeSelector, + new BadBlockStore(blockchain.BlocksDb, 100), + new FileSystem(), + blockchain.LogManager + ); + + IDebugRpcModule debugRpcModule = factory.Create(); + + return new(blockchain, debugRpcModule); + } + + public void Dispose() => Blockchain.Dispose(); + } + + [Test] + public async Task Debug_traceCall_fails_when_not_enough_balance() + { + using Context ctx = await Context.Create(); + + Address address = Build.An.Address.TestObject; + UInt256 balance = 100.Ether(), send = balance / 2; + + JsonRpcResponse response = await RpcTest.TestRequest(ctx.DebugRpcModule, "debug_traceCall", + new { from = $"{address}", to = $"{TestItem.AddressC}", value = send.ToString("X") } + ); + + response.Should().BeOfType() + .Which.Error?.Message?.Should().Contain("insufficient funds"); + } + + [Test] + public async Task Debug_traceCall_runs_on_top_of_specified_block() + { + using Context ctx = await Context.Create(); + TestRpcBlockchain blockchain = ctx.Blockchain; + + Address address = Build.An.Address.TestObject; + UInt256 balance = 100.Ether(); + + await blockchain.AddFunds(address, balance / 2); + await blockchain.AddFunds(address, balance / 2); + Hash256 lastBlockHash = blockchain.BlockTree.Head!.Hash!; + + JsonRpcResponse response = await RpcTest.TestRequest(ctx.DebugRpcModule, "debug_traceCall", + new { from = $"{address}", to = $"{TestItem.AddressC}", value = balance.ToString("X") }, + $"{lastBlockHash}" + ); + + response.Should().BeOfType(); + } + + [TestCase( + "Nonce override doesn't cause failure", + """{"from":"0x7f554713be84160fdf0178cc8df86f5aabd33397","to":"0xc200000000000000000000000000000000000000"}""", + """{"0x7f554713be84160fdf0178cc8df86f5aabd33397":{"nonce":"0x123"}}""" + )] + [TestCase( + "Uses account balance from state override", + """{"from":"0x7f554713be84160fdf0178cc8df86f5aabd33397","to":"0xc200000000000000000000000000000000000000","value":"0x100"}""", + """{"0x7f554713be84160fdf0178cc8df86f5aabd33397":{"balance":"0x100"}}""" + )] + [TestCase( + "Executes code from state override", + """{"from":"0xb7705ae4c6f81b66cdb323c65f4e8133690fc099","to":"0xc200000000000000000000000000000000000000","input":"0xf8b2cb4f000000000000000000000000b7705ae4c6f81b66cdb323c65f4e8133690fc099"}""", + """{"0xc200000000000000000000000000000000000000":{"code":"0x608060405234801561001057600080fd5b506004361061002b5760003560e01c8063f8b2cb4f14610030575b600080fd5b61004a600480360381019061004591906100e4565b610060565b604051610057919061012a565b60405180910390f35b60008173ffffffffffffffffffffffffffffffffffffffff16319050919050565b600080fd5b600073ffffffffffffffffffffffffffffffffffffffff82169050919050565b60006100b182610086565b9050919050565b6100c1816100a6565b81146100cc57600080fd5b50565b6000813590506100de816100b8565b92915050565b6000602082840312156100fa576100f9610081565b5b6000610108848285016100cf565b91505092915050565b6000819050919050565b61012481610111565b82525050565b600060208201905061013f600083018461011b565b9291505056fea2646970667358221220172c443a163d8a43e018c339d1b749c312c94b6de22835953d960985daf228c764736f6c63430008120033"}}""", + "00000000000000000000000000000000000000000000003635c9adc5de9f09e5" + )] + [TestCase( + "Executes precompile using overriden address", + """{"from":"0x7f554713be84160fdf0178cc8df86f5aabd33397","to":"0xc200000000000000000000000000000000000000","input":"0xB6E16D27AC5AB427A7F68900AC5559CE272DC6C37C82B3E052246C82244C50E4000000000000000000000000000000000000000000000000000000000000001C7B8B1991EB44757BC688016D27940DF8FB971D7C87F77A6BC4E938E3202C44037E9267B0AEAA82FA765361918F2D8ABD9CDD86E64AA6F2B81D3C4E0B69A7B055"}""", + """{"0x0000000000000000000000000000000000000001":{"movePrecompileToAddress":"0xc200000000000000000000000000000000000000", "code": "0x"}}""", + "000000000000000000000000b7705ae4c6f81b66cdb323c65f4e8133690fc099" + )] + public async Task Debug_traceCall_with_state_override(string name, string transactionJson, string stateOverrideJson, string? expectedValue = null) + { + var transaction = JsonSerializer.Deserialize(transactionJson); + var stateOverride = JsonSerializer.Deserialize(stateOverrideJson); + + using Context ctx = await Context.Create(); + + JsonRpcResponse response = await RpcTest.TestRequest(ctx.DebugRpcModule, "debug_traceCall", + transaction, null, new { stateOverrides = stateOverride } + ); + + GethLikeTxTrace trace = response.Should().BeOfType() + .Which.Result.Should().BeOfType() + .Subject; + + if (expectedValue != null) + Convert.ToHexString(trace.ReturnValue).Should().BeEquivalentTo(expectedValue); + } + + [TestCase( + "When balance is overriden", + """{"from":"0x7f554713be84160fdf0178cc8df86f5aabd33397","to":"0xbe5c953dd0ddb0ce033a98f36c981f1b74d3b33f","value":"0x100"}""", + """{"0x7f554713be84160fdf0178cc8df86f5aabd33397":{"balance":"0x100"}}""" + )] + [TestCase( + "When address code is overriden", + """{"from":"0x7f554713be84160fdf0178cc8df86f5aabd33397","to":"0xc200000000000000000000000000000000000000","input":"0xf8b2cb4f000000000000000000000000b7705ae4c6f81b66cdb323c65f4e8133690fc099"}""", + """{"0xc200000000000000000000000000000000000000":{"code":"0x608060405234801561001057600080fd5b506004361061002b5760003560e01c8063f8b2cb4f14610030575b600080fd5b61004a600480360381019061004591906100e4565b610060565b604051610057919061012a565b60405180910390f35b60008173ffffffffffffffffffffffffffffffffffffffff16319050919050565b600080fd5b600073ffffffffffffffffffffffffffffffffffffffff82169050919050565b60006100b182610086565b9050919050565b6100c1816100a6565b81146100cc57600080fd5b50565b6000813590506100de816100b8565b92915050565b6000602082840312156100fa576100f9610081565b5b6000610108848285016100cf565b91505092915050565b6000819050919050565b61012481610111565b82525050565b600060208201905061013f600083018461011b565b9291505056fea2646970667358221220172c443a163d8a43e018c339d1b749c312c94b6de22835953d960985daf228c764736f6c63430008120033"}}""" + )] + [TestCase( + "When precompile address is changed", + """{"from":"0x7f554713be84160fdf0178cc8df86f5aabd33397","to":"0xc200000000000000000000000000000000000000","input":"0xB6E16D27AC5AB427A7F68900AC5559CE272DC6C37C82B3E052246C82244C50E4000000000000000000000000000000000000000000000000000000000000001C7B8B1991EB44757BC688016D27940DF8FB971D7C87F77A6BC4E938E3202C44037E9267B0AEAA82FA765361918F2D8ABD9CDD86E64AA6F2B81D3C4E0B69A7B055"}""", + """{"0x0000000000000000000000000000000000000001":{"movePrecompileToAddress":"0xc200000000000000000000000000000000000000", "code": "0x"}}""" + )] + public async Task Debug_traceCall_with_state_override_does_not_affect_other_calls(string name, string transactionJson, string stateOverrideJson) + { + var transaction = JsonSerializer.Deserialize(transactionJson); + var stateOverride = JsonSerializer.Deserialize(stateOverrideJson); + + using Context ctx = await Context.Create(); + + var resultOverrideBefore = await RpcTest.TestSerializedRequest(ctx.DebugRpcModule, "debug_traceCall", transaction, null, new + { + stateOverrides = stateOverride, + enableMemory = false, + disableStorage = true, + disableStack = true, + tracer = "callTracer", + tracerConfig = new { withLog = false } + }); + + var resultNoOverride = await RpcTest.TestSerializedRequest(ctx.DebugRpcModule, "debug_traceCall", transaction, null, new + { + // configuration to minimize number of fields being compared + enableMemory = false, + disableStorage = true, + disableStack = true, + tracer = "callTracer", + tracerConfig = new { withLog = false } + }); + + var resultOverrideAfter = await RpcTest.TestSerializedRequest(ctx.DebugRpcModule, "debug_traceCall", transaction, null, new + { + stateOverrides = stateOverride, + enableMemory = false, + disableStorage = true, + disableStack = true, + tracer = "callTracer", + tracerConfig = new { withLog = false } + }); + + using (new AssertionScope()) + { + JToken.Parse(resultOverrideBefore).Should().BeEquivalentTo(resultOverrideAfter); + JToken.Parse(resultNoOverride).Should().NotBeEquivalentTo(resultOverrideAfter); + } + } +} 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 1087a3534c9..26077b2de8b 100644 --- a/src/Nethermind/Nethermind.JsonRpc.Test/Modules/Eth/EthRpcModuleTests.EstimateGas.cs +++ b/src/Nethermind/Nethermind.JsonRpc.Test/Modules/Eth/EthRpcModuleTests.EstimateGas.cs @@ -2,8 +2,11 @@ // SPDX-License-Identifier: LGPL-3.0-only using System; +using System.Text.Json; using System.Threading.Tasks; using FluentAssertions; +using FluentAssertions.Execution; +using FluentAssertions.Json; using Nethermind.Core; using Nethermind.Core.Crypto; using Nethermind.Core.Extensions; @@ -257,4 +260,76 @@ public async Task should_estimate_transaction_with_deployed_code_when_eip3607_en await ctx.Test.TestEthRpc("eth_estimateGas", transaction, "latest"); Assert.That(serialized, Is.EqualTo("{\"jsonrpc\":\"2.0\",\"result\":\"0x5208\",\"id\":67}")); } + + [TestCase( + "Nonce override doesn't cause failure", + """{"from":"0x7f554713be84160fdf0178cc8df86f5aabd33397","to":"0xc200000000000000000000000000000000000000"}""", + """{"0x7f554713be84160fdf0178cc8df86f5aabd33397":{"nonce":"0x123"}}""", + """{"jsonrpc":"2.0","result":"0x5208","id":67}""" // ETH transfer (intrinsic transaction cost) + )] + [TestCase( + "Uses account balance from state override", + """{"from":"0x7f554713be84160fdf0178cc8df86f5aabd33397","to":"0xc200000000000000000000000000000000000000","value":"0x100"}""", + """{"0x7f554713be84160fdf0178cc8df86f5aabd33397":{"balance":"0x100"}}""", + """{"jsonrpc":"2.0","result":"0x5208","id":67}""" // ETH transfer (intrinsic transaction cost) + )] + [TestCase( + "Executes code from state override", + """{"from":"0x7f554713be84160fdf0178cc8df86f5aabd33397","to":"0xc200000000000000000000000000000000000000","input":"0x60fe47b1112233445566778899001122334455667788990011223344556677889900112233445566778899001122"}""", + """{"0xc200000000000000000000000000000000000000":{"code":"0x6080604052348015600e575f80fd5b50600436106030575f3560e01c80632a1afcd914603457806360fe47b114604d575b5f80fd5b603b5f5481565b60405190815260200160405180910390f35b605c6058366004605e565b5f55565b005b5f60208284031215606d575f80fd5b503591905056fea2646970667358221220fd4e5f3894be8e57fc7460afebb5c90d96c3486d79bf47b00c2ed666ab2f82b364736f6c634300081a0033"}}""", + """{"jsonrpc":"2.0","result":"0xabdd","id":67}""" // Store uint256 (cold access) + few other light instructions + intrinsic transaction cost + )] + [TestCase( + "Executes precompile using overriden address", + """{"from":"0x7f554713be84160fdf0178cc8df86f5aabd33397","to":"0xc200000000000000000000000000000000000000","input":"0xB6E16D27AC5AB427A7F68900AC5559CE272DC6C37C82B3E052246C82244C50E4000000000000000000000000000000000000000000000000000000000000001C7B8B1991EB44757BC688016D27940DF8FB971D7C87F77A6BC4E938E3202C44037E9267B0AEAA82FA765361918F2D8ABD9CDD86E64AA6F2B81D3C4E0B69A7B055"}""", + """{"0x0000000000000000000000000000000000000001":{"movePrecompileToAddress":"0xc200000000000000000000000000000000000000", "code": "0x"}}""", + """{"jsonrpc":"2.0","result":"0x6440","id":67}""" // EcRecover call + intrinsic transaction cost + )] + public async Task Estimate_gas_with_state_override(string name, string transactionJson, string stateOverrideJson, string expectedResult) + { + var transaction = JsonSerializer.Deserialize(transactionJson); + var stateOverride = JsonSerializer.Deserialize(stateOverrideJson); + + TestSpecProvider specProvider = new(Prague.Instance); + using Context ctx = await Context.Create(specProvider); + + string serialized = await ctx.Test.TestEthRpc("eth_estimateGas", transaction, "latest", stateOverride); + + JToken.Parse(serialized).Should().BeEquivalentTo(expectedResult); + } + + [TestCase( + "When balance and nonce is overriden", + """{"from":"0x7f554713be84160fdf0178cc8df86f5aabd33397","to":"0xc200000000000000000000000000000000000000","value":"0x123"}""", + """{"0x7f554713be84160fdf0178cc8df86f5aabd33397":{"balance":"0x123", "nonce": "0x123"}}""" + )] + [TestCase( + "When address code is overriden", + """{"from":"0x7f554713be84160fdf0178cc8df86f5aabd33397","to":"0xc200000000000000000000000000000000000000","input":"0x60fe47b1112233445566778899001122334455667788990011223344556677889900112233445566778899001122"}""", + """{"0xc200000000000000000000000000000000000000":{"code":"0x6080604052348015600e575f80fd5b50600436106030575f3560e01c80632a1afcd914603457806360fe47b114604d575b5f80fd5b603b5f5481565b60405190815260200160405180910390f35b605c6058366004605e565b5f55565b005b5f60208284031215606d575f80fd5b503591905056fea2646970667358221220fd4e5f3894be8e57fc7460afebb5c90d96c3486d79bf47b00c2ed666ab2f82b364736f6c634300081a0033"}}""" + )] + [TestCase( + "When precompile address is changed", + """{"from":"0x7f554713be84160fdf0178cc8df86f5aabd33397","to":"0xc200000000000000000000000000000000000000","input":"0xB6E16D27AC5AB427A7F68900AC5559CE272DC6C37C82B3E052246C82244C50E4000000000000000000000000000000000000000000000000000000000000001C7B8B1991EB44757BC688016D27940DF8FB971D7C87F77A6BC4E938E3202C44037E9267B0AEAA82FA765361918F2D8ABD9CDD86E64AA6F2B81D3C4E0B69A7B055"}""", + """{"0x0000000000000000000000000000000000000001":{"movePrecompileToAddress":"0xc200000000000000000000000000000000000000", "code": "0x"}}""" + )] + public async Task Estimate_gas_with_state_override_does_not_affect_other_calls(string name, string transactionJson, string stateOverrideJson) + { + var transaction = JsonSerializer.Deserialize(transactionJson); + var stateOverride = JsonSerializer.Deserialize(stateOverrideJson); + + using Context ctx = await Context.Create(); + + var resultOverrideBefore = await ctx.Test.TestEthRpc("eth_estimateGas", transaction, "latest", stateOverride); + + var resultNoOverride = await ctx.Test.TestEthRpc("eth_estimateGas", transaction, "latest"); + + var resultOverrideAfter = await ctx.Test.TestEthRpc("eth_estimateGas", transaction, "latest", stateOverride); + + using (new AssertionScope()) + { + JToken.Parse(resultOverrideBefore).Should().BeEquivalentTo(resultOverrideAfter); + JToken.Parse(resultNoOverride).Should().NotBeEquivalentTo(resultOverrideAfter); + } + } } 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 adbf92f2ca3..caeb411c8d5 100644 --- a/src/Nethermind/Nethermind.JsonRpc.Test/Modules/Eth/EthRpcModuleTests.EthCall.cs +++ b/src/Nethermind/Nethermind.JsonRpc.Test/Modules/Eth/EthRpcModuleTests.EthCall.cs @@ -1,8 +1,11 @@ // SPDX-FileCopyrightText: 2022 Demerzel Solutions Limited // SPDX-License-Identifier: LGPL-3.0-only +using System.Text.Json; using System.Threading.Tasks; using FluentAssertions; +using FluentAssertions.Execution; +using FluentAssertions.Json; using Nethermind.Core; using Nethermind.Core.Crypto; using Nethermind.Core.Extensions; @@ -13,6 +16,7 @@ using Nethermind.Specs.Forks; using Nethermind.Specs.Test; using Nethermind.State; +using Newtonsoft.Json.Linq; using NUnit.Framework; namespace Nethermind.JsonRpc.Test.Modules.Eth; @@ -294,4 +298,75 @@ public async Task Eth_call_with_revert() Assert.That( serialized, Is.EqualTo("{\"jsonrpc\":\"2.0\",\"error\":{\"code\":-32015,\"message\":\"VM execution error.\",\"data\":\"revert\"},\"id\":67}")); } + + [TestCase( + "Nonce override doesn't cause failure", + """{"from":"0x7f554713be84160fdf0178cc8df86f5aabd33397","to":"0xc200000000000000000000000000000000000000"}""", + """{"0x7f554713be84160fdf0178cc8df86f5aabd33397":{"nonce":"0x123"}}""", + """{"jsonrpc":"2.0","result":"0x","id":67}""" + )] + [TestCase( + "Uses account balance from state override", + """{"from":"0x7f554713be84160fdf0178cc8df86f5aabd33397","to":"0xc200000000000000000000000000000000000000","value":"0x100"}""", + """{"0x7f554713be84160fdf0178cc8df86f5aabd33397":{"balance":"0x100"}}""", + """{"jsonrpc":"2.0","result":"0x","id":67}""" + )] + [TestCase( + "Executes code from state override", + """{"from":"0xb7705ae4c6f81b66cdb323c65f4e8133690fc099","to":"0xc200000000000000000000000000000000000000","input":"0xf8b2cb4f000000000000000000000000b7705ae4c6f81b66cdb323c65f4e8133690fc099"}""", + """{"0xc200000000000000000000000000000000000000":{"code":"0x608060405234801561001057600080fd5b506004361061002b5760003560e01c8063f8b2cb4f14610030575b600080fd5b61004a600480360381019061004591906100e4565b610060565b604051610057919061012a565b60405180910390f35b60008173ffffffffffffffffffffffffffffffffffffffff16319050919050565b600080fd5b600073ffffffffffffffffffffffffffffffffffffffff82169050919050565b60006100b182610086565b9050919050565b6100c1816100a6565b81146100cc57600080fd5b50565b6000813590506100de816100b8565b92915050565b6000602082840312156100fa576100f9610081565b5b6000610108848285016100cf565b91505092915050565b6000819050919050565b61012481610111565b82525050565b600060208201905061013f600083018461011b565b9291505056fea2646970667358221220172c443a163d8a43e018c339d1b749c312c94b6de22835953d960985daf228c764736f6c63430008120033"}}""", + """{"jsonrpc":"2.0","result":"0x00000000000000000000000000000000000000000000003635c9adc5de9f09e5","id":67}""" + )] + [TestCase( + "Executes precompile using overriden address", + """{"from":"0x7f554713be84160fdf0178cc8df86f5aabd33397","to":"0xc200000000000000000000000000000000000000","input":"0xB6E16D27AC5AB427A7F68900AC5559CE272DC6C37C82B3E052246C82244C50E4000000000000000000000000000000000000000000000000000000000000001C7B8B1991EB44757BC688016D27940DF8FB971D7C87F77A6BC4E938E3202C44037E9267B0AEAA82FA765361918F2D8ABD9CDD86E64AA6F2B81D3C4E0B69A7B055"}""", + """{"0x0000000000000000000000000000000000000001":{"movePrecompileToAddress":"0xc200000000000000000000000000000000000000", "code": "0x"}}""", + """{"jsonrpc":"2.0","result":"0x000000000000000000000000b7705ae4c6f81b66cdb323c65f4e8133690fc099","id":67}""" + )] + public async Task Eth_call_with_state_override(string name, string transactionJson, string stateOverrideJson, string expectedResult) + { + var transaction = JsonSerializer.Deserialize(transactionJson); + var stateOverride = JsonSerializer.Deserialize(stateOverrideJson); + + using Context ctx = await Context.Create(); + + string serialized = await ctx.Test.TestEthRpc("eth_call", transaction, "latest", stateOverride); + + JToken.Parse(serialized).Should().BeEquivalentTo(expectedResult); + } + + [TestCase( + "When balance and nonce is overriden", + """{"from":"0x7f554713be84160fdf0178cc8df86f5aabd33397","to":"0xbe5c953dd0ddb0ce033a98f36c981f1b74d3b33f","value":"0x1"}""", + """{"0x7f554713be84160fdf0178cc8df86f5aabd33397":{"balance":"0x123", "nonce": "0x123"}}""" + )] + [TestCase( + "When address code is overriden", + """{"from":"0x7f554713be84160fdf0178cc8df86f5aabd33397","to":"0xc200000000000000000000000000000000000000","input":"0xf8b2cb4f000000000000000000000000b7705ae4c6f81b66cdb323c65f4e8133690fc099"}""", + """{"0xc200000000000000000000000000000000000000":{"code":"0x608060405234801561001057600080fd5b506004361061002b5760003560e01c8063f8b2cb4f14610030575b600080fd5b61004a600480360381019061004591906100e4565b610060565b604051610057919061012a565b60405180910390f35b60008173ffffffffffffffffffffffffffffffffffffffff16319050919050565b600080fd5b600073ffffffffffffffffffffffffffffffffffffffff82169050919050565b60006100b182610086565b9050919050565b6100c1816100a6565b81146100cc57600080fd5b50565b6000813590506100de816100b8565b92915050565b6000602082840312156100fa576100f9610081565b5b6000610108848285016100cf565b91505092915050565b6000819050919050565b61012481610111565b82525050565b600060208201905061013f600083018461011b565b9291505056fea2646970667358221220172c443a163d8a43e018c339d1b749c312c94b6de22835953d960985daf228c764736f6c63430008120033"}}""" + )] + [TestCase( + "When precompile address is changed", + """{"from":"0x7f554713be84160fdf0178cc8df86f5aabd33397","to":"0xc200000000000000000000000000000000000000","input":"0xB6E16D27AC5AB427A7F68900AC5559CE272DC6C37C82B3E052246C82244C50E4000000000000000000000000000000000000000000000000000000000000001C7B8B1991EB44757BC688016D27940DF8FB971D7C87F77A6BC4E938E3202C44037E9267B0AEAA82FA765361918F2D8ABD9CDD86E64AA6F2B81D3C4E0B69A7B055"}""", + """{"0x0000000000000000000000000000000000000001":{"movePrecompileToAddress":"0xc200000000000000000000000000000000000000", "code": "0x"}}""" + )] + public async Task Eth_call_with_state_override_does_not_affect_other_calls(string name, string transactionJson, string stateOverrideJson) + { + var transaction = JsonSerializer.Deserialize(transactionJson); + var stateOverride = JsonSerializer.Deserialize(stateOverrideJson); + + using Context ctx = await Context.Create(); + + var resultOverrideBefore = await ctx.Test.TestEthRpc("eth_call", transaction, "latest", stateOverride); + + var resultNoOverride = await ctx.Test.TestEthRpc("eth_call", transaction, "latest"); + + var resultOverrideAfter = await ctx.Test.TestEthRpc("eth_call", transaction, "latest", stateOverride); + + using (new AssertionScope()) + { + JToken.Parse(resultOverrideBefore).Should().BeEquivalentTo(resultOverrideAfter); + JToken.Parse(resultNoOverride).Should().NotBeEquivalentTo(resultOverrideAfter); + } + } } diff --git a/src/Nethermind/Nethermind.JsonRpc.Test/Modules/Eth/Simulate/EthSimulateTestsBlocksAndTransactions.cs b/src/Nethermind/Nethermind.JsonRpc.Test/Modules/Eth/Simulate/EthSimulateTestsBlocksAndTransactions.cs index bcfa68f448a..a7f8b54e2dc 100644 --- a/src/Nethermind/Nethermind.JsonRpc.Test/Modules/Eth/Simulate/EthSimulateTestsBlocksAndTransactions.cs +++ b/src/Nethermind/Nethermind.JsonRpc.Test/Modules/Eth/Simulate/EthSimulateTestsBlocksAndTransactions.cs @@ -12,8 +12,8 @@ using Nethermind.Core.Extensions; using Nethermind.Core.Test.Builders; using Nethermind.Crypto; +using Nethermind.Evm; using Nethermind.Facade.Eth.RpcTransaction; -using Nethermind.Facade.Proxy.Models; using Nethermind.Facade.Proxy.Models.Simulate; using Nethermind.Int256; using Nethermind.JsonRpc.Modules.Eth; diff --git a/src/Nethermind/Nethermind.JsonRpc.Test/Modules/TestRpcBlockchain.cs b/src/Nethermind/Nethermind.JsonRpc.Test/Modules/TestRpcBlockchain.cs index 1a63fa846cd..5d81df7afa7 100644 --- a/src/Nethermind/Nethermind.JsonRpc.Test/Modules/TestRpcBlockchain.cs +++ b/src/Nethermind/Nethermind.JsonRpc.Test/Modules/TestRpcBlockchain.cs @@ -30,6 +30,7 @@ using Nethermind.Config; using Nethermind.Db; using Nethermind.Facade.Simulate; +using Nethermind.State; using Nethermind.Synchronization.ParallelSync; using NSubstitute; @@ -44,6 +45,7 @@ public class TestRpcBlockchain : TestBlockchain public ITxSender TxSender { get; private set; } = null!; public IReceiptFinder ReceiptFinder { get; private set; } = null!; public IGasPriceOracle GasPriceOracle { get; private set; } = null!; + public OverridableWorldStateManager OverridableWorldStateManager { get; private set; } = null!; public IKeyStore KeyStore { get; } = new MemKeyStore(TestItem.PrivateKeys, Path.Combine("testKeyStoreDir", Path.GetRandomFileName())); public IWallet TestWallet { get; } = @@ -145,8 +147,9 @@ protected override async Task Build( IFilterManager filterManager = new FilterManager(filterStore, BlockProcessor, TxPool, LimboLogs.Instance); var dbProvider = new ReadOnlyDbProvider(DbProvider, false); IReadOnlyBlockTree? roBlockTree = BlockTree!.AsReadOnly(); - ReadOnlyTxProcessingEnv processingEnv = new( - WorldStateManager, + OverridableWorldStateManager overridableWorldStateManager = new(DbProvider, WorldStateManager.TrieStore, LogManager); + OverridableTxProcessingEnv processingEnv = new( + overridableWorldStateManager, roBlockTree, SpecProvider, LimboLogs.Instance); @@ -169,6 +172,7 @@ protected override async Task Build( GasPriceOracle ??= new GasPriceOracle(BlockFinder, SpecProvider, LogManager); FeeHistoryOracle ??= new FeeHistoryOracle(BlockTree, ReceiptStorage, SpecProvider); EthRpcModule = _ethRpcModuleBuilder(this); + OverridableWorldStateManager = overridableWorldStateManager; 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 1c4023f547f..f4c12aeaaa6 100644 --- a/src/Nethermind/Nethermind.JsonRpc.Test/Modules/Trace/ParityStyleTracerTests.cs +++ b/src/Nethermind/Nethermind.JsonRpc.Test/Modules/Trace/ParityStyleTracerTests.cs @@ -41,6 +41,7 @@ public class ParityStyleTracerTests private Tracer? _tracer; private IPoSSwitcher? _poSSwitcher; private IStateReader _stateReader; + private TraceRpcModule _traceRpcModule; private readonly IJsonRpcConfig _jsonRpcConfig = new JsonRpcConfig(); [SetUp] @@ -84,7 +85,9 @@ public void Setup() _blockTree.SuggestBlock(genesis); _processor.Process(genesis, ProcessingOptions.None, NullBlockTracer.Instance); + IOverridableTxProcessorSource txProcessingSource = Substitute.For(); _tracer = new Tracer(stateProvider, _processor, _processor); + _traceRpcModule = new(NullReceiptStorage.Instance, _tracer, _blockTree, _jsonRpcConfig, _stateReader, txProcessingSource); } [TearDown] @@ -93,16 +96,14 @@ public void Setup() [Test] public void Can_trace_raw_parity_style() { - TraceRpcModule traceRpcModule = new(NullReceiptStorage.Instance, _tracer, _blockTree, _jsonRpcConfig, _stateReader); - ResultWrapper result = traceRpcModule.trace_rawTransaction(Bytes.FromHexString("f889808609184e72a00082271094000000000000000000000000000000000000000080a47f74657374320000000000000000000000000000000000000000000000000000006000571ca08a8bbf888cfa37bbf0bb965423625641fc956967b81d12e23709cead01446075a01ce999b56a8a88504be365442ea61239198e23d1fce7d00fcfc5cd3b44b7215f"), new[] { "trace" }); + ResultWrapper result = _traceRpcModule.trace_rawTransaction(Bytes.FromHexString("f889808609184e72a00082271094000000000000000000000000000000000000000080a47f74657374320000000000000000000000000000000000000000000000000000006000571ca08a8bbf888cfa37bbf0bb965423625641fc956967b81d12e23709cead01446075a01ce999b56a8a88504be365442ea61239198e23d1fce7d00fcfc5cd3b44b7215f"), new[] { "trace" }); Assert.That(result.Data, Is.Not.Null); } [Test] public void Can_trace_raw_parity_style_berlin_tx() { - TraceRpcModule traceRpcModule = new(NullReceiptStorage.Instance, _tracer, _blockTree, _jsonRpcConfig, _stateReader); - ResultWrapper result = traceRpcModule.trace_rawTransaction(Bytes.FromHexString("01f85b821e8e8204d7847735940083030d408080853a60005500c080a0f43e70c79190701347517e283ef63753f6143a5225cbb500b14d98eadfb7616ba070893923d8a1fc97499f426524f9e82f8e0322dfac7c3d7e8a9eee515f0bcdc4"), new[] { "trace" }); + ResultWrapper result = _traceRpcModule.trace_rawTransaction(Bytes.FromHexString("01f85b821e8e8204d7847735940083030d408080853a60005500c080a0f43e70c79190701347517e283ef63753f6143a5225cbb500b14d98eadfb7616ba070893923d8a1fc97499f426524f9e82f8e0322dfac7c3d7e8a9eee515f0bcdc4"), new[] { "trace" }); Assert.That(result.Data, Is.Not.Null); } @@ -114,8 +115,7 @@ public void Should_return_correct_block_reward(bool isPostMerge) _blockTree!.SuggestBlock(block).Should().Be(AddBlockResult.Added); _poSSwitcher!.IsPostMerge(Arg.Any()).Returns(isPostMerge); - TraceRpcModule traceRpcModule = new(NullReceiptStorage.Instance, _tracer, _blockTree, _jsonRpcConfig, _stateReader); - ParityTxTraceFromStore[] result = traceRpcModule.trace_block(new BlockParameter(block.Number)).Data.ToArray(); + ParityTxTraceFromStore[] result = _traceRpcModule.trace_block(new BlockParameter(block.Number)).Data.ToArray(); if (isPostMerge) { result.Length.Should().Be(1); diff --git a/src/Nethermind/Nethermind.JsonRpc.Test/Modules/TraceRpcModuleTests.cs b/src/Nethermind/Nethermind.JsonRpc.Test/Modules/TraceRpcModuleTests.cs index 5847c662c46..20c674df14a 100644 --- a/src/Nethermind/Nethermind.JsonRpc.Test/Modules/TraceRpcModuleTests.cs +++ b/src/Nethermind/Nethermind.JsonRpc.Test/Modules/TraceRpcModuleTests.cs @@ -3,10 +3,11 @@ using System.Collections.Generic; using System.Linq; +using System.Text.Json; using System.Threading.Tasks; using FluentAssertions; -using Nethermind.Blockchain; -using Nethermind.Blockchain.Receipts; +using FluentAssertions.Execution; +using FluentAssertions.Json; using Nethermind.Core; using Nethermind.Core.Extensions; using Nethermind.Core.Specs; @@ -16,19 +17,18 @@ using Nethermind.JsonRpc.Modules.Trace; using NUnit.Framework; using Nethermind.Blockchain.Find; -using Nethermind.Consensus.Processing; using Nethermind.Consensus.Rewards; -using Nethermind.Consensus.Tracing; -using Nethermind.Consensus.Validators; using Nethermind.Core.Crypto; +using Nethermind.Crypto; using Nethermind.Evm; -using Nethermind.Evm.TransactionProcessing; +using Nethermind.Evm.Tracing.ParityStyle; using Nethermind.Facade.Eth.RpcTransaction; using Nethermind.Serialization.Json; using Nethermind.Specs.Forks; using Nethermind.Specs.Test; using Nethermind.JsonRpc.Data; -using Nethermind.JsonRpc.Modules; +using Nethermind.Serialization.Rlp; +using Newtonsoft.Json.Linq; namespace Nethermind.JsonRpc.Test.Modules; @@ -40,59 +40,47 @@ public async Task Build(ISpecProvider? specProvider = null, bool isAura = false) { JsonRpcConfig = new JsonRpcConfig(); Blockchain = await TestRpcBlockchain.ForTest(isAura ? SealEngineType.AuRa : SealEngineType.NethDev).Build(specProvider); + await Blockchain.AddFunds(TestItem.AddressA, 1000.Ether()); await Blockchain.AddFunds(TestItem.AddressB, 1000.Ether()); await Blockchain.AddFunds(TestItem.AddressC, 1000.Ether()); - ReceiptsRecovery receiptsRecovery = - new(Blockchain.EthereumEcdsa, Blockchain.SpecProvider); - IReceiptFinder receiptFinder = new FullInfoReceiptFinder(Blockchain.ReceiptStorage, receiptsRecovery, Blockchain.BlockFinder); - ReadOnlyTxProcessingEnv txProcessingEnv = - new(Blockchain.WorldStateManager, Blockchain.BlockTree.AsReadOnly(), Blockchain.SpecProvider, Blockchain.LogManager); - IReadOnlyTxProcessingScope scope = txProcessingEnv.Build(Keccak.EmptyTreeHash); - - RewardCalculator rewardCalculatorSource = new(Blockchain.SpecProvider); - - IRewardCalculator rewardCalculator = rewardCalculatorSource.Get(scope.TransactionProcessor); - - RpcBlockTransactionsExecutor rpcBlockTransactionsExecutor = new(scope.TransactionProcessor, scope.WorldState); - BlockProcessor.BlockValidationTransactionsExecutor executeBlockTransactionsExecutor = new(scope.TransactionProcessor, - scope.WorldState); - - ReadOnlyChainProcessingEnv CreateChainProcessingEnv(IBlockProcessor.IBlockTransactionsExecutor transactionsExecutor) => new( - scope, - Always.Valid, - Blockchain.BlockPreprocessorStep, - rewardCalculator, - Blockchain.ReceiptStorage, - Blockchain.SpecProvider, - Blockchain.BlockTree, - Blockchain.StateReader, - Blockchain.LogManager, - transactionsExecutor); - - ReadOnlyChainProcessingEnv traceProcessingEnv = CreateChainProcessingEnv(rpcBlockTransactionsExecutor); - ReadOnlyChainProcessingEnv executeProcessingEnv = CreateChainProcessingEnv(executeBlockTransactionsExecutor); - - Tracer tracer = new(scope.WorldState, traceProcessingEnv.ChainProcessor, executeProcessingEnv.ChainProcessor); - TraceRpcModule = new TraceRpcModule(receiptFinder, tracer, Blockchain.BlockFinder, JsonRpcConfig, txProcessingEnv.StateReader); for (int i = 1; i < 10; i++) { List transactions = new(); for (int j = 0; j < i; j++) { - transactions.Add(Core.Test.Builders.Build.A.Transaction.WithNonce(Blockchain.State.GetNonce(TestItem.AddressB) + (UInt256)j) + transactions.Add(Core.Test.Builders.Build.A.Transaction + .WithTo(Address.Zero) + .WithNonce(Blockchain.State.GetNonce(TestItem.AddressB) + (UInt256)j) .SignedAndResolved(Blockchain.EthereumEcdsa, TestItem.PrivateKeyB).TestObject); } await Blockchain.AddBlock(transactions.ToArray()); } + + Factory = new( + Blockchain.OverridableWorldStateManager.TrieStore, + Blockchain.DbProvider, + Blockchain.BlockTree, + JsonRpcConfig, + Blockchain.BlockPreprocessorStep, + new RewardCalculator(Blockchain.SpecProvider), + Blockchain.ReceiptStorage, + Blockchain.SpecProvider, + Blockchain.PoSSwitcher, + Blockchain.LogManager + ); + + TraceRpcModule = Factory.Create(); } public ITraceRpcModule TraceRpcModule { get; private set; } = null!; + public TraceModuleFactory Factory { get; private set; } = null!; public IJsonRpcConfig JsonRpcConfig { get; private set; } = null!; public TestRpcBlockchain Blockchain { get; set; } = null!; } + [Test] public async Task Tx_positions_are_fine() { @@ -675,6 +663,93 @@ public async Task Trace_call_without_blockParameter_provided_test() Assert.That(traces.Data.Action.To, Is.EqualTo(TestItem.AddressC)); } + [Test] + public async Task Trace_call_runs_on_top_of_specified_block() + { + Context context = new(); + await context.Build(); + TestRpcBlockchain blockchain = context.Blockchain; + + PrivateKey addressKey = Build.A.PrivateKey.TestObject; + Address address = addressKey.Address; + UInt256 balance = 100.Ether(), send = balance / 2; + + await blockchain.AddFunds(address, balance); + Hash256 lastBlockHash = blockchain.BlockTree.Head!.Hash!; + + string[] traceTypes = ["stateDiff"]; + Transaction transaction = Build.A.Transaction + .SignedAndResolved(addressKey) + .WithTo(TestItem.AddressC) + .WithValue(send) + .TestObject; + + ResultWrapper traces = context.TraceRpcModule.trace_call( + TransactionForRpc.FromTransaction(transaction), traceTypes, new(lastBlockHash) + ); + + ParityAccountStateChange? stateChanges = traces.Data.StateChanges?.GetValueOrDefault(address); + stateChanges?.Balance?.Should().BeEquivalentTo(new ParityStateChange(balance, balance - send)); + } + + [Test] + public async Task Trace_callMany_runs_on_top_of_specified_block() + { + Context context = new(); + await context.Build(); + TestRpcBlockchain blockchain = context.Blockchain; + + PrivateKey addressKey = Build.A.PrivateKey.TestObject; + Address address = addressKey.Address; + UInt256 balance = 100.Ether(), send = balance / 2; + + await blockchain.AddFunds(address, balance); + Hash256 lastBlockHash = blockchain.BlockTree.Head!.Hash!; + + string[] traceTypes = ["stateDiff"]; + Transaction transaction = Build.A.Transaction + .SignedAndResolved(addressKey) + .WithTo(TestItem.AddressC) + .WithValue(send) + .TestObject; + + ResultWrapper> traces = context.TraceRpcModule.trace_callMany( + [new() { Transaction = TransactionForRpc.FromTransaction(transaction), TraceTypes = traceTypes }], + new(lastBlockHash) + ); + + ParityAccountStateChange? stateChanges = traces.Data.Single().StateChanges?.GetValueOrDefault(address); + stateChanges?.Balance?.Should().BeEquivalentTo(new ParityStateChange(balance, balance - send)); + } + + [Test] + public async Task Trace_rawTransaction_runs_on_top_of_specified_block() + { + Context context = new(); + await context.Build(); + TestRpcBlockchain blockchain = context.Blockchain; + + PrivateKey addressKey = Build.A.PrivateKey.TestObject; + Address address = addressKey.Address; + UInt256 balance = 100.Ether(), send = balance / 2; + + await blockchain.AddFunds(address, balance); + + string[] traceTypes = ["stateDiff"]; + Transaction transaction = Build.A.Transaction + .WithTo(TestItem.AddressC) + .WithValue(send) + .SignedAndResolved(addressKey) + .TestObject; + + ResultWrapper traces = context.TraceRpcModule.trace_rawTransaction( + TxDecoder.Instance.Encode(transaction).Bytes, traceTypes + ); + + ParityAccountStateChange? stateChanges = traces.Data.StateChanges?.GetValueOrDefault(address); + stateChanges?.Balance?.Should().BeEquivalentTo(new ParityStateChange(balance, balance - send)); + } + [Test] public async Task Trace_call_simple_tx_test() { @@ -774,7 +849,7 @@ public async Task Trace_callMany_accumulates_state_changes() context.TraceRpcModule, "trace_callMany", calls); - Assert.That(serialized, Is.EqualTo("{\"jsonrpc\":\"2.0\",\"result\":[{\"output\":null,\"stateDiff\":{\"0x0000000000000000000000000000000000000000\":{\"balance\":{\"*\":{\"from\":\"0x24\",\"to\":\"0x25\"}},\"code\":\"=\",\"nonce\":\"=\",\"storage\":{}},\"0xb7705ae4c6f81b66cdb323c65f4e8133690fc099\":{\"balance\":{\"*\":{\"from\":\"0x3635c9adc5de9f09e5\",\"to\":\"0x3635c9adc5de9f09e4\"}},\"code\":\"=\",\"nonce\":{\"*\":{\"from\":\"0x3\",\"to\":\"0x4\"}},\"storage\":{}}},\"trace\":[],\"vmTrace\":null},{\"output\":null,\"stateDiff\":{\"0x0000000000000000000000000000000000000000\":{\"balance\":{\"*\":{\"from\":\"0x25\",\"to\":\"0x26\"}},\"code\":\"=\",\"nonce\":\"=\",\"storage\":{}},\"0xb7705ae4c6f81b66cdb323c65f4e8133690fc099\":{\"balance\":{\"*\":{\"from\":\"0x3635c9adc5de9f09e4\",\"to\":\"0x3635c9adc5de9f09e3\"}},\"code\":\"=\",\"nonce\":{\"*\":{\"from\":\"0x4\",\"to\":\"0x5\"}},\"storage\":{}}},\"trace\":[],\"vmTrace\":null}],\"id\":67}"), serialized.Replace("\"", "\\\"")); + Assert.That(serialized, Is.EqualTo("{\"jsonrpc\":\"2.0\",\"result\":[{\"output\":null,\"stateDiff\":{\"0x0000000000000000000000000000000000000000\":{\"balance\":{\"*\":{\"from\":\"0x2d\",\"to\":\"0x2e\"}},\"code\":\"=\",\"nonce\":\"=\",\"storage\":{}},\"0xb7705ae4c6f81b66cdb323c65f4e8133690fc099\":{\"balance\":{\"*\":{\"from\":\"0x3635c9adc5de9f09e5\",\"to\":\"0x3635c9adc5de9f09e4\"}},\"code\":\"=\",\"nonce\":{\"*\":{\"from\":\"0x3\",\"to\":\"0x4\"}},\"storage\":{}}},\"trace\":[],\"vmTrace\":null},{\"output\":null,\"stateDiff\":{\"0x0000000000000000000000000000000000000000\":{\"balance\":{\"*\":{\"from\":\"0x2e\",\"to\":\"0x2f\"}},\"code\":\"=\",\"nonce\":\"=\",\"storage\":{}},\"0xb7705ae4c6f81b66cdb323c65f4e8133690fc099\":{\"balance\":{\"*\":{\"from\":\"0x3635c9adc5de9f09e4\",\"to\":\"0x3635c9adc5de9f09e3\"}},\"code\":\"=\",\"nonce\":{\"*\":{\"from\":\"0x4\",\"to\":\"0x5\"}},\"storage\":{}}},\"trace\":[],\"vmTrace\":null}],\"id\":67}"), serialized.Replace("\"", "\\\"")); } [Test] @@ -852,4 +927,89 @@ public async Task Trace_replayBlockTransactions_stateDiff() state[TestItem.AddressA].Balance!.After.Should().Be(accountA.Balance - 21000 * tx.GasPrice - tx.Value); state[TestItem.AddressF].Balance!.After.Should().Be(accountF.Balance + tx.Value); } + + [TestCase( + "Nonce increments from state override", + """{"from":"0x7f554713be84160fdf0178cc8df86f5aabd33397","to":"0xc200000000000000000000000000000000000000"}""", + "stateDiff", + """{"0x7f554713be84160fdf0178cc8df86f5aabd33397":{"nonce":"0x123"}}""", + """{"jsonrpc":"2.0","result":{"output":null,"stateDiff":{"0x7f554713be84160fdf0178cc8df86f5aabd33397":{"balance":"=","code":"=","nonce":{"*":{"from":"0x123","to":"0x124"}},"storage":{}}},"trace":[],"vmTrace":null},"id":67}""" + )] + [TestCase( + "Uses account balance from state override", + """{"from":"0x7f554713be84160fdf0178cc8df86f5aabd33397","to":"0xbe5c953dd0ddb0ce033a98f36c981f1b74d3b33f","value":"0x100"}""", + "stateDiff", + """{"0x7f554713be84160fdf0178cc8df86f5aabd33397":{"balance":"0x100"}}""", + """{"jsonrpc":"2.0","result":{"output":null,"stateDiff":{"0x7f554713be84160fdf0178cc8df86f5aabd33397":{"balance":{"*":{"from":"0x100","to":"0x0"}},"code":"=","nonce":{"*":{"from":"0x0","to":"0x1"}},"storage":{}},"0xbe5c953dd0ddb0ce033a98f36c981f1b74d3b33f":{"balance":{"\u002B":"0x100"},"code":"=","nonce":{"\u002B":"0x0"},"storage":{}}},"trace":[],"vmTrace":null},"id":67}""" + )] + [TestCase( + "Executes code from state override", + """{"from":"0x7f554713be84160fdf0178cc8df86f5aabd33397","to":"0xc200000000000000000000000000000000000000","input":"0x60fe47b1112233445566778899001122334455667788990011223344556677889900112233445566778899001122"}""", + "stateDiff", + """{"0xc200000000000000000000000000000000000000":{"code":"0x6080604052348015600e575f80fd5b50600436106030575f3560e01c80632a1afcd914603457806360fe47b114604d575b5f80fd5b603b5f5481565b60405190815260200160405180910390f35b605c6058366004605e565b5f55565b005b5f60208284031215606d575f80fd5b503591905056fea2646970667358221220fd4e5f3894be8e57fc7460afebb5c90d96c3486d79bf47b00c2ed666ab2f82b364736f6c634300081a0033"}}""", + """{"jsonrpc":"2.0","result":{"output":null,"stateDiff":{"0x7f554713be84160fdf0178cc8df86f5aabd33397":{"balance":{"\u002B":"0x0"},"code":"=","nonce":{"\u002B":"0x1"},"storage":{}},"0xc200000000000000000000000000000000000000":{"balance":"=","code":"=","nonce":"=","storage":{"0x0000000000000000000000000000000000000000000000000000000000000000":{"*":{"from":"0x0000000000000000000000000000000000000000000000000000000000000000","to":"0x1122334455667788990011223344556677889900112233445566778899001122"}}}}},"trace":[],"vmTrace":null},"id":67}""" + )] + [TestCase( + "Uses storage from state override", + """{"from":"0x7f554713be84160fdf0178cc8df86f5aabd33397","to":"0xc200000000000000000000000000000000000000","input":"0x60fe47b1112233445566778899001122334455667788990011223344556677889900112233445566778899001122"}""", + "stateDiff", + """{"0xc200000000000000000000000000000000000000":{"state": {"0x0000000000000000000000000000000000000000000000000000000000000000": "0x0000000000000000000000000000000000000000000000000000000000123456"}, "code":"0x6080604052348015600e575f80fd5b50600436106030575f3560e01c80632a1afcd914603457806360fe47b114604d575b5f80fd5b603b5f5481565b60405190815260200160405180910390f35b605c6058366004605e565b5f55565b005b5f60208284031215606d575f80fd5b503591905056fea2646970667358221220fd4e5f3894be8e57fc7460afebb5c90d96c3486d79bf47b00c2ed666ab2f82b364736f6c634300081a0033"}}""", + """{"jsonrpc":"2.0","result":{"output":null,"stateDiff":{"0x7f554713be84160fdf0178cc8df86f5aabd33397":{"balance":{"\u002B":"0x0"},"code":"=","nonce":{"\u002B":"0x1"},"storage":{}},"0xc200000000000000000000000000000000000000":{"balance":"=","code":"=","nonce":"=","storage":{"0x0000000000000000000000000000000000000000000000000000000000000000":{"*":{"from":"0x0000000000000000000000000000000000000000000000000000000000123456","to":"0x1122334455667788990011223344556677889900112233445566778899001122"}}}}},"trace":[],"vmTrace":null},"id":67}""" + )] + [TestCase( + "Executes precompile using overriden address", + """{"from":"0x7f554713be84160fdf0178cc8df86f5aabd33397","to":"0x0000000000000000000000000000000000123456","input":"0xB6E16D27AC5AB427A7F68900AC5559CE272DC6C37C82B3E052246C82244C50E4000000000000000000000000000000000000000000000000000000000000001C7B8B1991EB44757BC688016D27940DF8FB971D7C87F77A6BC4E938E3202C44037E9267B0AEAA82FA765361918F2D8ABD9CDD86E64AA6F2B81D3C4E0B69A7B055"}""", + "trace", + """{"0x0000000000000000000000000000000000000001":{"movePrecompileToAddress":"0x0000000000000000000000000000000000123456", "code": "0x"}}""", + """{"jsonrpc":"2.0","result":{"output":"0x000000000000000000000000b7705ae4c6f81b66cdb323c65f4e8133690fc099","stateDiff":null,"trace":[{"action":{"callType":"call","from":"0x7f554713be84160fdf0178cc8df86f5aabd33397","gas":"0x5f58878","input":"0xb6e16d27ac5ab427a7f68900ac5559ce272dc6c37c82b3e052246c82244c50e4000000000000000000000000000000000000000000000000000000000000001c7b8b1991eb44757bc688016d27940df8fb971d7c87f77a6bc4e938e3202c44037e9267b0aeaa82fa765361918f2d8abd9cdd86e64aa6f2b81d3c4e0b69a7b055","to":"0x0000000000000000000000000000000000123456","value":"0x0"},"result":{"gasUsed":"0xbb8","output":"0x000000000000000000000000b7705ae4c6f81b66cdb323c65f4e8133690fc099"},"subtraces":0,"traceAddress":[],"type":"call"}],"vmTrace":null},"id":67}""" + )] + public async Task Trace_call_with_state_override(string name, string transactionJson, string traceType, string stateOverrideJson, string expectedResult) + { + var transaction = JsonSerializer.Deserialize(transactionJson); + var stateOverride = JsonSerializer.Deserialize(stateOverrideJson); + + Context context = new(); + await context.Build(new TestSpecProvider(Prague.Instance)); + string serialized = await RpcTest.TestSerializedRequest( + context.TraceRpcModule, + "trace_call", transaction, new[] { traceType }, "latest", stateOverride); + + JToken.Parse(serialized).Should().BeEquivalentTo(expectedResult); + } + + [TestCase( + """{"from":"0xb7705ae4c6f81b66cdb323c65f4e8133690fc099","to":"0xc200000000000000000000000000000000000000"}""", + "stateDiff", + """{"0xb7705ae4c6f81b66cdb323c65f4e8133690fc099":{"balance":"0x123", "nonce": "0x123"}}""" + )] + [TestCase( + """{"from":"0xb7705ae4c6f81b66cdb323c65f4e8133690fc099","to":"0xc200000000000000000000000000000000000000","input":"0xf8b2cb4f000000000000000000000000b7705ae4c6f81b66cdb323c65f4e8133690fc099"}""", + "trace", + """{"0xc200000000000000000000000000000000000000":{"code":"0x608060405234801561001057600080fd5b506004361061002b5760003560e01c8063f8b2cb4f14610030575b600080fd5b61004a600480360381019061004591906100e4565b610060565b604051610057919061012a565b60405180910390f35b60008173ffffffffffffffffffffffffffffffffffffffff16319050919050565b600080fd5b600073ffffffffffffffffffffffffffffffffffffffff82169050919050565b60006100b182610086565b9050919050565b6100c1816100a6565b81146100cc57600080fd5b50565b6000813590506100de816100b8565b92915050565b6000602082840312156100fa576100f9610081565b5b6000610108848285016100cf565b91505092915050565b6000819050919050565b61012481610111565b82525050565b600060208201905061013f600083018461011b565b9291505056fea2646970667358221220172c443a163d8a43e018c339d1b749c312c94b6de22835953d960985daf228c764736f6c63430008120033"}}""" + )] + public async Task Trace_call_with_state_override_does_not_affect_other_calls(string transactionJson, string traceType, string stateOverrideJson) + { + var transaction = JsonSerializer.Deserialize(transactionJson); + var stateOverride = JsonSerializer.Deserialize(stateOverrideJson); + + Context context = new(); + await context.Build(); + + var traceTypes = new[] { traceType }; + + var resultOverrideBefore = await RpcTest.TestSerializedRequest(context.TraceRpcModule, "trace_call", + transaction, traceTypes, null, stateOverride); + + var resultNoOverride = await RpcTest.TestSerializedRequest(context.TraceRpcModule, "trace_call", + transaction, traceTypes, null); + + var resultOverrideAfter = await RpcTest.TestSerializedRequest(context.TraceRpcModule, "trace_call", + transaction, traceTypes, null, stateOverride); + + using (new AssertionScope()) + { + JToken.Parse(resultOverrideBefore).Should().BeEquivalentTo(resultOverrideAfter); + JToken.Parse(resultNoOverride).Should().NotBeEquivalentTo(resultOverrideAfter); + } + } } diff --git a/src/Nethermind/Nethermind.JsonRpc.TraceStore/TraceStoreRpcModule.cs b/src/Nethermind/Nethermind.JsonRpc.TraceStore/TraceStoreRpcModule.cs index 12b38afe90a..f73ed60d07f 100644 --- a/src/Nethermind/Nethermind.JsonRpc.TraceStore/TraceStoreRpcModule.cs +++ b/src/Nethermind/Nethermind.JsonRpc.TraceStore/TraceStoreRpcModule.cs @@ -6,6 +6,7 @@ using Nethermind.Core; using Nethermind.Core.Crypto; using Nethermind.Db; +using Nethermind.Evm; using Nethermind.Evm.Tracing.ParityStyle; using Nethermind.Facade.Eth.RpcTransaction; using Nethermind.JsonRpc.Data; @@ -52,8 +53,9 @@ public TraceStoreRpcModule(ITraceRpcModule traceModule, _logger = logManager.GetClassLogger(); } - public ResultWrapper trace_call(TransactionForRpc call, string[] traceTypes, BlockParameter? blockParameter = null) => - _traceModule.trace_call(call, traceTypes, blockParameter); + public ResultWrapper trace_call(TransactionForRpc call, string[] traceTypes, BlockParameter? blockParameter = null, + Dictionary? stateOverride = null) => + _traceModule.trace_call(call, traceTypes, blockParameter, stateOverride); public ResultWrapper> trace_callMany(TransactionForRpcWithTraceTypes[] calls, BlockParameter? blockParameter = null) => _traceModule.trace_callMany(calls, blockParameter); diff --git a/src/Nethermind/Nethermind.JsonRpc/Modules/DebugModule/DebugModuleFactory.cs b/src/Nethermind/Nethermind.JsonRpc/Modules/DebugModule/DebugModuleFactory.cs index 9856232921d..842adf7fa3a 100644 --- a/src/Nethermind/Nethermind.JsonRpc/Modules/DebugModule/DebugModuleFactory.cs +++ b/src/Nethermind/Nethermind.JsonRpc/Modules/DebugModule/DebugModuleFactory.cs @@ -18,12 +18,13 @@ using Nethermind.Logging; using Nethermind.State; using Nethermind.Synchronization.ParallelSync; +using Nethermind.Trie.Pruning; namespace Nethermind.JsonRpc.Modules.DebugModule; public class DebugModuleFactory : ModuleFactoryBase { - private readonly IWorldStateManager _worldStateManager; + private readonly IReadOnlyTrieStore _trieStore; private readonly IJsonRpcConfig _jsonRpcConfig; private readonly IBlockValidator _blockValidator; private readonly IRewardCalculatorSource _rewardCalculatorSource; @@ -38,10 +39,9 @@ public class DebugModuleFactory : ModuleFactoryBase private readonly ISyncModeSelector _syncModeSelector; private readonly IBadBlockStore _badBlockStore; private readonly IFileSystem _fileSystem; - private readonly ILogger _logger; public DebugModuleFactory( - IWorldStateManager worldStateManager, + IReadOnlyTrieStore trieStore, IDbProvider dbProvider, IBlockTree blockTree, IJsonRpcConfig jsonRpcConfig, @@ -57,7 +57,7 @@ public DebugModuleFactory( IFileSystem fileSystem, ILogManager logManager) { - _worldStateManager = worldStateManager; + _trieStore = trieStore; _dbProvider = dbProvider.AsReadOnly(false); _blockTree = blockTree.AsReadOnly(); _jsonRpcConfig = jsonRpcConfig ?? throw new ArgumentNullException(nameof(jsonRpcConfig)); @@ -72,16 +72,12 @@ public DebugModuleFactory( _syncModeSelector = syncModeSelector ?? throw new ArgumentNullException(nameof(syncModeSelector)); _badBlockStore = badBlockStore; _fileSystem = fileSystem ?? throw new ArgumentNullException(nameof(fileSystem)); - _logger = logManager.GetClassLogger(); } public override IDebugRpcModule Create() { - ReadOnlyTxProcessingEnv txEnv = new( - _worldStateManager, - _blockTree, - _specProvider, - _logManager); + OverridableWorldStateManager worldStateManager = new(_dbProvider, _trieStore, _logManager); + OverridableTxProcessingEnv txEnv = new(worldStateManager, _blockTree, _specProvider, _logManager); IReadOnlyTxProcessingScope scope = txEnv.Build(Keccak.EmptyTreeHash); @@ -95,7 +91,7 @@ public override IDebugRpcModule Create() _receiptStorage, _specProvider, _blockTree, - _worldStateManager.GlobalStateReader, + worldStateManager.GlobalStateReader, _logManager, transactionsExecutor); @@ -107,7 +103,8 @@ public override IDebugRpcModule Create() _badBlockStore, _specProvider, transactionProcessorAdapter, - _fileSystem); + _fileSystem, + txEnv); DebugBridge debugBridge = new( _configProvider, diff --git a/src/Nethermind/Nethermind.JsonRpc/Modules/Eth/EthRpcModule.TransactionExecutor.cs b/src/Nethermind/Nethermind.JsonRpc/Modules/Eth/EthRpcModule.TransactionExecutor.cs index 146df3f42a2..5f573a07c3f 100644 --- a/src/Nethermind/Nethermind.JsonRpc/Modules/Eth/EthRpcModule.TransactionExecutor.cs +++ b/src/Nethermind/Nethermind.JsonRpc/Modules/Eth/EthRpcModule.TransactionExecutor.cs @@ -1,6 +1,7 @@ // SPDX-FileCopyrightText: 2022 Demerzel Solutions Limited // SPDX-License-Identifier: LGPL-3.0-only +using System.Collections.Generic; using System.Threading; using Nethermind.Blockchain.Find; using Nethermind.Core; @@ -30,7 +31,7 @@ protected override Transaction Prepare(TransactionForRpc call) return tx; } - protected override ResultWrapper Execute(BlockHeader header, Transaction tx, CancellationToken token) + protected override ResultWrapper Execute(BlockHeader header, Transaction tx, Dictionary? stateOverride, CancellationToken token) { BlockHeader clonedHeader = header.Clone(); if (NoBaseFee) @@ -41,35 +42,36 @@ protected override ResultWrapper Execute(BlockHeader header, Transactio { return ResultWrapper.Fail("Contract creation without any data provided.", ErrorCodes.InvalidInput); } - return ExecuteTx(clonedHeader, tx, token); + return ExecuteTx(clonedHeader, tx, stateOverride, token); } public override ResultWrapper Execute( TransactionForRpc transactionCall, - BlockParameter? blockParameter) + BlockParameter? blockParameter, + Dictionary? stateOverride = null) { NoBaseFee = !transactionCall.ShouldSetBaseFee(); transactionCall.EnsureDefaults(_rpcConfig.GasCap); - return base.Execute(transactionCall, blockParameter); + return base.Execute(transactionCall, blockParameter, stateOverride); } - public ResultWrapper ExecuteTx(TransactionForRpc transactionCall, BlockParameter? blockParameter) => Execute(transactionCall, blockParameter); + public ResultWrapper ExecuteTx(TransactionForRpc transactionCall, BlockParameter? blockParameter, Dictionary? stateOverride = null) + => Execute(transactionCall, blockParameter, stateOverride); - protected abstract ResultWrapper ExecuteTx(BlockHeader header, Transaction tx, CancellationToken token); + protected abstract ResultWrapper ExecuteTx(BlockHeader header, Transaction tx, Dictionary? stateOverride, CancellationToken token); } private class CallTxExecutor(IBlockchainBridge blockchainBridge, IBlockFinder blockFinder, IJsonRpcConfig rpcConfig) : TxExecutor(blockchainBridge, blockFinder, rpcConfig) { - protected override ResultWrapper ExecuteTx(BlockHeader header, Transaction tx, CancellationToken token) + protected override ResultWrapper ExecuteTx(BlockHeader header, Transaction tx, Dictionary? stateOverride, CancellationToken token) { - CallOutput result = _blockchainBridge.Call(header, tx, token); + CallOutput result = _blockchainBridge.Call(header, tx, stateOverride, token); 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(IBlockchainBridge blockchainBridge, IBlockFinder blockFinder, IJsonRpcConfig rpcConfig) @@ -77,9 +79,9 @@ private class EstimateGasTxExecutor(IBlockchainBridge blockchainBridge, IBlockFi { private readonly int _errorMargin = rpcConfig.EstimateErrorMargin; - protected override ResultWrapper ExecuteTx(BlockHeader header, Transaction tx, CancellationToken token) + protected override ResultWrapper ExecuteTx(BlockHeader header, Transaction tx, Dictionary stateOverride, CancellationToken token) { - CallOutput result = _blockchainBridge.EstimateGas(header, tx, _errorMargin, token); + CallOutput result = _blockchainBridge.EstimateGas(header, tx, _errorMargin, stateOverride, token); return result switch { @@ -93,7 +95,7 @@ private class EstimateGasTxExecutor(IBlockchainBridge blockchainBridge, IBlockFi private class CreateAccessListTxExecutor(IBlockchainBridge blockchainBridge, IBlockFinder blockFinder, IJsonRpcConfig rpcConfig, bool optimize) : TxExecutor(blockchainBridge, blockFinder, rpcConfig) { - protected override ResultWrapper ExecuteTx(BlockHeader header, Transaction tx, CancellationToken token) + protected override ResultWrapper ExecuteTx(BlockHeader header, Transaction tx, Dictionary stateOverride, CancellationToken token) { CallOutput result = _blockchainBridge.CreateAccessList(header, tx, token, optimize); diff --git a/src/Nethermind/Nethermind.JsonRpc/Modules/Eth/EthRpcModule.cs b/src/Nethermind/Nethermind.JsonRpc/Modules/Eth/EthRpcModule.cs index 97246ca48de..70f06eeb6b0 100644 --- a/src/Nethermind/Nethermind.JsonRpc/Modules/Eth/EthRpcModule.cs +++ b/src/Nethermind/Nethermind.JsonRpc/Modules/Eth/EthRpcModule.cs @@ -22,6 +22,7 @@ using Nethermind.Facade.Eth; using Nethermind.Facade.Eth.RpcTransaction; using Nethermind.Facade.Filters; +using Nethermind.Facade.Proxy.Models; using Nethermind.Facade.Proxy.Models.Simulate; using Nethermind.Int256; using Nethermind.JsonRpc.Data; @@ -327,17 +328,17 @@ private async Task> SendTx(Transaction tx, } } - public ResultWrapper eth_call(TransactionForRpc transactionCall, BlockParameter? blockParameter = null) => + public ResultWrapper eth_call(TransactionForRpc transactionCall, BlockParameter? blockParameter = null, Dictionary? stateOverride = null) => new CallTxExecutor(_blockchainBridge, _blockFinder, _rpcConfig) - .ExecuteTx(transactionCall, blockParameter); + .ExecuteTx(transactionCall, blockParameter, stateOverride); 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) => + public ResultWrapper eth_estimateGas(TransactionForRpc transactionCall, BlockParameter? blockParameter, Dictionary? stateOverride = null) => new EstimateGasTxExecutor(_blockchainBridge, _blockFinder, _rpcConfig) - .ExecuteTx(transactionCall, blockParameter); + .ExecuteTx(transactionCall, blockParameter, stateOverride); public ResultWrapper eth_createAccessList(TransactionForRpc transactionCall, BlockParameter? blockParameter = null, bool optimize = true) => new CreateAccessListTxExecutor(_blockchainBridge, _blockFinder, _rpcConfig, optimize) diff --git a/src/Nethermind/Nethermind.JsonRpc/Modules/Eth/ExecutorBase.cs b/src/Nethermind/Nethermind.JsonRpc/Modules/Eth/ExecutorBase.cs index 2e04d10b455..1721a52d804 100644 --- a/src/Nethermind/Nethermind.JsonRpc/Modules/Eth/ExecutorBase.cs +++ b/src/Nethermind/Nethermind.JsonRpc/Modules/Eth/ExecutorBase.cs @@ -1,9 +1,11 @@ // SPDX-FileCopyrightText: 2023 Demerzel Solutions Limited // SPDX-License-Identifier: LGPL-3.0-only +using System.Collections.Generic; using System.Threading; using Nethermind.Blockchain.Find; using Nethermind.Core; +using Nethermind.Evm; using Nethermind.Facade; namespace Nethermind.JsonRpc.Modules.Eth; @@ -23,7 +25,8 @@ protected ExecutorBase(IBlockchainBridge blockchainBridge, IBlockFinder blockFin public virtual ResultWrapper Execute( TRequest call, - BlockParameter? blockParameter) + BlockParameter? blockParameter, + Dictionary? stateOverride = null) { SearchResult searchResult = _blockFinder.SearchForHeader(blockParameter); if (searchResult.IsError) return ResultWrapper.Fail(searchResult); @@ -35,12 +38,12 @@ public virtual ResultWrapper Execute( using CancellationTokenSource cancellationTokenSource = new(_rpcConfig.Timeout); TProcessing? toProcess = Prepare(call); - return Execute(header.Clone(), toProcess, cancellationTokenSource.Token); + return Execute(header.Clone(), toProcess, stateOverride, cancellationTokenSource.Token); } protected abstract TProcessing Prepare(TRequest call); - protected abstract ResultWrapper Execute(BlockHeader header, TProcessing tx, CancellationToken token); + protected abstract ResultWrapper Execute(BlockHeader header, TProcessing tx, Dictionary? stateOverride, CancellationToken token); protected ResultWrapper? TryGetInputError(CallOutput result) { diff --git a/src/Nethermind/Nethermind.JsonRpc/Modules/Eth/IEthRpcModule.cs b/src/Nethermind/Nethermind.JsonRpc/Modules/Eth/IEthRpcModule.cs index 3ded7a3a957..4e7f22a9665 100644 --- a/src/Nethermind/Nethermind.JsonRpc/Modules/Eth/IEthRpcModule.cs +++ b/src/Nethermind/Nethermind.JsonRpc/Modules/Eth/IEthRpcModule.cs @@ -6,6 +6,7 @@ using Nethermind.Blockchain.Find; using Nethermind.Core; using Nethermind.Core.Crypto; +using Nethermind.Evm; using Nethermind.Facade.Eth; using Nethermind.Facade.Eth.RpcTransaction; using Nethermind.Facade.Filters; @@ -150,7 +151,7 @@ public interface IEthRpcModule : IRpcModule Description = "Executes a tx call (does not create a transaction)", IsSharable = false, ExampleResponse = "0x")] - ResultWrapper eth_call([JsonRpcParameter(ExampleValue = "[{\"from\":\"0x0001020304050607080910111213141516171819\",\"gasPrice\":\"0x100000\", \"data\": \"0x70a082310000000000000000000000006c1f09f6271fbe133db38db9c9280307f5d22160\", \"to\": \"0x0d8775f648430679a709e98d2b0cb6250d2887ef\"}]")] TransactionForRpc transactionCall, BlockParameter? blockParameter = null); + ResultWrapper eth_call([JsonRpcParameter(ExampleValue = "[{\"from\":\"0x0001020304050607080910111213141516171819\",\"gasPrice\":\"0x100000\", \"data\": \"0x70a082310000000000000000000000006c1f09f6271fbe133db38db9c9280307f5d22160\", \"to\": \"0x0d8775f648430679a709e98d2b0cb6250d2887ef\"}]")] TransactionForRpc transactionCall, BlockParameter? blockParameter = null, Dictionary? stateOverride = null); [JsonRpcMethod(IsImplemented = true, Description = "Executes a simulation across multiple blocks (does not create a transaction or block)", @@ -163,7 +164,7 @@ ResultWrapper> eth_simulateV1([JsonRpcParamet Description = "Executes a tx call and returns gas used (does not create a transaction)", IsSharable = false, ExampleResponse = "0x")] - ResultWrapper eth_estimateGas([JsonRpcParameter(ExampleValue = "[\"{\"from\": \"0x0001020304050607080910111213141516171819\", \"gasPrice\": \"1048576\", \"to\": \"0x0d8775f648430679a709e98d2b0cb6250d2887ef\"}\"]")] TransactionForRpc transactionCall, BlockParameter? blockParameter = null); + ResultWrapper eth_estimateGas([JsonRpcParameter(ExampleValue = "[\"{\"from\": \"0x0001020304050607080910111213141516171819\", \"gasPrice\": \"1048576\", \"to\": \"0x0d8775f648430679a709e98d2b0cb6250d2887ef\"}\"]")] TransactionForRpc transactionCall, BlockParameter? blockParameter = null, Dictionary? stateOverride = null); [JsonRpcMethod(IsImplemented = true, Description = "Creates an [EIP2930](https://eips.ethereum.org/EIPS/eip-2930) type AccessList for the given transaction", diff --git a/src/Nethermind/Nethermind.JsonRpc/Modules/Eth/SimulateTxExecutor.cs b/src/Nethermind/Nethermind.JsonRpc/Modules/Eth/SimulateTxExecutor.cs index d87ae39bd1f..48db6e094e4 100644 --- a/src/Nethermind/Nethermind.JsonRpc/Modules/Eth/SimulateTxExecutor.cs +++ b/src/Nethermind/Nethermind.JsonRpc/Modules/Eth/SimulateTxExecutor.cs @@ -8,6 +8,7 @@ using Nethermind.Blockchain.Find; using Nethermind.Config; using Nethermind.Core; +using Nethermind.Evm; using Nethermind.Facade; using Nethermind.Facade.Eth.RpcTransaction; using Nethermind.Facade.Proxy.Models.Simulate; @@ -93,7 +94,8 @@ private static TransactionForRpc UpdateTxType(TransactionForRpc rpcTransaction) public override ResultWrapper> Execute( SimulatePayload call, - BlockParameter? blockParameter) + BlockParameter? blockParameter, + Dictionary? stateOverride = null) { if (call.BlockStateCalls is null) return ResultWrapper>.Fail("Must contain BlockStateCalls", ErrorCodes.InvalidParams); @@ -207,11 +209,11 @@ .. call.BlockStateCalls.Select(b => (long)(b.BlockOverrides?.Number ?? ulong.Min using CancellationTokenSource cancellationTokenSource = new(_rpcConfig.Timeout); //TODO remove! SimulatePayload toProcess = Prepare(call); - return Execute(header.Clone(), toProcess, cancellationTokenSource.Token); + return Execute(header.Clone(), toProcess, stateOverride, cancellationTokenSource.Token); } protected override ResultWrapper> Execute(BlockHeader header, - SimulatePayload tx, CancellationToken token) + SimulatePayload tx, Dictionary? stateOverride, CancellationToken token) { SimulateOutput results = _blockchainBridge.Simulate(header, tx, token); diff --git a/src/Nethermind/Nethermind.JsonRpc/Modules/Trace/ITraceRpcModule.cs b/src/Nethermind/Nethermind.JsonRpc/Modules/Trace/ITraceRpcModule.cs index d9a78740241..ef339ab2612 100644 --- a/src/Nethermind/Nethermind.JsonRpc/Modules/Trace/ITraceRpcModule.cs +++ b/src/Nethermind/Nethermind.JsonRpc/Modules/Trace/ITraceRpcModule.cs @@ -3,7 +3,9 @@ using System.Collections.Generic; using Nethermind.Blockchain.Find; +using Nethermind.Core; using Nethermind.Core.Crypto; +using Nethermind.Evm; using Nethermind.Facade.Eth.RpcTransaction; using Nethermind.JsonRpc.Data; @@ -13,7 +15,9 @@ namespace Nethermind.JsonRpc.Modules.Trace public interface ITraceRpcModule : IRpcModule { [JsonRpcMethod(Description = "", IsImplemented = true, IsSharable = false)] - ResultWrapper trace_call(TransactionForRpc call, [JsonRpcParameter(Description = "Possible values : [\"VmTrace\", \"StateDiff\", \"Trace\", \"Rewards\", \"All\"]")] string[] traceTypes, BlockParameter? blockParameter = null); + ResultWrapper trace_call(TransactionForRpc call, + [JsonRpcParameter(Description = "Possible values : [\"VmTrace\", \"StateDiff\", \"Trace\", \"Rewards\", \"All\"]")] string[] traceTypes, + BlockParameter? blockParameter = null, Dictionary? stateOverride = null); [JsonRpcMethod(Description = "Performs multiple traces on top of a block", IsImplemented = true, diff --git a/src/Nethermind/Nethermind.JsonRpc/Modules/Trace/TraceModuleFactory.cs b/src/Nethermind/Nethermind.JsonRpc/Modules/Trace/TraceModuleFactory.cs index fce0c4f0e1d..11a2ce1ba68 100644 --- a/src/Nethermind/Nethermind.JsonRpc/Modules/Trace/TraceModuleFactory.cs +++ b/src/Nethermind/Nethermind.JsonRpc/Modules/Trace/TraceModuleFactory.cs @@ -11,14 +11,17 @@ using Nethermind.Consensus.Validators; using Nethermind.Core.Crypto; using Nethermind.Core.Specs; +using Nethermind.Db; using Nethermind.Evm.TransactionProcessing; using Nethermind.Logging; using Nethermind.State; +using Nethermind.Trie.Pruning; namespace Nethermind.JsonRpc.Modules.Trace; public class TraceModuleFactory( - IWorldStateManager worldStateManager, + IReadOnlyTrieStore trieStore, + IDbProvider dbProvider, IBlockTree blockTree, IJsonRpcConfig jsonRpcConfig, IBlockPreprocessorStep recoveryStep, @@ -28,7 +31,7 @@ public class TraceModuleFactory( IPoSSwitcher poSSwitcher, ILogManager logManager) : ModuleFactoryBase { - protected readonly IWorldStateManager _worldStateManager = worldStateManager; + protected readonly IReadOnlyTrieStore _trieStore = trieStore; protected readonly IReadOnlyBlockTree _blockTree = blockTree.AsReadOnly(); protected readonly IJsonRpcConfig _jsonRpcConfig = jsonRpcConfig ?? throw new ArgumentNullException(nameof(jsonRpcConfig)); protected readonly IReceiptStorage _receiptStorage = receiptFinder ?? throw new ArgumentNullException(nameof(receiptFinder)); @@ -38,9 +41,9 @@ public class TraceModuleFactory( protected readonly IRewardCalculatorSource _rewardCalculatorSource = rewardCalculatorSource ?? throw new ArgumentNullException(nameof(rewardCalculatorSource)); protected readonly IPoSSwitcher _poSSwitcher = poSSwitcher ?? throw new ArgumentNullException(nameof(poSSwitcher)); - protected virtual ReadOnlyTxProcessingEnv CreateTxProcessingEnv() => new(_worldStateManager, _blockTree, _specProvider, _logManager); + protected virtual OverridableTxProcessingEnv CreateTxProcessingEnv(OverridableWorldStateManager worldStateManager) => new(worldStateManager, _blockTree, _specProvider, _logManager); - protected virtual ReadOnlyChainProcessingEnv CreateChainProcessingEnv(IBlockProcessor.IBlockTransactionsExecutor transactionsExecutor, IReadOnlyTxProcessingScope scope, IRewardCalculator rewardCalculator) => new( + protected virtual ReadOnlyChainProcessingEnv CreateChainProcessingEnv(OverridableWorldStateManager worldStateManager, IBlockProcessor.IBlockTransactionsExecutor transactionsExecutor, IReadOnlyTxProcessingScope scope, IRewardCalculator rewardCalculator) => new( scope, Always.Valid, _recoveryStep, @@ -48,13 +51,14 @@ public class TraceModuleFactory( _receiptStorage, _specProvider, _blockTree, - _worldStateManager.GlobalStateReader, + worldStateManager.GlobalStateReader, _logManager, transactionsExecutor); public override ITraceRpcModule Create() { - ReadOnlyTxProcessingEnv txProcessingEnv = CreateTxProcessingEnv(); + OverridableWorldStateManager worldStateManager = new(dbProvider, _trieStore, logManager); + OverridableTxProcessingEnv txProcessingEnv = CreateTxProcessingEnv(worldStateManager); IReadOnlyTxProcessingScope scope = txProcessingEnv.Build(Keccak.EmptyTreeHash); IRewardCalculator rewardCalculator = @@ -64,12 +68,12 @@ public override ITraceRpcModule Create() RpcBlockTransactionsExecutor rpcBlockTransactionsExecutor = new(scope.TransactionProcessor, scope.WorldState); BlockProcessor.BlockValidationTransactionsExecutor executeBlockTransactionsExecutor = new(scope.TransactionProcessor, scope.WorldState); - ReadOnlyChainProcessingEnv traceProcessingEnv = CreateChainProcessingEnv(rpcBlockTransactionsExecutor, scope, rewardCalculator); - ReadOnlyChainProcessingEnv executeProcessingEnv = CreateChainProcessingEnv(executeBlockTransactionsExecutor, scope, rewardCalculator); + ReadOnlyChainProcessingEnv traceProcessingEnv = CreateChainProcessingEnv(worldStateManager, rpcBlockTransactionsExecutor, scope, rewardCalculator); + ReadOnlyChainProcessingEnv executeProcessingEnv = CreateChainProcessingEnv(worldStateManager, executeBlockTransactionsExecutor, scope, rewardCalculator); - Tracer tracer = new(scope.WorldState, traceProcessingEnv.ChainProcessor, executeProcessingEnv.ChainProcessor); + Tracer tracer = new(scope.WorldState, traceProcessingEnv.ChainProcessor, executeProcessingEnv.ChainProcessor, + traceOptions: ProcessingOptions.TraceTransactions); - return new TraceRpcModule(_receiptStorage, tracer, _blockTree, _jsonRpcConfig, txProcessingEnv.StateReader); + return new TraceRpcModule(_receiptStorage, tracer, _blockTree, _jsonRpcConfig, txProcessingEnv); } - } diff --git a/src/Nethermind/Nethermind.JsonRpc/Modules/Trace/TraceRpcModule.cs b/src/Nethermind/Nethermind.JsonRpc/Modules/Trace/TraceRpcModule.cs index 622ba612700..05df3a6ff8d 100644 --- a/src/Nethermind/Nethermind.JsonRpc/Modules/Trace/TraceRpcModule.cs +++ b/src/Nethermind/Nethermind.JsonRpc/Modules/Trace/TraceRpcModule.cs @@ -8,12 +8,14 @@ using FastEnumUtility; using Nethermind.Blockchain.Find; using Nethermind.Blockchain.Receipts; +using Nethermind.Consensus.Processing; using Nethermind.Consensus.Tracing; using Nethermind.Core; using Nethermind.Core.Crypto; +using Nethermind.Evm; using Nethermind.Evm.Tracing; using Nethermind.Evm.Tracing.ParityStyle; -using Nethermind.Facade; +using Nethermind.Evm.TransactionProcessing; using Nethermind.Facade.Eth.RpcTransaction; using Nethermind.Int256; using Nethermind.JsonRpc.Data; @@ -39,31 +41,36 @@ public class TraceRpcModule : ITraceRpcModule private readonly IJsonRpcConfig _jsonRpcConfig; private readonly TimeSpan _cancellationTokenTimeout; private readonly IStateReader _stateReader; + private readonly IOverridableTxProcessorSource _env; - public TraceRpcModule(IReceiptFinder? receiptFinder, ITracer? tracer, IBlockFinder? blockFinder, IJsonRpcConfig? jsonRpcConfig, IStateReader stateReader) + public TraceRpcModule(IReceiptFinder? receiptFinder, ITracer? tracer, IBlockFinder? blockFinder, IJsonRpcConfig? jsonRpcConfig, IStateReader? stateReader, IOverridableTxProcessorSource? env) { _receiptFinder = receiptFinder ?? throw new ArgumentNullException(nameof(receiptFinder)); _tracer = tracer ?? throw new ArgumentNullException(nameof(tracer)); _blockFinder = blockFinder ?? throw new ArgumentNullException(nameof(blockFinder)); _jsonRpcConfig = jsonRpcConfig ?? throw new ArgumentNullException(nameof(jsonRpcConfig)); _stateReader = stateReader ?? throw new ArgumentNullException(nameof(stateReader)); + _env = env ?? throw new ArgumentNullException(nameof(env)); _cancellationTokenTimeout = TimeSpan.FromMilliseconds(_jsonRpcConfig.Timeout); } + public TraceRpcModule(IReceiptFinder? receiptFinder, ITracer? tracer, IBlockFinder? blockFinder, IJsonRpcConfig? jsonRpcConfig, OverridableTxProcessingEnv? env) + : this(receiptFinder, tracer, blockFinder, jsonRpcConfig, env?.StateReader, env) { } + public static ParityTraceTypes GetParityTypes(string[] types) => types.Select(s => FastEnum.Parse(s, true)).Aggregate((t1, t2) => t1 | t2); /// /// Traces one transaction. Doesn't charge fees. /// - public ResultWrapper trace_call(TransactionForRpc call, string[] traceTypes, BlockParameter? blockParameter = null) + public ResultWrapper trace_call(TransactionForRpc call, string[] traceTypes, BlockParameter? blockParameter = null, Dictionary? stateOverride = null) { blockParameter ??= BlockParameter.Latest; call.EnsureDefaults(_jsonRpcConfig.GasCap); Transaction tx = call.ToTransaction(); - return TraceTx(tx, traceTypes, blockParameter); + return TraceTx(tx, traceTypes, blockParameter, stateOverride); } /// @@ -73,15 +80,16 @@ public ResultWrapper> trace_callMany(Transa { blockParameter ??= BlockParameter.Latest; - SearchResult headerSearch = SearchBlockHeaderForTraceCall(blockParameter); + SearchResult headerSearch = _blockFinder.SearchForHeader(blockParameter); if (headerSearch.IsError) { return ResultWrapper>.Fail(headerSearch); } - if (!_stateReader.HasStateForBlock(headerSearch.Object)) + BlockHeader header = headerSearch.Object!; + if (!_stateReader.HasStateForBlock(header)) { - return GetStateFailureResult>(headerSearch.Object); + return GetStateFailureResult>(header); } Dictionary traceTypeByTransaction = new(calls.Length); @@ -96,8 +104,8 @@ public ResultWrapper> trace_callMany(Transa traceTypeByTransaction.Add(tx.Hash, traceTypes); } - Block block = new(headerSearch.Object!, txs, Enumerable.Empty()); - IReadOnlyCollection? traces = TraceBlock(block, new ParityLikeBlockTracer(traceTypeByTransaction)); + Block block = new(header, txs, Enumerable.Empty()); + IReadOnlyCollection? traces = TraceBlock(block, new(traceTypeByTransaction)); return ResultWrapper>.Success(traces.Select(t => new ParityTxTraceFromReplay(t))); } @@ -110,46 +118,19 @@ public ResultWrapper trace_rawTransaction(byte[] data, return TraceTx(tx, traceTypes, BlockParameter.Latest); } - private SearchResult SearchBlockHeaderForTraceCall(BlockParameter blockParameter) + private ResultWrapper TraceTx(Transaction tx, string[] traceTypes, BlockParameter blockParameter, + Dictionary? stateOverride = null) { SearchResult headerSearch = _blockFinder.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); - } - - private ResultWrapper TraceTx(Transaction tx, string[] traceTypes, BlockParameter blockParameter) - { - SearchResult headerSearch = SearchBlockHeaderForTraceCall(blockParameter); - if (headerSearch.IsError) { return ResultWrapper.Fail(headerSearch); } - Block block = new(headerSearch.Object!, new[] { tx }, Enumerable.Empty()); + BlockHeader header = headerSearch.Object!.Clone(); + Block block = new(header, [tx], []); + + using IOverridableTxProcessingScope? scope = stateOverride != null ? _env.BuildAndOverride(header, stateOverride) : null; ParityTraceTypes traceTypes1 = GetParityTypes(traceTypes); IReadOnlyCollection result = TraceBlock(block, new(traceTypes1)); diff --git a/src/Nethermind/Nethermind.Optimism/OptimismOverridableTxProcessingEnv.cs b/src/Nethermind/Nethermind.Optimism/OptimismOverridableTxProcessingEnv.cs new file mode 100644 index 00000000000..9e06be1ddcd --- /dev/null +++ b/src/Nethermind/Nethermind.Optimism/OptimismOverridableTxProcessingEnv.cs @@ -0,0 +1,33 @@ +// SPDX-FileCopyrightText: 2024 Demerzel Solutions Limited +// SPDX-License-Identifier: LGPL-3.0-only + +using System; +using Nethermind.Blockchain; +using Nethermind.Consensus.Processing; +using Nethermind.Core.Specs; +using Nethermind.Evm; +using Nethermind.Evm.TransactionProcessing; +using Nethermind.Logging; +using Nethermind.State; + +namespace Nethermind.Optimism; + +public class OptimismOverridableTxProcessingEnv( + OverridableWorldStateManager worldStateManager, + IReadOnlyBlockTree readOnlyBlockTree, + ISpecProvider specProvider, + ILogManager logManager, + IL1CostHelper l1CostHelper, + IOptimismSpecHelper opSpecHelper, + IWorldState? worldStateToWarmUp = null) + : OverridableTxProcessingEnv(worldStateManager, readOnlyBlockTree, specProvider, logManager, worldStateToWarmUp) +{ + protected override ITransactionProcessor CreateTransactionProcessor() + { + ArgumentNullException.ThrowIfNull(LogManager); + + 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/Rpc/OptimismTraceModuleFactory.cs b/src/Nethermind/Nethermind.Optimism/Rpc/OptimismTraceModuleFactory.cs index 8a1b5da4d21..bf5b837c883 100644 --- a/src/Nethermind/Nethermind.Optimism/Rpc/OptimismTraceModuleFactory.cs +++ b/src/Nethermind/Nethermind.Optimism/Rpc/OptimismTraceModuleFactory.cs @@ -9,16 +9,19 @@ using Nethermind.Consensus.Validators; using Nethermind.Consensus.Withdrawals; using Nethermind.Core.Specs; +using Nethermind.Db; using Nethermind.Evm.TransactionProcessing; using Nethermind.JsonRpc; using Nethermind.JsonRpc.Modules.Trace; using Nethermind.Logging; using Nethermind.State; +using Nethermind.Trie.Pruning; namespace Nethermind.Optimism.Rpc; public class OptimismTraceModuleFactory( - IWorldStateManager worldStateManager, + IReadOnlyTrieStore trieStore, + IDbProvider dbProvider, IBlockTree blockTree, IJsonRpcConfig jsonRpcConfig, IBlockPreprocessorStep recoveryStep, @@ -31,7 +34,8 @@ public class OptimismTraceModuleFactory( IOptimismSpecHelper opSpecHelper, Create2DeployerContractRewriter contractRewriter, IWithdrawalProcessor withdrawalProcessor) : TraceModuleFactory( - worldStateManager, + trieStore, + dbProvider, blockTree, jsonRpcConfig, recoveryStep, @@ -41,10 +45,10 @@ public class OptimismTraceModuleFactory( poSSwitcher, logManager) { - protected override ReadOnlyTxProcessingEnv CreateTxProcessingEnv() => - new OptimismReadOnlyTxProcessingEnv(_worldStateManager, _blockTree, _specProvider, _logManager, l1CostHelper, opSpecHelper); + protected override OverridableTxProcessingEnv CreateTxProcessingEnv(OverridableWorldStateManager worldStateManager) => + new OptimismOverridableTxProcessingEnv(worldStateManager, _blockTree, _specProvider, _logManager, l1CostHelper, opSpecHelper); - protected override ReadOnlyChainProcessingEnv CreateChainProcessingEnv(IBlockProcessor.IBlockTransactionsExecutor transactionsExecutor, IReadOnlyTxProcessingScope scope, IRewardCalculator rewardCalculator) => new OptimismReadOnlyChainProcessingEnv( + protected override ReadOnlyChainProcessingEnv CreateChainProcessingEnv(OverridableWorldStateManager worldStateManager, IBlockProcessor.IBlockTransactionsExecutor transactionsExecutor, IReadOnlyTxProcessingScope scope, IRewardCalculator rewardCalculator) => new OptimismReadOnlyChainProcessingEnv( scope, Always.Valid, _recoveryStep, @@ -52,7 +56,7 @@ protected override ReadOnlyTxProcessingEnv CreateTxProcessingEnv() => _receiptStorage, _specProvider, _blockTree, - _worldStateManager.GlobalStateReader, + worldStateManager.GlobalStateReader, _logManager, opSpecHelper, contractRewriter, diff --git a/src/Nethermind/Nethermind.Optimism/Rpc/RegisterOptimismRpcModules.cs b/src/Nethermind/Nethermind.Optimism/Rpc/RegisterOptimismRpcModules.cs index a53257f1ec3..3f3072485f4 100644 --- a/src/Nethermind/Nethermind.Optimism/Rpc/RegisterOptimismRpcModules.cs +++ b/src/Nethermind/Nethermind.Optimism/Rpc/RegisterOptimismRpcModules.cs @@ -93,6 +93,7 @@ protected override void RegisterEthRpcModule(IRpcModuleProvider rpcModuleProvide protected override void RegisterTraceRpcModule(IRpcModuleProvider rpcModuleProvider) { StepDependencyException.ThrowIfNull(_api.WorldStateManager); + StepDependencyException.ThrowIfNull(_api.DbProvider); StepDependencyException.ThrowIfNull(_api.BlockTree); StepDependencyException.ThrowIfNull(_api.ReceiptStorage); StepDependencyException.ThrowIfNull(_api.RewardCalculatorSource); @@ -102,7 +103,8 @@ protected override void RegisterTraceRpcModule(IRpcModuleProvider rpcModuleProvi StepDependencyException.ThrowIfNull(_api.SpecHelper); OptimismTraceModuleFactory traceModuleFactory = new( - _api.WorldStateManager, + _api.WorldStateManager.TrieStore, + _api.DbProvider, _api.BlockTree, _jsonRpcConfig, _api.BlockPreprocessor, diff --git a/src/Nethermind/Nethermind.State/OverlayWorldStateManager.cs b/src/Nethermind/Nethermind.State/OverlayWorldStateManager.cs index a4dda8fdd65..7b0fc9a6da7 100644 --- a/src/Nethermind/Nethermind.State/OverlayWorldStateManager.cs +++ b/src/Nethermind/Nethermind.State/OverlayWorldStateManager.cs @@ -29,17 +29,11 @@ public class OverlayWorldStateManager( 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); + ITrieStore trieStore = (forWarmup as IPreBlockCaches)?.Caches is { } preBlockCaches + ? new PreCachedTrieStore(overlayTrieStore, preBlockCaches.RlpCache) + : overlayTrieStore; + + return new WorldState(trieStore, _codeDb, logManager); } public event EventHandler? ReorgBoundaryReached diff --git a/src/Nethermind/Nethermind.State/OverridableWorldState.cs b/src/Nethermind/Nethermind.State/OverridableWorldState.cs new file mode 100644 index 00000000000..70a1bfc0afb --- /dev/null +++ b/src/Nethermind/Nethermind.State/OverridableWorldState.cs @@ -0,0 +1,27 @@ +// SPDX-FileCopyrightText: 2024 Demerzel Solutions Limited +// SPDX-License-Identifier: LGPL-3.0-only + +using Nethermind.Db; +using Nethermind.Logging; +using Nethermind.Trie.Pruning; + +namespace Nethermind.State; + +public class OverridableWorldState( + OverlayTrieStore trieStore, + IReadOnlyDbProvider dbProvider, + ILogManager? logManager, + PreBlockCaches? preBlockCaches = null, + bool populatePreBlockCache = true) + : WorldState(trieStore, dbProvider.GetDb(DbNames.Code), logManager, preBlockCaches, populatePreBlockCache) +{ + + /// + /// Resets changes applied via + /// + public void ResetOverrides() + { + trieStore.ResetOverrides(); + dbProvider.ClearTempChanges(); + } +} diff --git a/src/Nethermind/Nethermind.State/OverridableWorldStateManager.cs b/src/Nethermind/Nethermind.State/OverridableWorldStateManager.cs new file mode 100644 index 00000000000..82eb253ae56 --- /dev/null +++ b/src/Nethermind/Nethermind.State/OverridableWorldStateManager.cs @@ -0,0 +1,48 @@ +// SPDX-FileCopyrightText: 2024 Demerzel Solutions Limited +// SPDX-License-Identifier: LGPL-3.0-only + +using System; +using Nethermind.Db; +using Nethermind.Logging; +using Nethermind.Trie.Pruning; + +namespace Nethermind.State; + +public class OverridableWorldStateManager : IWorldStateManager +{ + private readonly ReadOnlyDbProvider _readOnlyDbProvider; + private readonly StateReader _reader; + private readonly WorldState _state; + + private readonly OverlayTrieStore _overlayTrieStore; + private readonly ILogManager? _logManager; + + public OverridableWorldStateManager(IDbProvider dbProvider, IReadOnlyTrieStore trieStore, ILogManager? logManager) + { + dbProvider = _readOnlyDbProvider = new(dbProvider, true); + OverlayTrieStore overlayTrieStore = new(dbProvider.StateDb, trieStore, logManager); + + _logManager = logManager; + _reader = new(overlayTrieStore, dbProvider.GetDb(DbNames.Code), logManager); + _state = new(overlayTrieStore, dbProvider.GetDb(DbNames.Code), logManager); + _overlayTrieStore = overlayTrieStore; + } + + public IWorldState GlobalWorldState => _state; + public IStateReader GlobalStateReader => _reader; + public IReadOnlyTrieStore TrieStore => _overlayTrieStore.AsReadOnly(); + + public IWorldState CreateResettableWorldState(IWorldState? forWarmup = null) + { + if (forWarmup is not null) + throw new NotSupportedException("Overridable world state with warm up is not supported."); + + return new OverridableWorldState(_overlayTrieStore, _readOnlyDbProvider, _logManager); + } + + public event EventHandler? ReorgBoundaryReached + { + add => _overlayTrieStore.ReorgBoundaryReached += value; + remove => _overlayTrieStore.ReorgBoundaryReached -= value; + } +} diff --git a/src/Nethermind/Nethermind.Trie/Pruning/OverlayTrieStore.cs b/src/Nethermind/Nethermind.Trie/Pruning/OverlayTrieStore.cs index c84a6538539..6108165f1a2 100644 --- a/src/Nethermind/Nethermind.Trie/Pruning/OverlayTrieStore.cs +++ b/src/Nethermind/Nethermind.Trie/Pruning/OverlayTrieStore.cs @@ -23,4 +23,11 @@ public override TrieNode FindCachedOrUnknown(Hash256? address, in TreePath path, 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); + + protected override void VerifyNewCommitSet(long blockNumber) + { + // Skip checks, as override can be applied using the same block number or without a state root + } + + public void ResetOverrides() => ClearCache(); } diff --git a/src/Nethermind/Nethermind.Trie/Pruning/TrieStore.cs b/src/Nethermind/Nethermind.Trie/Pruning/TrieStore.cs index a5fdf3407e7..f3781550d06 100644 --- a/src/Nethermind/Nethermind.Trie/Pruning/TrieStore.cs +++ b/src/Nethermind/Nethermind.Trie/Pruning/TrieStore.cs @@ -643,9 +643,6 @@ private void PruneCache(bool skipRecalculateMemory = false) if (_logger.IsDebug) _logger.Debug($"Finished pruning nodes in {(long)Stopwatch.GetElapsedTime(start).TotalMilliseconds}ms {MemoryUsedByDirtyCache / 1.MB()} MB, last persisted block: {LastPersistedBlockNumber} current: {LatestCommittedBlockNumber}."); } - /// - /// This method is here to support testing. - /// public void ClearCache() { foreach (TrieStoreDirtyNodesCache dirtyNode in _dirtyNodes) @@ -706,10 +703,8 @@ private static ConcurrentQueue CreateQueueAtomic(ref ConcurrentQ return prior ?? instance; } - private BlockCommitSet CreateCommitSet(long blockNumber) + protected virtual void VerifyNewCommitSet(long blockNumber) { - if (_logger.IsDebug) _logger.Debug($"Beginning new {nameof(BlockCommitSet)} - {blockNumber}"); - if (_lastCommitSet is not null) { Debug.Assert(_lastCommitSet.IsSealed, "Not sealed when beginning new block"); @@ -719,6 +714,13 @@ private BlockCommitSet CreateCommitSet(long blockNumber) if (_logger.IsInfo) _logger.Info($"Non consecutive block commit. This is likely a reorg. Last block commit: {_lastCommitSet.BlockNumber}. New block commit: {blockNumber}."); } } + } + + private BlockCommitSet CreateCommitSet(long blockNumber) + { + if (_logger.IsDebug) _logger.Debug($"Beginning new {nameof(BlockCommitSet)} - {blockNumber}"); + + VerifyNewCommitSet(blockNumber); BlockCommitSet commitSet = new(blockNumber); CommitSetQueue.Enqueue(commitSet);