Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Refactor Blockchain Bridge Simulate to Support Multiple Generic Tracers #8148

Draft
wants to merge 6 commits into
base: refactor/simulate-generic-tracers
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@

namespace Nethermind.Core.Resettables;

public class ResettableList<T> : IList<T>, IReadOnlyCollection<T>
public class ResettableList<T> : IList<T>, IReadOnlyList<T>
{
private readonly List<T> _wrapped;
private readonly int _startCapacity;
Expand Down
2 changes: 1 addition & 1 deletion src/Nethermind/Nethermind.Evm/Tracing/BlockTracerBase.cs
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,7 @@ public virtual void EndBlockTrace() { }

protected DisposableResettableList<TTrace> TxTraces { get; }

public IReadOnlyCollection<TTrace> BuildResult() => TxTraces;
public IReadOnlyList<TTrace> BuildResult() => TxTraces;

protected virtual void AddTrace(TTrace trace) => TxTraces.Add(trace);
}
2 changes: 1 addition & 1 deletion src/Nethermind/Nethermind.Evm/Tracing/IBlockTracer.cs
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,6 @@ public interface IBlockTracer

public interface IBlockTracer<out TTrace> : IBlockTracer
{
IReadOnlyCollection<TTrace> BuildResult();
IReadOnlyList<TTrace> BuildResult();
}
}
99 changes: 91 additions & 8 deletions src/Nethermind/Nethermind.Facade/BlockchainBridge.cs
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,8 @@
using Nethermind.Int256;
using Nethermind.Evm;
using Nethermind.Evm.Tracing;
using Nethermind.Evm.Tracing.GethStyle;
using Nethermind.Evm.Tracing.ParityStyle;
using Nethermind.Trie;
using Nethermind.TxPool;
using Block = Nethermind.Core.Block;
Expand All @@ -29,6 +31,7 @@
using Nethermind.Facade.Find;
using Nethermind.Facade.Proxy.Models.Simulate;
using Nethermind.Facade.Simulate;
using Nethermind.Facade.Tracing;
using Transaction = Nethermind.Core.Transaction;

namespace Nethermind.Facade
Expand Down Expand Up @@ -165,15 +168,32 @@ public CallOutput Call(BlockHeader header, Transaction tx, Dictionary<Address, A
};
}

