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

Implemented new approach of gas estimation. #3723

Merged
merged 10 commits into from
Jan 7, 2022
101 changes: 79 additions & 22 deletions src/Nethermind/Nethermind.Evm.Test/Tracing/EstimateGasTracerTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -17,36 +17,72 @@
using System;
using FluentAssertions;
using Nethermind.Core;
using Nethermind.Core.Extensions;
using Nethermind.Core.Specs;
using Nethermind.Core.Test.Builders;
using Nethermind.Crypto;
using Nethermind.Db;
using Nethermind.Evm.Tracing;
using Nethermind.Evm.TransactionProcessing;
using Nethermind.Int256;
using Nethermind.Logging;
using Nethermind.Specs;
using Nethermind.Specs.Forks;
using Nethermind.State;
using Nethermind.Trie.Pruning;
using NSubstitute;
using NUnit.Framework;

namespace Nethermind.Evm.Test.Tracing
{
[TestFixture(true)]
[TestFixture(false)]
[Parallelizable(ParallelScope.All)]
[Parallelizable(ParallelScope.Self)]
public class EstimateGasTracerTests
kjazgar marked this conversation as resolved.
Show resolved Hide resolved
{
private readonly ExecutionType _executionType;
private readonly IReleaseSpec _releaseSpec = Berlin.Instance;
private readonly ISpecProvider _specProvider;
private IEthereumEcdsa _ethereumEcdsa;
private TransactionProcessor _transactionProcessor;
private IStateProvider _stateProvider;
kjazgar marked this conversation as resolved.
Show resolved Hide resolved


public EstimateGasTracerTests(bool useCreates)
{
_executionType = useCreates ? ExecutionType.Create : ExecutionType.Call;
_specProvider = MainnetSpecProvider.Instance;

}

[SetUp]
public void Setup()
{
MemDb stateDb = new();
TrieStore trieStore = new(stateDb, LimboLogs.Instance);
_stateProvider = new StateProvider(trieStore, new MemDb(), LimboLogs.Instance);
_stateProvider.CreateAccount(TestItem.AddressA, 1.Ether());
_stateProvider.Commit(_specProvider.GenesisSpec);
_stateProvider.CommitTree(0);

StorageProvider storageProvider = new(trieStore, _stateProvider, LimboLogs.Instance);
VirtualMachine virtualMachine = new(TestBlockhashProvider.Instance, _specProvider, LimboLogs.Instance);
_transactionProcessor = new TransactionProcessor(_specProvider, _stateProvider, storageProvider, virtualMachine, LimboLogs.Instance);
_ethereumEcdsa = new EthereumEcdsa(_specProvider.ChainId, LimboLogs.Instance);
}

[Test]
public void Does_not_take_into_account_precompiles()
{
Transaction tx = Build.A.Transaction.WithGasLimit(1000).TestObject;
Block block = Build.A.Block.WithNumber(1).WithTransactions(tx).TestObject;

EstimateGasTracer tracer = new();
tracer.ReportAction(1000, 0, Address.Zero, Address.Zero, Array.Empty<byte>(), ExecutionType.Transaction, false);
tracer.ReportAction(1000, 0, Address.Zero, Address.Zero, Array.Empty<byte>(), ExecutionType.Call, true);
tracer.ReportActionEnd(400, Array.Empty<byte>()); // this would not happen but we want to ensure that precompiles are ignored
tracer.ReportActionEnd(600, Array.Empty<byte>());
tracer.CalculateEstimate(Build.A.Transaction.WithGasLimit(1000).TestObject, _releaseSpec).Should().Be(0);

tracer.CalculateEstimate(tx, block.Header, _stateProvider, (UInt256)(1000), _transactionProcessor, _specProvider.GetSpec(block.Header.Number)).Should().Be(0);
}

[Test]
Expand All @@ -63,20 +99,26 @@ public void Only_traces_actions_and_receipts()
|| tracer.IsTracingStack
|| tracer.IsTracingOpLevelStorage).Should().BeFalse();
}

