From 4305ee35cf68626ded5367245f80ddc94a214337 Mon Sep 17 00:00:00 2001 From: amit-momin Date: Wed, 14 Aug 2024 01:26:59 -0500 Subject: [PATCH] Added EstimatedGasBuffer and addressed feedback --- common/fee/models.go | 1 + common/txmgr/broadcaster.go | 7 +- .../ocrimpls/contract_transmitter_test.go | 1 + .../evm/config/chain_scoped_gas_estimator.go | 5 ++ core/chains/evm/config/config.go | 1 + core/chains/evm/config/mocks/gas_estimator.go | 45 ++++++++++ core/chains/evm/config/toml/config.go | 16 ++-- .../evm/config/toml/defaults/fallback.toml | 1 + core/chains/evm/gas/helpers_test.go | 5 ++ .../chains/evm/gas/mocks/evm_fee_estimator.go | 4 +- core/chains/evm/gas/models.go | 84 +++++++++++++------ core/chains/evm/gas/models_test.go | 61 ++++++++++++-- core/chains/evm/txmgr/attempts.go | 4 +- core/chains/evm/txmgr/broadcaster_test.go | 68 +++++++++++++++ core/chains/evm/txmgr/test_helpers.go | 35 ++++---- core/config/docs/chains-evm.toml | 2 + .../chainlink/testdata/config-full.toml | 1 + .../config-multi-chain-effective.toml | 3 + core/web/resolver/testdata/config-full.toml | 1 + .../config-multi-chain-effective.toml | 3 + docs/CONFIG.md | 68 +++++++++++++++ .../disk-based-logging-disabled.txtar | 1 + .../validate/disk-based-logging-no-dir.txtar | 1 + .../node/validate/disk-based-logging.txtar | 1 + testdata/scripts/node/validate/invalid.txtar | 1 + testdata/scripts/node/validate/valid.txtar | 1 + 26 files changed, 362 insertions(+), 59 deletions(-) diff --git a/common/fee/models.go b/common/fee/models.go index 0568a2f1433..0496d3929c2 100644 --- a/common/fee/models.go +++ b/common/fee/models.go @@ -14,6 +14,7 @@ var ( ErrBumpFeeExceedsLimit = errors.New("fee bump exceeds limit") ErrBump = errors.New("fee bump failed") ErrConnectivity = errors.New("transaction propagation issue: transactions are not being mined") + ErrFeeLimitTooLow = errors.New("provided fee limit too low") ) func IsBumpErr(err error) bool { diff --git a/common/txmgr/broadcaster.go b/common/txmgr/broadcaster.go index b2fb1dabff7..bb8943fe1d2 100644 --- a/common/txmgr/broadcaster.go +++ b/common/txmgr/broadcaster.go @@ -20,6 +20,7 @@ import ( "github.com/smartcontractkit/chainlink-common/pkg/utils" "github.com/smartcontractkit/chainlink/v2/common/client" + commonfee "github.com/smartcontractkit/chainlink/v2/common/fee" feetypes "github.com/smartcontractkit/chainlink/v2/common/fee/types" txmgrtypes "github.com/smartcontractkit/chainlink/v2/common/txmgr/types" "github.com/smartcontractkit/chainlink/v2/common/types" @@ -434,7 +435,11 @@ func (eb *Broadcaster[CHAIN_ID, HEAD, ADDR, TX_HASH, BLOCK_HASH, SEQ, FEE]) hand } attempt, _, _, retryable, err := eb.NewTxAttempt(ctx, *etx, eb.lggr) - if err != nil { + // Mark transaction as fatal if provided gas limit is set too low + if errors.Is(err, commonfee.ErrFeeLimitTooLow) { + etx.Error = null.StringFrom(commonfee.ErrFeeLimitTooLow.Error()) + return eb.saveFatallyErroredTransaction(eb.lggr, etx), false + } else if err != nil { return fmt.Errorf("processUnstartedTxs failed on NewAttempt: %w", err), retryable } diff --git a/core/capabilities/ccip/ocrimpls/contract_transmitter_test.go b/core/capabilities/ccip/ocrimpls/contract_transmitter_test.go index 6c399906437..39060e886fe 100644 --- a/core/capabilities/ccip/ocrimpls/contract_transmitter_test.go +++ b/core/capabilities/ccip/ocrimpls/contract_transmitter_test.go @@ -614,6 +614,7 @@ func (g *TestGasEstimatorConfig) PriceMaxKey(addr common.Address) *assets.Wei { return assets.GWei(1) } func (g *TestGasEstimatorConfig) EstimateGasLimit() bool { return false } +func (g *TestGasEstimatorConfig) EstimatedGasBuffer() float32 { return 1.25} func (e *TestEvmConfig) GasEstimator() evmconfig.GasEstimator { return &TestGasEstimatorConfig{bumpThreshold: e.BumpThreshold} diff --git a/core/chains/evm/config/chain_scoped_gas_estimator.go b/core/chains/evm/config/chain_scoped_gas_estimator.go index 4f2d8872d8c..69b90ae12d3 100644 --- a/core/chains/evm/config/chain_scoped_gas_estimator.go +++ b/core/chains/evm/config/chain_scoped_gas_estimator.go @@ -112,6 +112,11 @@ func (g *gasEstimatorConfig) EstimateGasLimit() bool { return *g.c.EstimateGasLimit } +func (g *gasEstimatorConfig) EstimatedGasBuffer() float32 { + f, _ := g.c.EstimatedGasBuffer.BigFloat().Float32() + return f +} + type limitJobTypeConfig struct { c toml.GasLimitJobType } diff --git a/core/chains/evm/config/config.go b/core/chains/evm/config/config.go index 9517c68716f..95535b58826 100644 --- a/core/chains/evm/config/config.go +++ b/core/chains/evm/config/config.go @@ -137,6 +137,7 @@ type GasEstimator interface { Mode() string PriceMaxKey(gethcommon.Address) *assets.Wei EstimateGasLimit() bool + EstimatedGasBuffer() float32 } type LimitJobType interface { diff --git a/core/chains/evm/config/mocks/gas_estimator.go b/core/chains/evm/config/mocks/gas_estimator.go index 40c10757b85..b5ac6dae54e 100644 --- a/core/chains/evm/config/mocks/gas_estimator.go +++ b/core/chains/evm/config/mocks/gas_estimator.go @@ -343,6 +343,51 @@ func (_c *GasEstimator_EstimateGasLimit_Call) RunAndReturn(run func() bool) *Gas return _c } +// EstimatedGasBuffer provides a mock function with given fields: +func (_m *GasEstimator) EstimatedGasBuffer() float32 { + ret := _m.Called() + + if len(ret) == 0 { + panic("no return value specified for EstimatedGasBuffer") + } + + var r0 float32 + if rf, ok := ret.Get(0).(func() float32); ok { + r0 = rf() + } else { + r0 = ret.Get(0).(float32) + } + + return r0 +} + +// GasEstimator_EstimatedGasBuffer_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'EstimatedGasBuffer' +type GasEstimator_EstimatedGasBuffer_Call struct { + *mock.Call +} + +// EstimatedGasBuffer is a helper method to define mock.On call +func (_e *GasEstimator_Expecter) EstimatedGasBuffer() *GasEstimator_EstimatedGasBuffer_Call { + return &GasEstimator_EstimatedGasBuffer_Call{Call: _e.mock.On("EstimatedGasBuffer")} +} + +func (_c *GasEstimator_EstimatedGasBuffer_Call) Run(run func()) *GasEstimator_EstimatedGasBuffer_Call { + _c.Call.Run(func(args mock.Arguments) { + run() + }) + return _c +} + +func (_c *GasEstimator_EstimatedGasBuffer_Call) Return(_a0 float32) *GasEstimator_EstimatedGasBuffer_Call { + _c.Call.Return(_a0) + return _c +} + +func (_c *GasEstimator_EstimatedGasBuffer_Call) RunAndReturn(run func() float32) *GasEstimator_EstimatedGasBuffer_Call { + _c.Call.Return(run) + return _c +} + // FeeCapDefault provides a mock function with given fields: func (_m *GasEstimator) FeeCapDefault() *assets.Wei { ret := _m.Called() diff --git a/core/chains/evm/config/toml/config.go b/core/chains/evm/config/toml/config.go index 7b34bea27d6..247a79ef3e3 100644 --- a/core/chains/evm/config/toml/config.go +++ b/core/chains/evm/config/toml/config.go @@ -541,12 +541,13 @@ type GasEstimator struct { PriceMax *assets.Wei PriceMin *assets.Wei - LimitDefault *uint64 - LimitMax *uint64 - LimitMultiplier *decimal.Decimal - LimitTransfer *uint64 - LimitJobType GasLimitJobType `toml:",omitempty"` - EstimateGasLimit *bool + LimitDefault *uint64 + LimitMax *uint64 + LimitMultiplier *decimal.Decimal + LimitTransfer *uint64 + LimitJobType GasLimitJobType `toml:",omitempty"` + EstimateGasLimit *bool + EstimatedGasBuffer *decimal.Decimal BumpMin *assets.Wei BumpPercent *uint16 @@ -637,6 +638,9 @@ func (e *GasEstimator) setFrom(f *GasEstimator) { if v := f.EstimateGasLimit; v != nil { e.EstimateGasLimit = v } + if v := f.EstimatedGasBuffer; v != nil { + e.EstimatedGasBuffer = v + } if v := f.PriceDefault; v != nil { e.PriceDefault = v } diff --git a/core/chains/evm/config/toml/defaults/fallback.toml b/core/chains/evm/config/toml/defaults/fallback.toml index 6dd02a8fd5b..bd7aca0e881 100644 --- a/core/chains/evm/config/toml/defaults/fallback.toml +++ b/core/chains/evm/config/toml/defaults/fallback.toml @@ -48,6 +48,7 @@ FeeCapDefault = '100 gwei' TipCapDefault = '1' TipCapMin = '1' EstimateGasLimit = false +EstimatedGasBuffer = 1.25 [GasEstimator.BlockHistory] BatchSize = 25 diff --git a/core/chains/evm/gas/helpers_test.go b/core/chains/evm/gas/helpers_test.go index d6af645fe8f..d3a584546a9 100644 --- a/core/chains/evm/gas/helpers_test.go +++ b/core/chains/evm/gas/helpers_test.go @@ -158,6 +158,7 @@ type MockGasEstimatorConfig struct { LimitMaxF uint64 ModeF string EstimateGasLimitF bool + EstimatedGasBufferF float32 } func NewMockGasConfig() *MockGasEstimatorConfig { @@ -219,3 +220,7 @@ func (m *MockGasEstimatorConfig) Mode() string { func (m *MockGasEstimatorConfig) EstimateGasLimit() bool { return m.EstimateGasLimitF } + +func (m *MockGasEstimatorConfig) EstimatedGasBuffer() float32 { + return m.EstimatedGasBufferF +} diff --git a/core/chains/evm/gas/mocks/evm_fee_estimator.go b/core/chains/evm/gas/mocks/evm_fee_estimator.go index 802a2803736..603115a94c7 100644 --- a/core/chains/evm/gas/mocks/evm_fee_estimator.go +++ b/core/chains/evm/gas/mocks/evm_fee_estimator.go @@ -219,8 +219,8 @@ func (_c *EvmFeeEstimator_GetFee_Call) Run(run func(ctx context.Context, calldat return _c } -func (_c *EvmFeeEstimator_GetFee_Call) Return(fee gas.EvmFee, chainSpecificFeeLimit uint64, err error) *EvmFeeEstimator_GetFee_Call { - _c.Call.Return(fee, chainSpecificFeeLimit, err) +func (_c *EvmFeeEstimator_GetFee_Call) Return(fee gas.EvmFee, estimatedFeeLimit uint64, err error) *EvmFeeEstimator_GetFee_Call { + _c.Call.Return(fee, estimatedFeeLimit, err) return _c } diff --git a/core/chains/evm/gas/models.go b/core/chains/evm/gas/models.go index 38a98f20a71..452e5c3dd32 100644 --- a/core/chains/evm/gas/models.go +++ b/core/chains/evm/gas/models.go @@ -33,7 +33,7 @@ type EvmFeeEstimator interface { // L1Oracle returns the L1 gas price oracle only if the chain has one, e.g. OP stack L2s and Arbitrum. L1Oracle() rollups.L1Oracle - GetFee(ctx context.Context, calldata []byte, feeLimit uint64, maxFeePrice *assets.Wei, toAddress *common.Address, opts ...feetypes.Opt) (fee EvmFee, chainSpecificFeeLimit uint64, err error) + GetFee(ctx context.Context, calldata []byte, feeLimit uint64, maxFeePrice *assets.Wei, toAddress *common.Address, opts ...feetypes.Opt) (fee EvmFee, estimatedFeeLimit uint64, err error) BumpFee(ctx context.Context, originalFee EvmFee, feeLimit uint64, maxFeePrice *assets.Wei, attempts []EvmPriorAttempt) (bumpedFee EvmFee, chainSpecificFeeLimit uint64, err error) // GetMaxCost returns the total value = max price x fee units + transferred value @@ -71,6 +71,8 @@ func NewEstimator(lggr logger.Logger, ethClient feeEstimatorClient, cfg Config, "tipCapMin", geCfg.TipCapMin(), "priceMax", geCfg.PriceMax(), "priceMin", geCfg.PriceMin(), + "estimateGasLimit", geCfg.EstimateGasLimit(), + "estimatedGasBuffer", geCfg.EstimatedGasBuffer(), ) df := geCfg.EIP1559DynamicFees() @@ -264,13 +266,10 @@ func (e *evmFeeEstimator) L1Oracle() rollups.L1Oracle { return e.EvmEstimator.L1Oracle() } -func (e *evmFeeEstimator) GetFee(ctx context.Context, calldata []byte, feeLimit uint64, maxFeePrice *assets.Wei, toAddress *common.Address, opts ...feetypes.Opt) (fee EvmFee, chainSpecificFeeLimit uint64, err error) { - // Create call msg for gas limit estimation, if needed - callMsg := ethereum.CallMsg{ - To: toAddress, - Data: calldata, - } - +// GetFee returns an initial estimated gas price and gas limit for a transaction +// The gas limit provided by the caller can be adjusted by gas estimation or for 2D fees +func (e *evmFeeEstimator) GetFee(ctx context.Context, calldata []byte, feeLimit uint64, maxFeePrice *assets.Wei, toAddress *common.Address, opts ...feetypes.Opt) (fee EvmFee, estimatedFeeLimit uint64, err error) { + var chainSpecificFeeLimit uint64 // get dynamic fee if e.EIP1559Enabled { var dynamicFee DynamicFee @@ -281,31 +280,15 @@ func (e *evmFeeEstimator) GetFee(ctx context.Context, calldata []byte, feeLimit fee.DynamicFeeCap = dynamicFee.FeeCap fee.DynamicTipCap = dynamicFee.TipCap chainSpecificFeeLimit = feeLimit - // Set call msg fields with dynamic fee fields for gas limit estimation, if needed - callMsg.GasFeeCap = fee.DynamicFeeCap.ToInt() - callMsg.GasTipCap = fee.DynamicTipCap.ToInt() } else { // get legacy fee fee.Legacy, chainSpecificFeeLimit, err = e.EvmEstimator.GetLegacyGas(ctx, calldata, feeLimit, maxFeePrice, opts...) if err != nil { return } - // Set call msg fields with legacy fee field for gas limit estimation, if needed - callMsg.GasPrice = fee.Legacy.ToInt() - } - - if e.geCfg.EstimateGasLimit() { - callMsg.Gas = chainSpecificFeeLimit - estimatedGasLimit, estimateErr := e.ethClient.EstimateGas(ctx, callMsg) - if estimateErr != nil { - // Do not return error if estimating gas failed, we can still use the provided limit instead since it is an upper limit - e.lggr.Errorw("failed to estimate gas limit. falling back to provided gas limit.", "toAddress", toAddress, "data", calldata, "gasLimit", chainSpecificFeeLimit, "error", estimateErr) - } else { - chainSpecificFeeLimit = estimatedGasLimit - } } - chainSpecificFeeLimit, err = commonfee.ApplyMultiplier(chainSpecificFeeLimit, e.geCfg.LimitMultiplier()) + estimatedFeeLimit, err = e.estimateFeeLimit(ctx, fee, chainSpecificFeeLimit, calldata, toAddress) return } @@ -361,6 +344,56 @@ func (e *evmFeeEstimator) BumpFee(ctx context.Context, originalFee EvmFee, feeLi return } +func (e *evmFeeEstimator) estimateFeeLimit(ctx context.Context, fee EvmFee, feeLimit uint64, calldata []byte, toAddress *common.Address) (estimatedFeeLimit uint64, err error) { + // Use provided fee limit by default is EstimateGasLimit is disabled + if !e.geCfg.EstimateGasLimit() { + return commonfee.ApplyMultiplier(feeLimit, e.geCfg.LimitMultiplier()) + } + + // Create call msg for gas limit estimation + // Skip setting Gas to avoid capping the results of the estimation + callMsg := ethereum.CallMsg{ + To: toAddress, + Data: calldata, + } + if e.EIP1559Enabled { + callMsg.GasFeeCap = fee.DynamicFeeCap.ToInt() + callMsg.GasTipCap = fee.DynamicTipCap.ToInt() + } else { + // Set call msg fields with legacy fee field for gas limit estimation, if needed + callMsg.GasPrice = fee.Legacy.ToInt() + } + e.lggr.Debugw("estimating gas limit", "callMsg", callMsg) + gasUsed, estimateErr := e.ethClient.EstimateGas(ctx, callMsg) + if estimateErr != nil { + // Do not return error if estimate gas failed, we can still use the provided limit instead since it is an upper limit + e.lggr.Errorw("failed to estimate gas limit. falling back to provided gas limit.", "callMsg", callMsg, "providedGasLimit", feeLimit, "error", estimateErr) + estimatedFeeLimit, err = commonfee.ApplyMultiplier(feeLimit, e.geCfg.LimitMultiplier()) + return + } + // Return error if estimated gas without the buffer exceeds the provided gas limit + // Transaction is destined to run out of gas and fail + if gasUsed > feeLimit { + e.lggr.Errorw("estimated gas limit exceeds provided limit", "estimatedGasLimit", gasUsed, "providedGasLimit", feeLimit) + return estimatedFeeLimit, commonfee.ErrFeeLimitTooLow + } + // Apply EstimatedGasBuffer multiplier to the estimated gas limit + estimatedFeeLimit, err = commonfee.ApplyMultiplier(gasUsed, e.geCfg.EstimatedGasBuffer()) + if err != nil { + return + } + + // Fallback to the provided gas limit if the buffer causes the estimated gas limit to exceed it + // The provided gas limit should be used as an upper bound to avoid unexpected behavior for products + if estimatedFeeLimit > feeLimit { + e.lggr.Debugw("estimated gas limit with buffer exceeds provided limit. falling back to the provided gas limit", "estimatedGasLimit", estimatedFeeLimit, "providedGasLimit", feeLimit) + estimatedFeeLimit = feeLimit + } + + estimatedFeeLimit, err = commonfee.ApplyMultiplier(estimatedFeeLimit, e.geCfg.LimitMultiplier()) + return +} + // Config defines an interface for configuration in the gas package type Config interface { ChainType() chaintype.ChainType @@ -383,6 +416,7 @@ type GasEstimatorConfig interface { PriceMax() *assets.Wei Mode() string EstimateGasLimit() bool + EstimatedGasBuffer() float32 } type BlockHistoryConfig interface { diff --git a/core/chains/evm/gas/models_test.go b/core/chains/evm/gas/models_test.go index b2e39a27fb6..d9fef74429b 100644 --- a/core/chains/evm/gas/models_test.go +++ b/core/chains/evm/gas/models_test.go @@ -13,6 +13,7 @@ import ( "github.com/smartcontractkit/chainlink-common/pkg/logger" "github.com/smartcontractkit/chainlink-common/pkg/utils/tests" + commonfee "github.com/smartcontractkit/chainlink/v2/common/fee" "github.com/smartcontractkit/chainlink/v2/core/chains/evm/assets" "github.com/smartcontractkit/chainlink/v2/core/chains/evm/config/chaintype" "github.com/smartcontractkit/chainlink/v2/core/chains/evm/gas" @@ -34,12 +35,13 @@ func TestWrappedEvmEstimator(t *testing.T) { TipCap: assets.NewWeiI(1), } limitMultiplier := float32(1.5) + estimatedGasBuffer := float32(1.25) est := mocks.NewEvmEstimator(t) est.On("GetDynamicFee", mock.Anything, mock.Anything). - Return(dynamicFee, nil).Times(4) + Return(dynamicFee, nil).Times(6) est.On("GetLegacyGas", mock.Anything, mock.Anything, mock.Anything, mock.Anything). - Return(legacyFee, gasLimit, nil).Times(4) + Return(legacyFee, gasLimit, nil).Times(6) est.On("BumpDynamicFee", mock.Anything, mock.Anything, mock.Anything, mock.Anything). Return(dynamicFee, nil).Once() est.On("BumpLegacyGas", mock.Anything, mock.Anything, mock.Anything, mock.Anything, mock.Anything). @@ -47,6 +49,7 @@ func TestWrappedEvmEstimator(t *testing.T) { getRootEst := func(logger.Logger) gas.EvmEstimator { return est } geCfg := gas.NewMockGasConfig() geCfg.LimitMultiplierF = limitMultiplier + geCfg.EstimatedGasBufferF = estimatedGasBuffer mockEstimatorName := "WrappedEvmEstimator" mockEvmEstimatorName := "WrappedEvmEstimator.MockEstimator" @@ -252,7 +255,7 @@ func TestWrappedEvmEstimator(t *testing.T) { require.NotNil(t, report[mockEstimatorName]) }) - t.Run("Estimate gas limit enabled, succeeds", func(t *testing.T) { + t.Run("GetFee, estimate gas limit enabled, succeeds", func(t *testing.T) { estimatedGasLimit := uint64(5) lggr := logger.Test(t) // expect legacy fee data @@ -264,7 +267,7 @@ func TestWrappedEvmEstimator(t *testing.T) { toAddress := testutils.NewAddress() fee, limit, err := estimator.GetFee(ctx, []byte{}, gasLimit, nil, &toAddress) require.NoError(t, err) - assert.Equal(t, uint64(float32(estimatedGasLimit)*limitMultiplier), limit) + assert.Equal(t, uint64(float32(estimatedGasLimit)*estimatedGasBuffer*limitMultiplier), limit) assert.True(t, legacyFee.Equal(fee.Legacy)) assert.Nil(t, fee.DynamicTipCap) assert.Nil(t, fee.DynamicFeeCap) @@ -274,13 +277,59 @@ func TestWrappedEvmEstimator(t *testing.T) { estimator = gas.NewEvmFeeEstimator(lggr, getRootEst, dynamicFees, geCfg, ethClient) fee, limit, err = estimator.GetFee(ctx, []byte{}, gasLimit, nil, &toAddress) require.NoError(t, err) - assert.Equal(t, uint64(float32(estimatedGasLimit)*limitMultiplier), limit) + assert.Equal(t, uint64(float32(estimatedGasLimit)*estimatedGasBuffer*limitMultiplier), limit) assert.True(t, dynamicFee.FeeCap.Equal(fee.DynamicFeeCap)) assert.True(t, dynamicFee.TipCap.Equal(fee.DynamicTipCap)) assert.Nil(t, fee.Legacy) }) - t.Run("Estimate gas limit enabled, fails and falls back to provided gas limit", func(t *testing.T) { + t.Run("GetFee, estimate gas limit enabled, estimate exceeds provided limit, returns error", func(t *testing.T) { + estimatedGasLimit := uint64(11) + lggr := logger.Test(t) + // expect legacy fee data + dynamicFees := false + geCfg.EstimateGasLimitF = true + ethClient := testutils.NewEthClientMockWithDefaultChain(t) + ethClient.On("EstimateGas", mock.Anything, mock.Anything).Return(estimatedGasLimit, nil).Twice() + estimator := gas.NewEvmFeeEstimator(lggr, getRootEst, dynamicFees, geCfg, ethClient) + toAddress := testutils.NewAddress() + _, _, err := estimator.GetFee(ctx, []byte{}, gasLimit, nil, &toAddress) + require.Error(t, err, commonfee.ErrFeeLimitTooLow) + + // expect dynamic fee data + dynamicFees = true + estimator = gas.NewEvmFeeEstimator(lggr, getRootEst, dynamicFees, geCfg, ethClient) + _, _, err = estimator.GetFee(ctx, []byte{}, gasLimit, nil, &toAddress) + require.Error(t, err, commonfee.ErrFeeLimitTooLow) + }) + + t.Run("GetFee, estimate gas limit enabled, buffer exceeds provided limit, fallsback to provided limit", func(t *testing.T) { + estimatedGasLimit := uint64(10) // same as provided limit + lggr := logger.Test(t) + dynamicFees := false // expect legacy fee data + geCfg.EstimateGasLimitF = true + ethClient := testutils.NewEthClientMockWithDefaultChain(t) + ethClient.On("EstimateGas", mock.Anything, mock.Anything).Return(estimatedGasLimit, nil).Twice() + estimator := gas.NewEvmFeeEstimator(lggr, getRootEst, dynamicFees, geCfg, ethClient) + toAddress := testutils.NewAddress() + fee, limit, err := estimator.GetFee(ctx, []byte{}, gasLimit, nil, &toAddress) + require.NoError(t, err) + assert.Equal(t, uint64(float32(gasLimit)*limitMultiplier), limit) + assert.True(t, legacyFee.Equal(fee.Legacy)) + assert.Nil(t, fee.DynamicTipCap) + assert.Nil(t, fee.DynamicFeeCap) + + dynamicFees = true // expect dynamic fee data + estimator = gas.NewEvmFeeEstimator(lggr, getRootEst, dynamicFees, geCfg, ethClient) + fee, limit, err = estimator.GetFee(ctx, []byte{}, gasLimit, nil, &toAddress) + require.NoError(t, err) + assert.Equal(t, uint64(float32(gasLimit)*limitMultiplier), limit) + assert.True(t, dynamicFee.FeeCap.Equal(fee.DynamicFeeCap)) + assert.True(t, dynamicFee.TipCap.Equal(fee.DynamicTipCap)) + assert.Nil(t, fee.Legacy) + }) + + t.Run("GetFee, estimate gas limit enabled, fails and fallsback to provided gas limit", func(t *testing.T) { lggr := logger.Test(t) // expect legacy fee data dynamicFees := false diff --git a/core/chains/evm/txmgr/attempts.go b/core/chains/evm/txmgr/attempts.go index d0a6e7f8e44..c57ecc44124 100644 --- a/core/chains/evm/txmgr/attempts.go +++ b/core/chains/evm/txmgr/attempts.go @@ -71,8 +71,8 @@ func (c *evmTxAttemptBuilder) NewTxAttemptWithType(ctx context.Context, etx Tx, // used in the txm broadcaster + confirmer when tx ix rejected for too low fee or is not included in a timely manner func (c *evmTxAttemptBuilder) NewBumpTxAttempt(ctx context.Context, etx Tx, previousAttempt TxAttempt, priorAttempts []TxAttempt, lggr logger.Logger) (attempt TxAttempt, bumpedFee gas.EvmFee, bumpedFeeLimit uint64, retryable bool, err error) { keySpecificMaxGasPriceWei := c.feeConfig.PriceMaxKey(etx.FromAddress) - - bumpedFee, bumpedFeeLimit, err = c.EvmFeeEstimator.BumpFee(ctx, previousAttempt.TxFee, etx.FeeLimit, keySpecificMaxGasPriceWei, newEvmPriorAttempts(priorAttempts)) + // Use the fee limit from the previous attempt to maintain limits adjusted for 2D fees or by estimation + bumpedFee, bumpedFeeLimit, err = c.EvmFeeEstimator.BumpFee(ctx, previousAttempt.TxFee, previousAttempt.ChainSpecificFeeLimit, keySpecificMaxGasPriceWei, newEvmPriorAttempts(priorAttempts)) if err != nil { return attempt, bumpedFee, bumpedFeeLimit, true, pkgerrors.Wrap(err, "failed to bump fee") // estimator errors are retryable } diff --git a/core/chains/evm/txmgr/broadcaster_test.go b/core/chains/evm/txmgr/broadcaster_test.go index 3180547ea75..2c54193dfcc 100644 --- a/core/chains/evm/txmgr/broadcaster_test.go +++ b/core/chains/evm/txmgr/broadcaster_test.go @@ -29,8 +29,10 @@ import ( "github.com/smartcontractkit/chainlink-common/pkg/utils/tests" commonclient "github.com/smartcontractkit/chainlink/v2/common/client" + commmonfee "github.com/smartcontractkit/chainlink/v2/common/fee" txmgrcommon "github.com/smartcontractkit/chainlink/v2/common/txmgr" txmgrtypes "github.com/smartcontractkit/chainlink/v2/common/txmgr/types" + "github.com/smartcontractkit/chainlink/v2/core/chains/evm/assets" "github.com/smartcontractkit/chainlink/v2/core/chains/evm/client" evmconfig "github.com/smartcontractkit/chainlink/v2/core/chains/evm/config" @@ -1647,6 +1649,72 @@ func TestEthBroadcaster_ProcessUnstartedEthTxs_Errors(t *testing.T) { }) } +func TestEthBroadcaster_ProcessUnstartedEthTxs_GasEstimationError(t *testing.T) { + toAddress := testutils.NewAddress() + value := big.Int(assets.NewEthValue(142)) + gasLimit := uint64(242) + encodedPayload := []byte{0, 1} + + db := pgtest.NewSqlxDB(t) + cfg := configtest.NewTestGeneralConfig(t) + cfg.EVMConfigs()[0].GasEstimator.EstimateGasLimit = ptr(true) // Enabled gas limit estimation + txStore := cltest.NewTestTxStore(t, db) + + ethKeyStore := cltest.NewKeyStore(t, db).Eth() + _, fromAddress := cltest.MustInsertRandomKey(t, ethKeyStore) + + config := evmtest.NewChainScopedConfig(t, cfg) + ethClient := testutils.NewEthClientMockWithDefaultChain(t) + ethClient.On("PendingNonceAt", mock.Anything, fromAddress).Return(uint64(0), nil).Once() + lggr := logger.Test(t) + txmClient := txmgr.NewEvmTxmClient(ethClient, nil) + nonceTracker := txmgr.NewNonceTracker(lggr, txStore, txmClient) + ge := config.EVM().GasEstimator() + estimator := gas.NewEvmFeeEstimator(lggr, func(lggr logger.Logger) gas.EvmEstimator { + return gas.NewFixedPriceEstimator(ge, nil, ge.BlockHistory(), lggr, nil) + }, ge.EIP1559DynamicFees(), ge, ethClient) + txBuilder := txmgr.NewEvmTxAttemptBuilder(*ethClient.ConfiguredChainID(), ge, ethKeyStore, estimator) + eb := txmgrcommon.NewBroadcaster(txStore, txmgr.NewEvmTxmClient(ethClient, nil), txmgr.NewEvmTxmConfig(config.EVM()), txmgr.NewEvmTxmFeeConfig(config.EVM().GasEstimator()), config.EVM().Transactions(), cfg.Database().Listener(), ethKeyStore, txBuilder, nonceTracker, lggr, &testCheckerFactory{}, false, "") + + // Mark instance as test + eb.XXXTestDisableUnstartedTxAutoProcessing() + servicetest.Run(t, eb) + ctx := tests.Context(t) + t.Run("gas limit lowered after estimation", func(t *testing.T) { + estimatedGasLimit := uint64(100) + estimatedGasBuffer := float32(1.25) + etx := mustCreateUnstartedTx(t, txStore, fromAddress, toAddress, encodedPayload, gasLimit, value, testutils.FixtureChainID) + ethClient.On("EstimateGas", mock.Anything, mock.Anything).Return(estimatedGasLimit, nil).Once() + ethClient.On("SendTransactionReturnCode", mock.Anything, mock.MatchedBy(func(tx *gethTypes.Transaction) bool { + return tx.Nonce() == uint64(0) + }), fromAddress).Return(commonclient.Successful, nil).Once() + + // Do the thing + retryable, err := eb.ProcessUnstartedTxs(ctx, fromAddress) + assert.NoError(t, err) + assert.False(t, retryable) + + dbEtx, err := txStore.FindTxWithAttempts(ctx, etx.ID) + require.NoError(t, err) + attempt := dbEtx.TxAttempts[0] + require.Equal(t, uint64(float32(estimatedGasLimit)*estimatedGasBuffer), attempt.ChainSpecificFeeLimit) + }) + t.Run("provided gas limit too low, transaction marked as fatal error", func(t *testing.T) { + etx := mustCreateUnstartedTx(t, txStore, fromAddress, toAddress, encodedPayload, gasLimit, value, testutils.FixtureChainID) + ethClient.On("EstimateGas", mock.Anything, mock.Anything).Return(gasLimit+1, nil).Once() + + // Do the thing + retryable, err := eb.ProcessUnstartedTxs(ctx, fromAddress) + assert.NoError(t, err) + assert.False(t, retryable) + + dbEtx, err := txStore.FindTxWithAttempts(ctx, etx.ID) + require.NoError(t, err) + require.Equal(t, txmgrcommon.TxFatalError, dbEtx.State) + require.Equal(t, commmonfee.ErrFeeLimitTooLow.Error(), dbEtx.Error.String) + }) +} + func TestEthBroadcaster_ProcessUnstartedEthTxs_KeystoreErrors(t *testing.T) { toAddress := gethCommon.HexToAddress("0x6C03DDA95a2AEd917EeCc6eddD4b9D16E6380411") value := big.Int(assets.NewEthValue(142)) diff --git a/core/chains/evm/txmgr/test_helpers.go b/core/chains/evm/txmgr/test_helpers.go index 3dea0243520..8cd3f35abb0 100644 --- a/core/chains/evm/txmgr/test_helpers.go +++ b/core/chains/evm/txmgr/test_helpers.go @@ -76,23 +76,24 @@ func (g *TestGasEstimatorConfig) BlockHistory() evmconfig.BlockHistory { return &TestBlockHistoryConfig{} } -func (g *TestGasEstimatorConfig) EIP1559DynamicFees() bool { return false } -func (g *TestGasEstimatorConfig) LimitDefault() uint64 { return 42 } -func (g *TestGasEstimatorConfig) BumpPercent() uint16 { return 42 } -func (g *TestGasEstimatorConfig) BumpThreshold() uint64 { return g.bumpThreshold } -func (g *TestGasEstimatorConfig) BumpMin() *assets.Wei { return assets.NewWeiI(42) } -func (g *TestGasEstimatorConfig) FeeCapDefault() *assets.Wei { return assets.NewWeiI(42) } -func (g *TestGasEstimatorConfig) PriceDefault() *assets.Wei { return assets.NewWeiI(42) } -func (g *TestGasEstimatorConfig) TipCapDefault() *assets.Wei { return assets.NewWeiI(42) } -func (g *TestGasEstimatorConfig) TipCapMin() *assets.Wei { return assets.NewWeiI(42) } -func (g *TestGasEstimatorConfig) LimitMax() uint64 { return 0 } -func (g *TestGasEstimatorConfig) LimitMultiplier() float32 { return 0 } -func (g *TestGasEstimatorConfig) BumpTxDepth() uint32 { return 42 } -func (g *TestGasEstimatorConfig) LimitTransfer() uint64 { return 42 } -func (g *TestGasEstimatorConfig) PriceMax() *assets.Wei { return assets.NewWeiI(42) } -func (g *TestGasEstimatorConfig) PriceMin() *assets.Wei { return assets.NewWeiI(42) } -func (g *TestGasEstimatorConfig) Mode() string { return "FixedPrice" } -func (g *TestGasEstimatorConfig) EstimateGasLimit() bool { return false } +func (g *TestGasEstimatorConfig) EIP1559DynamicFees() bool { return false } +func (g *TestGasEstimatorConfig) LimitDefault() uint64 { return 42 } +func (g *TestGasEstimatorConfig) BumpPercent() uint16 { return 42 } +func (g *TestGasEstimatorConfig) BumpThreshold() uint64 { return g.bumpThreshold } +func (g *TestGasEstimatorConfig) BumpMin() *assets.Wei { return assets.NewWeiI(42) } +func (g *TestGasEstimatorConfig) FeeCapDefault() *assets.Wei { return assets.NewWeiI(42) } +func (g *TestGasEstimatorConfig) PriceDefault() *assets.Wei { return assets.NewWeiI(42) } +func (g *TestGasEstimatorConfig) TipCapDefault() *assets.Wei { return assets.NewWeiI(42) } +func (g *TestGasEstimatorConfig) TipCapMin() *assets.Wei { return assets.NewWeiI(42) } +func (g *TestGasEstimatorConfig) LimitMax() uint64 { return 0 } +func (g *TestGasEstimatorConfig) LimitMultiplier() float32 { return 0 } +func (g *TestGasEstimatorConfig) BumpTxDepth() uint32 { return 42 } +func (g *TestGasEstimatorConfig) LimitTransfer() uint64 { return 42 } +func (g *TestGasEstimatorConfig) PriceMax() *assets.Wei { return assets.NewWeiI(42) } +func (g *TestGasEstimatorConfig) PriceMin() *assets.Wei { return assets.NewWeiI(42) } +func (g *TestGasEstimatorConfig) Mode() string { return "FixedPrice" } +func (g *TestGasEstimatorConfig) EstimateGasLimit() bool { return false } +func (g *TestGasEstimatorConfig) EstimatedGasBuffer() float32 { return 1.25 } func (g *TestGasEstimatorConfig) LimitJobType() evmconfig.LimitJobType { return &TestLimitJobTypeConfig{} } diff --git a/core/config/docs/chains-evm.toml b/core/config/docs/chains-evm.toml index b9a256ddf4d..c671698d16c 100644 --- a/core/config/docs/chains-evm.toml +++ b/core/config/docs/chains-evm.toml @@ -195,6 +195,8 @@ LimitMultiplier = '1.0' # Default LimitTransfer = 21_000 # Default # EstimateGasLimit enables estimating gas limits for transactions. This feature respects the gas limit provided during transaction creation as an upper bound. EstimateGasLimit = false # Default +# EstimatedGasBuffer is a multiplier applied to estimated gas limits when the EstimateGasLimit feature is enabled. This buffer is added for safety in case the gas used for a transaction changes between estimation and broadcasting. +EstimatedGasBuffer = 1.25 # Default # BumpMin is the minimum fixed amount of wei by which gas is bumped on each transaction attempt. BumpMin = '5 gwei' # Default # BumpPercent is the percentage by which to bump gas on a transaction that has exceeded `BumpThreshold`. The larger of `BumpPercent` and `BumpMin` is taken for gas bumps. diff --git a/core/services/chainlink/testdata/config-full.toml b/core/services/chainlink/testdata/config-full.toml index a7fc9dcb94c..c51ebad8cc8 100644 --- a/core/services/chainlink/testdata/config-full.toml +++ b/core/services/chainlink/testdata/config-full.toml @@ -317,6 +317,7 @@ LimitMax = 17 LimitMultiplier = '1.234' LimitTransfer = 100 EstimateGasLimit = false +EstimatedGasBuffer = 1.25 BumpMin = '100 wei' BumpPercent = 10 BumpThreshold = 6 diff --git a/core/services/chainlink/testdata/config-multi-chain-effective.toml b/core/services/chainlink/testdata/config-multi-chain-effective.toml index 8bfc93c7be0..00a943872fd 100644 --- a/core/services/chainlink/testdata/config-multi-chain-effective.toml +++ b/core/services/chainlink/testdata/config-multi-chain-effective.toml @@ -304,6 +304,7 @@ LimitMax = 500000 LimitMultiplier = '1' LimitTransfer = 21000 EstimateGasLimit = false +EstimatedGasBuffer = 1.25 BumpMin = '5 gwei' BumpPercent = 20 BumpThreshold = 3 @@ -405,6 +406,7 @@ LimitMax = 500000 LimitMultiplier = '1' LimitTransfer = 21000 EstimateGasLimit = false +EstimatedGasBuffer = 1.25 BumpMin = '5 gwei' BumpPercent = 20 BumpThreshold = 3 @@ -500,6 +502,7 @@ LimitMax = 500000 LimitMultiplier = '1' LimitTransfer = 21000 EstimateGasLimit = false +EstimatedGasBuffer = 1.25 BumpMin = '20 gwei' BumpPercent = 20 BumpThreshold = 5 diff --git a/core/web/resolver/testdata/config-full.toml b/core/web/resolver/testdata/config-full.toml index f67d4737b57..e8511b01819 100644 --- a/core/web/resolver/testdata/config-full.toml +++ b/core/web/resolver/testdata/config-full.toml @@ -317,6 +317,7 @@ LimitMax = 17 LimitMultiplier = '1.234' LimitTransfer = 100 EstimateGasLimit = false +EstimatedGasBuffer = 1.25 BumpMin = '100 wei' BumpPercent = 10 BumpThreshold = 6 diff --git a/core/web/resolver/testdata/config-multi-chain-effective.toml b/core/web/resolver/testdata/config-multi-chain-effective.toml index 55f998156c8..6fa822508c2 100644 --- a/core/web/resolver/testdata/config-multi-chain-effective.toml +++ b/core/web/resolver/testdata/config-multi-chain-effective.toml @@ -304,6 +304,7 @@ LimitMax = 500000 LimitMultiplier = '1' LimitTransfer = 21000 EstimateGasLimit = false +EstimatedGasBuffer = 1.25 BumpMin = '5 gwei' BumpPercent = 20 BumpThreshold = 3 @@ -405,6 +406,7 @@ LimitMax = 500000 LimitMultiplier = '1' LimitTransfer = 21000 EstimateGasLimit = false +EstimatedGasBuffer = 1.25 BumpMin = '5 gwei' BumpPercent = 20 BumpThreshold = 3 @@ -500,6 +502,7 @@ LimitMax = 500000 LimitMultiplier = '1' LimitTransfer = 21000 EstimateGasLimit = false +EstimatedGasBuffer = 1.25 BumpMin = '20 gwei' BumpPercent = 20 BumpThreshold = 5 diff --git a/docs/CONFIG.md b/docs/CONFIG.md index 32ab35b7cc0..8122efa91e1 100644 --- a/docs/CONFIG.md +++ b/docs/CONFIG.md @@ -1819,6 +1819,7 @@ LimitMax = 500000 LimitMultiplier = '1' LimitTransfer = 21000 EstimateGasLimit = false +EstimatedGasBuffer = '1.25' BumpMin = '5 gwei' BumpPercent = 20 BumpThreshold = 3 @@ -1914,6 +1915,7 @@ LimitMax = 500000 LimitMultiplier = '1' LimitTransfer = 21000 EstimateGasLimit = false +EstimatedGasBuffer = '1.25' BumpMin = '5 gwei' BumpPercent = 20 BumpThreshold = 3 @@ -2009,6 +2011,7 @@ LimitMax = 500000 LimitMultiplier = '1' LimitTransfer = 21000 EstimateGasLimit = false +EstimatedGasBuffer = '1.25' BumpMin = '5 gwei' BumpPercent = 20 BumpThreshold = 3 @@ -2104,6 +2107,7 @@ LimitMax = 500000 LimitMultiplier = '1' LimitTransfer = 21000 EstimateGasLimit = false +EstimatedGasBuffer = '1.25' BumpMin = '5 gwei' BumpPercent = 20 BumpThreshold = 3 @@ -2200,6 +2204,7 @@ LimitMax = 500000 LimitMultiplier = '1' LimitTransfer = 21000 EstimateGasLimit = false +EstimatedGasBuffer = '1.25' BumpMin = '100 wei' BumpPercent = 20 BumpThreshold = 3 @@ -2295,6 +2300,7 @@ LimitMax = 500000 LimitMultiplier = '1' LimitTransfer = 21000 EstimateGasLimit = false +EstimatedGasBuffer = '1.25' BumpMin = '5 gwei' BumpPercent = 20 BumpThreshold = 3 @@ -2390,6 +2396,7 @@ LimitMax = 500000 LimitMultiplier = '1' LimitTransfer = 21000 EstimateGasLimit = false +EstimatedGasBuffer = '1.25' BumpMin = '5 gwei' BumpPercent = 20 BumpThreshold = 3 @@ -2486,6 +2493,7 @@ LimitMax = 500000 LimitMultiplier = '1' LimitTransfer = 21000 EstimateGasLimit = false +EstimatedGasBuffer = '1.25' BumpMin = '5 gwei' BumpPercent = 20 BumpThreshold = 3 @@ -2581,6 +2589,7 @@ LimitMax = 500000 LimitMultiplier = '1' LimitTransfer = 21000 EstimateGasLimit = false +EstimatedGasBuffer = '1.25' BumpMin = '5 gwei' BumpPercent = 20 BumpThreshold = 5 @@ -2675,6 +2684,7 @@ LimitMax = 500000 LimitMultiplier = '1' LimitTransfer = 21000 EstimateGasLimit = false +EstimatedGasBuffer = '1.25' BumpMin = '5 gwei' BumpPercent = 20 BumpThreshold = 3 @@ -2769,6 +2779,7 @@ LimitMax = 500000 LimitMultiplier = '1' LimitTransfer = 21000 EstimateGasLimit = false +EstimatedGasBuffer = '1.25' BumpMin = '5 gwei' BumpPercent = 20 BumpThreshold = 3 @@ -2864,6 +2875,7 @@ LimitMax = 500000 LimitMultiplier = '1' LimitTransfer = 21000 EstimateGasLimit = false +EstimatedGasBuffer = '1.25' BumpMin = '5 gwei' BumpPercent = 20 BumpThreshold = 5 @@ -2960,6 +2972,7 @@ LimitMax = 500000 LimitMultiplier = '1' LimitTransfer = 21000 EstimateGasLimit = false +EstimatedGasBuffer = '1.25' BumpMin = '5 gwei' BumpPercent = 20 BumpThreshold = 3 @@ -3055,6 +3068,7 @@ LimitMax = 500000 LimitMultiplier = '1' LimitTransfer = 21000 EstimateGasLimit = false +EstimatedGasBuffer = '1.25' BumpMin = '5 gwei' BumpPercent = 20 BumpThreshold = 5 @@ -3150,6 +3164,7 @@ LimitMax = 500000 LimitMultiplier = '1' LimitTransfer = 21000 EstimateGasLimit = false +EstimatedGasBuffer = '1.25' BumpMin = '20 gwei' BumpPercent = 20 BumpThreshold = 5 @@ -3245,6 +3260,7 @@ LimitMax = 500000 LimitMultiplier = '1' LimitTransfer = 21000 EstimateGasLimit = false +EstimatedGasBuffer = '1.25' BumpMin = '20 mwei' BumpPercent = 40 BumpThreshold = 3 @@ -3340,6 +3356,7 @@ LimitMax = 500000 LimitMultiplier = '1' LimitTransfer = 21000 EstimateGasLimit = false +EstimatedGasBuffer = '1.25' BumpMin = '100 mwei' BumpPercent = 40 BumpThreshold = 3 @@ -3435,6 +3452,7 @@ LimitMax = 500000 LimitMultiplier = '1' LimitTransfer = 21000 EstimateGasLimit = false +EstimatedGasBuffer = '1.25' BumpMin = '5 gwei' BumpPercent = 20 BumpThreshold = 3 @@ -3530,6 +3548,7 @@ LimitMax = 500000 LimitMultiplier = '1' LimitTransfer = 21000 EstimateGasLimit = false +EstimatedGasBuffer = '1.25' BumpMin = '100 wei' BumpPercent = 20 BumpThreshold = 3 @@ -3625,6 +3644,7 @@ LimitMax = 500000 LimitMultiplier = '1' LimitTransfer = 21000 EstimateGasLimit = false +EstimatedGasBuffer = '1.25' BumpMin = '5 gwei' BumpPercent = 20 BumpThreshold = 3 @@ -3720,6 +3740,7 @@ LimitMax = 500000 LimitMultiplier = '1' LimitTransfer = 21000 EstimateGasLimit = false +EstimatedGasBuffer = '1.25' BumpMin = '5 gwei' BumpPercent = 20 BumpThreshold = 3 @@ -3815,6 +3836,7 @@ LimitMax = 500000 LimitMultiplier = '1' LimitTransfer = 21000 EstimateGasLimit = false +EstimatedGasBuffer = '1.25' BumpMin = '5 gwei' BumpPercent = 20 BumpThreshold = 3 @@ -3911,6 +3933,7 @@ LimitMax = 500000 LimitMultiplier = '1' LimitTransfer = 21000 EstimateGasLimit = false +EstimatedGasBuffer = '1.25' BumpMin = '100 wei' BumpPercent = 20 BumpThreshold = 3 @@ -4006,6 +4029,7 @@ LimitMax = 500000 LimitMultiplier = '1' LimitTransfer = 21000 EstimateGasLimit = false +EstimatedGasBuffer = '1.25' BumpMin = '5 gwei' BumpPercent = 20 BumpThreshold = 3 @@ -4100,6 +4124,7 @@ LimitMax = 500000 LimitMultiplier = '1' LimitTransfer = 21000 EstimateGasLimit = false +EstimatedGasBuffer = '1.25' BumpMin = '5 gwei' BumpPercent = 20 BumpThreshold = 5 @@ -4195,6 +4220,7 @@ LimitMax = 500000 LimitMultiplier = '1' LimitTransfer = 21000 EstimateGasLimit = false +EstimatedGasBuffer = '1.25' BumpMin = '5 gwei' BumpPercent = 20 BumpThreshold = 3 @@ -4290,6 +4316,7 @@ LimitMax = 500000 LimitMultiplier = '1' LimitTransfer = 21000 EstimateGasLimit = false +EstimatedGasBuffer = '1.25' BumpMin = '100 mwei' BumpPercent = 40 BumpThreshold = 3 @@ -4385,6 +4412,7 @@ LimitMax = 500000 LimitMultiplier = '1' LimitTransfer = 21000 EstimateGasLimit = false +EstimatedGasBuffer = '1.25' BumpMin = '5 gwei' BumpPercent = 20 BumpThreshold = 3 @@ -4480,6 +4508,7 @@ LimitMax = 500000 LimitMultiplier = '1' LimitTransfer = 21000 EstimateGasLimit = false +EstimatedGasBuffer = '1.25' BumpMin = '5 gwei' BumpPercent = 20 BumpThreshold = 3 @@ -4574,6 +4603,7 @@ LimitMax = 500000 LimitMultiplier = '1' LimitTransfer = 21000 EstimateGasLimit = false +EstimatedGasBuffer = '1.25' BumpMin = '5 gwei' BumpPercent = 20 BumpThreshold = 0 @@ -4669,6 +4699,7 @@ LimitMax = 500000 LimitMultiplier = '1' LimitTransfer = 21000 EstimateGasLimit = false +EstimatedGasBuffer = '1.25' BumpMin = '20 mwei' BumpPercent = 40 BumpThreshold = 3 @@ -4764,6 +4795,7 @@ LimitMax = 500000 LimitMultiplier = '1' LimitTransfer = 21000 EstimateGasLimit = false +EstimatedGasBuffer = '1.25' BumpMin = '100 wei' BumpPercent = 20 BumpThreshold = 3 @@ -4859,6 +4891,7 @@ LimitMax = 500000 LimitMultiplier = '1' LimitTransfer = 21000 EstimateGasLimit = false +EstimatedGasBuffer = '1.25' BumpMin = '20 mwei' BumpPercent = 40 BumpThreshold = 3 @@ -4954,6 +4987,7 @@ LimitMax = 500000 LimitMultiplier = '1' LimitTransfer = 21000 EstimateGasLimit = false +EstimatedGasBuffer = '1.25' BumpMin = '5 gwei' BumpPercent = 20 BumpThreshold = 3 @@ -5048,6 +5082,7 @@ LimitMax = 500000 LimitMultiplier = '1' LimitTransfer = 21000 EstimateGasLimit = false +EstimatedGasBuffer = '1.25' BumpMin = '5 gwei' BumpPercent = 20 BumpThreshold = 5 @@ -5143,6 +5178,7 @@ LimitMax = 500000 LimitMultiplier = '1' LimitTransfer = 21000 EstimateGasLimit = false +EstimatedGasBuffer = '1.25' BumpMin = '100 wei' BumpPercent = 20 BumpThreshold = 3 @@ -5238,6 +5274,7 @@ LimitMax = 500000 LimitMultiplier = '1' LimitTransfer = 21000 EstimateGasLimit = false +EstimatedGasBuffer = '1.25' BumpMin = '5 gwei' BumpPercent = 20 BumpThreshold = 3 @@ -5334,6 +5371,7 @@ LimitMax = 1000000000 LimitMultiplier = '1' LimitTransfer = 21000 EstimateGasLimit = false +EstimatedGasBuffer = '1.25' BumpMin = '5 gwei' BumpPercent = 20 BumpThreshold = 5 @@ -5430,6 +5468,7 @@ LimitMax = 1000000000 LimitMultiplier = '1' LimitTransfer = 21000 EstimateGasLimit = false +EstimatedGasBuffer = '1.25' BumpMin = '5 gwei' BumpPercent = 20 BumpThreshold = 5 @@ -5526,6 +5565,7 @@ LimitMax = 1000000000 LimitMultiplier = '1' LimitTransfer = 21000 EstimateGasLimit = false +EstimatedGasBuffer = '1.25' BumpMin = '5 gwei' BumpPercent = 20 BumpThreshold = 5 @@ -5621,6 +5661,7 @@ LimitMax = 500000 LimitMultiplier = '1' LimitTransfer = 21000 EstimateGasLimit = false +EstimatedGasBuffer = '1.25' BumpMin = '2 gwei' BumpPercent = 20 BumpThreshold = 3 @@ -5716,6 +5757,7 @@ LimitMax = 500000 LimitMultiplier = '1' LimitTransfer = 21000 EstimateGasLimit = false +EstimatedGasBuffer = '1.25' BumpMin = '5 gwei' BumpPercent = 20 BumpThreshold = 3 @@ -5811,6 +5853,7 @@ LimitMax = 500000 LimitMultiplier = '1' LimitTransfer = 21000 EstimateGasLimit = false +EstimatedGasBuffer = '1.25' BumpMin = '5 gwei' BumpPercent = 20 BumpThreshold = 3 @@ -5906,6 +5949,7 @@ LimitMax = 500000 LimitMultiplier = '1' LimitTransfer = 21000 EstimateGasLimit = false +EstimatedGasBuffer = '1.25' BumpMin = '2 gwei' BumpPercent = 20 BumpThreshold = 3 @@ -6000,6 +6044,7 @@ LimitMax = 500000 LimitMultiplier = '1' LimitTransfer = 21000 EstimateGasLimit = false +EstimatedGasBuffer = '1.25' BumpMin = '5 gwei' BumpPercent = 40 BumpThreshold = 3 @@ -6094,6 +6139,7 @@ LimitMax = 500000 LimitMultiplier = '1' LimitTransfer = 21000 EstimateGasLimit = false +EstimatedGasBuffer = '1.25' BumpMin = '5 gwei' BumpPercent = 20 BumpThreshold = 3 @@ -6188,6 +6234,7 @@ LimitMax = 500000 LimitMultiplier = '1' LimitTransfer = 21000 EstimateGasLimit = false +EstimatedGasBuffer = '1.25' BumpMin = '5 gwei' BumpPercent = 40 BumpThreshold = 3 @@ -6283,6 +6330,7 @@ LimitMax = 500000 LimitMultiplier = '1' LimitTransfer = 21000 EstimateGasLimit = false +EstimatedGasBuffer = '1.25' BumpMin = '5 gwei' BumpPercent = 20 BumpThreshold = 3 @@ -6378,6 +6426,7 @@ LimitMax = 500000 LimitMultiplier = '1' LimitTransfer = 21000 EstimateGasLimit = false +EstimatedGasBuffer = '1.25' BumpMin = '20 gwei' BumpPercent = 20 BumpThreshold = 5 @@ -6472,6 +6521,7 @@ LimitMax = 500000 LimitMultiplier = '1' LimitTransfer = 21000 EstimateGasLimit = false +EstimatedGasBuffer = '1.25' BumpMin = '20 gwei' BumpPercent = 20 BumpThreshold = 5 @@ -6567,6 +6617,7 @@ LimitMax = 500000 LimitMultiplier = '1' LimitTransfer = 21000 EstimateGasLimit = false +EstimatedGasBuffer = '1.25' BumpMin = '100 wei' BumpPercent = 20 BumpThreshold = 3 @@ -6662,6 +6713,7 @@ LimitMax = 500000 LimitMultiplier = '1' LimitTransfer = 21000 EstimateGasLimit = false +EstimatedGasBuffer = '1.25' BumpMin = '100 wei' BumpPercent = 20 BumpThreshold = 3 @@ -6758,6 +6810,7 @@ LimitMax = 1000000000 LimitMultiplier = '1' LimitTransfer = 21000 EstimateGasLimit = false +EstimatedGasBuffer = '1.25' BumpMin = '5 gwei' BumpPercent = 20 BumpThreshold = 5 @@ -6854,6 +6907,7 @@ LimitMax = 1000000000 LimitMultiplier = '1' LimitTransfer = 21000 EstimateGasLimit = false +EstimatedGasBuffer = '1.25' BumpMin = '5 gwei' BumpPercent = 20 BumpThreshold = 5 @@ -6949,6 +7003,7 @@ LimitMax = 1000000000 LimitMultiplier = '1' LimitTransfer = 21000 EstimateGasLimit = false +EstimatedGasBuffer = '1.25' BumpMin = '5 gwei' BumpPercent = 20 BumpThreshold = 5 @@ -7044,6 +7099,7 @@ LimitMax = 500000 LimitMultiplier = '1' LimitTransfer = 21000 EstimateGasLimit = false +EstimatedGasBuffer = '1.25' BumpMin = '1 gwei' BumpPercent = 20 BumpThreshold = 3 @@ -7139,6 +7195,7 @@ LimitMax = 500000 LimitMultiplier = '1' LimitTransfer = 21000 EstimateGasLimit = false +EstimatedGasBuffer = '1.25' BumpMin = '1 gwei' BumpPercent = 20 BumpThreshold = 3 @@ -7234,6 +7291,7 @@ LimitMax = 500000 LimitMultiplier = '1' LimitTransfer = 21000 EstimateGasLimit = false +EstimatedGasBuffer = '1.25' BumpMin = '5 gwei' BumpPercent = 20 BumpThreshold = 3 @@ -7329,6 +7387,7 @@ LimitMax = 500000 LimitMultiplier = '1' LimitTransfer = 21000 EstimateGasLimit = false +EstimatedGasBuffer = '1.25' BumpMin = '100 wei' BumpPercent = 20 BumpThreshold = 3 @@ -7424,6 +7483,7 @@ LimitMax = 500000 LimitMultiplier = '1' LimitTransfer = 21000 EstimateGasLimit = false +EstimatedGasBuffer = '1.25' BumpMin = '5 gwei' BumpPercent = 20 BumpThreshold = 3 @@ -7519,6 +7579,7 @@ LimitMax = 500000 LimitMultiplier = '1' LimitTransfer = 21000 EstimateGasLimit = false +EstimatedGasBuffer = '1.25' BumpMin = '5 gwei' BumpPercent = 20 BumpThreshold = 3 @@ -7881,6 +7942,7 @@ LimitMax = 500_000 # Default LimitMultiplier = '1.0' # Default LimitTransfer = 21_000 # Default EstimateGasLimit = false # Default +EstimatedGasBuffer = 1.25 # Default BumpMin = '5 gwei' # Default BumpPercent = 20 # Default BumpThreshold = 3 # Default @@ -7981,6 +8043,12 @@ EstimateGasLimit = false # Default ``` EstimateGasLimit enables estimating gas limits for transactions. This feature respects the gas limit provided during transaction creation as an upper bound. +### EstimatedGasBuffer +```toml +EstimatedGasBuffer = 1.25 # Default +``` +EstimatedGasBuffer is a multiplier applied to estimated gas limits when the EstimateGasLimit feature is enabled. This buffer is added for safety in case the gas used for a transaction changes between estimation and broadcasting. + ### BumpMin ```toml BumpMin = '5 gwei' # Default diff --git a/testdata/scripts/node/validate/disk-based-logging-disabled.txtar b/testdata/scripts/node/validate/disk-based-logging-disabled.txtar index 016d416d5f6..275ea039a2b 100644 --- a/testdata/scripts/node/validate/disk-based-logging-disabled.txtar +++ b/testdata/scripts/node/validate/disk-based-logging-disabled.txtar @@ -360,6 +360,7 @@ LimitMax = 500000 LimitMultiplier = '1' LimitTransfer = 21000 EstimateGasLimit = false +EstimatedGasBuffer = 1.25 BumpMin = '5 gwei' BumpPercent = 20 BumpThreshold = 3 diff --git a/testdata/scripts/node/validate/disk-based-logging-no-dir.txtar b/testdata/scripts/node/validate/disk-based-logging-no-dir.txtar index f8a98b2c49a..e50d523ecde 100644 --- a/testdata/scripts/node/validate/disk-based-logging-no-dir.txtar +++ b/testdata/scripts/node/validate/disk-based-logging-no-dir.txtar @@ -360,6 +360,7 @@ LimitMax = 500000 LimitMultiplier = '1' LimitTransfer = 21000 EstimateGasLimit = false +EstimatedGasBuffer = 1.25 BumpMin = '5 gwei' BumpPercent = 20 BumpThreshold = 3 diff --git a/testdata/scripts/node/validate/disk-based-logging.txtar b/testdata/scripts/node/validate/disk-based-logging.txtar index aef3b106a59..6acca8f1fbc 100644 --- a/testdata/scripts/node/validate/disk-based-logging.txtar +++ b/testdata/scripts/node/validate/disk-based-logging.txtar @@ -360,6 +360,7 @@ LimitMax = 500000 LimitMultiplier = '1' LimitTransfer = 21000 EstimateGasLimit = false +EstimatedGasBuffer = 1.25 BumpMin = '5 gwei' BumpPercent = 20 BumpThreshold = 3 diff --git a/testdata/scripts/node/validate/invalid.txtar b/testdata/scripts/node/validate/invalid.txtar index 2912a803274..d9c38d9f5a3 100644 --- a/testdata/scripts/node/validate/invalid.txtar +++ b/testdata/scripts/node/validate/invalid.txtar @@ -350,6 +350,7 @@ LimitMax = 500000 LimitMultiplier = '1' LimitTransfer = 21000 EstimateGasLimit = false +EstimatedGasBuffer = 1.25 BumpMin = '5 gwei' BumpPercent = 20 BumpThreshold = 3 diff --git a/testdata/scripts/node/validate/valid.txtar b/testdata/scripts/node/validate/valid.txtar index ce40c91f669..3dff1a403c4 100644 --- a/testdata/scripts/node/validate/valid.txtar +++ b/testdata/scripts/node/validate/valid.txtar @@ -357,6 +357,7 @@ LimitMax = 500000 LimitMultiplier = '1' LimitTransfer = 21000 EstimateGasLimit = false +EstimatedGasBuffer = 1.25 BumpMin = '5 gwei' BumpPercent = 20 BumpThreshold = 3