diff --git a/metrics/collector.go b/metrics/collector.go index ab6bce09a..8a8df6df7 100644 --- a/metrics/collector.go +++ b/metrics/collector.go @@ -19,22 +19,24 @@ type Collector interface { MeasureRequestDuration(start time.Time, method string) OperatorBalance(account *flow.Account) AvailableSigningKeys(count int) + GasEstimationIterations(count int) } var _ Collector = &DefaultCollector{} type DefaultCollector struct { // TODO: for now we cannot differentiate which api request failed number of times - apiErrorsCounter prometheus.Counter - serverPanicsCounters *prometheus.CounterVec - cadenceBlockHeight prometheus.Gauge - evmBlockHeight prometheus.Gauge - evmBlockIndexedCounter prometheus.Counter - evmTxIndexedCounter prometheus.Counter - operatorBalance prometheus.Gauge - evmAccountCallCounters *prometheus.CounterVec - requestDurations *prometheus.HistogramVec - availableSigningkeys prometheus.Gauge + apiErrorsCounter prometheus.Counter + serverPanicsCounters *prometheus.CounterVec + cadenceBlockHeight prometheus.Gauge + evmBlockHeight prometheus.Gauge + evmBlockIndexedCounter prometheus.Counter + evmTxIndexedCounter prometheus.Counter + operatorBalance prometheus.Gauge + evmAccountCallCounters *prometheus.CounterVec + requestDurations *prometheus.HistogramVec + availableSigningkeys prometheus.Gauge + gasEstimationIterations prometheus.Gauge } func NewCollector(logger zerolog.Logger) Collector { @@ -90,6 +92,11 @@ func NewCollector(logger zerolog.Logger) Collector { Help: "Number of keys available for transaction signing", }) + gasEstimationIterations := prometheus.NewGauge(prometheus.GaugeOpts{ + Name: prefixedName("gas_estimation_iterations"), + Help: "Number of iterations taken to estimate the gas of a EVM call/tx", + }) + metrics := []prometheus.Collector{ apiErrors, serverPanicsCounters, @@ -101,6 +108,7 @@ func NewCollector(logger zerolog.Logger) Collector { evmAccountCallCounters, requestDurations, availableSigningKeys, + gasEstimationIterations, } if err := registerMetrics(logger, metrics...); err != nil { logger.Info().Msg("using noop collector as metric register failed") @@ -108,16 +116,17 @@ func NewCollector(logger zerolog.Logger) Collector { } return &DefaultCollector{ - apiErrorsCounter: apiErrors, - serverPanicsCounters: serverPanicsCounters, - cadenceBlockHeight: cadenceBlockHeight, - evmBlockHeight: evmBlockHeight, - evmBlockIndexedCounter: evmBlockIndexedCounter, - evmTxIndexedCounter: evmTxIndexedCounter, - evmAccountCallCounters: evmAccountCallCounters, - requestDurations: requestDurations, - operatorBalance: operatorBalance, - availableSigningkeys: availableSigningKeys, + apiErrorsCounter: apiErrors, + serverPanicsCounters: serverPanicsCounters, + cadenceBlockHeight: cadenceBlockHeight, + evmBlockHeight: evmBlockHeight, + evmBlockIndexedCounter: evmBlockIndexedCounter, + evmTxIndexedCounter: evmTxIndexedCounter, + evmAccountCallCounters: evmAccountCallCounters, + requestDurations: requestDurations, + operatorBalance: operatorBalance, + availableSigningkeys: availableSigningKeys, + gasEstimationIterations: gasEstimationIterations, } } @@ -172,6 +181,10 @@ func (c *DefaultCollector) AvailableSigningKeys(count int) { c.availableSigningkeys.Set(float64(count)) } +func (c *DefaultCollector) GasEstimationIterations(count int) { + c.gasEstimationIterations.Set(float64(count)) +} + func prefixedName(name string) string { return fmt.Sprintf("evm_gateway_%s", name) } diff --git a/metrics/nop.go b/metrics/nop.go index b415c143b..62092793b 100644 --- a/metrics/nop.go +++ b/metrics/nop.go @@ -21,3 +21,4 @@ func (c *nopCollector) EVMAccountInteraction(string) {} func (c *nopCollector) MeasureRequestDuration(time.Time, string) {} func (c *nopCollector) OperatorBalance(*flow.Account) {} func (c *nopCollector) AvailableSigningKeys(count int) {} +func (c *nopCollector) GasEstimationIterations(count int) {} diff --git a/services/requester/requester.go b/services/requester/requester.go index bb07ef55e..be0291af5 100644 --- a/services/requester/requester.go +++ b/services/requester/requester.go @@ -310,6 +310,15 @@ func (e *EVM) EstimateGas( height uint64, stateOverrides *ethTypes.StateOverride, ) (uint64, error) { + iterations := 0 + + dryRun := func(gasLimit uint64) (*evmTypes.Result, error) { + tx.Gas = gasLimit + result, err := e.dryRunTx(tx, from, height, stateOverrides, nil) + iterations += 1 + return result, err + } + // Note: The following algorithm, is largely inspired from // https://github.com/onflow/go-ethereum/blob/master/eth/gasestimator/gasestimator.go#L49-L192, // and adapted to fit our use-case. @@ -323,10 +332,10 @@ func (e *EVM) EstimateGas( if tx.Gas >= gethParams.TxGas { passingGasLimit = tx.Gas } - tx.Gas = passingGasLimit + // We first execute the transaction at the highest allowable gas limit, // since if this fails we can return the error immediately. - result, err := e.dryRunTx(tx, from, height, stateOverrides, nil) + result, err := dryRun(passingGasLimit) if err != nil { return 0, err } @@ -338,6 +347,12 @@ func (e *EVM) EstimateGas( return 0, errs.NewFailedTransactionError(resultSummary.ErrorMessage) } + // We do not want to report iterations for calls/transactions + // that errored out or had their execution reverted. + defer func() { + e.collector.GasEstimationIterations(iterations) + }() + // For almost any transaction, the gas consumed by the unconstrained execution // above lower-bounds the gas limit required for it to succeed. One exception // is those that explicitly check gas remaining in order to execute within a @@ -350,8 +365,7 @@ func (e *EVM) EstimateGas( // Explicitly check that gas amount and use as a limit for the binary search. optimisticGasLimit := (result.GasConsumed + result.GasRefund + gethParams.CallStipend) * 64 / 63 if optimisticGasLimit < passingGasLimit { - tx.Gas = optimisticGasLimit - result, err = e.dryRunTx(tx, from, height, stateOverrides, nil) + result, err := dryRun(optimisticGasLimit) if err != nil { // This should not happen under normal conditions since if we make it this far the // transaction had run without error at least once before. @@ -380,8 +394,7 @@ func (e *EVM) EstimateGas( // range here is skewed to favor the low side. mid = failingGasLimit * 2 } - tx.Gas = mid - result, err = e.dryRunTx(tx, from, height, stateOverrides, nil) + result, err := dryRun(mid) if err != nil { return 0, err }