[Test]
public void Handles_well_top_level()
{
EstimateGasTracer tracer = new();
Transaction tx = Build.A.Transaction.WithGasLimit(1000).TestObject;
Block block = Build.A.Block.WithNumber(1).WithTransactions(tx).TestObject;

tracer.ReportAction(1000, 0, Address.Zero, Address.Zero, Array.Empty<byte>(), ExecutionType.Transaction, false);
tracer.ReportActionEnd(600, Array.Empty<byte>());
tracer.CalculateEstimate(Build.A.Transaction.WithGasLimit(1000).TestObject, _releaseSpec).Should().Be(0);
tracer.CalculateEstimate(tx, block.Header, _stateProvider, (UInt256)(1000), _transactionProcessor, _specProvider.GetSpec(block.Header.Number)).Should().Be(0);
}

[Test]
public void Handles_well_serial_calls()
{
EstimateGasTracer tracer = new();
Transaction tx = Build.A.Transaction.WithGasLimit(1000).TestObject;
Block block = Build.A.Block.WithNumber(1).WithTransactions(tx).TestObject;

tracer.ReportAction(1000, 0, Address.Zero, Address.Zero, Array.Empty<byte>(), ExecutionType.Transaction, false);
tracer.ReportAction(1000, 0, Address.Zero, Address.Zero, Array.Empty<byte>(), _executionType, false);
tracer.ReportActionEnd(400, Array.Empty<byte>());
Expand All @@ -91,18 +133,21 @@ public void Handles_well_serial_calls()
tracer.ReportActionEnd(200, Array.Empty<byte>());
tracer.ReportActionEnd(300, Array.Empty<byte>()); // should not happen
}

tracer.CalculateEstimate(Build.A.Transaction.WithGasLimit(1000).TestObject, _releaseSpec).Should().Be(14L);
tracer.CalculateEstimate(tx, block.Header, _stateProvider, (UInt256)(1000), _transactionProcessor, _specProvider.GetSpec(block.Header.Number)).Should().Be(14L);
}

[Test]
public void Handles_well_errors()
{
EstimateGasTracer tracer = new();
Transaction tx = Build.A.Transaction.WithGasLimit(1000).TestObject;
Block block = Build.A.Block.WithNumber(1).WithTransactions(tx).TestObject;

tracer.ReportAction(1000, 0, Address.Zero, Address.Zero, Array.Empty<byte>(), ExecutionType.Transaction, false);
tracer.ReportAction(1000, 0, Address.Zero, Address.Zero, Array.Empty<byte>(), _executionType, false);
tracer.ReportAction(400, 0, Address.Zero, Address.Zero, Array.Empty<byte>(), _executionType, false);

if (_executionType.IsAnyCreate())
{
tracer.ReportActionError(EvmExceptionType.Other);
Expand All @@ -115,22 +160,25 @@ public void Handles_well_errors()
tracer.ReportActionEnd(400, Array.Empty<byte>());
tracer.ReportActionEnd(500, Array.Empty<byte>()); // should not happen
}

tracer.CalculateEstimate(Build.A.Transaction.WithGasLimit(1000).TestObject, _releaseSpec).Should().Be(24L);
tracer.CalculateEstimate(tx, block.Header, _stateProvider, (UInt256)(1000), _transactionProcessor, _specProvider.GetSpec(block.Header.Number)).Should().Be(24L);
}

[Test]
public void Handles_well_revert()
{
long gasLimit = 100000000;
Transaction tx = Build.A.Transaction.WithGasLimit(100000000).TestObject;
Block block = Build.A.Block.WithNumber(1).WithTransactions(tx).TestObject;

EstimateGasTracer tracer = new();
long gasLeft = gasLimit - 22000;
tracer.ReportAction(gasLeft, 0, Address.Zero, Address.Zero, Array.Empty<byte>(), ExecutionType.Transaction, false);
gasLeft = 63 * gasLeft / 64;
tracer.ReportAction(gasLeft, 0, Address.Zero, Address.Zero, Array.Empty<byte>(), _executionType, false);
gasLeft = 63 * gasLeft / 64;
tracer.ReportAction(gasLeft, 0, Address.Zero, Address.Zero, Array.Empty<byte>(), _executionType, false);

if (_executionType.IsAnyCreate())
{
tracer.ReportActionError(EvmExceptionType.Revert, 96000000);
Expand All @@ -143,31 +191,37 @@ public void Handles_well_revert()
tracer.ReportActionError(EvmExceptionType.Revert, 98000000);
tracer.ReportActionError(EvmExceptionType.Revert, 99000000);
}

tracer.CalculateEstimate(Build.A.Transaction.WithGasLimit(100000000).TestObject, _releaseSpec).Should().Be(35146L);
tracer.CalculateEstimate(tx, block.Header, _stateProvider, (UInt256)(1000), _transactionProcessor, _specProvider.GetSpec(block.Header.Number)).Should().Be(35146L);
}