public SimulateOutput Simulate(BlockHeader header, SimulatePayload<TransactionWithSourceDetails> payload, CancellationToken cancellationToken)
public SimulateOutput<TTrace> Simulate<TTracer, TTrace>(
BlockHeader header,
SimulatePayload<TransactionWithSourceDetails> payload,
CancellationToken cancellationToken,
ITracerFactory<TTracer, TTrace> tracerFactory)
where TTracer : class, IBlockTracer<TTrace>
{
SimulateBlockTracer simulateOutputTracer = new(payload.TraceTransfers, payload.ReturnFullTransactionObjects, _specProvider);
BlockReceiptsTracer tracer = new();
tracer.SetOtherTracer(simulateOutputTracer);
SimulateOutput result = new();
TTracer tracer = tracerFactory.CreateTracer(payload.TraceTransfers, payload.ReturnFullTransactionObjects, _specProvider);
BlockReceiptsTracer receiptsTracer = new();
receiptsTracer.SetOtherTracer(tracer);

SimulateOutput<TTrace> result = new();

try
{
if (!_simulateBridgeHelper.TrySimulate(header, payload, simulateOutputTracer, new CancellationBlockTracer(tracer, cancellationToken), out string error))
// if (tracer is not SimulateBlockTracer<TTrace> validatedTracer)
// {
// throw new InvalidOperationException($"Unsupported tracer type: {typeof(TTracer).Name}");
// }

if (!_simulateBridgeHelper.TrySimulate<TTracer>(
header,
payload,
tracer,
new CancellationBlockTracer(receiptsTracer, cancellationToken),
out string error))
{
result.Error = error;
}
Expand All @@ -187,10 +207,10 @@ public SimulateOutput Simulate(BlockHeader header, SimulatePayload<TransactionWi
result.Error = ex.ToString();
}

result.Items = simulateOutputTracer.Results;
return result;
return new SimulateOutput<TTrace> { Items = tracer.BuildResult() };
}


public CallOutput EstimateGas(BlockHeader header, Transaction tx, int errorMargin, Dictionary<Address, AccountOverride>? stateOverride, CancellationToken cancellationToken)
{
using IOverridableTxProcessingScope scope = _processingEnv.BuildAndOverride(header, stateOverride);
Expand Down Expand Up @@ -432,5 +452,68 @@ public IEnumerable<FilterLog> FindLogs(LogFilter filter, CancellationToken cance
{
return _logFinder.FindLogs(filter, cancellationToken);
}

public class SimulateBlockTracerFactory : ITracerFactory<SimulateBlockTracer, SimulateBlockResult<SimulateCallResult>>
{
public SimulateBlockTracer CreateTracer(bool traceTransfers, bool returnFullTransactionObjects, ISpecProvider specProvider)
{
return new SimulateBlockTracer(traceTransfers, returnFullTransactionObjects, specProvider);
}
}

public class GethLikeBlockMemoryTracerFactory(GethTraceOptions options)
: ITracerFactory<TraceSimulateTracer<GethLikeTxTrace>, SimulateBlockResult<GethLikeTxTrace>>
{
public TraceSimulateTracer<GethLikeTxTrace> CreateTracer(bool traceTransfers, bool returnFullTransactionObjects, ISpecProvider specProvider)
{
return new TraceSimulateTracer<GethLikeTxTrace>(new GethLikeBlockMemoryTracer(options), specProvider);
}
}

public class ParityLikeBlockTracerFactory(ParityTraceTypes types) : ITracerFactory<TraceSimulateTracer<ParityLikeTxTrace>, SimulateBlockResult<ParityLikeTxTrace>>
{
public TraceSimulateTracer<ParityLikeTxTrace> CreateTracer(bool traceTransfers, bool returnFullTransactionObjects, ISpecProvider specProvider)
{
return new TraceSimulateTracer<ParityLikeTxTrace>(new ParityLikeBlockTracer(types), specProvider);
}
}

public class TraceSimulateTracer<TTrace>(IBlockTracer<TTrace> inner, ISpecProvider specProvider) : IBlockTracer<SimulateBlockResult<TTrace>>
{
private readonly List<SimulateBlockResult<TTrace>> _results = new();
private Block _currentBlock;

public IReadOnlyList<SimulateBlockResult<TTrace>> BuildResult() => _results;

public bool IsTracingRewards => inner.IsTracingRewards;

public void ReportReward(Address author, string rewardType, UInt256 rewardValue)
{
inner.ReportReward(author, rewardType, rewardValue);
}

public void StartNewBlockTrace(Block block)
{
_currentBlock = block;
inner.StartNewBlockTrace(block);
}

public ITxTracer StartNewTxTrace(Transaction? tx)
{
return inner.StartNewTxTrace(tx);
}

public void EndTxTrace()
{
inner.EndTxTrace();
}

public void EndBlockTrace()
{
inner.EndBlockTrace();
_results.Add(new SimulateBlockResult<TTrace>(_currentBlock, false, specProvider) { Calls = inner.BuildResult() });
}
}

}
}
5 changes: 4 additions & 1 deletion src/Nethermind/Nethermind.Facade/IBlockchainBridge.cs
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,9 @@
using Nethermind.Core;
using Nethermind.Core.Crypto;
using Nethermind.Evm;
using Nethermind.Evm.Tracing;
using Nethermind.Facade;
using Nethermind.Facade.Tracing;
using Nethermind.Facade.Filters;
using Nethermind.Facade.Find;
using Nethermind.Facade.Simulate;
Expand All @@ -28,7 +31,7 @@ public interface IBlockchainBridge : ILogFinder
(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, Dictionary<Address, AccountOverride>? stateOverride = null, CancellationToken cancellationToken = default);
SimulateOutput Simulate(BlockHeader header, SimulatePayload<TransactionWithSourceDetails> payload, CancellationToken cancellationToken);
SimulateOutput<TTrace> Simulate<TTracer, TTrace>(BlockHeader header, SimulatePayload<TransactionWithSourceDetails> payload, CancellationToken cancellationToken, ITracerFactory<TTracer, TTrace> tracerFactory) where TTracer : class, IBlockTracer<TTrace>;
CallOutput EstimateGas(BlockHeader header, Transaction tx, int errorMarginBasisPoints, Dictionary<Address, AccountOverride>? stateOverride = null, CancellationToken cancellationToken = default);

CallOutput CreateAccessList(BlockHeader header, Transaction tx, CancellationToken cancellationToken, bool optimize);
Expand Down
Original file line number Diff line number Diff line change
@@ -1,15 +1,16 @@
// SPDX-FileCopyrightText: 2023 Demerzel Solutions Limited
// SPDX-License-Identifier: LGPL-3.0-only

using System;
using System.Collections.Generic;
using Nethermind.Core;
using Nethermind.Core.Specs;
using Nethermind.Facade.Eth;

namespace Nethermind.Facade.Proxy.Models.Simulate;

public class SimulateBlockResult(Block source, bool includeFullTransactionData, ISpecProvider specProvider)
public class SimulateBlockResult<T>(Block source, bool includeFullTransactionData, ISpecProvider specProvider)
: BlockForRpc(source, includeFullTransactionData, specProvider)
{
public List<SimulateCallResult> Calls { get; set; } = new();
public IReadOnlyList<T> Calls { get; set; } = Array.Empty<T>();
}
13 changes: 6 additions & 7 deletions src/Nethermind/Nethermind.Facade/Simulate/SimulateBlockTracer.cs
Original file line number Diff line number Diff line change
Expand Up @@ -10,12 +10,14 @@

namespace Nethermind.Facade.Simulate;

public class SimulateBlockTracer(bool isTracingLogs, bool includeFullTxData, ISpecProvider spec) : BlockTracer
public class SimulateBlockTracer(bool isTracingLogs, bool includeFullTxData, ISpecProvider spec)
: BlockTracer, IBlockTracer<SimulateBlockResult<SimulateCallResult>>
{
private readonly List<SimulateTxMutatorTracer> _txTracers = new();

private Block _currentBlock = null!;
public List<SimulateBlockResult> Results { get; } = new();
private List<SimulateBlockResult<SimulateCallResult>> Results { get; } = new();

public IReadOnlyList<SimulateBlockResult<SimulateCallResult>> BuildResult() => Results;

public override void StartNewBlockTrace(Block block)
{
Expand All @@ -25,7 +27,6 @@ public override void StartNewBlockTrace(Block block)

public override ITxTracer StartNewTxTrace(Transaction? tx)
{

if (tx?.Hash is not null)
{
ulong txIndex = (ulong)_txTracers.Count;
Expand All @@ -34,17 +35,15 @@ public override ITxTracer StartNewTxTrace(Transaction? tx)
_txTracers.Add(result);
return result;
}

return NullTxTracer.Instance;
}

public override void EndBlockTrace()
{
SimulateBlockResult? result = new(_currentBlock, includeFullTxData, spec)
SimulateBlockResult<SimulateCallResult> result = new(_currentBlock, includeFullTxData, spec)
{
Calls = _txTracers.Select(t => t.TraceResult).ToList(),
};

Results.Add(result);
}
}
44 changes: 20 additions & 24 deletions src/Nethermind/Nethermind.Facade/Simulate/SimulateBridgeHelper.cs
Original file line number Diff line number Diff line change
Expand Up @@ -56,22 +56,25 @@ private void PrepareState(BlockHeader blockHeader,
blockHeader.StateRoot = stateProvider.StateRoot;
}

public bool TrySimulate(
public bool TrySimulate<TTracer>(
BlockHeader parent,
SimulatePayload<TransactionWithSourceDetails> payload,
SimulateBlockTracer simulateOutputTracer,
IBlockTracer tracer,
[NotNullWhen(false)] out string? error) =>
TrySimulate(parent, payload, simulateOutputTracer, tracer, simulateProcessingEnvFactory.Create(payload.Validation), out error);

TTracer tracer,
IBlockTracer blockTracer,
[NotNullWhen(false)] out string? error)
where TTracer : class
{
return TrySimulate(parent, payload, tracer, blockTracer, simulateProcessingEnvFactory.Create(payload.Validation), out error);
}

private bool TrySimulate(
private bool TrySimulate<TTracer>(
BlockHeader parent,
SimulatePayload<TransactionWithSourceDetails> payload,
SimulateBlockTracer simulateOutputTracer,
IBlockTracer tracer,
TTracer tracer,
IBlockTracer blockTracer,
SimulateReadOnlyBlocksProcessingEnv env,
[NotNullWhen(false)] out string? error)
where TTracer : class
{
IBlockTree blockTree = env.BlockTree;
IWorldState stateProvider = env.WorldState;
Expand All @@ -87,7 +90,7 @@ private bool TrySimulate(
{
nonceCache.Clear();

BlockHeader callHeader = GetCallHeader(blockCall, parent, payload.Validation, spec); //currentSpec is still parent spec
BlockHeader callHeader = GetCallHeader(blockCall, parent, payload.Validation, spec);
spec = env.SpecProvider.GetSpec(callHeader);
PrepareState(callHeader, parent, blockCall, env.WorldState, env.CodeInfoRepository, spec);
Transaction[] transactions = CreateTransactions(payload, blockCall, callHeader, stateProvider, nonceCache);
Expand All @@ -106,10 +109,15 @@ private bool TrySimulate(
suggestedBlocks[0] = currentBlock;

IBlockProcessor processor = env.GetProcessor(payload.Validation, spec.IsEip4844Enabled ? blockCall.BlockOverrides?.BlobBaseFee : null);
Block processedBlock = processor.Process(stateProvider.StateRoot, suggestedBlocks, processingFlags, tracer)[0];
Block processedBlock = processor.Process(stateProvider.StateRoot, suggestedBlocks, processingFlags, blockTracer)[0];

FinalizeStateAndBlock(stateProvider, processedBlock, spec, currentBlock, blockTree);
CheckMisssingAndSetTracedDefaults(simulateOutputTracer, processedBlock);

// if (tracer is SimulateBlockTracer simulateBlockTracer)
// {
// CheckMisssingAndSetTracedDefaults(simulateBlockTracer.Results, processedBlock);
// }


parent = processedBlock.Header;
}
Expand All @@ -119,18 +127,6 @@ private bool TrySimulate(
return true;
}

private static void CheckMisssingAndSetTracedDefaults(SimulateBlockTracer simulateOutputTracer, Block processedBlock)
{
SimulateBlockResult current = simulateOutputTracer.Results.Last();
current.StateRoot = processedBlock.StateRoot ?? Hash256.Zero;
current.ParentBeaconBlockRoot = processedBlock.ParentBeaconBlockRoot ?? Hash256.Zero;
current.TransactionsRoot = processedBlock.Header.TxRoot;
current.WithdrawalsRoot = processedBlock.WithdrawalsRoot ?? Keccak.EmptyTreeHash;
current.ExcessBlobGas = processedBlock.ExcessBlobGas ?? 0;
current.Withdrawals = processedBlock.Withdrawals ?? [];
current.Author = null;
}

private static void FinalizeStateAndBlock(IWorldState stateProvider, Block processedBlock, IReleaseSpec currentSpec, Block currentBlock, IBlockTree blockTree)
{
stateProvider.StateRoot = processedBlock.StateRoot!;
Expand Down
4 changes: 2 additions & 2 deletions src/Nethermind/Nethermind.Facade/Simulate/SimulateOutput.cs
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,10 @@

namespace Nethermind.Facade.Simulate;

public class SimulateOutput
public class SimulateOutput<T>
{
public string? Error { get; set; }
public int? ErrorCode { get; set; }

public IReadOnlyList<SimulateBlockResult> Items { get; set; }
public IReadOnlyList<T> Items { get; set; }
}
11 changes: 11 additions & 0 deletions src/Nethermind/Nethermind.Facade/Tracing/ITracerFactory.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
using Nethermind.Consensus.Tracing;
using Nethermind.Core.Specs;
using Nethermind.Evm.Tracing;

namespace Nethermind.Facade.Tracing
{
public interface ITracerFactory<out TTracer, TTrace> where TTracer : IBlockTracer<TTrace>
{
TTracer CreateTracer(bool traceTransfers, bool returnFullTransactionObjects, ISpecProvider specProvider);
}
}
4 changes: 2 additions & 2 deletions src/Nethermind/Nethermind.JsonRpc.Test/JsonRpcServiceTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -99,9 +99,9 @@ public void CanRunEthSimulateV1Empty()
string serializedCall = new EthereumJsonSerializer().Serialize(payload);
IEthRpcModule ethRpcModule = Substitute.For<IEthRpcModule>();
ethRpcModule.eth_simulateV1(payload).ReturnsForAnyArgs(static _ =>
ResultWrapper<IReadOnlyList<SimulateBlockResult>>.Success(Array.Empty<SimulateBlockResult>()));
ResultWrapper<IReadOnlyList<SimulateBlockResult<SimulateCallResult>>>.Success(Array.Empty<SimulateBlockResult<SimulateCallResult>>()));
JsonRpcSuccessResponse? response = TestRequest(ethRpcModule, "eth_simulateV1", serializedCall) as JsonRpcSuccessResponse;
Assert.That(response?.Result, Is.EqualTo(Array.Empty<SimulateBlockResult>()));
Assert.That(response?.Result, Is.EqualTo(Array.Empty<SimulateBlockResult<SimulateCallResult>>()));
}


Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,7 @@ public async Task Test_eth_simulate_create()
//will mock our GetCachedCodeInfo function - it shall be called 3 times if redirect is working, 2 times if not
SimulateTxExecutor executor = new(chain.Bridge, chain.BlockFinder, new JsonRpcConfig(), new BlocksConfig().SecondsPerSlot);

ResultWrapper<IReadOnlyList<SimulateBlockResult>> result = executor.Execute(payload, BlockParameter.Latest);
ResultWrapper<IReadOnlyList<SimulateBlockResult<SimulateCallResult>>> result = executor.Execute(payload, BlockParameter.Latest);

//Check results
byte[]? returnData = result.Data[0].Calls.First().ReturnData;
Expand Down Expand Up @@ -181,7 +181,7 @@ function ecrecover(bytes32 hash, uint8 v, bytes32 r, bytes32 s) public returns(
Debug.Assert(contractAddress is not null, nameof(contractAddress) + " is not null");
Assert.That(chain.State.AccountExists(contractAddress), Is.True);

ResultWrapper<IReadOnlyList<SimulateBlockResult>> result = executor.Execute(payload, BlockParameter.Latest);
ResultWrapper<IReadOnlyList<SimulateBlockResult<SimulateCallResult>>> result = executor.Execute(payload, BlockParameter.Latest);

//Check results
byte[] addressBytes = result.Data[0].Calls[0].ReturnData!.SliceWithZeroPaddingEmptyOnError(12, 20);
Expand Down
Loading