Skip to content
Merged
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
77 changes: 29 additions & 48 deletions src/Nethermind/Nethermind.Blockchain/Tracing/GasEstimator.cs
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,11 @@

namespace Nethermind.Blockchain.Tracing;

public class GasEstimator
public class GasEstimator(
ITransactionProcessor transactionProcessor,
IReadOnlyStateProvider stateProvider,
ISpecProvider specProvider,
IBlocksConfig blocksConfig)
{
/// <summary>
/// Error margin used if none other is specified expressed in basis points.
Expand All @@ -22,20 +26,6 @@ public class GasEstimator

private const int MaxErrorMargin = 10000;

private readonly ITransactionProcessor _transactionProcessor;
private readonly IReadOnlyStateProvider _stateProvider;
private readonly ISpecProvider _specProvider;
private readonly IBlocksConfig _blocksConfig;

public GasEstimator(ITransactionProcessor transactionProcessor, IReadOnlyStateProvider stateProvider,
ISpecProvider specProvider, IBlocksConfig blocksConfig)
{
_transactionProcessor = transactionProcessor;
_stateProvider = stateProvider;
_specProvider = specProvider;
_blocksConfig = blocksConfig;
}

public long Estimate(Transaction tx, BlockHeader header, EstimateGasTracer gasTracer, out string? err,
int errorMargin = DefaultErrorMargin, CancellationToken token = new())
{
Expand All @@ -53,30 +43,19 @@ public long Estimate(Transaction tx, BlockHeader header, EstimateGasTracer gasTr
}

IReleaseSpec releaseSpec =
_specProvider.GetSpec(header.Number + 1, header.Timestamp + _blocksConfig.SecondsPerSlot);
specProvider.GetSpec(header.Number + 1, header.Timestamp + blocksConfig.SecondsPerSlot);

tx.SenderAddress ??= Address.Zero; // If sender is not specified, use zero address.

// Calculate and return additional gas required in case of insufficient funds.
UInt256 senderBalance = _stateProvider.GetBalance(tx.SenderAddress);
UInt256 senderBalance = stateProvider.GetBalance(tx.SenderAddress);
if (tx.ValueRef != UInt256.Zero && tx.ValueRef > senderBalance && !tx.IsSystem())
{
long additionalGas = gasTracer.CalculateAdditionalGasRequired(tx, releaseSpec);
if (additionalGas == 0)
{
// If no additional gas can help, it's an insufficient balance error
if (gasTracer.OutOfGas)
{
err = "Gas estimation failed due to out of gas";
}
else if (gasTracer.StatusCode == StatusCode.Failure)
{
err = gasTracer.Error ?? "Transaction execution fails";
}
else
{
err = "insufficient balance";
}
err = GetError(gasTracer, "insufficient balance");
}
return additionalGas;
}
Expand All @@ -101,15 +80,21 @@ public long Estimate(Transaction tx, BlockHeader header, EstimateGasTracer gasTr
return BinarySearchEstimate(leftBound, rightBound, tx, header, gasTracer, errorMargin, token, out err);
}

private long BinarySearchEstimate(long leftBound, long rightBound, Transaction tx, BlockHeader header,
EstimateGasTracer gasTracer, int errorMargin, CancellationToken token, out string? err)
private long BinarySearchEstimate(
long leftBound,
long rightBound,
Transaction tx,
BlockHeader header,
EstimateGasTracer gasTracer,
int errorMargin,
CancellationToken token,
out string? err)
{
err = null;
double marginWithDecimals = errorMargin == 0 ? 1 : errorMargin / 10000d + 1;

//This approach is similar to Geth, by starting from an optimistic guess the number of iterations is greatly reduced in most cases
long optimisticGasEstimate =
(long)((gasTracer.GasSpent + gasTracer.TotalRefund + GasCostOf.CallStipend) * marginWithDecimals);
long optimisticGasEstimate = (long)((gasTracer.GasSpent + gasTracer.TotalRefund + GasCostOf.CallStipend) * marginWithDecimals);
if (optimisticGasEstimate > leftBound && optimisticGasEstimate < rightBound)
{
if (TryExecutableTransaction(tx, header, optimisticGasEstimate, token, gasTracer))
Expand All @@ -136,34 +121,30 @@ private long BinarySearchEstimate(long leftBound, long rightBound, Transaction t

if (rightBound == cap && !TryExecutableTransaction(tx, header, rightBound, token, gasTracer))
{
// Set error based on failure reason
if (gasTracer.OutOfGas)
{
err = "Gas estimation failed due to out of gas";
}
else if (gasTracer.StatusCode == StatusCode.Failure)
{
err = gasTracer.Error ?? "Transaction execution fails";
}
else
{
err = "Transaction execution fails";
}
err = GetError(gasTracer);
return 0;
}

return rightBound;
}

private static string GetError(EstimateGasTracer gasTracer, string defaultError = "Transaction execution fails") =>
gasTracer switch
{
{ OutOfGas: true } => "Gas estimation failed due to out of gas",
{ StatusCode: StatusCode.Failure } => gasTracer.Error ?? "Transaction execution fails",
_ => defaultError
};

private bool TryExecutableTransaction(Transaction transaction, BlockHeader block, long gasLimit,
CancellationToken token, EstimateGasTracer gasTracer)
{
Transaction txClone = new Transaction();
transaction.CopyTo(txClone);
txClone.GasLimit = gasLimit;

_transactionProcessor.SetBlockExecutionContext(new(block, _specProvider.GetSpec(block)));
TransactionResult result = _transactionProcessor.CallAndRestore(txClone, gasTracer.WithCancellation(token));
transactionProcessor.SetBlockExecutionContext(new(block, specProvider.GetSpec(block)));
TransactionResult result = transactionProcessor.CallAndRestore(txClone, gasTracer.WithCancellation(token));

return result.TransactionExecuted && gasTracer.StatusCode == StatusCode.Success && !gasTracer.OutOfGas;
}
Expand Down