[Test]
public void Easy_one_level_case()
{
EstimateGasTracer tracer = new();
Transaction tx = Build.A.Transaction.WithGasLimit(128).TestObject;
Block block = Build.A.Block.WithNumber(1).WithTransactions(tx).TestObject;

tracer.ReportAction(128, 0, Address.Zero, Address.Zero, Array.Empty<byte>(), ExecutionType.Transaction, false);
tracer.ReportAction(100, 0, Address.Zero, Address.Zero, Array.Empty<byte>(), _executionType, false);

tracer.ReportActionEnd(63, Array.Empty<byte>()); // second level
tracer.ReportActionEnd(65, Array.Empty<byte>());

tracer.CalculateEstimate(Build.A.Transaction.WithGasLimit(128).TestObject, _releaseSpec).Should().Be(1);
tracer.CalculateEstimate(tx, block.Header, _stateProvider, (UInt256)(128), _transactionProcessor,_specProvider.GetSpec(block.Header.Number)).Should().Be(1);
}

[Test]
public void Handles_well_nested_calls_where_most_nested_defines_excess()
{
EstimateGasTracer tracer = new();
Transaction tx = Build.A.Transaction.WithGasLimit(1000).TestObject;
Block block = Build.A.Block.WithNumber(1).WithTransactions(tx).TestObject;

tracer.ReportAction(1000, 0, Address.Zero, Address.Zero, Array.Empty<byte>(), ExecutionType.Transaction, false);
tracer.ReportAction(1000, 0, Address.Zero, Address.Zero, Array.Empty<byte>(), _executionType, false);
tracer.ReportAction(400, 0, Address.Zero, Address.Zero, Array.Empty<byte>(), _executionType, false);

if (_executionType.IsAnyCreate())
{
tracer.ReportActionEnd(200, Address.Zero, Array.Empty<byte>()); // second level
Expand All @@ -180,18 +234,21 @@ public void Handles_well_nested_calls_where_most_nested_defines_excess()
tracer.ReportActionEnd(400, Array.Empty<byte>());
tracer.ReportActionEnd(500, Array.Empty<byte>()); // should not happen
}

tracer.CalculateEstimate(Build.A.Transaction.WithGasLimit(1000).TestObject, _releaseSpec).Should().Be(18);
tracer.CalculateEstimate(tx, block.Header, _stateProvider, (UInt256)(1000), _transactionProcessor, _specProvider.GetSpec(block.Header.Number)).Should().Be(18);
}

[Test]
public void Handles_well_nested_calls_where_least_nested_defines_excess()
{
EstimateGasTracer tracer = new();
Transaction tx = Build.A.Transaction.WithGasLimit(1000).TestObject;
Block block = Build.A.Block.WithNumber(1).WithTransactions(tx).TestObject;

tracer.ReportAction(1000, 0, Address.Zero, Address.Zero, Array.Empty<byte>(), ExecutionType.Transaction, false);
tracer.ReportAction(1000, 0, Address.Zero, Address.Zero, Array.Empty<byte>(), _executionType, false);
tracer.ReportAction(400, 0, Address.Zero, Address.Zero, Array.Empty<byte>(), _executionType, false);

if (_executionType.IsAnyCreate())
{
tracer.ReportActionEnd(300, Address.Zero, Array.Empty<byte>()); // second level
Expand All @@ -204,8 +261,8 @@ public void Handles_well_nested_calls_where_least_nested_defines_excess()
tracer.ReportActionEnd(200, Array.Empty<byte>());
tracer.ReportActionEnd(500, Array.Empty<byte>()); // should not happen
}

tracer.CalculateEstimate(Build.A.Transaction.WithGasLimit(1000).TestObject, _releaseSpec).Should().Be(17);
tracer.CalculateEstimate(tx, block.Header, _stateProvider, (UInt256)(1000), _transactionProcessor, _specProvider.GetSpec(block.Header.Number)).Should().Be(17);
}
}
}
18 changes: 9 additions & 9 deletions src/Nethermind/Nethermind.Evm.Test/TransactionProcessorTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -332,7 +332,7 @@ public void Can_estimate_simple()
_transactionProcessor.CallAndRestore(tx, block.Header, tracer);

