Skip to content

Commit

Permalink
Refactor Simulate in Blockchain Bridge to be generic, enabling suppor…
Browse files Browse the repository at this point in the history
…t for SimulateBlockTracer, GethLikeBlockMemoryTracer, and ParityLikeBlockTracer while ensuring flexibility in SimulateBlockResult<T> and updating test cases accordingly.
  • Loading branch information
Deeptanshu Sankhwar committed Feb 1, 2025
1 parent 41fa470 commit f4269ec
Show file tree
Hide file tree
Showing 15 changed files with 141 additions and 49 deletions.
82 changes: 75 additions & 7 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<T> Simulate<T>(
BlockHeader header,
SimulatePayload<TransactionWithSourceDetails> payload,
CancellationToken cancellationToken,
ITracerFactory<T> tracerFactory)
where T : class
{
SimulateBlockTracer simulateOutputTracer = new(payload.TraceTransfers, payload.ReturnFullTransactionObjects, _specProvider);
BlockReceiptsTracer tracer = new();
tracer.SetOtherTracer(simulateOutputTracer);
SimulateOutput result = new();
var tracer = tracerFactory.CreateTracer(payload.TraceTransfers, payload.ReturnFullTransactionObjects, _specProvider);
BlockReceiptsTracer receiptsTracer = new();
receiptsTracer.SetOtherTracer(tracer);

SimulateOutput<T> result = new();

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

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

result.Items = simulateOutputTracer.Results;
if (tracer is SimulateBlockTracer simulateTracer)
{
result.Items = simulateTracer.Results as IReadOnlyList<SimulateBlockResult<T>>;
}
else
{
throw new InvalidOperationException($"Tracer type {typeof(T).Name} does not support 'Results'");
}

return result;
}


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 +461,44 @@ public IEnumerable<FilterLog> FindLogs(LogFilter filter, CancellationToken cance
{
return _logFinder.FindLogs(filter, cancellationToken);
}

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

public class GethLikeBlockMemoryTracerFactory : ITracerFactory<GethLikeTxTrace>
{
private readonly GethTraceOptions _options;

public GethLikeBlockMemoryTracerFactory(GethTraceOptions options)
{
_options = options;
}

public IBlockTracer<GethLikeTxTrace> CreateTracer(bool traceTransfers, bool returnFullTransactionObjects, ISpecProvider specProvider)
{
return new GethLikeBlockMemoryTracer(_options);
}
}

public class ParityLikeBlockTracerFactory : ITracerFactory<ParityLikeTxTrace>
{
private readonly ParityTraceTypes _types;

public ParityLikeBlockTracerFactory(ParityTraceTypes types)
{
_types = types;
}

public IBlockTracer<ParityLikeTxTrace> CreateTracer(bool traceTransfers, bool returnFullTransactionObjects, ISpecProvider specProvider)
{
return new ParityLikeBlockTracer(_types);
}
}

}
}
4 changes: 3 additions & 1 deletion src/Nethermind/Nethermind.Facade/IBlockchainBridge.cs
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@
using Nethermind.Core;
using Nethermind.Core.Crypto;
using Nethermind.Evm;
using Nethermind.Facade;
using Nethermind.Facade.Tracing;
using Nethermind.Facade.Filters;
using Nethermind.Facade.Find;
using Nethermind.Facade.Simulate;
Expand All @@ -28,7 +30,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<T> Simulate<T>(BlockHeader header, SimulatePayload<TransactionWithSourceDetails> payload, CancellationToken cancellationToken, ITracerFactory<T> tracerFactory) where T : class;
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
Expand Up @@ -8,8 +8,8 @@

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 List<T> Calls { get; set; } = new();
}
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ public class SimulateBlockTracer(bool isTracingLogs, bool includeFullTxData, ISp
private readonly List<SimulateTxMutatorTracer> _txTracers = new();

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

public override void StartNewBlockTrace(Block block)
{
Expand All @@ -40,7 +40,7 @@ public override ITxTracer StartNewTxTrace(Transaction? tx)

public override void EndBlockTrace()
{
SimulateBlockResult? result = new(_currentBlock, includeFullTxData, spec)
SimulateBlockResult<SimulateCallResult>? result = new(_currentBlock, includeFullTxData, spec)
{
Calls = _txTracers.Select(t => t.TraceResult).ToList(),
};
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -121,7 +121,7 @@ private bool TrySimulate(

private static void CheckMisssingAndSetTracedDefaults(SimulateBlockTracer simulateOutputTracer, Block processedBlock)
{
SimulateBlockResult current = simulateOutputTracer.Results.Last();
SimulateBlockResult<SimulateCallResult> current = simulateOutputTracer.Results.Last();
current.StateRoot = processedBlock.StateRoot ?? Hash256.Zero;
current.ParentBeaconBlockRoot = processedBlock.ParentBeaconBlockRoot ?? Hash256.Zero;
current.TransactionsRoot = processedBlock.Header.TxRoot;
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<SimulateBlockResult<T>> Items { get; set; }
}
10 changes: 10 additions & 0 deletions src/Nethermind/Nethermind.Facade/Tracing/ITracerFactory.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
using Nethermind.Core.Specs;
using Nethermind.Evm.Tracing;

namespace Nethermind.Facade.Tracing
{
public interface ITracerFactory<T>
{
IBlockTracer<T> 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
Original file line number Diff line number Diff line change
Expand Up @@ -76,11 +76,11 @@ public async Task Test_eth_simulate_serialisation()

//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);
IReadOnlyList<SimulateBlockResult> data = result.Data;
ResultWrapper<IReadOnlyList<SimulateBlockResult<SimulateCallResult>>> result = executor.Execute(payload, BlockParameter.Latest);
IReadOnlyList<SimulateBlockResult<SimulateCallResult>> data = result.Data;
Assert.That(data.Count, Is.EqualTo(7));

SimulateBlockResult blockResult = data.Last();
SimulateBlockResult<SimulateCallResult> blockResult = data.Last();
blockResult.Calls.Select(static c => c.Status).Should().BeEquivalentTo(new[] { (ulong)ResultType.Success, (ulong)ResultType.Success });

}
Expand Down Expand Up @@ -150,13 +150,13 @@ public async Task Test_eth_simulate_eth_moved()

//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 =
ResultWrapper<IReadOnlyList<SimulateBlockResult<SimulateCallResult>>> result =
executor.Execute(payload, BlockParameter.Latest);
IReadOnlyList<SimulateBlockResult> data = result.Data;
IReadOnlyList<SimulateBlockResult<SimulateCallResult>> data = result.Data;

Assert.That(data.Count, Is.EqualTo(9));

SimulateBlockResult blockResult = data[0];
SimulateBlockResult<SimulateCallResult> blockResult = data[0];
Assert.That(blockResult.Calls.Count, Is.EqualTo(2));
blockResult = data.Last();
Assert.That(blockResult.Calls.Count, Is.EqualTo(2));
Expand Down Expand Up @@ -235,7 +235,7 @@ public async Task Test_eth_simulate_transactions_forced_fail()
//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 =
ResultWrapper<IReadOnlyList<SimulateBlockResult<SimulateCallResult>>> result =
executor.Execute(payload, BlockParameter.Latest);
Assert.That(result.Result!.Error!.Contains("higher than sender balance"), Is.True);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,7 @@ public async Task TestsimulateHive(string name, string data)
SimulatePayload<TransactionForRpc>? payload = serializer.Deserialize<SimulatePayload<TransactionForRpc>>(data);
TestRpcBlockchain chain = await EthRpcSimulateTestsBase.CreateChain();
Console.WriteLine($"current test: {name}");
ResultWrapper<IReadOnlyList<SimulateBlockResult>> result =
ResultWrapper<IReadOnlyList<SimulateBlockResult<SimulateCallResult>>> result =
chain.EthRpcModule.eth_simulateV1(payload!, BlockParameter.Latest);

Console.WriteLine();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -94,7 +94,12 @@ public async Task Test_eth_simulate_erc()
TraceTransfers = true
};

SimulateOutput result = chain.Bridge.Simulate(chain.BlockFinder.Head?.Header!, payload, CancellationToken.None);
SimulateOutput<SimulateCallResult> result = chain.Bridge.Simulate(
chain.BlockFinder.Head?.Header!,
payload,
CancellationToken.None,
new BlockchainBridge.SimulateBlockTracerFactory()

Check failure on line 101 in src/Nethermind/Nethermind.JsonRpc.Test/Modules/Eth/Simulate/EthSimulateTestsSimplePrecompiles.cs

View workflow job for this annotation

GitHub Actions / Build (release, Nethermind)

The type or namespace name 'BlockchainBridge' could not be found (are you missing a using directive or an assembly reference?)

Check failure on line 101 in src/Nethermind/Nethermind.JsonRpc.Test/Modules/Eth/Simulate/EthSimulateTestsSimplePrecompiles.cs

View workflow job for this annotation

GitHub Actions / Build (release, Nethermind)

The type or namespace name 'BlockchainBridge' could not be found (are you missing a using directive or an assembly reference?)
);

byte[] addressBytes = result.Items[0].Calls[0].ReturnData!.SliceWithZeroPaddingEmptyOnError(12, 20);
Address resultingAddress = new(addressBytes);
Expand Down
8 changes: 6 additions & 2 deletions src/Nethermind/Nethermind.JsonRpc/Modules/Eth/EthRpcModule.cs
Original file line number Diff line number Diff line change
Expand Up @@ -342,9 +342,13 @@ public ResultWrapper<string> eth_call(TransactionForRpc transactionCall, BlockPa
new CallTxExecutor(_blockchainBridge, _blockFinder, _rpcConfig)
.ExecuteTx(transactionCall, blockParameter, stateOverride);

public ResultWrapper<IReadOnlyList<SimulateBlockResult>> eth_simulateV1(SimulatePayload<TransactionForRpc> payload, BlockParameter? blockParameter = null) =>
new SimulateTxExecutor(_blockchainBridge, _blockFinder, _rpcConfig, _secondsPerSlot)
public ResultWrapper<IReadOnlyList<SimulateBlockResult<SimulateCallResult>>> eth_simulateV1(
SimulatePayload<TransactionForRpc> payload, BlockParameter? blockParameter = null)
{
return new SimulateTxExecutor(_blockchainBridge, _blockFinder, _rpcConfig, _secondsPerSlot)
.Execute(payload, blockParameter);
}


public ResultWrapper<UInt256?> eth_estimateGas(TransactionForRpc transactionCall, BlockParameter? blockParameter, Dictionary<Address, AccountOverride>? stateOverride = null) =>
new EstimateGasTxExecutor(_blockchainBridge, _blockFinder, _rpcConfig)
Expand Down
Loading

0 comments on commit f4269ec

Please sign in to comment.