tracer.GasSpent.Should().Be(21000);
tracer.CalculateEstimate(tx, Berlin.Instance).Should().Be(21000);
tracer.CalculateEstimate(tx, block.Header, _stateProvider, (UInt256)gasLimit, _transactionProcessor, _specProvider.GetSpec(block.Number)).Should().Be(21000);
}

[Test]
Expand Down Expand Up @@ -367,7 +367,7 @@ public void Can_estimate_with_refund()
IReleaseSpec releaseSpec = Berlin.Instance;
tracer.CalculateAdditionalGasRequired(tx, releaseSpec).Should().Be(RefundOf.SSetReversedEip2200 + GasCostOf.CallStipend - GasCostOf.SStoreNetMeteredEip2200 + 1);
tracer.GasSpent.Should().Be(54764L);
long estimate = tracer.CalculateEstimate(tx, releaseSpec);
long estimate = tracer.CalculateEstimate(tx, block.Header, _stateProvider, (UInt256)gasLimit, _transactionProcessor, _specProvider.GetSpec(block.Number));
estimate.Should().Be(75465L);

ConfirmEnoughEstimate(tx, block, estimate);
Expand Down Expand Up @@ -404,8 +404,8 @@ public void Can_estimate_with_destroy_refund_and_below_intrinsic_pre_berlin()
actualIntrinsic.Should().Be(intrinsic);
tracer.CalculateAdditionalGasRequired(tx, releaseSpec).Should().Be(24080);
tracer.GasSpent.Should().Be(35228L);
long estimate = tracer.CalculateEstimate(tx, releaseSpec);
estimate.Should().Be(59308);
long estimate = tracer.CalculateEstimate(tx, block.Header, _stateProvider, (UInt256)gasLimit, _transactionProcessor, _specProvider.GetSpec(block.Number));
estimate.Should().Be(59307);

ConfirmEnoughEstimate(tx, block, estimate);
}
Expand Down Expand Up @@ -466,7 +466,7 @@ public void Can_estimate_with_stipend()
actualIntrinsic.Should().Be(intrinsic);
tracer.CalculateAdditionalGasRequired(tx, releaseSpec).Should().Be(2300);
tracer.GasSpent.Should().Be(85669L);
long estimate = tracer.CalculateEstimate(tx, releaseSpec);
long estimate = tracer.CalculateEstimate(tx, block.Header, _stateProvider, (UInt256)gasLimit, _transactionProcessor, _specProvider.GetSpec(block.Number));
estimate.Should().Be(87969L);

ConfirmEnoughEstimate(tx, block, estimate);
Expand Down Expand Up @@ -505,8 +505,8 @@ public void Can_estimate_with_stipend_and_refund()
actualIntrinsic.Should().Be(intrinsic);
tracer.CalculateAdditionalGasRequired(tx, releaseSpec).Should().Be(RefundOf.SSetReversedEip2200 + GasCostOf.CallStipend);
tracer.GasSpent.Should().Be(87429L);
long estimate = tracer.CalculateEstimate(tx, releaseSpec);
estimate.Should().Be(87429L + RefundOf.SSetReversedEip2200 + GasCostOf.CallStipend);
long estimate = tracer.CalculateEstimate(tx, block.Header, _stateProvider, (UInt256)gasLimit, _transactionProcessor, _specProvider.GetSpec(block.Number));
estimate.Should().Be(108130L);

ConfirmEnoughEstimate(tx, block, estimate);
}
Expand Down Expand Up @@ -540,8 +540,8 @@ public void Can_estimate_with_single_call()
actualIntrinsic.Should().Be(intrinsic);
tracer.CalculateAdditionalGasRequired(tx, releaseSpec).Should().Be(1);
tracer.GasSpent.Should().Be(54224L);
long estimate = tracer.CalculateEstimate(tx, releaseSpec);
estimate.Should().Be(54225L);
long estimate = tracer.CalculateEstimate(tx, block.Header, _stateProvider, (UInt256)gasLimit, _transactionProcessor, _specProvider.GetSpec(block.Number));
estimate.Should().Be(54224L);

ConfirmEnoughEstimate(tx, block, estimate);
}
Expand Down
70 changes: 68 additions & 2 deletions src/Nethermind/Nethermind.Evm/Tracing/EstimateGasTracer.cs
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,10 @@
using Nethermind.Core;
using Nethermind.Core.Crypto;
using Nethermind.Core.Specs;
using Nethermind.Evm.Tracing.GethStyle;
using Nethermind.Evm.TransactionProcessing;
using Nethermind.Int256;
using Nethermind.Serialization.Json;
using Nethermind.State;

namespace Nethermind.Evm.Tracing
Expand Down Expand Up @@ -194,10 +197,73 @@ internal long CalculateAdditionalGasRequired(Transaction tx, IReleaseSpec releas
return _currentGasAndNesting.Peek().AdditionalGasRequired + RefundHelper.CalculateClaimableRefund(intrinsicGas + NonIntrinsicGasSpentBeforeRefund, TotalRefund, releaseSpec);
}

public long CalculateEstimate(Transaction tx, IReleaseSpec releaseSpec)
private static bool Executable(Transaction transaction, BlockHeader block, GethLikeTxTracer txTracer, UInt256 gas, ITransactionProcessor transactionProcessor)
kjazgar marked this conversation as resolved.
Show resolved Hide resolved
{
transaction.GasLimit = (long)gas;

try
{
transactionProcessor.CallAndRestore(transaction, block, txTracer);
string gethTracer = new EthereumJsonSerializer().Serialize(txTracer.BuildResult(), true);

return !(gethTracer.Contains("\"failed\": true") || gethTracer.Contains("OutOfGas"));
kjazgar marked this conversation as resolved.
Show resolved Hide resolved
}
catch (Exception)
kjazgar marked this conversation as resolved.
Show resolved Hide resolved
{
return false;
}
}

private long BinarySearchEstimate(UInt256 leftBound, UInt256 rightBound, UInt256 cap, Transaction tx, BlockHeader header, ITransactionProcessor transactionProcessor)
{
while (leftBound + 1 < rightBound)
{
UInt256 mid = (leftBound + rightBound) / 2;
if (!Executable(tx, header, new GethLikeTxTracer(GethTraceOptions.Default), mid, transactionProcessor))
kjazgar marked this conversation as resolved.
Show resolved Hide resolved
leftBound = mid;
else
rightBound = mid;
}

if (rightBound == cap)
if (!Executable(tx, header, new GethLikeTxTracer(GethTraceOptions.Default), rightBound, transactionProcessor)) return 0;

return (long)(rightBound);
}

public long CalculateEstimate(Transaction tx, BlockHeader header, IStateProvider stateProvider, UInt256 gasCap, ITransactionProcessor transactionProcessor, IReleaseSpec releaseSpec)
{

long intrinsicGas = tx.GasLimit - IntrinsicGasAt;
return Math.Max(intrinsicGas, GasSpent + CalculateAdditionalGasRequired(tx, releaseSpec));
if (tx.GasLimit > header.GasLimit)
return Math.Max(intrinsicGas, GasSpent + CalculateAdditionalGasRequired(tx, releaseSpec));

UInt256 leftBound = Transaction.BaseTxGasCost - 1;
tx.SenderAddress ??= Address.Zero;
UInt256 rightBound = (tx.GasLimit != 0 && tx.GasPrice >= Transaction.BaseTxGasCost)? (UInt256)tx.GasLimit : (UInt256)header.GasLimit;
UInt256 feeCap;

if (tx.GasPrice != 0 && (tx.MaxFeePerGas.Equals( null) && tx.MaxPriorityFeePerGas.Equals(null)))
kjazgar marked this conversation as resolved.
Show resolved Hide resolved
feeCap = tx.GasPrice;
else if (!tx.MaxFeePerGas.Equals( null))
kjazgar marked this conversation as resolved.
Show resolved Hide resolved
feeCap = tx.MaxFeePerGas;
else
feeCap = 0;
kjazgar marked this conversation as resolved.
Show resolved Hide resolved

if (feeCap != 0)
kjazgar marked this conversation as resolved.
Show resolved Hide resolved
{
UInt256 senderBalance = stateProvider.GetBalance(tx.SenderAddress);

if (tx.Value != 0 && tx.Value >= senderBalance)
return CalculateAdditionalGasRequired(tx, releaseSpec);
}

if (gasCap != 0 && rightBound > gasCap)
rightBound = gasCap;

UInt256 cap = rightBound;

return BinarySearchEstimate(leftBound, rightBound, cap, tx, header, transactionProcessor);
kjazgar marked this conversation as resolved.
Show resolved Hide resolved
}

private int _currentNestingLevel = -1;
Expand Down
Loading